diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props index 28607e25a4..47c4a21308 100644 --- a/sources/Directory.Packages.props +++ b/sources/Directory.Packages.props @@ -25,6 +25,9 @@ + + + diff --git a/sources/core/Stride.Core.Mathematics/Color4.cs b/sources/core/Stride.Core.Mathematics/Color4.cs index bfdf7acf93..8673a10963 100644 --- a/sources/core/Stride.Core.Mathematics/Color4.cs +++ b/sources/core/Stride.Core.Mathematics/Color4.cs @@ -7,17 +7,17 @@ // ----------------------------------------------------------------------------- /* * Copyright (c) 2007-2011 SlimDX Group -* +* * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: -* +* * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. -* +* * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -34,7 +34,7 @@ namespace Stride.Core.Mathematics; /// -/// Represents a color in the form of rgba. +/// A RGBA color value with 32-bit floating-point precision per channel. /// [DataContract("Color4")] [DataStyle(DataStyle.Compact)] @@ -44,12 +44,17 @@ public struct Color4 : IEquatable, ISpanFormattable /// /// The Black color (0, 0, 0, 1). /// - public static readonly Color4 Black = new(0.0f, 0.0f, 0.0f); + public static readonly Color4 Black = new(red: 0, green: 0, blue: 0); /// /// The White color (1, 1, 1, 1). /// - public static readonly Color4 White = new(1.0f, 1.0f, 1.0f); + public static readonly Color4 White = new(red: 1, green: 1, blue: 1); + + /// + /// The transparent black color (0, 0, 0, 0). + /// + public static readonly Color4 TransparentBlack = default; /// /// The red component of the color. diff --git a/sources/core/Stride.Core/ComponentBase.cs b/sources/core/Stride.Core/ComponentBase.cs index bc9ec4058a..2b74fa5e2a 100644 --- a/sources/core/Stride.Core/ComponentBase.cs +++ b/sources/core/Stride.Core/ComponentBase.cs @@ -4,55 +4,71 @@ namespace Stride.Core; /// -/// Base class for a framework component. +/// Base class for a framework Component. /// +/// +/// +/// A Component is an object that can have a and other meta-information +/// (see ), and that can hold references to other sub-Components that depend on it. +/// +/// +/// Components have reference-counting lifetime management, so calling does +/// not immediately releases its underlying resources, but instead decreases an internal reference count. +/// When that reference count reaches zero, it then calls automatically, where the +/// underlying resources can then be released safely. +/// +/// +/// When is called, not only the resources associated with the Component itself should +/// be released, but it cascades the releasing of resources to its contained sub-Components. +/// +/// +/// +/// +/// [DataContract] public abstract class ComponentBase : DisposeBase, IComponent, ICollectorHolder { private string name; private ObjectCollector collector; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected ComponentBase() - : this(null) - { - } + protected ComponentBase() : this(name: null) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The name attached to this component + /// The name attached to the Component, or to use the type's name. protected ComponentBase(string? name) { collector = new ObjectCollector(); Tags = new PropertyContainer(this); - Name = name ?? GetType().Name; + this.name = name ?? GetType().Name; } + /// - /// Gets the attached properties to this component. + /// The properties attached to the Component. /// [DataMemberIgnore] // Do not try to recreate object (preserve Tags.Owner) public PropertyContainer Tags; /// - /// Gets or sets the name of this component. + /// Gets or sets the name of the Component. /// /// - /// The name. + /// The name that identifies the Component. It can be to denote it has no specific name. /// - [DataMemberIgnore] // By default don't store it, unless derived class are overriding this member + [DataMemberIgnore] // By default, don't store it, unless derived class are overriding this member public virtual string Name { - get - { - return name; - } + get => name; set { - if (value == name) return; + if (value == name) + return; name = value; @@ -60,14 +76,13 @@ public virtual string Name } } - /// - /// Disposes of object resources. - /// + /// protected override void Destroy() { collector.Dispose(); } + /// ObjectCollector ICollectorHolder.Collector { get @@ -78,12 +93,11 @@ ObjectCollector ICollectorHolder.Collector } /// - /// Called when property was changed. + /// Called when the property has changed. /// - protected virtual void OnNameChanged() - { - } + protected virtual void OnNameChanged() { } + /// public override string ToString() { return $"{GetType().Name}: {name}"; diff --git a/sources/core/Stride.Core/ComponentBaseExtensions.cs b/sources/core/Stride.Core/ComponentBaseExtensions.cs index f763115eb7..cd658cedd4 100644 --- a/sources/core/Stride.Core/ComponentBaseExtensions.cs +++ b/sources/core/Stride.Core/ComponentBaseExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; - namespace Stride.Core; /// @@ -11,92 +9,109 @@ namespace Stride.Core; public static class ComponentBaseExtensions { /// - /// Keeps a disposable object alive by adding it to a container. + /// Keeps a disposable object alive by adding it to a container. /// - /// A component - /// The component to keep alive. - /// The container that will keep a reference to the component. - /// The same component instance - public static T? DisposeBy(this T thisArg, ICollectorHolder container) + /// The type of the Component. + /// The Component to keep alive. + /// The container that will keep a reference to the Component. + /// The same Component instance. + public static T? DisposeBy(this T component, ICollectorHolder container) where T : IDisposable { - if (ReferenceEquals(thisArg, null)) + if (component is null) return default; - return container.Collector.Add(thisArg); + + return container.Collector.Add(component); } /// - /// Removes a disposable object that was being kept alive from a container. + /// Removes a disposable object from a container that keeping it alive. /// - /// A component - /// The component to remove. - /// The container that kept a reference to the component. - public static void RemoveDisposeBy(this T thisArg, ICollectorHolder container) + /// The type of the Component. + /// The Component to remove. + /// The container that kept a reference to the Component. + public static void RemoveDisposeBy(this T component, ICollectorHolder container) where T : IDisposable { - if (ReferenceEquals(thisArg, null)) + if (component is null) return; - container.Collector.Remove(thisArg); + + container.Collector.Remove(component); } /// - /// Keeps a referencable object alive by adding it to a container. + /// Keeps a referenceable object alive by adding it to a container. /// - /// A component - /// The component to keep alive. - /// The container that will keep a reference to the component. - /// The same component instance - public static T? ReleaseBy(this T thisArg, ICollectorHolder container) + /// The type of the Component. + /// The Component to keep alive. + /// The container that will keep a reference to the Component. + /// The same Component instance. + public static T? ReleaseBy(this T component, ICollectorHolder container) where T : IReferencable { - if (ReferenceEquals(thisArg, null)) + if (component is null) return default; - return container.Collector.Add(thisArg); + + return container.Collector.Add(component); } /// - /// Removes a referencable object that was being kept alive from a container. + /// Removes a referenceable object from a container that keeping it alive. /// - /// A component - /// The component to remove. - /// The container that kept a reference to the component. - public static void RemoveReleaseBy(this T thisArg, ICollectorHolder container) + /// The type of the Component. + /// The Component to remove. + /// The container that kept a reference to the Component. + public static void RemoveReleaseBy(this T component, ICollectorHolder container) where T : IReferencable { - if (ReferenceEquals(thisArg, null)) + if (component is null) return; - container.Collector.Remove(thisArg); + + container.Collector.Remove(component); } /// - /// Pins this component as a new reference. + /// Pins this component as a new reference. /// - /// A component - /// The component to add a reference to. - /// This component. - /// This method is equivalent to call and return this instance. - public static T? KeepReference(this T thisArg) + /// The type of the Component. + /// The Component to add a reference to. + /// The same Component instance. + /// + /// This method is equivalent to calling and returning the + /// same instance. + /// + public static T? KeepReference(this T component) where T : IReferencable { - if (ReferenceEquals(thisArg, null)) + if (component is null) return default; - thisArg.AddReference(); - return thisArg; + + component.AddReference(); + return component; } /// - /// Pushes a tag to a component and restore it after using it. See remarks for usage. + /// Sets a tag in a Component and removes it after using it. /// - /// - /// The component. - /// The key. - /// The value. - /// PropertyTagRestore<T>. + /// The type of the tag. + /// The Component to set a tag for. + /// The key to identify the tag to set. + /// The value of the tag. + /// + /// A structure that can be used with a clause (or + /// manually disposed) to set a tag, perform some action, and remove the tag once finished. + /// /// - /// This method is used to set save a property value from , set a new value - /// and restore it after. The returned object must be disposed once the original value must be restored. + /// + /// This method is used to set a property value in , perform some actions, and restore + /// the previous value after finishing. + /// + /// + /// The returned object must be disposed once the original value must be restored. It can be done manually + /// (by calling ) or automatically with a clause. + /// /// - public static PropertyTagRestore PushTagAndRestore(this ComponentBase component, PropertyKey key, T value) + public static PropertyTagRestore PushTagAndRestore(this ComponentBase component, PropertyKey key, T value) { // TODO: Not fully satisfied with the name and the extension point (on ComponentBase). We need to review this a bit more var restorer = new PropertyTagRestore(component, key); @@ -105,27 +120,38 @@ public static PropertyTagRestore PushTagAndRestore(this ComponentBase comp } /// - /// Struct PropertyTagRestore + /// A structure returned by that saves the value of a + /// tag property of an object and, once finished, can restore its value to the previous one. /// - /// + /// The type of the tag. public readonly struct PropertyTagRestore : IDisposable { private readonly ComponentBase container; - private readonly PropertyKey key; + private readonly PropertyKey key; - private readonly T previousValue; + private readonly T? previousValue; - public PropertyTagRestore(ComponentBase container, PropertyKey key) + + /// + /// Initializes a new instance of the structure. + /// + /// The Component that contains the tag to set and restore. + /// The key that identifies the tag to set and restore. + /// is . + /// is . + public PropertyTagRestore(ComponentBase container, PropertyKey key) : this() { ArgumentNullException.ThrowIfNull(container); ArgumentNullException.ThrowIfNull(key); + this.container = container; this.key = key; previousValue = container.Tags.Get(key); } + /// public readonly void Dispose() { // Restore the value diff --git a/sources/core/Stride.Core/DisposeBase.cs b/sources/core/Stride.Core/DisposeBase.cs index a1a3488065..2c7e3b7b63 100644 --- a/sources/core/Stride.Core/DisposeBase.cs +++ b/sources/core/Stride.Core/DisposeBase.cs @@ -6,13 +6,32 @@ namespace Stride.Core; /// -/// Base class for a interface. +/// Base class for a interface implementation with reference-counting semantics. /// +/// +/// A class inheriting from is an implementation of +/// where calls to decrements an internal reference count (following also ). +/// +/// Only when that reference count reaches zero, the object should be disposed and the method +/// is invoked. That method can be overriden by derived classes to release dependent resources. +/// +/// [DataContract] public abstract class DisposeBase : IDisposable, IReferencable { - private int counter = 1; + private int refCount = 1; + /// + /// Gets a value indicating whether the object has been disposed. + /// + public bool IsDisposed { get; private set; } + + + /// + /// Decrements the reference count of this object, disposing, releasing, and freeing associated + /// resources when the count reaches zero. + /// + /// public void Dispose() { if (!IsDisposed) @@ -22,27 +41,33 @@ public void Dispose() } /// - /// Has the component been disposed or not yet. + /// Disposes the object's resources. /// - public bool IsDisposed { get; private set; } + /// + /// + /// Override in a derived class to implement disposal logic specific to it. + /// + /// + /// This method is automatically called whenever a call to (or to ) + /// has decreased the internal reference count to zero, meaning no other objects (hopefully) hold a reference to this one + /// and its resources can be safely released. + /// + /// + protected virtual void Destroy() { } - /// - /// Disposes of object resources. - /// - protected virtual void Destroy() - { - } /// - int IReferencable.ReferenceCount => counter; + int IReferencable.ReferenceCount => refCount; /// int IReferencable.AddReference() { OnAddReference(); - var newCounter = Interlocked.Increment(ref counter); - if (newCounter <= 1) throw new InvalidOperationException(FrameworkResources.AddReferenceError); + var newCounter = Interlocked.Increment(ref refCount); + if (newCounter <= 1) + throw new InvalidOperationException(FrameworkResources.AddReferenceError); + return newCounter; } @@ -51,7 +76,7 @@ int IReferencable.Release() { OnReleaseReference(); - var newCounter = Interlocked.Decrement(ref counter); + var newCounter = Interlocked.Decrement(ref refCount); if (newCounter == 0) { Destroy(); @@ -64,11 +89,13 @@ int IReferencable.Release() return newCounter; } - protected virtual void OnAddReference() - { - } + /// + /// Called when a new reference of this object has been counted (via a call to ). + /// + protected virtual void OnAddReference() { } - protected virtual void OnReleaseReference() - { - } + /// + /// Called when a call to has decremented the reference count of this object. + /// + protected virtual void OnReleaseReference() { } } diff --git a/sources/core/Stride.Core/ICollectorHolder.cs b/sources/core/Stride.Core/ICollectorHolder.cs index 229268e03d..b6b17ba371 100644 --- a/sources/core/Stride.Core/ICollectorHolder.cs +++ b/sources/core/Stride.Core/ICollectorHolder.cs @@ -1,16 +1,15 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. namespace Stride.Core; /// -/// Interface ICollectorHolder for an instance that can collect other instance. +/// Interface for objects that can collect other object instances that can be released / disposed in bulk. /// public interface ICollectorHolder { /// - /// Gets the collector. + /// Gets the collector of associated objects that can be released / disposed. /// - /// The collector. ObjectCollector Collector { get; } } diff --git a/sources/core/Stride.Core/IComponent.cs b/sources/core/Stride.Core/IComponent.cs index 6e6e94b24f..87fac22942 100644 --- a/sources/core/Stride.Core/IComponent.cs +++ b/sources/core/Stride.Core/IComponent.cs @@ -4,13 +4,17 @@ namespace Stride.Core; /// -/// Base interface for all components. +/// Base interface for all framework Components. /// +/// +/// A Component is an object that can have an optional , and that +/// has reference-counting lifetime management. +/// +/// public interface IComponent : IReferencable { /// - /// Gets the name of this component. + /// Gets the name of the Component. /// - /// The name. string Name { get; } } diff --git a/sources/core/Stride.Core/IReferencable.cs b/sources/core/Stride.Core/IReferencable.cs index 04b452cb5d..78e4cb75fc 100644 --- a/sources/core/Stride.Core/IReferencable.cs +++ b/sources/core/Stride.Core/IReferencable.cs @@ -4,28 +4,28 @@ namespace Stride.Core; /// -/// Base interface for all referencable objects. +/// Base interface for all objects that use reference-counting lifetime management. /// public interface IReferencable { /// - /// Gets the reference count of this instance. + /// Gets the reference count of this instance. /// - /// - /// The reference count. - /// + /// The reference count. int ReferenceCount { get; } /// - /// Increments the reference count of this instance. + /// Increments the reference count of this instance. /// - /// The method returns the new reference count. + /// The new reference count. int AddReference(); /// - /// Decrements the reference count of this instance. + /// Decrements the reference count of this instance. /// - /// The method returns the new reference count. - /// When the reference count is going to 0, the component should release/dispose dependents objects. + /// The new reference count. + /// + /// When the reference count reaches 0, the component should release / dispose dependent objects. + /// int Release(); } diff --git a/sources/core/Stride.Core/ObjectCollector.cs b/sources/core/Stride.Core/ObjectCollector.cs index 6489bcec0e..4d9e871f44 100644 --- a/sources/core/Stride.Core/ObjectCollector.cs +++ b/sources/core/Stride.Core/ObjectCollector.cs @@ -1,35 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace Stride.Core; /// -/// A struct to dispose , instances and allocated unmanaged memory. +/// A struct to collect objects implementing the , or interfaces, or +/// pointers to memory allocated with , so they can be disposed in bulk at a later time. /// +[DebuggerDisplay("Instances: {Count}")] public struct ObjectCollector : IDisposable { private List disposables; /// - /// Gets the number of elements to dispose. + /// Gets the number of elements collected to be disposed. /// - /// The number of elements to dispose. - public readonly int Count => disposables.Count; + public readonly int Count => disposables?.Count ?? 0; /// - /// Disposes all object collected by this class and clear the list. The collector can still be used for collecting. + /// Disposes all the objects collected by this collector and clears the list. The collector can still be used for collecting. /// - /// - /// To completely dispose this instance and avoid further dispose, use method instead. - /// public readonly void Dispose() { - if (disposables == null) - { + if (disposables is null) return; - } for (var i = disposables.Count - 1; i >= 0; i--) { @@ -40,27 +37,33 @@ public readonly void Dispose() disposables.Clear(); } + /// + /// Ensures this collector is ready to be used for collecting object instances. + /// public void EnsureValid() { disposables ??= []; } /// - /// Adds a object or a allocated using to the list of the objects to dispose. + /// Adds an object implementing the or interfaces, + /// or a to an object allocated using + /// to the list of the objects to dispose. /// - /// To dispose. - /// If objectToDispose argument is not IDisposable or a valid memory pointer allocated by - public T Add(T objectToDispose) - where T : notnull + /// The type of the object to add. + /// The object to add to the collector to be disposed at a later time. + /// + /// does not implement the interface , , + /// and is not a valid memory pointer allocated by . + /// + public T Add(T objectToDispose) where T : notnull { - if (!(objectToDispose is IDisposable || objectToDispose is IntPtr || objectToDispose is IReferencable)) - throw new ArgumentException("Argument must be IDisposable, IReferenceable or IntPtr"); + if (objectToDispose is not (IDisposable or IReferencable or IntPtr)) + throw new ArgumentException("The object must be IDisposable, IReferenceable, or IntPtr", nameof(objectToDispose)); // Check memory alignment if (objectToDispose is IntPtr memoryPtr && !Utilities.IsMemoryAligned(memoryPtr)) - { - throw new ArgumentException("Memory pointer is invalid. Memory must have been allocated with Utilties.AllocateMemory"); - } + throw new ArgumentException("The memory pointer is invalid. Memory must have been allocated with Utilties.AllocateMemory", nameof(objectToDispose)); EnsureValid(); @@ -71,13 +74,22 @@ public T Add(T objectToDispose) } /// - /// Dispose a disposable object and set the reference to null. Removes this object from this instance.. + /// Removes a disposable object from the list of the objects to dispose. + /// + /// The type of the object to remove. + /// The object to be removed from the list of objects to dispose. + public readonly void Remove(T objectToDispose) where T : notnull + { + disposables?.Remove(objectToDispose); + } + + /// + /// Removes an object from this collector and disposes it immediately, setting the reference to . /// - /// Object to dispose. - public readonly void RemoveAndDispose([MaybeNull] ref T objectToDispose) - where T : notnull + /// The object to remove and dispose. + public readonly void RemoveAndDispose([MaybeNull] ref T objectToDispose) where T : notnull { - if (disposables != null) + if (disposables is not null) { Remove(objectToDispose); DisposeObject(objectToDispose); @@ -86,33 +98,28 @@ public readonly void RemoveAndDispose([MaybeNull] ref T objectToDispose) } /// - /// Removes a disposable object to the list of the objects to dispose. + /// Disposes an object that implements , or , or + /// a pointer to allocated memory. /// - /// - /// To dispose. - public readonly void Remove(T objectToDispose) - where T : notnull - { - disposables?.Remove(objectToDispose); - } - private static void DisposeObject(object objectToDispose) { - if (objectToDispose is IReferencable referenceableObject) + switch (objectToDispose) { - referenceableObject.Release(); - return; - } + case null: + return; - if (objectToDispose is IDisposable disposableObject) - { - disposableObject.Dispose(); - } - else - { - var localData = objectToDispose; - var dataPointer = (IntPtr)localData; - Utilities.FreeMemory(dataPointer); + case IReferencable referenceableObject: + referenceableObject.Release(); + break; + + case IDisposable disposableObject: + disposableObject.Dispose(); + break; + + default: + var dataPointer = (nint) objectToDispose; + Utilities.FreeMemory(dataPointer); + break; } } } diff --git a/sources/core/Stride.Core/PlatformType.cs b/sources/core/Stride.Core/PlatformType.cs index 89ef763ac5..bb4fb061d5 100644 --- a/sources/core/Stride.Core/PlatformType.cs +++ b/sources/core/Stride.Core/PlatformType.cs @@ -1,11 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +#pragma warning disable SA1300 // Element should begin with upper-case letter + namespace Stride.Core; /// -/// Describes the platform operating system. +/// Defines the platform Stride is running on. /// +/// +/// The platform can define the target operating system or environment +/// where the application is running. +/// #if STRIDE_ASSEMBLY_PROCESSOR // To avoid a CS1503 error when compiling projects that are using both the AssemblyProcessor // and Stride.Core. @@ -27,35 +33,32 @@ public enum PlatformType Shared, /// - /// The windows desktop OS. + /// The Windows operating system for desktop applications. /// Windows, /// - /// The android OS. + /// The Android operating system for mobile devices and tablets. /// Android, -#pragma warning disable SA1300 // Element must begin with upper-case letter /// - /// The iOS. + /// The iOS operating system for Apple mobile devices such as iPhone and iPad. /// iOS, -#pragma warning restore SA1300 // Element must begin with upper-case letter /// - /// The Universal Windows Platform (UWP). + /// The Universal Windows Platform (UWP) for applications that run on Windows 10 and later devices, and XBox gaming consoles. /// UWP, /// - /// The Linux OS. + /// The Linux operating system, typically used for servers and desktops. /// Linux, -#pragma warning disable SA1300 // Element must begin with upper-case letter /// - /// macOS + /// The macOS operating system for Apple desktop and laptop computers. /// - macOS, + macOS } diff --git a/sources/core/Stride.Core/Storage/ObjectId.cs b/sources/core/Stride.Core/Storage/ObjectId.cs index 2566da837e..bbc591d2af 100644 --- a/sources/core/Stride.Core/Storage/ObjectId.cs +++ b/sources/core/Stride.Core/Storage/ObjectId.cs @@ -281,6 +281,19 @@ public static ObjectId New() return FromBytes(Guid.NewGuid().ToByteArray()); } + /// + /// Computes a hash from a byte buffer. + /// + /// The byte buffer. + /// The hash of the object. + /// buffer + public static ObjectId FromBytes(ReadOnlySpan buffer) + { + var builder = new ObjectIdBuilder(); + builder.Write(buffer); + return builder.ComputeHash(); + } + /// /// Computes a hash from a byte buffer. /// diff --git a/sources/core/Stride.Core/Unsafe/StringMarshal.cs b/sources/core/Stride.Core/Unsafe/StringMarshal.cs new file mode 100644 index 0000000000..d256b2026b --- /dev/null +++ b/sources/core/Stride.Core/Unsafe/StringMarshal.cs @@ -0,0 +1,250 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +// Contains code from TerraFX Framework, Copyright (c) Tanner Gooding and Contributors +// Licensed under the MIT License (MIT). + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using static Stride.Core.UnsafeExtensions.UnsafeUtilities; + +namespace Stride.Core.UnsafeExtensions; + +/// +/// Provides a set of methods to supplement or replace when operating on s +/// and spans of characters. +/// +public static unsafe class StringMarshal +{ + /// + /// Gets a for a given span of bytes, assuming an UTF-8 encoding. + /// + /// The span for which to create the string. + /// A string created from . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? GetString(this ReadOnlySpan span) + => span.GetPointer() is not null + ? Encoding.UTF8.GetString(span) + : null; + + /// + /// Gets a for a given span, assuming an UTF-16 encoding. + /// + /// The span for which to create the string. + /// A string created from . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string? GetString(this ReadOnlySpan span) + => span.GetPointer() is not null + ? new string(span.As()) + : null; + + /// + /// Gets a null-terminated sequence of ASCII characters for a . + /// + /// The string for which to marshal. + /// + /// A containing a null-terminated ASCII string + /// that is equivalent to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetAsciiSpan(this string source) + { + ReadOnlySpan result; + + if (source is not null) + { + var maxLength = Encoding.ASCII.GetMaxByteCount(source.Length); + var bytes = new byte[maxLength + 1]; + + var length = Encoding.ASCII.GetBytes(source, bytes); + result = bytes.AsSpan(0, length); + } + else + { + result = null; + } + + return result; + } + + /// + /// Gets a span for a null-terminated ASCII character sequence. + /// + /// The pointer to a null-terminated ASCII character sequence. + /// + /// The maxmimum length of or -1 if the maximum length is unknown. + /// + /// + /// A that starts at and extends to + /// or the first null character, whichever comes first. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetAsciiSpan(byte* source, int maxLength = -1) + => GetUtf8Span(source, maxLength); + + /// + /// Gets a span for a null-terminated ASCII character sequence. + /// + /// The reference to a null-terminated ASCII character sequence. + /// + /// The maxmimum length of or -1 if the maximum length is unknown. + /// + /// + /// A that starts at and extends to + /// or the first null character, whichever comes first. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetAsciiSpan(in byte source, int maxLength = -1) + => GetUtf8Span(in source, maxLength); + + /// + /// Gets a null-terminated sequence of UTF-8 characters for a . + /// + /// The string for which to get the null-terminated UTF8 character sequence. + /// + /// A containing a null-terminated UTF-8 string + /// that is equivalent to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf8Span(this string source) + { + ReadOnlySpan result; + + if (source is not null) + { + var maxLength = Encoding.UTF8.GetMaxByteCount(source.Length); + var bytes = new byte[maxLength + 1]; + + var length = Encoding.UTF8.GetBytes(source, bytes); + result = bytes.AsSpan(0, length); + } + else + { + result = null; + } + + return result; + } + + /// + /// Gets a span for a null-terminated UTF-8 character sequence. + /// + /// The pointer to a null-terminated UTF-8 character sequence. + /// + /// The maxmimum length of or -1 if the maximum length is unknown. + /// + /// + /// A that starts at and extends to + /// or the first null character, whichever comes first. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf8Span(byte* source, int maxLength = -1) + => source is not null + ? GetUtf8Span(in source[0], maxLength) + : null; + + /// + /// Gets a span for a null-terminated UTF-8 character sequence. + /// + /// The reference to a null-terminated UTF-8 character sequence. + /// + /// The maxmimum length of or -1 if the maximum length is unknown. + /// + /// + /// A that starts at and extends to + /// or the first null character, whichever comes first. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf8Span(in byte source, int maxLength = -1) + { + ReadOnlySpan result; + + if (!IsNullRef(in source)) + { + if (maxLength < 0) + maxLength = int.MaxValue; + + result = CreateReadOnlySpan(in source, maxLength); + var length = result.IndexOf((byte) '\0'); + + if (length != -1) + { + result = result[..length]; + } + } + else + { + result = null; + } + + return result; + } + + /// + /// Gets a null-terminated sequence of UTF-16 characters for a . + /// + /// The string for which to get the null-terminated UTF-16 character sequence. + /// + /// A containing a null-terminated UTF-16 string + /// that is equivalent to . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf16Span(this string source) + => source.AsSpan().As(); + + /// + /// Gets a span for a null-terminated UTF-16 character sequence. + /// + /// The pointer to a null-terminated UTF-16 string. + /// + /// The maxmimum length of or -1 if the maximum length is unknown. + /// + /// + /// A that starts at and extends to + /// or the first null character, whichever comes first. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf16Span(ushort* source, int maxLength = -1) + => source is null + ? GetUtf16Span(in source[0], maxLength) + : null; + + /// + /// Gets a span for a null-terminated UTF-16 character sequence. + /// + /// The reference to a null-terminated UTF-16 string. + /// + /// The maxmimum length of or -1 if the maximum length is unknown. + /// + /// + /// A that starts at and extends to + /// or the first null character, whichever comes first. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan GetUtf16Span(in ushort source, int maxLength = -1) + { + ReadOnlySpan result; + + if (!IsNullRef(in source)) + { + if (maxLength < 0) + maxLength = int.MaxValue; + + result = CreateReadOnlySpan(in source, maxLength); + var length = result.IndexOf('\0'); + + if (length != -1) + { + result = result[..length]; + } + } + else + { + result = null; + } + + return result; + } +} diff --git a/sources/core/Stride.Core/Unsafe/UnsafeUtilities.cs b/sources/core/Stride.Core/Unsafe/UnsafeUtilities.cs new file mode 100644 index 0000000000..2a31a4a29d --- /dev/null +++ b/sources/core/Stride.Core/Unsafe/UnsafeUtilities.cs @@ -0,0 +1,489 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +// Contains code from TerraFX Framework, Copyright (c) Tanner Gooding and Contributors +// Licensed under the MIT License (MIT). + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using DotNetUnsafe = System.Runtime.CompilerServices.Unsafe; + +namespace Stride.Core.UnsafeExtensions +{ + /// + /// Provides a set of methods to supplement or replace and + /// , mainly for working with s and types. + /// + public static unsafe class UnsafeUtilities + { + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [return: NotNullIfNotNull(nameof(o))] + public static T? As(this object? o) + where T : class? + { + Debug.Assert(o is null or T); + + return DotNetUnsafe.As(o); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo As(this ref TFrom source) + where TFrom : unmanaged + where TTo : unmanaged + => ref DotNetUnsafe.As(ref source); + + /// + /// The span to reinterpret. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span As(this Span span) + where TFrom : unmanaged + where TTo : unmanaged + { + Debug.Assert(SizeOf() == SizeOf()); + + return MemoryMarshal.CreateSpan(ref DotNetUnsafe.As(ref span.GetReference()), span.Length); + } + + /// + /// The span to reinterpret. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan As(this ReadOnlySpan span) + where TFrom : unmanaged + where TTo : unmanaged + { + Debug.Assert(SizeOf() == SizeOf()); + + return MemoryMarshal.CreateReadOnlySpan(in AsReadonly(in span.GetReference()), span.Length); + } + + /// + /// Reinterprets a regular managed object as a read-only span of elements of another type. This can be useful + /// if a managed object represents a "fixed array". + /// + /// A reference to data. + /// A read-only span representing the specified reference as elements of type . + /// + /// This method should be used with caution. Even though the is annotated as , + /// it will be stored into the returned span, and the lifetime of the returned span will not be validated for safety, even by + /// span-aware languages. + /// + public static ReadOnlySpan AsReadOnlySpan(this scoped ref TFrom reference) + where TFrom : unmanaged + where TTo : unmanaged + { + // NOTE: `reference` should be passed as `ref readonly`, but that results in error CS8338. + // It must not be modified by this method however + + Debug.Assert(SizeOf() % SizeOf() == 0); + + ref readonly var referenceAsTTo = ref AsReadonly(in reference); + uint elementCount = SizeOf() / SizeOf(); + return CreateReadOnlySpan(in referenceAsTTo, (int) elementCount); + } + + /// + /// Reinterprets a regular managed object as an array of elements of another type and returns a read-only span over + /// a portion of that array. This can be useful if a managed object represents a "fixed array". + /// This is dangerous because the is not checked. + /// + /// A reference to data. + /// The number of elements the memory contains. + /// + /// A read-only span representing the specified reference as elements of type . + /// + /// + /// This method should be used with caution. It is dangerous because the argument is not checked. + /// Even though the is annotated as , it will be stored into the returned span, + /// and the lifetime of the returned span will not be validated for safety, even by span-aware languages. + /// + public static ReadOnlySpan AsReadOnlySpan(this scoped ref TFrom reference, int elementCount) + where TFrom : unmanaged + where TTo : unmanaged + { + // NOTE: `reference` should be passed as `ref readonly`, but that results in error CS8338. + // It must not be modified by this method however + + Debug.Assert((SizeOf() * elementCount) <= SizeOf()); + + ref readonly var referenceAsTTo = ref AsReadonly(in reference); + return CreateReadOnlySpan(in referenceAsTTo, elementCount); + } + + /// + /// Reinterprets a regular managed object as a span of elements of another type. This can be useful + /// if a managed object represents a "fixed array". + /// + /// A reference to data. + /// A span representing the specified reference as elements of type . + /// + /// This method should be used with caution. Even though the is annotated as , + /// it will be stored into the returned span, and the lifetime of the returned span will not be validated for safety, even by + /// span-aware languages. + /// + public static Span AsSpan(this scoped ref TFrom reference) + where TFrom : unmanaged + where TTo : unmanaged + { + Debug.Assert(SizeOf() % SizeOf() == 0); + + ref var referenceAsTTo = ref AsRef(in reference); + uint elementCount = SizeOf() / SizeOf(); + return CreateSpan(ref referenceAsTTo, (int) elementCount); + } + + /// + /// Reinterprets a regular managed object as an array of elements of another type and returns a span over + /// a portion of that array. This can be useful if a managed object represents a "fixed array". + /// This is dangerous because the is not checked. + /// + /// A reference to data. + /// The number of elements the memory contains. + /// + /// A span representing the specified reference as elements of type . + /// + /// + /// This method should be used with caution. It is dangerous because the argument is not checked. + /// Even though the is annotated as , it will be stored into the returned span, + /// and the lifetime of the returned span will not be validated for safety, even by span-aware languages. + /// + public static Span AsSpan(this scoped ref TFrom reference, int elementCount) + where TFrom : unmanaged + where TTo : unmanaged + { + Debug.Assert((SizeOf() * elementCount) <= SizeOf()); + + ref var referenceAsTTo = ref AsRef(in reference); + return CreateSpan(ref referenceAsTTo, elementCount); + } + + /// + /// Creates a new read-only span over the target array. + /// + /// The array. + /// A read-only span over the provided . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsReadOnlySpan(this T[]? array) => array; + + /// + /// Reinterprets a regular array as a read-only span of elements of another type. This can be useful + /// if the types are the same size and interchangeable. + /// + /// An array of data. + /// + /// A read-only span representing the data of reinterpreted as elements of type . + /// If the array is , returns an empty span. + /// + public static ReadOnlySpan AsReadOnlySpan(this TFrom[]? array) + where TFrom : unmanaged + where TTo : unmanaged + { + Debug.Assert(SizeOf() == SizeOf()); + + if (array is null) + return default; + + ref var refArray0 = ref MemoryMarshal.GetArrayDataReference(array); + return MemoryMarshal.CreateReadOnlySpan(in AsReadonly(in refArray0), array.Length); + } + + /// + /// Reinterprets a regular array as a span of elements of another type. This can be useful + /// if the types are the same size and interchangeable. + /// + /// An array of data. + /// + /// A span representing the data of reinterpreted as elements of type . + /// If the array is , returns an empty span. + /// + public static Span AsSpan(this TFrom[]? array) + where TFrom : unmanaged + where TTo : unmanaged + { + Debug.Assert(SizeOf() == SizeOf()); + + if (array is null) + return default; + + ref var refArray0 = ref MemoryMarshal.GetArrayDataReference(array); + return MemoryMarshal.CreateSpan(ref As(ref refArray0), array.Length); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* AsPointer(this ref T value) where T : unmanaged + => (T*) DotNetUnsafe.AsPointer(ref value); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly TTo AsReadonly(ref readonly TFrom source) + => ref DotNetUnsafe.As(ref AsRef(in source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* AsReadonlyPointer(ref readonly T value) where T : unmanaged + => AsPointer(ref AsRef(in value)); + + /// + /// Reinterprets the given native integer as a reference. + /// + /// The type of the reference. + /// The native integer to reinterpret. + /// A reference to a value of type . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(nint source) => ref DotNetUnsafe.AsRef((void*) source); + + /// + /// Reinterprets the given native unsigned integer as a reference. + /// + /// The type of the reference. + /// The native unsigned integer to reinterpret. + /// A reference to a value of type . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(nuint source) => ref DotNetUnsafe.AsRef((void*) source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(scoped ref readonly T source) => ref DotNetUnsafe.AsRef(in source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref TTo AsRef(scoped ref readonly TFrom source) + { + ref var mutable = ref DotNetUnsafe.AsRef(in source); + return ref DotNetUnsafe.As(ref mutable); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T AsRef(void* source) => ref DotNetUnsafe.AsRef(source); + + /// + /// Reinterprets the read-only span as a writeable span. + /// + /// The type of items in . + /// The read-only span to reinterpret. + /// A writeable span that points to the same items as . + public static Span AsSpan(this ReadOnlySpan span) + => MemoryMarshal.CreateSpan(ref DotNetUnsafe.AsRef(in span.GetReference()), span.Length); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsBytes(this ReadOnlySpan span) where T : struct + => MemoryMarshal.AsBytes(span); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span AsBytes(this Span span) where T : struct + => MemoryMarshal.AsBytes(span); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TTo BitCast(this TFrom source) + where TFrom : struct + where TTo : struct + { + return DotNetUnsafe.BitCast(source); + } + + /// + public static Span Cast(this Span span) + where TFrom : struct + where TTo : struct + { + return MemoryMarshal.Cast(span); + } + + /// + public static ReadOnlySpan Cast(this ReadOnlySpan span) + where TFrom : struct + where TTo : struct + { + return MemoryMarshal.Cast(span); + } + + + /// + public static void CopyBlock(ref TDestination destination, ref readonly TSource source, uint byteCount) + { + DotNetUnsafe.CopyBlock(destination: ref DotNetUnsafe.As(ref destination), + source: in AsReadonly(in source), + byteCount); + } + + /// + public static void CopyBlockUnaligned(ref TDestination destination, ref readonly TSource source, uint byteCount) + { + DotNetUnsafe.CopyBlockUnaligned(destination: ref DotNetUnsafe.As(ref destination), + source: in AsReadonly(in source), + byteCount); + } + + + /// + public static Span CreateSpan(scoped ref T reference, int length) + => MemoryMarshal.CreateSpan(ref reference, length); + + /// + public static ReadOnlySpan CreateReadOnlySpan(scoped ref readonly T reference, int length) + => MemoryMarshal.CreateReadOnlySpan(in reference, length); + + + /// + /// Returns a pointer to the element of the span at index zero. + /// + /// The type of items in . + /// The span from which the pointer is retrieved. + /// A pointer to the item at index zero of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* GetPointer(this Span span) where T : unmanaged + => (T*) DotNetUnsafe.AsPointer(ref span.GetReference()); + + /// + /// Returns a pointer to the element of the span at index zero. + /// + /// The type of items in . + /// The span from which the pointer is retrieved. + /// A pointer to the item at index zero of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* GetPointer(this ReadOnlySpan span) where T : unmanaged + => (T*) DotNetUnsafe.AsPointer(ref AsRef(in span.GetReference())); + + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this T[] array) + => ref MemoryMarshal.GetArrayDataReference(array); + + /// + /// + /// Returns a reference to the element at the specified of . + /// If the array is empty, returns a reference to where that element would have been stored. + /// Such a reference may be used for pinning but must never be dereferenced. + /// + /// The index of the element of for which to take a reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this T[] array, int index) + => ref DotNetUnsafe.Add(ref array.GetReference(), index); + + /// + /// + /// Returns a reference to the element at the specified of . + /// If the array is empty, returns a reference to where that element would have been stored. + /// Such a reference may be used for pinning but must never be dereferenced. + /// + /// The index of the element of for which to take a reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this T[] array, nuint index) => ref DotNetUnsafe.Add(ref array.GetReference(), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this Span span) + => ref MemoryMarshal.GetReference(span); + + /// + /// + /// Returns a reference to the element at the specified of . + /// If the span is empty, returns a reference to the location where that element would have been stored. + /// Such a reference may or may not be null. It can be used for pinning but must never be dereferenced. + /// + /// The index of the element of for which to take a reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this Span span, int index) + => ref DotNetUnsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// + /// + /// Returns a reference to the element at the specified of . + /// If the span is empty, returns a reference to the location where that element would have been stored. + /// Such a reference may or may not be null. It can be used for pinning but must never be dereferenced. + /// + /// The index of the element of for which to take a reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetReference(this Span span, nuint index) + => ref DotNetUnsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T GetReference(this ReadOnlySpan span) + => ref MemoryMarshal.GetReference(span); + + /// + /// + /// Returns a reference to the element at the specified of . + /// If the span is empty, returns a reference to the location where that element would have been stored. + /// Such a reference may or may not be null. It can be used for pinning but must never be dereferenced. + /// + /// The index of the element of for which to take a reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T GetReference(this ReadOnlySpan span, int index) + => ref DotNetUnsafe.Add(ref MemoryMarshal.GetReference(span), index); + + /// + /// + /// Returns a reference to the element at the specified of . + /// If the span is empty, returns a reference to the location where that element would have been stored. + /// Such a reference may or may not be null. It can be used for pinning but must never be dereferenced. + /// + /// The index of the element of for which to take a reference. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref readonly T GetReference(this ReadOnlySpan span, nuint index) + => ref DotNetUnsafe.Add(ref MemoryMarshal.GetReference(span), index); + + + /// + /// Determines if a given reference to a value of type is not a null reference. + /// + /// The type of the reference. + /// The reference to check. + /// + /// if is not a null reference; + /// otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNotNullRef(ref readonly T source) => !DotNetUnsafe.IsNullRef(in source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullRef(ref readonly T source) => DotNetUnsafe.IsNullRef(in source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T NullRef() => ref DotNetUnsafe.NullRef(); + + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadUnaligned(void* source) where T : unmanaged + => DotNetUnsafe.ReadUnaligned(source); + + /// + /// The offset in bytes from the location pointed to by . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ReadUnaligned(void* source, nuint offset) where T : unmanaged + => DotNetUnsafe.ReadUnaligned((void*) ((nuint) source + offset)); + + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUnaligned(void* destination, T value) where T : unmanaged + => DotNetUnsafe.WriteUnaligned(destination, value); + + /// + /// The offset in bytes from the location pointed to by . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteUnaligned(void* destination, nuint offset, T value) where T : unmanaged + => DotNetUnsafe.WriteUnaligned((void*) ((nuint) destination + offset), value); + + +#pragma warning disable CS8500 + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint SizeOf() => unchecked((uint) sizeof(T)); +#pragma warning restore CS8500 + } +} diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EditorGameLightProbeGizmoService.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EditorGameLightProbeGizmoService.cs index 13b91a184c..f5e95574c4 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EditorGameLightProbeGizmoService.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/EntityHierarchyEditor/Game/EditorGameLightProbeGizmoService.cs @@ -368,9 +368,9 @@ private unsafe Mesh ConvertToMesh(GraphicsDevice graphicsDevice, PrimitiveType p var meshDraw = new MeshDraw { IndexBuffer = new IndexBufferBinding(Buffer.Index.New(graphicsDevice, indices).RecreateWith(indices), true, indices.Length), - VertexBuffers = new[] { new VertexBufferBinding(Buffer.New(graphicsDevice, vertices, BufferFlags.VertexBuffer).RecreateWith(vertices), layout, vertices.Length) }, + VertexBuffers = [ new VertexBufferBinding(Buffer.Vertex.New(graphicsDevice, vertices).RecreateWith(vertices), layout, vertices.Length) ], DrawCount = indices.Length, - PrimitiveType = primitiveType, + PrimitiveType = primitiveType }; wireframeResources.Add(meshDraw.VertexBuffers[0].Buffer); diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs index e44ad50a25..897c444794 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/CameraGizmo.cs @@ -170,7 +170,7 @@ public void Build(CommandList commandList, CameraParameters parameters) public unsafe void RebuildVertexBuffer(CommandList commandList, CameraParameters parameters) { - var mappedVertices = commandList.MapSubresource(vertexBuffer, 0, MapMode.WriteDiscard); + var mappedVertices = commandList.MapSubResource(vertexBuffer, 0, MapMode.WriteDiscard); var vertexPointer = mappedVertices.DataBox.DataPointer; var vertex = (VertexPositionNormalTexture*)vertexPointer; @@ -191,7 +191,7 @@ public unsafe void RebuildVertexBuffer(CommandList commandList, CameraParameters ++vertex; } - commandList.UnmapSubresource(mappedVertices); + commandList.UnmapSubResource(mappedVertices); } } } diff --git a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs index 0c39982c76..e6b788fa67 100644 --- a/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs +++ b/sources/editor/Stride.Assets.Presentation/AssetEditors/Gizmos/LightSpotGizmo.cs @@ -188,7 +188,7 @@ public void Rebuild(CommandList commandList, LightSpot lightSpot) private unsafe void RebuildConeVertexBuffer(CommandList commandList, LightSpot lightSpot) { - var mappedVertices = commandList.MapSubresource(vertexBuffer, 0, MapMode.WriteDiscard); + var mappedVertices = commandList.MapSubResource(vertexBuffer, 0, MapMode.WriteDiscard); var vertexPointer = mappedVertices.DataBox.DataPointer; var vertex = (VertexPositionNormalTexture*)vertexPointer; @@ -208,12 +208,12 @@ private unsafe void RebuildConeVertexBuffer(CommandList commandList, LightSpot l vertex[4 * Tesselation].Position = Vector3.Zero; vertex[4 * Tesselation].Normal = new Vector3(0, 0, -1); - commandList.UnmapSubresource(mappedVertices); + commandList.UnmapSubResource(mappedVertices); } private unsafe void RebuildRectangleVertexBuffer(CommandList commandList, LightSpot lightSpot) { - var mappedVertices = commandList.MapSubresource(vertexBuffer, 0, MapMode.WriteDiscard); + var mappedVertices = commandList.MapSubResource(vertexBuffer, 0, MapMode.WriteDiscard); var vertexPointer = mappedVertices.DataBox.DataPointer; var vertex = (VertexPositionNormalTexture*)vertexPointer; @@ -259,7 +259,7 @@ private unsafe void RebuildRectangleVertexBuffer(CommandList commandList, LightS vertex[8].Position = new Vector3(-projectionPlaneX, projectionPlaneY, -projectionPlaneDistance); vertex[8].Normal = normal; - commandList.UnmapSubresource(mappedVertices); + commandList.UnmapSubResource(mappedVertices); } private static int[] BuildConeIndexBuffer() diff --git a/sources/editor/Stride.Assets.Presentation/Preview/TexturePreview.cs b/sources/editor/Stride.Assets.Presentation/Preview/TexturePreview.cs index 8e2c896750..740590eebf 100644 --- a/sources/editor/Stride.Assets.Presentation/Preview/TexturePreview.cs +++ b/sources/editor/Stride.Assets.Presentation/Preview/TexturePreview.cs @@ -69,7 +69,7 @@ public override IEnumerable GetAvailableMipMaps() if (texture == null) yield break; - for (var i = 1; i < texture.Description.MipLevels; ++i) + for (var i = 1; i < texture.Description.MipLevelCount; ++i) yield return i; } diff --git a/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.LightProbes.cs b/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.LightProbes.cs index 15c1c7e40d..33f1628e05 100644 --- a/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.LightProbes.cs +++ b/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.LightProbes.cs @@ -244,9 +244,9 @@ private unsafe void BakeLightProbes(RenderContext context, RenderDrawContext dra fixed (Vector4* matrices = lightProbesData.Matrices) fixed (Int4* probeIndices = lightProbesData.LightProbeIndices) { - drawContext.CommandList.UpdateSubresource(lightprobesCoefficients, 0, new DataBox((IntPtr)lightProbeCoefficients, 0, 0)); - drawContext.CommandList.UpdateSubresource(tetrahedronProbeIndices, 0, new DataBox((IntPtr)probeIndices, 0, 0)); - drawContext.CommandList.UpdateSubresource(tetrahedronMatrices, 0, new DataBox((IntPtr)matrices, 0, 0)); + drawContext.CommandList.UpdateSubResource(lightprobesCoefficients, 0, new DataBox((IntPtr)lightProbeCoefficients, 0, 0)); + drawContext.CommandList.UpdateSubResource(tetrahedronProbeIndices, 0, new DataBox((IntPtr)probeIndices, 0, 0)); + drawContext.CommandList.UpdateSubResource(tetrahedronMatrices, 0, new DataBox((IntPtr)matrices, 0, 0)); // Find which probe we are currently in // TODO: Optimize (use previous coherency info?) @@ -279,7 +279,7 @@ private unsafe void BakeLightProbes(RenderContext context, RenderDrawContext dra var vertexBuffer = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription((tetraResult.Count * 4 + 3) * LightProbeVertex.Size, BufferFlags.VertexBuffer, GraphicsResourceUsage.Dynamic))); var indexBuffer = PushScopedResource(Context.Allocator.GetTemporaryBuffer(new BufferDescription(tetraResult.Count * 12 * sizeof(uint), BufferFlags.IndexBuffer, GraphicsResourceUsage.Dynamic))); - var mappedVertexBuffer = drawContext.CommandList.MapSubresource(vertexBuffer, 0, MapMode.WriteDiscard); + var mappedVertexBuffer = drawContext.CommandList.MapSubResource(vertexBuffer, 0, MapMode.WriteDiscard); var vertices = (LightProbeVertex*)mappedVertexBuffer.DataBox.DataPointer; // Upload sorted tetrahedron indices for (int i = 0; i < tetraResult.Count; ++i) @@ -296,9 +296,9 @@ private unsafe void BakeLightProbes(RenderContext context, RenderDrawContext dra vertices[tetraResult.Count * 4 + 1] = new LightProbeVertex(new Vector3(3, 1, 0), (uint)tetraInsideIndex); vertices[tetraResult.Count * 4 + 2] = new LightProbeVertex(new Vector3(-1, -3, 0), (uint)tetraInsideIndex); } - drawContext.CommandList.UnmapSubresource(mappedVertexBuffer); + drawContext.CommandList.UnmapSubResource(mappedVertexBuffer); - var mappedIndexBuffer = drawContext.CommandList.MapSubresource(indexBuffer, 0, MapMode.WriteDiscard); + var mappedIndexBuffer = drawContext.CommandList.MapSubResource(indexBuffer, 0, MapMode.WriteDiscard); var indices = (int*)mappedIndexBuffer.DataBox.DataPointer; for (int i = 0; i < tetraResult.Count; ++i) { @@ -318,7 +318,7 @@ private unsafe void BakeLightProbes(RenderContext context, RenderDrawContext dra indices[i * 12 + 10] = i * 4 + 0; indices[i * 12 + 11] = i * 4 + 1; } - drawContext.CommandList.UnmapSubresource(mappedIndexBuffer); + drawContext.CommandList.UnmapSubResource(mappedIndexBuffer); drawContext.CommandList.SetVertexBuffer(0, vertexBuffer, 0, LightProbeVertex.Size); drawContext.CommandList.SetIndexBuffer(indexBuffer, 0, true); diff --git a/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs b/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs index 9be34ed187..f3ea368253 100644 --- a/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs +++ b/sources/engine/Stride.Engine/Rendering/Compositing/ForwardRenderer.cs @@ -115,7 +115,7 @@ public partial class ForwardRenderer : SceneRendererBase, ISharedRenderer /// /// /// This is needed by some effects such as particles soft edges. - /// + /// /// On recent platforms that can bind depth buffer as read-only (), depth buffer will be used as is. Otherwise, a copy will be generated. /// [DefaultValue(true)] @@ -139,7 +139,7 @@ protected override void InitializeCore() actualMultisampleCount = (MultisampleCount)Math.Min((int)actualMultisampleCount, (int)GraphicsDevice.Features[DepthBufferFormat].MultisampleCountMax); // Note: we cannot support MSAA on DX10 now - if (GraphicsDevice.Features.HasMultisampleDepthAsSRV == false && // TODO: Try enabling MSAA on DX9! + if (GraphicsDevice.Features.HasMultiSampleDepthAsSRV == false && // TODO: Try enabling MSAA on DX9! GraphicsDevice.Platform != GraphicsPlatform.OpenGL && GraphicsDevice.Platform != GraphicsPlatform.OpenGLES) { @@ -202,7 +202,7 @@ protected override void InitializeCore() { if (overlay != null && overlay.Texture != null) { - overlay.Overlay = VRSettings.VRDevice.CreateOverlay(overlay.Texture.Width, overlay.Texture.Height, overlay.Texture.MipLevels, (int)overlay.Texture.MultisampleCount); + overlay.Overlay = VRSettings.VRDevice.CreateOverlay(overlay.Texture.Width, overlay.Texture.Height, overlay.Texture.MipLevelCount, (int)overlay.Texture.MultisampleCount); } } } @@ -795,7 +795,8 @@ private Texture ResolveDepthAsSRV(RenderDrawContext context) } } - context.CommandList.SetRenderTargets(null, context.CommandList.RenderTargetCount, context.CommandList.RenderTargets); + context.CommandList.SetRenderTargets(depthStencilView: null, + context.CommandList.RenderTargetCount, context.CommandList.RenderTargets); var depthStencilROCached = context.Resolver.GetDepthStencilAsRenderTarget(depthStencil, this.depthStencilROCached); if (depthStencilROCached != this.depthStencilROCached) diff --git a/sources/engine/Stride.Games/Desktop/WindowsMessageLoop.cs b/sources/engine/Stride.Games/Desktop/WindowsMessageLoop.cs index a8b0d59448..de64ce3466 100644 --- a/sources/engine/Stride.Games/Desktop/WindowsMessageLoop.cs +++ b/sources/engine/Stride.Games/Desktop/WindowsMessageLoop.cs @@ -20,13 +20,12 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + #if (STRIDE_UI_WINFORMS || STRIDE_UI_WPF) + using System; using System.Globalization; using System.Windows.Forms; -#if !STRIDE_GRAPHICS_API_OPENGL && !STRIDE_GRAPHICS_API_VULKAN && !STRIDE_GRAPHICS_API_NULL -using SharpDX.Win32; -#endif using System.Runtime.InteropServices; namespace Stride.Games diff --git a/sources/engine/Stride.Games/GameContext.cs b/sources/engine/Stride.Games/GameContext.cs index 8e2338269e..97c9b31b1f 100644 --- a/sources/engine/Stride.Games/GameContext.cs +++ b/sources/engine/Stride.Games/GameContext.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -30,7 +30,8 @@ namespace Stride.Games { /// - /// Contains context used to render the game (Control for WinForm, a DrawingSurface for WP8...etc.). + /// Contains context used to render the Game (i.e. it abstracts a Windows Forms Control, + /// or a DrawingSurface for UWP, etc.) /// public abstract class GameContext { diff --git a/sources/engine/Stride.Games/GameGraphicsParameters.cs b/sources/engine/Stride.Games/GameGraphicsParameters.cs index 3c89d98b95..97db50fde3 100644 --- a/sources/engine/Stride.Games/GameGraphicsParameters.cs +++ b/sources/engine/Stride.Games/GameGraphicsParameters.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,71 +23,194 @@ using Stride.Graphics; -namespace Stride.Games +namespace Stride.Games; + +/// +/// Describes the preferred graphics configuration for a Game. +/// +public class GameGraphicsParameters { /// - /// Describess how data will be displayed to the screen. + /// The preferred width of the back-buffer, in pixels. /// - public class GameGraphicsParameters - { - /// - /// A value that describes the resolution width. - /// - public int PreferredBackBufferWidth; + /// + /// Both and + /// determine both the screen resolution (if in full-screen mode) or the window size + /// (if in windowed-mode). + /// + public int PreferredBackBufferWidth; - /// - /// A value that describes the resolution height. - /// - public int PreferredBackBufferHeight; + /// + /// The preferred height of the back-buffer, in pixels. + /// + /// + /// Both and + /// determine both the screen resolution (if in full-screen mode) or the window size + /// (if in windowed-mode). + /// + public int PreferredBackBufferHeight; - /// - /// A structure describing the display format. - /// - public PixelFormat PreferredBackBufferFormat; + /// + /// A specifying the display format. + /// + public PixelFormat PreferredBackBufferFormat; - /// - /// Gets or sets the depth stencil format - /// - public PixelFormat PreferredDepthStencilFormat; + /// + /// A specifying the depth-stencil format. + /// + /// + /// + /// The Depth Buffer (also known as Z-buffer) is used to determine the depth of each pixel + /// so close geometry correctly occludes farther geometry. The format determines the precission + /// of the depth buffer. + /// + /// + /// The Stencil Buffer is used to store additional information for each pixel, such as + /// marking or discarding specific pixels for different effects. + /// + /// + /// This format determines both because usually the stencil buffer is a part of the depth buffer + /// reserved for other uses. + /// + /// + /// Some examples are , where the depth buffer uses 24 bits + /// and the stencil buffer uses 8 bits, for a total of 32 bits per pixel, or + /// , which uses 32 bits for the depth buffer and no bits for the stencil buffer. + /// + /// + public PixelFormat PreferredDepthStencilFormat; - /// - /// Gets or sets a value indicating whether the application is in full screen mode. - /// - public bool IsFullScreen; + /// + /// A value indicating whether the application must render in full-screen mode () + /// or inside a window (). + /// + public bool IsFullScreen; - /// - /// The output (monitor) index to use when switching to fullscreen mode. Doesn't have any effect when windowed mode is used. - /// - public int PreferredFullScreenOutputIndex; + /// + /// The index of the output (monitor) to use when rendering in full-screen mode. + /// + /// + /// This output index does not have any effect when windowed mode is used (i.e. when + /// is set to ). + /// + public int PreferredFullScreenOutputIndex; - /// - /// Gets or sets the minimum graphics profile. - /// - public GraphicsProfile[] PreferredGraphicsProfile; + /// + /// An array of s ordered according to preference, where + /// they will be tested in order from the start and the first to succeed will be selected. + /// + /// + /// The Graphics Profile determines which hardware and software characteristics + /// and features are available for rendering, so it directly impacts things as performance, + /// image quality, and graphics effects complexity. Also, higher profiles may enable advanced + /// graphical effects but require modern hardware. + /// + public GraphicsProfile[] PreferredGraphicsProfile; - /// - /// The preferred refresh rate - /// - public Rational PreferredRefreshRate; + /// + /// The preferred refresh rate, in hertz. + /// + /// + /// + /// The Refresh Rate is the number of times per second the screen is refreshed, + /// i.e. the number of frames per second the monitor can display. + /// + /// + /// The value is represented as a , so it can represent both usual integer + /// refresh rates (e.g. 60Hz) and fractional refresh rates (e.g. 59.94Hz). + /// + /// + /// Usually, the refresh rate is only respected when rendering in full-screen mode (i.e. when + /// is set to ). + /// + /// + /// Common refresh rates include 60Hz, 120Hz, and 144Hz, depending on monitor capabilities. + /// + /// + public Rational PreferredRefreshRate; - /// - /// Gets or sets a value indicating the number of sample locations during multisampling. - /// - public MultisampleCount PreferredMultisampleCount; + /// + /// A indicating the number of sample locations during multi-sampling. + /// + /// + /// + /// The multi-sampling is applied to the back-buffer to reduce the aliasing artifacts. This is + /// known as Multi-Sampling Anti-Aliasing (MSAA). + /// + /// + /// The higher the number of samples, the aliasing patterns will be less visible, but it will result + /// in more memory being consumed, and costlier rasterization. + /// + /// + /// If is selected, no multi-sampling will be applied. + /// Common values include (minimal anti-aliasing) and + /// (high-quality anti-aliasing). + /// Higher values increase GPU workload. + /// + /// + public MultisampleCount PreferredMultisampleCount; - /// - /// Gets or sets a value indicating whether to synochrnize present with vertical blanking. - /// - public bool SynchronizeWithVerticalRetrace; + /// + /// A value indicating whether to synchronize the presentation of the rendered frame with + /// the vertical retrace (i.e. ) of the monitor. + /// + /// + /// + /// The synchronization with the vertical refresh, also known as V-Sync, + /// limits the frequency of presenting frames to the screen to . + /// + /// + /// If enabled (), the number of frames produced each second will be + /// limited by the number of times the monitor can refresh its image each second. + /// + /// + /// If disabled (), the number of frames produced each second will be + /// unlimited. However, due to mismatch between frame generation and monitor refresh rate, + /// an artifact known as screen tearing can occur when multiple frames are + /// displayed simultanously. This is especially noticeable at high frame rates. + /// + /// + public bool SynchronizeWithVerticalRetrace; - /// - /// Gets or sets the colorspace. - /// - public ColorSpace ColorSpace; + /// + /// The color space to use for presenting the frame to the screen. + /// + /// + /// + /// The Color Space defines how colors are represented and displayed on the screen. + /// Common values include: + /// + /// + /// + /// + /// + /// It usually represents sRGB, the standard RGB color space used in most monitors and applications. + /// It offers a limited range of colors suitable for general purposes. + /// + /// + /// + /// + /// + /// A linear color space suitable for high-dynamic-range color values like HDR10, supporting a wider + /// range of brightness and colors. This is commonly used in modern HDR displays for enhanced image quality. + /// + /// + /// + /// + /// Choosing the appropriate color space affects the visual quality of your application. + /// For example, sRGB is recommended for compatibility, while HDR10 may enhance visuals in + /// games or applications designed for HDR content. + /// + /// + public ColorSpace ColorSpace; - /// - /// If populated the engine will try to initialize the device with the same unique id - /// - public string RequiredAdapterUid; - } + /// + /// A value indicating whether the application should use a specific adapter + /// (based on its ) for rendering. + /// + /// + /// If is not , the engine will try to + /// initialize a device with the same . + /// + public string? RequiredAdapterUid; // TODO: What happens if invalid or unavailable? } diff --git a/sources/engine/Stride.Games/GamePlatform.cs b/sources/engine/Stride.Games/GamePlatform.cs index 36848ae436..704cef5861 100644 --- a/sources/engine/Stride.Games/GamePlatform.cs +++ b/sources/engine/Stride.Games/GamePlatform.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -121,7 +121,7 @@ public void Run(GameContext gameContext) gameWindow = CreateWindow(gameContext); - // Register on Activated + // Register on Activated gameWindow.Activated += OnActivated; gameWindow.Deactivated += OnDeactivated; gameWindow.InitCallback = OnInitCallback; @@ -266,9 +266,12 @@ public virtual List FindBestDevices(GameGraphicsParam // Iterate on each adapter foreach (var graphicsAdapter in GraphicsAdapterFactory.Adapters) { - if (!string.IsNullOrEmpty(preferredParameters.RequiredAdapterUid) && graphicsAdapter.AdapterUid != preferredParameters.RequiredAdapterUid) continue; + var adapterUid = graphicsAdapter.AdapterUid.ToString(); + + if (!string.IsNullOrEmpty(preferredParameters.RequiredAdapterUid) && adapterUid != preferredParameters.RequiredAdapterUid) + continue; - // Skip adapeters that don't have graphics output + // Skip adapters that don't have graphics output // but only if no RequiredAdapterUid is provided (OculusVR at init time might be in a device with no outputs) if (graphicsAdapter.Outputs.Length == 0 && string.IsNullOrEmpty(preferredParameters.RequiredAdapterUid)) { @@ -350,7 +353,7 @@ public virtual GraphicsDevice CreateDevice(GraphicsDeviceInformation deviceInfor public virtual void RecreateDevice(GraphicsDevice currentDevice, GraphicsDeviceInformation deviceInformation) { currentDevice.ColorSpace = deviceInformation.PresentationParameters.ColorSpace; - currentDevice.Recreate(deviceInformation.Adapter ?? GraphicsAdapterFactory.Default, new[] { deviceInformation.GraphicsProfile }, deviceInformation.DeviceCreationFlags, gameWindow.NativeWindow); + currentDevice.Recreate(deviceInformation.Adapter ?? GraphicsAdapterFactory.DefaultAdapter, new[] { deviceInformation.GraphicsProfile }, deviceInformation.DeviceCreationFlags, gameWindow.NativeWindow); } public virtual void DeviceChanged(GraphicsDevice currentDevice, GraphicsDeviceInformation deviceInformation) diff --git a/sources/engine/Stride.Games/GameSystemBase.cs b/sources/engine/Stride.Games/GameSystemBase.cs index 05b6471383..b14c21c392 100644 --- a/sources/engine/Stride.Games/GameSystemBase.cs +++ b/sources/engine/Stride.Games/GameSystemBase.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,25 +22,35 @@ // THE SOFTWARE. using System; + using Stride.Core; -using Stride.Core.Annotations; using Stride.Core.Serialization.Contents; using Stride.Graphics; namespace Stride.Games { /// - /// Base class for a component. + /// Base class for a Game System. /// /// - /// A component can be used to + /// + /// A Game System is a component that can be used to manage game logic, resources, or other systems within the Game. + /// For example, it can be used to manage audio, input, or other subsystems. + /// + /// + /// Game Systems are typically added to the class and are initialized when the game starts. + /// If the Game System needs to be updated or drawn, it can implement the and + /// interfaces respectively. + /// /// public abstract class GameSystemBase : ComponentBase, IGameSystemBase, IUpdateable, IDrawable, IContentable { private int drawOrder; private bool enabled; + private int updateOrder; private bool visible; + private IGraphicsDeviceService graphicsDeviceService; /// @@ -79,30 +89,35 @@ protected GameSystemBase([NotNull] IServiceRegistry registry) protected IContentManager Content { get; private set; } /// - /// Gets the graphics device. + /// Gets the Graphics Device. /// - /// The graphics device. + /// The Graphics Device. This can be if it has not been initialized yet. protected GraphicsDevice GraphicsDevice => graphicsDeviceService?.GraphicsDevice; #region IDrawable Members + /// public event EventHandler DrawOrderChanged; - + /// public event EventHandler VisibleChanged; + /// public virtual bool BeginDraw() { return true; } + /// public virtual void Draw(GameTime gameTime) { } + /// public virtual void EndDraw() { } + /// public bool Visible { get => visible; @@ -116,6 +131,7 @@ public bool Visible } } + /// public int DrawOrder { get => drawOrder; @@ -137,12 +153,16 @@ public virtual void Initialize() { } - protected void InitGraphicsDeviceService() + /// + /// Initializes the Graphics Device Service if it has not been initialized. + /// + /// + /// Upon returning from this method, the may be available + /// to get the , so that the Game System can use it if it is initialized. + /// + protected void InitializeGraphicsDeviceService() { - if (graphicsDeviceService == null) - { - graphicsDeviceService = Services.GetService(); - } + graphicsDeviceService ??= Services.GetService(); } #endregion @@ -185,11 +205,23 @@ public int UpdateOrder #endregion + /// + /// Method that is called when the property changes. + /// Raises the event. + /// + /// The source of the event. + /// An that contains the event data. protected virtual void OnDrawOrderChanged(object source, EventArgs e) { DrawOrderChanged?.Invoke(source, e); } + /// + /// Method that is called when the property changes. + /// Raises the event. + /// + /// The source of the event. + /// An that contains the event data. private void OnVisibleChanged(EventArgs e) { VisibleChanged?.Invoke(this, e); @@ -211,7 +243,7 @@ void IContentable.LoadContent() { Content = Services.GetService(); - InitGraphicsDeviceService(); + InitializeGraphicsDeviceService(); LoadContent(); } diff --git a/sources/engine/Stride.Games/GameWindowRenderer.cs b/sources/engine/Stride.Games/GameWindowRenderer.cs index 7baa22152f..bfa28facfd 100644 --- a/sources/engine/Stride.Games/GameWindowRenderer.cs +++ b/sources/engine/Stride.Games/GameWindowRenderer.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,6 +22,7 @@ // THE SOFTWARE. using System; + using Stride.Core; using Stride.Core.Mathematics; using Stride.Graphics; @@ -29,62 +30,58 @@ namespace Stride.Games { /// - /// A GameSystem that allows to draw to another window or control. Currently only valid on desktop with Windows.Forms. + /// A Game System that allows to render to a window. /// + /// + /// Note that this Game System can be used only on desktop Windows with Windows Forms currently. + /// public class GameWindowRenderer : GameSystemBase { private PixelFormat preferredBackBufferFormat; private int preferredBackBufferHeight; private int preferredBackBufferWidth; - private PixelFormat preferredDepthStencilFormat; + private ColorSpaceType preferredOutputColorSpace = ColorSpaceType.Rgb_Full_G22_None_P709; + + private GraphicsPresenter savedPresenter; + private bool isBackBufferToResize; - private ColorSpaceType preferredOutputColorSpace; private bool isColorSpaceToChange; - private GraphicsPresenter savedPresenter; private bool beginDrawOk; private bool windowUserResized; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The registry. - /// The window context. - public GameWindowRenderer(IServiceRegistry registry, GameContext gameContext) - : base(registry) + /// The service registry. + /// The Game context that contains information about the underlying platform's native window. + public GameWindowRenderer(IServiceRegistry registry, GameContext gameContext) : base(registry) { GameContext = gameContext; - preferredOutputColorSpace = ColorSpaceType.RgbFullG22NoneP709; } + /// - /// Gets the underlying native window. + /// Gets a context object that contains information about the underlying platform's native window. /// - /// The underlying native window. - public GameContext GameContext { get; private set; } + public GameContext GameContext { get; } /// - /// Gets the window. + /// Gets the window where the Game is rendered. /// - /// The window. public GameWindow Window { get; private set; } /// - /// Gets or sets the presenter. + /// Gets or sets the presenter that is used to render the Game to the window. /// - /// The presenter. public GraphicsPresenter Presenter { get; protected set; } /// - /// Gets or sets the preferred back buffer format. + /// Gets or sets the preferred format for the Back-Buffer. /// - /// The preferred back buffer format. public PixelFormat PreferredBackBufferFormat { - get - { - return preferredBackBufferFormat; - } - + get => preferredBackBufferFormat; set { if (preferredBackBufferFormat != value) @@ -96,17 +93,21 @@ public PixelFormat PreferredBackBufferFormat } /// - /// Gets or sets the preferred presenter output color space. Can be used to render to HDR monitors. Currently only supported by the DirectX backend. - /// See: https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range + /// Gets or sets the preferred output color space the should use. /// - /// The preferred presenter output color space. + /// + /// + /// The output color space can be used to render to HDR monitors. + /// + /// + /// Note that this is currently only supported in Stride when using the Direct3D Graphics API. + /// For more information about High Dynamic Range (HDR) rendering, see + /// . + /// + /// public ColorSpaceType PreferredOutputColorSpace { - get - { - return preferredOutputColorSpace; - } - + get => preferredOutputColorSpace; set { if (preferredOutputColorSpace != value) @@ -118,16 +119,11 @@ public ColorSpaceType PreferredOutputColorSpace } /// - /// Gets or sets the height of the preferred back buffer. + /// Gets or sets the preferred height for the Back-Buffer, in pixels. /// - /// The height of the preferred back buffer. public int PreferredBackBufferHeight { - get - { - return preferredBackBufferHeight; - } - + get => preferredBackBufferHeight; set { if (preferredBackBufferHeight != value) @@ -139,16 +135,11 @@ public int PreferredBackBufferHeight } /// - /// Gets or sets the width of the preferred back buffer. + /// Gets or sets the preferred width for the Back-Buffer, in pixels. /// - /// The width of the preferred back buffer. public int PreferredBackBufferWidth { - get - { - return preferredBackBufferWidth; - } - + get => preferredBackBufferWidth; set { if (preferredBackBufferWidth != value) @@ -160,63 +151,91 @@ public int PreferredBackBufferWidth } /// - /// Gets or sets the preferred depth stencil format. + /// Gets or sets the preferred Depth-Stencil format. /// - /// The preferred depth stencil format. - public PixelFormat PreferredDepthStencilFormat - { - get - { - return preferredDepthStencilFormat; - } + public PixelFormat PreferredDepthStencilFormat { get; set; } - set - { - preferredDepthStencilFormat = value; - } - } + /// public override void Initialize() { var gamePlatform = Services.GetService(); + GameContext.RequestedWidth = PreferredBackBufferWidth; GameContext.RequestedHeight = PreferredBackBufferHeight; + Window = gamePlatform.CreateWindow(GameContext); Window.Visible = true; Window.ClientSizeChanged += WindowOnClientSizeChanged; base.Initialize(); + + // + // Handler for the window's client size changed event to track user resizing. + // + void WindowOnClientSizeChanged(object sender, EventArgs eventArgs) + { + windowUserResized = true; + } } + /// protected override void Destroy() { Presenter?.Dispose(); Presenter = null; + Window?.Dispose(); Window = null; base.Destroy(); } - private Vector2 GetRequestedSize(out PixelFormat format) + + /// + /// Determines the requested size for the Back-Buffer based on the current window bounds and user preferences. + /// + /// + /// When this method returns, contains the pixel format to be used for the Back-Buffer. + /// This will be the preferred Back-Buffer format if specified; + /// otherwise, it defaults to . + /// + /// + /// An structure representing the width and height of the requested Back-Buffer size. + /// If the preferred Back-Buffer dimensions are not set or the window has been resized by the user, + /// the current window dimensions are used. + /// + private Int2 GetRequestedSize(out PixelFormat format) { var bounds = Window.ClientBounds; + format = PreferredBackBufferFormat == PixelFormat.None ? PixelFormat.R8G8B8A8_UNorm : PreferredBackBufferFormat; - return new Vector2( + + return new Int2( PreferredBackBufferWidth == 0 || windowUserResized ? bounds.Width : PreferredBackBufferWidth, PreferredBackBufferHeight == 0 || windowUserResized ? bounds.Height : PreferredBackBufferHeight); } + /// + /// Creates a new or updates the existing one for rendering graphics. + /// + /// + /// This method initializes the if it is currently , + /// using the requested size and format. It configures the presentation parameters, + /// including Depth-Stencil format and presentation interval. + /// protected virtual void CreateOrUpdatePresenter() { - if (Presenter == null || isColorSpaceToChange) + if (Presenter is null || isColorSpaceToChange) { - PixelFormat resizeFormat; - var size = GetRequestedSize(out resizeFormat); - var presentationParameters = new PresentationParameters((int)size.X, (int)size.Y, Window.NativeWindow, resizeFormat) { DepthStencilFormat = PreferredDepthStencilFormat }; - presentationParameters.PresentationInterval = PresentInterval.Immediate; - presentationParameters.OutputColorSpace = preferredOutputColorSpace; + var size = GetRequestedSize(out PixelFormat resizeFormat); + var presentationParameters = new PresentationParameters(size.X, size.Y, Window.NativeWindow, resizeFormat) + { + DepthStencilFormat = PreferredDepthStencilFormat, + PresentationInterval = PresentInterval.Immediate, + OutputColorSpace = preferredOutputColorSpace + }; #if STRIDE_GRAPHICS_API_DIRECT3D11 && STRIDE_PLATFORM_UWP if (Game.Context is GameContextUWPCoreWindow context && context.IsWindowsMixedReality) @@ -234,9 +253,10 @@ protected virtual void CreateOrUpdatePresenter() } } + /// public override bool BeginDraw() { - if (GraphicsDevice != null && Window.Visible) + if (GraphicsDevice is not null && Window.Visible) { savedPresenter = GraphicsDevice.Presenter; @@ -244,9 +264,8 @@ public override bool BeginDraw() if (isBackBufferToResize || windowUserResized) { - PixelFormat resizeFormat; - var size = GetRequestedSize(out resizeFormat); - Presenter.Resize((int)size.X, (int)size.Y, resizeFormat); + var size = GetRequestedSize(out PixelFormat resizeFormat); + Presenter.Resize(size.X, size.Y, resizeFormat); isBackBufferToResize = false; windowUserResized = false; @@ -262,20 +281,18 @@ public override bool BeginDraw() return false; } + /// public override void EndDraw() { - if (beginDrawOk && GraphicsDevice != null) + if (beginDrawOk && GraphicsDevice is not null) { try { Presenter.Present(); } - catch (GraphicsException ex) + catch (GraphicsDeviceException ex) when (ex.Status is not GraphicsDeviceStatus.Removed and not GraphicsDeviceStatus.Reset) { - if (ex.Status != GraphicsDeviceStatus.Removed && ex.Status != GraphicsDeviceStatus.Reset) - { - throw; - } + throw; } if (savedPresenter != null) @@ -284,10 +301,5 @@ public override void EndDraw() } } } - - private void WindowOnClientSizeChanged(object sender, EventArgs eventArgs) - { - windowUserResized = true; - } } } diff --git a/sources/engine/Stride.Games/GraphicsDeviceInformation.cs b/sources/engine/Stride.Games/GraphicsDeviceInformation.cs index 6187ba6912..254465285e 100644 --- a/sources/engine/Stride.Games/GraphicsDeviceInformation.cs +++ b/sources/engine/Stride.Games/GraphicsDeviceInformation.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -25,146 +25,95 @@ using Stride.Graphics; -namespace Stride.Games +namespace Stride.Games; + +/// +/// Contains information needed to create a with a specific +/// configuration. +/// +public class GraphicsDeviceInformation : IEquatable { - public class GraphicsDeviceInformation : IEquatable + /// + /// Gets or sets the Graphics Adapter that will do the rendering. + /// + public GraphicsAdapter Adapter { get; set; } + + /// + /// Gets or sets the graphics profile to aim for, which determines the hardware and software + /// features that are considered minimum for the Graphics Device. + /// + public GraphicsProfile GraphicsProfile { get; set; } + + /// + /// Gets or sets the presentation parameters, which determine how the Graphics Device + /// will present the rendered frame to the screen. + /// + public PresentationParameters PresentationParameters { get; set; } + + /// + /// Gets or sets the creation flags. + /// + /// + /// A combination of flags specifying which features and modes to request from + /// the created Graphics Device. + /// + public DeviceCreationFlags DeviceCreationFlags { get; set; } + + + /// + /// Initializes a new instance of the class. + /// + public GraphicsDeviceInformation() + { + Adapter = GraphicsAdapterFactory.DefaultAdapter; + PresentationParameters = new PresentationParameters(); + } + + + /// + public bool Equals(GraphicsDeviceInformation? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + + return Equals(Adapter, other.Adapter) + && GraphicsProfile == other.GraphicsProfile + && Equals(PresentationParameters, other.PresentationParameters); + } + + /// + public override bool Equals(object? obj) + { + // TODO: Can GraphicsDeviceInformation be sealed? (No GetType()) + return obj?.GetType() == GetType() && Equals((GraphicsDeviceInformation) obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Adapter, GraphicsProfile, PresentationParameters); + } + + public static bool operator ==(GraphicsDeviceInformation left, GraphicsDeviceInformation right) + { + return Equals(left, right); + } + + public static bool operator !=(GraphicsDeviceInformation left, GraphicsDeviceInformation right) + { + return !Equals(left, right); + } + + /// + /// Creates a new object that is a copy of the current instance. + /// + /// A new object that is a copy of this instance. + public GraphicsDeviceInformation Clone() { - #region Fields - - private GraphicsAdapter adapter; - - private GraphicsProfile graphicsProfile; - - private PresentationParameters presentationParameters; - - #endregion - - #region Constructors and Destructors - - /// - /// Initializes a new instance of the class. - /// - public GraphicsDeviceInformation() - { - Adapter = GraphicsAdapterFactory.Default; - PresentationParameters = new PresentationParameters(); - } - - #endregion - - #region Public Properties - - /// - /// Gets or sets the adapter. - /// - /// The adapter. - /// if value is null - public GraphicsAdapter Adapter - { - get - { - return adapter; - } - - set - { - adapter = value; - } - } - - /// - /// Gets or sets the graphics profile. - /// - /// The graphics profile. - /// if value is null - public GraphicsProfile GraphicsProfile - { - get - { - return graphicsProfile; - } - - set - { - graphicsProfile = value; - } - } - - /// - /// Gets or sets the presentation parameters. - /// - /// The presentation parameters. - /// if value is null - public PresentationParameters PresentationParameters - { - get - { - return presentationParameters; - } - - set - { - presentationParameters = value; - } - } - - /// - /// Gets or sets the creation flags. - /// - /// The creation flags. - public DeviceCreationFlags DeviceCreationFlags { get; set; } - - #endregion - - #region Public Methods and Operators - - public bool Equals(GraphicsDeviceInformation other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(adapter, other.adapter) && graphicsProfile == other.graphicsProfile && Equals(presentationParameters, other.presentationParameters); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((GraphicsDeviceInformation)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = (adapter != null ? adapter.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ (int)graphicsProfile; - hashCode = (hashCode * 397) ^ (presentationParameters != null ? presentationParameters.GetHashCode() : 0); - return hashCode; - } - } - - public static bool operator ==(GraphicsDeviceInformation left, GraphicsDeviceInformation right) - { - return Equals(left, right); - } - - public static bool operator !=(GraphicsDeviceInformation left, GraphicsDeviceInformation right) - { - return !Equals(left, right); - } - - /// - /// Clones this instance. - /// - /// A new copy-instance of this GraphicsDeviceInformation. - public GraphicsDeviceInformation Clone() - { - var newValue = (GraphicsDeviceInformation)MemberwiseClone(); - newValue.PresentationParameters = PresentationParameters.Clone(); - return newValue; - } - - #endregion + var newValue = (GraphicsDeviceInformation) MemberwiseClone(); + newValue.PresentationParameters = PresentationParameters.Clone(); + return newValue; } } diff --git a/sources/engine/Stride.Games/GraphicsDeviceManager.cs b/sources/engine/Stride.Games/GraphicsDeviceManager.cs index 86cfab0220..3bf1a76a41 100644 --- a/sources/engine/Stride.Games/GraphicsDeviceManager.cs +++ b/sources/engine/Stride.Games/GraphicsDeviceManager.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,192 +24,169 @@ using System; using System.Collections.Generic; using System.Threading; + using Stride.Core; using Stride.Core.Diagnostics; using Stride.Graphics; +using static Stride.Graphics.GraphicsProfile; + namespace Stride.Games { /// - /// Manages the lifecycle. + /// Manages the life-cycle, providing access to it and events that can be + /// subscribed to be notified of when the device is created, reset, or disposed. /// public class GraphicsDeviceManager : ComponentBase, IGraphicsDeviceManager, IGraphicsDeviceService { - #region Fields - /// - /// Default width for the back buffer. + /// Default width for the Back-Buffer. /// public static readonly int DefaultBackBufferWidth = 1280; - /// - /// Default height for the back buffer. + /// Default height for the Back-Buffer. /// public static readonly int DefaultBackBufferHeight = 720; - private readonly object lockDeviceCreation; + private readonly object lockDeviceCreation = new(); - private GameBase game; + private readonly GameBase game; private bool deviceSettingsChanged; private bool isFullScreen; + private bool isReallyFullScreen; - private MultisampleCount preferredMultisampleCount; - - private PixelFormat preferredBackBufferFormat; - - private int preferredBackBufferHeight; - - private int preferredBackBufferWidth; - - private Rational preferredRefreshRate; - - private PixelFormat preferredDepthStencilFormat; - - private DisplayOrientation supportedOrientations; - - private bool synchronizeWithVerticalRetrace; - - private int preferredFullScreenOutputIndex; + // Device settings Default values + private PixelFormat preferredBackBufferFormat = PixelFormat.R8G8B8A8_UNorm; + private int preferredBackBufferWidth = DefaultBackBufferWidth; + private int preferredBackBufferHeight = DefaultBackBufferHeight; + private Rational preferredRefreshRate = 60; + private PixelFormat preferredDepthStencilFormat = PixelFormat.D24_UNorm_S8_UInt; + private MultisampleCount preferredMultisampleCount = MultisampleCount.None; + private ColorSpace preferredColorSpace = ColorSpace.Linear; + private bool synchronizeWithVerticalRetrace = true; + private int preferredFullScreenOutputIndex; // Populated when ranking devices + private DisplayOrientation supportedOrientations; // Populated when ranking devices private bool isChangingDevice; private int resizedBackBufferWidth; - private int resizedBackBufferHeight; - private bool isBackBufferToResize = false; private DisplayOrientation currentWindowOrientation; private bool beginDrawOk; - private IGraphicsDeviceFactory graphicsDeviceFactory; - - private bool isReallyFullScreen; - - private ColorSpace preferredColorSpace; - - #endregion + private readonly IGraphicsDeviceFactory graphicsDeviceFactory; - #region Constructors and Destructors /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The game. - /// The game instance cannot be null. + /// The Game that needs to manage the Graphics Device. + /// The cannot be . + /// + /// Could not get the required service from the 's services. + /// internal GraphicsDeviceManager(GameBase game) { + ArgumentNullException.ThrowIfNull(game); this.game = game; - if (this.game == null) - { - throw new ArgumentNullException("game"); - } lockDeviceCreation = new object(); // Defines all default values - SynchronizeWithVerticalRetrace = true; - PreferredColorSpace = ColorSpace.Linear; - PreferredBackBufferFormat = PixelFormat.R8G8B8A8_UNorm; - PreferredDepthStencilFormat = PixelFormat.D24_UNorm_S8_UInt; - preferredBackBufferWidth = DefaultBackBufferWidth; - preferredBackBufferHeight = DefaultBackBufferHeight; - preferredRefreshRate = new Rational(60, 1); - PreferredMultisampleCount = MultisampleCount.None; - PreferredGraphicsProfile = new[] - { - GraphicsProfile.Level_11_1, - GraphicsProfile.Level_11_0, - GraphicsProfile.Level_10_1, - GraphicsProfile.Level_10_0, - GraphicsProfile.Level_9_3, - GraphicsProfile.Level_9_2, - GraphicsProfile.Level_9_1, - }; + PreferredGraphicsProfile = [ Level_11_1, Level_11_0, Level_10_1, Level_10_0, Level_9_3, Level_9_2, Level_9_1 ]; - graphicsDeviceFactory = game.Services.GetService(); - if (graphicsDeviceFactory == null) - { - throw new InvalidOperationException("IGraphicsDeviceFactory is not registered as a service"); - } + graphicsDeviceFactory = game.Services.GetService() + ?? throw new InvalidOperationException("IGraphicsDeviceFactory is not registered as a service"); game.WindowCreated += GameOnWindowCreated; } - private void GameOnWindowCreated(object sender, EventArgs eventArgs) - { - game.Window.ClientSizeChanged += Window_ClientSizeChanged; - game.Window.OrientationChanged += Window_OrientationChanged; - game.Window.FullscreenChanged += Window_FullscreenChanged; - } - - #endregion - - #region Public Events + /// + /// Occurs when a new Graphics Device is successfully created. + /// public event EventHandler DeviceCreated; - + /// + /// Occurs when the Graphics Device is being disposed. + /// public event EventHandler DeviceDisposing; + /// + /// Occurs when the Graphics Device is being reset. + /// public event EventHandler DeviceReset; - + /// + /// Occurs when the Graphics Device is being reset, but before it is actually reset. + /// public event EventHandler DeviceResetting; + /// + /// Occurs when the Graphics Device is being initialized to give a chance to the application at + /// adjusting the final settings for the device creation. + /// public event EventHandler PreparingDeviceSettings; - #endregion + /// + /// Method called when the game window is created. It subscribes to the resizing and orientation events. + /// + private void GameOnWindowCreated(object sender, EventArgs eventArgs) + { + game.Window.ClientSizeChanged += Window_ClientSizeChanged; + game.Window.OrientationChanged += Window_OrientationChanged; + game.Window.FullscreenChanged += Window_FullscreenChanged; + } - #region Public Properties + /// + /// Gets the Graphics Device associated with this manager. + /// public GraphicsDevice GraphicsDevice { get; internal set; } /// - /// Gets or sets the list of graphics profile to select from the best feature to the lower feature. See remarks. + /// Gets or sets the graphics profiles that are going to be tested when initializing the Graphics Device, + /// in order of preference. /// - /// The graphics profile. /// - /// By default, the PreferredGraphicsProfile is set to { , - /// , - /// , - /// , - /// , - /// , - /// } + /// By default, the preferred graphics profiles are, in order of preference, better first: + /// , , , , + /// , , and . /// - public GraphicsProfile[] PreferredGraphicsProfile { get; set; } + public GraphicsProfile[] PreferredGraphicsProfile { get; set; } = [ Level_11_1, Level_11_0, Level_10_1, Level_10_0, Level_9_3, Level_9_2, Level_9_1 ]; /// - /// Gets or sets the shader graphics profile that will be used to compile shaders. See remarks. + /// Gets or sets the Shader graphics profile that will be used to compile shaders. /// - /// The shader graphics profile. - /// If this property is not set, the profile used to compile the shader will be taken from the - /// based on the list provided by + /// + /// If this property is not set, the profile used to compile Shaders will be taken from the Graphics Device. + /// based on the list provided by . + /// public GraphicsProfile? ShaderProfile { get; set; } /// - /// Gets or sets the device creation flags that will be used to create the + /// Gets or sets the device creation flags that will be used to create the Graphics Device. /// - /// The device creation flags. public DeviceCreationFlags DeviceCreationFlags { get; set; } /// - /// If populated the engine will try to initialize the device with the same unique id + /// Gets or sets the unique identifier of the Graphis Adapter that should be used to create the Graphics Device. /// + /// + /// If this property is set to a non- the engine will try to + /// initialize the Graphics Device to present to an adapter with the same unique Id + /// public string RequiredAdapterUid { get; set; } /// - /// Gets or sets the default color space. + /// Gets or sets the preferred color space for the Back-Buffers. /// - /// The default color space. public ColorSpace PreferredColorSpace { - get - { - return preferredColorSpace; - } + get => preferredColorSpace; set { if (preferredColorSpace != value) @@ -221,46 +198,31 @@ public ColorSpace PreferredColorSpace } /// - /// Sets the preferred graphics profile. + /// Gets or sets a value indicating whether the Graphics Device should present in full-screen mode. /// - /// The levels. - /// - public void SetPreferredGraphicsProfile(params GraphicsProfile[] levels) - { - PreferredGraphicsProfile = levels; - } - - /// - /// Gets or sets a value indicating whether this instance is full screen. - /// - /// true if this instance is full screen; otherwise, false. + /// + /// if the device should render is full screen; + /// otherwise, . + /// public bool IsFullScreen { - get - { - return isFullScreen; - } - + get => isFullScreen; set { - if (isFullScreen == value) return; - - isFullScreen = value; - deviceSettingsChanged = true; + if (isFullScreen != value) + { + isFullScreen = value; + deviceSettingsChanged = true; + } } } /// - /// Gets or sets a value indicating whether [prefer multi sampling]. + /// Gets or sets the level of multisampling for the Back-Buffers. /// - /// true if [prefer multi sampling]; otherwise, false. public MultisampleCount PreferredMultisampleCount { - get - { - return preferredMultisampleCount; - } - + get => preferredMultisampleCount; set { if (preferredMultisampleCount != value) @@ -272,16 +234,17 @@ public MultisampleCount PreferredMultisampleCount } /// - /// Gets or sets the preferred back buffer format. + /// Gets or sets the preferred pixel format for the Back-Buffers. /// - /// The preferred back buffer format. + /// + /// Not all pixel formats are supported for Back-Buffers by all Graphics Devices. Typical formats + /// are or , + /// although there are some more advanced formats depending on the Graphics Device capabilities + /// and intended use (e.g. for HDR rendering). + /// public PixelFormat PreferredBackBufferFormat { - get - { - return preferredBackBufferFormat; - } - + get => preferredBackBufferFormat; set { if (preferredBackBufferFormat != value) @@ -293,16 +256,11 @@ public PixelFormat PreferredBackBufferFormat } /// - /// Gets or sets the height of the preferred back buffer. + /// Gets or sets the preferred height of the Back-Buffer, in pixels. /// - /// The height of the preferred back buffer. public int PreferredBackBufferHeight { - get - { - return preferredBackBufferHeight; - } - + get => preferredBackBufferHeight; set { if (preferredBackBufferHeight != value) @@ -315,16 +273,11 @@ public int PreferredBackBufferHeight } /// - /// Gets or sets the width of the preferred back buffer. + /// Gets or sets the preferred width of the Back-Buffer, in pixels. /// - /// The width of the preferred back buffer. public int PreferredBackBufferWidth { - get - { - return preferredBackBufferWidth; - } - + get => preferredBackBufferWidth; set { if (preferredBackBufferWidth != value) @@ -337,16 +290,22 @@ public int PreferredBackBufferWidth } /// - /// Gets or sets the preferred depth stencil format. + /// Gets or sets the preferred format for the Depth-Stencil Buffer. /// - /// The preferred depth stencil format. + /// + /// + /// Not all formats are supported for Depth-Stencil Buffers by all Graphics Devices. + /// Typical formats are or . + /// + /// + /// The format also determines the number of bits used for the depth and stencil buffers. For example. + /// the format uses 24 bits for the Depth-Buffer and 8 bits for the Stencil-Buffer, + /// while uses 32 bits for the Depth-Buffer and no Stencil-Buffer. + /// + /// public PixelFormat PreferredDepthStencilFormat { - get - { - return preferredDepthStencilFormat; - } - + get => preferredDepthStencilFormat; set { if (preferredDepthStencilFormat != value) @@ -358,16 +317,16 @@ public PixelFormat PreferredDepthStencilFormat } /// - /// Gets or sets the preferred refresh rate. + /// Gets or sets the preferred refresh rate, in hertz (number of frames per second). /// - /// The preferred refresh rate. + /// + /// The preferred refresh rate as a value, where the numerator is the number of frames per second + /// and the denominator is usually 1 (e.g., 60 frames per second is represented as 60 / 1). However, some adapters + /// may support fractional refresh rates, such as 59.94 Hz, which would be represented as 5994 / 100. + /// public Rational PreferredRefreshRate { - get - { - return preferredRefreshRate; - } - + get => preferredRefreshRate; set { if (preferredRefreshRate != value) @@ -379,15 +338,12 @@ public Rational PreferredRefreshRate } /// - /// The output (monitor) index to use when switching to fullscreen mode. Doesn't have any effect when windowed mode is used. + /// Gets or sets the preferred output (monitor) index to use when switching to fullscreen mode. + /// Doesn't have any effect when windowed mode is used. /// public int PreferredFullScreenOutputIndex { - get - { - return preferredFullScreenOutputIndex; - } - + get => preferredFullScreenOutputIndex; set { if (preferredFullScreenOutputIndex != value) @@ -399,16 +355,11 @@ public int PreferredFullScreenOutputIndex } /// - /// Gets or sets the supported orientations. + /// Gets or sets the supported orientations for displaying the Back-Buffers. /// - /// The supported orientations. public DisplayOrientation SupportedOrientations { - get - { - return supportedOrientations; - } - + get => supportedOrientations; set { if (supportedOrientations != value) @@ -420,15 +371,17 @@ public DisplayOrientation SupportedOrientations } /// - /// Gets or sets a value indicating whether [synchronize with vertical retrace]. + /// Gets or sets a value indicating whether the Graphics Device should synchronize with the vertical retrace, + /// commonly known as VSync. /// - /// true if [synchronize with vertical retrace]; otherwise, false. + /// + /// to synchronize with the vertical retrace, which can help prevent screen tearing + /// and ensure smoother animations by waiting for the monitor to finish displaying the current frame; + /// to not synchronize, which may result in faster frame rates but can lead to screen tearing. + /// public bool SynchronizeWithVerticalRetrace { - get - { - return synchronizeWithVerticalRetrace; - } + get => synchronizeWithVerticalRetrace; set { if (synchronizeWithVerticalRetrace != value) @@ -439,27 +392,30 @@ public bool SynchronizeWithVerticalRetrace } } - #endregion - - #region Public Methods and Operators /// - /// Applies the changes from this instance and change or create the according to the new values. + /// Applies the changes in the graphics settings and changes or recreates the + /// according to the new values. + ///
+ /// Does not have any effect if the is . ///
+ /// + /// Thrown if the Graphics Device could not be created or reconfigured. + /// public void ApplyChanges() { - if (GraphicsDevice != null && deviceSettingsChanged) + if (GraphicsDevice is not null && deviceSettingsChanged) { - ChangeOrCreateDevice(false); + ChangeOrCreateDevice(forceCreate: false); } } + + /// bool IGraphicsDeviceManager.BeginDraw() { - if (GraphicsDevice == null) - { + if (GraphicsDevice is null) return false; - } beginDrawOk = false; @@ -468,107 +424,134 @@ bool IGraphicsDeviceManager.BeginDraw() GraphicsDevice.Begin(); - // TODO GRAPHICS REFACTOR - //// Before drawing, we should clear the state to make sure that there is no unstable graphics device states (On some WP8 devices for example) - //// An application should not rely on previous state (last frame...etc.) after BeginDraw. + // TODO: GRAPHICS REFACTOR + // Before drawing, we should clear the state to make sure that there is no unstable graphics device states (On some WP8 devices for example) + // An application should not rely on previous state (last frame...etc.) after BeginDraw. //GraphicsDevice.ClearState(); // - //// By default, we setup the render target to the back buffer, and the viewport as well. - //if (GraphicsDevice.BackBuffer != null) + // By default, we setup the Render Target to the Back-Buffer, and the Viewport as well. + //if (GraphicsDevice.BackBuffer is not null) //{ // GraphicsDevice.SetDepthAndRenderTarget(GraphicsDevice.DepthStencilBuffer, GraphicsDevice.BackBuffer); //} - beginDrawOk = true; - return beginDrawOk; - } + return beginDrawOk = true; - private bool CheckDeviceState() - { - switch (GraphicsDevice.GraphicsDeviceStatus) + // + // Checks the current state of the Graphics Device and handles any necessary actions. + // + bool CheckDeviceState() { - case GraphicsDeviceStatus.Removed: - Thread.Sleep(TimeSpan.FromMilliseconds(20)); - return false; - case GraphicsDeviceStatus.Reset: - Thread.Sleep(TimeSpan.FromMilliseconds(20)); - try - { - ChangeOrCreateDevice(true); - } - catch (Exception) - { + const int SLEEP_TIME_WHEN_UNAVAILABLE = 20; // milliseconds + + switch (GraphicsDevice.GraphicsDeviceStatus) + { + case GraphicsDeviceStatus.Removed: + Thread.Sleep(SLEEP_TIME_WHEN_UNAVAILABLE); return false; - } - break; - } - return true; + case GraphicsDeviceStatus.Reset: + Thread.Sleep(SLEEP_TIME_WHEN_UNAVAILABLE); + try + { + ChangeOrCreateDevice(forceCreate: true); + } + catch { return false; } // If we fail to reset the device, we return false + break; + } + + return true; + } } + /// void IGraphicsDeviceManager.CreateDevice() { - // Force the creation of the device - ChangeOrCreateDevice(true); + ChangeOrCreateDevice(forceCreate: true); } + /// void IGraphicsDeviceManager.EndDraw(bool present) { - if (beginDrawOk && GraphicsDevice != null) + if (beginDrawOk && GraphicsDevice is not null) { - if (present && GraphicsDevice.Presenter != null) + // If we should present, we need a GraphicsPresenter to call the Present method + if (present && GraphicsDevice.Presenter is not null) { try { GraphicsDevice.Presenter.Present(); } - catch (GraphicsException ex) + catch (GraphicsDeviceException ex) when (ex.Status is not GraphicsDeviceStatus.Removed and not GraphicsDeviceStatus.Reset) { - // If this is not a DeviceRemoved or DeviceReset, than throw an exception - if (ex.Status != GraphicsDeviceStatus.Removed && ex.Status != GraphicsDeviceStatus.Reset) - { - throw; - } + throw; } finally { - beginDrawOk = false; - GraphicsDevice.End(); + EndDraw(); } } else { - beginDrawOk = false; - GraphicsDevice.End(); + EndDraw(); } } + + // + // Ends the current drawing. + // + void EndDraw() + { + beginDrawOk = false; + GraphicsDevice.End(); + } } - #endregion + /// + /// Determines the appropriate display orientation based on the specified parameters. + /// + /// + /// The desired display orientation. If set to , the orientation will be + /// determined based on the provided dimensions and landscape allowance. + /// + /// The width of the display area, in pixels. + /// The height of the display area, in pixels. + /// + /// A value indicating whether both landscape orientations ( and + /// ) are allowed. + /// + /// + /// The selected based on the input parameters. + /// + /// Returns if the height is greater than or equal to the width. + /// If landscape is allowed, returns a combination of and + /// . + /// Otherwise, returns . + /// + /// protected static DisplayOrientation SelectOrientation(DisplayOrientation orientation, int width, int height, bool allowLandscapeLeftAndRight) { if (orientation != DisplayOrientation.Default) { return orientation; } - if (width <= height) { return DisplayOrientation.Portrait; } - if (allowLandscapeLeftAndRight) { return DisplayOrientation.LandscapeRight | DisplayOrientation.LandscapeLeft; } - return DisplayOrientation.LandscapeRight; } + + /// protected override void Destroy() { - if (game != null) + if (game is not null) { if (game.Services.GetService() == this) { @@ -580,16 +563,16 @@ protected override void Destroy() } game.WindowCreated -= GameOnWindowCreated; - if (game.Window != null) + if (game.Window is not null) { game.Window.ClientSizeChanged -= Window_ClientSizeChanged; game.Window.OrientationChanged -= Window_OrientationChanged; } } - if (GraphicsDevice != null) + if (GraphicsDevice is not null) { - if (GraphicsDevice.Presenter != null) + if (GraphicsDevice.Presenter is not null) { GraphicsDevice.Presenter.Dispose(); GraphicsDevice.Presenter = null; @@ -607,52 +590,63 @@ protected override void Destroy() base.Destroy(); } + /// - /// Determines whether this instance is compatible with the the specified new . + /// Determines whether the Graphics Device is compatible with the the specified new + /// and can be reset with it. /// - /// The new device info. - /// true if this instance this instance is compatible with the the specified new ; otherwise, false. + /// The new device information to check compatibility with. + /// + /// if the Graphics Device is compatible with and can be + /// reinitialized with the new settings; + /// otherwise, . + /// protected virtual bool CanResetDevice(GraphicsDeviceInformation newDeviceInfo) { - // By default, a reset is compatible when we stay under the same graphics profile. + // By default, a reset is compatible when we stay under the same graphics profile return GraphicsDevice.Features.RequestedProfile == newDeviceInfo.GraphicsProfile; } /// - /// Finds the best device that is compatible with the preferences defined in this instance. + /// Finds the best Graphics Device configuration that is compatible with the set preferences. /// - /// if set to true a device can be selected from any existing adapters, otherwise, it will select only from default adapter. + /// + /// A value indicating whether to search for any suitable device on any of the available adapters () + /// or only from the default adapter (). + /// /// The graphics device information. - protected virtual GraphicsDeviceInformation FindBestDevice(bool anySuitableDevice) + /// + /// None of the graphics profiles specified in are supported. + /// + /// + /// A compatible screen mode could not be found based on the current settings. Check the full-screen mode, + /// the preferred Back-Buffer width and height, and the preferred Back-Buffer format. + /// + protected virtual GraphicsDeviceInformation FindBestDevice(bool anySuitableDevice) // TODO: anySuitableDevice is not used, remove it? { // Setup preferred parameters before passing them to the factory var preferredParameters = new GameGraphicsParameters - { - PreferredBackBufferWidth = PreferredBackBufferWidth, - PreferredBackBufferHeight = PreferredBackBufferHeight, - PreferredBackBufferFormat = PreferredBackBufferFormat, - PreferredDepthStencilFormat = PreferredDepthStencilFormat, - PreferredRefreshRate = PreferredRefreshRate, - PreferredFullScreenOutputIndex = PreferredFullScreenOutputIndex, - IsFullScreen = IsFullScreen, - PreferredMultisampleCount = PreferredMultisampleCount, - SynchronizeWithVerticalRetrace = SynchronizeWithVerticalRetrace, - PreferredGraphicsProfile = (GraphicsProfile[])PreferredGraphicsProfile.Clone(), - ColorSpace = PreferredColorSpace, - RequiredAdapterUid = RequiredAdapterUid, + { + ColorSpace = PreferredColorSpace, + PreferredBackBufferWidth = PreferredBackBufferWidth, + PreferredBackBufferHeight = PreferredBackBufferHeight, + PreferredBackBufferFormat = PreferredBackBufferFormat, + PreferredDepthStencilFormat = PreferredDepthStencilFormat, + PreferredMultisampleCount = PreferredMultisampleCount, + PreferredRefreshRate = PreferredRefreshRate, + SynchronizeWithVerticalRetrace = SynchronizeWithVerticalRetrace, + PreferredFullScreenOutputIndex = PreferredFullScreenOutputIndex, + RequiredAdapterUid = RequiredAdapterUid, + IsFullScreen = IsFullScreen, + PreferredGraphicsProfile = (GraphicsProfile[]) PreferredGraphicsProfile.Clone() }; - // Remap to Srgb backbuffer if necessary - if (PreferredColorSpace == ColorSpace.Linear) - { - // If the device support SRgb and ColorSpace is linear, we use automatically a SRgb backbuffer - preferredParameters.PreferredBackBufferFormat = preferredParameters.PreferredBackBufferFormat.ToSRgb(); - } - else - { - // If we are looking for gamma and the backbuffer format is SRgb, switch back to non srgb - preferredParameters.PreferredBackBufferFormat = preferredParameters.PreferredBackBufferFormat.ToNonSRgb(); - } + // Remap to sRGB Back-Buffer if necessary + preferredParameters.PreferredBackBufferFormat = PreferredColorSpace is ColorSpace.Linear + // If the device support sRGB and linear color space, we use automatically a sRGB Back-Buffer + ? preferredParameters.PreferredBackBufferFormat.ToSRgb() + // If we are looking for gamma color space and the Back-Buffer format is sRGB, switch back to non-sRGB format + : preferredParameters.PreferredBackBufferFormat.ToNonSRgb(); // Setup resized value if there is a resize pending if (!IsFullScreen && isBackBufferToResize) @@ -665,260 +659,328 @@ protected virtual GraphicsDeviceInformation FindBestDevice(bool anySuitableDevic if (devices.Count == 0) { // Nothing was found; first, let's check if graphics profile was actually supported - // Note: we don't do this preemptively because in some cases it seems to take lot of time (happened on a test machine, several seconds freeze on ID3D11Device.Release()) - GraphicsProfile availableGraphicsProfile; - if (!IsPreferredProfileAvailable(preferredParameters.PreferredGraphicsProfile, out availableGraphicsProfile)) + // NOTE: We don't do this preemptively because in some cases it seems to take lot of time + // (happened on a test machine, several seconds freeze on ID3D11Device.Release()) + if (!IsPreferredProfileAvailable(preferredParameters.PreferredGraphicsProfile, out var availableGraphicsProfile)) { - throw new InvalidOperationException($"Graphics profiles [{string.Join(", ", preferredParameters.PreferredGraphicsProfile)}] are not supported by the device. The highest available profile is [{availableGraphicsProfile}]."); + var notSupportedProfiles = string.Join(", ", preferredParameters.PreferredGraphicsProfile); + throw new InvalidOperationException($"None of the graphics profiles [{notSupportedProfiles}] are supported by the Graphics Device. " + + $"The highest available profile is [{availableGraphicsProfile}]."); } // Otherwise, there was just no screen mode - throw new InvalidOperationException("No screen modes found"); + throw new InvalidOperationException("No compatible screen mode found"); } RankDevices(devices); if (devices.Count == 0) { - throw new InvalidOperationException("No screen modes found after ranking"); + throw new InvalidOperationException("No compatible screen modes found after ranking"); } return devices[0]; } /// - /// Ranks a list of before creating a new device. + /// Ranks a list of s before creating a new Graphics Device. /// - /// The list of devices that can be reorder. + /// A list of possible device configurations to be ranked and reordered. protected virtual void RankDevices(List foundDevices) { // Don't sort if there is a single device (mostly for XAML/WP8) if (foundDevices.Count == 1) - { return; - } - foundDevices.Sort( - (left, right) => - { - var leftParams = left.PresentationParameters; - var rightParams = right.PresentationParameters; + foundDevices.Sort(CompareDeviceInformations); - var leftAdapter = left.Adapter; - var rightAdapter = right.Adapter; + // + // Compares two `GraphicsDeviceInformation` instances to determine their ranking. + // + int CompareDeviceInformations(GraphicsDeviceInformation left, GraphicsDeviceInformation right) + { + var leftParams = left.PresentationParameters; + var rightParams = right.PresentationParameters; - // Sort by GraphicsProfile - if (left.GraphicsProfile != right.GraphicsProfile) - { - return left.GraphicsProfile <= right.GraphicsProfile ? 1 : -1; - } + // Sort by GraphicsProfile + if (left.GraphicsProfile != right.GraphicsProfile) + { + return left.GraphicsProfile <= right.GraphicsProfile ? 1 : -1; + } - // Sort by FullScreen mode - if (leftParams.IsFullScreen != rightParams.IsFullScreen) - { - return IsFullScreen != leftParams.IsFullScreen ? 1 : -1; - } + // Sort by full-screen mode + if (leftParams.IsFullScreen != rightParams.IsFullScreen) + { + return IsFullScreen != leftParams.IsFullScreen ? 1 : -1; + } - // Sort by BackBufferFormat - int leftFormat = CalculateRankForFormat(leftParams.BackBufferFormat); - int rightFormat = CalculateRankForFormat(rightParams.BackBufferFormat); - if (leftFormat != rightFormat) - { - return leftFormat >= rightFormat ? 1 : -1; - } + // Sort by Back-Buffer format + int leftFormat = CalculateRankForFormat(leftParams.BackBufferFormat); + int rightFormat = CalculateRankForFormat(rightParams.BackBufferFormat); + if (leftFormat != rightFormat) + { + return leftFormat >= rightFormat ? 1 : -1; + } - // Sort by MultisampleCount - if (leftParams.MultisampleCount != rightParams.MultisampleCount) - { - return leftParams.MultisampleCount <= rightParams.MultisampleCount ? 1 : -1; - } + // Sort by multisample count + if (leftParams.MultisampleCount != rightParams.MultisampleCount) + { + return leftParams.MultisampleCount <= rightParams.MultisampleCount ? 1 : -1; + } - // Sort by AspectRatio - var targetAspectRatio = (PreferredBackBufferWidth == 0) || (PreferredBackBufferHeight == 0) ? (float)DefaultBackBufferWidth / DefaultBackBufferHeight : (float)PreferredBackBufferWidth / PreferredBackBufferHeight; - var leftDiffRatio = Math.Abs(((float)leftParams.BackBufferWidth / leftParams.BackBufferHeight) - targetAspectRatio); - var rightDiffRatio = Math.Abs(((float)rightParams.BackBufferWidth / rightParams.BackBufferHeight) - targetAspectRatio); - if (Math.Abs(leftDiffRatio - rightDiffRatio) > 0.2f) - { - return leftDiffRatio >= rightDiffRatio ? 1 : -1; - } + // Sort by aspect ratio (width / height) + var targetAspectRatio = (PreferredBackBufferWidth == 0) || (PreferredBackBufferHeight == 0) + ? (float) DefaultBackBufferWidth / DefaultBackBufferHeight + : (float) PreferredBackBufferWidth / PreferredBackBufferHeight; - // Sort by PixelCount - int leftPixelCount; - int rightPixelCount; - if (IsFullScreen) - { - if (((PreferredBackBufferWidth == 0) || (PreferredBackBufferHeight == 0)) && - PreferredFullScreenOutputIndex < leftAdapter.Outputs.Length && - PreferredFullScreenOutputIndex < rightAdapter.Outputs.Length) - { - // assume we got here only adapters that have the needed number of outputs: - var leftOutput = leftAdapter.Outputs[PreferredFullScreenOutputIndex]; - var rightOutput = rightAdapter.Outputs[PreferredFullScreenOutputIndex]; + var leftDiffRatio = Math.Abs(((float) leftParams.BackBufferWidth / leftParams.BackBufferHeight) - targetAspectRatio); + var rightDiffRatio = Math.Abs(((float) rightParams.BackBufferWidth / rightParams.BackBufferHeight) - targetAspectRatio); - leftPixelCount = leftOutput.CurrentDisplayMode.Width * leftOutput.CurrentDisplayMode.Height; - rightPixelCount = rightOutput.CurrentDisplayMode.Width * rightOutput.CurrentDisplayMode.Height; - } - else - { - leftPixelCount = rightPixelCount = PreferredBackBufferWidth * PreferredBackBufferHeight; - } - } - else if ((PreferredBackBufferWidth == 0) || (PreferredBackBufferHeight == 0)) - { - leftPixelCount = rightPixelCount = DefaultBackBufferWidth * DefaultBackBufferHeight; - } - else - { - leftPixelCount = rightPixelCount = PreferredBackBufferWidth * PreferredBackBufferHeight; - } + if (Math.Abs(leftDiffRatio - rightDiffRatio) > 0.2f) + { + return leftDiffRatio >= rightDiffRatio ? 1 : -1; + } - int leftDeltaPixelCount = Math.Abs((leftParams.BackBufferWidth * leftParams.BackBufferHeight) - leftPixelCount); - int rightDeltaPixelCount = Math.Abs((rightParams.BackBufferWidth * rightParams.BackBufferHeight) - rightPixelCount); - if (leftDeltaPixelCount != rightDeltaPixelCount) - { - return leftDeltaPixelCount >= rightDeltaPixelCount ? 1 : -1; - } + // Sort by pixel count + var leftAdapter = left.Adapter; + var rightAdapter = right.Adapter; - // Sort by default Adapter, default adapter first - if (left.Adapter != right.Adapter) - { - if (left.Adapter.IsDefaultAdapter) - { - return -1; - } + var (leftPixelCount, rightPixelCount) = CalculatePixelCount(leftAdapter, rightAdapter); - if (right.Adapter.IsDefaultAdapter) - { - return 1; - } - } + int leftDeltaPixelCount = Math.Abs((leftParams.BackBufferWidth * leftParams.BackBufferHeight) - leftPixelCount); + int rightDeltaPixelCount = Math.Abs((rightParams.BackBufferWidth * rightParams.BackBufferHeight) - rightPixelCount); + if (leftDeltaPixelCount != rightDeltaPixelCount) + { + return leftDeltaPixelCount >= rightDeltaPixelCount ? 1 : -1; + } + + // Sort by Graphics Adapter + if (left.Adapter != right.Adapter) + { + if (left.Adapter.IsDefaultAdapter) return -1; + if (right.Adapter.IsDefaultAdapter) return 1; + } + + return 0; // If all criteria are equal, consider them equal + } + + // + // Calculates the rank for a given pixel format. + // + int CalculateRankForFormat(PixelFormat format) + { + if (format == PreferredBackBufferFormat) + { + return 0; // The preferred format is the best + } + if (CalculateFormatSizeInBits(format) == CalculateFormatSizeInBits(PreferredBackBufferFormat)) + { + return 1; // The format size matches the preferred format size, so it's the second best + } + return int.MaxValue; // All other formats are ranked lower + } + + // + // Calculates the size in bits of a given pixel format. + // + int CalculateFormatSizeInBits(PixelFormat format) + { + return format switch + { + PixelFormat.R16G16B16A16_Float => 64, + + PixelFormat.R8G8B8A8_UNorm or PixelFormat.R8G8B8A8_UNorm_SRgb or + PixelFormat.B8G8R8A8_UNorm or PixelFormat.B8G8R8A8_UNorm_SRgb or + PixelFormat.R10G10B10A2_UNorm => 32, + + PixelFormat.B5G6R5_UNorm or PixelFormat.B5G5R5A1_UNorm => 16, - return 0; - }); + _ => 0, + }; + } + + // + // Calculates the pixel count for the left and right Graphiccs Adapters based on their current display modes. + // + (int leftPixelCount, int rightPixelCount) CalculatePixelCount(GraphicsAdapter leftAdapter, GraphicsAdapter rightAdapter) + { + int leftPixelCount, rightPixelCount; + + if (IsFullScreen) + { + if (((PreferredBackBufferWidth == 0) || (PreferredBackBufferHeight == 0)) && + PreferredFullScreenOutputIndex < leftAdapter.Outputs.Length && + PreferredFullScreenOutputIndex < rightAdapter.Outputs.Length) + { + // Assume we got here only adapters that have the needed number of outputs: + var leftOutput = leftAdapter.Outputs[PreferredFullScreenOutputIndex].CurrentDisplayMode ?? default; + var rightOutput = rightAdapter.Outputs[PreferredFullScreenOutputIndex].CurrentDisplayMode ?? default; + + leftPixelCount = leftOutput.Width * leftOutput.Height; + rightPixelCount = rightOutput.Width * rightOutput.Height; + } + else leftPixelCount = rightPixelCount = PreferredBackBufferWidth * PreferredBackBufferHeight; + } + else if (PreferredBackBufferWidth == 0 || PreferredBackBufferHeight == 0) + { + leftPixelCount = rightPixelCount = DefaultBackBufferWidth * DefaultBackBufferHeight; + } + else leftPixelCount = rightPixelCount = PreferredBackBufferWidth * PreferredBackBufferHeight; + + return (leftPixelCount, rightPixelCount); + } } + /// + /// Determines whether any of the preferred graphics profiles is available on the system. + /// + /// + /// An array of preferred graphics profiles to check for availability. The profiles should be ordered by + /// preference, with the most preferred profile first. + /// + /// + /// When the method returns, contains the highest graphics profile supported by the system that meets or exceeds + /// the preferences specified in . + /// If no preferred profile is available, this will contain the lowest supported graphics profile. + /// + /// + /// if a graphics profile that meets or exceeds one of the preferred profiles is + /// available; + /// otherwise, . + /// protected virtual bool IsPreferredProfileAvailable(GraphicsProfile[] preferredProfiles, out GraphicsProfile availableProfile) { - availableProfile = GraphicsProfile.Level_9_1; + availableProfile = Level_9_1; // Start from the lowest profile - var graphicsProfiles = Enum.GetValues(typeof(GraphicsProfile)); + var graphicsProfiles = Enum.GetValues(); + // Find the highest available profile that is supported by any of the adapters foreach (var graphicsAdapter in GraphicsAdapterFactory.Adapters) { - foreach (var graphicsProfileValue in graphicsProfiles) + foreach (var graphicsProfile in graphicsProfiles) { - var graphicsProfile = (GraphicsProfile)graphicsProfileValue; if (graphicsProfile > availableProfile && graphicsAdapter.IsProfileSupported(graphicsProfile)) availableProfile = graphicsProfile; } } - + // Check if the available profile meets any of the preferred profiles foreach (var preferredProfile in preferredProfiles) { if (availableProfile >= preferredProfile) return true; } - + // No preferred profile is available return false; } - private int CalculateRankForFormat(PixelFormat format) - { - if (format == PreferredBackBufferFormat) - { - return 0; - } - - if (CalculateFormatSize(format) == CalculateFormatSize(PreferredBackBufferFormat)) - { - return 1; - } - - return int.MaxValue; - } - - private int CalculateFormatSize(PixelFormat format) - { - switch (format) - { - case PixelFormat.R16G16B16A16_Float: - return 64; - - case PixelFormat.R8G8B8A8_UNorm: - case PixelFormat.R8G8B8A8_UNorm_SRgb: - case PixelFormat.B8G8R8A8_UNorm: - case PixelFormat.B8G8R8A8_UNorm_SRgb: - case PixelFormat.R10G10B10A2_UNorm: - return 32; - - case PixelFormat.B5G6R5_UNorm: - case PixelFormat.B5G5R5A1_UNorm: - return 16; - } - - return 0; - } + /// + /// Method called when the Graphics Device is created. + /// Invokes the event. + /// protected virtual void OnDeviceCreated(object sender, EventArgs args) { DeviceCreated?.Invoke(sender, args); } + /// + /// Method called when the Graphics Device is about to be disposed. + /// Invokes the event. + /// protected virtual void OnDeviceDisposing(object sender, EventArgs args) { DeviceDisposing?.Invoke(sender, args); } - + + /// + /// Method called when the Graphics Device is reset. + /// Invokes the event. + /// protected virtual void OnDeviceReset(object sender, EventArgs args) { DeviceReset?.Invoke(sender, args); } - + + /// + /// Method called when the Graphics Device is resetting, but before it is actually reset. + /// Invokes the event. + /// protected virtual void OnDeviceResetting(object sender, EventArgs args) { DeviceResetting?.Invoke(sender, args); } - + + /// + /// Method called when the Graphics Device is preparing to be created or reset, so the application can + /// examine or modify the device settings before the device is created or reset. + /// Invokes the event. + /// protected virtual void OnPreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs args) { PreparingDeviceSettings?.Invoke(sender, args); } + + /// + /// Method called when the Window's client size changes, which may require a device reinitialization + /// with a new Back-Buffer size. + /// + /// + /// Thrown if the Graphics Device could not be created or reconfigured. + /// private void Window_ClientSizeChanged(object sender, EventArgs e) { - if (!isChangingDevice && ((game.Window.ClientBounds.Height != 0) || (game.Window.ClientBounds.Width != 0))) + // Ignore changes while we are changing the device + if (isChangingDevice) + return; + + if (game.Window.ClientBounds.Height != 0 || game.Window.ClientBounds.Width != 0) { resizedBackBufferWidth = game.Window.ClientBounds.Width; resizedBackBufferHeight = game.Window.ClientBounds.Height; isBackBufferToResize = true; - if (GraphicsDevice != null) + + if (GraphicsDevice is not null) { - ChangeOrCreateDevice(false); + ChangeOrCreateDevice(forceCreate: false); } } } + /// + /// Method called when the Window's orientation changes, which may require a device reinitialization + /// with a new Back-Buffer size if the orientation allows it. + /// private void Window_OrientationChanged(object sender, EventArgs e) { - if ((!isChangingDevice && ((game.Window.ClientBounds.Height != 0) || (game.Window.ClientBounds.Width != 0))) && (game.Window.CurrentOrientation != currentWindowOrientation)) + // Ignore changes while we are changing the device + if (isChangingDevice) + return; + + if ((game.Window.ClientBounds.Height != 0 || game.Window.ClientBounds.Width != 0) && + game.Window.CurrentOrientation != currentWindowOrientation) { if ((game.Window.ClientBounds.Height > game.Window.ClientBounds.Width && preferredBackBufferWidth > preferredBackBufferHeight) || (game.Window.ClientBounds.Width > game.Window.ClientBounds.Height && preferredBackBufferHeight > preferredBackBufferWidth)) { - //Client size and Back Buffer size are different things - //in this case all we care is if orientation changed, if so we swap width and height - var w = PreferredBackBufferWidth; - PreferredBackBufferWidth = PreferredBackBufferHeight; - PreferredBackBufferHeight = w; + // Client size and Back-Buffer size are different things + // In this case all we care is if orientation changed, if so we swap width and height + (PreferredBackBufferHeight, PreferredBackBufferWidth) = (PreferredBackBufferWidth, PreferredBackBufferHeight); + ApplyChanges(); } } } + /// + /// Method called when the Window's fullscreen state changes, whick may require a resize of the Back-Buffer. + /// private void Window_FullscreenChanged(object sender, EventArgs eventArgs) { if (sender is GameWindow window) { + // The new state is the Window's fullscreen state IsFullScreen = window.IsFullscreen; + if (IsFullScreen) { PreferredBackBufferWidth = window.PreferredFullscreenSize.X; @@ -934,60 +996,50 @@ private void Window_FullscreenChanged(object sender, EventArgs eventArgs) } } - private void CreateDevice(GraphicsDeviceInformation newInfo) - { - newInfo.PresentationParameters.IsFullScreen = isFullScreen; - newInfo.PresentationParameters.PresentationInterval = SynchronizeWithVerticalRetrace ? PresentInterval.One : PresentInterval.Immediate; - newInfo.DeviceCreationFlags = DeviceCreationFlags; - - // this.ValidateGraphicsDeviceInformation(newInfo); - - bool deviceRecreate = GraphicsDevice != null; - - // Notify device is resetting (usually this should result in graphics resources being destroyed) - if (deviceRecreate) - OnDeviceResetting(this, EventArgs.Empty); - - // Create (or recreate) the graphics device - GraphicsDevice = graphicsDeviceFactory.ChangeOrCreateDevice(GraphicsDevice, newInfo); - - // Notify device is reset (usually this should result in graphics resources being recreated/reloaded) - if (deviceRecreate) - OnDeviceReset(this, EventArgs.Empty); - - // Use the shader profile returned by the GraphicsDeviceInformation otherwise use the one coming from the GameSettings - GraphicsDevice.ShaderProfile = ShaderProfile; - - // TODO HANDLE Device Resetting/Reset/Lost - //GraphicsDevice.DeviceResetting += GraphicsDevice_DeviceResetting; - //GraphicsDevice.DeviceReset += GraphicsDevice_DeviceReset; - //GraphicsDevice.DeviceLost += GraphicsDevice_DeviceLost; - if (!deviceRecreate) - GraphicsDevice.Disposing += GraphicsDevice_Disposing; - - OnDeviceCreated(this, EventArgs.Empty); - } - + /// + /// Method called when the Graphics Device is being reset, but before it is actually reset. + /// private void GraphicsDevice_DeviceResetting(object sender, EventArgs e) { - // TODO what to do? + // TODO: What to do? } + /// + /// Method called when the Graphics Device has been reset. + /// private void GraphicsDevice_DeviceReset(object sender, EventArgs e) { - // TODO what to do? + // TODO: What to do? } + /// + /// Method called when the Graphics Device has been lost, meaning it is currently unavailable + /// private void GraphicsDevice_DeviceLost(object sender, EventArgs e) { - // TODO what to do? + // TODO: What to do? } + /// + /// Method called when the Graphics Device is being disposed. + /// private void GraphicsDevice_Disposing(object sender, EventArgs e) { OnDeviceDisposing(sender, e); } + + /// + /// Changes or creates the Graphics Device based on the current settings. + /// + /// + /// A value indicating whether the Graphics Device should be forcibly recreated. + /// If , a new Graphics Device will be created regardless of the current state. + /// If , the method will attempt to reset and reuse the existing device if possible. + /// + /// + /// Thrown if the Graphics Device could not be created or is unexpectedly after the operation. + /// private void ChangeOrCreateDevice(bool forceCreate) { // We make sure that we won't be call by an asynchronous event (windows resized) @@ -995,109 +1047,152 @@ private void ChangeOrCreateDevice(bool forceCreate) { using (Profiler.Begin(GraphicsDeviceManagerProfilingKeys.CreateDevice)) { - game.ConfirmRenderingSettings(GraphicsDevice == null); //if Device is null we assume we are still at game creation phase + ChangeOrCreateDevice(); + } + } + + // + // Changes or creates the Graphics Device based on the current settings. + // + void ChangeOrCreateDevice() + { + game.ConfirmRenderingSettings(GraphicsDevice is null); // If no device we assume we are still at game creation phase - isChangingDevice = true; - var width = game.Window.ClientBounds.Width; - var height = game.Window.ClientBounds.Height; + isChangingDevice = true; + var width = game.Window.ClientBounds.Width; + var height = game.Window.ClientBounds.Height; - //If the orientation is free to be changed from portrait to landscape we actually need this check now, - //it is mostly useful only at initialization actually tho because Window_OrientationChanged does the same logic on runtime change - if (game.Window.CurrentOrientation != currentWindowOrientation) + // If the orientation is free to be changed from portrait to landscape we actually need this check now, + // it is mostly useful only at initialization because `Window_OrientationChanged` does the same logic on runtime change + if (game.Window.CurrentOrientation != currentWindowOrientation) + { + if ((game.Window.ClientBounds.Height > game.Window.ClientBounds.Width && preferredBackBufferWidth > preferredBackBufferHeight) || + (game.Window.ClientBounds.Width > game.Window.ClientBounds.Height && preferredBackBufferHeight > preferredBackBufferWidth)) { - if ((game.Window.ClientBounds.Height > game.Window.ClientBounds.Width && preferredBackBufferWidth > preferredBackBufferHeight) || - (game.Window.ClientBounds.Width > game.Window.ClientBounds.Height && preferredBackBufferHeight > preferredBackBufferWidth)) - { - //Client size and Back Buffer size are different things - //in this case all we care is if orientation changed, if so we swap width and height - var w = preferredBackBufferWidth; - preferredBackBufferWidth = preferredBackBufferHeight; - preferredBackBufferHeight = w; - } + // Client size and Back-Buffer size are different things + // In this case all we care is if orientation changed, if so we swap width and height + (preferredBackBufferHeight, preferredBackBufferWidth) = (preferredBackBufferWidth, preferredBackBufferHeight); } + } - var isBeginScreenDeviceChange = false; - try + var isBeginScreenDeviceChange = false; + try + { + // Notifies the game Window the new orientation + var orientation = SelectOrientation(supportedOrientations, PreferredBackBufferWidth, PreferredBackBufferHeight, allowLandscapeLeftAndRight: true); + game.Window.SetSupportedOrientations(orientation); + + // Find the best device configuration based on the current settings + var graphicsDeviceInformation = FindBestDevice(forceCreate); + // Give a chance to the game to modify the device settings before the device is created or reset + OnPreparingDeviceSettings(this, new PreparingDeviceSettingsEventArgs(graphicsDeviceInformation)); + + isFullScreen = graphicsDeviceInformation.PresentationParameters.IsFullScreen; + game.Window.BeginScreenDeviceChange(graphicsDeviceInformation.PresentationParameters.IsFullScreen); + isBeginScreenDeviceChange = true; + bool needToCreateNewDevice = true; + + // If we are not forced to create a new device and this is already an existing GraphicsDevice + // try to reset and resize it + if (!forceCreate && GraphicsDevice is not null) { - // Notifies the game window for the new orientation - var orientation = SelectOrientation(supportedOrientations, PreferredBackBufferWidth, PreferredBackBufferHeight, true); - game.Window.SetSupportedOrientations(orientation); + if (CanResetDevice(graphicsDeviceInformation)) + { + try + { + GraphicsDevice.ColorSpace = graphicsDeviceInformation.PresentationParameters.ColorSpace; + var newWidth = graphicsDeviceInformation.PresentationParameters.BackBufferWidth; + var newHeight = graphicsDeviceInformation.PresentationParameters.BackBufferHeight; + var newFormat = graphicsDeviceInformation.PresentationParameters.BackBufferFormat; + var newOutputIndex = graphicsDeviceInformation.PresentationParameters.PreferredFullScreenOutputIndex; - var graphicsDeviceInformation = FindBestDevice(forceCreate); + GraphicsDevice.Presenter.Description.PreferredFullScreenOutputIndex = newOutputIndex; + GraphicsDevice.Presenter.Description.RefreshRate = graphicsDeviceInformation.PresentationParameters.RefreshRate; - OnPreparingDeviceSettings(this, new PreparingDeviceSettingsEventArgs(graphicsDeviceInformation)); + GraphicsDevice.Presenter.Resize(newWidth, newHeight, newFormat); - isFullScreen = graphicsDeviceInformation.PresentationParameters.IsFullScreen; - game.Window.BeginScreenDeviceChange(graphicsDeviceInformation.PresentationParameters.IsFullScreen); - isBeginScreenDeviceChange = true; - bool needToCreateNewDevice = true; + // Change full screen if needed + GraphicsDevice.Presenter.IsFullScreen = graphicsDeviceInformation.PresentationParameters.IsFullScreen; - // If we are not forced to create a new device and this is already an existing GraphicsDevice - // try to reset and resize it. - if (!forceCreate && GraphicsDevice != null) - { - if (CanResetDevice(graphicsDeviceInformation)) - { - try - { - GraphicsDevice.ColorSpace = graphicsDeviceInformation.PresentationParameters.ColorSpace; - var newWidth = graphicsDeviceInformation.PresentationParameters.BackBufferWidth; - var newHeight = graphicsDeviceInformation.PresentationParameters.BackBufferHeight; - var newFormat = graphicsDeviceInformation.PresentationParameters.BackBufferFormat; - var newOutputIndex = graphicsDeviceInformation.PresentationParameters.PreferredFullScreenOutputIndex; - - GraphicsDevice.Presenter.Description.PreferredFullScreenOutputIndex = newOutputIndex; - GraphicsDevice.Presenter.Description.RefreshRate = graphicsDeviceInformation.PresentationParameters.RefreshRate; - GraphicsDevice.Presenter.Resize(newWidth, newHeight, newFormat); - - // Change full screen if needed - GraphicsDevice.Presenter.IsFullScreen = graphicsDeviceInformation.PresentationParameters.IsFullScreen; - - needToCreateNewDevice = false; - } - catch - { - // ignored - } + needToCreateNewDevice = false; } + catch { /* Ignore any exception */ } } + } - // If we still need to create a device, then we need to create it - if (needToCreateNewDevice) - { - CreateDevice(graphicsDeviceInformation); - } + // If we still need to create a device, then we need to create it + if (needToCreateNewDevice) + { + CreateDevice(graphicsDeviceInformation); + } - if (GraphicsDevice == null) - { - throw new InvalidOperationException("Unexpected null GraphicsDevice"); - } + if (GraphicsDevice is null) + throw new InvalidOperationException("Unexpected null GraphicsDevice"); - var presentationParameters = GraphicsDevice.Presenter.Description; - isReallyFullScreen = presentationParameters.IsFullScreen; - if (presentationParameters.BackBufferWidth != 0) - { - width = presentationParameters.BackBufferWidth; - } + var presentationParameters = GraphicsDevice.Presenter.Description; + isReallyFullScreen = presentationParameters.IsFullScreen; - if (presentationParameters.BackBufferHeight != 0) - { - height = presentationParameters.BackBufferHeight; - } - deviceSettingsChanged = false; + if (presentationParameters.BackBufferWidth != 0) + { + width = presentationParameters.BackBufferWidth; } - finally + if (presentationParameters.BackBufferHeight != 0) { - if (isBeginScreenDeviceChange) - { - game.Window.EndScreenDeviceChange(width, height); - game.Window.SetIsReallyFullscreen(isReallyFullScreen); - } - - currentWindowOrientation = game.Window.CurrentOrientation; - isChangingDevice = false; + height = presentationParameters.BackBufferHeight; } + deviceSettingsChanged = false; } + finally + { + // Notify the game Window that the screen device change is over + if (isBeginScreenDeviceChange) + { + game.Window.EndScreenDeviceChange(width, height); + game.Window.SetIsReallyFullscreen(isReallyFullScreen); + } + + currentWindowOrientation = game.Window.CurrentOrientation; + isChangingDevice = false; + } + } + + // + // Creates a new Graphics Device based on the provided `GraphicsDeviceInformation`. + // + void CreateDevice(GraphicsDeviceInformation newInfo) + { + newInfo.PresentationParameters.IsFullScreen = isFullScreen; + newInfo.PresentationParameters.PresentationInterval = SynchronizeWithVerticalRetrace ? PresentInterval.One : PresentInterval.Immediate; + newInfo.DeviceCreationFlags = DeviceCreationFlags; + + // this.ValidateGraphicsDeviceInformation(newInfo); + + bool recreateDevice = GraphicsDevice is not null; + + // Notify device is resetting (usually this should result in Graphics Resources being destroyed) + if (recreateDevice) + OnDeviceResetting(this, EventArgs.Empty); + + // Create (or recreate) the graphics device + GraphicsDevice = graphicsDeviceFactory.ChangeOrCreateDevice(GraphicsDevice, newInfo); + + // Notify device is reset (usually this should result in Graphics Resources being recreated / reloaded) + if (recreateDevice) + OnDeviceReset(this, EventArgs.Empty); + + // Use the Shader profile returned by the GraphicsDeviceInformation otherwise use the one coming from the GameSettings + // TODO: Stale comment? + GraphicsDevice.ShaderProfile = ShaderProfile; + + // TODO: HANDLE Device Resetting/Reset/Lost + //GraphicsDevice.DeviceResetting += GraphicsDevice_DeviceResetting; + //GraphicsDevice.DeviceReset += GraphicsDevice_DeviceReset; + //GraphicsDevice.DeviceLost += GraphicsDevice_DeviceLost; + + if (!recreateDevice) + GraphicsDevice.Disposing += GraphicsDevice_Disposing; + + OnDeviceCreated(this, EventArgs.Empty); } } } diff --git a/sources/engine/Stride.Games/IDrawable.cs b/sources/engine/Stride.Games/IDrawable.cs index 7dff38f26e..41a127d509 100644 --- a/sources/engine/Stride.Games/IDrawable.cs +++ b/sources/engine/Stride.Games/IDrawable.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,52 +20,69 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + using System; namespace Stride.Games { /// - /// An interface for a drawable game component that is called by the class. + /// An interface for a drawable Game Component that is called by the class. /// public interface IDrawable { /// - /// Occurs when the property changes. + /// Occurs when the property changes. /// event EventHandler DrawOrderChanged; - /// - /// Occurs when the property changes. + /// Occurs when the property changes. /// event EventHandler VisibleChanged; + /// - /// Starts the drawing of a frame. This method is followed by calls to Draw and EndDraw. + /// Starts the drawing of the Game Component. + /// It prepares the drawable component for rendering and determines whether a following + /// and call should occur. /// - /// true if Draw should occur, false otherwise + /// + /// if should be called; + /// otherwise. + /// bool BeginDraw(); /// - /// Draws this instance. + /// Draws the Game Component. /// - /// The current timing. + /// The current timing information. void Draw(GameTime gameTime); /// - /// Ends the drawing of a frame. This method is preceeded by calls to Draw and BeginDraw. + /// Ends the drawing of the Game Component. /// + /// + /// This method must be preceeded by calls to and . + /// + /// + /// The Game Device this Game Component is using to draw itself is not in a valid state to end drawing, or it is not available. + /// void EndDraw(); + /// - /// Gets a value indicating whether the method should be called by . + /// Gets a value indicating whether the Game Component's method + /// should be called by . /// - /// true if this drawable component is visible; otherwise, false. + /// + /// if the Game Component is visible and should be drawn; + /// otherwise, . + /// bool Visible { get; } - + /// - /// Gets the draw order relative to other objects. objects with a lower value are drawn first. + /// Gets the draw order relative to other Game Components. + /// components with a lower value are drawn first. /// - /// The draw order. int DrawOrder { get; } } } diff --git a/sources/engine/Stride.Games/IGameSystemBase.cs b/sources/engine/Stride.Games/IGameSystemBase.cs index d4cd639109..f15840f20e 100644 --- a/sources/engine/Stride.Games/IGameSystemBase.cs +++ b/sources/engine/Stride.Games/IGameSystemBase.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,15 +26,28 @@ namespace Stride.Games { /// - /// Defines a generic game system. + /// Interface for a component that represents a Game System. /// + /// + /// + /// A Game System is a component that can be used to manage game logic, resources, or other systems within the Game. + /// For example, it can be used to manage audio, input, or other subsystems. + /// + /// + /// Game Systems are typically added to the class and are initialized when the game starts. + /// If the Game System needs to be updated or drawn, it can implement the and + /// interfaces respectively. + /// + /// public interface IGameSystemBase : IComponent { /// - /// This method is called when the component is added to the game. + /// Initializes the Game System. /// /// - /// This method can be used for tasks like querying for services the component needs and setting up non-graphics resources. + /// This method is called when the component is added to the Game. + /// This can be used for tasks like querying for services the component needs + /// and setting up non-graphics resources (as here the Graphics Device may have not been initialized yet). /// void Initialize(); } diff --git a/sources/engine/Stride.Games/IGraphicsDeviceFactory.cs b/sources/engine/Stride.Games/IGraphicsDeviceFactory.cs index 72e2610226..2a89b38c3a 100644 --- a/sources/engine/Stride.Games/IGraphicsDeviceFactory.cs +++ b/sources/engine/Stride.Games/IGraphicsDeviceFactory.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -25,12 +25,47 @@ using Stride.Graphics; -namespace Stride.Games +namespace Stride.Games; + +/// +/// Base interface for a factory that creates instances. +/// +/// +/// +/// The implementers of are responsible for creating +/// instances, and for selecting the best device based on the +/// preferred configuration. +/// +/// +/// A good example of a factory is the class, which not only +/// abstracts the platform, but also the windowing system and the Graphics Device creation. +/// +/// +public interface IGraphicsDeviceFactory { - public interface IGraphicsDeviceFactory - { - List FindBestDevices(GameGraphicsParameters graphicsParameters); + /// + /// Returns a list of instances, representing + /// the best found graphics adapters and their corresponding configuration based on the + /// given graphics parameters. + /// + /// The preferred graphics configuration. + /// + /// A list of the best found adapters, devices, and configurations found. + /// + List FindBestDevices(GameGraphicsParameters graphicsParameters); - GraphicsDevice ChangeOrCreateDevice(GraphicsDevice currentDevice, GraphicsDeviceInformation deviceInformation); - } + /// + /// Changes an existing Graphics Device or creates a new one with the specified + /// configuration. + /// + /// + /// An optional instance to reconfigure. + /// Specify to create a new device. + /// + /// + /// A specifying the intended graphics adapter and + /// its configuration + /// + /// The created (or changed) Graphics Device. + GraphicsDevice ChangeOrCreateDevice(GraphicsDevice? currentDevice, GraphicsDeviceInformation deviceInformation); } diff --git a/sources/engine/Stride.Games/IGraphicsDeviceManager.cs b/sources/engine/Stride.Games/IGraphicsDeviceManager.cs index 2a18ed7565..87937d8751 100644 --- a/sources/engine/Stride.Games/IGraphicsDeviceManager.cs +++ b/sources/engine/Stride.Games/IGraphicsDeviceManager.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,29 +21,43 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System; + using Stride.Graphics; -namespace Stride.Games +namespace Stride.Games; + +/// +/// Defines the interface for an object that manages the Graphics Device lifecycle. +/// +public interface IGraphicsDeviceManager { /// - /// Defines the interface for an object that manages a GraphicsDevice. + /// Creates a valid Graphics Device ready to draw. /// - public interface IGraphicsDeviceManager - { - /// - /// Starts the drawing of a frame. - /// - /// true if XXXX, false otherwise - bool BeginDraw(); + /// + /// Thrown if the Graphics Device could not be created. + /// + void CreateDevice(); - /// - /// Called to ensure that the device manager has created a valid device. - /// - void CreateDevice(); + /// + /// Called by the Game at the beginning of drawing. + /// + /// + /// if the Graphics Device is ready to draw; + /// if the Graphics Device is not ready or if the Game should skip drawing this frame. + /// + bool BeginDraw(); - /// - /// Called by the game at the end of drawing; if requested, presents the final rendering. - /// - void EndDraw(bool present); - } + /// + /// Called by the Game at the end of drawing. + /// + /// A value indicating whether the Game should present the Back-Buffer to the screen. + /// + /// Could not present the Back-Buffer after drawing. + /// + /// + /// The Game Device is not in a valid state to end drawing, or it is not available. + /// + void EndDraw(bool present); } diff --git a/sources/engine/Stride.Graphics.Regression/ImageTester.cs b/sources/engine/Stride.Graphics.Regression/ImageTester.cs index 0fa815ce69..6fb261f2eb 100644 --- a/sources/engine/Stride.Graphics.Regression/ImageTester.cs +++ b/sources/engine/Stride.Graphics.Regression/ImageTester.cs @@ -70,7 +70,7 @@ public static bool CompareImage(Image image, string testFilename) || buffer.RowStride != referenceBuffer.RowStride) return false; - var swapBGR = buffer.Format.IsBGRAOrder() != referenceBuffer.Format.IsBGRAOrder(); + var swapBGR = buffer.Format.IsBgraOrder() != referenceBuffer.Format.IsBgraOrder(); // For now, we handle only those specific cases if ((buffer.Format != PixelFormat.R8G8B8A8_UNorm_SRgb && buffer.Format != PixelFormat.B8G8R8A8_UNorm_SRgb) || referenceBuffer.Format != PixelFormat.B8G8R8A8_UNorm) diff --git a/sources/engine/Stride.Graphics.Tests/TestTexture.cs b/sources/engine/Stride.Graphics.Tests/TestTexture.cs index e80f1037d4..ecefe803b3 100644 --- a/sources/engine/Stride.Graphics.Tests/TestTexture.cs +++ b/sources/engine/Stride.Graphics.Tests/TestTexture.cs @@ -74,7 +74,7 @@ public void TestTexture1DMipMap() var texture = Texture.New1D(device, 256, true, PixelFormat.R8_UNorm, TextureFlags.ShaderResource | TextureFlags.RenderTarget); // Verify the number of mipmap levels - Assert.Equal(texture.MipLevels, Math.Log(data.Length, 2) + 1); + Assert.Equal(texture.MipLevelCount, Math.Log(data.Length, 2) + 1); // Get a render target on the mipmap 1 (128) with value 1 and get back the data var renderTarget1 = texture.ToTextureView(ViewType.Single, 0, 1); @@ -144,7 +144,7 @@ public void TestTexture2DArray() var texture = Texture.New2D(device, 256, 256, 1, PixelFormat.R8_UNorm, TextureFlags.ShaderResource | TextureFlags.RenderTarget, 4); // Verify the number of mipmap levels - Assert.Equal(1, texture.MipLevels); + Assert.Equal(1, texture.MipLevelCount); // Get a render target on the array 1 (128) with value 1 and get back the data var renderTarget1 = texture.ToTextureView(ViewType.Single, 1, 0); @@ -436,7 +436,7 @@ public void TestLoadDraw(ImageFileType sourceFormat) // Load an image from a file and dispose it. Texture texture; using (var inStream = game.Content.OpenAsStream(filePath, StreamFlags.None)) - texture = Texture.Load(device, inStream, loadAsSRGB: true); + texture = Texture.Load(device, inStream, loadAsSrgb: true); game.GraphicsContext.DrawTexture(texture, BlendStates.AlphaBlend); }, diff --git a/sources/engine/Stride.Graphics/AppContextType.cs b/sources/engine/Stride.Graphics/AppContextType.cs index a767ba508b..09f7701b32 100644 --- a/sources/engine/Stride.Graphics/AppContextType.cs +++ b/sources/engine/Stride.Graphics/AppContextType.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,48 +21,49 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -namespace Stride.Games +namespace Stride.Games; + +/// +/// Defines the type of a GameContext, which usually determines the platform, or +/// a combination of platform and UI/presentation framework. +/// +public enum AppContextType { /// - /// Type of a `GameContext`. + /// The Game runs on desktop in a Windows Forms' Form or Control. /// - public enum AppContextType - { - /// - /// Game running on desktop in a form or Control. - /// - Desktop, + Desktop, - /// - /// Game running on desktop in a SDL window. - /// - DesktopSDL, + /// + /// The Game runs on desktop in a SDL window. + /// + DesktopSDL, - /// - /// Game running on desktop in a WPF window through a D3DImage. - /// - DesktopWpf, + /// + /// The Game runs on desktop in a WPF window through a D3DImage. + /// + DesktopWpf, - /// - /// Game running on Android in an AndroidStrideGameView. - /// - Android, + /// + /// The Game runs on an Android device in an AndroidStrideGameView. + /// + Android, - /// - /// Game running on UWP in a Xaml SwapChainPanel. - /// - UWPXaml, + /// + /// The Game runs on UWP (Universal Windows Platform) in a Xaml + /// SwapChainPanel. + /// + UWPXaml, - /// - /// Game running on UWP in a CoreWindow. - /// - UWPCoreWindow, + /// + /// The Game runs on UWP (Universal Windows Platform) in a CoreWindow. + /// + UWPCoreWindow, #pragma warning disable SA1300 // Element must begin with upper-case letter - /// - /// Game running on iOS in a iPhoneOSGameView. - /// - iOS, + /// + /// The Game runs on an iOS device in an iPhoneOSGameView. + /// + iOS #pragma warning restore SA1300 // Element must begin with upper-case letter - } } diff --git a/sources/engine/Stride.Graphics/BatchBase.cs b/sources/engine/Stride.Graphics/BatchBase.cs index 5c680e8f99..6d16488717 100644 --- a/sources/engine/Stride.Graphics/BatchBase.cs +++ b/sources/engine/Stride.Graphics/BatchBase.cs @@ -501,9 +501,9 @@ private void DrawBatchPerTextureAndPass(ElementInfo[] sprites, int offset, int c //else { var mappedIndices = new MappedResource(); - var mappedVertices = GraphicsContext.CommandList.MapSubresource(ResourceContext.VertexBuffer, 0, MapMode.WriteNoOverwrite, false, offsetVertexInBytes, vertexCount * vertexStructSize); + var mappedVertices = GraphicsContext.CommandList.MapSubResource(ResourceContext.VertexBuffer, 0, MapMode.WriteNoOverwrite, false, offsetVertexInBytes, vertexCount * vertexStructSize); if (ResourceContext.IsIndexBufferDynamic) - mappedIndices = GraphicsContext.CommandList.MapSubresource(ResourceContext.IndexBuffer, 0, MapMode.WriteNoOverwrite, false, offsetIndexInBytes, indexCount * indexStructSize); + mappedIndices = GraphicsContext.CommandList.MapSubResource(ResourceContext.IndexBuffer, 0, MapMode.WriteNoOverwrite, false, offsetIndexInBytes, indexCount * indexStructSize); var vertexPointer = mappedVertices.DataBox.DataPointer; var indexPointer = mappedIndices.DataBox.DataPointer; @@ -520,9 +520,9 @@ private void DrawBatchPerTextureAndPass(ElementInfo[] sprites, int offset, int c indexPointer += indexStructSize * spriteElementInfo.IndexCount; } - GraphicsContext.CommandList.UnmapSubresource(mappedVertices); + GraphicsContext.CommandList.UnmapSubResource(mappedVertices); if (ResourceContext.IsIndexBufferDynamic) - GraphicsContext.CommandList.UnmapSubresource(mappedIndices); + GraphicsContext.CommandList.UnmapSubResource(mappedIndices); } // Draw from the specified index diff --git a/sources/engine/Stride.Graphics/Blend.cs b/sources/engine/Stride.Graphics/Blend.cs index bae46b4f87..517f7a4e98 100644 --- a/sources/engine/Stride.Graphics/Blend.cs +++ b/sources/engine/Stride.Graphics/Blend.cs @@ -1,101 +1,143 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A Blend Option identifying the data source and an optional pre-blend operation, +/// which help define how Pixel Shader output is mixed with the destination color in rendering operations. +/// +/// +/// Blend options are specified in a . +/// +[DataContract] +public enum Blend { /// - /// Blend option. A blend option identifies the data source and an optional pre-blend operation. - /// - /// - /// Blend options are specified in a . - /// - [DataContract] - public enum Blend - { - /// - /// The data source is the color black (0, 0, 0, 0). No pre-blend operation. - /// - Zero = 1, - - /// - /// The data source is the color white (1, 1, 1, 1). No pre-blend operation. - /// - One = 2, - - /// - /// The data source is color data (RGB) from a pixel shader. No pre-blend operation. - /// - SourceColor = 3, - - /// - /// The data source is color data (RGB) from a pixel shader. The pre-blend operation inverts the data, generating 1 - RGB. - /// - InverseSourceColor = 4, - - /// - /// The data source is alpha data (A) from a pixel shader. No pre-blend operation. - /// - SourceAlpha = 5, - - /// - /// The data source is alpha data (A) from a pixel shader. The pre-blend operation inverts the data, generating 1 - A. - /// - InverseSourceAlpha = 6, - - /// - /// The data source is alpha data from a rendertarget. No pre-blend operation. - /// - DestinationAlpha = 7, - - /// - /// The data source is alpha data from a rendertarget. The pre-blend operation inverts the data, generating 1 - A. - /// - InverseDestinationAlpha = 8, - - /// - /// The data source is color data from a rendertarget. No pre-blend operation. - /// - DestinationColor = 9, - - /// - /// The data source is color data from a rendertarget. The pre-blend operation inverts the data, generating 1 - RGB. - /// - InverseDestinationColor = 10, - - /// - /// The data source is alpha data from a pixel shader. The pre-blend operation clamps the data to 1 or less. - /// - SourceAlphaSaturate = 11, - - /// - /// The data source is the blend factor set with . No pre-blend operation. - /// - BlendFactor = 14, - - /// - /// The data source is the blend factor set with . The pre-blend operation inverts the blend factor, generating 1 - blend_factor. - /// - InverseBlendFactor = 15, - - /// - /// The data sources are both color data output by a pixel shader. There is no pre-blend operation. This options supports dual-source color blending. - /// - SecondarySourceColor = 16, - - /// - /// The data sources are both color data output by a pixel shader. The pre-blend operation inverts the data, generating 1 - RGB. This options supports dual-source color blending. - /// - InverseSecondarySourceColor = 17, - - /// - /// The data sources are alpha data output by a pixel shader. There is no pre-blend operation. This options supports dual-source color blending. - /// - SecondarySourceAlpha = 18, - - /// - /// The data sources are alpha data output by a pixel shader. The pre-blend operation inverts the data, generating 1 - A. This options supports dual-source color blending. - /// - InverseSecondarySourceAlpha = 19, - } + /// The data source is the zero vector (0, 0, 0, 0), meaning each component of the color generated + /// by the Pixel Shader is multiplied by zero. + ///
+ /// No pre-blend operation. + ///
+ Zero = 1, + + /// + /// The data source is the one vector (1, 1, 1, 1), meaning each component of the color generated + /// by the Pixel Shader is multiplied by one + ///
+ /// No pre-blend operation. + ///
+ One = 2, + + /// + /// The data source is color data (RGB) generated by the Pixel Shader. + ///
+ /// No pre-blend operation. Each component is directly used for blending. + ///
+ SourceColor = 3, + + /// + /// The data source is color data (RGB) generated by the Pixel Shader. + ///
+ /// The pre-blend operation inverts the data. Each component is inverted (1 - RGB) before blending. + ///
+ InverseSourceColor = 4, + + /// + /// The data source is alpha data (A) generated by the Pixel Shader. + ///
+ /// No pre-blend operation. The alpha value is directly used for blending. + ///
+ SourceAlpha = 5, + + /// + /// The data source is alpha data (A) generated by the Pixel Shader. + ///
+ /// The pre-blend operation inverts the data. The alpha value is inverted (1 - A) before blending. + ///
+ InverseSourceAlpha = 6, + + /// + /// The data source is the alpha value that comes from the existing Render Target before blending. + ///
+ /// No pre-blend operation. The alpha value is directly used for blending. + ///
+ DestinationAlpha = 7, + + /// + /// The data source is the alpha value that comes from the existing Render Target before blending. + ///
+ /// The pre-blend operation inverts the data. The alpha value is inverted (1 - A) before blending. + ///
+ InverseDestinationAlpha = 8, + + /// + /// The data source is color data already present in a Render Target. + ///
+ /// No pre-blend operation. Each component is directly used for blending. + ///
+ DestinationColor = 9, + + /// + /// The data source is color data already present in a Render Target. + ///
+ /// The pre-blend operation inverts the data. Each component is inverted (1 - RGB) before blending. + ///
+ InverseDestinationColor = 10, + + /// + /// The data source is alpha data generated by the Pixel Shader. + ///
+ /// The pre-blend operation clamps the data to 1 or less. + /// This ensures the alpha value does not exceed 1, preventing unintended brightness shifts. + ///
+ SourceAlphaSaturate = 11, + + /// + /// The data source is a blend factor set in the . + ///
+ /// No pre-blend operation. The blend factor is directly used for blending. + ///
+ BlendFactor = 14, + + /// + /// The data source is a blend factor set in the . + ///
+ /// The pre-blend operation inverts the data. The blend factor is inverted (1 - blendFactor) before blending. + ///
+ InverseBlendFactor = 15, + + /// + /// Uses two color outputs (RGB0 and RGB1) from the Pixel Shader for advanced blending techniques. + ///
+ /// No pre-blend operation. Each component is directly used for blending. + ///
+ SecondarySourceColor = 16, + + /// + /// The data source is primary color data (RGB0) and secondary color data (RGB1), + /// both generated by the Pixel Shader, for use in dual-source color blending. + ///
+ /// The pre-blend operation inverts the data. Each component of both of the colors is inverted (1 - RGB) before blending. + /// This can create interesting effects like color negation. + ///
+ InverseSecondarySourceColor = 17, + + /// + /// The data source is primary alpha data (A0) and secondary alpha data (A1), + /// both generated by the Pixel Shader, for use in dual-source color blending. + ///
+ /// No pre-blend operation. The alpha value is directly used for blending. + ///
+ SecondarySourceAlpha = 18, + + /// + /// The data source is primary alpha data (A0) and secondary alpha data (A1), + /// both generated by the Pixel Shader, for use in dual-source color blending. + ///
+ /// The pre-blend operation inverts the data. Both alpha values are inverted (1 - A) before blending. + ///
+ InverseSecondarySourceAlpha = 19 } diff --git a/sources/engine/Stride.Graphics/BlendFunction.cs b/sources/engine/Stride.Graphics/BlendFunction.cs index a4048ba402..9bf6854268 100644 --- a/sources/engine/Stride.Graphics/BlendFunction.cs +++ b/sources/engine/Stride.Graphics/BlendFunction.cs @@ -1,41 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a function for color blending. +/// +/// +/// The runtime implements color (RGB) blending and alpha blending separately. Therefore, a Blend State requires separate blend operations +/// for RGB data and alpha data. These blend operations are specified in a and in a . +/// +[DataContract] +public enum BlendFunction { /// - /// RGB or alpha blending operation. + /// The function adds destination to the source: (srcColor * srcBlend) + (destColor * destBlend) /// - /// - /// The runtime implements RGB blending and alpha blending separately. Therefore, blend state requires separate blend operations for RGB data and alpha data. These blend operations are specified in a and in a . - /// - [DataContract] - public enum BlendFunction - { - /// - /// Add source 1 and source 2. - /// - Add = 1, + Add = 1, - /// - /// Subtract source 1 from source 2. - /// - Subtract = 2, + /// + /// The function subtracts destination from source: (srcColor * srcBlend) − (destColor * destBlend) + /// + Subtract = 2, - /// - /// Subtract source 2 from source 1. - /// - ReverseSubtract = 3, + /// + /// The function subtracts source from destination: (destColor * destBlend) - (srcColor * srcBlend) + /// + ReverseSubtract = 3, - /// - /// Find the minimum of source 1 and source 2. - /// - Min = 4, + /// + /// The function finds the minimum of the source and destination: min( (srcColor * srcBlend), (destColor * destBlend) ) + /// + Min = 4, - /// - /// Find the maximum of source 1 and source 2. - /// - Max = 5, - } + /// + /// The function finds the maximum of the source and destination: max( (srcColor * srcBlend), (destColor * destBlend) ) + /// + Max = 5 } diff --git a/sources/engine/Stride.Graphics/BlendStateDescription.cs b/sources/engine/Stride.Graphics/BlendStateDescription.cs index ca018377aa..e9605f7f9d 100644 --- a/sources/engine/Stride.Graphics/BlendStateDescription.cs +++ b/sources/engine/Stride.Graphics/BlendStateDescription.cs @@ -1,152 +1,190 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Stride.Core; -using Stride.Core.Extensions; -using Stride.Core.Mathematics; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A description of a Blend State, which defines how colors are blended when rendering to one +/// or multiple Render Targets. +/// +/// +/// This structure controls transparency, color mixing, and blend modes across all the Render Targets. Modify this to achieve effects +/// like alpha blending, additive blending, or custom shader-based blends. +/// It also controls whether to use alpha-to-coverage as a multi-sampling technique when writing a pixel to a Render Target. +/// +/// +[DataContract] +[StructLayout(LayoutKind.Sequential)] +public struct BlendStateDescription : IEquatable { /// - /// Describes a blend state. + /// Initializes a new instance of the structure. /// - [DataContract] - [StructLayout(LayoutKind.Sequential)] - public struct BlendStateDescription : IEquatable + /// The source blend. + /// The destination blend. + public BlendStateDescription(Blend sourceBlend, Blend destinationBlend) : this() { - /// - /// Initializes a new instance of the class. - /// - /// The source blend. - /// The destination blend. - public BlendStateDescription(Blend sourceBlend, Blend destinationBlend) : this() - { - SetDefaults(); - RenderTarget0.BlendEnable = true; - RenderTarget0.ColorSourceBlend = sourceBlend; - RenderTarget0.ColorDestinationBlend = destinationBlend; - RenderTarget0.AlphaSourceBlend = sourceBlend; - RenderTarget0.AlphaDestinationBlend = destinationBlend; - } + SetDefaults(); + RenderTargets[0].BlendEnable = true; + RenderTargets[0].ColorSourceBlend = sourceBlend; + RenderTargets[0].ColorDestinationBlend = destinationBlend; + RenderTargets[0].AlphaSourceBlend = sourceBlend; + RenderTargets[0].AlphaDestinationBlend = destinationBlend; + } - /// - /// Setup this blend description with defaults value. - /// - public unsafe void SetDefaults() - { - AlphaToCoverageEnable = false; - IndependentBlendEnable = false; - - fixed (BlendStateRenderTargetDescription* renderTargets = &RenderTarget0) - { - for (int i = 0; i < 8; i++) - { - ref var renderTarget = ref renderTargets[i]; - renderTarget.BlendEnable = false; - renderTarget.ColorSourceBlend = Blend.One; - renderTarget.ColorDestinationBlend = Blend.Zero; - renderTarget.ColorBlendFunction = BlendFunction.Add; - - renderTarget.AlphaSourceBlend = Blend.One; - renderTarget.AlphaDestinationBlend = Blend.Zero; - renderTarget.AlphaBlendFunction = BlendFunction.Add; - - renderTarget.ColorWriteChannels = ColorWriteChannels.All; - } - } - } + /// + /// Sets default values for this Blend State Description. + /// + /// + /// The default values are: + /// + /// + /// Alpha-to-Coverage + /// Disabled + /// + /// + /// Independent Blending + /// Disabled. Only enable blend for the first Render Target + /// + /// Disable blending for all the Render Targets. + /// + /// + public void SetDefaults() + { + AlphaToCoverageEnable = false; + IndependentBlendEnable = false; - /// - /// Gets default values for this instance. - /// - public static BlendStateDescription Default + for (int i = 0; i < RenderTargets.Count; i++) { - get - { - var desc = new BlendStateDescription(); - desc.SetDefaults(); - return desc; - } + ref var renderTarget = ref RenderTargets[i]; + + renderTarget.BlendEnable = false; + renderTarget.ColorSourceBlend = Blend.One; + renderTarget.ColorDestinationBlend = Blend.Zero; + renderTarget.ColorBlendFunction = BlendFunction.Add; + + renderTarget.AlphaSourceBlend = Blend.One; + renderTarget.AlphaDestinationBlend = Blend.Zero; + renderTarget.AlphaBlendFunction = BlendFunction.Add; + + renderTarget.ColorWriteChannels = ColorWriteChannels.All; } + } - /// - /// Determines whether or not to use alpha-to-coverage as a multisampling technique when setting a pixel to a rendertarget. - /// - public bool AlphaToCoverageEnable; + + /// + /// A value that determines whether or not to use alpha-to-coverage as a multi-sampling technique + /// when writing a pixel to a Render Target. + /// + /// + /// Alpha-to-coverage is a technique that uses the alpha value of a pixel to determine how much coverage it should have + /// in a multi-sampled anti-aliasing (MSAA) scenario. + /// This can help achieve smoother edges in transparent textures by blending the coverage of the pixel based on its alpha value. + /// + public bool AlphaToCoverageEnable; + + /// + /// A value indicating whether to enable independent blending in simultaneous Render Targets, + /// meaning per-Render Target blending settings. + /// + /// + /// + /// If set to , each of the can have its own blend settings. + /// + /// + /// If set to , only the first Render Target () is taken into account. + /// The others ( to ) are ignored. + /// + /// + public bool IndependentBlendEnable; + + /// + /// An array of Render Target blend descriptions (see ); + /// these correspond to the eight Render Targets that can be set to the output-merger stage at one time. + /// + public RenderTargetBlendStates RenderTargets; + + #region Render Targets inline array + + /// + /// A structure that contains an inline array of for up to eight render targets. + /// + [System.Runtime.CompilerServices.InlineArray(SIMULTANEOUS_RENDERTARGET_COUNT)] + public struct RenderTargetBlendStates + { + private const int SIMULTANEOUS_RENDERTARGET_COUNT = 8; /// - /// Set to true to enable independent blending in simultaneous render targets. If set to false, only the RenderTarget[0] members are used. RenderTarget[1..7] are ignored. + /// Gets the number of Render Target blend descriptions in a Blend State Description. /// - public bool IndependentBlendEnable; + public readonly int Count => SIMULTANEOUS_RENDERTARGET_COUNT; + + private BlendStateRenderTargetDescription _renderTarget0; + /// - /// An array of render-target-blend descriptions (see ); these correspond to the eight rendertargets that can be set to the output-merger stage at one time. + /// Returns a writable span of for the Render Targets. /// - public BlendStateRenderTargetDescription RenderTarget0; - public BlendStateRenderTargetDescription RenderTarget1; - public BlendStateRenderTargetDescription RenderTarget2; - public BlendStateRenderTargetDescription RenderTarget3; - public BlendStateRenderTargetDescription RenderTarget4; - public BlendStateRenderTargetDescription RenderTarget5; - public BlendStateRenderTargetDescription RenderTarget6; - public BlendStateRenderTargetDescription RenderTarget7; - - /// - public bool Equals(BlendStateDescription other) + /// A of Blend State descriptions for the Render Targets. + [UnscopedRef] + public Span AsSpan() { - if (AlphaToCoverageEnable != other.AlphaToCoverageEnable - || IndependentBlendEnable != other.IndependentBlendEnable) - return false; - - if (RenderTarget0 != other.RenderTarget0 - || RenderTarget1 != other.RenderTarget1 - || RenderTarget2 != other.RenderTarget2 - || RenderTarget3 != other.RenderTarget3 - || RenderTarget4 != other.RenderTarget4 - || RenderTarget5 != other.RenderTarget5 - || RenderTarget6 != other.RenderTarget6 - || RenderTarget7 != other.RenderTarget7) - return false; - - return true; + return MemoryMarshal.CreateSpan(ref _renderTarget0, SIMULTANEOUS_RENDERTARGET_COUNT); } - /// - public override bool Equals(object obj) + /// + /// Returns a read-only span of for the Render Targets. + /// + /// A of Blend State descriptions for the Render Targets. + [UnscopedRef] + public readonly ReadOnlySpan AsReadOnlySpan() { - if (ReferenceEquals(null, obj)) return false; - return obj is BlendStateDescription && Equals((BlendStateDescription)obj); + return MemoryMarshal.CreateReadOnlySpan(ref System.Runtime.CompilerServices.Unsafe.AsRef(in _renderTarget0), SIMULTANEOUS_RENDERTARGET_COUNT); } + } - public static bool operator ==(BlendStateDescription left, BlendStateDescription right) - { - return left.Equals(right); - } + #endregion - public static bool operator !=(BlendStateDescription left, BlendStateDescription right) - { - return !left.Equals(right); - } - /// - public override int GetHashCode() - { - unchecked - { - int hashCode = AlphaToCoverageEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ IndependentBlendEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget0.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget1.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget2.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget3.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget4.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget5.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget6.GetHashCode(); - hashCode = (hashCode * 397) ^ RenderTarget7.GetHashCode(); - return hashCode; - } - } + /// + public readonly bool Equals(BlendStateDescription other) + { + if (AlphaToCoverageEnable != other.AlphaToCoverageEnable || + IndependentBlendEnable != other.IndependentBlendEnable) + return false; + + return RenderTargets.AsReadOnlySpan().SequenceEqual(other.RenderTargets); + } + + /// + public override readonly bool Equals(object obj) + { + return obj is BlendStateDescription description && Equals(description); + } + + public static bool operator ==(BlendStateDescription left, BlendStateDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(BlendStateDescription left, BlendStateDescription right) + { + return !left.Equals(right); + } + + /// + public override readonly int GetHashCode() + { + var hash = new HashCode(); + hash.Add(AlphaToCoverageEnable); + hash.Add(IndependentBlendEnable); + hash.Add(RenderTargets); + return hash.ToHashCode(); } } diff --git a/sources/engine/Stride.Graphics/BlendStateRenderTargetDescription.cs b/sources/engine/Stride.Graphics/BlendStateRenderTargetDescription.cs index e5c9f3c74a..ecbbc849ca 100644 --- a/sources/engine/Stride.Graphics/BlendStateRenderTargetDescription.cs +++ b/sources/engine/Stride.Graphics/BlendStateRenderTargetDescription.cs @@ -2,91 +2,107 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A description of a Blend State for a Render Target, which defines how colors are blended when rendering. +///
+///
+/// +/// This structure controls transparency, color mixing, and blend modes for a Render Target. Modify this to achieve effects +/// like alpha blending, additive blending, or custom shader-based blends. +/// +[DataContract] +public struct BlendStateRenderTargetDescription : IEquatable { /// - /// Describes the blend state for a render target. + /// A value indicating whether to enable or disable blending. + /// + public bool BlendEnable; + + /// + /// Specifies the first color (RGB) data source and includes an optional pre-blend operation. /// - [DataContract] - public struct BlendStateRenderTargetDescription : IEquatable + /// + public Blend ColorSourceBlend; + + /// + /// Specifies the second color (RGB) data source and includes an optional pre-blend operation. + /// + /// + public Blend ColorDestinationBlend; + + /// + /// Defines the function used to combine the color (RGB) data sources. + /// + /// + public BlendFunction ColorBlendFunction; + + /// + /// Specifies the first alpha data source and includes an optional pre-blend operation. + /// + /// + /// + /// options that end in Color are not allowed. + /// + public Blend AlphaSourceBlend; + + /// + /// Specifies the second alpha data source and includes an optional pre-blend operation. + /// + /// + /// + /// options that end in Color are not allowed. + /// + public Blend AlphaDestinationBlend; + + /// + /// Defines the function used to combine the alpha data sources. + /// + /// + public BlendFunction AlphaBlendFunction; + + /// + /// A combination of flags that specify which color channels (Red, Green, Blue, Alpha) can be written to the Render Target when blending. + /// + public ColorWriteChannels ColorWriteChannels; + + + /// + public readonly bool Equals(BlendStateRenderTargetDescription other) + { + return BlendEnable == other.BlendEnable + && ColorSourceBlend == other.ColorSourceBlend + && ColorDestinationBlend == other.ColorDestinationBlend + && ColorBlendFunction == other.ColorBlendFunction + && AlphaSourceBlend == other.AlphaSourceBlend + && AlphaDestinationBlend == other.AlphaDestinationBlend + && AlphaBlendFunction == other.AlphaBlendFunction + && ColorWriteChannels == other.ColorWriteChannels; + } + + /// + public override readonly bool Equals(object obj) + { + return obj is BlendStateRenderTargetDescription description && Equals(description); + } + + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(BlendEnable, ColorSourceBlend, ColorDestinationBlend, ColorBlendFunction, AlphaSourceBlend, AlphaDestinationBlend, AlphaBlendFunction, ColorWriteChannels); + } + + public static bool operator ==(BlendStateRenderTargetDescription left, BlendStateRenderTargetDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(BlendStateRenderTargetDescription left, BlendStateRenderTargetDescription right) { - /// - /// Enable (or disable) blending. - /// - public bool BlendEnable; - - /// - /// This specifies the first RGB data source and includes an optional pre-blend operation. - /// - public Blend ColorSourceBlend; - - /// - /// This specifies the second RGB data source and includes an optional pre-blend operation. - /// - public Blend ColorDestinationBlend; - - /// - /// This defines how to combine the RGB data sources. - /// - public BlendFunction ColorBlendFunction; - - /// - /// This specifies the first alpha data source and includes an optional pre-blend operation. Blend options that end in _COLOR are not allowed. - /// - public Blend AlphaSourceBlend; - - /// - /// This specifies the second alpha data source and includes an optional pre-blend operation. Blend options that end in _COLOR are not allowed. - /// - public Blend AlphaDestinationBlend; - - /// - /// This defines how to combine the alpha data sources. - /// - public BlendFunction AlphaBlendFunction; - - /// - /// A write mask. - /// - public ColorWriteChannels ColorWriteChannels; - - public bool Equals(BlendStateRenderTargetDescription other) - { - return BlendEnable == other.BlendEnable && ColorSourceBlend == other.ColorSourceBlend && ColorDestinationBlend == other.ColorDestinationBlend && ColorBlendFunction == other.ColorBlendFunction && AlphaSourceBlend == other.AlphaSourceBlend && AlphaDestinationBlend == other.AlphaDestinationBlend && AlphaBlendFunction == other.AlphaBlendFunction && ColorWriteChannels == other.ColorWriteChannels; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is BlendStateRenderTargetDescription && Equals((BlendStateRenderTargetDescription)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = BlendEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ (int)ColorSourceBlend; - hashCode = (hashCode * 397) ^ (int)ColorDestinationBlend; - hashCode = (hashCode * 397) ^ (int)ColorBlendFunction; - hashCode = (hashCode * 397) ^ (int)AlphaSourceBlend; - hashCode = (hashCode * 397) ^ (int)AlphaDestinationBlend; - hashCode = (hashCode * 397) ^ (int)AlphaBlendFunction; - hashCode = (hashCode * 397) ^ (int)ColorWriteChannels; - return hashCode; - } - } - - public static bool operator ==(BlendStateRenderTargetDescription left, BlendStateRenderTargetDescription right) - { - return left.Equals(right); - } - - public static bool operator !=(BlendStateRenderTargetDescription left, BlendStateRenderTargetDescription right) - { - return !left.Equals(right); - } + return !left.Equals(right); } } diff --git a/sources/engine/Stride.Graphics/BlendStates.cs b/sources/engine/Stride.Graphics/BlendStates.cs index 5a5da17159..4c2d2d1e35 100644 --- a/sources/engine/Stride.Graphics/BlendStates.cs +++ b/sources/engine/Stride.Graphics/BlendStates.cs @@ -1,54 +1,158 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a set of built-in s for common blending configurations. +/// +public static class BlendStates { - /// - /// Known values for . - /// - public static class BlendStates + static BlendStates() { - static BlendStates() - { - var blendDescription = new BlendStateDescription(Blend.One, Blend.Zero); - blendDescription.SetDefaults(); - Default = blendDescription; - - var colorDisabledDescription = new BlendStateDescription(); - colorDisabledDescription.SetDefaults(); - colorDisabledDescription.RenderTarget0.ColorWriteChannels = ColorWriteChannels.None; - ColorDisabled = colorDisabledDescription; - } - - /// - /// A built-in state object with settings for default blend, that is no blend at all. - /// - public static readonly BlendStateDescription Default; - - /// - /// A built-in state object with settings for additive blend, that is adding the destination data to the source data without using alpha. - /// - public static readonly BlendStateDescription Additive = new BlendStateDescription(Blend.SourceAlpha, Blend.One); - - /// - /// A built-in state object with settings for alpha blend, that is blending the source and destination data using alpha. - /// - public static readonly BlendStateDescription AlphaBlend = new BlendStateDescription(Blend.One, Blend.InverseSourceAlpha); - - /// - /// A built-in state object with settings for blending with non-premultipled alpha, that is blending source and destination data using alpha while assuming the color data contains no alpha information. - /// - public static readonly BlendStateDescription NonPremultiplied = new BlendStateDescription(Blend.SourceAlpha, Blend.InverseSourceAlpha); - - /// - /// A built-in state object with settings for opaque blend, that is overwriting the source with the destination data. - /// - public static readonly BlendStateDescription Opaque = new BlendStateDescription(Blend.One, Blend.Zero); - - /// - /// A built-in state object with settings for no color rendering on target 0, that is only render to depth stencil buffer. - /// - public static readonly BlendStateDescription ColorDisabled; + var blendDescription = new BlendStateDescription(Blend.One, Blend.Zero); + blendDescription.SetDefaults(); + Default = blendDescription; + + var colorDisabledDescription = new BlendStateDescription(); + colorDisabledDescription.SetDefaults(); + colorDisabledDescription.RenderTargets[0].ColorWriteChannels = ColorWriteChannels.None; + ColorDisabled = colorDisabledDescription; } + + + /// + /// A built-in Blend State description with default settings, that is no blend at all. + /// + /// + public static readonly BlendStateDescription Default; + + /// + /// A built-in Blend State description with settings for additive blending, + /// that is adding the destination data to the source data without using alpha. + /// + /// + /// This built-in state object has the following settings for the first Render Target: + /// + /// + /// Property Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static readonly BlendStateDescription Additive = new(sourceBlend: Blend.SourceAlpha, destinationBlend: Blend.One); + + /// + /// A built-in Blend State description with settings for alpha blending, + /// that is blending the source and destination data using alpha. + /// + /// + /// This built-in state object has the following settings for the first Render Target: + /// + /// + /// Property Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static readonly BlendStateDescription AlphaBlend = new(sourceBlend: Blend.One, destinationBlend: Blend.InverseSourceAlpha); + + /// + /// A built-in Blend State description with settings for blending with non-premultipled alpha, + /// that is blending source and destination data using alpha while assuming the color data contains no alpha information. + /// + /// + /// This built-in state object has the following settings for the first Render Target: + /// + /// + /// Property Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static readonly BlendStateDescription NonPremultiplied = new(sourceBlend : Blend.SourceAlpha, destinationBlend : Blend.InverseSourceAlpha); + + /// + /// A built-in Blend State description with settings for opaque blending, + /// that is overwriting the destination with the source data. + /// + /// + /// This built-in state object has the following settings for the first Render Target: + /// + /// + /// Property Value + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static readonly BlendStateDescription Opaque = new(sourceBlend : Blend.One, destinationBlend : Blend.Zero); + + /// + /// A built-in Blend State description with settings for disabling color rendering on the first Render Target (target 0), + /// that is rendering only to the Depth-Stencil Buffer. + /// + /// + /// This is the same as the state, but with the first Render Target's color write channels disabled. + /// + public static readonly BlendStateDescription ColorDisabled; } diff --git a/sources/engine/Stride.Graphics/Buffer.Argument.cs b/sources/engine/Stride.Graphics/Buffer.Argument.cs index 52828c6ef4..62e24175e7 100644 --- a/sources/engine/Stride.Graphics/Buffer.Argument.cs +++ b/sources/engine/Stride.Graphics/Buffer.Argument.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,45 +26,59 @@ namespace Stride.Graphics public partial class Buffer { /// - /// Argument buffer helper methods. + /// Helper methods for creating Argument Buffers. /// + /// + /// An Argument Buffer is a that is used to pass arguments / parameters to shaders + /// or in indirect rendering (i.e. GPU-driven rendering). + /// The key differences with Constant Buffers is that Argument Buffer are (by default) writable by the GPU, + /// can have an arbitrary size, and have somewhat slower access than Constant Buffers (as they are more akin to regular Buffers). + /// + /// + /// public static class Argument { /// - /// Creates a new Argument buffer with uasge by default. + /// Creates a new Argument Buffer of a given size. /// /// The . - /// The size in bytes. - /// The usage. - /// A Argument buffer - public static Buffer New(GraphicsDevice device, int size, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// Size of the Buffer in bytes. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, size, BufferFlags.ArgumentBuffer, usage); + return Buffer.New(device, bufferSize, BufferFlags.ArgumentBuffer, usage); } /// - /// Creates a new Argument buffer with uasge by default. + /// Creates a new Argument Buffer. /// - /// Type of the Argument buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The usage. - /// A Argument buffer + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . public static Buffer New(GraphicsDevice device, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, 1, BufferFlags.ArgumentBuffer, usage); + return Buffer.New(device, elementCount: 1, BufferFlags.ArgumentBuffer, usage); } /// - /// Creates a new Argument buffer with uasge by default. + /// Creates a new Argument Buffer with initial data. /// - /// Type of the Argument buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the Argument buffer. - /// The usage of this resource. - /// A Argument buffer - public static Buffer New(GraphicsDevice device, ref T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The value to initialize the Argument Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ref readonly T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, ref value, BufferFlags.ArgumentBuffer, usage); + return Buffer.New(device, in value, BufferFlags.ArgumentBuffer, usage); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.Constant.cs b/sources/engine/Stride.Graphics/Buffer.Constant.cs index 82f1f458ef..d8c476cba3 100644 --- a/sources/engine/Stride.Graphics/Buffer.Constant.cs +++ b/sources/engine/Stride.Graphics/Buffer.Constant.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,89 +22,105 @@ // THE SOFTWARE. using System; -using Stride.Games; namespace Stride.Graphics { public partial class Buffer { /// - /// Constant buffer helper methods. + /// Helper methods for creating Constant Buffers. /// + /// + /// A Constant Buffer is a that is used to pass shader constants / parameters to shaders. + /// They have fast access due to special hardware optimizations, being read-only to shaders, and being of a limited size + /// (64 kB usually). + /// + /// + /// public static class Constant { /// - /// Creates a new constant buffer with a default usage. + /// Creates a new Constant Buffer of a given size. /// /// The . - /// The size in bytes. - /// The usage. - /// A constant buffer - public static Buffer New(GraphicsDevice device, int size, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) + /// Size of the Buffer in bytes. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) { - return Buffer.New(device, size, BufferFlags.ConstantBuffer, usage); + return Buffer.New(device, bufferSize, BufferFlags.ConstantBuffer, usage); } /// - /// Creates a new constant buffer with usage. + /// Creates a new Constant Buffer with usage. /// - /// Type of the constant buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// A constant buffer + /// A new instance of . public static Buffer New(GraphicsDevice device) where T : unmanaged { - return Buffer.New(device, 1, BufferFlags.ConstantBuffer, GraphicsResourceUsage.Dynamic); + return Buffer.New(device, elementCount: 1, BufferFlags.ConstantBuffer, GraphicsResourceUsage.Dynamic); } /// - /// Creates a new constant buffer with usage. + /// Creates a new Constant Buffer with initial data. /// - /// Type of the constant buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the constant buffer. - /// The usage of this resource. - /// A constant buffer - public static Buffer New(GraphicsDevice device, ref T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) where T : unmanaged + /// The value to initialize the Constant Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ref readonly T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) where T : unmanaged { - return Buffer.New(device, ref value, BufferFlags.ConstantBuffer, usage); + return Buffer.New(device, in value, BufferFlags.ConstantBuffer, usage); } /// - /// Creates a new constant buffer with usage. + /// Creates a new Constant Buffer with initial data. /// - /// Type of the constant buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the constant buffer. - /// The usage of this resource. - /// A constant buffer - public static Buffer New(GraphicsDevice device, T[] value, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) where T : unmanaged + /// The data to initialize the Constant Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) where T : unmanaged { - return Buffer.New(device, value, BufferFlags.ConstantBuffer, usage:usage); + return Buffer.New(device, data, BufferFlags.ConstantBuffer, usage:usage); } /// - /// Creates a new constant buffer with usage. + /// Creates a new Constant Buffer with initial data. /// /// The . - /// The value to initialize the constant buffer. - /// The usage of this resource. - /// A constant buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) + /// The data to initialize the Constant Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) { - return Buffer.New(device, value, 0, BufferFlags.ConstantBuffer, usage:usage); + return Buffer.New(device, data, elementSize: 0, BufferFlags.ConstantBuffer, usage:usage); } /// - /// Creates a new constant buffer with usage. + /// Creates a new Constant Buffer with initial data. /// /// The . - /// The value to initialize the constant buffer. - /// The usage of this resource. - /// A constant buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) + /// The data pointer to the data to initialize the Constant Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, GraphicsResourceUsage usage = GraphicsResourceUsage.Dynamic) { - return Buffer.New(device, value, 0, BufferFlags.ConstantBuffer, usage); + return Buffer.New(device, dataPointer, 0, BufferFlags.ConstantBuffer, usage); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.Index.cs b/sources/engine/Stride.Graphics/Buffer.Index.cs index e58ae15290..d43d5988d8 100644 --- a/sources/engine/Stride.Graphics/Buffer.Index.cs +++ b/sources/engine/Stride.Graphics/Buffer.Index.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,103 +22,125 @@ // THE SOFTWARE. using System; -using Stride.Games; namespace Stride.Graphics { public partial class Buffer { /// - /// Index buffer helper methods. + /// Helper methods for creating Index Buffers. /// + /// + /// An Index Buffer is a that stores indices that reference vertices in a Vertex Buffer. + /// They are used for efficient rendering of triangle lists / strip. They are, however, limited to only 16-bit or 32-bit + /// values. + /// + /// + /// public static class Index { /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer of a given size. /// /// The . - /// The size in bytes. - /// The usage. - /// A index buffer - public static Buffer New(GraphicsDevice device, int size, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// Size of the Buffer in bytes. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, size, BufferFlags.IndexBuffer, usage); + return Buffer.New(device, bufferSize, BufferFlags.IndexBuffer, usage); } /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer. /// - /// Type of the index buffer to get the sizeof from + /// Type of the indices stored in the Buffer. It can only be or . /// The . - /// The usage. - /// A index buffer + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . public static Buffer New(GraphicsDevice device, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, 1, BufferFlags.IndexBuffer, usage); + return Buffer.New(device, elementCount: 1, BufferFlags.IndexBuffer, usage); } /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer with initial data. /// - /// Type of the index buffer to get the sizeof from + /// Type of the indices stored in the Buffer. It can only be or . /// The . - /// The value to initialize the index buffer. - /// The usage of this resource. - /// A index buffer - public static Buffer New(GraphicsDevice device, ref T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + /// The value to initialize the Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ref readonly T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged { - return Buffer.New(device, ref value, BufferFlags.IndexBuffer, usage); + return Buffer.New(device, in value, BufferFlags.IndexBuffer, usage); } /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer with initial data. /// - /// Type of the index buffer to get the sizeof from + /// Type of the indices stored in the Buffer. It can only be or . /// The . - /// The value to initialize the index buffer. - /// The usage of this resource. - /// A index buffer - public static Buffer New(GraphicsDevice device, T[] value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + /// The data to initialize the Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged { - return Buffer.New(device, (ReadOnlySpan)value, BufferFlags.IndexBuffer, usage:usage); + return Buffer.New(device, (ReadOnlySpan) data, BufferFlags.IndexBuffer, usage:usage); } /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer with initial data. /// /// The . - /// The value to initialize the index buffer. - /// Set to true if the buffer is using a 32 bit index or false for 16 bit index. - /// The usage of this resource. - /// A index buffer - public static Buffer New(GraphicsDevice device, byte[] value, bool is32BitIndex, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// The data to initialize the Index Buffer. + /// + /// if the Buffer will use 32-bit indices; for using 16-bit indices. + /// + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, byte[] data, bool is32BitIndex, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - return Buffer.New(device, value, is32BitIndex ? 4 : 2, BufferFlags.IndexBuffer, PixelFormat.None, usage); + return Buffer.New(device, data, elementSize: (is32BitIndex ? 4 : 2), BufferFlags.IndexBuffer, PixelFormat.None, usage); } /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer with initial data. /// /// The . - /// The value to initialize the index buffer. - /// The usage of this resource. - /// A index buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// The data to initialize the Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - return Buffer.New(device, value, 0, BufferFlags.IndexBuffer, PixelFormat.None, usage); + return Buffer.New(device, data, elementSize: 0, BufferFlags.IndexBuffer, PixelFormat.None, usage); } /// - /// Creates a new index buffer with uasge by default. + /// Creates a new Index Buffer. /// /// The . - /// The value to initialize the index buffer. - /// The usage of this resource. - /// A index buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// The data pointer to the data to initialize the Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - return Buffer.New(device, value, 0, BufferFlags.IndexBuffer, usage); + return Buffer.New(device, dataPointer, elementSize: 0, BufferFlags.IndexBuffer, usage); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.Raw.cs b/sources/engine/Stride.Graphics/Buffer.Raw.cs index da943a36d9..5dc501c7b8 100644 --- a/sources/engine/Stride.Graphics/Buffer.Raw.cs +++ b/sources/engine/Stride.Graphics/Buffer.Raw.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,99 +22,120 @@ // THE SOFTWARE. using System; -using Stride.Games; namespace Stride.Graphics { public partial class Buffer { /// - /// Raw buffer helper methods. + /// Helper methods for creating Raw Buffers. /// /// - /// Example in HLSL: ByteAddressBuffer or RWByteAddressBuffer for raw buffers supporting unordered access. + /// A Raw Buffer is a that can be read in shaders as raw bytes. + /// They are unformatted Buffers that can be accessed at byte level. + /// + /// An example of this kind of Buffer in SDSL would be: + /// + /// ByteAddressBuffer bab; + /// RWByteAddressBuffer rwbab; // For Raw Buffers supporting unordered access + /// + /// /// + /// + /// public static class Raw { /// - /// Creates a new Raw buffer uasge. + /// Creates a new Raw Buffer of a given size. /// /// The . - /// The size in bytes. - /// The additional bindings (for example, to create a combined raw/index buffer, pass ) - /// The usage. - /// A Raw buffer - public static Buffer New(GraphicsDevice device, int size, BufferFlags additionalBindings = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// Size of the Buffer in bytes. + /// Additional flags. For example, you can specify to create a combined Raw/Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, BufferFlags additionalFlags = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, size, BufferFlags.RawBuffer | additionalBindings, usage); + return Buffer.New(device, bufferSize, BufferFlags.RawBuffer | additionalFlags, usage); } /// - /// Creates a new Raw buffer with uasge by default. + /// Creates a new Raw Buffer. /// - /// Type of the Raw buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The additional bindings (for example, to create a combined raw/index buffer, pass ) - /// The usage. - /// A Raw buffer - public static Buffer New(GraphicsDevice device, BufferFlags additionalBindings = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// Additional flags. For example, you can specify to create a combined Raw/Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, BufferFlags additionalFlags = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, 1, BufferFlags.RawBuffer | additionalBindings, usage); + return Buffer.New(device, elementCount: 1, BufferFlags.RawBuffer | additionalFlags, usage); } /// - /// Creates a new Raw buffer with uasge by default. + /// Creates a new Raw Buffer with initial data. /// - /// Type of the Raw buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the Raw buffer. - /// The additional bindings (for example, to create a combined raw/index buffer, pass ) - /// The usage of this resource. - /// A Raw buffer - public static Buffer New(GraphicsDevice device, ref T value, BufferFlags additionalBindings = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The value to initialize the Raw Buffer. + /// Additional flags. For example, you can specify to create a combined Raw/Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ref readonly T value, BufferFlags additionalFlags = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, ref value, BufferFlags.RawBuffer | additionalBindings, usage); + return Buffer.New(device, in value, BufferFlags.RawBuffer | additionalFlags, usage); } /// - /// Creates a new Raw buffer with uasge by default. + /// Creates a new Raw Buffer with initial data. /// - /// Type of the Raw buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the Raw buffer. - /// The additional bindings (for example, to create a combined raw/index buffer, pass ) - /// The usage of this resource. - /// A Raw buffer - public static Buffer New(GraphicsDevice device, T[] value, BufferFlags additionalBindings = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The data to initialize the Raw Buffer. + /// Additional flags. For example, you can specify to create a combined Raw/Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data, BufferFlags additionalFlags = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, (ReadOnlySpan)value, BufferFlags.RawBuffer | additionalBindings, usage:usage); + return Buffer.New(device, (ReadOnlySpan) data, BufferFlags.RawBuffer | additionalFlags, PixelFormat.None, usage); } /// - /// Creates a new Raw buffer with uasge by default. + /// Creates a new Raw Buffer with initial data. /// /// The . - /// The value to initialize the Raw buffer. - /// The additional bindings (for example, to create a combined raw/index buffer, pass ) - /// The usage of this resource. - /// A Raw buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, BufferFlags additionalBindings = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// The data to initialize the Raw Buffer. + /// Additional flags. For example, you can specify to create a combined Raw/Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, BufferFlags additionalFlags = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, value, 0, BufferFlags.RawBuffer | additionalBindings, usage:usage); + return Buffer.New(device, data, elementSize: 0, BufferFlags.RawBuffer | additionalFlags, PixelFormat.None, usage); } /// - /// Creates a new Raw buffer with uasge by default. + /// Creates a new Raw Buffer with initial data. /// /// The . - /// The value to initialize the Raw buffer. - /// The additional bindings (for example, to create a combined raw/index buffer, pass ) - /// The usage of this resource. - /// A Raw buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, BufferFlags additionalBindings = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// The data pointer to the data to initialize the Raw Buffer. + /// Additional flags. For example, you can specify to create a combined Raw/Index Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, BufferFlags additionalFlags = BufferFlags.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, value, 0, BufferFlags.RawBuffer | additionalBindings, usage); + return Buffer.New(device, dataPointer, elementSize: 0, BufferFlags.RawBuffer | additionalFlags, usage); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.Structured.cs b/sources/engine/Stride.Graphics/Buffer.Structured.cs index da9d99cc60..3a52af40a9 100644 --- a/sources/engine/Stride.Graphics/Buffer.Structured.cs +++ b/sources/engine/Stride.Graphics/Buffer.Structured.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,259 +22,287 @@ // THE SOFTWARE. using System; -using Stride.Games; namespace Stride.Graphics { public partial class Buffer { /// - /// Structured buffer helper methods. + /// Helper methods for creating Structured Buffers. /// /// - /// Example in HLSL: StructuredBuffer<float4> or RWStructuredBuffer<float4> for structured buffers supporting unordered access. + /// A Structured Buffer is a that can be read in shaders using a structured format. + /// They are an array of uniformly sized structures. + /// + /// An example of this kind of Buffer in SDSL would be: + /// + /// StructuredBuffer<float4> sb; + /// RWStructuredBuffer<float4> rwsb; // For Structured Buffers supporting unordered access + /// + /// /// + /// + /// public static class Structured { /// - /// Creates a new Structured buffer accessible as a and optionally as a . + /// Creates a new Structured Buffer of a given size. /// /// The . - /// The number of element in this buffer. - /// Size of the struct. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// A Structured buffer - public static Buffer New(GraphicsDevice device, int count, int elementSize, bool isUnorderedAccess = false) + /// The number of elements in the Buffer. + /// The size in bytes of each element (the structure). + /// if the Buffer should support unordered access (RW in SDSL). + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount, int elementSize, bool unorderedAccess = false) { var bufferFlags = BufferFlags.StructuredBuffer | BufferFlags.ShaderResource; - if (isUnorderedAccess) + if (unorderedAccess) bufferFlags |= BufferFlags.UnorderedAccess; - return Buffer.New(device, count * elementSize, elementSize, bufferFlags); + return Buffer.New(device, elementCount * elementSize, elementSize, bufferFlags, PixelFormat.None); } /// - /// Creates a new Structured buffer accessible as a and optionally as a . + /// Creates a new Structured Buffer of a given size. /// - /// Type of the element in the structured buffer + /// Type of the data stored in the Buffer. /// The . - /// The number of element in this buffer. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// A Structured buffer - public static Buffer New(GraphicsDevice device, int count, bool isUnorderedAccess = false) where T : unmanaged + /// The number of elements in the Buffer. + /// if the Buffer should support unordered access (RW in SDSL). + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount, bool unorderedAccess = false) where T : unmanaged { var bufferFlags = BufferFlags.StructuredBuffer | BufferFlags.ShaderResource; - if (isUnorderedAccess) + if (unorderedAccess) bufferFlags |= BufferFlags.UnorderedAccess; - return Buffer.New(device, count, bufferFlags); + return Buffer.New(device, elementCount, bufferFlags); } /// - /// Creates a new Structured buffer usage. + /// Creates a new Structured Buffer with initial data. /// - /// The . /// Type of the Structured buffer to get the sizeof from - /// The value to initialize the Structured buffer. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// A Structured buffer - public static Buffer New(GraphicsDevice device, T[] value, bool isUnorderedAccess = false) where T : unmanaged + /// The . + /// The data to initialize the Structured Buffer. + /// if the Buffer should support unordered access (RW in SDSL). + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data, bool unorderedAccess = false) where T : unmanaged { var bufferFlags = BufferFlags.StructuredBuffer | BufferFlags.ShaderResource; - if (isUnorderedAccess) + if (unorderedAccess) bufferFlags |= BufferFlags.UnorderedAccess; - return Buffer.New(device, (ReadOnlySpan)value, bufferFlags); + return Buffer.New(device, (ReadOnlySpan) data, bufferFlags); } /// - /// Creates a new Structured buffer usage. + /// Creates a new Structured Buffer with initial data. /// /// The . - /// The value to initialize the Structured buffer. - /// Size of the element. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// A Structured buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, int elementSize, bool isUnorderedAccess = false) + /// The data to initialize the Structured Buffer. + /// The size in bytes of each element (the structure). + /// if the Buffer should support unordered access (RW in SDSL). + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, int elementSize, bool unorderedAccess = false) { var bufferFlags = BufferFlags.StructuredBuffer | BufferFlags.ShaderResource; - if (isUnorderedAccess) + if (unorderedAccess) bufferFlags |= BufferFlags.UnorderedAccess; - return Buffer.New(device, value, elementSize, bufferFlags); + return Buffer.New(device, data, elementSize, bufferFlags); } /// - /// Creates a new Structured buffer usage. + /// Creates a new Structured Buffer with initial data. /// /// The . - /// The value to initialize the Structured buffer. - /// Size of the element. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// A Structured buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, int elementSize, bool isUnorderedAccess = false) + /// The data pointer to the data to initialize the Structured Buffer. + /// The size in bytes of each element (the structure). + /// if the Buffer should support unordered access (RW in SDSL). + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, int elementSize, bool unorderedAccess = false) { var bufferFlags = BufferFlags.StructuredBuffer | BufferFlags.ShaderResource; - if (isUnorderedAccess) + if (unorderedAccess) bufferFlags |= BufferFlags.UnorderedAccess; - return Buffer.New(device, value, elementSize, bufferFlags); + return Buffer.New(device, dataPointer, elementSize, bufferFlags, PixelFormat.None); } } + /// - /// StructuredAppend buffer helper methods. + /// Helper methods for creating Structured Append Buffers. /// /// - /// Example in HLSL: AppendStructuredBuffer<float4> or ConsumeStructuredBuffer<float4>. + /// A Structured Append Buffer (also known as Append / Consume Buffer) is a that + /// allows atomic append operations from shaders. They work like a stack: elements can be appended to the end. + /// They are a special kind of Structured Buffers, so they are also an array of uniformly sized structures. + /// + /// An example of this kind of Buffer in SLSL would be: + /// + /// AppendStructuredBuffer<float4> asb; + /// ConsumeStructuredBuffer<float4> csb; + /// + /// /// + /// + /// public static class StructuredAppend { + private const BufferFlags StructuredAppendBufferFlags = BufferFlags.StructuredAppendBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; + /// - /// Creates a new StructuredAppend buffer accessible as a and as a . + /// Creates a new Structured Append Buffer of a given size. /// /// The . - /// The number of element in this buffer. - /// Size of the struct. - /// A StructuredAppend buffer - public static Buffer New(GraphicsDevice device, int count, int elementSize) + /// The number of elements in the Buffer. + /// The size in bytes of each element (the structure). + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount, int elementSize) { - const BufferFlags BufferFlags = BufferFlags.StructuredAppendBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, count * elementSize, elementSize, BufferFlags); + return Buffer.New(device, elementCount * elementSize, elementSize, StructuredAppendBufferFlags, PixelFormat.None); } /// - /// Creates a new StructuredAppend buffer accessible as a and optionally as a . + /// Creates a new Structured Append Buffer of a given size. /// + /// Type of the data stored in the Buffer. /// The . - /// Type of the element in the structured buffer - /// The number of element in this buffer. - /// A Structured buffer - public static Buffer New(GraphicsDevice device, int count) where T : unmanaged + /// The number of elements in the Buffer. + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount) where T : unmanaged { - const BufferFlags BufferFlags = BufferFlags.StructuredAppendBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, count, BufferFlags); + return Buffer.New(device, elementCount, StructuredAppendBufferFlags); } /// - /// Creates a new StructuredAppend buffer usage. + /// Creates a new Structured Append Buffer with initial data. /// + /// Type of the data stored in the Buffer. /// The . - /// Type of the StructuredAppend buffer to get the sizeof from - /// The value to initialize the StructuredAppend buffer. - /// A StructuredAppend buffer - public static Buffer New(GraphicsDevice device, T[] value) where T : unmanaged + /// The data to initialize the Structured Append Buffer. + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data) where T : unmanaged { - const BufferFlags BufferFlags = BufferFlags.StructuredAppendBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, (ReadOnlySpan)value, BufferFlags); + return Buffer.New(device, (ReadOnlySpan) data, StructuredAppendBufferFlags); } /// - /// Creates a new StructuredAppend buffer usage. + /// Creates a new Structured Append Buffer with initial data. /// /// The . - /// The value to initialize the StructuredAppend buffer. - /// Size of the element. - /// A StructuredAppend buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, int elementSize) + /// The data to initialize the Structured Append Buffer. + /// The size in bytes of each element (the structure). + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, int elementSize) { - const BufferFlags BufferFlags = BufferFlags.StructuredAppendBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, value, elementSize, BufferFlags); + return Buffer.New(device, data, elementSize, StructuredAppendBufferFlags); } /// - /// Creates a new StructuredAppend buffer usage. + /// Creates a new Structured Append Buffer with initial data. /// /// The . - /// The value to initialize the StructuredAppend buffer. - /// Size of the element. - /// A StructuredAppend buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, int elementSize) + /// The data pointer to the data to initialize the Structured Append Buffer. + /// The size in bytes of each element (the structure). + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, int elementSize) { - const BufferFlags BufferFlags = BufferFlags.StructuredAppendBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, value, elementSize, BufferFlags); + return Buffer.New(device, dataPointer, elementSize, StructuredAppendBufferFlags, PixelFormat.None); } } + /// - /// StructuredCounter buffer helper methods. + /// Helper methods for creating Structured Counter Buffers. /// /// - /// Example in HLSL: StructuredBuffer<float4> or RWStructuredBuffer<float4> for structured buffers supporting unordered access. + /// A Structured Counter Buffer is a that allows atomic append operations + /// from shaders (similar to Append / Consume Buffers, see ), but with an associated counter. + /// That counter can be read / written atomically from shaders. + /// They are a special kind of Structured Buffers, so they are also an array of uniformly sized structures. + /// + /// An example of this kind of Buffer in SLSL would be: + /// + /// StructuredBuffer<float4> sb; + /// RWStructuredBuffer<float4> rwsb; // For structured buffers supporting unordered access + /// + /// /// + /// + /// public static class StructuredCounter { + const BufferFlags StructuredCounterBufferFlags = BufferFlags.StructuredCounterBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; + /// - /// Creates a new StructuredCounter buffer accessible as a and as a . + /// Creates a new Structured Counter Buffer of a given size. /// /// The . - /// The number of element in this buffer. - /// Size of the struct. - /// A StructuredCounter buffer - public static Buffer New(GraphicsDevice device, int count, int elementSize) + /// The number of elements in the Buffer. + /// The size in bytes of each element (the structure). + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount, int elementSize) { - const BufferFlags BufferFlags = BufferFlags.StructuredCounterBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, count * elementSize, elementSize, BufferFlags); + return Buffer.New(device, elementCount* elementSize, elementSize, StructuredCounterBufferFlags, PixelFormat.None); } /// - /// Creates a new StructuredCounter buffer accessible as a and optionally as a . + /// Creates a new Structured Counter Buffer of a given size. /// - /// Type of the element in the structured buffer + /// Type of the data stored in the Buffer. /// The . - /// The number of element in this buffer. - /// A Structured buffer - public static Buffer New(GraphicsDevice device, int count) where T : unmanaged + /// The number of elements in the Buffer. + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount) where T : unmanaged { - const BufferFlags BufferFlags = BufferFlags.StructuredCounterBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, count, BufferFlags); + return Buffer.New(device, elementCount, StructuredCounterBufferFlags); } /// - /// Creates a new StructuredCounter buffer usage. + /// Creates a new Structured Counter Buffer with initial data. /// /// Type of the StructuredCounter buffer to get the sizeof from /// The . - /// The value to initialize the StructuredCounter buffer. - /// A StructuredCounter buffer - public static Buffer New(GraphicsDevice device, T[] value) where T : unmanaged + /// The data to initialize the Structured Counter Buffer. + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data) where T : unmanaged { - const BufferFlags BufferFlags = BufferFlags.StructuredCounterBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, (ReadOnlySpan)value, BufferFlags); + return Buffer.New(device, (ReadOnlySpan) data, StructuredCounterBufferFlags); } /// - /// Creates a new StructuredCounter buffer usage. + /// Creates a new Structured Counter Buffer with initial data. /// /// The . - /// The value to initialize the StructuredCounter buffer. - /// Size of the element. - /// A StructuredCounter buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, int elementSize) + /// The data to initialize the Structured Counter Buffer. + /// The size in bytes of each element (the structure). + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, int elementSize) { - const BufferFlags BufferFlags = BufferFlags.StructuredCounterBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, value, elementSize, BufferFlags); + return Buffer.New(device, data, elementSize, StructuredCounterBufferFlags); } /// - /// Creates a new StructuredCounter buffer usage. + /// Creates a new Structured Counter Buffer with initial data. /// /// The . - /// The value to initialize the StructuredCounter buffer. - /// Size of the element. - /// A StructuredCounter buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, int elementSize) + /// The data pointer to the data to initialize the Structured Counter Buffer. + /// The size in bytes of each element (the structure). + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, int elementSize) { - const BufferFlags BufferFlags = BufferFlags.StructuredCounterBuffer | BufferFlags.ShaderResource | BufferFlags.UnorderedAccess; - return Buffer.New(device, value, elementSize, BufferFlags); + return Buffer.New(device, dataPointer, elementSize, StructuredCounterBufferFlags, PixelFormat.None); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.Typed.cs b/sources/engine/Stride.Graphics/Buffer.Typed.cs index 6f95cfd411..59f096943c 100644 --- a/sources/engine/Stride.Graphics/Buffer.Typed.cs +++ b/sources/engine/Stride.Graphics/Buffer.Typed.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,77 +22,93 @@ // THE SOFTWARE. using System; -using Stride.Games; namespace Stride.Graphics { public partial class Buffer { /// - /// Typed buffer helper methods. + /// Helper methods for creating Typed Buffers. /// /// - /// Example in HLSL: Buffer<float4>. + /// A Typed Buffer is a that is accessed through a Shader Resource View with a specific format. + /// Because of this, they are created with that specific format. + /// + /// An example of this kind of Buffer in SDSL would be: + /// + /// Buffer<float4> tb; + /// + /// /// + /// + /// public static class Typed { /// - /// Creates a new Typed buffer uasge. + /// Creates a new Typed Buffer of a given size. /// /// The . - /// The number of data with the following viewFormat. - /// The view format of the buffer. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// The usage. - /// A Typed buffer - public static Buffer New(GraphicsDevice device, int count, PixelFormat viewFormat, bool isUnorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// The number of data with the following viewFormat. + /// The format of the typed elements in the Buffer. + /// if the Buffer should support unordered access (RW in SDSL). + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int elementCount, PixelFormat elementFormat, bool unorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, count * viewFormat.SizeInBytes(), BufferFlags.ShaderResource | (isUnorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), viewFormat, usage); + return Buffer.New(device, bufferSize: elementCount * elementFormat.SizeInBytes(), BufferFlags.ShaderResource | (unorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), elementFormat, usage); } /// - /// Creates a new Typed buffer uasge. + /// Creates a new Typed Buffer with initial data. /// - /// Type of the Typed buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the Typed buffer. - /// The view format of the buffer. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// The usage of this resource. - /// A Typed buffer - public static Buffer New(GraphicsDevice device, T[] value, PixelFormat viewFormat, bool isUnorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The data to initialize the Typed Buffer. + /// The format of the typed elements in the Buffer. + /// if the Buffer should support unordered access (RW in SDSL). + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data, PixelFormat elementFormat, bool unorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, (ReadOnlySpan)value, BufferFlags.ShaderResource | (isUnorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), viewFormat, usage); + return Buffer.New(device, (ReadOnlySpan)data, BufferFlags.ShaderResource | (unorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), elementFormat, usage); } /// - /// Creates a new Typed buffer uasge. + /// Creates a new Typed Buffer with initial data. /// - /// Type of the Typed buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the Typed buffer. - /// The view format of the buffer. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// The usage of this resource. - /// A Typed buffer - public static Buffer New(GraphicsDevice device, ReadOnlySpan value, PixelFormat viewFormat, bool isUnorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The data to initialize the Typed Buffer. + /// The format of the typed elements in the Buffer. + /// if the Buffer should support unordered access (RW in SDSL). + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, PixelFormat elementFormat, bool unorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, value, BufferFlags.ShaderResource | (isUnorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), viewFormat, usage); + return Buffer.New(device, data, BufferFlags.ShaderResource | (unorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), elementFormat, usage); } /// - /// Creates a new Typed buffer uasge. + /// Creates a new Typed Buffer with initial data. /// /// The . - /// The value to initialize the Typed buffer. - /// The view format of the buffer. - /// if set to true this buffer supports unordered access (RW in HLSL). - /// The usage of this resource. - /// A Typed buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, PixelFormat viewFormat, bool isUnorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// The data pointer to the data to initialize the Typed Buffer. + /// The format of the typed elements in the Buffer. + /// if the Buffer should support unordered access (RW in SDSL). + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, PixelFormat elementFormat, bool unorderedAccess = false, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return Buffer.New(device, value, 0, BufferFlags.ShaderResource | (isUnorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), viewFormat, usage); + return Buffer.New(device, dataPointer, 0, BufferFlags.ShaderResource | (unorderedAccess ? BufferFlags.UnorderedAccess : BufferFlags.None), elementFormat, usage); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.Vertex.cs b/sources/engine/Stride.Graphics/Buffer.Vertex.cs index e17cdcc429..624bf024aa 100644 --- a/sources/engine/Stride.Graphics/Buffer.Vertex.cs +++ b/sources/engine/Stride.Graphics/Buffer.Vertex.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,106 +22,125 @@ // THE SOFTWARE. using System; -using Stride.Games; namespace Stride.Graphics { public partial class Buffer { /// - /// Vertex buffer helper methods. + /// Helper methods for creating Vertex Buffers. /// + /// + /// A Vertex Buffer is a that is used as input to the vertex shader stage. + /// They store vertex data for 3D models (positions, normals, UVs, etc.). + /// + /// + /// public static class Vertex { /// - /// Creates a new Vertex buffer with uasge by default. + /// Creates a new Vertex Buffer of a given size. /// /// The . - /// The size in bytes. - /// The usage. - /// The bind flags, can be combined with to use the buffer as a stream output target. - /// - /// A Vertex buffer - /// - public static Buffer New(GraphicsDevice device, int size, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, BufferFlags bindFlags = BufferFlags.VertexBuffer) + /// Size of the Buffer in bytes. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// + /// Additional Buffer flags. For example, you can specify to use the Buffer as a Stream Output target. + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, BufferFlags additionalFlags = BufferFlags.None) { - return Buffer.New(device, size, bindFlags, usage); + return Buffer.New(device, bufferSize, BufferFlags.VertexBuffer | additionalFlags, usage); } /// - /// Creates a new Vertex buffer with uasge by default. + /// Creates a new Vertex Buffer. /// - /// Type of the Vertex buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The usage. - /// A Vertex buffer + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . public static Buffer New(GraphicsDevice device, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, 1, BufferFlags.VertexBuffer, usage); + return Buffer.New(device, elementCount: 1, BufferFlags.VertexBuffer, usage); } /// - /// Creates a new Vertex buffer with uasge by default. + /// Creates a new Vertex Buffer with initial data. /// - /// Type of the Vertex buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . /// The value to initialize the Vertex buffer. - /// The usage of this resource. - /// A Vertex buffer - public static Buffer New(GraphicsDevice device, ref T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ref readonly T value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged { - return Buffer.New(device, ref value, BufferFlags.VertexBuffer, usage); + return Buffer.New(device, in value, BufferFlags.VertexBuffer, usage); } /// - /// Creates a new Vertex buffer with uasge by default. + /// Creates a new Vertex Buffer with initial data. /// - /// Type of the Vertex buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// The value to initialize the Vertex buffer. - /// The usage of this resource. - /// A Vertex buffer - public static Buffer New(GraphicsDevice device, T[] value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + /// The data to initialize the Vertex buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] data, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged { - return Buffer.New(device, (ReadOnlySpan)value, BufferFlags.VertexBuffer, usage:usage); + return Buffer.New(device, (ReadOnlySpan) data, BufferFlags.VertexBuffer, PixelFormat.None, usage); } /// - /// Creates a new Vertex buffer with usage by default. + /// Creates a new Vertex Buffer. /// - /// Type of the Vertex buffer to get the sizeof from + /// Type of the data stored in the Buffer. /// The . - /// Number of vertex in this buffer with the sizeof(T). - /// The usage. - /// A Vertex buffer - public static Buffer New(GraphicsDevice device, int vertexBufferCount, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The number of vertices in this Buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, int vertexCount, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return Buffer.New(device, vertexBufferCount, BufferFlags.VertexBuffer, usage); + return Buffer.New(device, vertexCount, BufferFlags.VertexBuffer, usage); } /// - /// Creates a new Vertex buffer with uasge by default. + /// Creates a new Vertex Buffer with initial data. /// /// The . - /// The value to initialize the Vertex buffer. - /// The usage of this resource. - /// A Vertex buffer - public static Buffer New(GraphicsDevice device, Span value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// The data to initialize the Vertex buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan data, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - return Buffer.New(device, value, 0, BufferFlags.VertexBuffer, usage:usage); + return Buffer.New(device, data, elementSize: 0, BufferFlags.VertexBuffer, PixelFormat.None, usage); } /// - /// Creates a new Vertex buffer with uasge by default. + /// Creates a new Vertex Buffer with initial data. /// /// The . - /// The value to initialize the Vertex buffer. - /// The usage of this resource. - /// A Vertex buffer - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer value, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// The data pointer to the data to initialize the Vertex buffer. + /// + /// The usage for the Buffer, which determines who can read/write data. By default, it is . + /// + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - return Buffer.New(device, value, 0, BufferFlags.VertexBuffer, usage); + return Buffer.New(device, dataPointer, elementSize: 0, BufferFlags.VertexBuffer, usage); } } } diff --git a/sources/engine/Stride.Graphics/Buffer.cs b/sources/engine/Stride.Graphics/Buffer.cs index 638aaad092..aa6112264f 100644 --- a/sources/engine/Stride.Graphics/Buffer.cs +++ b/sources/engine/Stride.Graphics/Buffer.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,19 +24,37 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Stride.Core; + using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; +using Stride.Core.UnsafeExtensions; using Stride.Graphics.Data; namespace Stride.Graphics { /// - /// All-in-One Buffer class linked . + /// All-in-one GPU Buffer that is able to represent many types of Buffers (shader Constant Buffers, Structured Buffers, + /// Raw Buffers, Argument Buffers, etc.). /// /// - /// This class is able to create constant buffers, indexelementCountrtex buffers, structured buffer, raw buffers, argument buffers. + /// constains static methods for creating new Buffers by specifying all their characteristics. + /// + /// Also look for the following static methods that aid in the creation of specific kinds of Buffers: + /// (for Argument Buffers), (for Constant Buffers), + /// (for Index Buffers), (for Raw Buffers), + /// (for Structured Buffers), (for Typed Buffers), + /// and (for Vertex Buffers). + /// + /// Consult the documentation of your graphics API for more information on each kind of Buffer. /// + /// + /// + /// + /// + /// + /// + /// + /// [DataSerializer(typeof(BufferSerializer))] [ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] [ContentSerializer(typeof(DataContentSerializer))] @@ -45,700 +63,989 @@ public partial class Buffer : GraphicsResource protected int elementCount; private BufferDescription bufferDescription; - public Buffer() - { - } + + public Buffer() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The . - protected Buffer(GraphicsDevice device) : base(device) - { - } + /// The the Buffer belongs to. + protected Buffer(GraphicsDevice device) : base(device) { } /// - /// Gets the description of this buffer. + /// Initializes a new instance of the class. /// - public BufferDescription Description - { - get { return bufferDescription; } - } + /// The the Buffer belongs to. + /// + /// A string to use as a name for identifying the Buffer. Useful when debugging. + /// Specify to use the type's name instead. + /// + protected Buffer(GraphicsDevice device, string? name) : base(device, name) { } + /// - /// Value that identifies how the buffer is to be read from and written to. + /// Gets a description of the Buffer. /// - public GraphicsResourceUsage Usage - { - get { return bufferDescription.Usage; } - } + public BufferDescription Description => bufferDescription; /// - /// Buffer flags describing the type of buffer. + /// Gets a value that indicates how the Buffer is to be read from and written to. /// - public BufferFlags Flags - { - get { return bufferDescription.BufferFlags; } - } + public GraphicsResourceUsage Usage => bufferDescription.Usage; /// - /// Gets the size of the buffer in bytes. + /// Gets a combination of flags describing the type of the Buffer. /// - /// - /// The size of the buffer in bytes. - /// - public int SizeInBytes - { - get { return bufferDescription.SizeInBytes; } - } + public BufferFlags Flags => bufferDescription.BufferFlags; /// - /// The size of the structure (in bytes) when it represents a structured/typed buffer. + /// Gets the size of the Buffer in bytes. /// - public int StructureByteStride - { - get { return bufferDescription.StructureByteStride; } - } + public int SizeInBytes => bufferDescription.SizeInBytes; + + /// + /// Gets the size of the structure (in bytes) when the Buffer represents a typed / structured buffer. + /// + public int StructureByteStride => bufferDescription.StructureByteStride; /// - /// Gets the number of elements. + /// Gets the number of elements in the Buffer. /// /// - /// This value is valid for structured buffers, raw buffers and index buffers that are used as a SharedResourceView. + /// This value is valid for Structured Buffers, Raw Buffers, and Index Buffers that are used as a Shared Resource View. /// public int ElementCount { - get - { - return elementCount; - } - protected set - { - elementCount = value; - } + get => elementCount; + protected set => elementCount = value; } /// - /// Gets the type of this buffer view. + /// Gets a combination of flags describing how a View over the Buffer should behave. /// public BufferFlags ViewFlags { get; private set; } /// - /// Gets the format of this buffer view. + /// Gets the format of the elements of the Buffer as interpreted through a View. /// public PixelFormat ViewFormat { get; private set; } /// - /// The initial Append/Consume buffer counter offset. A value of -1 indicates the current offset - /// should be kept. Any other values set the hidden counter for that Appendable/Consumable - /// Buffer. This value is only relevant for Buffers which have the 'Append' or 'Counter' - /// flag, otherwise it is ignored. The value get's initialized to -1. + /// Gets or sets the initial Append / Consume Buffer counter offset. /// + /// + /// A value of -1 indicates the current offset should be kept. + /// Any other values set the hidden counter for that Appendable / Consumable Buffer. + /// The default value is -1. + /// + /// + /// This value is only relevant for Buffers which have the or + /// flags, otherwise it is ignored. + /// public int InitialCounterOffset { get; set; } = -1; + /// - /// Return an equivalent staging texture CPU read-writable from this instance. + /// Returns a staging Buffer that can be read / written by the CPU that is equivalent to the Buffer. /// - /// A new instance of this buffer as a staging resource + /// A new instance of the Buffer as a staging resource. public Buffer ToStaging() { - var stagingDesc = Description; - stagingDesc.Usage = GraphicsResourceUsage.Staging; - stagingDesc.BufferFlags = BufferFlags.None; - return new Buffer(GraphicsDevice).InitializeFromImpl(stagingDesc, BufferFlags.None, ViewFormat, IntPtr.Zero); + var stagingDesc = Description with { Usage = GraphicsResourceUsage.Staging, BufferFlags = BufferFlags.None }; + + var buffer = GraphicsDevice.IsDebugMode + ? new Buffer(GraphicsDevice, GetDebugName(in stagingDesc, elementType: null, ElementCount)) + : new Buffer(GraphicsDevice, Name); + + return buffer.InitializeFromImpl(in stagingDesc, BufferFlags.None, ViewFormat, dataPointer: IntPtr.Zero); } /// - /// Clones this instance. + /// Returns a new Buffer with exactly the same characteristics as the Buffer, but does not copy its contents. /// - /// A clone of this instance - /// - /// This method will not copy the content of the buffer to the clone - /// + /// A clone of the Buffer. public Buffer Clone() { - return new Buffer(GraphicsDevice).InitializeFromImpl(Description, ViewFlags, ViewFormat, IntPtr.Zero); + return new Buffer(GraphicsDevice, Name).InitializeFromImpl(in bufferDescription, ViewFlags, ViewFormat, dataPointer: IntPtr.Zero); } + #region Initialization + /// - /// Gets the content of this buffer to an array of data. + /// Initializes this instance with the provided options. /// - /// The type of the T data. + /// A structure describing the Buffer characteristics. + /// A combination of flags determining how the Views over the Buffer should behave. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The data pointer to the data to initialize the Buffer with. + /// This same instance of already initialized. + protected partial Buffer InitializeFromImpl(ref readonly BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer); + + #endregion + + #region Debug + + /// + /// Generates a debug-friendly name for the Buffer based on its usage, flags, and size. + /// + /// The description of the Buffer. + /// The name of the type of the elements in the Buffer. + /// The size in bytes of an element in the Buffer. + /// A string representing the debug name of the Buffer. + private static string GetDebugName(ref readonly BufferDescription bufferDescription, string? elementType = null, int elementSize = 0) + { + return GetDebugName(bufferDescription.Usage, bufferDescription.BufferFlags, bufferDescription.SizeInBytes, elementType, elementSize); + } + + /// + /// Generates a debug-friendly name for the Buffer based on its usage, flags, and size. + /// + /// The description of the Buffer. + /// The name of the type of the elements in the Buffer. + /// The size in bytes of an element in the Buffer. + /// A string representing the debug name of the Buffer. + private static string GetDebugName(GraphicsResourceUsage bufferUsage, BufferFlags bufferFlags, int sizeInBytes, string? elementType = null, int elementSize = 0) + { + var usage = bufferUsage != GraphicsResourceUsage.Default + ? $"{bufferUsage} " + : string.Empty; + + var flags = bufferFlags switch + { + BufferFlags.ConstantBuffer => "Constant Buffer", + BufferFlags.IndexBuffer => "Index Buffer", + BufferFlags.VertexBuffer => "Vertex Buffer", + BufferFlags.ArgumentBuffer => "Argument Buffer", + BufferFlags.RawBuffer => "Raw Buffer", + BufferFlags.StructuredAppendBuffer => "Structured Append Buffer", + BufferFlags.StructuredCounterBuffer => "Structured Counter Buffer", + + _ => "Buffer" + }; + var typeOfElement = elementType is not null ? $" of {elementType}" : string.Empty; + var elementCount = elementSize > 0 && elementSize < sizeInBytes + ? sizeInBytes / elementSize + : 0; + var elements = elementCount > 0 ? $", {elementCount} elements" : string.Empty; + + return $"{usage}{flags}{typeOfElement} ({sizeInBytes} bytes{elements})"; + } + + #endregion + + #region GetData: Reading data from the Buffer + + /// + /// Gets the contents of the Buffer as an array of data. + /// + /// The type of the data to read from the Buffer. + /// The . + /// An array of data with the contents of the Buffer. /// - /// This method is only working when called from the main thread that is accessing the main . - /// This method creates internally a stagging resource if this texture is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource - /// for optimal performances. - public TData[] GetData(CommandList commandList) where TData : unmanaged + /// This method only works when called from the main thread that is accessing the main . + /// + /// This method creates internally a staging resource (if this is not already a staging resource), + /// copies to it and map it to memory. Use a method that allows to specify an explicit staging resource for optimal performance. + /// + /// + public unsafe TData[] GetData(CommandList commandList) where TData : unmanaged { - var toData = new TData[this.Description.SizeInBytes / Unsafe.SizeOf()]; + var toData = new TData[SizeInBytes / sizeof(TData)]; GetData(commandList, toData); return toData; } /// - /// Copies the content of this buffer to an array of data. + /// Copies the contents of the Buffer to an array of data. /// - /// The type of the T data. - /// The destination buffer to receive a copy of the texture datas. + /// The type of the data to read from the Buffer. + /// The . + /// The destination array where to copy the Buffer contents. + /// + /// The length of the destination data buffer () is larger than the size of the Buffer. + /// /// - /// This method is only working when called from the main thread that is accessing the main . - /// This method creates internally a stagging resource if this texture is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource - /// for optimal performances. + /// This method only works when called from the main thread that is accessing the main . + /// + /// This method creates internally a staging resource (if this is not already a staging resource), + /// copies to it and map it to memory. Use a method that allows to specify an explicit staging resource for optimal performance. + /// + /// public void GetData(CommandList commandList, TData[] toData) where TData : unmanaged { // Get data from this resource - if (this.Description.Usage == GraphicsResourceUsage.Staging) + if (Description.Usage == GraphicsResourceUsage.Staging) { // Directly if this is a staging resource GetData(commandList, this, toData); } else { - // Unefficient way to use the Copy method using dynamic staging texture + // Inefficient way to use the Copy method using dynamic staging Buffer using var throughStaging = ToStaging(); GetData(commandList, throughStaging, toData); } } /// - /// Copies the content of this buffer to an array of data. + /// Gets a single data element of the Buffer. /// - /// The type of the T data. - /// The destination buffer to receive a copy of the texture datas. + /// The type of the data to read from the Buffer. + /// The . + /// When this method returns, contains the element read from the Buffer. + /// + /// The length of the destination data buffer () is larger than the size of the Buffer. + /// /// - /// This method is only working when called from the main thread that is accessing the main . - /// This method creates internally a stagging resource if this texture is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource - /// for optimal performances. + /// This method only works when called from the main thread that is accessing the main . + /// + /// This method creates internally a staging resource (if this is not already a staging resource), + /// copies to it and map it to memory. Use a method that allows to specify an explicit staging resource for optimal performance. + /// + /// public void GetData(CommandList commandList, ref TData toData) where TData : unmanaged { // Get data from this resource - if (this.Description.Usage == GraphicsResourceUsage.Staging) + if (Description.Usage == GraphicsResourceUsage.Staging) { // Directly if this is a staging resource GetData(commandList, this, ref toData); } else { - // Unefficient way to use the Copy method using dynamic staging texture + // Inefficient way to use the Copy method using dynamic staging Buffer using var throughStaging = ToStaging(); GetData(commandList, throughStaging, ref toData); } } /// - /// Copies the content of this buffer from GPU memory to an array of data on CPU memory using a specific staging resource. + /// Copies a single data element of the Buffer from GPU memory to data on CPU memory using a specific staging resource. /// - /// The type of the T data. - /// The staging buffer used to transfer the buffer. - /// To data. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + /// The type of the data to read from the Buffer. + /// The . + /// The staging buffer used to transfer the data from GPU memory. + /// When this method returns, contains the element read from the Buffer. + /// + /// The length of the destination data buffer () is larger than the size of the Buffer. + /// /// - /// This method is only working when called from the main thread that is accessing the main . + /// This method only works when called from the main thread that is accessing the main . /// - public unsafe void GetData(CommandList commandList, Buffer stagingTexture, ref TData toData) where TData : unmanaged + public void GetData(CommandList commandList, Buffer stagingBuffer, ref TData toData) where TData : unmanaged { - GetData(commandList, stagingTexture, MemoryMarshal.CreateSpan(ref toData, 1)); + GetData(commandList, stagingBuffer, MemoryMarshal.CreateSpan(ref toData, 1)); } /// - /// Copies the content of this buffer from GPU memory to an array of data on CPU memory using a specific staging resource. + /// Copies data from the Buffer from GPU memory into an array on CPU memory using a specific staging resource. /// - /// The type of the T data. - /// The staging buffer used to transfer the buffer. - /// To data. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + /// The type of the data to read from the Buffer. + /// The . + /// The staging buffer used to transfer the data from GPU memory. + /// Array where the read data should be copied. + /// + /// The length of the destination data buffer () is larger than the size of the Buffer. + /// /// - /// This method is only working when called from the main thread that is accessing the main . + /// This method only works when called from the main thread that is accessing the main . /// - public unsafe void GetData(CommandList commandList, Buffer stagingTexture, TData[] toData) where TData : unmanaged + public void GetData(CommandList commandList, Buffer stagingBuffer, TData[] toData) where TData : unmanaged { - GetData(commandList, stagingTexture, toData.AsSpan()); + GetData(commandList, stagingBuffer, toData.AsSpan()); } /// - /// Copies the content an array of data on CPU memory to this buffer into GPU memory. + /// Copies the contents of the Buffer from GPU memory to a CPU memory pointer using a specific staging resource. /// - /// The type of the T data. /// The . - /// The data to copy from. - /// The offset in bytes to write to. - /// + /// The staging buffer used to transfer the data from GPU memory. + /// To destination data pointer. + /// + /// The length of the destination data buffer () is larger than the size of the Buffer. + /// /// - /// See the unmanaged documentation about Map/UnMap for usage and restrictions. + /// This method only works when called from the main thread that is accessing the main . /// - public void SetData(CommandList commandList, ref TData fromData, int offsetInBytes = 0) where TData : unmanaged + [Obsolete("This method is obsolete. Use the Span-based methods instead")] + public unsafe void GetData(CommandList commandList, Buffer stagingBuffer, DataPointer toData) { - SetData(commandList, MemoryMarshal.CreateReadOnlySpan(ref fromData, 1), offsetInBytes); + GetData(commandList, stagingBuffer, new Span((void*) toData.Pointer, toData.Size)); } /// - /// Copies the content an array of data on CPU memory to this buffer into GPU memory. + /// Copies the content of the Buffer from GPU memory to a CPU memory pointer using a specific staging resource. /// - /// The type of the T data. + /// The type of the data to read from the Buffer. /// The . - /// The data to copy from. - /// The offset in bytes to write to. - /// + /// The staging buffer used to transfer the data from GPU memory. + /// To destination span where the read data will be written. + /// + /// The length of the destination data buffer () is larger than the size of the Buffer. + /// /// - /// See the unmanaged documentation about Map/UnMap for usage and restrictions. + /// This method only works when called from the main thread that is accessing the main . /// - public void SetData(CommandList commandList, TData[] fromData, int offsetInBytes = 0) where TData : unmanaged + public unsafe void GetData(CommandList commandList, Buffer stagingBuffer, Span toData) where TData : unmanaged { - SetData(commandList, (ReadOnlySpan)fromData.AsSpan(), offsetInBytes); + // Check destination buffer has valid size + int toDataSizeInBytes = toData.Length * sizeof(TData); + if (toDataSizeInBytes > SizeInBytes) + throw new ArgumentException("The length of the destination data buffer is larger than the size of the Buffer"); + + // Copy the Buffer to a staging resource + if (!ReferenceEquals(this, stagingBuffer)) + commandList.Copy(this, stagingBuffer); + + // Map the staging resource to CPU-readable memory + var mappedResource = commandList.MapSubResource(stagingBuffer, subResourceIndex: 0, MapMode.Read); + var fromData = new ReadOnlySpan((void*) mappedResource.DataBox.DataPointer, toData.Length); + fromData.CopyTo(toData); + commandList.UnmapSubResource(mappedResource); } + #endregion + + #region SetData: Writing data into the Buffer + /// - /// Copies the content of this buffer from GPU memory to a CPU memory using a specific staging resource. + /// Copies the contents an array of data on CPU memory into the Buffer in GPU memory. /// - /// The staging buffer used to transfer the buffer. - /// To data pointer. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + /// The type of the data to write into the Buffer. + /// The . + /// The data to copy from. + /// The offset in bytes to write to. + /// + /// is only supported for Buffers declared with . + /// /// - /// This method is only working when called from the main thread that is accessing the main . + /// See and for more information about + /// usage and restrictions. /// - [Obsolete("Use span instead")] - public unsafe void GetData(CommandList commandList, Buffer stagingTexture, DataPointer toData) + public void SetData(CommandList commandList, ref readonly TData fromData, int offsetInBytes = 0) where TData : unmanaged { - GetData(commandList, stagingTexture, new Span((void*)toData.Pointer, toData.Size)); + SetData(commandList, MemoryMarshal.CreateReadOnlySpan(in fromData, 1), offsetInBytes); } /// - /// Copies the content of this buffer from GPU memory to a CPU memory using a specific staging resource. + /// Copies the contents of an array of data on CPU memory into the Buffer in GPU memory. /// - /// The staging buffer used to transfer the buffer. - /// To data pointer. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + /// The type of the data to write into the Buffer. + /// The . + /// The array of data to copy from. + /// The offset in bytes from the start of the Buffer where data is to be written. + /// + /// is only supported for Buffers declared with . + /// /// - /// This method is only working when called from the main thread that is accessing the main . + /// See and for more information about + /// usage and restrictions. /// - public unsafe void GetData(CommandList commandList, Buffer stagingTexture, Span toData) where T : unmanaged + public void SetData(CommandList commandList, TData[] fromData, int offsetInBytes = 0) where TData : unmanaged { - int toDataInBytes = toData.Length * sizeof(T); - - // Check size validity of data to copy to - if (toDataInBytes > this.Description.SizeInBytes) - throw new ArgumentException($"Length of {nameof(toData)} is larger than size of buffer"); - - // Copy the texture to a staging resource - if (!ReferenceEquals(this, stagingTexture)) - commandList.Copy(this, stagingTexture); - - // Map the staging resource to a CPU accessible memory - var mappedResource = commandList.MapSubresource(stagingTexture, 0, MapMode.Read); - fixed (void* pointer = toData) - Unsafe.CopyBlockUnaligned(pointer, (void*)mappedResource.DataBox.DataPointer, (uint)toDataInBytes); - // Make sure that we unmap the resource in case of an exception - commandList.UnmapSubresource(mappedResource); + SetData(commandList, (ReadOnlySpan) fromData.AsSpan(), offsetInBytes); } /// - /// Copies the content an array of data on CPU memory to this buffer into GPU memory. + /// Copies data from a pointer to data on CPU memory into the Buffer in GPU memory. /// /// The . - /// A data pointer. - /// The offset in bytes to write to. - /// + /// The pointer to the data to copy from. + /// The offset in bytes from the start of the Buffer where data is to be written. + /// + /// is only supported for Buffers declared with . + /// /// - /// See the unmanaged documentation about Map/UnMap for usage and restrictions. + /// See and for more information about + /// usage and restrictions. /// - [Obsolete("Use span instead")] + [Obsolete("This method is obsolete. Use the Span-based methods instead")] public unsafe void SetData(CommandList commandList, DataPointer fromData, int offsetInBytes = 0) { - SetData(commandList, new ReadOnlySpan((void*)fromData.Pointer, fromData.Size), offsetInBytes); + SetData(commandList, new ReadOnlySpan((void*) fromData.Pointer, fromData.Size), offsetInBytes); } /// - /// Copies the content an array of data on CPU memory to this buffer into GPU memory. + /// Copies data from a span of data on CPU memory into the Buffer in GPU memory. /// + /// The type of the data to write into the Buffer. /// The . - /// A data pointer. - /// The offset in bytes to write to. - /// + /// The span of data to copy from. + /// The offset in bytes from the start of the Buffer where data is to be written. + /// + /// The length of is larger than the size of the Buffer. + /// + /// + /// is only supported for Buffers declared with . + /// /// - /// See the unmanaged documentation about Map/UnMap for usage and restrictions. + /// See and for more information about + /// usage and restrictions. /// - public unsafe void SetData(CommandList commandList, ReadOnlySpan fromData, int offsetInBytes = 0) where T : unmanaged + public unsafe void SetData(CommandList commandList, ReadOnlySpan fromData, int offsetInBytes = 0) where TData : unmanaged { - int sizeInBytes = fromData.Length * sizeof(T); - // Check size validity of data to copy to - if (sizeInBytes > this.Description.SizeInBytes) - throw new ArgumentException("Size of data to upload larger than size of buffer"); + // Check size validity of data to copy from + var fromDataAsBytes = fromData.AsBytes(); + var fromDataSizeInBytes = fromDataAsBytes.Length; + if (fromDataAsBytes.Length > SizeInBytes) + throw new ArgumentException("The length of the source data to upload is larger than the size of the Buffer"); - fixed (void* pointer = fromData) + // If the Buffer is declared as Default usage, we can only use UpdateSubresource, which is not optimal but better than nothing + if (Description.Usage == GraphicsResourceUsage.Default) { - // If this texture is declared as default usage, we can only use UpdateSubresource, which is not optimal but better than nothing - if (this.Description.Usage == GraphicsResourceUsage.Default) + // Set up the dest region inside the Buffer + if (Description.BufferFlags.HasFlag(BufferFlags.ConstantBuffer)) { - // Setup the dest region inside the buffer - if ((this.Description.BufferFlags & BufferFlags.ConstantBuffer) != 0) - { - commandList.UpdateSubresource(this, 0, new DataBox((nint)pointer, 0, 0)); - } - else - { - var destRegion = new ResourceRegion(offsetInBytes, 0, 0, offsetInBytes + sizeInBytes, 1, 1); - commandList.UpdateSubresource(this, 0, new DataBox((nint)pointer, 0, 0), destRegion); - } + commandList.UpdateSubResource(this, subResourceIndex: 0, fromDataAsBytes); } else { - if (offsetInBytes > 0) - throw new ArgumentException("offset is only supported for textured declared with ResourceUsage.Default", "offsetInBytes"); - - var mappedResource = commandList.MapSubresource(this, 0, Usage == GraphicsResourceUsage.Staging ? MapMode.Write : MapMode.WriteDiscard); - Unsafe.CopyBlockUnaligned((void*)mappedResource.DataBox.DataPointer, pointer, (uint)sizeInBytes); - commandList.UnmapSubresource(mappedResource); + var destRegion = new ResourceRegion(left: offsetInBytes, top: 0, front: 0, right: offsetInBytes + fromDataSizeInBytes, bottom: 1, back: 1); + commandList.UpdateSubResource(this, subResourceIndex: 0, fromDataAsBytes, destRegion); } } + else + { + if (offsetInBytes > 0) + throw new ArgumentException("offset is only supported for Buffers declared with ResourceUsage.Default", nameof(offsetInBytes)); + + // Map the Buffer to CPU-writable memory + var mappedResource = commandList.MapSubResource(this, subResourceIndex: 0, Usage == GraphicsResourceUsage.Staging ? MapMode.Write : MapMode.WriteDiscard); + var toData = new Span((void*) mappedResource.DataBox.DataPointer, fromData.Length); + fromData.CopyTo(toData); + commandList.UnmapSubResource(mappedResource); + } } + #endregion + /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// The description of the buffer. - /// View format used if the buffer is used as a shared resource view. - /// An instance of a new + /// The description of the Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// A new instance of . public static Buffer New(GraphicsDevice device, BufferDescription description, PixelFormat viewFormat = PixelFormat.None) { var bufferType = description.BufferFlags; - return new Buffer(device).InitializeFromImpl(description, bufferType, viewFormat, IntPtr.Zero); + + var buffer = device.IsDebugMode + ? new Buffer(device, GetDebugName(in description)) + : new Buffer(device); + + return buffer.InitializeFromImpl(in description, bufferType, viewFormat, dataPointer: IntPtr.Zero); } /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// Size of the buffer in bytes. - /// The buffer flags to specify the type of buffer. - /// The usage. - /// An instance of a new + /// Size of the Buffer in bytes. + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . public static Buffer New(GraphicsDevice device, int bufferSize, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return New(device, bufferSize, 0, bufferFlags, PixelFormat.None, usage); + return New(device, bufferSize, elementSize: 0, bufferFlags, viewFormat: PixelFormat.None, usage); } /// - /// Creates a new instance. + /// Creates a new . /// + /// The type of elements the Buffer will contain. /// The . - /// Number of T elment in this buffer. - /// The buffer flags to specify the type of buffer. - /// The usage. - /// An instance of a new + /// Number of elements of type the Buffer will contain. + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . public static Buffer New(GraphicsDevice device, int elementCount, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - int bufferSize = Unsafe.SizeOf() * elementCount; int elementSize = Unsafe.SizeOf(); + int bufferSize = elementSize * elementCount; var description = NewDescription(bufferSize, elementSize, bufferFlags, usage); - return new Buffer(device, description, bufferFlags, PixelFormat.None, IntPtr.Zero); + + return new Buffer(device, description, bufferFlags, viewFormat: PixelFormat.None, dataPointer: IntPtr.Zero); } /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// Size of the buffer in bytes. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new - public static Buffer New(GraphicsDevice device, int bufferSize, BufferFlags bufferFlags, PixelFormat viewFormat, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// Size of the Buffer in bytes. + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return New(device, bufferSize, 0, bufferFlags, viewFormat, usage); + return New(device, bufferSize, elementSize: 0, bufferFlags, viewFormat, usage); } /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// Size of the buffer in bytes. - /// Size of an element in the buffer. - /// The buffer flags to specify the type of buffer. - /// The usage. - /// An instance of a new + /// Size of the Buffer in bytes. + /// Size of an element in the Buffer. + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . public static Buffer New(GraphicsDevice device, int bufferSize, int elementSize, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return New(device, bufferSize, elementSize, bufferFlags, PixelFormat.None, usage); + return New(device, bufferSize, elementSize, bufferFlags, viewFormat: PixelFormat.None, usage); } /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// Size of the buffer in bytes. - /// Size of an element in the buffer. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new - public static Buffer New(GraphicsDevice device, int bufferSize, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// Size of the Buffer in bytes. + /// Size of an element in the Buffer. + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + public static Buffer New(GraphicsDevice device, int bufferSize, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { viewFormat = CheckPixelFormat(bufferFlags, elementSize, viewFormat); + var description = NewDescription(bufferSize, elementSize, bufferFlags, usage); - return new Buffer(device).InitializeFromImpl(description, bufferFlags, viewFormat, IntPtr.Zero); + + var buffer = device.IsDebugMode + ? new Buffer(device, GetDebugName(in description, elementType: null, elementSize)) + : new Buffer(device); + + return buffer.InitializeFromImpl(in description, bufferFlags, viewFormat, dataPointer: IntPtr.Zero); } /// - /// Creates a new instance. + /// Creates a new . /// + /// The type of the element the Buffer will contain. /// The . - /// Type of the buffer, to get the sizeof from. - /// The initial value of this buffer. - /// The buffer flags to specify the type of buffer. - /// The usage. - /// An instance of a new - public static Buffer New(GraphicsDevice device, ref T value, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The initial value for the element in the Buffer. + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + public static Buffer New(GraphicsDevice device, ref readonly T value, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return New(device, ref value, bufferFlags, PixelFormat.None, usage); + return New(device, in value, bufferFlags, viewFormat: PixelFormat.None, usage); } /// - /// Creates a new instance. + /// Creates a new . /// + /// The type of the element the Buffer will contain. /// The . - /// Type of the buffer, to get the sizeof from. - /// The initial value of this buffer. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new - public static unsafe Buffer New(GraphicsDevice device, ref T value, BufferFlags bufferFlags, PixelFormat viewFormat, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The initial value for the element in the Buffer. + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + public static unsafe Buffer New(GraphicsDevice device, ref readonly T value, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - int bufferSize = Unsafe.SizeOf(); - int elementSize = ((bufferFlags & BufferFlags.StructuredBuffer) != 0) ? Unsafe.SizeOf() : 0; + int sizeOfT = sizeof(T); + int bufferSize = sizeOfT; + int elementSize = bufferFlags.HasFlag(BufferFlags.StructuredBuffer) ? sizeOfT : 0; viewFormat = CheckPixelFormat(bufferFlags, elementSize, viewFormat); var description = NewDescription(bufferSize, elementSize, bufferFlags, usage); - fixed (T* v = &value) - return new Buffer(device, description, bufferFlags, viewFormat, (nint)v); + + fixed (T* ptrValue = &value) + return device.IsDebugMode + ? new Buffer(device, description, bufferFlags, viewFormat, (nint) ptrValue, GetDebugName(in description, typeof(T).Name, elementSize)) + : new Buffer(device, description, bufferFlags, viewFormat, (nint) ptrValue); } /// - /// Creates a new instance. + /// Creates a new . /// - /// Type of the buffer, to get the sizeof from. + /// The type of the elements the Buffer will contain. /// The . - /// The initial value of this buffer. - /// The buffer flags to specify the type of buffer. - /// The usage. - /// An instance of a new + /// The initial data the Buffer will contain. + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . public static Buffer New(GraphicsDevice device, T[] initialValue, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return New(device, (ReadOnlySpan)initialValue.AsSpan(), bufferFlags, PixelFormat.None, usage); + return New(device, (ReadOnlySpan) initialValue.AsSpan(), bufferFlags, viewFormat: PixelFormat.None, usage); } /// - /// Creates a new instance. + /// Creates a new . /// - /// Type of the buffer, to get the sizeof from. + /// The type of the elements the Buffer will contain. /// The . - /// The initial value of this buffer. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new - public static Buffer New(GraphicsDevice device, T[] initialValue, BufferFlags bufferFlags, PixelFormat viewFormat, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged + /// The initial value of the Buffer. + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + public static Buffer New(GraphicsDevice device, T[] initialValue, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - return New(device, (ReadOnlySpan)initialValue.AsSpan(), bufferFlags, PixelFormat.None, usage); + return New(device, (ReadOnlySpan) initialValue.AsSpan(), bufferFlags, viewFormat, usage); } /// - /// Creates a new instance. + /// Creates a new . /// - /// Type of the buffer, to get the sizeof from. + /// The type of the elements the Buffer will contain. /// The . - /// The initial data this buffer will contain. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new + /// The initial data the Buffer will contain. + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . public static unsafe Buffer New(GraphicsDevice device, ReadOnlySpan initialValues, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) where T : unmanaged { - int bufferSize = sizeof(T) * initialValues.Length; int elementSize = sizeof(T); + int bufferSize = elementSize * initialValues.Length; + viewFormat = CheckPixelFormat(bufferFlags, elementSize, viewFormat); var description = NewDescription(bufferSize, elementSize, bufferFlags, usage); - fixed (void* pointer = initialValues) - return new Buffer(device, description, bufferFlags, viewFormat, (nint)pointer); + + fixed (void* ptrInitialValue = initialValues) + return device.IsDebugMode + ? new Buffer(device, description, bufferFlags, viewFormat, (nint) ptrInitialValue, GetDebugName(in description, typeof(T).Name, elementSize)) + : new Buffer(device, description, bufferFlags, viewFormat, (nint) ptrInitialValue); } /// - /// Creates a new instance from a byte array. + /// Creates a new from a byte array. /// /// The . - /// The initial value of this buffer. - /// Size of an element. Must be equal to 2 or 4 for an index buffer, or to the size of a struct for a structured/typed buffer. Can be set to 0 for other buffers. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new - public static Buffer New(GraphicsDevice device, ReadOnlySpan initialValue, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// The initial data the Buffer will contain. + /// + /// The size of an element in bytes. + /// + /// For Index Buffers this must be equal to 2 (ths size of ) or 4 bytes (the size of ). + /// For Structured / Typed Buffers this must be equal to the size of the element . + /// For other types of Buffers, this can be set to 0. + /// + /// + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + public static Buffer New(GraphicsDevice device, ReadOnlySpan initialValues, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - return new Buffer(device).InitializeFrom(initialValue, elementSize, bufferFlags, viewFormat, usage); + var buffer = device.IsDebugMode + ? new Buffer(device, GetDebugName(GraphicsResourceUsage.Default, bufferFlags, initialValues.Length, elementType: null, elementSize)) + : new Buffer(device); + + return buffer.InitializeFrom(initialValues, elementSize, bufferFlags, viewFormat, usage); } /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// The data pointer. - /// Size of the element. - /// The buffer flags to specify the type of buffer. - /// The usage. - /// An instance of a new - [Obsolete("Use span instead")] + /// The data pointer to the initial data the Buffer will contain. + /// + /// The size of an element in bytes. + /// + /// For Index Buffers this must be equal to 2 (ths size of ) or 4 bytes (the size of ). + /// For Structured / Typed Buffers this must be equal to the size of the element . + /// For other types of Buffers, this can be set to 0. + /// + /// + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] public static Buffer New(GraphicsDevice device, DataPointer dataPointer, int elementSize, BufferFlags bufferFlags, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - return New(device, dataPointer, elementSize, bufferFlags, PixelFormat.None, usage); + return New(device, dataPointer, elementSize, bufferFlags, viewFormat: PixelFormat.None, usage); } /// - /// Creates a new instance. + /// Creates a new . /// /// The . - /// The data pointer. - /// Size of the element. - /// The buffer flags to specify the type of buffer. - /// The view format must be specified if the buffer is declared as a shared resource view. - /// The usage. - /// An instance of a new - [Obsolete("Use span instead")] - public static Buffer New(GraphicsDevice device, DataPointer dataPointer, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// The data pointer to the initial data the Buffer will contain. + /// + /// The size of an element in bytes. + /// + /// For Index Buffers this must be equal to 2 (ths size of ) or 4 bytes (the size of ). + /// For Structured / Typed Buffers this must be equal to the size of the element . + /// For other types of Buffers, this can be set to 0. + /// + /// + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// A new instance of . + [Obsolete("This method is obsolete. Use the span-based methods instead")] + public static Buffer New(GraphicsDevice device, DataPointer dataPointer, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { int bufferSize = dataPointer.Size; + viewFormat = CheckPixelFormat(bufferFlags, elementSize, viewFormat); + var description = NewDescription(bufferSize, elementSize, bufferFlags, usage); - return new Buffer(device).InitializeFromImpl(description, bufferFlags, viewFormat, dataPointer.Pointer); + + var buffer = device.IsDebugMode + ? new Buffer(device, GetDebugName(in description, elementType: null, elementSize)) + : new Buffer(device); + + return new Buffer(device).InitializeFromImpl(in description, bufferFlags, viewFormat, dataPointer.Pointer); } - internal unsafe Buffer InitializeFrom(ReadOnlySpan initialValue, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// + /// Initializes this instance with the provided options. + /// + /// The initial data the Buffer will contain. + /// + /// The size of an element in bytes. + /// + /// For Index Buffers this must be equal to 2 (ths size of ) or 4 bytes (the size of ). + /// For Structured / Typed Buffers this must be equal to the size of the element . + /// For other types of Buffers, this can be set to 0. + /// + /// + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The usage for the Buffer, which determines who can read/write data. + /// This same instance of already initialized. + internal unsafe Buffer InitializeFrom(ReadOnlySpan initialValues, int elementSize, BufferFlags bufferFlags, PixelFormat viewFormat = PixelFormat.None, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - int bufferSize = initialValue.Length; + int bufferSize = initialValues.Length; + viewFormat = CheckPixelFormat(bufferFlags, elementSize, viewFormat); var description = NewDescription(bufferSize, elementSize, bufferFlags, usage); - fixed (void* initial = initialValue) - return InitializeFromImpl(description, bufferFlags, viewFormat, (nint)initial); + fixed (void* ptrInitialValue = initialValues) + return InitializeFromImpl(description, bufferFlags, viewFormat, (nint) ptrInitialValue); } + /// + /// Checks the intended format for a is compatible with its type and flags. + /// + /// The buffer flags to specify the type of Buffer. + /// + /// The size of an element in bytes. + /// + /// For Index Buffers this must be equal to 2 (ths size of ) or 4 bytes (the size of ). + /// For Structured / Typed Buffers this must be equal to the size of the element . + /// For other types of Buffers, this can be set to 0. + /// + /// + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The proposed to use. + /// + /// The is an Index Buffer that will be bound as a Shader Resource, + /// but the is neither 2 bytes (sizeof(short)) nor 4 bytes (sizeof(int)). + /// private static PixelFormat CheckPixelFormat(BufferFlags bufferFlags, int elementSize, PixelFormat viewFormat) { - if ((bufferFlags & BufferFlags.IndexBuffer) != 0 && (bufferFlags & BufferFlags.ShaderResource) != 0) - { - if (elementSize != 2 && elementSize != 4) - throw new ArgumentException("Element size must be set to sizeof(short) = 2 or sizeof(int) = 4 for index buffer if index buffer is bound to a ShaderResource", "elementSize"); + if (!bufferFlags.HasFlag(BufferFlags.IndexBuffer) || !bufferFlags.HasFlag(BufferFlags.ShaderResource)) + return viewFormat; - viewFormat = elementSize == 2 ? PixelFormat.R16_UInt : PixelFormat.R32_UInt; - } - return viewFormat; + if (elementSize != 2 && elementSize != 4) + throw new ArgumentException("Element size must be set to sizeof(short) = 2 or sizeof(int) = 4 for Index Buffers if bound as a Shader Resource", nameof(elementSize)); + + return elementSize == 2 ? PixelFormat.R16_UInt : PixelFormat.R32_UInt; } + /// + /// Composes a new structure with the provided options. + /// + /// The size in bytes of the Buffer. + /// The size in bytes of each element (in case of a Structured Buffer). + /// The buffer flags to specify the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// A new . private static BufferDescription NewDescription(int bufferSize, int elementSize, BufferFlags bufferFlags, GraphicsResourceUsage usage) { - return new BufferDescription() + return new BufferDescription { SizeInBytes = bufferSize, - StructureByteStride = (bufferFlags & BufferFlags.StructuredBuffer) != 0 ? elementSize : 0, + StructureByteStride = bufferFlags.HasFlag(BufferFlags.StructuredBuffer) ? elementSize : 0, BufferFlags = bufferFlags, - Usage = usage, + Usage = usage }; } /// - /// Reload from given data if has been reset. + /// Sets the to be recreated with the specified data whenever the it depends on is reset. /// - /// - /// The data pointer. + /// The type of the elements the Buffer will contain. + /// The data to use to recreate the Buffer with. /// This instance. - public Buffer RecreateWith(T[] dataPointer) where T : unmanaged + public Buffer RecreateWith(T[] data) where T : unmanaged { - Reload = (graphicsResource, services) => ((Buffer)graphicsResource).Recreate(dataPointer); + Reload = (graphicsResource, _) => ((Buffer) graphicsResource).Recreate(data); return this; } /// - /// Reload from given data if has been reset. + /// Sets the to be recreated with the specified data whenever the it depends on is reset. /// - /// - /// The data pointer. + /// The data pointer to the data to use to recreate the Buffer with. /// This instance. public Buffer RecreateWith(IntPtr dataPointer) { - Reload = (graphicsResource, services) => ((Buffer)graphicsResource).Recreate(dataPointer); + Reload = (graphicsResource, _) => ((Buffer) graphicsResource).Recreate(dataPointer); return this; } /// - /// Explicitly recreate buffer with given data. Usually called after a reset. + /// Recreates the Buffer explicitly with the provided data. Usually called after the has been reset. /// - /// - /// - public unsafe void Recreate(T[] dataPointer) where T : unmanaged + /// The type of the elements the Buffer will contain. + /// The data to use to recreate the Buffer with. + public unsafe void Recreate(T[] data) where T : unmanaged { - fixed (void* data = dataPointer) - Recreate((nint)data); + fixed (void* ptrData = data) + Recreate((nint) ptrData); } } /// - /// A buffer with typed information. + /// All-in-one GPU buffer that is able to represent many types of Buffers (shader Constant Buffers, Structured Buffers, + /// Raw Buffers, Argument Buffers, etc.), but with typed information. /// - /// Type of an element of this buffer. + /// The type of the elements of the Buffer. + /// + /// constains static methods for creating new Buffers with typed information by specifying all their characteristics. + /// + /// Also look for the following static methods that aid in the creation of specific kinds of Buffers: + /// (for Argument Buffers), (for Constant Buffers), + /// (for Index Buffers), (for Raw Buffers), + /// (for Structured Buffers), (for Typed Buffers), + /// and (for Vertex Buffers). + /// + /// You can also check the methods of for creating Buffers with the maximum flexibility. + /// Consult the documentation of your graphics API for more information on each kind of Buffer. + /// + /// + /// + /// + /// + /// + /// + /// + /// public class Buffer : Buffer where T : unmanaged { - protected internal Buffer(GraphicsDevice device, BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) : base(device) + /// + /// Initializes a new instance of typed . + /// + /// The . + /// The description of the Buffer's characteristics. + /// The buffer flags to specify the type of Buffer. + /// + /// View format used if the Buffer is used as a Shader Resource View, + /// or if not. + /// + /// The data pointer to the initial data the Buffer will contain. + /// + /// A name for the Buffer, used for debugging purposes. + /// Specify to not set a name and use the name of the type instead. + /// + protected internal Buffer(GraphicsDevice device, + BufferDescription description, BufferFlags bufferFlags, PixelFormat viewFormat, + IntPtr dataPointer, + string? name = null) + : base(device, name) { - InitializeFromImpl(description, viewFlags, viewFormat, dataPointer); - this.ElementSize = Unsafe.SizeOf(); - this.ElementCount = Description.SizeInBytes / ElementSize; + InitializeFromImpl(in description, bufferFlags, viewFormat, dataPointer); + + ElementSize = Unsafe.SizeOf(); + ElementCount = SizeInBytes / ElementSize; } /// - /// Gets the size of element T. + /// The size of the elements in this (i.e. the size of ). /// public readonly int ElementSize; /// - /// Gets the content of this texture to an array of data. + /// Gets the contents of the Buffer as an array of data. /// - /// An array of data. - /// This method is only working when called from the main thread that is accessing the main . - /// This method creates internally a stagging resource if this texture is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource - /// for optimal performances. + /// The . + /// An array of data with the contents of the Buffer. + /// + /// This method only works when called from the main thread that is accessing the main . + /// + /// This method creates internally a staging resource (if this is not already a staging resource), + /// copies to it and map it to memory. Use a method that allows to specify an explicit staging resource for optimal performance. + /// + /// public T[] GetData(CommandList commandList) { return GetData(commandList); } /// - /// Copies the content of a single structure data from CPU memory to this buffer into GPU memory. + /// Copies the contents an array of data on CPU memory into the Buffer in GPU memory. /// - /// The . + /// The . /// The data to copy from. /// The offset in bytes to write to. - /// + /// + /// is only supported for Buffers declared with . + /// /// - /// This method is only working when called from the main thread that is accessing the main . See the unmanaged documentation about Map/UnMap for usage and restrictions. + /// See and for more information about + /// usage and restrictions. /// - public void SetData(CommandList commandList, ref T fromData, int offsetInBytes = 0) + public void SetData(CommandList commandList, ref readonly T fromData, int offsetInBytes = 0) { - base.SetData(commandList, ref fromData, offsetInBytes); + base.SetData(commandList, in fromData, offsetInBytes); } /// - /// Copies the content an array of data from CPU memory to this buffer into GPU memory. + /// Copies the contents of an array of data on CPU memory into the Buffer in GPU memory. /// - /// The . - /// The data to copy from. - /// The offset in bytes to write to. + /// The . + /// The array of data to copy from. + /// The offset in bytes from the start of the Buffer where data is to be written. + /// + /// is only supported for Buffers declared with . + /// /// - /// This method is only working when called from the main thread that is accessing the main . See the unmanaged documentation about Map/UnMap for usage and restrictions. + /// See and for more information about + /// usage and restrictions. /// public void SetData(CommandList commandList, T[] fromData, int offsetInBytes = 0) { diff --git a/sources/engine/Stride.Graphics/BufferDescription.cs b/sources/engine/Stride.Graphics/BufferDescription.cs index c082070f1f..9634433175 100644 --- a/sources/engine/Stride.Graphics/BufferDescription.cs +++ b/sources/engine/Stride.Graphics/BufferDescription.cs @@ -6,17 +6,24 @@ namespace Stride.Graphics { /// - /// Describes a buffer. + /// Describes a GPU . /// public struct BufferDescription : IEquatable { /// - /// Initializes a new instance of struct. + /// Initializes a new instance of struct. /// - /// Size of the buffer in bytes. - /// Buffer flags describing the type of buffer. - /// Usage of this buffer. - /// The size of the structure (in bytes) when it represents a structured/typed buffer. Default = 0. + /// Size of the Buffer in bytes. + /// Buffer flags describing the type of Buffer. + /// The usage for the Buffer, which determines who can read/write data. + /// + /// + /// If the Buffer is a Structured Buffer or a Typed Buffer, this parameter indicates + /// the stride of each element of the Buffer (the structure). The stride is not only the size of the + /// structure, but also any padding in between two consecutive elements. + /// + /// For any other kind of Buffer, this parameter can be 0. + /// public BufferDescription(int sizeInBytes, BufferFlags bufferFlags, GraphicsResourceUsage usage, int structureByteStride = 0) { SizeInBytes = sizeInBytes; @@ -25,70 +32,55 @@ public BufferDescription(int sizeInBytes, BufferFlags bufferFlags, GraphicsResou StructureByteStride = structureByteStride; } + /// - /// Size of the buffer in bytes. + /// Size of the in bytes. /// public int SizeInBytes; /// - /// Buffer flags describing the type of buffer. + /// Flags describing the type of . /// public BufferFlags BufferFlags; /// - /// Usage of this buffer. + /// Usage for the , which determines who can read / write data. /// public GraphicsResourceUsage Usage; /// - /// The size of the structure (in bytes) when it represents a structured/typed buffer. + /// The size in bytes of the structure (each element in the ) when it represents a + /// Structured Buffer or a Typed Buffer. /// public int StructureByteStride; - public bool Equals(BufferDescription other) - { - return SizeInBytes == other.SizeInBytes && BufferFlags == other.BufferFlags && Usage == other.Usage && StructureByteStride == other.StructureByteStride; - } - public override bool Equals(object obj) + /// + public readonly bool Equals(BufferDescription other) { - if (ReferenceEquals(null, obj)) return false; - return obj is BufferDescription && Equals((BufferDescription)obj); + return SizeInBytes == other.SizeInBytes + && BufferFlags == other.BufferFlags + && Usage == other.Usage + && StructureByteStride == other.StructureByteStride; } /// - public override int GetHashCode() + public override readonly bool Equals(object obj) { - unchecked - { - var hashCode = SizeInBytes; - hashCode = (hashCode * 397) ^ (int)BufferFlags; - hashCode = (hashCode * 397) ^ (int)Usage; - hashCode = (hashCode * 397) ^ StructureByteStride; - return hashCode; - } - } + if (obj is null) + return false; - /// - /// Implements the operator ==. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator ==(BufferDescription left, BufferDescription right) - { - return left.Equals(right); + return obj is BufferDescription description && Equals(description); } - /// - /// Implements the operator !=. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator !=(BufferDescription left, BufferDescription right) + /// + public override readonly int GetHashCode() { - return !left.Equals(right); + return HashCode.Combine(SizeInBytes, BufferFlags, Usage, StructureByteStride); } + + public static bool operator ==(BufferDescription left, BufferDescription right) => left.Equals(right); + + public static bool operator !=(BufferDescription left, BufferDescription right) => !left.Equals(right); } } diff --git a/sources/engine/Stride.Graphics/BufferFlags.cs b/sources/engine/Stride.Graphics/BufferFlags.cs index f47cd32595..b2db01c2c8 100644 --- a/sources/engine/Stride.Graphics/BufferFlags.cs +++ b/sources/engine/Stride.Graphics/BufferFlags.cs @@ -1,80 +1,81 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using Stride.Core; namespace Stride.Graphics { + /// + /// Identifies the intended use of a graphics when rendering. + /// [Flags] [DataContract] public enum BufferFlags { /// - /// Creates a none buffer. + /// No special flags. /// - /// - /// This is equivalent to . - /// None = 0, /// - /// Creates a constant buffer. + /// The buffer is a constant buffer. /// ConstantBuffer = 1, /// - /// Creates an index buffer. + /// The buffer is an index buffer. /// IndexBuffer = 2, /// - /// Creates a vertex buffer. + /// The buffer is a vertex buffer. /// VertexBuffer = 4, /// - /// Creates a render target buffer. + /// The buffer can be used as a render target. /// RenderTarget = 8, /// - /// Creates a buffer usable as a ShaderResourceView. + /// The buffer can be used as a shader resource. /// ShaderResource = 16, /// - /// Creates an unordered access buffer. + /// The buffer can be used as an unordered access buffer. /// UnorderedAccess = 32, /// - /// Creates a structured buffer. + /// The buffer can be used as a structured buffer. /// StructuredBuffer = 64, /// - /// Creates a structured buffer that supports unordered acccess and append. + /// The buffer can be used as a structured buffer that supports unordered acccess and append. /// StructuredAppendBuffer = UnorderedAccess | StructuredBuffer | 128, /// - /// Creates a structured buffer that supports unordered acccess and counter. + /// The buffer can be used as a structured buffer that supports unordered acccess and counter. /// StructuredCounterBuffer = UnorderedAccess | StructuredBuffer | 256, /// - /// Creates a raw buffer. + /// The buffer is a raw buffer. /// RawBuffer = 512, /// - /// Creates an indirect arguments buffer. + /// The buffer is an indirect arguments buffer. /// ArgumentBuffer = 1024, /// - /// Creates a buffer for the geometry shader stream-output stage. + /// The buffer is a buffer for the geometry shader stream-output stage. /// - StreamOutput = 2048, + StreamOutput = 2048 } } diff --git a/sources/engine/Stride.Graphics/BufferPool.cs b/sources/engine/Stride.Graphics/BufferPool.cs index bd8581c82a..d441b261c4 100644 --- a/sources/engine/Stride.Graphics/BufferPool.cs +++ b/sources/engine/Stride.Graphics/BufferPool.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Runtime.InteropServices; @@ -68,7 +69,7 @@ public void Map(CommandList commandList) using (new DefaultCommandListLock(commandList)) { this.commandList = commandList; - mappedConstantBuffer = commandList.MapSubresource(constantBuffer, 0, MapMode.WriteNoOverwrite); + mappedConstantBuffer = commandList.MapSubResource(constantBuffer, 0, MapMode.WriteNoOverwrite); Data = mappedConstantBuffer.DataBox.DataPointer; } } @@ -82,7 +83,7 @@ public void Unmap() { using (new DefaultCommandListLock(commandList)) { - commandList.UnmapSubresource(mappedConstantBuffer); + commandList.UnmapSubResource(mappedConstantBuffer); mappedConstantBuffer = new MappedResource(); } } @@ -173,6 +174,6 @@ public enum BufferPoolAllocationType /// In practice, on older D3D11 (not 11.1) and OpenGL ES 2.0 hardware, we will use a dedicated cbuffer. /// This has no effect on new API where we can bind cbuffer offsets. /// - UsedMultipleTime, + UsedMultipleTime } } diff --git a/sources/engine/Stride.Graphics/ColorWriteChannels.cs b/sources/engine/Stride.Graphics/ColorWriteChannels.cs index 830e4c2f3f..427dee9180 100644 --- a/sources/engine/Stride.Graphics/ColorWriteChannels.cs +++ b/sources/engine/Stride.Graphics/ColorWriteChannels.cs @@ -1,48 +1,49 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Identifies which components of each pixel of a Render Target can be written to during blending. +/// +/// +/// These flags can be combined with a bitwise OR and set in and . +/// +[Flags] +[DataContract] +public enum ColorWriteChannels { /// - /// Identify which components of each pixel of a render target are writable during blending. + /// None of the data is stored. + /// + None = 0, + + /// + /// Allow data to be stored in the red component (R). + /// + Red = 1, + + /// + /// Allow data to be stored in the green component (G). + /// + Green = 2, + + /// + /// Allow data to be stored in the blue component (B). + /// + Blue = 4, + + /// + /// Allow data to be stored in the alpha component (A). + /// + Alpha = 8, + + /// + /// Allow data to be stored in all components. /// - /// - /// These flags can be combined with a bitwise OR and is used in and . - /// - [Flags] - [DataContract] - public enum ColorWriteChannels - { - /// - /// None of the data are stored. - /// - None = 0, - - /// - /// Allow data to be stored in the red component. - /// - Red = 1, - - /// - /// Allow data to be stored in the green component. - /// - Green = 2, - - /// - /// Allow data to be stored in the blue component. - /// - Blue = 4, - - /// - /// Allow data to be stored in the alpha component. - /// - Alpha = 8, - - /// - /// Allow data to be stored in all components. - /// - All = Alpha | Blue | Green | Red, - } + All = Alpha | Blue | Green | Red } diff --git a/sources/engine/Stride.Graphics/CommandList.cs b/sources/engine/Stride.Graphics/CommandList.cs index 530adc1269..3e4a9e45d8 100644 --- a/sources/engine/Stride.Graphics/CommandList.cs +++ b/sources/engine/Stride.Graphics/CommandList.cs @@ -1,153 +1,131 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; +using System; using Stride.Core.Mathematics; namespace Stride.Graphics { /// - /// Performs resource bindings and primitive-based rendering. See the class to learn more about the class. + /// Represents a list of graphics commands for playback, which can include resource binding, primitive-based rendering, etc. /// public partial class CommandList : GraphicsResourceBase { private const int MaxRenderTargetCount = 8; internal const int MaxViewportAndScissorRectangleCount = 16; + + #region Viewports + private bool viewportDirty = false; private int boundViewportCount; private readonly Viewport[] viewports = new Viewport[MaxViewportAndScissorRectangleCount]; - private int boundScissorCount; - private readonly Rectangle[] scissors = new Rectangle[MaxViewportAndScissorRectangleCount]; - -#pragma warning disable 414 // The field 'CommandList.scissorsDirty' is assigned but its value is never used - // This field is used in CommandList.Direct3D12.cs and CommandList.Vulkan.cs - private bool scissorsDirty = false; -#pragma warning restore 414 - - private Texture depthStencilBuffer; - - private Texture[] renderTargets = new Texture[MaxRenderTargetCount]; - private int renderTargetCount; /// - /// Gets the first viewport. + /// Gets the first viewport bound to the rasterizer stage of the pipeline. /// - /// The first viewport. public Viewport Viewport => viewports[0]; /// - /// Gets the first scissor. - /// - /// The first scissor. - public Rectangle Scissor => scissors[0]; - - /// - /// Gets the depth stencil buffer currently sets on this instance. + /// Gets the array of viewports bound to the rasterizer stage of the pipeline. /// - /// - /// The depth stencil buffer currently sets on this instance. - /// - public Texture DepthStencilBuffer => depthStencilBuffer; - - /// - /// Gets the render target buffer currently sets on this instance. - /// - /// - /// The render target buffer currently sets on this instance. - /// - public Texture RenderTarget => renderTargets[0]; - - public Texture[] RenderTargets => renderTargets; - - public int RenderTargetCount => renderTargetCount; - public Viewport[] Viewports => viewports; - public int ViewportCount => boundViewportCount; - - /// - /// Clears the state and restore the state of the device. - /// - public void ClearState() - { - ClearStateImpl(); - - // Setup empty viewports - for (int i = 0; i < viewports.Length; i++) - viewports[i] = new Viewport(); - - // Setup empty scissors - scissorsDirty = true; - for (int i = 0; i < scissors.Length; i++) - scissors[i] = new Rectangle(); - - // Setup the default render target - var deviceDepthStencilBuffer = GraphicsDevice.Presenter?.DepthStencilBuffer; - var deviceBackBuffer = GraphicsDevice.Presenter?.BackBuffer; - SetRenderTargetAndViewport(deviceDepthStencilBuffer, deviceBackBuffer); - } /// - /// Unbinds all depth-stencil buffer and render targets from the output-merger stage. + /// Gets the number of viewports currently bound to the rasterizer stage of the pipeline. /// - public void ResetTargets() - { - ResetTargetsImpl(); - - depthStencilBuffer = null; - for (int i = 0; i < renderTargets.Length; i++) - renderTargets[i] = null; - } + public int ViewportCount => boundViewportCount; /// - /// Sets a viewport. + /// Binds a single viewport to the rasterizer stage of the pipeline. /// - /// The viewport. - public void SetViewport(Viewport value) + /// The viewport to set. + public void SetViewport(Viewport viewport) { viewportDirty |= boundViewportCount != 1; boundViewportCount = 1; - if (viewports[0] != value) + + if (viewports[0] != viewport) { viewportDirty = true; - viewports[0] = value; + viewports[0] = viewport; } } /// - /// Sets the viewports. + /// Binds an array of viewports to the rasterizer stage of the pipeline. /// - /// The viewport. - public void SetViewports(Viewport[] values) + /// The array of viewports to set. + /// is . + public void SetViewports(Viewport[] viewports) { - SetViewports(values.Length, values); + SetViewports(viewports?.Length ?? 0, viewports); } /// - /// Sets the viewports. + /// Binds an array of viewports to the rasterizer stage of the pipeline. /// - /// The viewport. - public void SetViewports(int viewportCount, Viewport[] values) + /// The number of viewports to set. + /// The array of viewports to set. + /// is . + /// + /// is greater than the number of items in . + /// + public void SetViewports(int viewportCount, Viewport[] viewports) { - viewportDirty |= this.boundViewportCount != viewportCount; + ArgumentNullException.ThrowIfNull(viewports); + ArgumentOutOfRangeException.ThrowIfLessThan(viewports.Length, viewportCount); + + viewportDirty |= boundViewportCount != viewportCount; boundViewportCount = viewportCount; + for (int i = 0; i < viewportCount; i++) { - if (viewports[i] != values[i]) + if (this.viewports[i] != viewports[i]) { viewportDirty = true; - viewports[i] = values[i]; + this.viewports[i] = viewports[i]; } } } + #endregion + + #region Scissor rectangles + + private int boundScissorCount; + private readonly Rectangle[] scissors = new Rectangle[MaxViewportAndScissorRectangleCount]; + +#pragma warning disable 414 // The field 'CommandList.scissorsDirty' is assigned but its value is never used + // This field is used in CommandList.Direct3D12.cs and CommandList.Vulkan.cs + private bool scissorsDirty = false; +#pragma warning restore 414 + + /// - /// Binds a single scissor rectangle to the rasterizer stage. - /// See Set the scissor - /// in the manual for more information. + /// Gets the first scissor rectangle bound to the rasterizer stage of the pipeline. /// - /// The scissor rectangle. + public Rectangle Scissor => scissors[0]; + + /// + /// Gets the array of scissor rectangles bound to the rasterizer stage of the pipeline. + /// + public Rectangle[] Scissors => scissors; + + /// + /// Gets the number of scissor rectangles currently bound to the rasterizer stage of the pipeline. + /// + public int ScissorCount => boundScissorCount; + + /// + /// Binds a single scissor rectangle to the rasterizer stage. + /// + /// The scissor rectangle to set. + /// + /// See Set the scissor + /// in the manual for more information. + /// public void SetScissorRectangle(Rectangle rectangle) { scissorsDirty = true; @@ -157,178 +135,361 @@ public void SetScissorRectangle(Rectangle rectangle) } /// - /// Binds a set of scissor rectangles to the rasterizer stage. - /// See Set the scissor - /// in the manual for more information. + /// Binds a set of scissor rectangles to the rasterizer stage. /// /// The set of scissor rectangles to bind. + /// + /// See Set the scissor + /// in the manual for more information. + /// + /// is . public void SetScissorRectangles(Rectangle[] scissorRectangles) { - SetScissorRectangles(scissorRectangles.Length, scissorRectangles); + SetScissorRectangles(scissorRectangles?.Length ?? 0, scissorRectangles); } /// - /// Binds a set of scissor rectangles to the rasterizer stage. - /// See Set the scissor - /// in the manual for more information. + /// Binds a set of scissor rectangles to the rasterizer stage. /// /// The number of scissor rectangles to bind. /// The set of scissor rectangles to bind. + /// + /// See Set the scissor + /// in the manual for more information. + /// + /// is . + /// + /// is greater than the number of items in . + /// public void SetScissorRectangles(int scissorCount, Rectangle[] scissorRectangles) { + ArgumentNullException.ThrowIfNull(scissorRectangles); + ArgumentOutOfRangeException.ThrowIfLessThan(scissorRectangles.Length, scissorCount); + scissorsDirty = true; boundScissorCount = scissorCount; - for (int i = 0; i < scissorCount; ++i) - { - scissors[i] = scissorRectangles[i]; - } + scissorRectangles.AsSpan(..scissorCount).CopyTo(scissors); + SetScissorRectanglesImpl(scissorCount, scissorRectangles); } + + /// + /// Platform-specific implementation that sets a scissor rectangle to the rasterizer stage. + /// + /// The scissor rectangle to set. + private unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle); + + /// + /// Platform-specific implementation that sets one or more scissor rectangles to the rasterizer stage. + /// + /// The number of scissor rectangles to bind. + /// The set of scissor rectangles to bind. + private unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles); + + #endregion + + #region Render Targets / Depth-Stencil Buffer + + private Texture depthStencilBuffer; + + private readonly Texture[] renderTargets = new Texture[MaxRenderTargetCount]; + private int renderTargetCount; + + + /// + /// Gets the Depth-Stencil Buffer (Z-Buffer) currently bound to the pipeline. + /// + public Texture DepthStencilBuffer => depthStencilBuffer; + + /// + /// Gets the first Render Target currently bound to the pipeline. + /// + public Texture RenderTarget => renderTargets[0]; + + /// + /// Gets the set of Render Targets currently bound to the pipeline. + /// + public Texture[] RenderTargets => renderTargets; + + /// + /// Gets the number of Render Targets currently bound to the pipeline. + /// + public int RenderTargetCount => renderTargetCount; + + + /// + /// Unbinds all the Render Targets and the Depth-Stencil Buffer from the output-merger stage. + /// + public void ResetTargets() + { + ResetTargetsImpl(); + + depthStencilBuffer = null; + renderTargets.AsSpan().Clear(); + } + /// - /// Binds a depth-stencil buffer and a single render target to the output-merger stage. - /// See Use a render target - /// in the manual for more information. + /// Binds a Depth-Stencil Buffer and a single Render Target to the output-merger stage, + /// setting also the viewport according to their dimensions. /// - /// A view of the depth-stencil buffer to bind. - /// A view of the render target to bind. + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// A view of the Render Target to bind. + /// Specify to unbind the currently bound Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTargetAndViewport(Texture depthStencilView, Texture renderTargetView) { depthStencilBuffer = depthStencilView; renderTargets[0] = renderTargetView; - renderTargetCount = renderTargetView != null ? 1 : 0; + renderTargetCount = renderTargetView is not null ? 1 : 0; - CommonSetRenderTargetsAndViewport(depthStencilBuffer, renderTargetCount, renderTargets); + SetRenderTargetsAndViewportImpl(depthStencilBuffer, renderTargetCount, renderTargets); } - + + /// + /// Binds a Depth-Stencil Buffer and two Render Targets to the output-merger stage, + /// setting also the viewport according to their dimensions. + /// + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// A view of the first Render Target to bind. + /// Specify to unbind the currently bound first Render Target. + /// + /// + /// A view of a second Render Target to bind. + /// Specify to unbind the currently bound second Render Target. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTargetAndViewport(Texture depthStencilView, Texture renderTargetView, Texture secondRenderTarget) { depthStencilBuffer = depthStencilView; renderTargets[0] = renderTargetView; renderTargets[1] = secondRenderTarget; - renderTargetCount = renderTargetView != null ? 1 : 0; + renderTargetCount = renderTargetView is not null ? 1 : 0; - if (secondRenderTarget != null) + if (secondRenderTarget is not null) ++renderTargetCount; - CommonSetRenderTargetsAndViewport(depthStencilBuffer, renderTargetCount, renderTargets); + SetRenderTargetsAndViewportImpl(depthStencilBuffer, renderTargetCount, renderTargets); } /// - ///

Bind one or more render targets atomically and the depth-stencil buffer to the output-merger stage. See to learn how to use it.

+ /// Binds one or more Render Targets atomically to the output-merger stage, + /// setting also the viewport according to their dimensions. Also unbinds the current Depth-Stencil Buffer. ///
- /// A set of render target views to bind. + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTargetsAndViewport(Texture[] renderTargetViews) { - SetRenderTargetsAndViewport(null, renderTargetViews); + SetRenderTargetsAndViewport(depthStencilView: null, renderTargetViews); } /// - /// Binds a depth-stencil buffer and a set of render targets to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and one or more Render Targets atomically to the output-merger stage, + /// setting also the viewport according to their dimensions. /// - /// A view of the depth-stencil buffer to bind. - /// The number of render target in . - /// A set of render target views to bind. - /// renderTargetViews + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// The current number of Render Targets to bind. + /// Specify 0 to unbind the currently bound Render Targets. + /// + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// + /// + /// is not zero, but is . + /// + /// + /// has less elements than what specifies. + /// + /// + /// indicates too many Render Targets to set. + /// public void SetRenderTargetsAndViewport(Texture depthStencilView, int renderTargetViewCount, Texture[] renderTargetViews) { depthStencilBuffer = depthStencilView; renderTargetCount = renderTargetViewCount; - for (int i = 0; i < renderTargetCount; i++) - { - renderTargets[i] = renderTargetViews[i]; - } + renderTargetViews.AsSpan(..renderTargetCount).CopyTo(renderTargets); - CommonSetRenderTargetsAndViewport(depthStencilBuffer, renderTargetCount, renderTargets); + SetRenderTargetsAndViewportImpl(depthStencilBuffer, renderTargetCount, renderTargets); } /// - /// Binds a depth-stencil buffer and a set of render targets to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and one or more Render Targets atomically to the output-merger stage, + /// setting also the viewport according to their dimensions. /// - /// A view of the depth-stencil buffer to bind. - /// A set of render target views to bind. - /// renderTargetViews + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// contains too many Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTargetsAndViewport(Texture depthStencilView, Texture[] renderTargetViews) { depthStencilBuffer = depthStencilView; - if (renderTargetViews != null) + if (renderTargetViews is not null) { renderTargetCount = renderTargetViews.Length; - for (int i = 0; i < renderTargetViews.Length; i++) - { - renderTargets[i] = renderTargetViews[i]; - } + renderTargetViews.AsSpan().CopyTo(renderTargets); } else { renderTargetCount = 0; } - CommonSetRenderTargetsAndViewport(depthStencilBuffer, renderTargetCount, renderTargets); + SetRenderTargetsAndViewportImpl(depthStencilBuffer, renderTargetCount, renderTargets); } /// - /// Binds a depth-stencil buffer and a single render target to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and a single Render Target to the output-merger stage. /// - /// A view of the depth-stencil buffer to bind. - /// A view of the render target to bind. + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// A view of the Render Target to bind. + /// Specify to unbind the currently bound Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTarget(Texture depthStencilView, Texture renderTargetView) { depthStencilBuffer = depthStencilView; renderTargets[0] = renderTargetView; - renderTargetCount = renderTargetView != null ? 1 : 0; + renderTargetCount = renderTargetView is not null ? 1 : 0; SetRenderTargetsImpl(depthStencilBuffer, renderTargetCount, renderTargets); } /// - ///

Bind one or more render targets atomically and the depth-stencil buffer to the output-merger stage. See to learn how to use it.

+ /// Binds one or more Render Targets atomically to the output-merger stage, and unbinds any Depth-Stencil Buffer. ///
- /// A set of render target views to bind. + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTargets(Texture[] renderTargetViews) { - SetRenderTargets(null, renderTargetViews); + SetRenderTargets(depthStencilView: null, renderTargetViews); } - + /// - /// Binds a depth-stencil buffer and a set of render targets to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and one or more Render Targets atomically to the output-merger stage. /// - /// A view of the depth-stencil buffer to bind. - /// The number of render target in . - /// A set of render target views to bind. - /// renderTargetViews + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// The current number of Render Targets to bind. + /// Specify 0 to unbind the currently bound Render Targets. + /// + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// + /// + /// is not zero, but is . + /// + /// + /// has less elements than what specifies. + /// + /// + /// indicates too many Render Targets to set. + /// public void SetRenderTargets(Texture depthStencilView, int renderTargetViewCount, Texture[] renderTargetViews) { + ArgumentOutOfRangeException.ThrowIfGreaterThan(renderTargetViewCount, renderTargetViews?.Length ?? 0); + if (renderTargetViewCount > 0) + ArgumentNullException.ThrowIfNull(renderTargetViews); + ArgumentOutOfRangeException.ThrowIfGreaterThan(renderTargetViewCount, MaxRenderTargetCount); + depthStencilBuffer = depthStencilView; renderTargetCount = renderTargetViewCount; - for (int i = 0; i < renderTargetCount; i++) - { - renderTargets[i] = renderTargetViews[i]; - } + renderTargetViews.AsSpan(..renderTargetCount).CopyTo(renderTargets); SetRenderTargetsImpl(depthStencilBuffer, renderTargetCount, renderTargets); } - + /// - /// Binds a depth-stencil buffer and a set of render targets to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and one or more Render Targets atomically to the output-merger stage. /// - /// A view of the depth-stencil buffer to bind. - /// A set of render target views to bind. - /// renderTargetViews + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// contains too many Render Targets to set. + /// + /// + /// See Use a Render Target + /// in the manual for more information. + /// public void SetRenderTargets(Texture depthStencilView, Texture[] renderTargetViews) { + ArgumentOutOfRangeException.ThrowIfGreaterThan(renderTargetViews?.Length ?? 0, MaxRenderTargetCount); + depthStencilBuffer = depthStencilView; - if (renderTargetViews != null) + if (renderTargetViews is not null) { renderTargetCount = renderTargetViews.Length; - for (var i = 0; i < renderTargetViews.Length; i++) - { - renderTargets[i] = renderTargetViews[i]; - } + renderTargetViews.AsSpan().CopyTo(renderTargets); } else { @@ -338,15 +499,44 @@ public void SetRenderTargets(Texture depthStencilView, Texture[] renderTargetVie SetRenderTargetsImpl(depthStencilBuffer, renderTargetCount, renderTargets); } - private void CommonSetRenderTargetsAndViewport(Texture depthStencilView, int currentRenderTargetCount, Texture[] renderTargetViews) + /// + /// Binds a Depth-Stencil Buffer and one or more Render Targets atomically to the output-merger stage, + /// setting also the viewport according to their dimensions. + /// + /// + /// A view of the Depth-Stencil Buffer to bind. + /// Specify to unbind the currently bound Depth-Stencil Buffer. + /// + /// + /// The current number of Render Targets to bind. + /// Specify 0 to unbind the currently bound Render Targets. + /// + /// + /// A set of Render Targets to bind. + /// Specify or an empty array to unbind the currently bound Render Targets. + /// + /// + /// is not zero, but is . + /// + /// + /// has less elements than what specifies. + /// + /// + /// indicates too many Render Targets to set. + /// + private void SetRenderTargetsAndViewportImpl(Texture depthStencilView, int currentRenderTargetCount, Texture[] renderTargetViews) { - if (depthStencilView != null) + if (depthStencilView is not null) { SetViewport(new Viewport(0, 0, depthStencilView.ViewWidth, depthStencilView.ViewHeight)); } else if (currentRenderTargetCount > 0) { - // Setup the viewport from the rendertarget view + ArgumentNullException.ThrowIfNull(renderTargetViews); + ArgumentOutOfRangeException.ThrowIfLessThan(renderTargetViews.Length, currentRenderTargetCount); + ArgumentOutOfRangeException.ThrowIfGreaterThan(currentRenderTargetCount, MaxRenderTargetCount); + + // Setup the viewport from the Render Target View var rtv = renderTargetViews[0]; SetViewport(new Viewport(0, 0, rtv.ViewWidth, rtv.ViewHeight)); } @@ -354,8 +544,182 @@ private void CommonSetRenderTargetsAndViewport(Texture depthStencilView, int cur SetRenderTargetsImpl(depthStencilView, currentRenderTargetCount, renderTargetViews); } - unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle); + #endregion + + /// + /// Resets a Command List back to its initial state as if a new Command List was just created. + /// + public unsafe partial void Reset(); + + /// + /// Closes and executes the Command List. + /// + public partial void Flush(); + + /// + /// Indicates that recording to the Command List has finished. + /// + /// + /// A representing the frozen list of recorded commands + /// that can be executed at a later time. + /// + public partial CompiledCommandList Close(); - unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles); + /// + /// Clears and restores the state of the Graphics Device. + /// + public void ClearState() + { + ClearStateImpl(); + + // Setup empty viewports + Array.Clear(viewports); + + // Setup empty scissors + scissorsDirty = true; + Array.Clear(scissors); + + // Setup the default Render Target and Depth-Stencil Buffer + var deviceDepthStencilBuffer = GraphicsDevice.Presenter?.DepthStencilBuffer; + var deviceBackBuffer = GraphicsDevice.Presenter?.BackBuffer; + SetRenderTargetAndViewport(deviceDepthStencilBuffer, deviceBackBuffer); + } + + /// + /// Platform-specific implementation that clears and restores the state of the Graphics Device. + /// + private partial void ClearStateImpl(); + + + // TODO GRAPHICS REFACTOR what should we do with this? + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// is a , but its is not one of the supported types. + /// is of an unknown type and cannot be updated. + /// + internal unsafe partial void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData, ResourceRegion region); + + // TODO GRAPHICS REFACTOR what should we do with this? + /// + /// Maps a sub-resource of a Graphics Resource to be accessible from CPU memory, and in the process denies the GPU access to that sub-resource. + /// + /// The Graphics Resource to map to CPU memory. + /// The index of the sub-resource to get access to. + /// A value of indicating the way the Graphics Resource should be mapped to CPU memory. + /// + /// A value indicating if this method will return immediately if the Graphics Resource is still being used by the GPU for writing + /// . The default value is , which means the method will wait until the GPU is done. + /// + /// + /// The offset in bytes from the beginning of the mapped memory of the sub-resource. + /// Defaults to 0, which means it is mapped from the beginning. + /// + /// + /// The length in bytes of the memory to map from the sub-resource. + /// Defaults to 0, which means the entire sub-resource is mapped. + /// + /// A structure pointing to the GPU resource mapped for CPU access. + /// is . + /// + /// For s: + /// + /// Usage Instructions: + /// + /// + /// Ensure the was created with the correct usage. + /// For example, you should specify if you plan to update its contents frequently. + /// + /// This method can be called multiple times, and nested calls are supported. + /// + /// Use appropriate values when calling . + /// For example, indicates that the old data in the Buffer can be discarded. + /// + /// + /// + /// + /// Restrictions: + /// + /// + /// The returned by is not guaranteed to be consistent across different calls. + /// Applications should not rely on the address being the same unless is persistently nested. + /// + /// may invalidate the CPU cache to ensure that CPU reads reflect any modifications made by the GPU. + /// If your graphics API supports them, use fences for synchronization to ensure proper coordination between the CPU and GPU. + /// Ensure that the Buffer data is properly aligned to meet the requirements of your graphics API. + /// + /// Stick to simple usage models (e.g., for upload, , + /// for readback) unless advanced models are necessary for your application. + /// + /// + /// + /// + /// For s: + /// + /// Usage Instructions: + /// + /// + /// Ensure to use the correct data format when writing data to the Texture. + /// + /// Textures can have multiple mipmap levels. You must specify which level you want to map with . + /// + /// Use appropriate values when calling . + /// For example, is usually used to update dynamic Textures. + /// + /// + /// + /// + /// Restrictions: + /// + /// + /// Not all s are compatible with mapping operations. + /// + /// Concurrent access to a Texture both from the CPU and the GPU may not be allowed and might require careful synchronization. + /// Ensure that the Texture data is properly aligned to meet the requirements of your graphics API and the . + /// + /// + /// + /// For State Objects (like , , etc): + /// + /// Restrictions: + /// + /// + /// State Objects are not usually mapped nor directly updated. They are created with specific configurations and are treated + /// as immutable from now on. Instead, if you need changes, you can create a new State Object with the updated settings. + /// + /// + /// + /// + /// After updating the , call to release the CPU pointer and allow the GPU to access the updated data. + /// + public unsafe partial MappedResource MapSubResource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0); + + // TODO GRAPHICS REFACTOR what should we do with this? + /// + /// Unmaps a sub-resource of a Graphics Resource, which was previously mapped to CPU memory with , + /// and in the process re-enables the GPU access to that sub-resource. + /// + /// + /// A structure identifying the sub-resource to unmap. + /// + public unsafe partial void UnmapSubResource(MappedResource mappedResource); } } diff --git a/sources/engine/Stride.Graphics/CompiledCommandList.cs b/sources/engine/Stride.Graphics/CompiledCommandList.cs index 37486631a0..c44bed7338 100644 --- a/sources/engine/Stride.Graphics/CompiledCommandList.cs +++ b/sources/engine/Stride.Graphics/CompiledCommandList.cs @@ -1,12 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A list of commands that have been recorded for execution at a later time. +/// +public partial struct CompiledCommandList { - /// - /// A token for one-time execution of a by the - /// - public partial struct CompiledCommandList - { - } + // TODO: Implement deferred command list execution } diff --git a/sources/engine/Stride.Graphics/ComputeShaderFormatSupport.cs b/sources/engine/Stride.Graphics/ComputeShaderFormatSupport.cs new file mode 100644 index 0000000000..091390d89d --- /dev/null +++ b/sources/engine/Stride.Graphics/ComputeShaderFormatSupport.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Graphics +{ + /// + /// Flags specifying which resources and features are supported when using compute shaders + /// for a given pixel format for a graphics device. + /// + /// + /// For more information, see . + /// + [Flags] + public enum ComputeShaderFormatSupport + { + None = 0, + + // D3D11_FORMAT_SUPPORT2_UAV_ATOMIC_ADD + AtomicAdd = 1, + + // D3D11_FORMAT_SUPPORT2_UAV_ATOMIC_BITWISE_OPS + AtomicBitwiseOperations = 2, + + // D3D11_FORMAT_SUPPORT2_UAV_ATOMIC_COMPARE_STORE_OR_COMPARE_EXCHANGE + AtomicCompareStoreOrCompareExchange = 4, + + // D3D11_FORMAT_SUPPORT2_UAV_ATOMIC_EXCHANGE + AtomicExchange = 8, + + // D3D11_FORMAT_SUPPORT2_UAV_ATOMIC_SIGNED_MIN_OR_MAX + AtomicSignedMinimumOrMaximum = 0x00000010, + + // D3D11_FORMAT_SUPPORT2_UAV_ATOMIC_UNSIGNED_MIN_OR_MAX + AtomicUnsignedMinimumOrMaximum = 0x00000020, + + // D3D11_FORMAT_SUPPORT2_UAV_TYPED_LOAD + TypedLoad = 0x00000040, + + // D3D11_FORMAT_SUPPORT2_UAV_TYPED_STORE + TypedStore = 0x00000080, + + // D3D11_FORMAT_SUPPORT2_OUTPUT_MERGER_LOGIC_OP + OutputMergerLogicOperation = 0x00000100, + + // D3D11_FORMAT_SUPPORT2_TILED + Tiled = 0x00000200, + + // D3D11_FORMAT_SUPPORT2_SHAREABLE + Shareable = 0x00000400, + + // D3D11_FORMAT_SUPPORT2_MULTIPLANE_OVERLAY + MultiplaneOverlay = 0x00004000 + } +} diff --git a/sources/engine/Stride.Graphics/CullMode.cs b/sources/engine/Stride.Graphics/CullMode.cs index 1ce88e4aae..7347584fc2 100644 --- a/sources/engine/Stride.Graphics/CullMode.cs +++ b/sources/engine/Stride.Graphics/CullMode.cs @@ -1,31 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Indicates the facing direction of triangles that will be culled (not drawn). +/// +/// +/// This enumeration is part of a Rasterizer State object description (see ). +/// +[DataContract] +public enum CullMode { /// - /// Indicates triangles facing a particular direction are not drawn. + /// Always draw all triangles. /// - /// - /// This enumeration is part of a rasterizer-state object description (see ). - /// - [DataContract] - public enum CullMode - { - /// - /// Always draw all triangles. - /// - None = 1, + None = 1, - /// - /// Do not draw triangles that are front-facing. - /// - Front = 2, + /// + /// Do not draw triangles that are front-facing. + /// + Front = 2, - /// - /// Do not draw triangles that are back-facing. - /// - Back = 3, - } + /// + /// Do not draw triangles that are back-facing. + /// + Back = 3 } diff --git a/sources/engine/Stride.Graphics/DepthStencilClearOptions.cs b/sources/engine/Stride.Graphics/DepthStencilClearOptions.cs index 8a7f5e1617..e48d8d59ff 100644 --- a/sources/engine/Stride.Graphics/DepthStencilClearOptions.cs +++ b/sources/engine/Stride.Graphics/DepthStencilClearOptions.cs @@ -1,22 +1,29 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Flags that specify what parts of a Depth-Stencil Buffer to clear when calling +/// . +/// +[Flags] +public enum DepthStencilClearOptions { /// - /// Specifies the buffer to use when calling Clear. + /// Neither the Depth Buffer nor the Stencil Buffer will be cleared. + /// + None = 0, + + /// + /// Selects the Depth Buffer. + /// + DepthBuffer = 1, + + /// + /// Selects the Stencil Buffer. /// - [Flags] - public enum DepthStencilClearOptions - { - /// - /// A depth buffer. - /// - DepthBuffer = 1, - /// - /// A stencil buffer. - /// - Stencil = 2, - } + Stencil = 2 } diff --git a/sources/engine/Stride.Graphics/DepthStencilStateDescription.cs b/sources/engine/Stride.Graphics/DepthStencilStateDescription.cs index d5a7965775..21785e6af6 100644 --- a/sources/engine/Stride.Graphics/DepthStencilStateDescription.cs +++ b/sources/engine/Stride.Graphics/DepthStencilStateDescription.cs @@ -6,143 +6,192 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A description of a Depth-Stencil State, which defines how depth and stencil testing +/// are performed during rasterization. +/// +/// +/// This structure controls whether depth and stencil tests are enabled, how they are configured, and +/// how they affect pixel visibility. +///
+/// It allows fine-grained control over depth comparisons, stencil operations, and write masks, +/// enabling advanced rendering techniques such as shadow volumes, outlines, and complex masking. +///
+/// +[DataContract] +[StructLayout(LayoutKind.Sequential)] +public struct DepthStencilStateDescription : IEquatable { /// - /// Describes a depth stencil state. + /// Initializes a new instance of the structure. /// - [DataContract] - [StructLayout(LayoutKind.Sequential)] - public struct DepthStencilStateDescription : IEquatable + public DepthStencilStateDescription(bool depthEnable, bool depthWriteEnable) : this() { - /// - /// Initializes a new instance of the class. - /// - public DepthStencilStateDescription(bool depthEnable, bool depthWriteEnable) : this() - { - SetDefault(); - DepthBufferEnable = depthEnable; - DepthBufferWriteEnable = depthWriteEnable; - } - - /// - /// Enables or disables depth buffering. The default is true. - /// - public bool DepthBufferEnable; - - /// - /// Gets or sets the comparison function for the depth-buffer test. The default is CompareFunction.LessEqual - /// - public CompareFunction DepthBufferFunction; - - /// - /// Enables or disables writing to the depth buffer. The default is true. - /// - public bool DepthBufferWriteEnable; - - /// - /// Gets or sets stencil enabling. The default is false. - /// - public bool StencilEnable; - - /// - /// Gets or sets the mask applied to the reference value and each stencil buffer entry to determine the significant bits for the stencil test. The default mask is byte.MaxValue. - /// - public byte StencilMask; - - /// - /// Gets or sets the write mask applied to values written into the stencil buffer. The default mask is byte.MaxValue. - /// - public byte StencilWriteMask; - - /// - /// Identify how to use the results of the depth test and the stencil test for pixels whose surface normal is facing towards the camera. - /// - public DepthStencilStencilOpDescription FrontFace; - - /// - /// Identify how to use the results of the depth test and the stencil test for pixels whose surface normal is facing away the camera. - /// - public DepthStencilStencilOpDescription BackFace; - - /// - /// Sets default values for this instance. - /// - public DepthStencilStateDescription SetDefault() - { - DepthBufferEnable = true; - DepthBufferWriteEnable = true; - DepthBufferFunction = CompareFunction.LessEqual; - StencilEnable = false; - - FrontFace.StencilFunction = CompareFunction.Always; - FrontFace.StencilPass = StencilOperation.Keep; - FrontFace.StencilFail = StencilOperation.Keep; - FrontFace.StencilDepthBufferFail = StencilOperation.Keep; - - BackFace.StencilFunction = CompareFunction.Always; - BackFace.StencilPass = StencilOperation.Keep; - BackFace.StencilFail = StencilOperation.Keep; - BackFace.StencilDepthBufferFail = StencilOperation.Keep; - - StencilMask = byte.MaxValue; - StencilWriteMask = byte.MaxValue; - return this; - } - - /// - /// Gets default values for this instance. - /// - public static DepthStencilStateDescription Default - { - get - { - var desc = new DepthStencilStateDescription(); - desc.SetDefault(); - return desc; - } - } - - public DepthStencilStateDescription Clone() - { - return (DepthStencilStateDescription)MemberwiseClone(); - } - - public bool Equals(DepthStencilStateDescription other) - { - return DepthBufferEnable == other.DepthBufferEnable && DepthBufferFunction == other.DepthBufferFunction && DepthBufferWriteEnable == other.DepthBufferWriteEnable && StencilEnable == other.StencilEnable && StencilMask == other.StencilMask && StencilWriteMask == other.StencilWriteMask && FrontFace.Equals(other.FrontFace) && BackFace.Equals(other.BackFace); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is DepthStencilStateDescription && Equals((DepthStencilStateDescription)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = DepthBufferEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ (int)DepthBufferFunction; - hashCode = (hashCode * 397) ^ DepthBufferWriteEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ StencilEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ StencilMask.GetHashCode(); - hashCode = (hashCode * 397) ^ StencilWriteMask.GetHashCode(); - hashCode = (hashCode * 397) ^ FrontFace.GetHashCode(); - hashCode = (hashCode * 397) ^ BackFace.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(DepthStencilStateDescription left, DepthStencilStateDescription right) - { - return left.Equals(right); - } - - public static bool operator !=(DepthStencilStateDescription left, DepthStencilStateDescription right) - { - return !left.Equals(right); - } + SetDefaults(); + DepthBufferEnable = depthEnable; + DepthBufferWriteEnable = depthWriteEnable; + } + + + /// + /// Enables or disables depth testing during rasterization. + /// + /// + /// When enabled, the depth test compares each pixel's depth value against the existing value in the Depth-Stencil Buffer, + /// using the function specified by . + ///
+ /// If disabled, all pixels pass the depth test. + ///
+ public bool DepthBufferEnable; + + /// + /// Specifies the comparison function used in the depth test. + /// + /// + /// This function determines whether a pixel should be drawn based on its depth value. + /// For example, CompareFunction.LessEqual allows a pixel to pass if its depth is less than or equal to + /// the current Depth-Stencil Buffer value. + /// + public CompareFunction DepthBufferFunction; + + /// + /// Enables or disables writing to the depth buffer. + /// + /// + /// Disabling depth writes can be useful for rendering transparent objects or overlays that should not affect + /// depth testing of subsequent geometry. + /// + public bool DepthBufferWriteEnable; + + /// + /// Enables or disables stencil testing. + /// + /// + /// When enabled, the stencil test is performed for each pixel using the configured stencil operations and masks. + /// This allows for advanced rendering techniques such as masking, outlining, and shadow volumes. + /// + public bool StencilEnable; + + /// + /// Bitmask applied to both the reference value and stencil buffer entry during stencil testing. + /// Default is . + /// + /// + /// This mask controls which bits are considered significant in the stencil comparison. + /// For example, a mask of 0x0F limits the test to the lower 4 bits of the stencil value. + /// + public byte StencilMask; + + /// + /// Bitmask applied to values written into the stencil buffer. + /// Default is . + /// + /// + /// This mask determines which bits can be modified during stencil write operations. + /// It allows selective updating of stencil buffer bits. + /// + public byte StencilWriteMask; + + /// + /// Describes stencil operations and comparison function for front-facing polygons. + /// + /// + /// This includes the operations to perform when the stencil test fails, when the depth test fails, + /// and when both pass. It also defines the comparison function used for the stencil test. + /// + public DepthStencilStencilOpDescription FrontFace; + + /// + /// Describes stencil operations and comparison function for back-facing polygons. + /// + /// + /// Typically used in conjunction with to implement two-sided stencil operations, + /// such as shadow volume rendering or complex masking. + /// + public DepthStencilStencilOpDescription BackFace; + + + /// + /// Sets default values for this Depth-Stencil State description. + /// + /// + /// The default values are: + /// + /// Enables depth testing and depth writing. + /// Uses the depth comparison function . + /// Disables stencil testing. + /// Sets the stencil bitmasks to (all birs set to 1). + /// + /// The stencil operations and comparison functions for both front-facing and back-facing pixels are set to + /// (they always succeed) and + /// (the value in the stencil buffer is not modified). + /// + /// + public void SetDefaults() + { + DepthBufferEnable = true; + DepthBufferWriteEnable = true; + DepthBufferFunction = CompareFunction.LessEqual; + StencilEnable = false; + + FrontFace.StencilFunction = CompareFunction.Always; + FrontFace.StencilPass = StencilOperation.Keep; + FrontFace.StencilFail = StencilOperation.Keep; + FrontFace.StencilDepthBufferFail = StencilOperation.Keep; + + BackFace.StencilFunction = CompareFunction.Always; + BackFace.StencilPass = StencilOperation.Keep; + BackFace.StencilFail = StencilOperation.Keep; + BackFace.StencilDepthBufferFail = StencilOperation.Keep; + + StencilMask = byte.MaxValue; + StencilWriteMask = byte.MaxValue; + } + + + /// + /// Creates a new instance of with the same values as this instance. + /// + /// A new Depth-Stencil State description that is a copy of this instance. + public readonly DepthStencilStateDescription Clone() + { + return (DepthStencilStateDescription) MemberwiseClone(); + } + + /// + public readonly bool Equals(DepthStencilStateDescription other) + { + return DepthBufferEnable == other.DepthBufferEnable + && DepthBufferFunction == other.DepthBufferFunction + && DepthBufferWriteEnable == other.DepthBufferWriteEnable + && StencilEnable == other.StencilEnable + && StencilMask == other.StencilMask + && StencilWriteMask == other.StencilWriteMask + && FrontFace.Equals(other.FrontFace) + && BackFace.Equals(other.BackFace); + } + + /// + public override readonly bool Equals(object obj) + { + return obj is DepthStencilStateDescription dssdesc && Equals(dssdesc); + } + + public static bool operator ==(DepthStencilStateDescription left, DepthStencilStateDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(DepthStencilStateDescription left, DepthStencilStateDescription right) + { + return !left.Equals(right); + } + + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(DepthBufferEnable, DepthBufferFunction, DepthBufferWriteEnable, StencilEnable, StencilMask, StencilWriteMask, FrontFace, BackFace); } } diff --git a/sources/engine/Stride.Graphics/DepthStencilStates.cs b/sources/engine/Stride.Graphics/DepthStencilStates.cs index 5c019240bd..d65922ae87 100644 --- a/sources/engine/Stride.Graphics/DepthStencilStates.cs +++ b/sources/engine/Stride.Graphics/DepthStencilStates.cs @@ -1,32 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a set of built-in s for common depth and stencil testing configurations. +/// +public static class DepthStencilStates { + static DepthStencilStates() + { + var defaultDescription = new DepthStencilStateDescription(); + defaultDescription.SetDefaults(); + Default = defaultDescription; + } + + /// - /// Known values for . + /// A built-in Depth-Stencil State object with default settings. /// - public static class DepthStencilStates - { - /// - /// A built-in state object with default settings for using a depth stencil buffer. - /// - public static readonly DepthStencilStateDescription Default = new DepthStencilStateDescription(true, true); + /// + public static readonly DepthStencilStateDescription Default; - /// - /// A built-in state object with default settings using greater comparison for Z. - /// - public static readonly DepthStencilStateDescription DefaultInverse = new DepthStencilStateDescription(true, true) { DepthBufferFunction = CompareFunction.GreaterEqual }; + /// + /// A built-in Depth-Stencil State object with default settings using + /// function when comparing depth values. + /// + public static readonly DepthStencilStateDescription DefaultInverse = new(depthEnable: true, depthWriteEnable: true) + { + DepthBufferFunction = CompareFunction.GreaterEqual + }; - /// - /// A built-in state object with settings for enabling a read-only depth stencil buffer. - /// - public static readonly DepthStencilStateDescription DepthRead = new DepthStencilStateDescription(true, false); + /// + /// A built-in Depth-Stencil State object with settings for enabling a read-only Depth-Stencil Buffer. + /// + public static readonly DepthStencilStateDescription DepthRead = new(depthEnable: true, depthWriteEnable: false); - /// - /// A built-in state object with settings for not using a depth stencil buffer. - /// - public static readonly DepthStencilStateDescription None = new DepthStencilStateDescription(false, false); - } + /// + /// A built-in Depth-Stencil State object with settings for not using a Depth-Stencil Buffer. + /// + public static readonly DepthStencilStateDescription None = new(depthEnable: false, depthWriteEnable: false); } diff --git a/sources/engine/Stride.Graphics/DepthStencilStencilOpDescription.cs b/sources/engine/Stride.Graphics/DepthStencilStencilOpDescription.cs index d4397ab9b2..0965537b05 100644 --- a/sources/engine/Stride.Graphics/DepthStencilStencilOpDescription.cs +++ b/sources/engine/Stride.Graphics/DepthStencilStencilOpDescription.cs @@ -6,63 +6,85 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes the stencil operations and comparison function used during stencil testing +/// for a given face orientation (front or back). +/// +/// +/// This structure defines how the stencil buffer is updated based on the outcome of the stencil and depth tests. +/// It is used in to configure separate behavior for front-facing and back-facing polygons. +/// +[DataContract] +[StructLayout(LayoutKind.Sequential)] +public struct DepthStencilStencilOpDescription : IEquatable { - [DataContract] - [StructLayout(LayoutKind.Sequential)] - public struct DepthStencilStencilOpDescription : IEquatable - { - /// - /// Gets or sets the stencil operation to perform if the stencil test fails. The default is StencilOperation.Keep. - /// - public StencilOperation StencilFail { get; set; } + /// + /// Specifies the stencil operation to perform when the stencil test fails. + /// + /// + /// This operation is applied regardless of the result of the depth test. + /// Common values include (no change) and / + /// for masking or outlining effects. + /// + public StencilOperation StencilFail; + + /// + /// Specifies the stencil operation to perform when the stencil test passes but the depth test fails. + /// + /// + /// This is useful for effects like shadow volumes, where depth failure indicates occlusion. + /// + public StencilOperation StencilDepthBufferFail; + + /// + /// Specifies the stencil operation to perform when both the stencil and depth tests pass. + /// + /// + /// This is the most common path for visible pixels. + /// The operation typically updates the stencil buffer to mark the pixel as processed. + /// + public StencilOperation StencilPass; - /// - /// Gets or sets the stencil operation to perform if the stencil test passes and the depth-test fails. The default is StencilOperation.Keep. - /// - public StencilOperation StencilDepthBufferFail { get; set; } - - /// - /// Gets or sets the stencil operation to perform if the stencil test passes. The default is StencilOperation.Keep. - /// - public StencilOperation StencilPass { get; set; } - - /// - /// Gets or sets the comparison function for the stencil test. The default is CompareFunction.Always. - /// - public CompareFunction StencilFunction { get; set; } + /// + /// Specifies the comparison function used to evaluate the stencil test. + /// + /// + /// The test compares the stencil buffer value with a reference value using this function. + /// For example, passes only if the values match. + /// + public CompareFunction StencilFunction; - public bool Equals(DepthStencilStencilOpDescription other) - { - return StencilFail == other.StencilFail && StencilDepthBufferFail == other.StencilDepthBufferFail && StencilPass == other.StencilPass && StencilFunction == other.StencilFunction; - } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is DepthStencilStencilOpDescription && Equals((DepthStencilStencilOpDescription)obj); - } + /// + public readonly bool Equals(DepthStencilStencilOpDescription other) + { + return StencilFail == other.StencilFail + && StencilDepthBufferFail == other.StencilDepthBufferFail + && StencilPass == other.StencilPass + && StencilFunction == other.StencilFunction; + } + + /// + public override readonly bool Equals(object obj) + { + return obj is DepthStencilStencilOpDescription dssOp && Equals(dssOp); + } - public override int GetHashCode() - { - unchecked - { - var hashCode = (int)StencilFail; - hashCode = (hashCode * 397) ^ (int)StencilDepthBufferFail; - hashCode = (hashCode * 397) ^ (int)StencilPass; - hashCode = (hashCode * 397) ^ (int)StencilFunction; - return hashCode; - } - } + public static bool operator ==(DepthStencilStencilOpDescription left, DepthStencilStencilOpDescription right) + { + return left.Equals(right); + } - public static bool operator ==(DepthStencilStencilOpDescription left, DepthStencilStencilOpDescription right) - { - return left.Equals(right); - } + public static bool operator !=(DepthStencilStencilOpDescription left, DepthStencilStencilOpDescription right) + { + return !left.Equals(right); + } - public static bool operator !=(DepthStencilStencilOpDescription left, DepthStencilStencilOpDescription right) - { - return !left.Equals(right); - } + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(StencilFail, StencilDepthBufferFail, StencilPass, StencilFunction); } } diff --git a/sources/engine/Stride.Graphics/DescriptorPool.cs b/sources/engine/Stride.Graphics/DescriptorPool.cs index 35923b956e..05c7ca5592 100644 --- a/sources/engine/Stride.Graphics/DescriptorPool.cs +++ b/sources/engine/Stride.Graphics/DescriptorPool.cs @@ -1,56 +1,85 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A pool where the application can allocate Descriptors that are grouped together in s. +/// +public partial class DescriptorPool : GraphicsResourceBase { /// - /// Storage area for . + /// Creates a new Descriptor Pool with space for the specified counts of each Descriptor type. /// - public partial class DescriptorPool : GraphicsResourceBase + /// The Graphics Device. + /// + /// A list of structures indicating the number of each type of Descriptor + /// that need to be allocated. + /// + /// The new Descriptor Pool. + public static DescriptorPool New(GraphicsDevice graphicsDevice, DescriptorTypeCount[] counts) { - public static DescriptorPool New(GraphicsDevice graphicsDevice, DescriptorTypeCount[] counts) - { - return new DescriptorPool(graphicsDevice, counts); - } + return new DescriptorPool(graphicsDevice, counts); + } #if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_OPENGL || (STRIDE_GRAPHICS_API_VULKAN && STRIDE_GRAPHICS_NO_DESCRIPTOR_COPIES) - internal DescriptorSetEntry[] Entries; - private int descriptorAllocationOffset; - private DescriptorPool(GraphicsDevice graphicsDevice, DescriptorTypeCount[] counts) - { - // For now, we put everything together so let's compute total count - var totalCount = 0; - foreach (var count in counts) - { - totalCount += count.Count; - } - - Entries = new DescriptorSetEntry[totalCount]; - } + /// + /// The Descriptors allocated in this Descriptor Pool, along with their offset and size. + /// + internal DescriptorSetEntry[] Entries; + + // Current allocation offset in the Entries array + private int descriptorAllocationOffset; - protected override void Destroy() - { - Entries = null; - base.Destroy(); - } - public void Reset() + private DescriptorPool(GraphicsDevice graphicsDevice, DescriptorTypeCount[] counts) + { + // For now, we put everything together so let's compute total count + var totalCount = 0; + foreach (var count in counts) { - Array.Clear(Entries, 0, descriptorAllocationOffset); - descriptorAllocationOffset = 0; + totalCount += count.Count; } - internal int Allocate(int size) - { - if (descriptorAllocationOffset + size > Entries.Length) - return -1; + Entries = new DescriptorSetEntry[totalCount]; + } - var result = descriptorAllocationOffset; - descriptorAllocationOffset += size; - return result; - } -#endif + /// + protected override void Destroy() + { + Entries = null; + base.Destroy(); + } + + /// + /// Clears the Descriptor Pool, resetting all allocated Descriptors. + /// + public void Reset() + { + Array.Clear(Entries, 0, descriptorAllocationOffset); + descriptorAllocationOffset = 0; } + + /// + /// Tries to allocate space for a number of Descriptor in the Descriptor Pool. + /// + /// The number of Descriptors to allocate. + /// + /// The offset in the array where the allocated Descriptors start, + /// or -1 if there is not enough space in the Descriptor Pool to allocate the requested number of Descriptors. + /// + internal int Allocate(int size) + { + if (descriptorAllocationOffset + size > Entries.Length) + return -1; + + var result = descriptorAllocationOffset; + descriptorAllocationOffset += size; + return result; + } + +#endif } diff --git a/sources/engine/Stride.Graphics/DescriptorSet.cs b/sources/engine/Stride.Graphics/DescriptorSet.cs index 0b14f5ca6b..80cb0bfd71 100644 --- a/sources/engine/Stride.Graphics/DescriptorSet.cs +++ b/sources/engine/Stride.Graphics/DescriptorSet.cs @@ -1,85 +1,106 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; +namespace Stride.Graphics; -namespace Stride.Graphics +/// +/// Represents a set of Descriptors (such as Textures or Buffers) that can be bound together to a graphics pipeline. +/// +public readonly partial struct DescriptorSet { /// - /// Contains a list descriptors (such as textures) that can be bound together to the graphics pipeline. + /// Creates a new Descriptor Set. /// - public partial struct DescriptorSet + /// The Graphics Device. + /// The pool where Descriptor Sets are allocated. + /// A description of the Graphics Resources in the Descriptor Set and their layout. + /// + /// The new Descriptor Set. + /// + public static DescriptorSet New(GraphicsDevice graphicsDevice, DescriptorPool pool, DescriptorSetLayout layout) { - public static DescriptorSet New(GraphicsDevice graphicsDevice, DescriptorPool pool, DescriptorSetLayout desc) - { - return new DescriptorSet(graphicsDevice, pool, desc); - } + return new DescriptorSet(graphicsDevice, pool, layout); + } #if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_OPENGL || (STRIDE_GRAPHICS_API_VULKAN && STRIDE_GRAPHICS_NO_DESCRIPTOR_COPIES) - internal readonly DescriptorSetEntry[] HeapObjects; - internal readonly int DescriptorStartOffset; - private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, DescriptorSetLayout desc) - { - this.HeapObjects = pool.Entries; - this.DescriptorStartOffset = pool.Allocate(desc.ElementCount); - } + /// + /// An array of Descriptors in the Descriptor Set used for managing the Graphics Resources, + /// as allocated by the Descriptor Pool. + /// + internal readonly DescriptorSetEntry[] HeapObjects; - public bool IsValid => DescriptorStartOffset != -1; + /// + /// The start offset in the array where the Descriptors for this Descriptor Set begin. + /// + internal readonly int DescriptorStartOffset; - /// - /// Sets a descriptor. - /// - /// The slot. - /// The descriptor. - public void SetValue(int slot, object value) - { - HeapObjects[DescriptorStartOffset + slot].Value = value; - } - /// - /// Sets a shader resource view descriptor. - /// - /// The slot. - /// The shader resource view. - public void SetShaderResourceView(int slot, GraphicsResource shaderResourceView) - { - HeapObjects[DescriptorStartOffset + slot].Value = shaderResourceView; - } + private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, DescriptorSetLayout desc) + { + HeapObjects = pool.Entries; + DescriptorStartOffset = pool.Allocate(desc.ElementCount); + } - /// - /// Sets a sampler state descriptor. - /// - /// The slot. - /// The sampler state. - public void SetSamplerState(int slot, SamplerState samplerState) - { - HeapObjects[DescriptorStartOffset + slot].Value = samplerState; - } - /// - /// Sets a constant buffer view descriptor. - /// - /// The slot. - /// The constant buffer. - /// The constant buffer view start offset. - /// The constant buffer view size. - public void SetConstantBuffer(int slot, Buffer buffer, int offset, int size) - { - HeapObjects[DescriptorStartOffset + slot] = new DescriptorSetEntry(buffer, offset, size); - } + /// + /// Gets a value indicating whether this Descriptor Set is valid (i.e. it has been allocated and has a valid start offset). + /// + public readonly bool IsValid => DescriptorStartOffset != -1; + - /// - /// Sets an unordered access view descriptor. - /// - /// The slot. - /// The unordered access view. - public void SetUnorderedAccessView(int slot, GraphicsResource unorderedAccessView) - { - ref var heapObject = ref HeapObjects[DescriptorStartOffset + slot]; - heapObject.Value = unorderedAccessView; - heapObject.Offset = (unorderedAccessView as Buffer)?.InitialCounterOffset ?? -1; - } -#endif + /// + /// Sets a Descriptor in the specified slot. + /// + /// The slot index. + /// The Descriptor to set. + public readonly void SetValue(int slot, object value) + { + HeapObjects[DescriptorStartOffset + slot].Value = value; } + + /// + /// Sets a Shader Resource View on a Graphics Resource in the specified slot. + /// + /// The slot index. + /// The Shader Resource View on a Graphics Resource to set. + public readonly void SetShaderResourceView(int slot, GraphicsResource shaderResourceView) + { + HeapObjects[DescriptorStartOffset + slot].Value = shaderResourceView; + } + + /// + /// Sets a Sampler State in the specified slot. + /// + /// The slot index. + /// The Sampler State to set. + public readonly void SetSamplerState(int slot, SamplerState samplerState) + { + HeapObjects[DescriptorStartOffset + slot].Value = samplerState; + } + + /// + /// Sets a Constant Buffer View in the specified slot. + /// + /// The slot index. + /// The Constant Buffer to set. + /// The Constant Buffer View start offset. + /// The Constant Buffer View size. + public readonly void SetConstantBuffer(int slot, Buffer buffer, int offset, int size) + { + HeapObjects[DescriptorStartOffset + slot] = new DescriptorSetEntry(buffer, offset, size); + } + + /// + /// Sets an Unordered Access View in the specified slot. + /// + /// The slot index. + /// The Unordered Access View to set. + public readonly void SetUnorderedAccessView(int slot, GraphicsResource unorderedAccessView) + { + ref var heapObject = ref HeapObjects[DescriptorStartOffset + slot]; + heapObject.Value = unorderedAccessView; + heapObject.Offset = (unorderedAccessView as Buffer)?.InitialCounterOffset ?? -1; + } +#endif } diff --git a/sources/engine/Stride.Graphics/DescriptorSetEntry.cs b/sources/engine/Stride.Graphics/DescriptorSetEntry.cs index de1dcc389b..32194140de 100644 --- a/sources/engine/Stride.Graphics/DescriptorSetEntry.cs +++ b/sources/engine/Stride.Graphics/DescriptorSetEntry.cs @@ -1,26 +1,24 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Internal structure to store Descriptor entries in a . +/// +/// The Descriptor, representing the Graphics Resource to bind. +/// +/// The offset in the Constant Buffer or the initial counter offset value for Unordered Access Views of Compute Shaders. +/// +/// The size of the Buffer view, if applicable. +internal struct DescriptorSetEntry(object value, int offset, int size) { - /// - /// Used internally to store descriptor entries. - /// - internal struct DescriptorSetEntry - { - public object Value; + /// + public object Value = value; - /// - /// The offset, shared parameter for either cbuffer or unordered access view. - /// Describes the cbuffer offset or the initial counter offset value for UAVs of compute shaders. - /// - public int Offset; - public int Size; + /// + public int Offset = offset; - public DescriptorSetEntry(object value, int offset, int size) - { - Value = value; - Offset = offset; - Size = size; - } - } + /// + public int Size = size; } diff --git a/sources/engine/Stride.Graphics/DescriptorSetLayout.cs b/sources/engine/Stride.Graphics/DescriptorSetLayout.cs index c2174e83ca..564f427700 100644 --- a/sources/engine/Stride.Graphics/DescriptorSetLayout.cs +++ b/sources/engine/Stride.Graphics/DescriptorSetLayout.cs @@ -1,30 +1,51 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Rendering; -using Stride.Shaders; -namespace Stride.Graphics +namespace Stride.Graphics; + +// D3D11 version + +/// +/// Defines the Graphics Resources (Descriptors) that need to be bound together, their layout, types, and other associated metadata. +///
+/// This description is used to allocate a . +///
+public partial class DescriptorSetLayout : GraphicsResourceBase { - // D3D11 version /// - /// Defines a list of descriptor layout. This is used to allocate a . + /// Creates a new Descriptor Set Layout. /// - public partial class DescriptorSetLayout : GraphicsResourceBase + /// The Graphics Device. + /// + /// A that defines the bound Graphics Resources of the Descriptor Set. + /// + /// The new . + public static DescriptorSetLayout New(GraphicsDevice device, DescriptorSetLayoutBuilder builder) { - public static DescriptorSetLayout New(GraphicsDevice device, DescriptorSetLayoutBuilder builder) - { - return new DescriptorSetLayout(device, builder); - } + return new DescriptorSetLayout(device, builder); + } #if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_OPENGL || (STRIDE_GRAPHICS_API_VULKAN && STRIDE_GRAPHICS_NO_DESCRIPTOR_COPIES) - internal readonly int ElementCount; - internal readonly DescriptorSetLayoutBuilder.Entry[] Entries; - - private DescriptorSetLayout(GraphicsDevice device, DescriptorSetLayoutBuilder builder) - { - ElementCount = builder.ElementCount; - Entries = builder.Entries.ToArray(); - } -#endif + + /// + /// The number of elements in the Descriptor Set Layout. + /// + /// + /// This is not just the number of , but the total number of elements across all entries, + /// as some of the entries can be Arrays with several elements. + /// + internal readonly int ElementCount; + + /// + /// The entries that define the bindings and layout of the Descriptor Set. + /// + internal readonly DescriptorSetLayoutBuilder.Entry[] Entries; + + + private DescriptorSetLayout(GraphicsDevice device, DescriptorSetLayoutBuilder builder) + { + ElementCount = builder.ElementCount; + Entries = builder.Entries.ToArray(); } +#endif } diff --git a/sources/engine/Stride.Graphics/DescriptorSetLayoutBuilder.cs b/sources/engine/Stride.Graphics/DescriptorSetLayoutBuilder.cs index 8e6cb2a8ed..8bf04c3f43 100644 --- a/sources/engine/Stride.Graphics/DescriptorSetLayoutBuilder.cs +++ b/sources/engine/Stride.Graphics/DescriptorSetLayoutBuilder.cs @@ -1,51 +1,95 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections.Generic; + using Stride.Core.Storage; using Stride.Rendering; using Stride.Shaders; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Helper class to build a . +/// +public class DescriptorSetLayoutBuilder { /// - /// Helper class to build a . + /// The total number of elements in the DescriptorSetLayout. + /// + internal int ElementCount; + + /// + /// A list of entries that define the layout of a Descriptor Set. + /// + internal List Entries = []; + + private ObjectIdBuilder hashBuilder = new(); + + /// + /// Gets a hash identifying the current state of a Descriptor Set. + /// This hash is used to know if the Descriptors in the set can be shared. + /// + public ObjectId Hash => hashBuilder.ComputeHash(); + + + /// + /// Adds a binding to the Descriptor Set layout. /// - public class DescriptorSetLayoutBuilder + /// The by which the Graphics Resource' Descriptor can be identified in the Shader. + /// A logical group name, used to group related Descriptors and variables together. + /// The kind of the parameter in the Shader. + /// The data type of the parameter in the Shader. + /// + /// The data type of the elements of the parameter in the Shader in case represents an + /// Array, Buffer, or Texture type. + /// + /// + /// The number of elements in the Array in case represents an Array type. + /// Specify 1 if the parameter is not an Array. This is the default value. + /// + /// + /// An optional unmodifiable Sampler State described directly in the Shader for sampling the parameter. + /// Specify if the parameter does not require a Sampler State. + /// + public void AddBinding(ParameterKey key, string logicalGroup, EffectParameterClass @class, EffectParameterType type, EffectParameterType elementType, int arraySize = 1, SamplerState? immutableSampler = null) { - internal int ElementCount; - internal List Entries = new List(); - - private ObjectIdBuilder hashBuilder = new ObjectIdBuilder(); - - /// - /// Returns hash describing current state of DescriptorSet (to know if they can be shared) - /// - public ObjectId Hash => hashBuilder.ComputeHash(); - - /// - /// Gets (or creates) an entry to the DescriptorSetLayout and gets its index. - /// - /// The future entry index. - public void AddBinding(ParameterKey key, string logicalGroup, EffectParameterClass @class, EffectParameterType type, EffectParameterType elementType, int arraySize = 1, SamplerState immutableSampler = null) - { - hashBuilder.Write(key.Name); - hashBuilder.Write(@class); - hashBuilder.Write(arraySize); - - ElementCount += arraySize; - Entries.Add(new Entry { Key = key, LogicalGroup = logicalGroup, Class = @class, Type = type, ElementType = elementType, ArraySize = arraySize, ImmutableSampler = immutableSampler }); - } - - internal struct Entry - { - public ParameterKey Key; - public string LogicalGroup; - public EffectParameterClass Class; - public EffectParameterType Type; - public EffectParameterType ElementType; - public int ArraySize; - public SamplerState ImmutableSampler; - } + hashBuilder.Write(key.Name); + hashBuilder.Write(@class); + hashBuilder.Write(arraySize); + + ElementCount += arraySize; + Entries.Add(new Entry(key, logicalGroup, @class, type, elementType, arraySize, immutableSampler)); } + + /// + /// Represents an resource bindings in a Descriptor Set layout, containing metadata about a parameter's key, grouping, + /// type, and other attributes. + /// + /// The by which the Graphics Resource' Descriptor can be identified in the Shader. + /// A logical group name, used to group related Descriptors and variables together. + /// The kind of the parameter in the Shader. + /// The data type of the parameter in the Shader. + /// + /// The data type of the elements of the parameter in the Shader in case represents an + /// Array, Buffer, or Texture type. + /// + /// + /// The number of elements in the Array in case represents an Array type. + /// Specify 1 if the parameter is not an Array. + /// + /// + /// An optional unmodifiable Sampler State described directly in the Shader for sampling the parameter. + /// Specify if the parameter does not require a Sampler State. + /// + internal readonly record struct Entry + ( + ParameterKey Key, + string LogicalGroup, + EffectParameterClass Class, + EffectParameterType Type, + EffectParameterType ElementType, + int ArraySize, + SamplerState? ImmutableSampler + ); } diff --git a/sources/engine/Stride.Graphics/DescriptorTypeCount.cs b/sources/engine/Stride.Graphics/DescriptorTypeCount.cs index 9d879cdf09..b3390da508 100644 --- a/sources/engine/Stride.Graphics/DescriptorTypeCount.cs +++ b/sources/engine/Stride.Graphics/DescriptorTypeCount.cs @@ -1,21 +1,13 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Shaders; -namespace Stride.Graphics -{ - /// - /// Describes how many descriptor of a specific type will need to be allocated in a . - /// - public struct DescriptorTypeCount - { - public EffectParameterClass Type; - public int Count; +namespace Stride.Graphics; - public DescriptorTypeCount(EffectParameterClass type, int count) - { - Type = type; - Count = count; - } - } -} +/// +/// Describes how many Descriptor of a specific type will need to be allocated in a . +/// +/// The type of the Descriptors to allocate. +/// The number of Descriptors that need to be allocated. +public readonly record struct DescriptorTypeCount(EffectParameterClass Type, int Count); diff --git a/sources/engine/Stride.Graphics/DeviceCreationFlags.cs b/sources/engine/Stride.Graphics/DeviceCreationFlags.cs index 3d379eec6e..d20c41829c 100644 --- a/sources/engine/Stride.Graphics/DeviceCreationFlags.cs +++ b/sources/engine/Stride.Graphics/DeviceCreationFlags.cs @@ -1,41 +1,56 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Flags describing the parameters that are used to create a Graphics Device. +/// +[Flags] +public enum DeviceCreationFlags : int { + None = 0, + /// - /// - /// Describes parameters that are used to create a device. - /// + /// Creates a Graphics Device that supports the debug layer. /// - [Flags] - public enum DeviceCreationFlags : int - { - /// - /// Creates a device that supports the debug layer. - /// - Debug = unchecked((int)2), + Debug = 2, - /// - /// Required for Direct2D interoperability with Direct3D resource. - /// - BgraSupport = unchecked((int)32), + /// + /// Creates a Graphics Device requiring BGRA pixel format support, which is required for + /// Direct2D interoperability with Direct3D resources. + /// + BgraSupport = 32, - /// - /// Forces the creation of the Direct3D device to fail if the display driver is not implemented to the WDDM for Windows Developer Preview (WDDM 1.2). When the display driver is not implemented to WDDM 1.2, only a Direct3D device that is created with feature level 9.1, 9.2, or 9.3 supports video; therefore, if this flag is set, the runtime creates the Direct3D device only for feature level 9.1, 9.2, or 9.3. We recommend not to specify this flag for applications that want to favor Direct3D capability over video. If feature level 10 and higher is available, the runtime will use that feature level regardless of video support. - /// If this flag is set, device creation on the Basic Render Device (BRD) will succeed regardless of the BRD's missing support for video decode. This is because the Media Foundation video stack operates in software mode on BRD. In this situation, if you force the video stack to create the Direct3D device twice (create the device once with this flag, next discover BRD, then again create the device without the flag), you actually degrade performance. - /// - /// - /// If you attempt to create a Direct3D device with driver type , , or , device creation fails at any feature level because none of the associated drivers provide video capability. If you attempt to create a Direct3D device with driver type , device creation succeeds to allow software fallback for video. - /// - /// Direct 3D 11:?? - This value is not supported until Direct3D 11.1. - /// - VideoSupport = unchecked((int)2048), + // TODO: Should public API mention Silk? It's internal detail and device type is not configurable by Stride - /// - /// None. - /// - None = unchecked((int)0), - } + /// + /// Forces the creation of the Graphics Device to fail if the display driver is not implemented + /// as a WDDM 1.2 driver, in which case only a Direct3D device that is created with feature level + /// 9.1, 9.2, or 9.3 supports video; + /// Therefore, if this flag is set, the runtime creates the Direct3D device only for feature level + /// 9.1, 9.2, or 9.3. + /// + /// + /// We recommend not to specify this flag for applications that want to favor Direct3D capability over video. + /// If feature level 10 and higher is available, the runtime will use that feature level regardless of + /// video support. + /// + /// If this flag is set, device creation on the Basic Render Device (BRD) will succeed regardless of the + /// BRD's missing support for video decode. + /// This is because the Media Foundation video stack operates in software mode on BRD. In this situation, + /// if you force the video stack to create the Direct3D device twice (create the device once with this flag, + /// next discover BRD, then again create the device without the flag), you actually degrade performance. + /// + /// If you attempt to create a Direct3D device with driver type , + /// , or , + /// device creation fails at any feature level because none of the associated drivers provide video capability. + /// If you attempt to create a Direct3D device with driver type , + /// device creation succeeds to allow software fallback for video. + /// + /// This value is not supported until Direct3D 11.1. + /// + VideoSupport = 2048 } diff --git a/sources/engine/Stride.Graphics/Direct3D/Buffer.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/Buffer.Direct3D.cs index f7d753fdd2..cfcfee1c5b 100644 --- a/sources/engine/Stride.Graphics/Direct3D/Buffer.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/Buffer.Direct3D.cs @@ -1,62 +1,172 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 + using System; -using System.Collections.Generic; -using SharpDX; -using SharpDX.Direct3D11; -using SharpDX.DXGI; +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D11; + +using static Stride.Graphics.ComPtrHelpers; namespace Stride.Graphics { - public partial class Buffer + public unsafe partial class Buffer { - private SharpDX.Direct3D11.BufferDescription nativeDescription; + // Internal Direct3D 11 Buffer + private ID3D11Buffer* nativeBuffer; + + // Internal Direct3D 11 Buffer description + private BufferDesc nativeDescription; - internal SharpDX.Direct3D11.Buffer NativeBuffer - { - get - { - return (SharpDX.Direct3D11.Buffer)NativeDeviceChild; - } - } /// - /// Initializes a new instance of the class. + /// Gets the internal Direct3D 11 Buffer. /// - /// The description. - /// Type of the buffer. - /// The view format. - /// The data pointer. - protected Buffer InitializeFromImpl(BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeBuffer => ToComPtr(nativeBuffer); + + + /// + /// Initializes this instance with the provided options. + /// + /// A structure describing the buffer characteristics. + /// A combination of flags determining how the Views over this buffer should behave. + /// + /// View format used if the buffer is used as a Shader Resource View, + /// or if not. + /// + /// The data pointer to the data to initialize the buffer with. + /// This same instance of already initialized. + /// Element size (StructureByteStride) must be greater than zero for Structured Buffers. + protected partial Buffer InitializeFromImpl(ref readonly BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) { bufferDescription = description; - nativeDescription = ConvertToNativeDescription(Description); + nativeDescription = ConvertToNativeDescription(in description); + ViewFlags = viewFlags; - InitCountAndViewFormat(out this.elementCount, ref viewFormat); + InitCountAndViewFormat(out elementCount, ref viewFormat); ViewFormat = viewFormat; - NativeDeviceChild = new SharpDX.Direct3D11.Buffer(GraphicsDevice.NativeDevice, dataPointer, nativeDescription); + + var subresourceData = dataPointer != 0 ? new SubresourceData(dataPointer.ToPointer()) : default; + + var buffer = NullComPtr(); + + HResult result = dataPointer == 0 + ? NativeDevice.CreateBuffer(in nativeDescription, pInitialData: null, ref buffer) + : NativeDevice.CreateBuffer(in nativeDescription, in subresourceData, ref buffer); + + if (result.IsFailure) + result.Throw(); + + nativeBuffer = buffer.Handle; + SetNativeDeviceChild(buffer.AsDeviceChild()); // Staging resource don't have any views - if (nativeDescription.Usage != ResourceUsage.Staging) - this.InitializeViews(); + if (nativeDescription.Usage != Silk.NET.Direct3D11.Usage.Staging) + InitializeViews(); + + GraphicsDevice.RegisterBufferMemoryUsage(SizeInBytes); - if (GraphicsDevice != null) + return this; + + + /// + /// Returns a from the buffer's description. + /// + /// Element size (StructureByteStride) must be greater than zero for Structured Buffers. + static BufferDesc ConvertToNativeDescription(ref readonly BufferDescription bufferDescription) { - GraphicsDevice.RegisterBufferMemoryUsage(SizeInBytes); + var bufferDesc = new BufferDesc + { + ByteWidth = (uint) bufferDescription.SizeInBytes, + StructureByteStride = (uint) bufferDescription.StructureByteStride, + CPUAccessFlags = 0, + BindFlags = 0, + Usage = (Usage) bufferDescription.Usage + }; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.ConstantBuffer)) + { + bufferDesc.BindFlags |= (uint) BindFlag.ConstantBuffer; + bufferDesc.StructureByteStride = (uint) bufferDescription.StructureByteStride + (16 - ((uint) bufferDescription.StructureByteStride % 16)); + bufferDesc.CPUAccessFlags = (uint) CpuAccessFlag.Write; + } + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.IndexBuffer)) + bufferDesc.BindFlags |= (uint) BindFlag.IndexBuffer; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.VertexBuffer)) + bufferDesc.BindFlags |= (uint) BindFlag.VertexBuffer; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.RenderTarget)) + bufferDesc.BindFlags |= (uint) BindFlag.RenderTarget; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.ShaderResource)) + bufferDesc.BindFlags |= (uint) BindFlag.ShaderResource; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.UnorderedAccess)) + bufferDesc.BindFlags |= (uint) BindFlag.UnorderedAccess; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.StructuredBuffer)) + { + bufferDesc.MiscFlags |= (uint) BufferFlags.StructuredBuffer; + if (bufferDescription.StructureByteStride <= 0) + throw new ArgumentException("Element size must be greater than zero for structured buffers"); + } + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.RawBuffer)) + bufferDesc.MiscFlags |= (uint) ResourceMiscFlag.BufferAllowRawViews; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.ArgumentBuffer)) + bufferDesc.MiscFlags |= (uint) ResourceMiscFlag.DrawindirectArgs; + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.StreamOutput)) + bufferDesc.BindFlags |= (uint) BindFlag.StreamOutput; + + if (bufferDesc.Usage == Silk.NET.Direct3D11.Usage.Dynamic) + { + bufferDesc.CPUAccessFlags = (uint) CpuAccessFlag.Write; + if (bufferDesc.BindFlags == 0) + bufferDesc.BindFlags = (uint) BindFlag.ConstantBuffer; + } + + return bufferDesc; } - return this; + /// + /// Determines the number of elements and the element format depending on the type of buffer and intended view format. + /// + void InitCountAndViewFormat(out int count, ref PixelFormat viewFormat) + { + if (Description.StructureByteStride == 0) + { + // TODO: The way to calculate the count is not always correct depending on the ViewFlags...etc. + count = ViewFlags.HasFlag(BufferFlags.RawBuffer) ? Description.SizeInBytes / sizeof(int) : + ViewFlags.HasFlag(BufferFlags.ShaderResource) ? Description.SizeInBytes / viewFormat.SizeInBytes() : + 0; + } + else + { + // Structured Buffer + count = Description.SizeInBytes / Description.StructureByteStride; + viewFormat = PixelFormat.None; + } + } } /// protected internal override void OnDestroyed() { - if (GraphicsDevice != null) - { - GraphicsDevice.RegisterBufferMemoryUsage(-SizeInBytes); - } + SafeRelease(ref nativeBuffer); + UnsetNativeDeviceChild(); + + GraphicsDevice.RegisterBufferMemoryUsage(-SizeInBytes); base.OnDestroyed(); } @@ -66,233 +176,214 @@ protected internal override bool OnRecreate() { base.OnRecreate(); - if (Description.Usage == GraphicsResourceUsage.Immutable - || Description.Usage == GraphicsResourceUsage.Default) + if (Description.Usage is GraphicsResourceUsage.Immutable or GraphicsResourceUsage.Default) return false; - NativeDeviceChild = new SharpDX.Direct3D11.Buffer(GraphicsDevice.NativeDevice, IntPtr.Zero, nativeDescription); + var buffer = NullComPtr(); + + HResult result = NativeDevice.CreateBuffer(in nativeDescription, pInitialData: null, ref buffer); + + if (result.IsFailure) + result.Throw(); + + nativeBuffer = buffer.Handle; + SetNativeDeviceChild(buffer.AsDeviceChild()); // Staging resource don't have any views - if (nativeDescription.Usage != ResourceUsage.Staging) - this.InitializeViews(); + if (nativeDescription.Usage != Silk.NET.Direct3D11.Usage.Staging) + InitializeViews(); return true; } /// - /// Explicitly recreate buffer with given data. Usually called after a reset. + /// Recreates this buffer explicitly with the provided data. Usually called after the has been reset. /// - /// - /// + /// + /// The data pointer to the data to use to recreate the buffer with. + /// Specify if no initial data is needed. + /// public void Recreate(IntPtr dataPointer) { - NativeDeviceChild = new SharpDX.Direct3D11.Buffer(GraphicsDevice.NativeDevice, dataPointer, nativeDescription); + var buffer = NullComPtr(); + + var subresourceData = dataPointer == 0 ? new SubresourceData(dataPointer.ToPointer()) : default; + + HResult result = dataPointer == 0 + ? NativeDevice.CreateBuffer(in nativeDescription, pInitialData: null, ref buffer) + : NativeDevice.CreateBuffer(in nativeDescription, in subresourceData, ref buffer); + + if (result.IsFailure) + result.Throw(); + + nativeBuffer = buffer.Handle; + SetNativeDeviceChild(buffer.AsDeviceChild()); // Staging resource don't have any views - if (nativeDescription.Usage != ResourceUsage.Staging) - this.InitializeViews(); + if (nativeDescription.Usage != Silk.NET.Direct3D11.Usage.Staging) + InitializeViews(); } /// - /// Gets a for a particular . + /// Gets a for this Buffer for a particular . /// /// The view format. - /// A for the particular view format. + /// A for the particular view format. /// - /// The buffer must have been declared with . - /// The ShaderResourceView instance is kept by this buffer and will be disposed when this buffer is disposed. + /// The must have been declared with . + /// The Shader Resource View is kept by this Buffer and will be disposed when this Buffer is disposed. /// - internal ShaderResourceView GetShaderResourceView(PixelFormat viewFormat) + internal ComPtr GetShaderResourceView(PixelFormat viewFormat) { - ShaderResourceView srv = null; - if ((nativeDescription.BindFlags & BindFlags.ShaderResource) != 0) + var srv = NullComPtr(); + + var bindFlags = (BindFlag) nativeDescription.BindFlags; + + if (bindFlags.HasFlag(BindFlag.ShaderResource)) { - var description = new ShaderResourceViewDescription + var description = new ShaderResourceViewDesc { - Format = (SharpDX.DXGI.Format)viewFormat, - Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.ExtendedBuffer, - BufferEx = + Format = (Format) viewFormat, + ViewDimension = D3DSrvDimension.D3D11SrvDimensionBufferex, + + BufferEx = new() { - ElementCount = this.ElementCount, + NumElements = (uint) ElementCount, FirstElement = 0, - Flags = ShaderResourceViewExtendedBufferFlags.None, - }, + Flags = 0 + } }; - if (((ViewFlags & BufferFlags.RawBuffer) == BufferFlags.RawBuffer)) - description.BufferEx.Flags |= ShaderResourceViewExtendedBufferFlags.Raw; + if (ViewFlags.HasFlag(BufferFlags.RawBuffer)) + { + description.BufferEx.Flags |= (uint) BufferexSrvFlag.Raw; + } + + HResult result = NativeDevice.CreateShaderResourceView(NativeResource, in description, ref srv); - srv = new ShaderResourceView(this.GraphicsDevice.NativeDevice, NativeResource, description); + if (result.IsFailure) + result.Throw(); } + return srv; } /// - /// Gets a for a particular . + /// Gets a for this Buffer for a particular . /// /// The view format. - /// The width in pixels of the render target. - /// A for the particular view format. - /// The buffer must have been declared with . - /// The RenderTargetView instance is kept by this buffer and will be disposed when this buffer is disposed. - internal RenderTargetView GetRenderTargetView(PixelFormat pixelFormat, int width) + /// The width in pixels of the Render Target View. + /// A for the particular view format. + /// + /// The must have been declared with . + /// The Render Target View is kept by this Buffer and will be disposed when this Buffer is disposed. + /// + internal ComPtr GetRenderTargetView(PixelFormat pixelFormat, int width) { - RenderTargetView srv = null; - if ((nativeDescription.BindFlags & BindFlags.RenderTarget) != 0) + var rtv = NullComPtr(); + + var bindFlags = (BindFlag) nativeDescription.BindFlags; + + if (bindFlags.HasFlag(BindFlag.RenderTarget)) { - var description = new RenderTargetViewDescription() + var description = new RenderTargetViewDesc { - Format = (SharpDX.DXGI.Format)pixelFormat, - Dimension = RenderTargetViewDimension.Buffer, - Buffer = + Format = (Format) pixelFormat, + ViewDimension = RtvDimension.Buffer, + + Buffer = new() { - ElementWidth = pixelFormat.SizeInBytes() * width, - ElementOffset = 0, - }, + ElementWidth = (uint) (pixelFormat.SizeInBytes() * width), + ElementOffset = 0 + } }; - srv = new RenderTargetView(this.GraphicsDevice.NativeDevice, NativeBuffer, description); + HResult result = NativeDevice.CreateRenderTargetView(NativeResource, in description, ref rtv); + + if (result.IsFailure) + result.Throw(); } - return srv; + + return rtv; } + /// protected override void OnNameChanged() { base.OnNameChanged(); - if (GraphicsDevice != null && GraphicsDevice.IsDebugMode) - { - if (NativeShaderResourceView != null) - NativeShaderResourceView.DebugName = Name == null ? null : string.Format("{0} SRV", Name); - if (NativeUnorderedAccessView != null) - NativeUnorderedAccessView.DebugName = Name == null ? null : string.Format("{0} UAV", Name); - } - } + if (GraphicsDevice is not { IsDebugMode: true }) + return; - private void InitCountAndViewFormat(out int count, ref PixelFormat viewFormat) - { - if (Description.StructureByteStride == 0) + if (NativeShaderResourceView.IsNotNull()) { - // TODO: The way to calculate the count is not always correct depending on the ViewFlags...etc. - if ((ViewFlags & BufferFlags.RawBuffer) != 0) - { - count = Description.SizeInBytes / sizeof(int); - } - else if ((ViewFlags & BufferFlags.ShaderResource) != 0) - { - count = Description.SizeInBytes / viewFormat.SizeInBytes(); - } - else - { - count = 0; - } + NativeShaderResourceView.SetDebugName(Name is null ? null : $"{Name} SRV"); } - else - { - // For structured buffer - count = Description.SizeInBytes / Description.StructureByteStride; - viewFormat = PixelFormat.None; - } - } - - private static SharpDX.Direct3D11.BufferDescription ConvertToNativeDescription(BufferDescription bufferDescription) - { - var desc = new SharpDX.Direct3D11.BufferDescription() - { - SizeInBytes = bufferDescription.SizeInBytes, - StructureByteStride = bufferDescription.StructureByteStride, - CpuAccessFlags = GetCpuAccessFlagsFromUsage(bufferDescription.Usage), - BindFlags = BindFlags.None, - OptionFlags = ResourceOptionFlags.None, - Usage = (SharpDX.Direct3D11.ResourceUsage)bufferDescription.Usage, - }; - - var bufferFlags = bufferDescription.BufferFlags; - - if ((bufferFlags & BufferFlags.ConstantBuffer) != 0) - desc.BindFlags |= BindFlags.ConstantBuffer; - - if ((bufferFlags & BufferFlags.IndexBuffer) != 0) - desc.BindFlags |= BindFlags.IndexBuffer; - - if ((bufferFlags & BufferFlags.VertexBuffer) != 0) - desc.BindFlags |= BindFlags.VertexBuffer; - - if ((bufferFlags & BufferFlags.RenderTarget) != 0) - desc.BindFlags |= BindFlags.RenderTarget; - - if ((bufferFlags & BufferFlags.ShaderResource) != 0) - desc.BindFlags |= BindFlags.ShaderResource; - - if ((bufferFlags & BufferFlags.UnorderedAccess) != 0) - desc.BindFlags |= BindFlags.UnorderedAccess; - - if ((bufferFlags & BufferFlags.StructuredBuffer) != 0) + if (NativeUnorderedAccessView.IsNotNull()) { - desc.OptionFlags |= ResourceOptionFlags.BufferStructured; - if (bufferDescription.StructureByteStride <= 0) - throw new ArgumentException("Element size cannot be less or equal 0 for structured buffer"); + NativeUnorderedAccessView.SetDebugName(Name is null ? null : $"{Name} UAV"); } - - if ((bufferFlags & BufferFlags.RawBuffer) == BufferFlags.RawBuffer) - desc.OptionFlags |= ResourceOptionFlags.BufferAllowRawViews; - - if ((bufferFlags & BufferFlags.ArgumentBuffer) == BufferFlags.ArgumentBuffer) - desc.OptionFlags |= ResourceOptionFlags.DrawIndirectArguments; - - if ((bufferFlags & BufferFlags.StreamOutput) != 0) - desc.BindFlags |= BindFlags.StreamOutput; - - return desc; } /// - /// Initializes the views. + /// Initializes the views associated with this (a Shader Resource View and an + /// Unordered Access View). /// private void InitializeViews() { - var bindFlags = nativeDescription.BindFlags; + var bindFlags = (BindFlag) nativeDescription.BindFlags; var srvFormat = ViewFormat; var uavFormat = ViewFormat; - if (((ViewFlags & BufferFlags.RawBuffer) != 0)) + if (ViewFlags.HasFlag(BufferFlags.RawBuffer)) { srvFormat = PixelFormat.R32_Typeless; uavFormat = PixelFormat.R32_Typeless; } - if ((bindFlags & BindFlags.ShaderResource) != 0) + if (bindFlags.HasFlag(BindFlag.ShaderResource)) { - this.NativeShaderResourceView = GetShaderResourceView(srvFormat); + NativeShaderResourceView = GetShaderResourceView(srvFormat); } - if ((bindFlags & BindFlags.UnorderedAccess) != 0) + if (bindFlags.HasFlag(BindFlag.UnorderedAccess)) { - var description = new UnorderedAccessViewDescription() + var description = new UnorderedAccessViewDesc { - Format = (SharpDX.DXGI.Format)uavFormat, - Dimension = UnorderedAccessViewDimension.Buffer, - Buffer = + Format = (Format) uavFormat, + ViewDimension = UavDimension.Buffer, + + Buffer = new() { - ElementCount = this.ElementCount, + NumElements = (uint) ElementCount, FirstElement = 0, - Flags = UnorderedAccessViewBufferFlags.None, - }, + Flags = 0 + } }; - if (((ViewFlags & BufferFlags.RawBuffer) == BufferFlags.RawBuffer)) - description.Buffer.Flags |= UnorderedAccessViewBufferFlags.Raw; + var bufferFlags = (BufferUavFlag) description.Buffer.Flags; + + if (ViewFlags.HasFlag(BufferFlags.RawBuffer)) + bufferFlags |= BufferUavFlag.Raw; + + if (ViewFlags.HasFlag(BufferFlags.StructuredAppendBuffer)) + bufferFlags |= BufferUavFlag.Append; - if (((ViewFlags & BufferFlags.StructuredAppendBuffer) == BufferFlags.StructuredAppendBuffer)) - description.Buffer.Flags |= UnorderedAccessViewBufferFlags.Append; + if (ViewFlags.HasFlag(BufferFlags.StructuredCounterBuffer)) + bufferFlags |= BufferUavFlag.Counter; - if (((ViewFlags & BufferFlags.StructuredCounterBuffer) == BufferFlags.StructuredCounterBuffer)) - description.Buffer.Flags |= UnorderedAccessViewBufferFlags.Counter; + description.Buffer = description.Buffer with { Flags = (uint) bufferFlags }; - this.NativeUnorderedAccessView = new UnorderedAccessView(this.GraphicsDevice.NativeDevice, NativeBuffer, description); + var unorderedAccessView = NullComPtr(); + + HResult result = NativeDevice.CreateUnorderedAccessView(NativeResource, in description, ref unorderedAccessView); + + if (result.IsFailure) + result.Throw(); } } } -} -#endif +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/ColorHelper.cs b/sources/engine/Stride.Graphics/Direct3D/ColorHelper.cs deleted file mode 100644 index ace43a99ed..0000000000 --- a/sources/engine/Stride.Graphics/Direct3D/ColorHelper.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_DIRECT3D -// Copyright (c) 2010-2011 SharpDX - Alexandre Mutel -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System; -using SharpDX; -using SharpDX.Mathematics.Interop; -using Stride.Core.Mathematics; - -namespace Stride.Graphics -{ - internal class ColorHelper - { - public static unsafe RawColor4 Convert(Color4 color) - { - return *(RawColor4*)&color; - } - - public static unsafe Color4 Convert(RawColor4 color) - { - return *(Color4*)&color; - } - - public static unsafe RawVector4 ConvertToVector4(Color4 color) - { - return *(RawVector4*)&color; - } - -#if STRIDE_GRAPHICS_API_DIRECT3D12 - public static unsafe SharpDX.Direct3D12.StaticBorderColor ConvertStatic(Color4 color) - { - if (color == Color4.Black) - { - return SharpDX.Direct3D12.StaticBorderColor.OpaqueBlack; - } - else if (color == Color4.White) - { - return SharpDX.Direct3D12.StaticBorderColor.OpaqueWhite; - } - else if (color == new Color4()) - { - return SharpDX.Direct3D12.StaticBorderColor.TransparentBlack; - } - - throw new NotSupportedException("Static sampler can only have opaque black, opaque white or transparent white as border color."); - } -#endif - } -} - - -#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/ComPtrEqualityComparer.cs b/sources/engine/Stride.Graphics/Direct3D/ComPtrEqualityComparer.cs new file mode 100644 index 0000000000..e7335bd750 --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D/ComPtrEqualityComparer.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +using Silk.NET.Core.Native; + +namespace Stride.Graphics; + +/// +/// Provides a mechanism for comparing two instances for equality. +/// +/// +/// The type of the unmanaged COM interface that the instances point to. +/// +internal sealed class ComPtrEqualityComparer : IEqualityComparer> + where T : unmanaged, IComVtbl +{ + /// + /// Provides a default instance of the class. + /// + public static ComPtrEqualityComparer Default = new(); + + private ComPtrEqualityComparer() { } + + /// + public unsafe bool Equals(ComPtr x, ComPtr y) => x.Handle == y.Handle; + + /// + public unsafe int GetHashCode([DisallowNull] ComPtr obj) => ((nint) obj.Handle).GetHashCode(); +} diff --git a/sources/engine/Stride.Graphics/Direct3D/ComPtrHelpers.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/ComPtrHelpers.Direct3D.cs new file mode 100644 index 0000000000..417159ae34 --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D/ComPtrHelpers.Direct3D.cs @@ -0,0 +1,216 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 + +using System.Runtime.CompilerServices; + +using Silk.NET.Core.Native; +#if STRIDE_GRAPHICS_API_DIRECT3D11 +using Silk.NET.Direct3D11; +#elif STRIDE_GRAPHICS_API_DIRECT3D12 +using Silk.NET.Direct3D12; +#endif + +namespace Stride.Graphics; + +/// +/// Defines helper methods to aid in the usage of Direct3D COM pointers with . +/// +internal static unsafe class ComPtrHelpers +{ + /// + /// Returns a COM pointer. + /// + /// The type of the COM pointer. + /// + /// A COM pointer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ComPtr NullComPtr() + where T : unmanaged, IComVtbl + { + return default; + } + + /// + /// Checks if the underlying COM pointer is . + /// + /// The type of the COM pointer. + /// The COM pointer to check. + /// + /// if the COM pointer is ; otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNull(this ComPtr comPtr) + where T : unmanaged, IComVtbl + { + return comPtr.Handle == null; + } + + /// + /// Checks if the underlying COM pointer is a valid non- pointer. + /// + /// The type of the COM pointer. + /// The COM pointer to check. + /// + /// if the COM pointer is not ; otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNotNull(this ComPtr comPtr) + where T : unmanaged, IComVtbl + { + return comPtr.Handle != null; + } + + /// + /// Indicates whether two COM pointers are equal (i.e. point to the same COM object). + /// + /// The type of the COM pointer. + /// The first COM pointer to compare. + /// The second COM pointer to compare. + /// + /// if the two COM pointer are equal; otherwise, . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool EqualsComPtr(this ComPtr left, ComPtr right) + where T : unmanaged, IComVtbl + { + return left.Handle == right.Handle; + } + + /// + /// Casts a COM pointer from one interface type to another. + /// + /// The source interface type. + /// The target interface type. + /// The COM pointer to be cast. + /// A new representing the casted COM pointer. + /// + /// This method performs a direct cast of the underlying pointer. It is the caller's responsibility + /// to ensure that the cast is valid. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ComPtr CastComPtr(ComPtr comPtr) + where TFrom : unmanaged, IComVtbl + where TTo : unmanaged, IComVtbl + { + return new ComPtr { Handle = (TTo*) comPtr.Handle }; + } + + /// + /// Returns a new instance wrapping the specified native COM pointer without + /// altering its reference count. + /// + /// The type of the COM pointer. + /// The native COM pointer to wrap. + /// A instance wrapping . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ComPtr ToComPtr(T* nativePtr) + where T : unmanaged, IComVtbl + { + return new ComPtr { Handle = nativePtr }; + } + + /// + /// Returns a new instance wrapping the specified native COM pointer without + /// altering its reference count. + /// + /// The type of the resulting COM pointer. + /// The type of the COM pointer to an interface that inherits from that of . + /// The native COM pointer to wrap. + /// A to a instance wrapping . + public static ComPtr ToComPtr(TChild* nativePtr) + where TParent : unmanaged, IComVtbl + where TChild : unmanaged, IComVtbl, IComVtbl + { + var parentPtr = (TParent*) nativePtr; + return new ComPtr { Handle = parentPtr }; + } + + /// + /// Releases a COM pointer and sets it to . + /// + /// The type of the COM pointer. + /// The COM pointer to release and forget. + public static void SafeRelease(ref T* nativePtr) + where T : unmanaged, IComVtbl + { + if (nativePtr != null) + { + var iUnknown = (IUnknown*) nativePtr; + iUnknown->Release(); + nativePtr = null; + } + } + + /// + /// Releases a COM pointer and sets it to . + /// + /// The type of the COM pointer. + /// The COM pointer to release and forget. + public static void SafeRelease(ref ComPtr comPtr) + where T : unmanaged, IComVtbl, IComVtbl + { + if (comPtr.Handle != null) + { + var iUnknown = (IUnknown*) comPtr.Detach(); + iUnknown->Release(); + } + } + + /// + /// Returns a reinterpreting a COM pointer to a Direct3D 11 device child as a + /// COM pointer to a . + /// + /// The COM pointer of a Direct3D 11 device child. + /// The COM pointer reinterpreted as a . + public static ComPtr AsIUnknown(this ComPtr comPtr) + where T : unmanaged, IComVtbl, IComVtbl + { + return new ComPtr { Handle = (IUnknown*) comPtr.Handle }; + } + + /// + /// Reinterprets a to an interface of type as a + /// COM pointer to an interface of type which it inherits from. + /// + /// The COM pointer to reinterpret. + /// The COM pointer reinterpreted as a of type . + public static ComPtr AsComPtr(this ComPtr comPtr) + where TFrom : unmanaged, IComVtbl, IComVtbl + where TTo : unmanaged, IComVtbl + { + return new ComPtr { Handle = (TTo*) comPtr.Handle }; + } + +#if STRIDE_GRAPHICS_API_DIRECT3D11 + /// + /// Returns a reinterpreting a COM pointer to a Direct3D 11 device child as a + /// COM pointer to a . + /// + /// The COM pointer of a Direct3D 11 device child. + /// The COM pointer reinterpreted as a . + public static ComPtr AsDeviceChild(this ComPtr comPtr) + where T : unmanaged, IComVtbl, IComVtbl + { + return new ComPtr { Handle = (ID3D11DeviceChild*) comPtr.Handle }; + } +#endif + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + /// + /// Returns a reinterpreting a COM pointer to a Direct3D 12 device child as a + /// COM pointer to a . + /// + /// The COM pointer of a Direct3D 12 device child. + /// The COM pointer reinterpreted as a . + public static ComPtr AsDeviceChild(this ComPtr comPtr) + where T : unmanaged, IComVtbl, IComVtbl + { + return new ComPtr { Handle = (ID3D12DeviceChild*) comPtr.Handle }; + } +#endif +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/CommandList.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/CommandList.Direct3D.cs index 88e13420bd..a1700fa576 100644 --- a/sources/engine/Stride.Graphics/Direct3D/CommandList.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/CommandList.Direct3D.cs @@ -1,122 +1,181 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 + using System; -using SharpDX.Mathematics.Interop; -using Stride.Core; +using System.Diagnostics; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D11; + using Stride.Core.Mathematics; +using Stride.Core.UnsafeExtensions; using Stride.Shaders; +using SilkBox2I = Silk.NET.Maths.Box2D; +using D3D11Viewport = Silk.NET.Direct3D11.Viewport; +using D3D11Box = Silk.NET.Direct3D11.Box; + +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Graphics.ComPtrHelpers; + namespace Stride.Graphics { - public partial class CommandList + public unsafe partial class CommandList { - private const int ConstantBufferCount = SharpDX.Direct3D11.CommonShaderStage.ConstantBufferApiSlotCount; // 14 actually - private const int SamplerStateCount = SharpDX.Direct3D11.CommonShaderStage.SamplerSlotCount; - private const int ShaderResourceViewCount = SharpDX.Direct3D11.CommonShaderStage.InputResourceSlotCount; - private const int SimultaneousRenderTargetCount = SharpDX.Direct3D11.OutputMergerStage.SimultaneousRenderTargetCount; - private const int StageCount = 6; - private const int UnorderedAcccesViewCount = SharpDX.Direct3D11.ComputeShaderStage.UnorderedAccessViewSlotCount; + private const int ConstantBufferCount = D3D11.CommonshaderConstantBufferApiSlotCount; // 14 actually + private const int SamplerStateCount = D3D11.CommonshaderSamplerSlotCount; + private const int ShaderResourceViewCount = D3D11.CommonshaderInputResourceSlotCount; // TODO: Unused? + private const int SimultaneousRenderTargetCount = D3D11.SimultaneousRenderTargetCount; + private const int UnorderedAcccesViewCount = D3D11.D3D111UavSlotCount; + + private ID3D11DeviceContext* nativeDeviceContext; + private ID3D11DeviceContext1* nativeDeviceContext1; // TODO: Unused? + private ID3DUserDefinedAnnotation* nativeDeviceProfiler; - private SharpDX.Direct3D11.DeviceContext nativeDeviceContext; - private SharpDX.Direct3D11.DeviceContext1 nativeDeviceContext1; - private SharpDX.Direct3D11.UserDefinedAnnotation nativeDeviceProfiler; + private readonly ComPtr[] currentRenderTargetViews = new ComPtr[SimultaneousRenderTargetCount]; + private int currentRenderTargetViewsActiveCount = 0; - private SharpDX.Direct3D11.InputAssemblerStage inputAssembler; - private SharpDX.Direct3D11.OutputMergerStage outputMerger; + private readonly ComPtr[] currentUARenderTargetViews = new ComPtr[SimultaneousRenderTargetCount]; + private readonly ComPtr[] unorderedAccessViews = new ComPtr[UnorderedAcccesViewCount]; // Only CS + + private const int StageCount = 6; - private readonly SharpDX.Direct3D11.RenderTargetView[] currentRenderTargetViews = new SharpDX.Direct3D11.RenderTargetView[SimultaneousRenderTargetCount]; - private int currentRenderTargetViewsActiveCount = 0; - private readonly SharpDX.Direct3D11.UnorderedAccessView[] currentUARenderTargetViews = new SharpDX.Direct3D11.UnorderedAccessView[SimultaneousRenderTargetCount]; - private readonly SharpDX.Direct3D11.CommonShaderStage[] shaderStages = new SharpDX.Direct3D11.CommonShaderStage[StageCount]; private readonly Buffer[] constantBuffers = new Buffer[StageCount * ConstantBufferCount]; private readonly SamplerState[] samplerStates = new SamplerState[StageCount * SamplerStateCount]; - private readonly SharpDX.Direct3D11.UnorderedAccessView[] unorderedAccessViews = new SharpDX.Direct3D11.UnorderedAccessView[UnorderedAcccesViewCount]; // Only CS private PipelineState currentPipelineState; + + /// + /// Gets the internal Direct3D 11 Device Context. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeDeviceContext => ToComPtr(nativeDeviceContext); + + + /// + /// Creates a new . + /// + /// The Graphics Device. + /// The new instance of . + /// Creation of additional Command Lists is not supported for Direct3D 11. public static CommandList New(GraphicsDevice device) { - throw new InvalidOperationException("Can't create multiple command lists with D3D11"); + throw new InvalidOperationException("Creation of additional Command Lists is not supported for Direct3D 11"); } + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. internal CommandList(GraphicsDevice device) : base(device) { - nativeDeviceContext = device.NativeDeviceContext; - NativeDeviceChild = nativeDeviceContext; - nativeDeviceContext1 = new SharpDX.Direct3D11.DeviceContext1(nativeDeviceContext.NativePointer); - nativeDeviceProfiler = device.IsDebugMode ? SharpDX.ComObject.QueryInterfaceOrNull(nativeDeviceContext.NativePointer) : null; + nativeDeviceContext = device.NativeDeviceContext.Handle; + SetNativeDeviceChild(NativeDeviceContext.AsDeviceChild()); + + HResult result = nativeDeviceContext->QueryInterface(out ComPtr _); + + if (result.IsFailure) + result.Throw(); - InitializeStages(); + ComPtr deviceProfiler = default; + if (device.IsDebugMode) + { + result = nativeDeviceContext->QueryInterface(out deviceProfiler); + if (result.IsFailure) + deviceProfiler = null; + } + nativeDeviceProfiler = deviceProfiler; ClearState(); } - /// - /// Gets the native device context. - /// - /// The native device context. - internal SharpDX.Direct3D11.DeviceContext NativeDeviceContext => nativeDeviceContext; - /// protected internal override void OnDestroyed() { - Utilities.Dispose(ref nativeDeviceProfiler); + SafeRelease(ref nativeDeviceContext); + SafeRelease(ref nativeDeviceContext1); + SafeRelease(ref nativeDeviceProfiler); base.OnDestroyed(); } - public void Reset() + + /// + /// Resets a Command List back to its initial state as if a new Command List was just created. + /// + /// + /// Deferred execution of Command Lists is not supported for Direct3D 11. This method does nothing. + /// + public unsafe partial void Reset() { } - public void Flush() + /// + /// Closes and executes the Command List. + /// + public partial void Flush() { } - public CompiledCommandList Close() + /// + /// Indicates that recording to the Command List has finished. + /// + /// + /// A representing the frozen list of recorded commands + /// that can be executed at a later time. + /// + /// + /// Compiled Command Lists are not supported for Direct3D 11. This method returns a empty one. + /// + public partial CompiledCommandList Close() { - return default(CompiledCommandList); + return default; } - private void ClearStateImpl() + /// + /// Direct3D 11 implementation that clears and restores the state of the Graphics Device. + /// + private partial void ClearStateImpl() { - NativeDeviceContext?.ClearState(); + if (nativeDeviceContext is not null) + nativeDeviceContext->ClearState(); - for (int i = 0; i < samplerStates.Length; ++i) - samplerStates[i] = null; - for (int i = 0; i < constantBuffers.Length; ++i) - constantBuffers[i] = null; - for (int i = 0; i < unorderedAccessViews.Length; ++i) - unorderedAccessViews[i] = null; - for (int i = 0; i < currentRenderTargetViews.Length; i++) - currentRenderTargetViews[i] = null; - for (int i = 0; i < currentUARenderTargetViews.Length; i++) - currentUARenderTargetViews[i] = null; + Array.Clear(samplerStates); + Array.Clear(constantBuffers); + + Array.Clear(unorderedAccessViews); + Array.Clear(currentRenderTargetViews); + Array.Clear(currentUARenderTargetViews); // Since nothing can be drawn in default state, no need to set anything (another SetPipelineState should happen before) currentPipelineState = GraphicsDevice.DefaultPipelineState; } /// - /// Unbinds all depth-stencil buffer and render targets from the output-merger stage. + /// Unbinds the Depth-Stencil Buffer and all the Render Targets from the output-merger stage. /// private void ResetTargetsImpl() { - for (int i = 0; i < currentRenderTargetViews.Length; i++) - currentRenderTargetViews[i] = null; - for (int i = 0; i < currentUARenderTargetViews.Length; i++) - currentUARenderTargetViews[i] = null; - outputMerger.ResetTargets(); + Array.Clear(currentRenderTargetViews); + Array.Clear(currentUARenderTargetViews); + + // Reset all targets + nativeDeviceContext->OMSetRenderTargets(NumViews: 0, ppRenderTargetViews: null, pDepthStencilView: (ID3D11DepthStencilView*) null); } /// - /// Binds a depth-stencil buffer and a set of render targets to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and a set of Render Targets to the output-merger stage. /// - /// The depth stencil buffer. - /// The number of render targets. - /// The render targets. - /// renderTargetViews + /// The Depth-Stencil Buffer to bind. + /// The number of Render Targets to bind. + /// The Render Targets to bind. private void SetRenderTargetsImpl(Texture depthStencilBuffer, int renderTargetCount, Texture[] renderTargets) { currentRenderTargetViewsActiveCount = renderTargetCount; @@ -124,51 +183,69 @@ private void SetRenderTargetsImpl(Texture depthStencilBuffer, int renderTargetCo for (int i = 0; i < renderTargetCount; i++) currentRenderTargetViews[i] = renderTargets[i].NativeRenderTargetView; - outputMerger.SetTargets(depthStencilBuffer != null ? depthStencilBuffer.NativeDepthStencilView : null, renderTargetCount, currentRenderTargetViews); + nativeDeviceContext->OMSetRenderTargets(NumViews: (uint) renderTargetCount, + ppRenderTargetViews: ref currentRenderTargetViews[0], + pDepthStencilView: depthStencilBuffer?.NativeDepthStencilView ?? NullComPtr()); } - unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) + /// + /// Direct3D 11 implementation that sets a scissor rectangle to the rasterizer stage. + /// + /// The scissor rectangle to set. + private unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) { - NativeDeviceContext.Rasterizer.SetScissorRectangle(scissorRectangle.Left, scissorRectangle.Top, scissorRectangle.Right, scissorRectangle.Bottom); + ref var scissorBox = ref scissorRectangle.As(); + + nativeDeviceContext->RSSetScissorRects(NumRects: 1, in scissorBox); } - unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) + /// + /// Direct3D 11 implementation that sets one or more scissor rectangles to the rasterizer stage. + /// + /// The number of scissor rectangles to bind. + /// The set of scissor rectangles to bind. + private unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) { - if (scissorRectangles == null) throw new ArgumentNullException("scissorRectangles"); - var localScissorRectangles = new RawRectangle[scissorCount]; - for (int i = 0; i < scissorCount; i++) - { - localScissorRectangles[i] = new RawRectangle(scissorRectangles[i].X, scissorRectangles[i].Y, scissorRectangles[i].Right, scissorRectangles[i].Bottom); - } - NativeDeviceContext.Rasterizer.SetScissorRectangles(localScissorRectangles); + Debug.Assert(scissorRectangles is not null); + Debug.Assert(scissorRectangles.Length >= scissorCount); + + var scissorBoxes = scissorRectangles.AsSpan(); + + nativeDeviceContext->RSSetScissorRects((uint) scissorCount, in scissorBoxes[0]); } /// - /// Sets the stream targets. + /// Sets the stream output Buffers. /// - /// The buffers. + /// + /// The Buffers to set for stream output. + /// Specify or an empty array to unset any bound output Buffer. + /// public void SetStreamTargets(params Buffer[] buffers) { - SharpDX.Direct3D11.StreamOutputBufferBinding[] streamOutputBufferBindings; + var numBuffers = buffers?.Length ?? 0; - if (buffers != null) + if (numBuffers > 0) { - streamOutputBufferBindings = new SharpDX.Direct3D11.StreamOutputBufferBinding[buffers.Length]; - for (int i = 0; i < buffers.Length; ++i) - streamOutputBufferBindings[i].Buffer = buffers[i].NativeBuffer; + Span> streamOutputBuffers = stackalloc ComPtr[numBuffers]; + Span streamOutputOffsets = stackalloc uint[numBuffers]; + + for (int i = 0; i < numBuffers; ++i) + { + streamOutputBuffers[i] = buffers[i].NativeBuffer.Handle; + streamOutputOffsets[i] = 0; + } + nativeDeviceContext->SOSetTargets((uint) numBuffers, ref streamOutputBuffers[0], in streamOutputOffsets[0]); } else { - streamOutputBufferBindings = null; + nativeDeviceContext->SOSetTargets(NumBuffers: 0, ppSOTargets: null, pOffsets: (uint*) null); } - - NativeDeviceContext.StreamOutput.SetTargets(streamOutputBufferBindings); } /// - /// Gets or sets the 1st viewport. See to learn how to use it. + /// Sets the viewports to the rasterizer stage. /// - /// The viewport. private unsafe void SetViewportImpl() { if (!viewportDirty) @@ -176,202 +253,339 @@ private unsafe void SetViewportImpl() viewportDirty = false; - fixed (Viewport* viewportsPtr = viewports) - { - nativeDeviceContext.Rasterizer.SetViewports((RawViewportF*)viewportsPtr, renderTargetCount > 0 ? renderTargetCount : 1); - } + uint viewportCount = renderTargetCount > 0 ? (uint) renderTargetCount : 1; + var viewportsToSet = viewports.AsSpan(); + + nativeDeviceContext->RSSetViewports(viewportCount, in viewportsToSet[0]); } /// - /// Unsets the render targets. + /// Unsets the Render Targets currently bound to the pipeline. /// public void UnsetRenderTargets() { - NativeDeviceContext.OutputMerger.ResetTargets(); + var noRenderTargets = NullComPtr(); + var noDepthBuffer = NullComPtr(); + + nativeDeviceContext->OMSetRenderTargets(NumViews: 0, ppRenderTargetViews: ref noRenderTargets, noDepthBuffer); } /// - /// Sets a constant buffer to the shader pipeline. + /// Sets a Constant Buffer to the shader pipeline. /// /// The shader stage. /// The binding slot. - /// The constant buffer to set. + /// The Constant Buffer to set. + /// + /// is . Cannot set a Constant Buffer to an invalid shader stage. + /// internal void SetConstantBuffer(ShaderStage stage, int slot, Buffer buffer) { if (stage == ShaderStage.None) - throw new ArgumentException("Cannot use Stage.None", "stage"); + throw new ArgumentException($"Cannot use {nameof(ShaderStage)}.{nameof(ShaderStage.None)}", nameof(stage)); - int stageIndex = (int)stage - 1; + int stageIndex = (int) stage - 1; int slotIndex = stageIndex * ConstantBufferCount + slot; + if (constantBuffers[slotIndex] != buffer) { constantBuffers[slotIndex] = buffer; - shaderStages[stageIndex].SetConstantBuffer(slot, buffer != null ? buffer.NativeBuffer : null); + + var nativeBuffer = buffer is not null ? buffer.NativeBuffer : default; + + switch (stage) + { + case ShaderStage.Vertex: nativeDeviceContext->VSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Hull: nativeDeviceContext->HSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Domain: nativeDeviceContext->DSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Geometry: nativeDeviceContext->GSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Pixel: nativeDeviceContext->PSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Compute: nativeDeviceContext->CSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + } } } /// - /// Sets a constant buffer to the shader pipeline. + /// Sets a Constant Buffer to the shader pipeline. /// /// The shader stage. /// The binding slot. - /// The constant buffer to set. + /// The Constant Buffer to set. + /// + /// is . Cannot set a Constant Buffer to an invalid shader stage. + /// internal void SetConstantBuffer(ShaderStage stage, int slot, int offset, Buffer buffer) { + // TODO: offset param is not used; This method is exactly the same as the one above! + if (stage == ShaderStage.None) - throw new ArgumentException("Cannot use Stage.None", "stage"); + throw new ArgumentException($"Cannot use {nameof(ShaderStage)}.{nameof(ShaderStage.None)}", nameof(stage)); - int stageIndex = (int)stage - 1; + int stageIndex = (int) stage - 1; int slotIndex = stageIndex * ConstantBufferCount + slot; + if (constantBuffers[slotIndex] != buffer) { constantBuffers[slotIndex] = buffer; - shaderStages[stageIndex].SetConstantBuffer(slot, buffer != null ? buffer.NativeBuffer : null); + + var nativeBuffer = buffer is not null ? buffer.NativeBuffer : default; + + switch (stage) + { + case ShaderStage.Vertex: nativeDeviceContext->VSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Hull: nativeDeviceContext->HSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Domain: nativeDeviceContext->DSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Geometry: nativeDeviceContext->GSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Pixel: nativeDeviceContext->PSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + case ShaderStage.Compute: nativeDeviceContext->CSSetConstantBuffers((uint) slot, NumBuffers: 1, ref nativeBuffer); break; + } } } /// - /// Sets a sampler state to the shader pipeline. + /// Sets a Sampler State to the shader pipeline. /// /// The shader stage. /// The binding slot. - /// The sampler state to set. + /// The Sampler State to set. + /// + /// is . Cannot set a Sampler State to an invalid shader stage. + /// internal void SetSamplerState(ShaderStage stage, int slot, SamplerState samplerState) { if (stage == ShaderStage.None) - throw new ArgumentException("Cannot use Stage.None", "stage"); - int stageIndex = (int)stage - 1; + throw new ArgumentException($"Cannot use {nameof(ShaderStage)}.{nameof(ShaderStage.None)}", nameof(stage)); + + int stageIndex = (int) stage - 1; int slotIndex = stageIndex * SamplerStateCount + slot; + if (samplerStates[slotIndex] != samplerState) { samplerStates[slotIndex] = samplerState; - shaderStages[stageIndex].SetSampler(slot, samplerState != null ? (SharpDX.Direct3D11.SamplerState)samplerState.NativeDeviceChild : null); + + var nativeSampler = samplerState is not null ? samplerState.NativeSamplerState : default; + + switch (stage) + { + case ShaderStage.Vertex: nativeDeviceContext->VSSetSamplers((uint) slot, NumSamplers: 1, ref nativeSampler); break; + case ShaderStage.Hull: nativeDeviceContext->HSSetSamplers((uint) slot, NumSamplers: 1, ref nativeSampler); break; + case ShaderStage.Domain: nativeDeviceContext->DSSetSamplers((uint) slot, NumSamplers: 1, ref nativeSampler); break; + case ShaderStage.Geometry: nativeDeviceContext->GSSetSamplers((uint) slot, NumSamplers: 1, ref nativeSampler); break; + case ShaderStage.Pixel: nativeDeviceContext->PSSetSamplers((uint) slot, NumSamplers: 1, ref nativeSampler); break; + case ShaderStage.Compute: nativeDeviceContext->CSSetSamplers((uint) slot, NumSamplers: 1, ref nativeSampler); break; + } } } /// - /// Sets a shader resource view to the shader pipeline. + /// Sets a Shader Resource View to the shader pipeline. /// /// The shader stage. /// The binding slot. - /// The shader resource view. + /// The Shader Resource View to set. + /// + /// is . Cannot set a Shader Resource View to an invalid shader stage. + /// internal void SetShaderResourceView(ShaderStage stage, int slot, GraphicsResource shaderResourceView) { - shaderStages[(int)stage - 1].SetShaderResource(slot, shaderResourceView != null ? shaderResourceView.NativeShaderResourceView : null); + if (stage == ShaderStage.None) + throw new ArgumentException($"Cannot use {nameof(ShaderStage)}.{nameof(ShaderStage.None)}", nameof(stage)); + + var nativeShaderResourceView = shaderResourceView is not null ? shaderResourceView.NativeShaderResourceView : default; + + switch (stage) + { + case ShaderStage.Vertex: nativeDeviceContext->VSSetShaderResources((uint) slot, NumViews: 1, ref nativeShaderResourceView); break; + case ShaderStage.Hull: nativeDeviceContext->HSSetShaderResources((uint) slot, NumViews: 1, ref nativeShaderResourceView); break; + case ShaderStage.Domain: nativeDeviceContext->DSSetShaderResources((uint) slot, NumViews: 1, ref nativeShaderResourceView); break; + case ShaderStage.Geometry: nativeDeviceContext->GSSetShaderResources((uint) slot, NumViews: 1, ref nativeShaderResourceView); break; + case ShaderStage.Pixel: nativeDeviceContext->PSSetShaderResources((uint) slot, NumViews: 1, ref nativeShaderResourceView); break; + case ShaderStage.Compute: nativeDeviceContext->CSSetShaderResources((uint) slot, NumViews: 1, ref nativeShaderResourceView); break; + } } /// - /// Sets an unordered access view to the shader pipeline, without affecting ones that are already set. + /// Sets an Unordered Access View to the shader pipeline, without affecting ones that are already set. /// /// The binding slot. - /// The shader resource view. - /// The native unordered access view. - /// The Append/Consume buffer offset. See SetUnorderedAccessView for more details. - internal void OMSetSingleUnorderedAccessView(int slot, SharpDX.Direct3D11.UnorderedAccessView view, int uavInitialOffset) + /// The Unordered Access View to set. + /// + /// The Append/Consume Buffer offset. + /// + /// A value of -1 indicates the current offset should be kept. + /// + /// Any other value sets the hidden counter for that Appendable/Consumable UAV. + /// flag, otherwise the argument is ignored. + /// + /// + /// This parameter is only relevant for UAVs which have the or + /// Buffer flags. + /// + internal unsafe void OMSetSingleUnorderedAccessView(int slot, ComPtr view, int uavInitialOffset) { currentUARenderTargetViews[slot] = view; int remainingSlots = currentUARenderTargetViews.Length - currentRenderTargetViewsActiveCount; - var uavs = new SharpDX.Direct3D11.UnorderedAccessView[remainingSlots]; - Array.Copy(currentUARenderTargetViews, currentRenderTargetViewsActiveCount, uavs, 0, remainingSlots); + var uavs = stackalloc ComPtr[remainingSlots]; + + for (int fromIndex = currentRenderTargetViewsActiveCount, toIndex = 0; + toIndex < remainingSlots; + fromIndex++, toIndex++) + { + uavs[toIndex] = currentUARenderTargetViews[fromIndex]; + } + + var uavInitialCounts = stackalloc uint[remainingSlots]; - var uavInitialCounts = new int[remainingSlots]; for (int i = 0; i < remainingSlots; i++) - uavInitialCounts[i] = -1; - uavInitialCounts[slot - currentRenderTargetViewsActiveCount] = uavInitialOffset; + uavInitialCounts[i] = unchecked((uint) -1); + + uavInitialCounts[slot - currentRenderTargetViewsActiveCount] = (uint) uavInitialOffset; - outputMerger.SetUnorderedAccessViews(currentRenderTargetViewsActiveCount, uavs, uavInitialCounts); + nativeDeviceContext->CSSetUnorderedAccessViews((uint) currentRenderTargetViewsActiveCount, NumUAVs: (uint) remainingSlots, + ref uavs[0], uavInitialCounts); } /// - /// Sets an unordered access view to the shader pipeline. + /// Sets an Unordered Access View to the shader pipeline. /// - /// The stage. - /// The slot. - /// The unordered access view. - /// The Append/Consume buffer offset. A value of -1 indicates the current offset - /// should be kept. Any other values set the hidden counter for that Appendable/Consumable - /// UAV. uavInitialCount is only relevant for UAVs which have the 'Append' or 'Counter' buffer - /// flag, otherwise the argument is ignored. - /// Invalid stage.;stage + /// + /// The shader stage. Only valid options are and . + /// + /// The binding slot. + /// The Unordered Access View to set. + /// + /// The Append/Consume Buffer offset. + /// + /// A value of -1 indicates the current offset should be kept. + /// + /// Any other value sets the hidden counter for that Appendable/Consumable UAV. + /// flag, otherwise the argument is ignored. + /// + /// + /// This parameter is only relevant for UAVs which have the or + /// Buffer flags. + /// + /// + /// Invalid . Only valid options are and . + /// internal void SetUnorderedAccessView(ShaderStage stage, int slot, GraphicsResource unorderedAccessView, int uavInitialOffset) { - if (stage != ShaderStage.Compute && stage != ShaderStage.Pixel) - throw new ArgumentException("Invalid stage.", "stage"); + if (stage is not ShaderStage.Compute and not ShaderStage.Pixel) + throw new ArgumentException("Invalid shader stage", nameof(stage)); + + var nativeUnorderedAccessView = unorderedAccessView is not null ? unorderedAccessView.NativeUnorderedAccessView : default; - var view = unorderedAccessView?.NativeUnorderedAccessView; if (stage == ShaderStage.Compute) { - if (unorderedAccessViews[slot] != view) + if (unorderedAccessViews[slot].Handle != nativeUnorderedAccessView.Handle) { - unorderedAccessViews[slot] = view; - NativeDeviceContext.ComputeShader.SetUnorderedAccessView(slot, view, uavInitialOffset); + unorderedAccessViews[slot] = nativeUnorderedAccessView; + + nativeDeviceContext->CSSetUnorderedAccessViews((uint) slot, NumUAVs: 1, ref nativeUnorderedAccessView, (uint*) &uavInitialOffset); } } else { - if (currentUARenderTargetViews[slot] != view) + if (currentUARenderTargetViews[slot].Handle != nativeUnorderedAccessView.Handle) { - OMSetSingleUnorderedAccessView(slot, view, uavInitialOffset); + OMSetSingleUnorderedAccessView(slot, nativeUnorderedAccessView, uavInitialOffset); } } } /// - /// Unsets an unordered access view from the shader pipeline. + /// Unsets an Unordered Access View from the shader pipeline. /// - /// The unordered access view. + /// The Unordered Access View to unset. internal void UnsetUnorderedAccessView(GraphicsResource unorderedAccessView) { - var view = unorderedAccessView?.NativeUnorderedAccessView; - if (view == null) + var nativeUav = unorderedAccessView is not null ? unorderedAccessView.NativeUnorderedAccessView : default; + if (nativeUav.IsNull()) return; for (int slot = 0; slot < UnorderedAcccesViewCount; slot++) { - if (unorderedAccessViews[slot] == view) + if (unorderedAccessViews[slot].Handle == nativeUav.Handle) { - unorderedAccessViews[slot] = null; - NativeDeviceContext.ComputeShader.SetUnorderedAccessView(slot, null); + var nullUav = NullComPtr(); + unorderedAccessViews[slot] = nullUav; + NativeDeviceContext.CSSetUnorderedAccessViews((uint) slot, NumUAVs: 1, ppUnorderedAccessViews: ref nullUav, pUAVInitialCounts: null); } } for (int slot = 0; slot < SimultaneousRenderTargetCount; slot++) { - if (currentUARenderTargetViews[slot] == view) + if (currentUARenderTargetViews[slot].Handle == nativeUav.Handle) { - OMSetSingleUnorderedAccessView(slot, null, -1); + OMSetSingleUnorderedAccessView(slot, NullComPtr(), uavInitialOffset: -1); } } } /// - /// Prepares a draw call. This method is called before each Draw() method to setup the correct Primitive, InputLayout and VertexBuffers. + /// Prepares the Command List for a subsequent draw command. /// - /// Cannot GraphicsDevice.Draw*() without an effect being previously applied with Effect.Apply() method + /// + /// This method is called before each Draw() method to setup the correct Viewport. + /// private void PrepareDraw() { SetViewportImpl(); } + /// + /// Sets the reference value for Depth-Stencil tests. + /// + /// Reference value to perform against when doing a Depth-Stencil test. + /// public void SetStencilReference(int stencilReference) { - nativeDeviceContext.OutputMerger.DepthStencilReference = stencilReference; + SkipInit(out uint stencilRef); + + nativeDeviceContext->OMGetDepthStencilState(ppDepthStencilState: null, ref stencilRef); + nativeDeviceContext->OMSetDepthStencilState(pDepthStencilState: null, (uint) stencilReference); } + /// + /// Sets the blend factors for blending each of the RGBA components. + /// + /// + /// + /// A representing the blend factors for each RGBA component. + /// The blend factors modulate values for the pixel Shader, Render Target, or both. + /// + /// + /// If you have configured the Blend-State object with or , + /// the blending stage uses the blend factors specified by . + /// Otherwise, the blend factors will not be taken into account for the blend stage. + /// + /// + /// public void SetBlendFactor(Color4 blendFactor) { - nativeDeviceContext.OutputMerger.BlendFactor = ColorHelper.Convert(blendFactor); + var blendState = NullComPtr(); + + scoped Span blendFactorFloats = blendFactor.AsSpan(); + SkipInit(out uint sampleMask); + + nativeDeviceContext->OMGetBlendState(ppBlendState: null, BlendFactor: null, ref sampleMask); + nativeDeviceContext->OMSetBlendState(pBlendState: null, ref blendFactorFloats[0], sampleMask); } + /// + /// Sets the configuration of the graphics pipeline which, among other things, control the shaders, input layout, + /// render states, and output settings. + /// + /// The Pipeline State object to set. Specify to use the default one. + /// public void SetPipelineState(PipelineState pipelineState) { var newPipelineState = pipelineState ?? GraphicsDevice.DefaultPipelineState; - // Pipeline state if (newPipelineState != currentPipelineState) { newPipelineState.Apply(this, currentPipelineState); @@ -379,417 +593,853 @@ public void SetPipelineState(PipelineState pipelineState) } } + /// + /// Sets a Vertex Buffer for the input assembler stage of the pipeline. + /// + /// + /// The input slot for binding the Vertex Buffer; + /// The maximum number of input slots available depends on platform and graphics profile, usually 16 or 32. + /// + /// + /// The Vertex Buffer to set. It must have been created with . + /// + /// + /// The number of bytes between the first element of the Vertex Buffer and the first element that will be used. + /// + /// + /// The size (in bytes) of the elements that are to be used from the Vertex Buffer. + /// public void SetVertexBuffer(int index, Buffer buffer, int offset, int stride) { - inputAssembler.SetVertexBuffers(index, new SharpDX.Direct3D11.VertexBufferBinding(buffer.NativeBuffer, stride, offset)); + var nativeVertexBuffer = buffer?.NativeBuffer ?? default; + + nativeDeviceContext->IASetVertexBuffers((uint) index, NumBuffers: 1, ref nativeVertexBuffer, (uint*) &stride, (uint*) &offset); } + /// + /// Sets the Index Buffer for the input assembler stage of the pipeline. + /// + /// + /// The Index Buffer to set. It must have been created with . + /// + /// Offset (in bytes) from the start of the Index Buffer to the first index to use. + /// + /// A value indicating if the Index Buffer elements are 32-bit indices (), or 16-bit (). + /// public void SetIndexBuffer(Buffer buffer, int offset, bool is32bits) { - inputAssembler.SetIndexBuffer(buffer != null ? buffer.NativeBuffer : null, is32bits ? SharpDX.DXGI.Format.R32_UInt : SharpDX.DXGI.Format.R16_UInt, offset); + var indexFormat = is32bits ? Format.FormatR32Uint : Format.FormatR16Uint; + + var nativeIndexBuffer = buffer?.NativeBuffer ?? default; + + NativeDeviceContext.IASetIndexBuffer(nativeIndexBuffer, indexFormat, (uint) offset); } + /// + /// Inserts a barrier that transitions a Graphics Resource to a new state, ensuring proper synchronization + /// between different GPU operations accessing the resource. + /// + /// The Graphics Resource to transition to a different state. + /// The new state of . + /// + /// The Direct3D 11 implementation does not have synchronization barriers for Graphics Resource transitions. + /// public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) { // Nothing to do } + /// + /// Binds an array of Descriptor Sets at the specified index in the current pipeline's Root Signature, + /// making shader resources available for rendering operations. + /// + /// + /// The starting slot where the Descriptor Sets will be bound. This is not used in the Direct3D 11 implementation. + /// + /// + /// An array of Descriptor Sets containing resource bindings (such as Textures, Samplers, and Constant Buffers) + /// to be used by the currently active Pipeline State. + /// public void SetDescriptorSets(int index, DescriptorSet[] descriptorSets) { - // Bind resources currentPipelineState?.ResourceBinder.BindResources(this, descriptorSets); } - /// + /// + /// Dispatches a Compute Shader workload with the specified number of thread groups in each dimension. + /// + /// Number of thread groups in the X dimension. + /// Number of thread groups in the Y dimension. + /// Number of thread groups in the Z dimension. public void Dispatch(int threadCountX, int threadCountY, int threadCountZ) { - PrepareDraw(); + PrepareDraw(); // TODO: PrepareDraw for Compute dispatch? - NativeDeviceContext.Dispatch(threadCountX, threadCountY, threadCountZ); + nativeDeviceContext->Dispatch((uint) threadCountX, (uint) threadCountY, (uint) threadCountZ); } /// - /// Dispatches the specified indirect buffer. + /// Dispatches a Compute Shader workload using an Indirect Buffer, allowing the thread group count to be determined at runtime. /// - /// The indirect buffer. - /// The offset information bytes. + /// + /// A Buffer containing the dispatch parameters, structured as (threadCountX, threadCountY, threadCountZ). + /// + /// + /// The byte offset within the where the dispatch parameters are located. + /// + /// is . public void Dispatch(Buffer indirectBuffer, int offsetInBytes) { - PrepareDraw(); + ArgumentNullException.ThrowIfNull(indirectBuffer); + + PrepareDraw(); // TODO: PrepareDraw for Compute dispatch? - if (indirectBuffer == null) throw new ArgumentNullException("indirectBuffer"); - NativeDeviceContext.DispatchIndirect(indirectBuffer.NativeBuffer, offsetInBytes); + nativeDeviceContext->DispatchIndirect(indirectBuffer.NativeBuffer, (uint) offsetInBytes); } /// - /// Draw non-indexed, non-instanced primitives. + /// Issues a non-indexed draw call, rendering a sequence of vertices directly from the bound Vertex Buffer. /// - /// Number of vertices to draw. - /// Index of the first vertex, which is usually an offset in a vertex buffer; it could also be used as the first vertex id generated for a shader parameter marked with the SV_TargetId system-value semantic. + /// The number of vertices to draw. + /// + /// Index of the first vertex in the Vertex Buffer to begin rendering from; + /// it could also be used as the first vertex id generated for a shader parameter marked with the SV_TargetId system-value semantic. + /// public void Draw(int vertexCount, int startVertexLocation = 0) { PrepareDraw(); - NativeDeviceContext.Draw(vertexCount, startVertexLocation); + nativeDeviceContext->Draw((uint) vertexCount, (uint) startVertexLocation); - GraphicsDevice.FrameTriangleCount += (uint)vertexCount; + GraphicsDevice.FrameTriangleCount += (uint) vertexCount; GraphicsDevice.FrameDrawCalls++; } /// - /// Draw geometry of an unknown size. + /// Issues a draw call for geometry of unknown size, typically used with Vertex or Index Buffers populated via Stream Output. + /// The vertex count is inferred from the data written by the GPU. /// public void DrawAuto() { PrepareDraw(); - NativeDeviceContext.DrawAuto(); + nativeDeviceContext->DrawAuto(); GraphicsDevice.FrameDrawCalls++; } /// - /// Draw indexed, non-instanced primitives. + /// Issues an indexed non-instanced draw call using the currently bound Index Buffer. /// /// Number of indices to draw. - /// The location of the first index read by the GPU from the index buffer. - /// A value added to each index before reading a vertex from the vertex buffer. + /// The location of the first index read by the GPU from the Index Buffer. + /// A value added to each index before reading a vertex from the Vertex Buffer. public void DrawIndexed(int indexCount, int startIndexLocation = 0, int baseVertexLocation = 0) { PrepareDraw(); - NativeDeviceContext.DrawIndexed(indexCount, startIndexLocation, baseVertexLocation); + nativeDeviceContext->DrawIndexed((uint) indexCount, (uint) startIndexLocation, baseVertexLocation); GraphicsDevice.FrameDrawCalls++; - GraphicsDevice.FrameTriangleCount += (uint)indexCount; + GraphicsDevice.FrameTriangleCount += (uint) indexCount; } /// - /// Draw indexed, instanced primitives. + /// Issues an indexed instanced draw call, rendering multiple instances of indexed geometry. /// - /// Number of indices read from the index buffer for each instance. + /// Number of indices read from the Index Buffer for each instance. /// Number of instances to draw. - /// The location of the first index read by the GPU from the index buffer. - /// A value added to each index before reading a vertex from the vertex buffer. - /// A value added to each index before reading per-instance data from a vertex buffer. + /// The location of the first index read by the GPU from the Index Buffer. + /// A value added to each index before reading a vertex from the Vertex Buffer. + /// A value added to each index before reading per-instance data from a Vertex Buffer. public void DrawIndexedInstanced(int indexCountPerInstance, int instanceCount, int startIndexLocation = 0, int baseVertexLocation = 0, int startInstanceLocation = 0) { PrepareDraw(); - NativeDeviceContext.DrawIndexedInstanced(indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation); + nativeDeviceContext->DrawIndexedInstanced((uint) indexCountPerInstance, (uint) instanceCount, (uint) startIndexLocation, baseVertexLocation, (uint) startInstanceLocation); GraphicsDevice.FrameDrawCalls++; - GraphicsDevice.FrameTriangleCount += (uint)(indexCountPerInstance * instanceCount); + GraphicsDevice.FrameTriangleCount += (uint) (indexCountPerInstance * instanceCount); } /// - /// Draw indexed, instanced, GPU-generated primitives. + /// Issues an indexed instanced draw call using an Indirect Arguments Buffer, allowing parameters to be determined at runtime. /// - /// A buffer containing the GPU generated primitives. - /// Offset in pBufferForArgs to the start of the GPU generated primitives. + /// A Buffer containing the draw parameters (index count, instance count, etc.). + /// + /// Byte offset within the where the draw arguments are located. + /// + /// is . public void DrawIndexedInstanced(Buffer argumentsBuffer, int alignedByteOffsetForArgs = 0) { - if (argumentsBuffer == null) throw new ArgumentNullException("argumentsBuffer"); + ArgumentNullException.ThrowIfNull(argumentsBuffer); PrepareDraw(); - NativeDeviceContext.DrawIndexedInstancedIndirect(argumentsBuffer.NativeBuffer, alignedByteOffsetForArgs); + nativeDeviceContext->DrawIndexedInstancedIndirect(argumentsBuffer.NativeBuffer, (uint) alignedByteOffsetForArgs); GraphicsDevice.FrameDrawCalls++; } /// - /// Draw non-indexed, instanced primitives. + /// Issues a non-indexed instanced draw call, rendering multiple instances of geometry using a Vertex Buffer. /// - /// Number of vertices to draw. - /// Number of instances to draw. - /// Index of the first vertex. - /// A value added to each index before reading per-instance data from a vertex buffer. + /// The number of vertices to draw per instance. + /// The number of instances to draw. + /// The index of the first vertex in the Vertex Buffer. + /// A value added to each index before reading per-instance data from a Vertex Buffer. public void DrawInstanced(int vertexCountPerInstance, int instanceCount, int startVertexLocation = 0, int startInstanceLocation = 0) { PrepareDraw(); - NativeDeviceContext.DrawInstanced(vertexCountPerInstance, instanceCount, startVertexLocation, startInstanceLocation); + nativeDeviceContext->DrawInstanced((uint) vertexCountPerInstance, (uint) instanceCount, (uint) startVertexLocation, (uint) startInstanceLocation); GraphicsDevice.FrameDrawCalls++; - GraphicsDevice.FrameTriangleCount += (uint)(vertexCountPerInstance * instanceCount); + GraphicsDevice.FrameTriangleCount += (uint) (vertexCountPerInstance * instanceCount); } /// - /// Draw instanced, GPU-generated primitives. + /// Issues a non-indexed instanced draw call using an Indirect Arguments Buffer, allowing parameters to be determined at runtime. /// - /// An arguments buffer - /// Offset in pBufferForArgs to the start of the GPU generated primitives. + /// A Buffer containing the draw parameters (vertex count, instance count, etc.). + /// + /// Byte offset within the where the draw arguments are located. + /// + /// is . public void DrawInstanced(Buffer argumentsBuffer, int alignedByteOffsetForArgs = 0) { - if (argumentsBuffer == null) throw new ArgumentNullException("argumentsBuffer"); + ArgumentNullException.ThrowIfNull(argumentsBuffer); PrepareDraw(); - NativeDeviceContext.DrawInstancedIndirect(argumentsBuffer.NativeBuffer, alignedByteOffsetForArgs); + nativeDeviceContext->DrawInstancedIndirect(argumentsBuffer.NativeBuffer, (uint) alignedByteOffsetForArgs); GraphicsDevice.FrameDrawCalls++; } /// - /// Submits a GPU timestamp query. + /// Submits a GPU timestamp Query. /// - /// The owning the query. - /// The query index. + /// The owning the Query. + /// The index of the Query to write. public void WriteTimestamp(QueryPool queryPool, int index) { - nativeDeviceContext.End(queryPool.NativeQueries[index]); + var query = queryPool.NativeQueries[index]; + + nativeDeviceContext->End(query); } /// - /// Begins debug event. + /// Marks the beginning of a profile section. /// - /// Color of the profile. - /// The name. + /// A color that a profiling tool can use to display the event. + /// A descriptive name for the profile section. + /// + /// + /// Each call to must be matched with a corresponding call to , + /// which defines a region of code that can be identified with a name and possibly a color in a compatible + /// profiling tool. + /// + /// + /// A pair of calls to and can be nested inside a call + /// to and in an upper level in the call stack. + /// This allows to form a hierarchy of profile sections. + /// + /// public void BeginProfile(Color4 profileColor, string name) { - nativeDeviceProfiler?.BeginEvent(name); + nativeDeviceProfiler->BeginEvent(name); } /// - /// Ends debug event. + /// Marks the end of a profile section previously started by a call to . /// + /// public void EndProfile() { - nativeDeviceProfiler?.EndEvent(); + nativeDeviceProfiler->EndEvent(); } /// - /// Clears the specified depth stencil buffer. See to learn how to use it. + /// Clears the specified Depth-Stencil Buffer. /// - /// The depth stencil buffer. - /// The options. - /// The depth. - /// The stencil. - /// + /// The Depth-Stencil Buffer to clear. + /// + /// A combination of flags identifying what parts of the Depth-Stencil Buffer to clear. + /// + /// The depth value to use for clearing the Depth Buffer. + /// The stencil value to use for clearing the Stencil Buffer. + /// is . + /// Cannot clear a Stencil Buffer without a Stencil Buffer format. public void Clear(Texture depthStencilBuffer, DepthStencilClearOptions options, float depth = 1, byte stencil = 0) { - if (depthStencilBuffer == null) throw new ArgumentNullException("depthStencilBuffer"); + ArgumentNullException.ThrowIfNull(depthStencilBuffer); - var flags = ((options & DepthStencilClearOptions.DepthBuffer) != 0) ? SharpDX.Direct3D11.DepthStencilClearFlags.Depth : 0; + var flags = options.HasFlag(DepthStencilClearOptions.DepthBuffer) ? ClearFlag.Depth : 0; - // Check that the DepthStencilBuffer has a Stencil if Clear Stencil is requested - if ((options & DepthStencilClearOptions.Stencil) != 0) + // Check that the Depth-Stencil Buffer has a Stencil if Clear Stencil is requested + if (options.HasFlag(DepthStencilClearOptions.Stencil)) { if (!depthStencilBuffer.HasStencil) throw new InvalidOperationException(string.Format(FrameworkResources.NoStencilBufferForDepthFormat, depthStencilBuffer.ViewFormat)); - flags |= SharpDX.Direct3D11.DepthStencilClearFlags.Stencil; + + flags |= ClearFlag.Stencil; } - NativeDeviceContext.ClearDepthStencilView(depthStencilBuffer.NativeDepthStencilView, flags, depth, stencil); + nativeDeviceContext->ClearDepthStencilView(depthStencilBuffer.NativeDepthStencilView, (uint) flags, depth, stencil); } /// - /// Clears the specified render target. See to learn how to use it. + /// Clears the specified Render Target. /// - /// The render target. - /// The color. - /// renderTarget - public unsafe void Clear(Texture renderTarget, Color4 color) + /// The Render Target to clear. + /// The color to use to clear the Render Target. + /// is . + public void Clear(Texture renderTarget, Color4 color) { - if (renderTarget == null) throw new ArgumentNullException("renderTarget"); + ArgumentNullException.ThrowIfNull(renderTarget); - NativeDeviceContext.ClearRenderTargetView(renderTarget.NativeRenderTargetView, *(RawColor4*)&color); + nativeDeviceContext->ClearRenderTargetView(renderTarget.NativeRenderTargetView, (float*) &color); } /// - /// Clears a read-write Buffer. This buffer must have been created with read-write/unordered access. + /// Clears a Read-Write Buffer. /// - /// The buffer. - /// The value. - /// buffer - /// Expecting buffer supporting UAV;buffer + /// The Buffer to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Buffer. + /// is . + /// must support Unordered Access. public unsafe void ClearReadWrite(Buffer buffer, Vector4 value) { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (buffer.NativeUnorderedAccessView == null) throw new ArgumentException("Expecting buffer supporting UAV", nameof(buffer)); + ArgumentNullException.ThrowIfNull(buffer); - NativeDeviceContext.ClearUnorderedAccessView(buffer.NativeUnorderedAccessView, *(RawVector4*)&value); + if (buffer.NativeUnorderedAccessView.IsNull()) + throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); + + nativeDeviceContext->ClearUnorderedAccessViewFloat(buffer.NativeUnorderedAccessView, (float*) &value); } /// - /// Clears a read-write Buffer. This buffer must have been created with read-write/unordered access. + /// Clears a Read-Write Buffer. /// - /// The buffer. - /// The value. - /// buffer - /// Expecting buffer supporting UAV;buffer + /// The Buffer to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Buffer. + /// is . + /// must support Unordered Access. public unsafe void ClearReadWrite(Buffer buffer, Int4 value) { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (buffer.NativeUnorderedAccessView == null) throw new ArgumentException("Expecting buffer supporting UAV", nameof(buffer)); + ArgumentNullException.ThrowIfNull(buffer); + + if (buffer.NativeUnorderedAccessView.IsNull()) + throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); - NativeDeviceContext.ClearUnorderedAccessView(buffer.NativeUnorderedAccessView, *(RawInt4*)&value); + nativeDeviceContext->ClearUnorderedAccessViewUint(buffer.NativeUnorderedAccessView, (uint*) &value); } /// - /// Clears a read-write Buffer. This buffer must have been created with read-write/unordered access. + /// Clears a Read-Write Buffer. /// - /// The buffer. - /// The value. - /// buffer - /// Expecting buffer supporting UAV;buffer + /// The Buffer to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Buffer. + /// is . + /// must support Unordered Access. public unsafe void ClearReadWrite(Buffer buffer, UInt4 value) { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (buffer.NativeUnorderedAccessView == null) throw new ArgumentException("Expecting buffer supporting UAV", nameof(buffer)); + ArgumentNullException.ThrowIfNull(buffer); - NativeDeviceContext.ClearUnorderedAccessView(buffer.NativeUnorderedAccessView, *(RawInt4*)&value); + if (buffer.NativeUnorderedAccessView.IsNull()) + throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); + + nativeDeviceContext->ClearUnorderedAccessViewUint(buffer.NativeUnorderedAccessView, (uint*) &value); } /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. + /// Clears a Read-Write Texture. /// - /// The texture. - /// The value. - /// texture - /// Expecting texture supporting UAV;texture + /// The Texture to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Texture. + /// is . + /// must support Unordered Access. public unsafe void ClearReadWrite(Texture texture, Vector4 value) { - if (texture == null) throw new ArgumentNullException(nameof(texture)); - if (texture.NativeUnorderedAccessView == null) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ArgumentNullException.ThrowIfNull(texture); + + if (texture.NativeUnorderedAccessView.IsNull()) + throw new ArgumentException("Expecting a Texture supporting UAV", nameof(texture)); - NativeDeviceContext.ClearUnorderedAccessView(texture.NativeUnorderedAccessView, *(RawVector4*)&value); + nativeDeviceContext->ClearUnorderedAccessViewFloat(texture.NativeUnorderedAccessView, (float*) &value); } /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. + /// Clears a Read-Write Texture. /// - /// The texture. - /// The value. - /// texture - /// Expecting texture supporting UAV;texture + /// The Texture to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Texture. + /// is . + /// must support Unordered Access. public unsafe void ClearReadWrite(Texture texture, Int4 value) { - if (texture == null) throw new ArgumentNullException(nameof(texture)); - if (texture.NativeUnorderedAccessView == null) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ArgumentNullException.ThrowIfNull(texture); + + if (texture.NativeUnorderedAccessView.IsNull()) + throw new ArgumentException("Expecting a Texture supporting UAV", nameof(texture)); - NativeDeviceContext.ClearUnorderedAccessView(texture.NativeUnorderedAccessView, *(RawInt4*)&value); + nativeDeviceContext->ClearUnorderedAccessViewUint(texture.NativeUnorderedAccessView, (uint*) &value); } /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. + /// Clears a Read-Write Texture. /// - /// The texture. - /// The value. - /// texture - /// Expecting texture supporting UAV;texture + /// The Texture to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Texture. + /// is . + /// must support Unordered Access. public unsafe void ClearReadWrite(Texture texture, UInt4 value) { - if (texture == null) throw new ArgumentNullException(nameof(texture)); - if (texture.NativeUnorderedAccessView == null) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ArgumentNullException.ThrowIfNull(texture); - NativeDeviceContext.ClearUnorderedAccessView(texture.NativeUnorderedAccessView, *(RawInt4*)&value); + if (texture.NativeUnorderedAccessView.IsNull()) + throw new ArgumentException("Expecting a Texture supporting UAV", nameof(texture)); + + nativeDeviceContext->ClearUnorderedAccessViewUint(texture.NativeUnorderedAccessView, (uint*) &value); } /// - /// Copy a texture. View is ignored and full underlying texture is copied. + /// Copies the data from a Graphics Resource to another. + /// Views are ignored and the full underlying data is copied. /// - /// The source texture. - /// The destination texture. + /// The source Graphics Resource. + /// The destination Graphics Resource. + /// is . + /// is . public void Copy(GraphicsResource source, GraphicsResource destination) { - if (source == null) throw new ArgumentNullException("source"); - if (destination == null) throw new ArgumentNullException("destination"); - NativeDeviceContext.CopyResource(source.NativeResource, destination.NativeResource); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(destination); + + nativeDeviceContext->CopyResource(destination.NativeResource, source.NativeResource); } - public void CopyMultisample(Texture sourceMultisampleTexture, int sourceSubResource, Texture destTexture, int destSubResource, PixelFormat format = PixelFormat.None) + /// + /// Copies the data from a multi-sampled Texture (which is resolved) to another Texture. + /// + /// The source multi-sampled Texture. + /// The sub-resource index of the source Texture. + /// The destination Texture. + /// The sub-resource index of the destination Texture. + /// + /// A that indicates how the multi-sampled Texture will be resolved to a single-sampled resource. + /// + /// is not a multi-sampled Texture. + /// is . + /// is . + /// + /// The and must have the same dimensions. + /// In addition, they must have compatible formats. There are three scenarios for this: + /// + /// + /// Scenario + /// Requirements + /// + /// + /// Source and destination are prestructured and typed + /// + /// Both the source and destination must have identical formats and that format must be specified in the parameter. + /// + /// + /// + /// One resource is prestructured and typed and the other is prestructured and typeless + /// + /// The typed resource must have a format that is compatible with the typeless resource (i.e. the typed resource is + /// and the typeless resource is ). + /// The format of the typed resource must be specified in the parameter. + /// + /// + /// + /// Source and destination are prestructured and typeless + /// + /// + /// Both the source and destination must have the same typeless format (i.e. both must have ), and the + /// parameter must specify a format that is compatible with the source and destination + /// (i.e. if both are then could be specified in the + /// parameter). + /// + /// + /// For example, given the format: + /// + /// The source (or destination) format could be . + /// The destination (or source) format could be . + /// + /// + /// + /// + /// + /// + public void CopyMultiSampled(Texture sourceMultiSampledTexture, int sourceSubResourceIndex, + Texture destinationTexture, int destinationSubResourceIndex, + PixelFormat format = PixelFormat.None) { - if (sourceMultisampleTexture == null) throw new ArgumentNullException(nameof(sourceMultisampleTexture)); - if (destTexture == null) throw new ArgumentNullException("destTexture"); - if (!sourceMultisampleTexture.IsMultisample) throw new ArgumentOutOfRangeException(nameof(sourceMultisampleTexture), "Source texture is not a MSAA texture"); + ArgumentNullException.ThrowIfNull(sourceMultiSampledTexture); + ArgumentNullException.ThrowIfNull(destinationTexture); + + if (!sourceMultiSampledTexture.IsMultiSampled) + throw new ArgumentException("Source Texture is not a MSAA Texture", nameof(sourceMultiSampledTexture)); - NativeDeviceContext.ResolveSubresource(sourceMultisampleTexture.NativeResource, sourceSubResource, destTexture.NativeResource, destSubResource, (SharpDX.DXGI.Format)(format == PixelFormat.None ? destTexture.Format : format)); + nativeDeviceContext->ResolveSubresource(destinationTexture.NativeResource, (uint) destinationSubResourceIndex, + sourceMultiSampledTexture.NativeResource, (uint) sourceSubResourceIndex, + (Format)(format == PixelFormat.None ? destinationTexture.Format : format)); } - public void CopyRegion(GraphicsResource source, int sourceSubresource, ResourceRegion? sourecRegion, GraphicsResource destination, int destinationSubResource, int dstX = 0, int dstY = 0, int dstZ = 0) + /// + /// Copies a region from a source Graphics Resource to a destination Graphics Resource. + /// + /// The source Graphics Resource to copy from. + /// The index of the sub-resource of to copy from. + /// + /// + /// An optional that defines the source sub-resource to copy from. + /// Specify the entire source sub-resource is copied. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// The destination Graphics Resource to copy to. + /// The index of the sub-resource of to copy to. + /// The X-coordinate of the upper left corner of the destination region. + /// The Y-coordinate of the upper left corner of the destination region. For a 1D sub-resource, this must be zero. + /// The Z-coordinate of the upper left corner of the destination region. For a 1D or 2D sub-resource, this must be zero. + /// is . + /// is . + /// + /// + /// The must be within the size of the source resource. + /// The destination offsets, (, , and ), + /// allow the source region to be offset when writing into the destination resource; + /// however, the dimensions of the source region and the offsets must be within the size of the resource. + /// + /// + /// If the resources are Buffers, all coordinates are in bytes; if the resources are Textures, all coordinates are in texels. + /// + /// + /// performs the copy on the GPU (similar to a memcpy by the CPU). As a consequence, + /// the source and destination resources: + /// + /// Must be different sub-resources (although they can be from the same Graphics Resource). + /// Must be the same type. + /// + /// Must have compatible formats (identical or from the same type group). For example, a Texture + /// can be copied to a Texture since both of these formats are in the + /// group. + /// + /// May not be currently mapped with . + /// + /// + /// + /// only supports copy; it doesn't support any stretch, color key, or blend. + /// + /// + public void CopyRegion(GraphicsResource source, int sourceSubResourceIndex, ResourceRegion? sourceRegion, + GraphicsResource destination, int destinationSubResourceIndex, int dstX = 0, int dstY = 0, int dstZ = 0) { - if (source == null) throw new ArgumentNullException("source"); - if (destination == null) throw new ArgumentNullException("destination"); + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(destination); - var nullableSharpDxRegion = new SharpDX.Direct3D11.ResourceRegion?(); + if (sourceRegion is ResourceRegion srcResourceRegion) + { + // NOTE: We assume the same layout and size as D3D11_BOX + Debug.Assert(sizeof(D3D11Box) == sizeof(ResourceRegion)); + var sourceBox = srcResourceRegion.BitCast(); - if (sourecRegion.HasValue) + nativeDeviceContext->CopySubresourceRegion(destination.NativeResource, (uint) destinationSubResourceIndex, (uint) dstX, (uint) dstY, (uint) dstZ, + source.NativeResource, (uint) sourceSubResourceIndex, in sourceBox); + } + else { - var value = sourecRegion.Value; - nullableSharpDxRegion = new SharpDX.Direct3D11.ResourceRegion(value.Left, value.Top, value.Front, value.Right, value.Bottom, value.Back); + nativeDeviceContext->CopySubresourceRegion(destination.NativeResource, (uint) destinationSubResourceIndex, (uint) dstX, (uint) dstY, (uint) dstZ, + source.NativeResource, (uint) sourceSubResourceIndex, pSrcBox: null); } + } - NativeDeviceContext.CopySubresourceRegion(source.NativeResource, sourceSubresource, nullableSharpDxRegion, destination.NativeResource, destinationSubResource, dstX, dstY, dstZ); + /// + /// Copies data from a Buffer holding variable length data to another Buffer. + /// + /// + /// A Structured Buffer created with either or . + /// These types of resources have hidden counters tracking "how many" records have been written. + /// + /// + /// A Buffer resource to copy the data to. Any Buffer that other copy commands, such as or , are able to write to. + /// + /// + /// The offset in bytes from the start of to write 32-bit + /// structure (vertex) count from . + /// The offset must be aligned to 4 bytes. + /// + /// is . + /// is . + public void CopyCount(Buffer sourceBuffer, Buffer destinationBuffer, int destinationOffsetInBytes) + { + ArgumentNullException.ThrowIfNull(sourceBuffer); + ArgumentNullException.ThrowIfNull(destinationBuffer); + + nativeDeviceContext->CopyStructureCount(destinationBuffer.NativeBuffer, (uint) destinationOffsetInBytes, sourceBuffer.NativeUnorderedAccessView); } - /// - public void CopyCount(Buffer sourceBuffer, Buffer destBuffer, int offsetInBytes) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// is . + /// + /// + /// If is a Constant Buffer, it must be updated in full. + /// It is not possible to use this method to partially update a Constant Buffer. + /// + /// + /// A Graphics Resource cannot be used as a destination if: + /// + /// The resource was created with or . + /// The resource was created as a Depth-Stencil Buffer. + /// The resource is a Texture created with multi-sampling capability (see ). + /// + /// + /// + /// When returns, the application is free to change or even free the data pointed to by + /// because the method has already copied/snapped away the original contents. + /// + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData) { - if (sourceBuffer == null) throw new ArgumentNullException("sourceBuffer"); - if (destBuffer == null) throw new ArgumentNullException("destBuffer"); - NativeDeviceContext.CopyStructureCount(destBuffer.NativeBuffer, offsetInBytes, sourceBuffer.NativeUnorderedAccessView); + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + nativeDeviceContext->UpdateSubresource(resource.NativeResource, (uint)subResourceIndex, pDstBox: null, + sourceData.GetPointer(), SrcRowPitch: 0, SrcDepthPitch: 0); } - internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// is . + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData) { - if (resource == null) throw new ArgumentNullException("resource"); - NativeDeviceContext.UpdateSubresource(databox.AsSharpDX(), resource.NativeResource, subResourceIndex); + ArgumentNullException.ThrowIfNull(resource); + + nativeDeviceContext->UpdateSubresource(resource.NativeResource, (uint) subResourceIndex, pDstBox: null, + pSrcData: (void*) sourceData.DataPointer, (uint) sourceData.RowPitch, (uint) sourceData.SlicePitch); } - internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData, ResourceRegion region) { - if (resource == null) throw new ArgumentNullException("resource"); - NativeDeviceContext.UpdateSubresource(databox.AsSharpDX(), resource.NativeResource, subResourceIndex, region.AsSharpDX()); + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + ref Box destBox = ref region.As(); + + nativeDeviceContext->UpdateSubresource(resource.NativeResource, (uint) subResourceIndex, in destBox, + sourceData.GetPointer(), SrcRowPitch: 0, SrcDepthPitch: 0); + } + + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// + internal unsafe partial void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData, ResourceRegion region) + { + ArgumentNullException.ThrowIfNull(resource); + + ref Box destBox = ref region.As(); + + nativeDeviceContext->UpdateSubresource(resource.NativeResource, (uint) subResourceIndex, in destBox, + pSrcData: (void*) sourceData.DataPointer, (uint) sourceData.RowPitch, (uint) sourceData.SlicePitch); } // TODO GRAPHICS REFACTOR what should we do with this? /// - /// Maps a subresource. + /// Maps a sub-resource of a Graphics Resource to be accessible from CPU memory, and in the process denies the GPU access to that sub-resource. /// - /// The resource. - /// Index of the sub resource. - /// The map mode. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// The offset information in bytes. - /// The length information in bytes. - /// Pointer to the sub resource to map. - public unsafe MappedResource MapSubresource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) + /// The Graphics Resource to map to CPU memory. + /// The index of the sub-resource to get access to. + /// A value of indicating the way the Graphics Resource should be mapped to CPU memory. + /// + /// A value indicating if this method will return immediately if the Graphics Resource is still being used by the GPU for writing + /// . The default value is , which means the method will wait until the GPU is done. + /// + /// + /// The offset in bytes from the beginning of the mapped memory of the sub-resource. + /// Defaults to 0, which means it is mapped from the beginning. + /// + /// + /// The length in bytes of the memory to map from the sub-resource. + /// Defaults to 0, which means the entire sub-resource is mapped. + /// + /// A structure pointing to the GPU resource mapped for CPU access. + /// is . + /// + /// For s: + /// + /// Usage Instructions: + /// + /// + /// Ensure the was created with the correct usage. + /// For example, you should specify if you plan to update its contents frequently. + /// + /// This method can be called multiple times, and nested calls are supported. + /// + /// Use appropriate values when calling . + /// For example, indicates that the old data in the Buffer can be discarded. + /// + /// + /// + /// + /// Restrictions: + /// + /// + /// The returned by is not guaranteed to be consistent across different calls. + /// Applications should not rely on the address being the same unless is persistently nested. + /// + /// may invalidate the CPU cache to ensure that CPU reads reflect any modifications made by the GPU. + /// If your graphics API supports them, use fences for synchronization to ensure proper coordination between the CPU and GPU. + /// Ensure that the Buffer data is properly aligned to meet the requirements of your graphics API. + /// + /// Stick to simple usage models (e.g., for upload, , + /// for readback) unless advanced models are necessary for your application. + /// + /// + /// + /// + /// For s: + /// + /// Usage Instructions: + /// + /// + /// Ensure to use the correct data format when writing data to the Texture. + /// + /// Textures can have multiple mipmap levels. You must specify which level you want to map with . + /// + /// Use appropriate values when calling . + /// For example, is usually used to update dynamic Textures. + /// + /// + /// + /// + /// Restrictions: + /// + /// + /// Not all s are compatible with mapping operations. + /// + /// Concurrent access to a Texture both from the CPU and the GPU may not be allowed and might require careful synchronization. + /// Ensure that the Texture data is properly aligned to meet the requirements of your graphics API and the . + /// + /// + /// + /// For State Objects (like , , etc): + /// + /// Restrictions: + /// + /// + /// State Objects are not usually mapped nor directly updated. They are created with specific configurations and are treated + /// as immutable from now on. Instead, if you need changes, you can create a new State Object with the updated settings. + /// + /// + /// + /// + /// After updating the , call to release the CPU pointer and allow the GPU to access the updated data. + /// + public unsafe partial MappedResource MapSubResource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) { - if (resource == null) throw new ArgumentNullException("resource"); + // TODO: D3D 11 does not support lengthInBytes, we should throw an exception if it is not 0? + // TODO: Also, even if not used, as we are returning it in MappedResource, shouldn't we compute it the same as in D3D 12? - // This resource has just been recycled by the GraphicsResourceAllocator, we force a rename to avoid GPU=>GPU sync point + ArgumentNullException.ThrowIfNull(resource); + + // This resource has just been recycled by the GraphicsResourceAllocator, we force a rename to avoid GPU => GPU sync point if (resource.DiscardNextMap && mapMode == MapMode.WriteNoOverwrite) mapMode = MapMode.WriteDiscard; - SharpDX.DataBox dataBox = NativeDeviceContext.MapSubresource(resource.NativeResource, subResourceIndex, (SharpDX.Direct3D11.MapMode)mapMode, doNotWait ? SharpDX.Direct3D11.MapFlags.DoNotWait : SharpDX.Direct3D11.MapFlags.None); - ref var databox = ref dataBox.AsStride(); - if (!dataBox.IsEmpty) + var mapType = (Map) mapMode; + var mapFlags = (uint) (doNotWait ? MapFlag.DONotWait : 0); + + MappedResource mappedResource = new(resource, subResourceIndex, dataBox: default); + ref var mappedSubresource = ref UnsafeUtilities.AsRef(in mappedResource.DataBox); + + HResult result = nativeDeviceContext->Map(resource.NativeResource, (uint) subResourceIndex, mapType, mapFlags, ref mappedSubresource); + + if (doNotWait && result == DxgiConstants.ErrorWasStillDrawing) + return default; + + if (result.IsFailure) + result.Throw(); + + if (!mappedResource.DataBox.IsEmpty) { - databox.DataPointer = (IntPtr)((byte*)databox.DataPointer + offsetInBytes); + mappedSubresource.PData = (byte*) mappedSubresource.PData + offsetInBytes; } - return new MappedResource(resource, subResourceIndex, databox); + return mappedResource; } // TODO GRAPHICS REFACTOR what should we do with this? - public void UnmapSubresource(MappedResource unmapped) - { - NativeDeviceContext.UnmapSubresource(unmapped.Resource.NativeResource, unmapped.SubResourceIndex); - } - - private void InitializeStages() + /// + /// Unmaps a sub-resource of a Graphics Resource, which was previously mapped to CPU memory with , + /// and in the process re-enables the GPU access to that sub-resource. + /// + /// + /// A structure identifying the sub-resource to unmap. + /// + public unsafe partial void UnmapSubResource(MappedResource mappedResource) { - inputAssembler = nativeDeviceContext.InputAssembler; - outputMerger = nativeDeviceContext.OutputMerger; - shaderStages[(int)ShaderStage.Vertex - 1] = nativeDeviceContext.VertexShader; - shaderStages[(int)ShaderStage.Hull - 1] = nativeDeviceContext.HullShader; - shaderStages[(int)ShaderStage.Domain - 1] = nativeDeviceContext.DomainShader; - shaderStages[(int)ShaderStage.Geometry - 1] = nativeDeviceContext.GeometryShader; - shaderStages[(int)ShaderStage.Pixel - 1] = nativeDeviceContext.PixelShader; - shaderStages[(int)ShaderStage.Compute - 1] = nativeDeviceContext.ComputeShader; + nativeDeviceContext->Unmap(mappedResource.Resource.NativeResource, (uint) mappedResource.SubResourceIndex); } } } - -#endif + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/DebugHelpers.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/DebugHelpers.Direct3D.cs new file mode 100644 index 0000000000..ea727ad7db --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D/DebugHelpers.Direct3D.cs @@ -0,0 +1,110 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.Direct3D12; +using Silk.NET.DXGI; +using Stride.Core.UnsafeExtensions; + +using static Stride.Graphics.ComPtrHelpers; + +namespace Stride.Graphics; + +internal static unsafe class DebugHelpers +{ + /// + /// A flag indicating whether to log debug names for Direct3D objects whenever they are set. + /// + public const bool LogDebugNames = true; + + + // From d3dcommon.h in Windows SDK (WKPDID_D3DDebugObjectName) + public static Guid* DebugObjectName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ReadOnlySpan data = [ + 0x22, 0x8C, 0x9B, 0x42, + 0x88, 0x91, + 0x0C, 0x4B, + 0x87, + 0x42, + 0xAC, + 0xB0, + 0xBF, + 0x85, + 0xC2, + 0x00 + ]; + + Debug.Assert(data.Length == sizeof(Guid)); + + return (Guid*) Unsafe.AsPointer(ref MemoryMarshal.GetReference(data)); + } + } + + + /// + /// Associates a debug name to the private data of a DXGI or Direct3D object, useful to see a friendly name + /// in some graphics debuggers. + /// + /// A COM pointer to the object to set the debug name for. + /// The name to associate with the object. + /// Thrown when the specified COM pointer type is not supported. + public static void SetDebugName(this ComPtr comPtr, string name) + where T : unmanaged, IComVtbl + { + var comPtrVtbl = *comPtr.Handle; + + switch (comPtrVtbl) + { + case IComVtbl: + { + var nameSpan = name.GetAsciiSpan(); + var nameSpanLength = (uint) nameSpan.Length; + + var dxgiObject = CastComPtr(comPtr); + dxgiObject.SetPrivateData(DebugObjectName, nameSpanLength, nameSpan); + break; + } +#if STRIDE_GRAPHICS_API_DIRECT3D11 + case IComVtbl: + { + var nameSpan = name.GetAsciiSpan(); + var nameSpanLength = (uint) nameSpan.Length; + + var d3d11DeviceChild = CastComPtr(comPtr); + d3d11DeviceChild.SetPrivateData(DebugObjectName, nameSpanLength, nameSpan); + break; + } +#elif STRIDE_GRAPHICS_API_DIRECT3D12 + case IComVtbl: + { + var d3d12DeviceChild = CastComPtr(comPtr); + d3d12DeviceChild.SetName(name); + break; + } +#endif + default: + throw new NotSupportedException("The specified COM pointer type is not supported."); + } + + if (LogDebugNames) + { + // Log the debug name for the object + var typeName = typeof(T).Name; + var ptr = (nint) comPtr.Handle; + Debug.WriteLine($"Changed or set the debug name for {typeName} at 0x{ptr:X8} to '{name}'."); + } + } +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/DisplayMode.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/DisplayMode.Direct3D.cs index afc8922d8c..5654b3fa4d 100644 --- a/sources/engine/Stride.Graphics/Direct3D/DisplayMode.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/DisplayMode.Direct3D.cs @@ -1,22 +1,52 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D -using SharpDX.DXGI; +using Silk.NET.DXGI; namespace Stride.Graphics { - public partial class DisplayMode + public partial record struct DisplayMode { - internal ModeDescription ToDescription() + /// + /// Gets a DXGI from the display mode. + /// + /// Returns a . + internal ModeDesc ToDescription() + { + return new ModeDesc((uint) Width, (uint) Height, RefreshRate.ToSilk(), format: (Format) Format); + } + + /// + /// Gets a DXGI from the display mode. + /// + /// Returns a . + internal ModeDesc1 ToDescription1() { - return new ModeDescription(Width, Height, RefreshRate.ToSharpDX(), format: (SharpDX.DXGI.Format)Format); + return new ModeDesc1((uint) Width, (uint) Height, RefreshRate.ToSilk(), format: (Format) Format); } - internal static DisplayMode FromDescription(ModeDescription description) + /// + /// Gets a from a DXGI structure. + /// + /// The DXGI structure. + /// A corresponding . + internal static DisplayMode FromDescription(ref readonly ModeDesc description) { - return new DisplayMode((PixelFormat)description.Format, description.Width, description.Height, new Rational(description.RefreshRate.Numerator, description.RefreshRate.Denominator)); + return new DisplayMode((PixelFormat) description.Format, (int) description.Width, (int) description.Height, new Rational((int) description.RefreshRate.Numerator, (int) description.RefreshRate.Denominator)); + } + + /// + /// Gets a from a DXGI structure. + /// + /// The DXGI structure. + /// A corresponding . + internal static DisplayMode FromDescription(ref readonly ModeDesc1 description) + { + return new DisplayMode((PixelFormat) description.Format, (int) description.Width, (int) description.Height, new Rational((int) description.RefreshRate.Numerator, (int) description.RefreshRate.Denominator)); } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/DxgiConstants.cs b/sources/engine/Stride.Graphics/Direct3D/DxgiConstants.cs new file mode 100644 index 0000000000..7d4403a237 --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D/DxgiConstants.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Graphics; + +/// +/// Defines common DXGI constants used by Stride. +/// +internal static class DxgiConstants +{ + /// + /// A DXGI object that was being queried has not been found. + /// + public const int ErrorNotFound = unchecked((int) 0x887A0002); + + /// + /// A DXGI object that was being queried has not been found. + /// + public const int ErrorNotCurrentlyAvailable = unchecked((int) 0x887A0022); + + /// + /// A resource was still in use by the GPU when there was a try to map the resource to CPU memory immediately. + /// + public const int ErrorWasStillDrawing = unchecked((int) 0x887A000A); + + /// + /// A resource access flag that identifies a shared resource with write access. + /// + public const uint SharedAccessResourceWrite = 1; + + /// + /// A factory creation flag that instructs the runtime to load the debug layer (DXGIDebug.dll) + /// alongside the factory object. + /// + public const uint CreateFactoryDebug = 1; + + /// + /// A flag that instructs the runtime to not use the ALT+ENTER key combination + /// to switch to full screen. + /// + public const uint WindowAssociation_NoAltEnter = 2; + + /// + /// Identifies the reason a graphics device was unavailable when a command tried to be executed on it. + /// + public enum DeviceRemoveReason + { + // From DXGI_ERROR constants in Winerror.h + + None = 0, // S_OK -- No error + + DeviceHung = unchecked((int) 0x887A0006), // DEVICE_HUNG + DeviceRemoved = unchecked((int) 0x887A0005), // DEVICE_REMOVED + DeviceReset = unchecked((int) 0x887A0007), // DEVICE_RESET + DriverInternalError = unchecked((int) 0x887A0020), // DRIVER_INTERNAL_ERROR + InvalidCall = unchecked((int) 0x887A0001) // INVALID_CALL + } +} diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapter.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapter.Direct3D.cs index c515ff6941..c5a54983d2 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapter.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapter.Direct3D.cs @@ -1,18 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D + // Copyright (c) 2010-2014 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,108 +22,122 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + using System; using System.Collections.Generic; -using System.Resources; -using SharpDX; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.DXGI; +using System.Runtime.CompilerServices; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; using Stride.Core; -using ComponentBase = Stride.Core.ComponentBase; -using Utilities = Stride.Core.Utilities; +using Stride.Core.UnsafeExtensions; namespace Stride.Graphics { - /// - /// Provides methods to retrieve and manipulate graphics adapters. This is the equivalent to . - /// - /// ff471329 - /// IDXGIAdapter1 - /// IDXGIAdapter1 - public partial class GraphicsAdapter + public sealed unsafe partial class GraphicsAdapter { - private readonly Adapter1 adapter; - private readonly int adapterOrdinal; - private readonly AdapterDescription1 description; + private IDXGIAdapter1* dxgiAdapter; - private GraphicsProfile minimumUnsupportedProfile = (GraphicsProfile)int.MaxValue; + private readonly uint adapterOrdinal; + private readonly AdapterDesc1 adapterDesc; + +#if STRIDE_GRAPHICS_API_DIRECT3D11 + private GraphicsProfile minimumUnsupportedProfile = (GraphicsProfile) int.MaxValue; private GraphicsProfile maximumSupportedProfile; +#endif /// - /// Initializes a new instance of the class. + /// Gets the native DXGI adapter. /// - /// The default factory. - /// The adapter ordinal. - internal GraphicsAdapter(Factory1 defaultFactory, int adapterOrdinal) - { - this.adapterOrdinal = adapterOrdinal; - adapter = defaultFactory.GetAdapter1(adapterOrdinal).DisposeBy(this); - description = adapter.Description1; - description.Description = description.Description.TrimEnd('\0'); // for some reason sharpDX returns an adaptater name of fixed size filled with trailing '\0' - //var nativeOutputs = adapter.Outputs; + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeAdapter => ComPtrHelpers.ToComPtr(dxgiAdapter); - var count = adapter.GetOutputCount(); - outputs = new GraphicsOutput[count]; - for (var i = 0; i < outputs.Length; i++) - outputs[i] = new GraphicsOutput(this, i).DisposeBy(this); - - AdapterUid = adapter.Description1.Luid.ToString(); - } + /// + /// Gets the description of this adapter. + /// + public string Description { get; } /// - /// Gets the description of this adapter. + /// Gets the vendor identifier of this adapter. /// - /// The description. - public string Description - { - get - { - return description.Description; - } - } + public int VendorId => (int) adapterDesc.VendorId; /// - /// Gets or sets the vendor identifier. + /// Determines if this is the default adapter. /// - /// - /// The vendor identifier. - /// - public int VendorId - { - get { return description.VendorId; } - } + public bool IsDefaultAdapter => adapterOrdinal == 0; + /// - /// Determines if this instance of GraphicsAdapter is the default adapter. + /// Initializes a new instance of the class. /// - public bool IsDefaultAdapter + /// + /// A COM pointer to the native interface. The ownership is transferred to this instance, so the reference count is not incremented. + /// + /// The adapter ordinal. + internal GraphicsAdapter(ComPtr adapter, uint adapterOrdinal) { - get + this.adapterOrdinal = adapterOrdinal; + dxgiAdapter = adapter.Handle; + + Unsafe.SkipInit(out AdapterDesc1 dxgiAdapterDesc); + HResult result = NativeAdapter.GetDesc1(ref dxgiAdapterDesc); + + if (result.IsFailure) + result.Throw(); + + adapterDesc = dxgiAdapterDesc; + Name = Description = SilkMarshal.PtrToString((nint) dxgiAdapterDesc.Description, NativeStringEncoding.LPWStr); + AdapterUid = dxgiAdapterDesc.AdapterLuid.BitCast(); + + uint outputIndex = 0; + var outputsList = new List(); + bool foundValidOutput; + ComPtr output = default; + + do { - return adapterOrdinal == 0; + result = adapter.EnumOutputs(outputIndex, ref output); + + foundValidOutput = result.IsSuccess && result.Code != DxgiConstants.ErrorNotFound; + if (!foundValidOutput) + break; + + var gfxOutput = new GraphicsOutput(adapter: this, output, outputIndex); + gfxOutput.DisposeBy(this); + outputsList.Add(gfxOutput); + + outputIndex++; } + while (foundValidOutput); + + graphicsOutputs = outputsList.ToArray(); } - internal Adapter1 NativeAdapter + /// + protected override void Destroy() { - get - { - return adapter; - } + base.Destroy(); + + ComPtrHelpers.SafeRelease(ref dxgiAdapter); } /// - /// Tests to see if the adapter supports the requested profile. + /// Checks if the graphics adapter supports the requested profile. /// - /// The graphics profile. - /// true if the profile is supported + /// The graphics profile to check. + /// + /// if the graphics profile is supported; otherwise. + /// public bool IsProfileSupported(GraphicsProfile graphicsProfile) { #if STRIDE_GRAPHICS_API_DIRECT3D12 return true; #else - // Did we check fo this or a higher profile, and it was supported? + // Did we check for this or a higher profile, and it was supported? if (maximumSupportedProfile >= graphicsProfile) return true; @@ -130,7 +146,24 @@ public bool IsProfileSupported(GraphicsProfile graphicsProfile) return false; // Check and min/max cached values - if (SharpDX.Direct3D11.Device.IsSupportedFeatureLevel(this.NativeAdapter, (SharpDX.Direct3D.FeatureLevel)graphicsProfile)) + + var d3d11 = D3D11.GetApi(window: null); + + ID3D11Device* device = null; + ID3D11DeviceContext* deviceContext = null; + + D3DFeatureLevel matchedFeatureLevel = 0; + var featureLevel = (D3DFeatureLevel) graphicsProfile; + var featureLevels = stackalloc D3DFeatureLevel[] { featureLevel }; + + HResult result = d3d11.CreateDevice(pAdapter: null, D3DDriverType.Hardware, Software: IntPtr.Zero, + Flags: 0, featureLevels, 1, D3D11.SdkVersion, + ref device, ref matchedFeatureLevel, ref deviceContext); + + ComPtrHelpers.SafeRelease(ref deviceContext); + ComPtrHelpers.SafeRelease(ref device); + + if (result.IsSuccess && matchedFeatureLevel == featureLevel) { maximumSupportedProfile = graphicsProfile; return true; @@ -143,5 +176,6 @@ public bool IsProfileSupported(GraphicsProfile graphicsProfile) #endif } } -} +} + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapterFactory.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapterFactory.Direct3D.cs index 9eba9d5cdf..62df986055 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapterFactory.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsAdapterFactory.Direct3D.cs @@ -1,65 +1,97 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D + using System.Collections.Generic; -using SharpDX.DXGI; +using Silk.NET.Core.Native; +using Silk.NET.DXGI; -namespace Stride.Graphics -{ - public static partial class GraphicsAdapterFactory - { #if STRIDE_PLATFORM_UWP || DIRECTX11_1 - internal static Factory2 NativeFactory; +using DxgiFactoryType = Silk.NET.DXGI.IDXGIFactory2; #else - internal static Factory1 NativeFactory; +using DxgiFactoryType = Silk.NET.DXGI.IDXGIFactory1; #endif +namespace Stride.Graphics +{ + public static unsafe partial class GraphicsAdapterFactory + { + private static DxgiFactoryType* dxgiFactory; + + /// + /// Gets the native DXGI factory object. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal static ComPtr NativeFactory + { + get + { + lock (StaticLock) + { + Initialize(); + return ComPtrHelpers.ToComPtr(dxgiFactory); + } + } + } + + /// - /// Initializes all adapters with the specified factory. + /// Initializes all the s. /// internal static void InitializeInternal() { staticCollector.Dispose(); -#if DIRECTX11_1 - using (var factory = new Factory1()) - NativeFactory = factory.QueryInterface(); -#elif STRIDE_PLATFORM_UWP - // Maybe this will become default code for everybody if we switch to DX 11.1/11.2 SharpDX dll? - NativeFactory = new Factory2(); + var dxgi = DXGI.GetApi(window: null); + + HResult result = default; + +#if STRIDE_PLATFORM_UWP || DIRECTX11_1 + +#if DEBUG + uint factoryFlags = DxgiConstants.CreateFactoryDebug; +#else + uint factoryFlags = 0; +#endif + result = dxgi.CreateDXGIFactory2(factoryFlags, out var factory); #else - NativeFactory = new Factory1(); + result = dxgi.CreateDXGIFactory1(out var factory); #endif + if (result.IsFailure) + result.Throw(); - staticCollector.Add(NativeFactory); + dxgiFactory = factory.Handle; + staticCollector.Add(factory); - int countAdapters = NativeFactory.GetAdapterCount1(); + uint adapterIndex = 0; var adapterList = new List(); - for (int i = 0; i < countAdapters; i++) + bool foundValidAdapter; + ComPtr dxgiAdapter = default; + + do { - var adapter = new GraphicsAdapter(NativeFactory, i); + result = dxgiFactory->EnumAdapters1(adapterIndex, ref dxgiAdapter); + + foundValidAdapter = result.IsSuccess && result.Code != DxgiConstants.ErrorNotFound; + if (!foundValidAdapter) + break; + + var adapter = new GraphicsAdapter(dxgiAdapter, adapterIndex); staticCollector.Add(adapter); adapterList.Add(adapter); - } + + adapterIndex++; + + } while (foundValidAdapter); defaultAdapter = adapterList.Count > 0 ? adapterList[0] : null; adapters = adapterList.ToArray(); } - - /// - /// Gets the used by all GraphicsAdapter. - /// - internal static Factory1 Factory - { - get - { - lock (StaticLock) - { - Initialize(); - return NativeFactory; - } - } - } } } -#endif + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsDevice.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsDevice.Direct3D.cs index fef75c1b74..300483c51c 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsDevice.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsDevice.Direct3D.cs @@ -2,16 +2,26 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_DIRECT3D11 + using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SharpDX; -using SharpDX.Direct3D11; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D11; + using Stride.Core; +using Stride.Core.UnsafeExtensions; + +using static Stride.Graphics.ComPtrHelpers; +using static Stride.Graphics.DxgiConstants; namespace Stride.Graphics { - public partial class GraphicsDevice + public unsafe partial class GraphicsDevice { internal readonly int ConstantBufferDataPlacementAlignment = 16; @@ -20,24 +30,47 @@ public partial class GraphicsDevice private bool simulateReset = false; private string rendererName; - private Device nativeDevice; - private DeviceContext nativeDeviceContext; - private readonly Queue disjointQueries = new Queue(4); - private readonly Stack currentDisjointQueries = new Stack(2); + private ID3D11Device* nativeDevice; + private ID3D11DeviceContext* nativeDeviceContext; + + private ID3D11InfoQueue* nativeInfoQueue; + /// + /// Gets the internal Direct3D 11 Device. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + public ComPtr NativeDevice => ToComPtr(nativeDevice); + + /// + /// Gets the internal Direct3D 11 Device Context. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeDeviceContext => ToComPtr(nativeDeviceContext); + + private readonly Queue> disjointQueries = new(4); + private readonly Stack> currentDisjointQueries = new(2); + + /// + /// The requested graphics profile for the Graphics Device. + /// internal GraphicsProfile RequestedProfile; - private SharpDX.Direct3D11.DeviceCreationFlags creationFlags; + private CreateDeviceFlag creationFlags; /// - /// The tick frquency of timestamp queries in Hertz. + /// Gets the tick frquency of timestamp queries, in hertz. /// - public long TimestampFrequency { get; private set; } + public ulong TimestampFrequency { get; private set; } /// - /// Gets the status of this device. + /// Gets the current status of the Graphics Device. /// - /// The graphics device status. public GraphicsDeviceStatus GraphicsDeviceStatus { get @@ -48,260 +81,380 @@ public GraphicsDeviceStatus GraphicsDeviceStatus return GraphicsDeviceStatus.Reset; } - var result = NativeDevice.DeviceRemovedReason; - if (result == SharpDX.DXGI.ResultCode.DeviceRemoved) - { - return GraphicsDeviceStatus.Removed; - } - - if (result == SharpDX.DXGI.ResultCode.DeviceReset) - { - return GraphicsDeviceStatus.Reset; - } - - if (result == SharpDX.DXGI.ResultCode.DeviceHung) - { - return GraphicsDeviceStatus.Hung; - } - - if (result == SharpDX.DXGI.ResultCode.DriverInternalError) - { - return GraphicsDeviceStatus.InternalError; - } + var result = (DeviceRemoveReason) nativeDevice->GetDeviceRemovedReason(); - if (result == SharpDX.DXGI.ResultCode.InvalidCall) + return result switch { - return GraphicsDeviceStatus.InvalidCall; - } - - if (result.Code < 0) - { - return GraphicsDeviceStatus.Reset; - } - - return GraphicsDeviceStatus.Normal; - } - } - - /// - /// Gets the native device. - /// - /// The native device. - internal SharpDX.Direct3D11.Device NativeDevice - { - get - { - return nativeDevice; + DeviceRemoveReason.DeviceRemoved => GraphicsDeviceStatus.Removed, + DeviceRemoveReason.DeviceReset => GraphicsDeviceStatus.Reset, + DeviceRemoveReason.DeviceHung => GraphicsDeviceStatus.Hung, + DeviceRemoveReason.DriverInternalError => GraphicsDeviceStatus.InternalError, + DeviceRemoveReason.InvalidCall => GraphicsDeviceStatus.InvalidCall, + + < 0 => GraphicsDeviceStatus.Reset, + _ => GraphicsDeviceStatus.Normal + }; } } /// - /// Gets the native device context. - /// - /// The native device context. - internal SharpDX.Direct3D11.DeviceContext NativeDeviceContext - { - get - { - return nativeDeviceContext; - } - } - - /// - /// Marks context as active on the current thread. + /// Marks the Graphics Device Context as active on the current thread. /// public void Begin() { FrameTriangleCount = 0; FrameDrawCalls = 0; - Query currentDisjointQuery; + Unsafe.SkipInit(out QueryDataTimestampDisjoint queryResult); + + ComPtr currentDisjointQuery = default; - // Try to read back the oldest disjoint query and reuse it. If not ready, create a new one. - if (disjointQueries.Count > 0 && NativeDeviceContext.GetData(disjointQueries.Peek(), out QueryDataTimestampDisjoint result)) + // Try to read back the oldest disjoint query and reuse it. If not ready, create a new one + if (disjointQueries.Count > 0) { - TimestampFrequency = result.Frequency; + currentDisjointQuery = disjointQueries.Peek(); + + var asyncQuery = currentDisjointQuery; + var dataSize = currentDisjointQuery.GetDataSize(); + HResult result = nativeDeviceContext->GetData(asyncQuery, ref queryResult, dataSize, (int) AsyncGetdataFlag.Donotflush); + + if (result.IsFailure) + result.Throw(); + + TimestampFrequency = queryResult.Frequency; currentDisjointQuery = disjointQueries.Dequeue(); } else { - var disjointQueryDiscription = new QueryDescription { Type = SharpDX.Direct3D11.QueryType.TimestampDisjoint }; - currentDisjointQuery = new Query(NativeDevice, disjointQueryDiscription); + var disjointQueryDescription = new QueryDesc(Query.TimestampDisjoint); + + HResult result = nativeDevice->CreateQuery(in disjointQueryDescription, ref currentDisjointQuery); + + if (result.IsFailure) + result.Throw(); } currentDisjointQueries.Push(currentDisjointQuery); - NativeDeviceContext.Begin(currentDisjointQuery); + + nativeDeviceContext->Begin(currentDisjointQuery); } /// - /// Enables profiling. + /// Enables or disables profiling. /// - /// if set to true [enabled flag]. - public void EnableProfile(bool enabledFlag) - { - } + /// to enable profiling; to disable it. + public void EnableProfile(bool enabledFlag) { } // TODO: Implement profiling with PIX markers? Currently, profiling is only implemented for OpenGL /// - /// Unmarks context as active on the current thread. + /// Marks the Graphics Device Context as inactive on the current thread. /// public void End() { - // If this fails, it means Begin()/End() don't match, something is very wrong + // If this fails, it means Begin() / End() don't match, something is very wrong var currentDisjointQuery = currentDisjointQueries.Pop(); - NativeDeviceContext.End(currentDisjointQuery); + nativeDeviceContext->End(currentDisjointQuery); disjointQueries.Enqueue(currentDisjointQuery); + + if (IsDebugMode) + { + // Process any messages in the InfoQueue + ProcessInfoQueueMessages(); + } } /// - /// Executes a deferred command list. + /// Executes a Compiled Command List. /// - /// The deferred command list. - public void ExecuteCommandList(CompiledCommandList commandList) - { - throw new NotImplementedException(); - } + /// The Compiled Command List to execute. + /// Deferred CommandList execution is not implemented for Direct3D 11." + /// + /// A Compiled Command List is a list of commands that have been recorded for execution on the Graphics Device + /// at a later time. This method executes the commands in the list. This is known as deferred execution. + /// + public void ExecuteCommandList(CompiledCommandList commandList) => throw new NotImplementedException(); /// - /// Executes multiple deferred command lists. + /// Executes multiple Compiled Command Lists. /// - /// The deferred command lists. - public void ExecuteCommandLists(int count, CompiledCommandList[] commandLists) - { - throw new NotImplementedException(); - } + /// The number of Compiled Command Lists to execute. + /// The Compiled Command Lists to execute. + /// Deferred CommandList execution is not implemented for Direct3D 11." + /// + /// A Compiled Command List is a list of commands that have been recorded for execution on the Graphics Device + /// at a later time. This method executes the commands in the list. This is known as deferred execution. + /// + public void ExecuteCommandLists(int count, CompiledCommandList[] commandLists) => throw new NotImplementedException(); + /// + /// Sets the Graphics Device to simulate a situation in which the device is lost and then reset. + /// public void SimulateReset() { simulateReset = true; } - private void InitializePostFeatures() + /// + /// Initializes the platform-specific features of the Graphics Device once it has been fully initialized. + /// + private unsafe partial void InitializePostFeatures() { // Create the main command list - InternalMainCommandList = new CommandList(this); + InternalMainCommandList = new CommandList(this).DisposeBy(this); } - private string GetRendererName() - { - return rendererName; - } + private partial string GetRendererName() => rendererName; /// - /// Initializes the specified device. + /// Initialize the platform-specific implementation of the Graphics Device. /// - /// The graphics profiles. + /// A non- list of the graphics profiles to try, in order of preference. /// The device creation flags. /// The window handle. - private void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) + private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) { - if (nativeDevice != null) + if (nativeDevice is not null) { // Destroy previous device ReleaseDevice(); } - rendererName = Adapter.NativeAdapter.Description.Description; + rendererName = Adapter.Description; - // Profiling is supported through pix markers + // Profiling is supported through PIX markers IsProfilingSupported = true; - // Map GraphicsProfile to D3D11 FeatureLevel - creationFlags = (SharpDX.Direct3D11.DeviceCreationFlags)deviceCreationFlags; + creationFlags = (CreateDeviceFlag) deviceCreationFlags; - // Create Device D3D11 with feature Level based on profile + var d3d11 = D3D11.GetApi(window: null); + + // Create D3D11 Device with feature Level based on profile for (int index = 0; index < graphicsProfiles.Length; index++) { + // Map GraphicsProfiles to D3D11 FeatureLevels var graphicsProfile = graphicsProfiles[index]; - try + var featureLevel = graphicsProfile.ToFeatureLevel(); + + // INTEL workaround: it seems Intel driver doesn't support properly feature level 9.x. Fallback to 10. + if (Adapter.VendorId == 0x8086) { - // D3D12 supports only feature level 11+ - var level = graphicsProfile.ToFeatureLevel(); - - // INTEL workaround: it seems Intel driver doesn't support properly feature level 9.x. Fallback to 10. - if (Adapter.VendorId == 0x8086) - { - if (level < SharpDX.Direct3D.FeatureLevel.Level_10_0) - level = SharpDX.Direct3D.FeatureLevel.Level_10_0; - } - - if (Core.Platform.Type == PlatformType.Windows - && GetModuleHandle("renderdoc.dll") != IntPtr.Zero) - { - if (level < SharpDX.Direct3D.FeatureLevel.Level_11_0) - level = SharpDX.Direct3D.FeatureLevel.Level_11_0; - } - - nativeDevice = new SharpDX.Direct3D11.Device(Adapter.NativeAdapter, creationFlags, level); - - // INTEL workaround: force ShaderProfile to be 10+ as well - if (Adapter.VendorId == 0x8086) - { - if (graphicsProfile < GraphicsProfile.Level_10_0 && (!ShaderProfile.HasValue || ShaderProfile.Value < GraphicsProfile.Level_10_0)) - ShaderProfile = GraphicsProfile.Level_10_0; - } - - RequestedProfile = graphicsProfile; - break; + // TODO: This is relevant still? Newer Intel Arc HW and drivers should have better support for 9.x + if (featureLevel < D3DFeatureLevel.Level100) + featureLevel = D3DFeatureLevel.Level100; } - catch (Exception) + + if (Core.Platform.Type == PlatformType.Windows && GetModuleHandle("renderdoc.dll") != IntPtr.Zero) + { + if (featureLevel < D3DFeatureLevel.Level110) + featureLevel = D3DFeatureLevel.Level110; + } + + var adapter = Adapter.NativeAdapter.AsComPtr(); + ComPtr device = default; + ComPtr deviceContext = default; + D3DFeatureLevel usedFeatureLevel = 0; + + HResult result = d3d11.CreateDevice(adapter, D3DDriverType.Unknown, Software: 0, (uint) creationFlags, + in featureLevel, FeatureLevels: 1, D3D11.SdkVersion, + ref device, ref usedFeatureLevel, ref deviceContext); + if (result.IsFailure) { if (index == graphicsProfiles.Length - 1) - throw; + result.Throw(); + else + continue; + } + + nativeDevice = device; + nativeDeviceContext = deviceContext; + + // INTEL workaround: force ShaderProfile to be 10+ as well + if (Adapter.VendorId == 0x8086) + { + // TODO: This is relevant still? Newer Intel Arc HW and drivers should have better support for 9.x + if (graphicsProfile < GraphicsProfile.Level_10_0 && (!ShaderProfile.HasValue || ShaderProfile.Value < GraphicsProfile.Level_10_0)) + ShaderProfile = GraphicsProfile.Level_10_0; } + + RequestedProfile = graphicsProfile; + break; } - nativeDeviceContext = nativeDevice.ImmediateContext; - // We keep one reference so that it doesn't disappear with InternalMainCommandList - ((IUnknown)nativeDeviceContext).AddReference(); if (IsDebugMode) { - GraphicsResourceBase.SetDebugName(this, nativeDeviceContext, "ImmediateContext"); + HResult result = nativeDevice->QueryInterface(out ComPtr infoQueue); + + if (result.IsSuccess && infoQueue.IsNotNull()) + { + nativeInfoQueue = infoQueue; + + infoQueue.SetMessageCountLimit(1000); + infoQueue.SetBreakOnSeverity(MessageSeverity.Corruption, true); + infoQueue.SetBreakOnSeverity(MessageSeverity.Error, true); + infoQueue.SetBreakOnSeverity(MessageSeverity.Warning, false); + } + } + } + + /// + /// Called when a message is received from the Direct3D 11 InfoQueue. + /// This method calls the event handler. + /// + /// The message received from the InfoQueue. + private void OnDeviceInfoQueueMessage(ref readonly Message message) + { + var eventHandler = DeviceInfoQueueMessage; + if (eventHandler is null) + return; + + var descriptionSpan = new ReadOnlySpan(message.PDescription, (int) message.DescriptionByteLength); + var description = descriptionSpan.GetString(); + + eventHandler(in message, description); + } + + /// + /// Processes all messages stored in the information queue, and invokes the event handler + /// for each message. + /// + internal void ProcessInfoQueueMessages() + { + Debug.Assert(nativeInfoQueue is not null, "NativeInfoQueue is null. Ensure that the Graphics Device is initialized with the Debug flag."); + + var numMessages = nativeInfoQueue->GetNumStoredMessages(); + if (numMessages == 0) + return; + + // If no event handler is registered, just clear the messages + var eventHandler = DeviceInfoQueueMessage; + if (eventHandler is null) + { + nativeInfoQueue->ClearStoredMessages(); + return; + } + + for (var i = 0ul; i < numMessages; i++) + { + ProcessMessage(i); + } + + nativeInfoQueue->ClearStoredMessages(); + + // + // Retrieves a message from the InfoQueue and invokes the event handler. + // + void ProcessMessage(ulong index) + { + nuint messageLength = default; + HResult result = nativeInfoQueue->GetMessageA(index, pMessage: null, ref messageLength); + + if (result.IsFailure) + result.Throw(); + + Span messageBytes = stackalloc byte[(int) messageLength]; + + ref var message = ref Unsafe.As(ref messageBytes[0]); + result = nativeInfoQueue->GetMessageA(index, ref message, ref messageLength); + + if (result.IsFailure) + result.Throw(); + + OnDeviceInfoQueueMessage(in message); } } - private void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) + /// + /// Represents a method that handles messages related to Graphics Device information. + /// + /// A reference to the message containing graphics device information. This parameter is read-only. + /// An optional description providing additional context about the message. Can be . + public delegate void GraphicsDeviceInfoMessageHandler(ref readonly Message message, string? description); + + /// + /// Occurs when a message is received in the Graphics Device information queue. + /// + /// + /// + /// This event is triggered when the Direct3D 11 InfoQueue receives a message. + /// This only happens if the Graphics Device was created with the flag. + /// + /// + /// Subscribe to this event to handle messages related to Graphics Device information. + /// The event handler receives an argument of type , which contains the message data. + /// + /// + public event GraphicsDeviceInfoMessageHandler? DeviceInfoQueueMessage; + + /// + /// Makes Direct3D 11-specific adjustments to the Pipeline State objects created by the Graphics Device. + /// + /// A Pipeline State description that can be modified and adjusted. + private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) { // On D3D, default state is Less instead of our LessEqual // Let's update default pipeline state so that it correspond to D3D state after a "ClearState()" pipelineStateDescription.DepthStencilState.DepthBufferFunction = CompareFunction.Less; } - protected void DestroyPlatformDevice() + /// + /// Releases the platform-specific Graphics Device and all its associated resources. + /// + protected partial void DestroyPlatformDevice() { ReleaseDevice(); } + /// + /// Disposes the Direct3D 11 Device and all its associated resources. + /// private void ReleaseDevice() { foreach (var query in disjointQueries) { - query.Dispose(); + query.Release(); } disjointQueries.Clear(); - // Display D3D11 ref counting info - NativeDevice.ImmediateContext.ClearState(); - NativeDevice.ImmediateContext.Flush(); + nativeDeviceContext->ClearState(); + nativeDeviceContext->Flush(); if (IsDebugMode) { - var debugDevice = NativeDevice.QueryInterfaceOrNull(); - if (debugDevice != null) + // Display D3D11 ref counting info + HResult result = nativeDevice->QueryInterface(out ComPtr debugDevice); + + if (result.IsSuccess && debugDevice.IsNotNull()) { - debugDevice.ReportLiveDeviceObjects(SharpDX.Direct3D11.ReportingLevel.Detail); - debugDevice.Dispose(); + debugDevice.ReportLiveDeviceObjects(RldoFlags.Detail); + debugDevice.Release(); } + + // Process any messages in the InfoQueue before releasing the device + ProcessInfoQueueMessages(); + + // Release the InfoQueue + nativeInfoQueue->ClearStoredMessages(); + SafeRelease(ref nativeInfoQueue); } - nativeDevice.Dispose(); - nativeDevice = null; + SafeRelease(ref nativeDevice); } + /// + /// Called when the Graphics Device is being destroyed. + /// internal void OnDestroyed() { } - internal void TagResource(GraphicsResourceLink resourceLink) + + /// + /// Tags a Graphics Resource as no having alive references, meaning it should be safe to dispose it + /// or discard its contents during the next or SetData operation. + /// + /// + /// A object identifying the Graphics Resource along some related allocation information. + /// + internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink) { if (resourceLink.Resource is GraphicsResource resource) resource.DiscardNextMap = true; @@ -311,4 +464,5 @@ internal void TagResource(GraphicsResourceLink resourceLink) private static extern IntPtr GetModuleHandle(string lpModuleName); } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsDeviceFeatures.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsDeviceFeatures.Direct3D.cs index 06f7549b20..c74af6e96e 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsDeviceFeatures.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsDeviceFeatures.Direct3D.cs @@ -1,18 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 + // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,82 +24,215 @@ // THE SOFTWARE. using System; -using System.Collections.Generic; -using SharpDX.Direct3D11; -using SharpDX.DXGI; +using System.Linq; +using System.Runtime.CompilerServices; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; + +using Feature = Silk.NET.Direct3D11.Feature; + +using static Stride.Graphics.GraphicsProfile; namespace Stride.Graphics { - /// - /// Features supported by a . - /// - /// - /// This class gives also features for a particular format, using the operator this[dxgiFormat] on this structure. - /// - public partial struct GraphicsDeviceFeatures + public unsafe partial struct GraphicsDeviceFeatures { - private static readonly List ObsoleteFormatToExcludes = new List() { Format.R1_UNorm, Format.B5G6R5_UNorm, Format.B5G5R5A1_UNorm }; + private static readonly Format[] ObsoleteFormatsToExclude = + [ + Format.FormatR1Unorm, + Format.FormatB5G6R5Unorm, + Format.FormatB5G5R5A1Unorm + ]; - internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) + internal GraphicsDeviceFeatures(GraphicsDevice device) { - var nativeDevice = deviceRoot.NativeDevice; + var nativeDevice = device.NativeDevice; HasSRgb = true; mapFeaturesPerFormat = new FeaturesPerFormat[256]; // Set back the real GraphicsProfile that is used - RequestedProfile = deviceRoot.RequestedProfile; - CurrentProfile = GraphicsProfileHelper.FromFeatureLevel(nativeDevice.FeatureLevel); + RequestedProfile = device.RequestedProfile; + CurrentProfile = GraphicsProfileHelper.FromFeatureLevel(nativeDevice.GetFeatureLevel()); HasResourceRenaming = true; - HasComputeShaders = nativeDevice.CheckFeatureSupport(SharpDX.Direct3D11.Feature.ComputeShaders); - HasDoublePrecision = nativeDevice.CheckFeatureSupport(SharpDX.Direct3D11.Feature.ShaderDoubles); - nativeDevice.CheckThreadingSupport(out HasMultiThreadingConcurrentResources, out this.HasDriverCommandLists); + HasComputeShaders = CheckComputeShadersSupport(); + HasDoublePrecision = CheckDoubleOpsInShadersSupport(); + CheckThreadingSupport(out HasMultiThreadingConcurrentResources, out HasDriverCommandLists); - HasDepthAsSRV = (CurrentProfile >= GraphicsProfile.Level_10_0); - HasDepthAsReadOnlyRT = CurrentProfile >= GraphicsProfile.Level_11_0; - HasMultisampleDepthAsSRV = CurrentProfile >= GraphicsProfile.Level_11_0; + HasDepthAsSRV = CurrentProfile >= Level_10_0; + HasDepthAsReadOnlyRT = CurrentProfile >= Level_11_0; + HasMultiSampleDepthAsSRV = CurrentProfile >= Level_11_0; // Check features for each DXGI.Format - foreach (var format in Enum.GetValues(typeof(SharpDX.DXGI.Format))) + foreach (var format in Enum.GetValues()) + { + if (format == Format.FormatForceUint) + continue; + + if (ObsoleteFormatsToExclude.Contains(format)) + continue; + + var maximumMultisampleCount = GetMaximumMultisampleCount(format); + + var computeShaderFormatSupport = HasComputeShaders + ? CheckComputeShaderFormatSupport(format) + : ComputeShaderFormatSupport.None; + + var formatSupport = CheckFormatSupport(format); + + var pixelFormat = (PixelFormat) format; + mapFeaturesPerFormat[(int) format] = new FeaturesPerFormat(pixelFormat, maximumMultisampleCount, computeShaderFormatSupport, formatSupport); + } + + // Sets the max resource sizes, mip counts, etc., for the supported profile + switch (CurrentProfile) + { + case Level_11_2: + case Level_11_1: + case Level_11_0: + MaximumMipLevels = 15; + ResourceSizeInMegabytes = 128; + MaximumTexture1DArraySize = 2048; + MaximumTexture2DArraySize = 2048; + MaximumTexture1DSize = 16384; + MaximumTexture2DSize = 16384; + MaximumTexture3DSize = 2048; + MaximumTextureCubeSize = 16384; + break; + + case Level_10_1: + case Level_10_0: + MaximumMipLevels = 14; + ResourceSizeInMegabytes = 128; + MaximumTexture1DArraySize = 512; + MaximumTexture2DArraySize = 512; + MaximumTexture1DSize = 8192; + MaximumTexture2DSize = 8192; + MaximumTexture3DSize = 2048; + MaximumTextureCubeSize = 8192; + break; + + case Level_9_1: + case Level_9_2: + case Level_9_3: + MaximumMipLevels = 14; + ResourceSizeInMegabytes = 128; + MaximumTexture1DArraySize = 512; + MaximumTexture2DArraySize = 512; + MaximumTexture1DSize = CurrentProfile < Level_9_3 ? 2048 : 4096; + MaximumTexture2DSize = CurrentProfile < Level_9_3 ? 2048 : 4096; + MaximumTexture3DSize = 256; + MaximumTextureCubeSize = CurrentProfile < Level_9_3 ? 512 : 4096; + break; + } + + /// + /// Checks if the Direct3D device does support Compute Shaders. + /// + bool CheckComputeShadersSupport() + { + Unsafe.SkipInit(out FeatureDataD3D10XHardwareOptions hwOptions); + + HResult result = nativeDevice.CheckFeatureSupport(Feature.D3D10XHardwareOptions, ref hwOptions, (uint) sizeof(FeatureDataD3D10XHardwareOptions)); + + if (result.IsFailure) + return false; + + return hwOptions.ComputeShadersPlusRawAndStructuredBuffersViaShader4X; + } + + /// + /// Checks if the Direct3D device does support double precision operations in shaders. + /// + bool CheckDoubleOpsInShadersSupport() + { + Unsafe.SkipInit(out FeatureDataDoubles doubles); + + HResult result = nativeDevice.CheckFeatureSupport(Feature.Doubles, ref doubles, (uint) sizeof(FeatureDataDoubles)); + + if (result.IsFailure) + return false; + + return doubles.DoublePrecisionFloatShaderOps; + } + + /// + /// Checks if the Direct3D device does support threading. + /// + void CheckThreadingSupport(out bool supportsConcurrentResources, out bool supportsCommandLists) + { + Unsafe.SkipInit(out FeatureDataThreading featureDataThreading); + + HResult result = nativeDevice.CheckFeatureSupport(Feature.Threading, ref featureDataThreading, (uint) sizeof(FeatureDataThreading)); + + if (result.IsFailure) + { + supportsConcurrentResources = false; + supportsCommandLists = false; + } + else + { + supportsConcurrentResources = featureDataThreading.DriverConcurrentCreates; + supportsCommandLists = featureDataThreading.DriverCommandLists; + } + } + + /// + /// Gets the maximum sample count when enabling multi-sampling for a particular . + /// + MultisampleCount GetMaximumMultisampleCount(Format pixelFormat) { - var dxgiFormat = (SharpDX.DXGI.Format)format; - var maximumMultisampleCount = MultisampleCount.None; - var computeShaderFormatSupport = ComputeShaderFormatSupport.None; - var formatSupport = FormatSupport.None; + uint maxCount = 1; - if (!ObsoleteFormatToExcludes.Contains(dxgiFormat)) + for (uint sampleCount = 1; sampleCount <= 8; sampleCount *= 2) { - maximumMultisampleCount = GetMaximumMultisampleCount(nativeDevice, dxgiFormat); - if (HasComputeShaders) - computeShaderFormatSupport = nativeDevice.CheckComputeShaderFormatSupport(dxgiFormat); + uint qualityLevels = 0; - formatSupport = (FormatSupport)nativeDevice.CheckFormatSupport(dxgiFormat); + HResult result = nativeDevice.CheckMultisampleQualityLevels(pixelFormat, sampleCount, ref qualityLevels); + + if (result.IsSuccess && qualityLevels != 0) + maxCount = sampleCount; } + return (MultisampleCount) maxCount; + } + + /// + /// Check if the Direct3D device does support Compute Shaders for the specified format. + /// + /// Flags indicating usage contexts in which the specified format is supported. + ComputeShaderFormatSupport CheckComputeShaderFormatSupport(Format format) + { + var dataFormatSupport2 = new FeatureDataFormatSupport2(format); + + HResult result = nativeDevice.CheckFeatureSupport(Feature.FormatSupport2, ref dataFormatSupport2, (uint) sizeof(FeatureDataFormatSupport2)); + + if (result.IsFailure) + return 0; - //mapFeaturesPerFormat[(int)dxgiFormat] = new FeaturesPerFormat((PixelFormat)dxgiFormat, maximumMultisampleCount, computeShaderFormatSupport, formatSupport); - mapFeaturesPerFormat[(int)dxgiFormat] = new FeaturesPerFormat((PixelFormat)dxgiFormat, maximumMultisampleCount, formatSupport); + return (ComputeShaderFormatSupport) dataFormatSupport2.OutFormatSupport2; } - } - /// - /// Gets the maximum multisample count for a particular . - /// - /// The device. - /// The pixelFormat. - /// The maximum multisample count for this pixel pixelFormat - private static MultisampleCount GetMaximumMultisampleCount(SharpDX.Direct3D11.Device device, SharpDX.DXGI.Format pixelFormat) - { - int maxCount = 1; - for (int i = 1; i <= 8; i *= 2) + /// + /// Check the support the Direct3D device has for the specified format. + /// + /// Flags indicating usage contexts in which the specified format is supported. + FormatSupport CheckFormatSupport(Format format) { - if (device.CheckMultisampleQualityLevels(pixelFormat, i) != 0) - maxCount = i; + var dataFormatSupport = new FeatureDataFormatSupport(format); + + HResult result = nativeDevice.CheckFeatureSupport(Feature.FormatSupport, ref dataFormatSupport, (uint) sizeof(FeatureDataFormatSupport)); + + if (result.IsFailure) + return 0; + + return (FormatSupport) dataFormatSupport.OutFormatSupport; } - return (MultisampleCount)maxCount; } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsOutput.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsOutput.Direct3D.cs index 202b25f21e..0a7e39a7b8 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsOutput.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsOutput.Direct3D.cs @@ -1,19 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 // Copyright (c) 2010-2014 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,288 +25,340 @@ using System; using System.Collections.Generic; -using SharpDX; -using SharpDX.Direct3D; -using SharpDX.DXGI; -using SharpDX.Mathematics.Interop; -using Stride.Core; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Silk.NET.Maths; +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D11; + using Stride.Core.Mathematics; +using Stride.Core.UnsafeExtensions; -using ResultCode = SharpDX.DXGI.ResultCode; +using Rectangle = Stride.Core.Mathematics.Rectangle; namespace Stride.Graphics { - /// - /// Provides methods to retrieve and manipulate an graphics output (a monitor), it is equivalent to . - /// - /// bb174546 - /// IDXGIOutput - /// IDXGIOutput - public partial class GraphicsOutput + public sealed unsafe partial class GraphicsOutput { - private readonly int outputIndex; - private readonly Output output; - private readonly OutputDescription outputDescription; + private IDXGIOutput* dxgiOutput; + private readonly uint outputIndex; + + private readonly OutputDesc outputDescription; /// - /// Initializes a new instance of . + /// Gets the native DXGI output. /// - /// The adapter. - /// Index of the output. - /// output - /// output - internal GraphicsOutput(GraphicsAdapter adapter, int outputIndex) - { - if (adapter == null) throw new ArgumentNullException("adapter"); + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeOutput => ComPtrHelpers.ToComPtr(dxgiOutput); - this.outputIndex = outputIndex; - this.adapter = adapter; - this.output = adapter.NativeAdapter.GetOutput(outputIndex).DisposeBy(this); - outputDescription = output.Description; + /// + /// Gets the handle of the monitor associated with this . + /// + public nint MonitorHandle => outputDescription.Monitor; - unsafe - { - var rectangle = outputDescription.DesktopBounds; - desktopBounds = *(Rectangle*)&rectangle; - } - } /// - /// Find the display mode that most closely matches the requested display mode. + /// Initializes a new instance of . /// - /// The target profile, as available formats are different depending on the feature level.. - /// The mode. - /// Returns the closes display mode. - /// HRESULT IDXGIOutput::FindClosestMatchingMode([In] const DXGI_MODE_DESC* pModeToMatch,[Out] DXGI_MODE_DESC* pClosestMatch,[In, Optional] IUnknown* pConcernedDevice) - /// Direct3D devices require UNORM formats. This method finds the closest matching available display mode to the mode specified in pModeToMatch. Similarly ranked fields (i.e. all specified, or all unspecified, etc) are resolved in the following order. ScanlineOrdering Scaling Format Resolution RefreshRate When determining the closest value for a particular field, previously matched fields are used to filter the display mode list choices, and other fields are ignored. For example, when matching Resolution, the display mode list will have already been filtered by a certain ScanlineOrdering, Scaling, and Format, while RefreshRate is ignored. This ordering doesn't define the absolute ordering for every usage scenario of FindClosestMatchingMode, because the application can choose some values initially, effectively changing the order that fields are chosen. Fields of the display mode are matched one at a time, generally in a specified order. If a field is unspecified, FindClosestMatchingMode gravitates toward the values for the desktop related to this output. If this output is not part of the desktop, then the default desktop output is used to find values. If an application uses a fully unspecified display mode, FindClosestMatchingMode will typically return a display mode that matches the desktop settings for this output. Unspecified fields are lower priority than specified fields and will be resolved later than specified fields. - public DisplayMode FindClosestMatchingDisplayMode(GraphicsProfile[] targetProfiles, DisplayMode mode) + /// The Graphics Adapter this output is attached to. + /// + /// A COM pointer to the native interface. + /// The ownership is transferred to this instance, so the reference count is not incremented. + /// + /// The index of the output. + /// is . + internal GraphicsOutput(GraphicsAdapter adapter, ComPtr nativeOutput, uint outputIndex) { - if (targetProfiles == null) throw new ArgumentNullException("targetProfiles"); + ArgumentNullException.ThrowIfNull(adapter); - ModeDescription closestDescription; - SharpDX.Direct3D11.Device deviceTemp = null; - FeatureLevel[] features = null; - try - { - features = new FeatureLevel[targetProfiles.Length]; - for (int i = 0; i < targetProfiles.Length; i++) - { - features[i] = (FeatureLevel)targetProfiles[i]; - } + Debug.Assert(nativeOutput.IsNotNull()); - deviceTemp = new SharpDX.Direct3D11.Device(adapter.NativeAdapter, SharpDX.Direct3D11.DeviceCreationFlags.None, features); - } - catch (Exception exception) - { - Log.Error($"Failed to create Direct3D device using {adapter.NativeAdapter.Description} adapter with features: {string.Join(", ", features)}.\nException: {exception}"); - } + this.outputIndex = outputIndex; + dxgiOutput = nativeOutput; + + Adapter = adapter; + + Unsafe.SkipInit(out OutputDesc outputDesc); + HResult result = nativeOutput.GetDesc(ref outputDesc); - var description = new ModeDescription() + if (result.IsFailure) + result.Throw(); + + Name = SilkMarshal.PtrToString((nint) outputDesc.DeviceName, NativeStringEncoding.LPWStr); + + ref var rectangle = ref outputDesc.DesktopCoordinates; + DesktopBounds = new Rectangle { - Width = mode.Width, - Height = mode.Height, - RefreshRate = mode.RefreshRate.ToSharpDX(), - Format = (Format)mode.Format, - Scaling = DisplayModeScaling.Unspecified, - ScanlineOrdering = DisplayModeScanlineOrder.Unspecified, + Location = rectangle.Min.BitCast, Point>(), + Width = rectangle.Size.X, + Height = rectangle.Size.Y }; - using (var device = deviceTemp) - output.GetClosestMatchingMode(device, description, out closestDescription); - return DisplayMode.FromDescription(closestDescription); + outputDescription = outputDesc; + } + + /// + protected override void Destroy() + { + base.Destroy(); + + ComPtrHelpers.SafeRelease(ref dxgiOutput); } - /// - /// Retrieves the handle of the monitor associated with this . - /// - /// bb173068 - /// HMONITOR Monitor - /// HMONITOR Monitor - public IntPtr MonitorHandle { get { return outputDescription.MonitorHandle; } } /// - /// Gets the native output. + /// Finds the display mode that most closely matches the requested display mode. /// - /// The native output. - internal Output NativeOutput + /// The target profiles, as available formats differ depending on the graphics profile. + /// + /// The desired display mode. + /// + /// Members of can be unspecified indicating no preference for that member. + /// + /// + /// A value of 0 for or indicates the value is unspecified. + /// If either Width or Height are 0, both must be 0. + /// + /// + /// A numerator and denominator of 0 in indicate it is unspecified. + /// + /// + /// A value of for indicates the pixel format is unspecified. + /// + /// + /// Returns the mode that most closely matches . + /// is empty and does not specify any graphics profile to test. + /// + /// Direct3D devices require UNORM pixel formats. + /// + /// Unspecified fields are lower priority than specified fields and will be resolved later than specified fields. + /// Similarly ranked fields (i.e. all specified, or all unspecified, etc.) are resolved in the following order: Format, Width, Height, RefreshRate. + /// + /// + /// When determining the closest value for a particular field, previously matched fields are used to filter the display mode list choices, and other fields are ignored. + /// For example, when matching resolution, the display mode list will have already been filtered by a certain pixel format, while the refresh rate is ignored. + /// + /// + /// This ordering doesn't define the absolute ordering for every usage scenario of , because the application can choose some + /// values initially, effectively changing the order that fields are chosen. Fields of the display mode are matched one at a time, generally in a specified order. + /// If a field is unspecified, this method gravitates toward the values for the desktop related to this output. If this output is not part of the desktop, then + /// the default desktop output is used to find values. + /// + /// + /// If an application uses a fully unspecified display mode, will typically return a display mode that matches the + /// desktop settings for this output. + /// + /// + public DisplayMode FindClosestMatchingDisplayMode(ReadOnlySpan targetProfiles, DisplayMode modeToMatch) { - get + if (targetProfiles.IsEmpty) + throw new ArgumentNullException(nameof(targetProfiles)); + + var d3d11 = D3D11.GetApi(window: null); + + // NOTE: Assume the same underlying integer type + Debug.Assert(sizeof(GraphicsProfile) == sizeof(D3DFeatureLevel)); + var featureLevels = targetProfiles.Cast(); + + IDXGIAdapter* nativeAdapter = (IDXGIAdapter*) Adapter.NativeAdapter.Handle; + ID3D11Device* deviceTemp = null; + ID3D11DeviceContext* deviceContext = null; + D3DFeatureLevel createdFeatureLevel = default; + + d3d11.CreateDevice(nativeAdapter, D3DDriverType.Unknown, Software: 0, Flags: 0, + in featureLevels[0], (uint) featureLevels.Length, + D3D11.SdkVersion, + ref deviceTemp, ref createdFeatureLevel, ref deviceContext); + + ModeDesc modeDescription = modeToMatch.ToDescription(); + + Unsafe.SkipInit(out ModeDesc closestDescription); + HResult result = dxgiOutput->FindClosestMatchingMode(in modeDescription, ref closestDescription, (IUnknown*) deviceTemp); + + if (result.IsFailure) + ThrowNoCompatibleProfile(result, Adapter, targetProfiles); + + deviceContext->Release(); + deviceTemp->Release(); + + return DisplayMode.FromDescription(in closestDescription); + + /// + /// Logs and throws an exception reporting that no compatible profile was found among the specified ones. + /// + [DoesNotReturn] + static void ThrowNoCompatibleProfile(HResult result, GraphicsAdapter adapter, ReadOnlySpan targetProfiles) { - return output; + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to create Direct3D device using adapter '{adapter.Description}' with profiles: {string.Join(", ", targetProfiles.ToArray())}.\nException: {exception}"); + throw exception; } } /// - /// Enumerates all available display modes for this output and stores them in . + /// Enumerates all available display modes for this output and stores them in . /// private void InitializeSupportedDisplayModes() { var modesAvailable = new List(); - var modesMap = new Dictionary(); + var knownModes = new Dictionary(); #if DIRECTX11_1 - var output1 = output.QueryInterface(); + using ComPtr dxgiOutput1 = dxgiOutput->QueryInterface(); #endif + const uint DisplayModeEnumerationFlags = DXGI.EnumModesInterlaced | DXGI.EnumModesScaling; - try + foreach (var format in Enum.GetValues()) { - const DisplayModeEnumerationFlags displayModeEnumerationFlags = DisplayModeEnumerationFlags.Interlaced | DisplayModeEnumerationFlags.Scaling; + if (format == Format.FormatForceUint) + continue; - foreach (var format in Enum.GetValues(typeof(SharpDX.DXGI.Format))) - { - var dxgiFormat = (Format)format; + uint displayModeCount = 0; #if DIRECTX11_1 - var modes = output1.GetDisplayModeList1(dxgiFormat, displayModeEnumerationFlags); + HResult result = dxgiOutput1.GetDisplayModeList1(format, DisplayModeEnumerationFlags, ref displayModeCount, pDesc: null); #else - var modes = output.GetDisplayModeList(dxgiFormat, displayModeEnumerationFlags); + HResult result = dxgiOutput->GetDisplayModeList(format, DisplayModeEnumerationFlags, ref displayModeCount, pDesc: null); #endif + if (result.IsFailure && result.Code != DxgiConstants.ErrorNotCurrentlyAvailable) + result.Throw(); + if (displayModeCount == 0) + continue; - foreach (var mode in modes) +#if DIRECTX11_1 + Span displayModes = stackalloc ModeDesc1[(int) displayModeCount]; + result = dxgiOutput1.GetDisplayModeList1(format, DisplayModeEnumerationFlags, ref displayModeCount, ref displayModes[0]); +#else + Span displayModes = stackalloc ModeDesc[(int) displayModeCount]; + result = dxgiOutput->GetDisplayModeList(format, DisplayModeEnumerationFlags, ref displayModeCount, ref displayModes[0]); +#endif + if (result.IsFailure) + continue; + + for (int i = 0; i < displayModeCount; i++) + { + ref var mode = ref displayModes[i]; + + if (mode.Scaling != ModeScaling.Unspecified) + continue; + + var modeKey = HashCode.Combine(format, mode.Width, mode.Height, mode.RefreshRate.Numerator, mode.RefreshRate.Denominator); + + if (!knownModes.ContainsKey(modeKey)) { - if (mode.Scaling == DisplayModeScaling.Unspecified) - { - var key = format + ";" + mode.Width + ";" + mode.Height + ";" + mode.RefreshRate.Numerator + ";" + mode.RefreshRate.Denominator; - - DisplayMode oldMode; - if (!modesMap.TryGetValue(key, out oldMode)) - { - var displayMode = DisplayMode.FromDescription(mode); - - modesMap.Add(key, displayMode); - modesAvailable.Add(displayMode); - } - } + var displayMode = DisplayMode.FromDescription(in mode); + + knownModes.Add(modeKey, displayMode); + modesAvailable.Add(displayMode); } } } - catch (SharpDX.SharpDXException dxgiException) - { - if (dxgiException.ResultCode != ResultCode.NotCurrentlyAvailable) - throw; - } -#if DIRECTX11_1 - output1.Dispose(); -#endif supportedDisplayModes = modesAvailable.ToArray(); } /// - /// Initializes with the current , - /// closest matching mode with the provided format ( or ) - /// or null in case of errors. + /// Initializes with the current , + /// the closest matching mode with the common formats or ), + /// or in no matching mode could be found. /// private void InitializeCurrentDisplayMode() { - if (!TryGetCurrentDisplayMode(out DisplayMode displayMode) && !TryGetClosestMatchingMode(Format.R8G8B8A8_UNorm, out displayMode)) - TryGetClosestMatchingMode(Format.B8G8R8A8_UNorm, out displayMode); - - currentDisplayMode = displayMode; + currentDisplayMode = GetCurrentDisplayMode() ?? + GetClosestMatchingMode(PixelFormat.R8G8B8A8_UNorm) ?? + GetClosestMatchingMode(PixelFormat.B8G8R8A8_UNorm); } /// - /// Tries to get current display mode based on output bounds from . + /// Tries to get the current based on the . /// - /// Current or null - /// depending on the outcome - private bool TryGetCurrentDisplayMode(out DisplayMode currentDisplayMode) + /// The current of the output, or if couldn't be determined. + private DisplayMode? GetCurrentDisplayMode() { - // We don't care about FeatureLevel because we want to get missing data - // about the current display/monitor mode and not the supported display mode for the specific graphics profile - if (!TryCreateDirect3DDevice(out SharpDX.Direct3D11.Device deviceTemp)) + var d3d11 = D3D11.GetApi(null); + + // Try to create a dummy ID3D11Device with no consideration to Graphics Profiles, etc. + // We only want to get missing information about the current display irrespective of graphics profiles + ID3D11Device* device = null; + ID3D11DeviceContext* deviceContext = null; + + D3DFeatureLevel selectedLevel = 0; + HResult result = d3d11.CreateDevice(pAdapter: null, D3DDriverType.Unknown, Software: IntPtr.Zero, Flags: 0, + pFeatureLevels: null, FeatureLevels: 0, + D3D11.SdkVersion, + ref device, &selectedLevel, ref deviceContext); + if (result.IsFailure) { - currentDisplayMode = null; - - return false; + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to create Direct3D device using adapter '{Adapter.Description}'.\nException: {exception}"); + return null; } - RawRectangle desktopBounds = outputDescription.DesktopBounds; - // We don't specify RefreshRate on purpose, it will be automatically - // filled in with current RefreshRate of the output (dispaly/monitor) by GetClosestMatchingMode - ModeDescription description = new ModeDescription - { - Width = desktopBounds.Right - desktopBounds.Left, - Height = desktopBounds.Bottom - desktopBounds.Top, - // Format will be automatically filled with the RefreshRate parameter if we pass reference to the Direct3D11 device - Format = Format.Unknown - }; + using var d3dDevice = new ComPtr { Handle = device }; + using var d3dDeviceContext = new ComPtr { Handle = deviceContext }; - using (SharpDX.Direct3D11.Device device = deviceTemp) + Unsafe.SkipInit(out ModeDesc closestMatch); + var modeDesc = new ModeDesc { - try - { - output.GetClosestMatchingMode(device, description, out ModeDescription closestDescription); - - currentDisplayMode = DisplayMode.FromDescription(closestDescription); - - return true; - } - catch (Exception exception) - { - Log.Error($"Failed to get current display mode. The resolution: {description.Width}x{description.Height} " + - $"taken from output is not correct.\nException: {exception}"); + Width = (uint) DesktopBounds.Width, + Height = (uint) DesktopBounds.Height, + // Format and RefreshRate will be automatically filled if we pass reference to the Direct3D 11 device + Format = Format.FormatUnknown + }; - currentDisplayMode = null; + result = dxgiOutput->FindClosestMatchingMode(in modeDesc, ref closestMatch, (IUnknown*) device); - return false; - } + if (result.IsFailure) + { + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to get current display mode. The resolution ({modeDesc.Width}x{modeDesc.Height}) " + + $"taken from the output is not correct.\nException: {exception}"); + return null; } + + return DisplayMode.FromDescription(in closestMatch); } /// - /// Tries to get closest display mode based on output bounds from and provided format. + /// Tries to get the closest based on the and the provided format. /// - /// Format in which we want find closest display mode - /// closest or null - /// depending on the outcome - private bool TryGetClosestMatchingMode(Format format, out DisplayMode closestMatchingMode) + /// The format in which we want to find the closest display mode. + /// + /// The found to be closest to the current resolution of the output and using the + /// specified , or if none could be found. + /// + private DisplayMode? GetClosestMatchingMode(PixelFormat format) { - RawRectangle desktopBounds = outputDescription.DesktopBounds; - // We don't specify RefreshRate on purpose, it will be automatically - // filled in with current RefreshRate of the output (dispaly/monitor) by GetClosestMatchingMode - ModeDescription description = new ModeDescription + var modeDesc = new ModeDesc { - Width = desktopBounds.Right - desktopBounds.Left, - Height = desktopBounds.Bottom - desktopBounds.Top, - Format = format + // NOTE: We don't specify RefreshRate on purpose, it will be automatically + // filled in with current RefreshRate of the output (display/monitor) by GetClosestMatchingMode + Width = (uint) DesktopBounds.Width, + Height = (uint) DesktopBounds.Height, + Format = (Format) format }; - try - { - output.GetClosestMatchingMode(null, description, out ModeDescription closestDescription); + Unsafe.SkipInit(out ModeDesc closestModeDesc); - closestMatchingMode = DisplayMode.FromDescription(closestDescription); + HResult result = dxgiOutput->FindClosestMatchingMode(in modeDesc, ref closestModeDesc, null); - return true; - } - catch (Exception exception) + if (result.IsFailure) { - Log.Error($"Failed to find closest matching mode. The resolution: {description.Width}x{description.Height} " + - $"taken from output and/or format: {format} is not correct.\nException: {exception}"); - - closestMatchingMode = null; - - return false; + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to find closest matching mode. The resolution ({modeDesc.Width}x{modeDesc.Height}) " + + $"taken from the output and/or the format ({format}) is not correct.\nException: {exception}"); + return null; } - } - private bool TryCreateDirect3DDevice(out SharpDX.Direct3D11.Device device) - { - device = null; - - try - { - device = new SharpDX.Direct3D11.Device(adapter.NativeAdapter); - } - catch (Exception exception) - { - Log.Error($"Failed to create Direct3D device using {adapter.NativeAdapter.Description}.\nException: {exception}"); - - return false; - } - - return true; + return DisplayMode.FromDescription(in closestModeDesc); } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsProfileHelper.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsProfileHelper.cs index 00cdc93b5f..10381abd93 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsProfileHelper.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsProfileHelper.cs @@ -1,49 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_DIRECT3D -using SharpDX.Direct3D; -namespace Stride.Graphics +#if STRIDE_GRAPHICS_API_DIRECT3D + +using Silk.NET.Core.Native; + +using Stride.Core.UnsafeExtensions; + +namespace Stride.Graphics; + +/// +/// Provides utility methods for converting between and . +/// +internal static class GraphicsProfileHelper { - internal static class GraphicsProfileHelper + /// + /// Converts an array of s to an array of corresponding s. + /// + /// An array of s to convert. + /// An array of Direct3D s. + public static D3DFeatureLevel[] ToFeatureLevel(this GraphicsProfile[] profiles) { - /// - /// Returns a GraphicsProfile from a FeatureLevel. - /// - /// associated GraphicsProfile - public static FeatureLevel[] ToFeatureLevel(this GraphicsProfile[] profiles) - { - if (profiles == null) - { - return null; - } - - var levels = new FeatureLevel[profiles.Length]; - for (int i = 0; i < levels.Length; i++) - { - levels[i] = (FeatureLevel)profiles[i]; - } - return levels; - } - - /// - /// Returns a GraphicsProfile from a FeatureLevel. - /// - /// associated GraphicsProfile - public static FeatureLevel ToFeatureLevel(this GraphicsProfile profile) - { - return (FeatureLevel)profile; - } - - /// - /// Returns a GraphicsProfile from a FeatureLevel. - /// - /// The level. - /// associated GraphicsProfile - public static GraphicsProfile FromFeatureLevel(FeatureLevel level) - { - return (GraphicsProfile)level; - } + if (profiles is null or []) + return null; + + var featureLevels = profiles.AsReadOnlySpan().ToArray(); + return featureLevels; } -} -#endif + + /// + /// Converts a to its corresponding . + /// + /// A to convert. + /// A Direct3D . + public static D3DFeatureLevel ToFeatureLevel(this GraphicsProfile profile) => (D3DFeatureLevel) profile; + + /// + /// Converts a to its corresponding . + /// + /// A to convert. + /// A Stride . + public static GraphicsProfile FromFeatureLevel(D3DFeatureLevel level) => (GraphicsProfile) level; +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsResource.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsResource.Direct3D.cs index 6b9c90c5c6..d90c94099d 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsResource.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsResource.Direct3D.cs @@ -1,97 +1,136 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 -using System; -using SharpDX.Direct3D11; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; + +using static Stride.Graphics.ComPtrHelpers; + +namespace Stride.Graphics; -namespace Stride.Graphics +public abstract unsafe partial class GraphicsResource { + private ID3D11ShaderResourceView* shaderResourceView; + private ID3D11UnorderedAccessView* unorderedAccessView; + /// - /// GraphicsResource class + /// Used to internally force a WriteDiscard (to force a resource rename) with the . /// - public abstract partial class GraphicsResource + internal bool DiscardNextMap; + + /// + /// Gets a value indicating whether the Graphics Resource is in "Debug mode". + /// + /// + /// if the Graphics Resource is initialized in "Debug mode"; otherwise, . + /// + protected bool IsDebugMode => GraphicsDevice?.IsDebugMode == true; + + /// + protected override void OnNameChanged() { - private ShaderResourceView shaderResourceView; - private UnorderedAccessView unorderedAccessView; - internal bool DiscardNextMap; // Used to internally force a WriteDiscard (to force a rename) with the GraphicsResourceAllocator + base.OnNameChanged(); - protected bool IsDebugMode + if (IsDebugMode) { - get + if (shaderResourceView is not null) { - return GraphicsDevice != null && GraphicsDevice.IsDebugMode; + var srv = ToComPtr(shaderResourceView).AsDeviceChild(); + srv.SetDebugName(Name is null ? null : $"{Name} SRV"); } - } - - protected override void OnNameChanged() - { - base.OnNameChanged(); - if (IsDebugMode) + if (unorderedAccessView is not null) { - if (this.shaderResourceView != null) - { - shaderResourceView.DebugName = Name == null ? null : $"{Name} SRV"; - } - - if (this.unorderedAccessView != null) - { - unorderedAccessView.DebugName = Name == null ? null : $"{Name} UAV"; - } + var uav = ToComPtr(unorderedAccessView).AsDeviceChild(); + uav.SetDebugName(Name is null ? null : $"{Name} UAV"); } } + } - /// - /// Gets or sets the ShaderResourceView attached to this GraphicsResource. - /// Note that only Texture, Texture3D, RenderTarget2D, RenderTarget3D, DepthStencil are using this ShaderResourceView - /// - /// The device child. - protected internal SharpDX.Direct3D11.ShaderResourceView NativeShaderResourceView + /// + /// Gets or sets the attached to the Graphics Resource. + /// + /// The Shader Resource View associated with the Graphics Resource. + /// + /// Only s are using this Shader Resource View. + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + /// + protected internal ComPtr NativeShaderResourceView + { + get => ToComPtr(shaderResourceView); + set { - get + if (shaderResourceView == value.Handle) + return; + + var previousShaderResourceView = shaderResourceView; + + shaderResourceView = value.Handle; + + if (shaderResourceView != previousShaderResourceView) { - return shaderResourceView; + if (previousShaderResourceView is not null) + previousShaderResourceView->Release(); } - set - { - shaderResourceView = value; - if (IsDebugMode && shaderResourceView != null) - { - shaderResourceView.DebugName = Name == null ? null : $"{Name} SRV"; - } + if (IsDebugMode && shaderResourceView is not null) + { + var srv = ToComPtr(shaderResourceView).AsDeviceChild(); + srv.SetDebugName(Name is null ? null : $"{Name} SRV"); } } + } - /// - /// Gets or sets the UnorderedAccessView attached to this GraphicsResource. - /// - /// The device child. - protected internal UnorderedAccessView NativeUnorderedAccessView + /// + /// Gets or sets the attached to the Graphics Resource. + /// + /// The Unordered Access View associated with the Graphics Resource. + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr NativeUnorderedAccessView + { + get => ToComPtr(unorderedAccessView); + set { - get + if (unorderedAccessView == value.Handle) + return; + + var previousUnorderedAccessView = unorderedAccessView; + + unorderedAccessView = value.Handle; + + if (unorderedAccessView != previousUnorderedAccessView) { - return unorderedAccessView; + if (previousUnorderedAccessView is not null) + previousUnorderedAccessView->Release(); } - set - { - unorderedAccessView = value; - if (IsDebugMode && unorderedAccessView != null) - { - unorderedAccessView.DebugName = Name == null ? null : $"{Name} UAV"; - } + if (IsDebugMode && unorderedAccessView is not null) + { + var uav = ToComPtr(unorderedAccessView).AsDeviceChild(); + uav.SetDebugName(Name is null ? null : $"{Name} UAV"); } } + } - protected internal override void OnDestroyed() - { - ReleaseComObject(ref shaderResourceView); - ReleaseComObject(ref unorderedAccessView); + /// + /// + /// This method releases the underlying native resources ( and ), + /// and then calls . + /// + protected internal override void OnDestroyed() + { + SafeRelease(ref shaderResourceView); + SafeRelease(ref unorderedAccessView); - base.OnDestroyed(); - } + base.OnDestroyed(); } } - + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/GraphicsResourceBase.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/GraphicsResourceBase.Direct3D.cs index 7b14ec328e..569a6b5c7e 100644 --- a/sources/engine/Stride.Graphics/Direct3D/GraphicsResourceBase.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/GraphicsResourceBase.Direct3D.cs @@ -1,114 +1,163 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 -#pragma warning disable SA1405 // Debug.Assert must provide message text + using System; -using System.Diagnostics; -using SharpDX; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; + +using static Stride.Graphics.ComPtrHelpers; + +namespace Stride.Graphics; -namespace Stride.Graphics +public abstract unsafe partial class GraphicsResourceBase { + private ID3D11DeviceChild* nativeDeviceChild; + private ID3D11Resource* nativeResource; + + /// + /// Gets the internal Direct3D 11 Resource. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr NativeResource => ToComPtr(nativeResource); + /// - /// GraphicsResource class + /// Gets or sets the internal . /// - public abstract partial class GraphicsResourceBase + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr NativeDeviceChild { - private SharpDX.Direct3D11.DeviceChild nativeDeviceChild; + get => ToComPtr(nativeDeviceChild); + set + { + if (nativeDeviceChild == value.Handle) + return; - protected internal SharpDX.Direct3D11.Resource NativeResource { get; private set; } + var oldDeviceChild = nativeDeviceChild; + if (oldDeviceChild is not null) + oldDeviceChild->Release(); - private void Initialize() - { - } + nativeDeviceChild = value.Handle; - /// - /// Gets or sets the device child. - /// - /// The device child. - protected internal SharpDX.Direct3D11.DeviceChild NativeDeviceChild - { - get - { - return nativeDeviceChild; - } - set - { - nativeDeviceChild = value; - NativeResource = nativeDeviceChild as SharpDX.Direct3D11.Resource; - // Associate PrivateData to this DeviceResource - SetDebugName(GraphicsDevice, nativeDeviceChild, Name); - } - } + if (nativeDeviceChild is null) + return; - /// - /// Associates the private data to the device child, useful to get the name in PIX debugger. - /// - internal static void SetDebugName(GraphicsDevice graphicsDevice, SharpDX.Direct3D11.DeviceChild deviceChild, string name) - { - if (graphicsDevice.IsDebugMode && deviceChild != null) - { - deviceChild.DebugName = name; - } - } + nativeDeviceChild->AddRef(); - /// - /// Called when graphics device has been detected to be internally destroyed. - /// - protected internal virtual void OnDestroyed() - { - Destroyed?.Invoke(this, EventArgs.Empty); + // The device child can be something that is not a Direct3D resource actually, + // like a Sampler State, for example + nativeResource = TryGetResource(); - ReleaseComObject(ref nativeDeviceChild); - NativeResource = null; + NativeDeviceChild.SetDebugName(Name); } + } - /// - /// Called when graphics device has been recreated. - /// - /// True if item transitioned to a state. - protected internal virtual bool OnRecreate() - { - return false; - } + /// + /// Internal method to detach the internal without incrementing or decrementing + /// the reference count. + /// + protected internal void UnsetNativeDeviceChild() + { + nativeDeviceChild = default; + } - protected SharpDX.Direct3D11.Device NativeDevice - { - get - { - return GraphicsDevice != null ? GraphicsDevice.NativeDevice : null; - } - } + /// + /// Internal method to set the internal without incrementing or decrementing + /// the reference count. + /// + protected internal void SetNativeDeviceChild(ComPtr deviceChild) + { + nativeDeviceChild = deviceChild.Handle; + + // The device child can be something that is not a Direct3D resource actually, + // like a Sampler State, for example + nativeResource = TryGetResource(); - /// - /// Gets the cpu access flags from resource usage. - /// - /// The usage. - /// - internal static SharpDX.Direct3D11.CpuAccessFlags GetCpuAccessFlagsFromUsage(GraphicsResourceUsage usage) + NativeDeviceChild.SetDebugName(Name); + } + + /// + /// Attempts to retrieve the Direct3D 11 resource associated with the current device child. + /// + /// + /// A representing the Direct3D 11 resource if the operation is successful; + /// otherwise, the a COM pointer. + /// + private ComPtr TryGetResource() + { + HResult result = nativeDeviceChild->QueryInterface(out ComPtr d3dResource); + if (result.IsSuccess) { - switch (usage) - { - case GraphicsResourceUsage.Dynamic: - return SharpDX.Direct3D11.CpuAccessFlags.Write; - case GraphicsResourceUsage.Staging: - return SharpDX.Direct3D11.CpuAccessFlags.Read | SharpDX.Direct3D11.CpuAccessFlags.Write; - } - return SharpDX.Direct3D11.CpuAccessFlags.None; + return d3dResource; } + return default; + } + + /// + /// Gets the internal Direct3D 11 device () if the resource is attached to + /// a , or if not. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected ComPtr NativeDevice => GraphicsDevice?.NativeDevice ?? default; + + + // No Direct3D-specific initialization + private partial void Initialize() { } + + /// + /// Called when the has been detected to be internally destroyed, + /// or when the methad has been called. Raises the event. + /// + /// + /// This method releases the underlying native resources ( and ). + /// + protected internal virtual partial void OnDestroyed() + { + Destroyed?.Invoke(this, EventArgs.Empty); - internal static void ReleaseComObject(ref T comObject) where T : class + SafeRelease(ref nativeDeviceChild); + SafeRelease(ref nativeResource); + } + + /// + /// Called when the has been recreated. + /// + /// + /// if resource has transitioned to the state. + /// + protected internal virtual bool OnRecreate() + { + return false; + } + + /// + /// Gets the CPU access flags from the intended resource usage. + /// + /// The intended usage for the resource. + /// A combination of one or more flags. + internal static CpuAccessFlag GetCpuAccessFlagsFromUsage(GraphicsResourceUsage usage) + { + return usage switch { - // We can't put IUnknown as a constraint on the generic as it would break compilation (trying to import SharpDX in projects with InternalVisibleTo) - var iUnknownObject = comObject as IUnknown; - if (iUnknownObject != null) - { - var refCountResult = iUnknownObject.Release(); - Debug.Assert(refCountResult >= 0); - comObject = null; - } - } + GraphicsResourceUsage.Dynamic => CpuAccessFlag.Write, + GraphicsResourceUsage.Staging => CpuAccessFlag.Read | CpuAccessFlag.Write, + GraphicsResourceUsage.Immutable => CpuAccessFlag.None, + + _ => CpuAccessFlag.Read + }; } + } - + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/PipelineState.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/PipelineState.Direct3D.cs index b6ff4fefae..14e56dbad2 100644 --- a/sources/engine/Stride.Graphics/Direct3D/PipelineState.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/PipelineState.Direct3D.cs @@ -1,221 +1,323 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 + using System; using System.Collections.Generic; -using System.Linq; -using Stride.Core; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.DXGI; + +using Stride.Core.Mathematics; using Stride.Core.Storage; +using Stride.Core.UnsafeExtensions; using Stride.Shaders; +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Graphics.ComPtrHelpers; + namespace Stride.Graphics { - public partial class PipelineState + public unsafe partial class PipelineState { // Effect private readonly RootSignature rootSignature; private readonly EffectBytecode effectBytecode; internal ResourceBinder ResourceBinder; - private SharpDX.Direct3D11.VertexShader vertexShader; - private SharpDX.Direct3D11.GeometryShader geometryShader; - private SharpDX.Direct3D11.PixelShader pixelShader; - private SharpDX.Direct3D11.HullShader hullShader; - private SharpDX.Direct3D11.DomainShader domainShader; - private SharpDX.Direct3D11.ComputeShader computeShader; + private ID3D11VertexShader* vertexShader; + private ID3D11GeometryShader* geometryShader; + private ID3D11PixelShader* pixelShader; + private ID3D11HullShader* hullShader; + private ID3D11DomainShader* domainShader; + private ID3D11ComputeShader* computeShader; private byte[] inputSignature; - private readonly SharpDX.Direct3D11.BlendState blendState; + private readonly ID3D11BlendState* blendState; private readonly uint sampleMask; - private readonly SharpDX.Direct3D11.RasterizerState rasterizerState; - private readonly SharpDX.Direct3D11.DepthStencilState depthStencilState; + private readonly ID3D11RasterizerState* rasterizerState; + private readonly ID3D11DepthStencilState* depthStencilState; + + private ID3D11InputLayout* inputLayout; - private SharpDX.Direct3D11.InputLayout inputLayout; + private readonly D3DPrimitiveTopology primitiveTopology; - private readonly SharpDX.Direct3D.PrimitiveTopology primitiveTopology; - // Note: no need to store RTV/DSV formats + // NOTE: No need to store RTV/DSV formats - internal PipelineState(GraphicsDevice graphicsDevice, PipelineStateDescription pipelineStateDescription) : base(graphicsDevice) + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// A description of the Pipeline State to create. + internal PipelineState(GraphicsDevice graphicsDevice, PipelineStateDescription pipelineStateDescription) + : base(graphicsDevice) { // First time, build caches var pipelineStateCache = GetPipelineStateCache(); // Effect - this.rootSignature = pipelineStateDescription.RootSignature; - this.effectBytecode = pipelineStateDescription.EffectBytecode; + rootSignature = pipelineStateDescription.RootSignature; + effectBytecode = pipelineStateDescription.EffectBytecode; + CreateShaders(pipelineStateCache); - if (rootSignature != null && effectBytecode != null) - ResourceBinder.Compile(graphicsDevice, rootSignature.EffectDescriptorSetReflection, this.effectBytecode); + + if (rootSignature is not null && effectBytecode is not null) + ResourceBinder.Compile(rootSignature.EffectDescriptorSetReflection, effectBytecode); // TODO: Cache over Effect|RootSignature to create binding operations // States blendState = pipelineStateCache.BlendStateCache.Instantiate(pipelineStateDescription.BlendState); - this.sampleMask = pipelineStateDescription.SampleMask; + sampleMask = pipelineStateDescription.SampleMask; rasterizerState = pipelineStateCache.RasterizerStateCache.Instantiate(pipelineStateDescription.RasterizerState); depthStencilState = pipelineStateCache.DepthStencilStateCache.Instantiate(pipelineStateDescription.DepthStencilState); CreateInputLayout(pipelineStateDescription.InputElements); - primitiveTopology = (SharpDX.Direct3D.PrimitiveTopology)pipelineStateDescription.PrimitiveType; + primitiveTopology = (D3DPrimitiveTopology) pipelineStateDescription.PrimitiveType; } + /// + /// Applies the current Pipeline State to the specified Command List, if it has changed since the last time it was applied. + /// + /// The Command List. + /// The previous state of the graphics pipeline. internal void Apply(CommandList commandList, PipelineState previousPipeline) { var nativeDeviceContext = commandList.NativeDeviceContext; if (rootSignature != previousPipeline.rootSignature) { - //rootSignature.Apply + //rootSignature.Apply // Not a concept in Direct3D 11, it is applied through the EffectBytecode } if (effectBytecode != previousPipeline.effectBytecode) { if (computeShader != previousPipeline.computeShader) - nativeDeviceContext.ComputeShader.Set(computeShader); + nativeDeviceContext.CSSetShader(computeShader, ppClassInstances: null, NumClassInstances: 0); + if (vertexShader != previousPipeline.vertexShader) - nativeDeviceContext.VertexShader.Set(vertexShader); + nativeDeviceContext.VSSetShader(vertexShader, ppClassInstances: null, NumClassInstances: 0); + if (pixelShader != previousPipeline.pixelShader) - nativeDeviceContext.PixelShader.Set(pixelShader); + nativeDeviceContext.PSSetShader(pixelShader, ppClassInstances: null, NumClassInstances: 0); + if (hullShader != previousPipeline.hullShader) - nativeDeviceContext.HullShader.Set(hullShader); + nativeDeviceContext.HSSetShader(hullShader, ppClassInstances: null, NumClassInstances: 0); + if (domainShader != previousPipeline.domainShader) - nativeDeviceContext.DomainShader.Set(domainShader); + nativeDeviceContext.DSSetShader(domainShader, ppClassInstances: null, NumClassInstances: 0); + if (geometryShader != previousPipeline.geometryShader) - nativeDeviceContext.GeometryShader.Set(geometryShader); + nativeDeviceContext.GSSetShader(geometryShader, ppClassInstances: null, NumClassInstances: 0); } if (blendState != previousPipeline.blendState || sampleMask != previousPipeline.sampleMask) { - nativeDeviceContext.OutputMerger.SetBlendState(blendState, nativeDeviceContext.OutputMerger.BlendFactor, sampleMask); + SkipInit(out Color4 blendFactor); + var blendFactorFloats = blendFactor.AsSpan(4); + + nativeDeviceContext.OMGetBlendState(ppBlendState: null, blendFactorFloats, pSampleMask: (uint*) null); + nativeDeviceContext.OMSetBlendState(blendState, blendFactorFloats, sampleMask); } if (rasterizerState != previousPipeline.rasterizerState) { - nativeDeviceContext.Rasterizer.State = rasterizerState; + nativeDeviceContext.RSSetState(rasterizerState); } if (depthStencilState != previousPipeline.depthStencilState) { - nativeDeviceContext.OutputMerger.DepthStencilState = depthStencilState; + SkipInit(out uint stencilRef); + + nativeDeviceContext.OMGetDepthStencilState(ppDepthStencilState: null, ref stencilRef); + nativeDeviceContext.OMSetDepthStencilState(depthStencilState, stencilRef); } if (inputLayout != previousPipeline.inputLayout) { - nativeDeviceContext.InputAssembler.InputLayout = inputLayout; + nativeDeviceContext.IASetInputLayout(inputLayout); } if (primitiveTopology != previousPipeline.primitiveTopology) { - nativeDeviceContext.InputAssembler.PrimitiveTopology = primitiveTopology; + nativeDeviceContext.IASetPrimitiveTopology(primitiveTopology); } } + /// protected internal override void OnDestroyed() { var pipelineStateCache = GetPipelineStateCache(); - if (blendState != null) + if (blendState is not null) pipelineStateCache.BlendStateCache.Release(blendState); - if (rasterizerState != null) + if (rasterizerState is not null) pipelineStateCache.RasterizerStateCache.Release(rasterizerState); - if (depthStencilState != null) + if (depthStencilState is not null) pipelineStateCache.DepthStencilStateCache.Release(depthStencilState); - if (vertexShader != null) + if (vertexShader is not null) pipelineStateCache.VertexShaderCache.Release(vertexShader); - if (pixelShader != null) + if (pixelShader is not null) pipelineStateCache.PixelShaderCache.Release(pixelShader); - if (geometryShader != null) + if (geometryShader is not null) pipelineStateCache.GeometryShaderCache.Release(geometryShader); - if (hullShader != null) + if (hullShader is not null) pipelineStateCache.HullShaderCache.Release(hullShader); - if (domainShader != null) + if (domainShader is not null) pipelineStateCache.DomainShaderCache.Release(domainShader); - if (computeShader != null) + if (computeShader is not null) pipelineStateCache.ComputeShaderCache.Release(computeShader); - inputLayout?.Dispose(); + if (inputLayout is not null) + inputLayout->Release(); base.OnDestroyed(); } + /// + /// Creates a ID3D11InputLayout for the graphics pipeline based on the provided Input Element Descriptions. + /// + /// + /// + /// An array of objects that define the input elements for the layout. + /// Each element specifies the semantic name, index, format, input slot, and byte offset for a vertex attribute. + /// + /// + /// If this parameter is , the method exits without performing any operations. + /// + /// private void CreateInputLayout(InputElementDescription[] inputElements) { - if (inputElements == null) + if (inputElements is null) return; - var nativeInputElements = new SharpDX.Direct3D11.InputElement[inputElements.Length]; + var nativeInputElements = stackalloc InputElementDesc[inputElements.Length]; + for (int index = 0; index < inputElements.Length; index++) { - var inputElement = inputElements[index]; - nativeInputElements[index] = new SharpDX.Direct3D11.InputElement + ref var inputElement = ref inputElements[index]; + + var nameLength = Encoding.ASCII.GetByteCount(inputElement.SemanticName); + var semanticName = stackalloc byte[nameLength]; + Encoding.ASCII.GetBytes(inputElement.SemanticName, new Span(semanticName, nameLength)); + + nativeInputElements[index] = new() { - Slot = inputElement.InputSlot, - SemanticName = inputElement.SemanticName, - SemanticIndex = inputElement.SemanticIndex, - AlignedByteOffset = inputElement.AlignedByteOffset, - Format = (SharpDX.DXGI.Format)inputElement.Format, + InputSlot = (uint) inputElement.InputSlot, + SemanticName = semanticName, + SemanticIndex = (uint) inputElement.SemanticIndex, + AlignedByteOffset = (uint) inputElement.AlignedByteOffset, + Format = (Format) inputElement.Format }; } - inputLayout = new SharpDX.Direct3D11.InputLayout(NativeDevice, inputSignature, nativeInputElements); + + ComPtr tempInputLayout = default; + + HResult result = NativeDevice.CreateInputLayout(nativeInputElements, NumElements: (uint) inputElements.Length, + in inputSignature[0], (uint) inputSignature.Length, ref tempInputLayout); + if (result.IsFailure) + result.Throw(); + + inputLayout = tempInputLayout; } - private void CreateShaders(DevicePipelineStateCache pipelineStateCache) + /// + /// Creates and initializes the Shaders for the current Effect set in the graphics pipeline, and + /// caches them in the specified Pipeline State cache. + /// + /// + /// The cache used to store and retrieve Pipeline State objects, including Shader instances. + /// If the Effect bytecode is , the method exits without performing any operations. + /// + private void CreateShaders(PipelineStateCache pipelineStateCache) { - if (effectBytecode == null) + if (effectBytecode is null) return; foreach (var shaderBytecode in effectBytecode.Stages) { var reflection = effectBytecode.Reflection; - // TODO CACHE Shaders with a bytecode hash + // TODO: CACHE Shaders with a bytecode hash + // TODO: Stale comment? Is it not what GraphicsCache does? switch (shaderBytecode.Stage) { case ShaderStage.Vertex: vertexShader = pipelineStateCache.VertexShaderCache.Instantiate(shaderBytecode); - // Note: input signature can be reused when reseting device since it only stores non-GPU data, - // so just keep it if it has already been created before. - if (inputSignature == null) - inputSignature = shaderBytecode; + + // NOTE: Input signature can be reused when reseting device since it only stores non-GPU data, + // so just keep it if it has already been created before. + inputSignature ??= shaderBytecode; break; + case ShaderStage.Domain: domainShader = pipelineStateCache.DomainShaderCache.Instantiate(shaderBytecode); break; + case ShaderStage.Hull: hullShader = pipelineStateCache.HullShaderCache.Instantiate(shaderBytecode); break; + case ShaderStage.Geometry: - if (reflection.ShaderStreamOutputDeclarations != null && reflection.ShaderStreamOutputDeclarations.Count > 0) + if (reflection.ShaderStreamOutputDeclarations?.Count > 0) { - // stream out elements - var soElements = new List(); + // Stream-out elements + var streamOutElementCount = reflection.ShaderStreamOutputDeclarations.Count; + var streamOutElements = stackalloc SODeclarationEntry[streamOutElementCount]; + + int index = 0; foreach (var streamOutputElement in reflection.ShaderStreamOutputDeclarations) { - var soElem = new SharpDX.Direct3D11.StreamOutputElement() + var nameLength = Encoding.ASCII.GetByteCount(streamOutputElement.SemanticName); + var semanticName = stackalloc byte[nameLength]; + Encoding.ASCII.GetBytes(streamOutputElement.SemanticName, new Span(semanticName, nameLength)); + + streamOutElements[index++] = new SODeclarationEntry { - Stream = streamOutputElement.Stream, - SemanticIndex = streamOutputElement.SemanticIndex, - SemanticName = streamOutputElement.SemanticName, + Stream = (uint) streamOutputElement.Stream, + SemanticIndex = (uint) streamOutputElement.SemanticIndex, + SemanticName = semanticName, StartComponent = streamOutputElement.StartComponent, ComponentCount = streamOutputElement.ComponentCount, OutputSlot = streamOutputElement.OutputSlot }; - soElements.Add(soElem); } - // TODO GRAPHICS REFACTOR better cache - geometryShader = new SharpDX.Direct3D11.GeometryShader(GraphicsDevice.NativeDevice, shaderBytecode, soElements.ToArray(), reflection.StreamOutputStrides, reflection.StreamOutputRasterizedStream); + // TODO: GRAPHICS REFACTOR: better cache + ID3D11GeometryShader* tempGeometryShader; + var bufferStrides = reflection.StreamOutputStrides.AsSpan(); + + HResult result = NativeDevice.CreateGeometryShaderWithStreamOutput( + in shaderBytecode.Data[0], (uint) shaderBytecode.Data.Length, + streamOutElements, (uint) streamOutElementCount, + in bufferStrides[0], (uint) bufferStrides.Length, + (uint) reflection.StreamOutputRasterizedStream, pClassLinkage: null, + &tempGeometryShader); + + if (result.IsFailure) + result.Throw(); + + geometryShader = tempGeometryShader; } else { geometryShader = pipelineStateCache.GeometryShaderCache.Instantiate(shaderBytecode); } break; + case ShaderStage.Pixel: pixelShader = pipelineStateCache.PixelShaderCache.Instantiate(shaderBytecode); break; + case ShaderStage.Compute: computeShader = pipelineStateCache.ComputeShaderCache.Instantiate(shaderBytecode); break; @@ -223,206 +325,428 @@ private void CreateShaders(DevicePipelineStateCache pipelineStateCache) } } - // Small helper to cache SharpDX graphics objects - private class GraphicsCache : IDisposable where TValue : SharpDX.IUnknown + /// + /// Gets the shared for the current Graphics Device, or + /// creates a new one if it does not exist. + /// + /// A Pipeline State cache shared for the current Graphics Device. + private PipelineStateCache GetPipelineStateCache() { - private object lockObject = new object(); + return GraphicsDevice.GetOrCreateSharedData(typeof(PipelineStateCache), device => new PipelineStateCache(device)); + } + + #region Pipeline State cache + + /// + /// Provides a caching mechanism for Direct3D 11 Pipeline State objects and Shaders. + /// + /// + /// The class manages caches for various Direct3D 11 Pipeline State objects, + /// including Shaders and State descriptions. This allows efficient reuse of Pipeline State objects and + /// reduces overhead associated with their creation. + /// + [DebuggerDisplay("", Name = $"{nameof(GraphicsDevice)}::{nameof(PipelineStateCache)}")] + private unsafe class PipelineStateCache : IDisposable + { + // Shaders + public readonly GraphicsCache VertexShaderCache; + public readonly GraphicsCache PixelShaderCache; + public readonly GraphicsCache GeometryShaderCache; + public readonly GraphicsCache HullShaderCache; + public readonly GraphicsCache DomainShaderCache; + public readonly GraphicsCache ComputeShaderCache; + + // States + public readonly GraphicsCache BlendStateCache; + public readonly GraphicsCache RasterizerStateCache; + public readonly GraphicsCache DepthStencilStateCache; + + + /// + /// Initializes a new instance of the class. + /// + /// + /// The Graphics Device associated with this cache. This is used to create and manage + /// Pipeline State objects and Shaders. + /// + public PipelineStateCache(GraphicsDevice graphicsDevice) + { + var nativeDevice = graphicsDevice.NativeDevice; + + // Shaders + var nullClassLinkage = NullComPtr(); + + VertexShaderCache = new(GetShaderId, CreateVertexShader, static vs => vs.Release()); + PixelShaderCache = new(GetShaderId, CreatePixelShader, static ps => ps.Release()); + GeometryShaderCache = new(GetShaderId, CreateGeometryShader, static gs => gs.Release()); + HullShaderCache = new(GetShaderId, CreateHullShader, static hs => hs.Release()); + DomainShaderCache = new(GetShaderId, CreateDomainShader, static ds => ds.Release()); + ComputeShaderCache = new(GetShaderId, CreateComputeShader, static cs => cs.Release()); + + // States + BlendStateCache = new(static state => state, CreateBlendState, static bs => bs.Release()); + RasterizerStateCache = new(static state => state, CreateRasterizerState, static rs => rs.Release()); + DepthStencilStateCache = new(static state => state, CreateDepthStencilState, static dss => dss.Release()); + + + // + // Gets the unique identifier for a Shader bytecode. + // + static ObjectId GetShaderId(ShaderBytecode shader) + { + return shader.Id; + } + + // + // Creates a Direct3D 11 Vertex Shader from the provided Shader bytecode. + // + ComPtr CreateVertexShader(ShaderBytecode source) + { + ComPtr vertexShader = default; + + HResult result = nativeDevice.CreateVertexShader(in source.Data[0], (nuint) source.Data.Length, nullClassLinkage, ref vertexShader); + + if (result.IsFailure) + result.Throw(); + + return vertexShader; + } + + // + // Creates a Direct3D 11 Hull Shader from the provided Shader bytecode. + // + ComPtr CreateHullShader(ShaderBytecode source) + { + ComPtr hullShader = default; + + HResult result = nativeDevice.CreateHullShader(in source.Data[0], (nuint) source.Data.Length, nullClassLinkage, ref hullShader); + + if (result.IsFailure) + result.Throw(); + + return hullShader; + } + + // + // Creates a Direct3D 11 Domain Shader from the provided Shader bytecode. + // + ComPtr CreateDomainShader(ShaderBytecode source) + { + ComPtr domainShader = default; + + HResult result = nativeDevice.CreateDomainShader(in source.Data[0], (nuint) source.Data.Length, nullClassLinkage, ref domainShader); + + if (result.IsFailure) + result.Throw(); + + return domainShader; + } + + // + // Creates a Direct3D 11 Pixel Shader from the provided Shader bytecode. + // + ComPtr CreatePixelShader(ShaderBytecode source) + { + ComPtr pixelShader = default; + + HResult result = nativeDevice.CreatePixelShader(in source.Data[0], (nuint) source.Data.Length, nullClassLinkage, ref pixelShader); + + if (result.IsFailure) + result.Throw(); + + return pixelShader; + } + + // + // Creates a Direct3D 11 Compute Shader from the provided Shader bytecode. + // + ComPtr CreateComputeShader(ShaderBytecode source) + { + ComPtr computeShader = default; + + HResult result = nativeDevice.CreateComputeShader(in source.Data[0], (nuint) source.Data.Length, nullClassLinkage, ref computeShader); + + if (result.IsFailure) + result.Throw(); + + return computeShader; + } + + // + // Creates a Direct3D 11 Geometry Shader from the provided Shader bytecode. + // + ComPtr CreateGeometryShader(ShaderBytecode source) + { + ComPtr geometryShader = default; + + HResult result = nativeDevice.CreateGeometryShader(in source.Data[0], (nuint) source.Data.Length, nullClassLinkage, ref geometryShader); + + if (result.IsFailure) + result.Throw(); + + return geometryShader; + } + + // + // Creates a Direct3D 11 Blend State from the provided description. + // + ComPtr CreateBlendState(BlendStateDescription description) + { + var nativeDescription = new BlendDesc + { + AlphaToCoverageEnable = description.AlphaToCoverageEnable, + IndependentBlendEnable = description.IndependentBlendEnable + }; - // Store instantiated objects - private readonly Dictionary storage = new Dictionary(); - // Used for quick removal - private readonly Dictionary reverse = new Dictionary(); + var renderTargetCount = description.RenderTargets.Count; + ref var renderTargets = ref description.RenderTargets; + for (int i = 0; i < renderTargetCount; i++) + { + ref var renderTarget = ref renderTargets[i]; + ref var nativeRenderTarget = ref nativeDescription.RenderTarget[i]; + + nativeRenderTarget.BlendEnable = renderTarget.BlendEnable; + nativeRenderTarget.SrcBlend = (Silk.NET.Direct3D11.Blend) renderTarget.ColorSourceBlend; + nativeRenderTarget.DestBlend = (Silk.NET.Direct3D11.Blend) renderTarget.ColorDestinationBlend; + nativeRenderTarget.BlendOp = (BlendOp) renderTarget.ColorBlendFunction; + nativeRenderTarget.SrcBlendAlpha = (Silk.NET.Direct3D11.Blend) renderTarget.AlphaSourceBlend; + nativeRenderTarget.DestBlendAlpha = (Silk.NET.Direct3D11.Blend) renderTarget.AlphaDestinationBlend; + nativeRenderTarget.BlendOpAlpha = (BlendOp) renderTarget.AlphaBlendFunction; + nativeRenderTarget.RenderTargetWriteMask = (byte) renderTarget.ColorWriteChannels; + } + + ComPtr blendState = default; + + HResult result = nativeDevice.CreateBlendState(in nativeDescription, ref blendState); + + if (result.IsFailure) + result.Throw(); + + return blendState; + } + + // + // Creates a Direct3D 11 Rasterizer State from the provided description. + // + ComPtr CreateRasterizerState(RasterizerStateDescription description) + { + var nativeDescription = new RasterizerDesc + { + CullMode = (Silk.NET.Direct3D11.CullMode) description.CullMode, + FillMode = (Silk.NET.Direct3D11.FillMode) description.FillMode, + FrontCounterClockwise = description.FrontFaceCounterClockwise, + DepthBias = description.DepthBias, + SlopeScaledDepthBias = description.SlopeScaleDepthBias, + DepthBiasClamp = description.DepthBiasClamp, + DepthClipEnable = description.DepthClipEnable, + ScissorEnable = description.ScissorTestEnable, + MultisampleEnable = description.MultisampleCount > MultisampleCount.None, + AntialiasedLineEnable = description.MultisampleAntiAliasLine + }; + + ComPtr rasterizerState = default; + + HResult result = nativeDevice.CreateRasterizerState(in nativeDescription, ref rasterizerState); + + if (result.IsFailure) + result.Throw(); + + return rasterizerState; + } - private readonly Dictionary counter = new Dictionary(); + // + // Creates a Direct3D 11 Depth-Stencil State from the provided description. + // + ComPtr CreateDepthStencilState(DepthStencilStateDescription description) + { + var nativeDescription = new DepthStencilDesc + { + DepthEnable = description.DepthBufferEnable, + DepthFunc = (ComparisonFunc) description.DepthBufferFunction, + DepthWriteMask = description.DepthBufferWriteEnable ? DepthWriteMask.All : DepthWriteMask.Zero, + + StencilEnable = description.StencilEnable, + StencilReadMask = description.StencilMask, + StencilWriteMask = description.StencilWriteMask, - private readonly Func computeKey; - private readonly Func computeValue; + FrontFace = + { + StencilFailOp = (StencilOp) description.FrontFace.StencilFail, + StencilPassOp = (StencilOp) description.FrontFace.StencilPass, + StencilDepthFailOp = (StencilOp) description.FrontFace.StencilDepthBufferFail, + StencilFunc = (ComparisonFunc) description.FrontFace.StencilFunction + }, + BackFace = + { + StencilFailOp = (StencilOp) description.BackFace.StencilFail, + StencilPassOp = (StencilOp) description.BackFace.StencilPass, + StencilDepthFailOp = (StencilOp) description.BackFace.StencilDepthBufferFail, + StencilFunc = (ComparisonFunc) description.BackFace.StencilFunction + } + }; + + ComPtr depthStencilState = default; + + HResult result = nativeDevice.CreateDepthStencilState(in nativeDescription, ref depthStencilState); + + if (result.IsFailure) + result.Throw(); + + return depthStencilState; + } + } - public GraphicsCache(Func computeKey, Func computeValue) + /// + public void Dispose() { - this.computeKey = computeKey; - this.computeValue = computeValue; + VertexShaderCache.Dispose(); + PixelShaderCache.Dispose(); + GeometryShaderCache.Dispose(); + HullShaderCache.Dispose(); + DomainShaderCache.Dispose(); + ComputeShaderCache.Dispose(); + BlendStateCache.Dispose(); + RasterizerStateCache.Dispose(); + DepthStencilStateCache.Dispose(); } + } - public TValue Instantiate(TSource source) + #endregion + + #region GraphicsCache + + /// + /// Small helper class to cache Direct3D graphics objects. + /// + /// The type of the source object for which to cache the Direct3D object. + /// The type of the key used to uniquely identify the cached object. + /// The type of the cached Direct3D object. + /// A function to compute the key from the source object. + /// A function to compute the Direct3D object from the source object. + /// Optional action to release the cached Direct3D object when it is no longer needed. + private class GraphicsCache( + Func computeKey, + Func> computeValue, + Action> releaseValue = null) : IDisposable + + where TKey : notnull + where TSource : notnull + where TValue : unmanaged, IComVtbl, IComVtbl + { + private readonly object lockObject = new(); + + // Instantiated objects + private readonly Dictionary> storage = []; + // Reverse lookup for quick removal + private readonly Dictionary, TKey> reverse = new(comparer: ComPtrEqualityComparer.Default); + + // Reference count for each cached object + private readonly Dictionary, int> referenceCount = new(comparer: ComPtrEqualityComparer.Default); + + private readonly Func computeKey = computeKey; + private readonly Func> computeValue = computeValue; + private readonly Action> releaseValue = releaseValue; + + + /// + /// Instantiates a new value or retrieves an existing one based on the specified source. + /// + /// The source object to cache, along with its associated Direct3D object. + /// The Direct3D object associated with the provided source object to cache. + /// + /// This method ensures thread-safe access to the underlying storage. + /// + /// If the key corresponding to the source does not exist, a new value is computed, added to the storage, and its reference count is initialized to 1. + ///
+ /// If the key already exists, the reference count of the associated value is incremented. + ///
+ ///
+ public ComPtr Instantiate(TSource source) { lock (lockObject) { - TValue value; var key = computeKey(source); - if (!storage.TryGetValue(key, out value)) + + if (!storage.TryGetValue(key, out ComPtr value)) { + // New value: Add it to the cache value = computeValue(source); + storage.Add(key, value); reverse.Add(value, key); - counter.Add(value, 1); + referenceCount.Add(value, 1); } else { - counter[value] = counter[value] + 1; + // Old value: Increment reference count + ref int refCount = ref CollectionsMarshal.GetValueRefOrNullRef(referenceCount, value); + if (!Unsafe.IsNullRef(ref refCount)) + refCount++; } return value; } } - public void Release(TValue value) + /// + /// Releases a reference to the specified Direct3D object, potentially removing it from the cache if no references remain. + /// + /// The Direct3D object to release. Must be a valid value that exists in the cache. + /// + /// This method decreases the reference count for the given value. If the reference count reaches zero, the value + /// is removed from the cache, along with any associated keys. If a release action is defined, it will be invoked + /// for the value being removed. + /// + public void Release(TValue* value) + // NOTE: We use ToComPtr to ensure that the reference count is not incremented inadvertently by the implicit conversion to ComPtr + => Release(ToComPtr(value)); + + /// + public void Release(ComPtr value) { - // Should we remove it from the cache? lock (lockObject) { - int refCount; - if (!counter.TryGetValue(value, out refCount)) + ref int refCount = ref CollectionsMarshal.GetValueRefOrNullRef(referenceCount, value); + + if (Unsafe.IsNullRef(ref refCount)) return; - counter[value] = --refCount; - if (refCount == 0) + // If the reference count reaches 0, no one is using this value anymore. We can remove it from the cache + if (--refCount == 0) { - counter.Remove(value); - reverse.Remove(value); - TKey key; - if (reverse.TryGetValue(value, out key)) + referenceCount.Remove(value); + if (reverse.TryGetValue(value, out TKey key)) { storage.Remove(key); } + reverse.Remove(value); - value.Release(); + releaseValue?.Invoke(value); } } } + /// public void Dispose() { lock (lockObject) { // Release everything - foreach (var entry in reverse) - { - entry.Key.Release(); - } + if (releaseValue is not null) + foreach ((ComPtr value, _) in reverse) + { + releaseValue(value); + } reverse.Clear(); storage.Clear(); - counter.Clear(); + referenceCount.Clear(); } } } - private DevicePipelineStateCache GetPipelineStateCache() - { - return GraphicsDevice.GetOrCreateSharedData(typeof(DevicePipelineStateCache), device => new DevicePipelineStateCache(device)); - } - - // Caches - private class DevicePipelineStateCache : IDisposable - { - public readonly GraphicsCache VertexShaderCache; - public readonly GraphicsCache PixelShaderCache; - public readonly GraphicsCache GeometryShaderCache; - public readonly GraphicsCache HullShaderCache; - public readonly GraphicsCache DomainShaderCache; - public readonly GraphicsCache ComputeShaderCache; - public readonly GraphicsCache BlendStateCache; - public readonly GraphicsCache RasterizerStateCache; - public readonly GraphicsCache DepthStencilStateCache; - - public DevicePipelineStateCache(GraphicsDevice graphicsDevice) - { - // Shaders - VertexShaderCache = new GraphicsCache(source => source.Id, source => new SharpDX.Direct3D11.VertexShader(graphicsDevice.NativeDevice, source)); - PixelShaderCache = new GraphicsCache(source => source.Id, source => new SharpDX.Direct3D11.PixelShader(graphicsDevice.NativeDevice, source)); - GeometryShaderCache = new GraphicsCache(source => source.Id, source => new SharpDX.Direct3D11.GeometryShader(graphicsDevice.NativeDevice, source)); - HullShaderCache = new GraphicsCache(source => source.Id, source => new SharpDX.Direct3D11.HullShader(graphicsDevice.NativeDevice, source)); - DomainShaderCache = new GraphicsCache(source => source.Id, source => new SharpDX.Direct3D11.DomainShader(graphicsDevice.NativeDevice, source)); - ComputeShaderCache = new GraphicsCache(source => source.Id, source => new SharpDX.Direct3D11.ComputeShader(graphicsDevice.NativeDevice, source)); - - // States - BlendStateCache = new GraphicsCache(source => source, source => new SharpDX.Direct3D11.BlendState(graphicsDevice.NativeDevice, CreateBlendState(source))); - RasterizerStateCache = new GraphicsCache(source => source, source => new SharpDX.Direct3D11.RasterizerState(graphicsDevice.NativeDevice, CreateRasterizerState(source))); - DepthStencilStateCache = new GraphicsCache(source => source, source => new SharpDX.Direct3D11.DepthStencilState(graphicsDevice.NativeDevice, CreateDepthStencilState(source))); - } - - private unsafe SharpDX.Direct3D11.BlendStateDescription CreateBlendState(BlendStateDescription description) - { - var nativeDescription = new SharpDX.Direct3D11.BlendStateDescription(); - - nativeDescription.AlphaToCoverageEnable = description.AlphaToCoverageEnable; - nativeDescription.IndependentBlendEnable = description.IndependentBlendEnable; - - var renderTargets = &description.RenderTarget0; - for (int i = 0; i < 8; i++) - { - ref var renderTarget = ref renderTargets[i]; - ref var nativeRenderTarget = ref nativeDescription.RenderTarget[i]; - nativeRenderTarget.IsBlendEnabled = renderTarget.BlendEnable; - nativeRenderTarget.SourceBlend = (SharpDX.Direct3D11.BlendOption)renderTarget.ColorSourceBlend; - nativeRenderTarget.DestinationBlend = (SharpDX.Direct3D11.BlendOption)renderTarget.ColorDestinationBlend; - nativeRenderTarget.BlendOperation = (SharpDX.Direct3D11.BlendOperation)renderTarget.ColorBlendFunction; - nativeRenderTarget.SourceAlphaBlend = (SharpDX.Direct3D11.BlendOption)renderTarget.AlphaSourceBlend; - nativeRenderTarget.DestinationAlphaBlend = (SharpDX.Direct3D11.BlendOption)renderTarget.AlphaDestinationBlend; - nativeRenderTarget.AlphaBlendOperation = (SharpDX.Direct3D11.BlendOperation)renderTarget.AlphaBlendFunction; - nativeRenderTarget.RenderTargetWriteMask = (SharpDX.Direct3D11.ColorWriteMaskFlags)renderTarget.ColorWriteChannels; - } - - return nativeDescription; - } - - private SharpDX.Direct3D11.RasterizerStateDescription CreateRasterizerState(RasterizerStateDescription description) - { - SharpDX.Direct3D11.RasterizerStateDescription nativeDescription; - - nativeDescription.CullMode = (SharpDX.Direct3D11.CullMode)description.CullMode; - nativeDescription.FillMode = (SharpDX.Direct3D11.FillMode)description.FillMode; - nativeDescription.IsFrontCounterClockwise = description.FrontFaceCounterClockwise; - nativeDescription.DepthBias = description.DepthBias; - nativeDescription.SlopeScaledDepthBias = description.SlopeScaleDepthBias; - nativeDescription.DepthBiasClamp = description.DepthBiasClamp; - nativeDescription.IsDepthClipEnabled = description.DepthClipEnable; - nativeDescription.IsScissorEnabled = description.ScissorTestEnable; - nativeDescription.IsMultisampleEnabled = description.MultisampleCount > MultisampleCount.None; - nativeDescription.IsAntialiasedLineEnabled = description.MultisampleAntiAliasLine; - - return nativeDescription; - } - - private SharpDX.Direct3D11.DepthStencilStateDescription CreateDepthStencilState(DepthStencilStateDescription description) - { - SharpDX.Direct3D11.DepthStencilStateDescription nativeDescription; - - nativeDescription.IsDepthEnabled = description.DepthBufferEnable; - nativeDescription.DepthComparison = (SharpDX.Direct3D11.Comparison)description.DepthBufferFunction; - nativeDescription.DepthWriteMask = description.DepthBufferWriteEnable ? SharpDX.Direct3D11.DepthWriteMask.All : SharpDX.Direct3D11.DepthWriteMask.Zero; - - nativeDescription.IsStencilEnabled = description.StencilEnable; - nativeDescription.StencilReadMask = description.StencilMask; - nativeDescription.StencilWriteMask = description.StencilWriteMask; - - nativeDescription.FrontFace.FailOperation = (SharpDX.Direct3D11.StencilOperation)description.FrontFace.StencilFail; - nativeDescription.FrontFace.PassOperation = (SharpDX.Direct3D11.StencilOperation)description.FrontFace.StencilPass; - nativeDescription.FrontFace.DepthFailOperation = (SharpDX.Direct3D11.StencilOperation)description.FrontFace.StencilDepthBufferFail; - nativeDescription.FrontFace.Comparison = (SharpDX.Direct3D11.Comparison)description.FrontFace.StencilFunction; - - nativeDescription.BackFace.FailOperation = (SharpDX.Direct3D11.StencilOperation)description.BackFace.StencilFail; - nativeDescription.BackFace.PassOperation = (SharpDX.Direct3D11.StencilOperation)description.BackFace.StencilPass; - nativeDescription.BackFace.DepthFailOperation = (SharpDX.Direct3D11.StencilOperation)description.BackFace.StencilDepthBufferFail; - nativeDescription.BackFace.Comparison = (SharpDX.Direct3D11.Comparison)description.BackFace.StencilFunction; - - return nativeDescription; - } - - public void Dispose() - { - VertexShaderCache.Dispose(); - PixelShaderCache.Dispose(); - GeometryShaderCache.Dispose(); - HullShaderCache.Dispose(); - DomainShaderCache.Dispose(); - ComputeShaderCache.Dispose(); - BlendStateCache.Dispose(); - RasterizerStateCache.Dispose(); - DepthStencilStateCache.Dispose(); - } - } + #endregion } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/QueryPool.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/QueryPool.Direct3D.cs index 5bc333e2ff..ab2f22ab18 100644 --- a/sources/engine/Stride.Graphics/Direct3D/QueryPool.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/QueryPool.Direct3D.cs @@ -1,57 +1,97 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 + using System; -using SharpDX.Direct3D11; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; + +namespace Stride.Graphics; -namespace Stride.Graphics +public unsafe partial class QueryPool { - public partial class QueryPool + private ComPtr[] nativeQueries; + + /// + /// Gets the internal Direct3D 11 Queries. + /// + /// + /// If any of the references is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ReadOnlySpan> NativeQueries => nativeQueries; + + + /// + /// Attempts to retrieve data from the in-flight GPU queries. + /// + /// + /// An array of values to be populated with the retrieved data. The array must have a length + /// equal to the number of queries performed (). + /// + /// if all data queries succeed; otherwise, . + /// + /// This method tries to perform reads for the multiple GPU queries in the pool and populates the provided array + /// with the results. If any query fails, the method returns and the array may contain + /// partial or uninitialized data. + /// + public bool TryGetData(long[] dataArray) { - internal Query[] NativeQueries; + var deviceContext = GraphicsDevice.NativeDeviceContext; - public bool TryGetData(long[] dataArray) + var queryCount = QueryCount; + for (var index = 0; index < queryCount; index++) { - for (var index = 0; index < NativeQueries.Length; index++) - { - if (!GraphicsDevice.NativeDeviceContext.GetData(NativeQueries[index], out dataArray[index])) - return false; - } + HResult result = deviceContext.GetData(nativeQueries[index], ref dataArray[index], sizeof(long), GetDataFlags: 0); - return true; + if (result.IsFailure) + return false; } - /// - protected internal override void OnDestroyed() - { - for (var i = 0; i < QueryCount; i++) - { - NativeQueries[i].Dispose(); - } - NativeQueries = null; + return true; + } - base.OnDestroyed(); + /// + protected internal override void OnDestroyed() + { + for (var i = 0; i < QueryCount; i++) + { + ComPtrHelpers.SafeRelease(ref nativeQueries[i]); } + nativeQueries = null; - private void Recreate() - { - var queryDescription = new QueryDescription(); + base.OnDestroyed(); + } - switch (QueryType) + /// + /// Implementation in Direct3D 11 that recreates the queries in the pool. + /// + /// + /// Only GPU queries of type are supported. + /// + private unsafe partial void Recreate() + { + var queryDescription = new QueryDesc + { + Query = QueryType switch { - case QueryType.Timestamp: - queryDescription.Type = SharpDX.Direct3D11.QueryType.Timestamp; - break; + QueryType.Timestamp => Query.Timestamp, - default: - throw new NotImplementedException(); + _ => throw new NotImplementedException($"Query type {QueryType} not supported") } + }; - NativeQueries = new Query[QueryCount]; - for (var i = 0; i < QueryCount; i++) - { - NativeQueries[i] = new Query(NativeDevice, queryDescription); - } + nativeQueries = new ComPtr[QueryCount]; + for (var i = 0; i < QueryCount; i++) + { + ComPtr query = default; + HResult result = NativeDevice.CreateQuery(in queryDescription, ref query); + + if (result.IsFailure) + result.Throw(); + + nativeQueries[i] = query; } } } diff --git a/sources/engine/Stride.Graphics/Direct3D/Rational.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/Rational.Direct3D.cs index 8ae6e29704..dfb539ec66 100644 --- a/sources/engine/Stride.Graphics/Direct3D/Rational.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/Rational.Direct3D.cs @@ -1,31 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_DIRECT3D -using System; +#if STRIDE_GRAPHICS_API_DIRECT3D namespace Stride.Graphics { public partial struct Rational { /// - /// Converts from SharpDX representation. + /// Converts from a Silk.NET representation. /// - /// The rational. - /// Rational. - internal static Rational FromSharpDX(SharpDX.DXGI.Rational rational) + /// The rational to convert. + /// The converted . + internal static Rational FromSilk(Silk.NET.DXGI.Rational rational) { - return new Rational(rational.Numerator, rational.Denominator); + return new Rational((int) rational.Numerator, (int) rational.Denominator); } /// - /// Converts to SharpDX representation. + /// Converts to a Silk.NET representation. /// - /// SharpDX.DXGI.Rational. - internal SharpDX.DXGI.Rational ToSharpDX() + /// The converted . + internal readonly Silk.NET.DXGI.Rational ToSilk() { - return new SharpDX.DXGI.Rational(Numerator, Denominator); + return new Silk.NET.DXGI.Rational((uint) Numerator, (uint) Denominator); } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/SamplerState.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/SamplerState.Direct3D.cs index 5e4ebc9738..7efd9ad5f1 100644 --- a/sources/engine/Stride.Graphics/Direct3D/SamplerState.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/SamplerState.Direct3D.cs @@ -1,71 +1,107 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 -using System; -using SharpDX; + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; using Stride.Core.Mathematics; -namespace Stride.Graphics +namespace Stride.Graphics; + +public unsafe partial class SamplerState { + private ID3D11SamplerState* samplerState; + + /// + /// Gets the internal Direct3D 11 Sampler State object. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeSamplerState => ComPtrHelpers.ToComPtr(samplerState); + + /// - /// Describes a sampler state used for texture sampling. + /// Initializes a new instance of the class. /// - public partial class SamplerState + /// The Graphics Device. + /// + /// A structure describing the Sampler State + /// object to create. + /// + /// An optional name that can be used to identify the Sampler State. + private SamplerState(GraphicsDevice device, ref readonly SamplerStateDescription description, string? name = null) + : base(device, name) { - /// - /// Initializes a new instance of the class. - /// - /// The device. - /// The name. - /// The sampler state description. - private SamplerState(GraphicsDevice device, SamplerStateDescription samplerStateDescription) : base(device) - { - Description = samplerStateDescription; + Description = description; - CreateNativeDeviceChild(); - } + CreateNativeSamplerState(); + } + + /// + protected internal override bool OnRecreate() + { + base.OnRecreate(); - /// - protected internal override bool OnRecreate() + CreateNativeSamplerState(); + return true; + } + + /// + protected internal override void OnDestroyed() + { + ComPtrHelpers.SafeRelease(ref samplerState); + + base.OnDestroyed(); + } + + private unsafe void CreateNativeSamplerState() + { + var samplerDescription = new SamplerDesc { - base.OnRecreate(); - CreateNativeDeviceChild(); - return true; - } + AddressU = (Silk.NET.Direct3D11.TextureAddressMode) Description.AddressU, + AddressV = (Silk.NET.Direct3D11.TextureAddressMode) Description.AddressV, + AddressW = (Silk.NET.Direct3D11.TextureAddressMode) Description.AddressW, + ComparisonFunc = (ComparisonFunc) Description.CompareFunction, + Filter = (Filter) Description.Filter, + MaxAnisotropy = (uint) Description.MaxAnisotropy, + MaxLOD = (uint) Description.MaxMipLevel, + MinLOD = (uint) Description.MinMipLevel, + MipLODBias = Description.MipMapLevelOfDetailBias + }; + Debug.Assert(sizeof(Color4) == (4 * sizeof(float))); + Unsafe.AsRef(samplerDescription.BorderColor) = Description.BorderColor; - private void CreateNativeDeviceChild() + // For 9.1, anisotropy cannot be larger than 2. + // Mirror once is not supported either. + if (GraphicsDevice.Features.CurrentProfile == GraphicsProfile.Level_9_1) { - SharpDX.Direct3D11.SamplerStateDescription nativeDescription; - - nativeDescription.AddressU = (SharpDX.Direct3D11.TextureAddressMode)Description.AddressU; - nativeDescription.AddressV = (SharpDX.Direct3D11.TextureAddressMode)Description.AddressV; - nativeDescription.AddressW = (SharpDX.Direct3D11.TextureAddressMode)Description.AddressW; - nativeDescription.BorderColor = ColorHelper.Convert(Description.BorderColor); - nativeDescription.ComparisonFunction = (SharpDX.Direct3D11.Comparison)Description.CompareFunction; - nativeDescription.Filter = (SharpDX.Direct3D11.Filter)Description.Filter; - nativeDescription.MaximumAnisotropy = Description.MaxAnisotropy; - nativeDescription.MaximumLod = Description.MaxMipLevel; - nativeDescription.MinimumLod = Description.MinMipLevel; - nativeDescription.MipLodBias = Description.MipMapLevelOfDetailBias; - - // For 9.1, anisotropy cannot be larger then 2 - // mirror once is not supported either - if (GraphicsDevice.Features.CurrentProfile == GraphicsProfile.Level_9_1) - { - // TODO: Min with user-value instead? - nativeDescription.MaximumAnisotropy = 2; - - if (nativeDescription.AddressU == SharpDX.Direct3D11.TextureAddressMode.MirrorOnce) - nativeDescription.AddressU = SharpDX.Direct3D11.TextureAddressMode.Mirror; - if (nativeDescription.AddressV == SharpDX.Direct3D11.TextureAddressMode.MirrorOnce) - nativeDescription.AddressV = SharpDX.Direct3D11.TextureAddressMode.Mirror; - if (nativeDescription.AddressW == SharpDX.Direct3D11.TextureAddressMode.MirrorOnce) - nativeDescription.AddressW = SharpDX.Direct3D11.TextureAddressMode.Mirror; - } - - NativeDeviceChild = new SharpDX.Direct3D11.SamplerState(NativeDevice, nativeDescription); + // TODO: Min with user-value instead? + samplerDescription.MaxAnisotropy = 2; + + if (samplerDescription.AddressU == Silk.NET.Direct3D11.TextureAddressMode.MirrorOnce) + samplerDescription.AddressU = Silk.NET.Direct3D11.TextureAddressMode.Mirror; + if (samplerDescription.AddressV == Silk.NET.Direct3D11.TextureAddressMode.MirrorOnce) + samplerDescription.AddressV = Silk.NET.Direct3D11.TextureAddressMode.Mirror; + if (samplerDescription.AddressW == Silk.NET.Direct3D11.TextureAddressMode.MirrorOnce) + samplerDescription.AddressW = Silk.NET.Direct3D11.TextureAddressMode.Mirror; } + + ID3D11SamplerState* samplerState; + HResult result = NativeDevice.CreateSamplerState(in samplerDescription, &samplerState); + + if (result.IsFailure) + result.Throw(); + + this.samplerState = samplerState; + NativeDeviceChild = NativeSamplerState.AsDeviceChild(); } -} +} + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/SharpDXInterop.cs b/sources/engine/Stride.Graphics/Direct3D/SharpDXInterop.cs deleted file mode 100644 index 81ff3049aa..0000000000 --- a/sources/engine/Stride.Graphics/Direct3D/SharpDXInterop.cs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 - -using System.Runtime.CompilerServices; -using SharpDX; -#if STRIDE_GRAPHICS_API_DIRECT3D11 -using SharpDX.Direct3D11; -#elif STRIDE_GRAPHICS_API_DIRECT3D12 -using SharpDX.Direct3D12; -#endif - -namespace Stride.Graphics -{ - public static class SharpDXInterop - { - public static ref SharpDX.DataBox AsSharpDX(ref this DataBox @this) => ref Unsafe.As(ref @this); - public static ref SharpDX.Direct3D11.ResourceRegion AsSharpDX(ref this ResourceRegion @this) => ref Unsafe.As(ref @this); - public static ref DataBox AsStride(ref this SharpDX.DataBox @this) => ref Unsafe.As(ref @this); - public static ref ResourceRegion AsStride(ref this SharpDX.Direct3D11.ResourceRegion @this) => ref Unsafe.As(ref @this); - - /// - /// Gets the native device (DX11/DX12) - /// - /// The Stride GraphicsDevice - /// - public static object GetNativeDevice(GraphicsDevice device) - { - return GetNativeDeviceImpl(device); - } - - /// - /// Gets the native device context (DX11) - /// - /// The Stride GraphicsDevice - /// - public static object GetNativeDeviceContext(GraphicsDevice device) - { - return GetNativeDeviceContextImpl(device); - } - - /// - /// Gets the native command queue (DX12 only) - /// - /// The Stride GraphicsDevice - /// - public static object GetNativeCommandQueue(GraphicsDevice device) - { - return GetNativeCommandQueueImpl(device); - } - - /// - /// Gets the DX11 native resource handle - /// - /// The Stride GraphicsResourceBase - /// - public static object GetNativeResource(GraphicsResource resource) - { - return GetNativeResourceImpl(resource); - } - - public static object GetNativeShaderResourceView(GraphicsResource resource) - { - return GetNativeShaderResourceViewImpl(resource); - } - - public static object GetNativeRenderTargetView(Texture texture) - { - return GetNativeRenderTargetViewImpl(texture); - } - - /// - /// Creates a Texture from a DirectX11 native texture - /// This method internally will call AddReference on the dxTexture2D texture. - /// - /// The GraphicsDevice in use - /// The DX11 texture - /// If false AddRef will be called on the texture, if true will not, effectively taking ownership - /// Set the format to SRgb - /// - public static Texture CreateTextureFromNative(GraphicsDevice device, object dxTexture2D, bool takeOwnership, bool isSRgb = false) - { -#if STRIDE_GRAPHICS_API_DIRECT3D11 - return CreateTextureFromNativeImpl(device, (Texture2D)dxTexture2D, takeOwnership, isSRgb); -#elif STRIDE_GRAPHICS_API_DIRECT3D12 - return CreateTextureFromNativeImpl(device, (Resource)dxTexture2D, takeOwnership, isSRgb); -#endif - } - -#if STRIDE_GRAPHICS_API_DIRECT3D11 - /// - /// Gets the DX11 native device - /// - /// The Stride GraphicsDevice - /// - private static Device GetNativeDeviceImpl(GraphicsDevice device) - { - return device.NativeDevice; - } - - private static DeviceContext GetNativeDeviceContextImpl(GraphicsDevice device) - { - return device.NativeDeviceContext; - } - - private static object GetNativeCommandQueueImpl(GraphicsDevice device) - { - return null; - } - - /// - /// Gets the DX11 native resource handle - /// - /// The Stride GraphicsResourceBase - /// - private static Resource GetNativeResourceImpl(GraphicsResource resource) - { - return resource.NativeResource; - } - - private static ShaderResourceView GetNativeShaderResourceViewImpl(GraphicsResource resource) - { - return resource.NativeShaderResourceView; - } - - private static RenderTargetView GetNativeRenderTargetViewImpl(Texture texture) - { - return texture.NativeRenderTargetView; - } - - /// - /// Creates a Texture from a DirectX11 native texture - /// This method internally will call AddReference on the dxTexture2D texture. - /// - /// The GraphicsDevice in use - /// The DX11 texture - /// If false AddRef will be called on the texture, if true will not, effectively taking ownership - /// Set the format to SRgb - /// - private static Texture CreateTextureFromNativeImpl(GraphicsDevice device, Texture2D dxTexture2D, bool takeOwnership, bool isSRgb = false) - { - var tex = new Texture(device); - - if (takeOwnership) - { - var unknown = dxTexture2D as IUnknown; - unknown.AddReference(); - } - - tex.InitializeFromImpl(dxTexture2D, isSRgb); - - return tex; - } - -#elif STRIDE_GRAPHICS_API_DIRECT3D12 - /// - /// Gets the DX11 native device - /// - /// The Stride GraphicsDevice - /// - private static Device GetNativeDeviceImpl(GraphicsDevice device) - { - return device.NativeDevice; - } - - private static object GetNativeDeviceContextImpl(GraphicsDevice device) - { - return null; - } - - private static CommandQueue GetNativeCommandQueueImpl(GraphicsDevice device) - { - return device.NativeCommandQueue; - } - - /// - /// Gets the DX11 native resource handle - /// - /// The Stride GraphicsResourceBase - /// - private static Resource GetNativeResourceImpl(GraphicsResource resource) - { - return resource.NativeResource; - } - - private static CpuDescriptorHandle GetNativeShaderResourceViewImpl(GraphicsResource resource) - { - return resource.NativeShaderResourceView; - } - - private static CpuDescriptorHandle GetNativeRenderTargetViewImpl(Texture texture) - { - return texture.NativeRenderTargetView; - } - - /// - /// Creates a Texture from a DirectX11 native texture - /// This method internally will call AddReference on the dxTexture2D texture. - /// - /// The GraphicsDevice in use - /// The DX11 texture - /// If false AddRef will be called on the texture, if true will not, effectively taking ownership - /// Set the format to SRgb - /// - private static Texture CreateTextureFromNativeImpl(GraphicsDevice device, Resource dxTexture2D, bool takeOwnership, bool isSRgb = false) - { - var tex = new Texture(device); - - if (takeOwnership) - { - var unknown = dxTexture2D as IUnknown; - unknown.AddReference(); - } - - tex.InitializeFromImpl(dxTexture2D, isSRgb); - - return tex; - } -#endif - } -} - -#endif diff --git a/sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs index ef9f03289e..d7b5ce8e9a 100644 --- a/sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/SwapChainGraphicsPresenter.Direct3D.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,102 +22,192 @@ // THE SOFTWARE. #if STRIDE_GRAPHICS_API_DIRECT3D + using System; -using System.Reflection; -using SharpDX; -using SharpDX.DXGI; -using SharpDX.Mathematics.Interop; -using Stride.Core.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; + #if STRIDE_GRAPHICS_API_DIRECT3D11 -using BackBufferResourceType = SharpDX.Direct3D11.Texture2D; +using BackBufferResourceType = Silk.NET.Direct3D11.ID3D11Texture2D; #elif STRIDE_GRAPHICS_API_DIRECT3D12 -using BackBufferResourceType = SharpDX.Direct3D12.Resource; +using BackBufferResourceType = Silk.NET.Direct3D12.ID3D12Resource; #endif -using DXGI_Format = SharpDX.DXGI.Format; + +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Graphics.ComPtrHelpers; namespace Stride.Graphics { /// - /// Graphics presenter for SwapChain. + /// A wrapping a DirectX Swap-Chain + /// (). /// - public class SwapChainGraphicsPresenter : GraphicsPresenter + /// + public unsafe class SwapChainGraphicsPresenter : GraphicsPresenter { private readonly Texture backBuffer; +#if STRIDE_GRAPHICS_API_DIRECT3D11 private readonly bool flipModelSupport; +#elif STRIDE_GRAPHICS_API_DIRECT3D12 + // From MSDN: https://learn.microsoft.com/en-us/windows/win32/api/dxgi/ne-dxgi-dxgi_swap_effect + + // DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_SEQUENTIAL: + // This enumeration value is never supported. D3D12 apps must use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL + // or DXGI_SWAP_EFFECT_FLIP_DISCARD. + private readonly bool flipModelSupport = true; +#endif private readonly bool tearingSupport; - private SwapChain swapChain; + private bool useFlipModel; - private int bufferCount; + private IDXGISwapChain* swapChain; - private bool useFlipModel; + private int bufferCount; #if STRIDE_GRAPHICS_API_DIRECT3D12 - private int bufferSwapIndex; + private uint bufferSwapIndex; #endif + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// + /// The parameters describing the buffers the will present to. + /// + /// is . + /// is . + /// + /// is or + /// the is invalid or zero. + /// + /// The Depth-Stencil format specified is not supported. public SwapChainGraphicsPresenter(GraphicsDevice device, PresentationParameters presentationParameters) : base(device, presentationParameters) { PresentInterval = presentationParameters.PresentationInterval; - flipModelSupport = CheckFlipModelSupport(device); - tearingSupport = CheckTearingSupport(device); + CheckDeviceFeatures(out flipModelSupport, out tearingSupport); // Initialize the swap chain swapChain = CreateSwapChain(); - backBuffer = new Texture(device).InitializeFromImpl(swapChain.GetBackBuffer(0), Description.BackBufferFormat.IsSRgb()); + var nativeBackBuffer = GetBackBuffer(); - // Reload should get backbuffer from swapchain as well - //backBufferTexture.Reload = graphicsResource => ((Texture)graphicsResource).Recreate(swapChain.GetBackBuffer(0)); + backBuffer = GraphicsDevice.IsDebugMode + ? new Texture(device, "SwapChain Back-Buffer") + : new Texture(device); - static bool CheckFlipModelSupport(GraphicsDevice device) + backBuffer.InitializeFromImpl(nativeBackBuffer, Description.BackBufferFormat.IsSRgb()); + + // Reload should get Back-Buffer from Swap-Chain as well + // TODO: Stale statement/comment? + //backBuffer.Reload = graphicsResource => ((Texture)graphicsResource).Recreate(swapChain.GetBackBuffer(0)); + + // + // Determines if the Graphics Device supports the flip model and tearing. + // + // TODO: Shouldn't these checks be in GraphicsAdapter? + void CheckDeviceFeatures(out bool supportsFlipModel, out bool supportsTearing) { - try - { - // From https://github.com/walbourn/directx-vs-templates/blob/main/d3d11game_win32_dr/DeviceResources.cpp#L138 - using var dxgiDevice = device.NativeDevice.QueryInterface(); - using var dxgiAdapter = dxgiDevice.Adapter; - using var dxgiFactory = dxgiAdapter.GetParent(); - return dxgiFactory != null; - } - catch - { - // The requested interfaces need at least Windows 8 - return false; - } + var nativeAdapter = device.Adapter.NativeAdapter; + +#if STRIDE_GRAPHICS_API_DIRECT3D11 + supportsFlipModel = CheckFlipModelSupport(nativeAdapter); + +#elif STRIDE_GRAPHICS_API_DIRECT3D12 + + // From MSDN: https://learn.microsoft.com/en-us/windows/win32/api/dxgi/ne-dxgi-dxgi_swap_effect + + // DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_SEQUENTIAL: + // This enumeration value is never supported. D3D12 apps must use DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL + // or DXGI_SWAP_EFFECT_FLIP_DISCARD. + + supportsFlipModel = true; +#endif + supportsTearing = CheckTearingSupport(nativeAdapter); } - static unsafe bool CheckTearingSupport(GraphicsDevice device) +#if STRIDE_GRAPHICS_API_DIRECT3D11 + // + // Determines if the DXGI adapter and the system supports the flip model. + // From https://github.com/walbourn/directx-vs-templates/blob/main/d3d11game_win32_dr/DeviceResources.cpp#L138 + // + static bool CheckFlipModelSupport(ComPtr adapter) { - try - { - // From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays - using var dxgiDevice = device.NativeDevice.QueryInterface(); - using var dxgiAdapter = dxgiDevice.Adapter; - using var dxgiFactory = dxgiAdapter.GetParent(); - if (dxgiFactory is null) - return false; - - int allowTearing = 0; - dxgiFactory.CheckFeatureSupport(Feature.PresentAllowTearing, new IntPtr(&allowTearing), sizeof(int)); - return allowTearing != 0; - } - catch - { - // The requested interfaces need at least Windows 10 + HResult result = adapter.GetParent(out var dxgiAdapterFactory4); + + // The requested interfaces need at least Windows 8 + var supportsFlipModel = result.IsSuccess && dxgiAdapterFactory4.IsNotNull(); + + dxgiAdapterFactory4.Release(); + return supportsFlipModel; + } +#endif + // + // Determines if the DXGI adapter and the system supports tearing, also known as "vsync-off". + // This flag is particularly useful for variable refresh rate displays. + // From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays + // + static unsafe bool CheckTearingSupport(ComPtr adapter) + { + HResult result = adapter.GetParent(out var dxgiAdapterFactory5); + + if (result.IsFailure || dxgiAdapterFactory5.IsNull()) return false; - } + + // The requested interfaces need at least Windows 10 + int allowTearing = 0; + result = dxgiAdapterFactory5.CheckFeatureSupport(Feature.PresentAllowTearing, ref allowTearing, sizeof(int)); + + var supportsTearing = result.IsSuccess && allowTearing != 0; + + dxgiAdapterFactory5.Release(); + return supportsTearing; } } + /// + /// Gets one of the Swap-Chain Back-Buffers. + /// + /// The interface of the surface to resolve from the Back-Buffer. + /// + /// A zero-based buffer index. + /// If the swap effect is not , this method only has + /// access to the first Buffer; for this case (which is the default), set the index to zero. + /// + /// Returns a reference to a back-buffer interface. + private ComPtr GetBackBuffer(uint index = 0) where TD3DResource : unmanaged, IComVtbl + { + // NOTE: The Swap-Chain Back-Buffer is a COM object, so this AddRef()s. + // It must be released when swapping or discarding the reference. + + swapChain->GetBuffer(index, out ComPtr resource); + return resource; + } + + /// public override Texture BackBuffer => backBuffer; - public override object NativePresenter => swapChain; + // TODO: This boxes the ComPtr, which is not ideal + /// + public override object NativePresenter => NativeSwapChain; + /// + /// Gets the internal DXGI Swap-Chain. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeSwapChain => ToComPtr(swapChain); + + /// public override bool IsFullScreen { get @@ -125,41 +215,44 @@ public override bool IsFullScreen #if STRIDE_PLATFORM_UWP return false; #else - return swapChain.IsFullScreen; + return GetFullScreenState(); #endif } set { #if !STRIDE_PLATFORM_UWP - if (swapChain == null) + if (swapChain is null) return; var outputIndex = Description.PreferredFullScreenOutputIndex; - // no outputs connected to the current graphics adapter - var output = GraphicsDevice.Adapter != null && outputIndex < GraphicsDevice.Adapter.Outputs.Length ? GraphicsDevice.Adapter.Outputs[outputIndex] : null; + var output = GraphicsDevice.Adapter != null && outputIndex < GraphicsDevice.Adapter.Outputs.Length + ? GraphicsDevice.Adapter.Outputs[outputIndex] + // There are no outputs connected to the current Graphics Adapter + : null; - Output currentOutput = null; + bool isCurrentlyFullscreen = GetFullScreenState(out var currentOutput); - try - { - RawBool isCurrentlyFullscreen; - swapChain.GetFullscreenState(out isCurrentlyFullscreen, out currentOutput); + if (currentOutput.IsNotNull()) + currentOutput.Release(); - // check if the current fullscreen monitor is the same as new one - // If not fullscreen, currentOutput will be null but output won't be, so don't compare them - if (isCurrentlyFullscreen == value && (isCurrentlyFullscreen == false || (output != null && currentOutput != null && currentOutput.NativePointer == output.NativeOutput.NativePointer))) - return; - } - finally - { - currentOutput?.Dispose(); - } + // Check if the current fullscreen monitor is the same as the new one. + // If not fullscreen, currentOutput will be null but output won't be, so don't compare them + if (isCurrentlyFullscreen == value && + (isCurrentlyFullscreen is false || (output is not null && currentOutput.IsNotNull() && currentOutput.Handle == output.NativeOutput.Handle))) + return; bool switchToFullScreen = value; + // If going to fullscreen mode: call 1) SwapChain.ResizeTarget 2) SwapChain.IsFullScreen - var description = new ModeDescription(backBuffer.ViewWidth, backBuffer.ViewHeight, Description.RefreshRate.ToSharpDX(), (DXGI_Format)Description.BackBufferFormat); + var description = new ModeDesc + { + Width = (uint) backBuffer.ViewWidth, + Height = (uint) backBuffer.ViewHeight, + RefreshRate = Description.RefreshRate.ToSilk(), + Format = (Format) Description.BackBufferFormat + }; if (switchToFullScreen) { OnDestroyed(); @@ -171,113 +264,182 @@ public override bool IsFullScreen else { Description.IsFullScreen = false; - swapChain.IsFullScreen = false; + HResult result = swapChain->SetFullscreenState(Fullscreen: 0, pTarget: null); + + if (result.IsFailure) + result.Throw(); - // call 1) SwapChain.IsFullScreen 2) SwapChain.Resize + // Call 1) SwapChain.IsFullScreen 2) SwapChain.Resize Resize(backBuffer.ViewWidth, backBuffer.ViewHeight, backBuffer.ViewFormat); } - // If going to window mode: + // If going to window mode: if (!switchToFullScreen) { - // call 1) SwapChain.IsFullScreen 2) SwapChain.Resize - description.RefreshRate = new SharpDX.DXGI.Rational(0, 0); - swapChain.ResizeTarget(ref description); + // Call 1) SwapChain.IsFullScreen 2) SwapChain.Resize + description.RefreshRate = default; + HResult result = swapChain->ResizeTarget(in description); + + if (result.IsFailure) + result.Throw(); } #endif } } + /// + /// Determines if the Swap-Chain is presenting in fullscreen mode, and to which output. + /// + /// + /// When this method returns, + /// + /// If the Swap-Chain is presenting in fullscreen mode, contains the output (screen) to which it is presenting. + /// If the Swap-Chain is presenting to a window, contains a pointer. + /// + /// + /// + /// if the Swap-Chain is in fullscreen mode; otherwise. + /// + private bool GetFullScreenState(out ComPtr fullScreenOutput) + { + int isFullScreen = default; + fullScreenOutput = default; + swapChain->GetFullscreenState(ref isFullScreen, ref fullScreenOutput); + + return isFullScreen != 0; + } + + /// + /// Determines if the Swap-Chain is presenting in fullscreen mode. + /// + /// + /// if the Swap-Chain is in fullscreen mode; otherwise. + /// + private bool GetFullScreenState() + { + SkipInit(out int isFullScreen); + swapChain->GetFullscreenState(ref isFullScreen, ppTarget: null); + + return isFullScreen != 0; + } + + /// public override void BeginDraw(CommandList commandList) { } + /// public override void EndDraw(CommandList commandList, bool present) { } + /// + /// + /// An unexpected error occurred while presenting the Swap-Chain. Check the status of the Graphics Device + /// for more information (). + /// public override void Present() { - try - { - var presentInterval = GraphicsDevice.Tags.Get(ForcedPresentInterval) ?? PresentInterval; + var presentInterval = GraphicsDevice.Tags.Get(ForcedPresentInterval) ?? PresentInterval; + + // From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays + // DXGI_PRESENT_ALLOW_TEARING can only be used with sync interval 0. It is recommended to always pass this + // tearing flag when using sync interval 0 if CheckFeatureSupport reports that tearing is supported and the + // app is in a windowed mode - including border-less fullscreen mode. - // From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays - // DXGI_PRESENT_ALLOW_TEARING can only be used with sync interval 0. It is recommended to always pass this - // tearing flag when using sync interval 0 if CheckFeatureSupport reports that tearing is supported and the - // app is in a windowed mode - including border-less fullscreen mode. - var presentFlags = useFlipModel && tearingSupport && presentInterval == PresentInterval.Immediate && !Description.IsFullScreen - ? PresentFlags.AllowTearing - : PresentFlags.None; + var presentFlags = useFlipModel && tearingSupport && presentInterval == PresentInterval.Immediate && !Description.IsFullScreen + ? DXGI.PresentAllowTearing + : 0; - swapChain.Present((int)presentInterval, presentFlags); -#if STRIDE_GRAPHICS_API_DIRECT3D12 - // Manually swap back buffer - backBuffer.NativeResource.Dispose(); - backBuffer.InitializeFromImpl(swapChain.GetBackBuffer((++bufferSwapIndex) % bufferCount), Description.BackBufferFormat.IsSRgb()); -#endif - } - catch (SharpDXException sharpDxException) + HResult result = swapChain->Present((uint) presentInterval, presentFlags); + + if (result.IsFailure) { var deviceStatus = GraphicsDevice.GraphicsDeviceStatus; - throw new GraphicsException($"Unexpected error on Present (device status: {deviceStatus})", sharpDxException, deviceStatus); + + var exception = Marshal.GetExceptionForHR(result); + throw new GraphicsDeviceException($"Unexpected error on Present (device status: {deviceStatus})", exception, deviceStatus); } + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + // Manually swap the Back-Buffers + backBuffer.NativeResource.Release(); + + bufferSwapIndex = (uint)((++bufferSwapIndex) % bufferCount); + var nextBackBuffer = GetBackBuffer(bufferSwapIndex); + + backBuffer.InitializeFromImpl(nextBackBuffer, Description.BackBufferFormat.IsSRgb()); +#endif } + /// protected override void OnNameChanged() { base.OnNameChanged(); - if (Name != null && GraphicsDevice != null && GraphicsDevice.IsDebugMode && swapChain != null) + + if (GraphicsDevice.IsDebugMode is true && Name is not null && swapChain is not null) { - swapChain.DebugName = Name; + ToComPtr(swapChain).SetDebugName(Name); } } + /// protected internal override void OnDestroyed() { - // Manually update back buffer texture + // Manually update Back-Buffer Texture backBuffer.OnDestroyed(); backBuffer.LifetimeState = GraphicsResourceLifetimeState.Destroyed; - swapChain.Dispose(); - swapChain = null; + SafeRelease(ref swapChain); base.OnDestroyed(); } + /// public override void OnRecreated() { base.OnRecreated(); - // Recreate swap chain + // Recreate the Swap-Chain swapChain = CreateSwapChain(); - // Get newly created native texture - var backBufferTexture = swapChain.GetBackBuffer(0); + // Get the newly created native Texture + var backBufferTexture = GetBackBuffer(); - // Put it in our back buffer texture + // Put it in our Back-Buffer Texture // TODO: Update new size + // TODO: Size is already updated in InitializeFromImpl with the new TextureDescription, isn't it? backBuffer.InitializeFromImpl(backBufferTexture, Description.BackBufferFormat.IsSRgb()); backBuffer.LifetimeState = GraphicsResourceLifetimeState.Active; } + /// protected override void ResizeBackBuffer(int width, int height, PixelFormat format) { - // Manually update back buffer texture + HResult result; + + // Manually update the Back-Buffer Texture backBuffer.OnDestroyed(); - // Manually update all children textures - var fastList = DestroyChildrenTextures(backBuffer); + // Manually update all children Textures (Views) + var childrenTextures = DestroyChildrenTextures(backBuffer); #if STRIDE_PLATFORM_UWP - var swapChainPanel = Description.DeviceWindowHandle.NativeWindow as Windows.UI.Xaml.Controls.SwapChainPanel; - if (swapChainPanel != null) + if (Description.DeviceWindowHandle.NativeWindow is Windows.UI.Xaml.Controls.SwapChainPanel swapChainPanel) { - var swapChain2 = swapChain.QueryInterface(); - if (swapChain2 != null) + IDXGISwapChain2* swapChain2; + result = swapChain->QueryInterface(SilkMarshal.GuidPtrOf(), (void**)&swapChain2); + + if (result.IsSuccess && swapChain2 is not null) { - swapChain2.MatrixTransform = new RawMatrix3x2 { M11 = 1f / swapChainPanel.CompositionScaleX, M22 = 1f / swapChainPanel.CompositionScaleY }; - swapChain2.Dispose(); + Matrix3X2F transform = new() + { + DXGI11 = 1f / swapChainPanel.CompositionScaleX, + DXGI22 = 1f / swapChainPanel.CompositionScaleY + }; + + swapChain2->SetMatrixTransform(ref transform); + swapChain2->Release(); } } #endif @@ -286,76 +448,94 @@ protected override void ResizeBackBuffer(int width, int height, PixelFormat form format = ToSupportedFlipModelFormat(format); // See CreateSwapChainForDesktop // If format is same as before, using Unknown (None) will keep the current - // We do that because on Win10/RT, actual format might be the non-srgb one and we don't want to switch to srgb one by mistake (or need #ifdef) - // Eideren: the comment above isn't very clear, I think they mean that we don't want to swap to srgb because it'll crash with flip model + // We do that because on Win10/RT, actual format might be the non-sRGB one and we don't want to switch to sRGB one by mistake (or need #ifdef) + // Eideren: the comment above isn't very clear, I think they mean that we don't want to swap to sRGB because it'll crash with flip model // I've added the flip model check above because the previous logic wasn't enough, see issue #1770 // Testing against swapChain format instead of the backbuffer as they may not match. - if ((DXGI_Format)format == swapChain.Description.ModeDescription.Format) + + SkipInit(out SwapChainDesc swapChainDesc); + result = swapChain->GetDesc(ref swapChainDesc); + + if (result.IsFailure) + result.Throw(); + + if ((Format) format == swapChainDesc.BufferDesc.Format) format = PixelFormat.None; - swapChain.ResizeBuffers(bufferCount, width, height, (DXGI_Format)format, GetSwapChainFlags()); + result = swapChain->ResizeBuffers((uint) bufferCount, (uint) width, (uint) height, (Format) format, (uint) GetSwapChainFlags()); + + if (result.IsFailure) + result.Throw(); - // Get newly created native texture - var backBufferTexture = swapChain.GetBackBuffer(0); + // Get the newly created native Texture + var backBufferTexture = GetBackBuffer(); - // Put it in our back buffer texture + // Put it in our Back-Buffer Texture backBuffer.InitializeFromImpl(backBufferTexture, Description.BackBufferFormat.IsSRgb()); - foreach (var texture in fastList) + foreach (var childTexture in childrenTextures) { - texture.InitializeFrom(backBuffer, texture.ViewDescription); + childTexture.InitializeFrom(parentTexture: backBuffer, in childTexture.ViewDescription); } } + /// protected override void ResizeDepthStencilBuffer(int width, int height, PixelFormat format) { - var newTextureDescription = DepthStencilBuffer.Description; - newTextureDescription.Width = width; - newTextureDescription.Height = height; + var newTextureDescription = DepthStencilBuffer.Description with + { + Width = width, + Height = height + }; - // Manually update the texture + // Manually update the Depth-Stencil Buffer DepthStencilBuffer.OnDestroyed(); - // Manually update all children textures - var fastList = DestroyChildrenTextures(DepthStencilBuffer); + // Manually update all children Textures (Views) + var childrenTextures = DestroyChildrenTextures(DepthStencilBuffer); - // Put it in our back buffer texture + // Put it in our Depth-Stencil Buffer DepthStencilBuffer.InitializeFrom(newTextureDescription); - foreach (var texture in fastList) + foreach (var childTexture in childrenTextures) { - texture.InitializeFrom(DepthStencilBuffer, texture.ViewDescription); + childTexture.InitializeFrom(parentTexture: DepthStencilBuffer, in childTexture.ViewDescription); } } /// - /// Calls for all children of the specified texture + /// Calls for all children of the specified Texture. /// - /// Specified parent texture - /// A list of the children textures which were destroyed - private FastList DestroyChildrenTextures(Texture parentTexture) + /// The parent Texture whose children are to be destroyed. + /// A list of the children Textures which were destroyed. + private List DestroyChildrenTextures(Texture parentTexture) { - var fastList = new FastList(); + var childrenTextures = new List(); + foreach (var resource in GraphicsDevice.Resources) { - var texture = resource as Texture; - if (texture != null && texture.ParentTexture == parentTexture) + if (resource is Texture texture && texture.ParentTexture == parentTexture) { texture.OnDestroyed(); - fastList.Add(texture); + childrenTextures.Add(texture); } } - return fastList; + return childrenTextures; } - private SwapChain CreateSwapChain() + /// + /// Creates or reinitializes the Swap-Chain with the current configuration. + /// + /// The new or recreated . + /// + /// is or + /// the is invalid or zero. + /// + private IDXGISwapChain* CreateSwapChain() { - // Check for Window Handle parameter - if (Description.DeviceWindowHandle == null) - { - throw new ArgumentException("DeviceWindowHandle cannot be null"); - } + if (Description.DeviceWindowHandle is null) + throw new InvalidOperationException("DeviceWindowHandle cannot be null"); #if STRIDE_PLATFORM_UWP return CreateSwapChainForUWP(); @@ -365,84 +545,127 @@ private SwapChain CreateSwapChain() } #if STRIDE_PLATFORM_UWP - private SwapChain CreateSwapChainForUWP() + /// + /// Creates or reinitializes the Swap-Chain on the Universal Windows Platform (UWP). + /// + /// The new or recreated . + private IDXGISwapChain* CreateSwapChainForUWP() { bufferCount = 2; - var description = new SwapChainDescription1 + + var description = new SwapChainDesc1 { // Automatic sizing - Width = Description.BackBufferWidth, - Height = Description.BackBufferHeight, - Format = (DXGI_Format)Description.BackBufferFormat.ToNonSRgb(), - Stereo = false, - SampleDescription = new SharpDX.DXGI.SampleDescription((int)Description.MultisampleCount, 0), - Usage = Usage.BackBuffer | Usage.RenderTargetOutput, - // Use two buffers to enable flip effect. - BufferCount = bufferCount, - Scaling = SharpDX.DXGI.Scaling.Stretch, - SwapEffect = SharpDX.DXGI.SwapEffect.FlipSequential, + Width = (uint) Description.BackBufferWidth, + Height = (uint) Description.BackBufferHeight, + Format = (Format) Description.BackBufferFormat.ToNonSRgb(), + Stereo = 0, + SampleDesc = new SampleDesc(count: (uint) Description.MultisampleCount, quality: 0), + BufferUsage = USAGE_BACKBUFFER | USAGE_RENDER_TARGET_OUTPUT, + BufferCount = (uint) bufferCount, // Use two buffers to enable flip effect + Scaling = Scaling.ScalingStretch, + SwapEffect = SwapEffect.FlipSequential }; - SwapChain swapChain = null; + IDXGISwapChain1* swapChain = null; + var deviceAsIUnknown = (IUnknown*) GraphicsDevice.NativeDevice; + switch (Description.DeviceWindowHandle.Context) { case Games.AppContextType.UWPXaml: { - var nativePanel = ComObject.As(Description.DeviceWindowHandle.NativeWindow); + var hWindow = Description.DeviceWindowHandle.Handle; + var nativePanel = GetNativePanelFromHandle(Description.DeviceWindowHandle); - // Creates the swap chain for XAML composition - swapChain = new SwapChain1(GraphicsAdapterFactory.NativeFactory, GraphicsDevice.NativeDevice, ref description); + var swapChainFactory = (IDXGIFactory2*) GraphicsAdapterFactory.NativeFactory; + + // Creates the swapchain for XAML composition + HResult result = swapChainFactory->CreateSwapChainForHwnd(deviceAsIUnknown, hWindow, &description, pFullscreenDesc: null, pRestrictToOutput: null, &swapChain); // Associate the SwapChainPanel with the swap chain nativePanel.SwapChain = swapChain; break; - } - case Games.AppContextType.UWPCoreWindow: - { - using (var dxgiDevice = GraphicsDevice.NativeDevice.QueryInterface()) + /// + /// Gets an from the handle of the window. + /// + static ISwapChainPanelNative GetNativePanelFromHandle(WindowHandle windowHandle) { - // Ensure that DXGI does not queue more than one frame at a time. This both reduces - // latency and ensures that the application will only render after each VSync, minimizing - // power consumption. - dxgiDevice.MaximumFrameLatency = 1; - - // Next, get the parent factory from the DXGI Device. - using (var dxgiAdapter = dxgiDevice.Adapter) - using (var dxgiFactory = dxgiAdapter.GetParent()) - // Finally, create the swap chain. - using (var coreWindow = new SharpDX.ComObject(Description.DeviceWindowHandle.NativeWindow)) - { - swapChain = new SharpDX.DXGI.SwapChain1(dxgiFactory - , GraphicsDevice.NativeDevice, coreWindow, ref description); - } + var nativeWindow = windowHandle.NativeWindow; + var comPtr = Marshal.GetIUnknownForObject(nativeWindow); + + HResult result = Marshal.QueryInterface(comPtr, ref SilkMarshal.GuidOf(), out var ptrPanel); + + if (result.IsFailure || ptrPanel == IntPtr.Zero) + result.Throw(); + + return new ISwapChainPanelNative(ptrPanel); } + } + case Games.AppContextType.UWPCoreWindow: + { + IDXGIDevice2* dxgiDevice; + HResult result = GraphicsDevice.NativeDevice->QueryInterface(SilkMarshal.GuidPtrOf(), (void**) &dxgiDevice); + + if (result.IsFailure) + result.Throw(); + + // Ensure that DXGI does not queue more than one frame at a time. This both reduces + // latency and ensures that the application will only render after each VSync, minimizing + // power consumption. + dxgiDevice->SetMaximumFrameLatency(1); + + // Next, get the parent factory from the DXGI Device + IDXGIAdapter* dxgiAdapter; + dxgiDevice->GetAdapter(&dxgiAdapter); + IDXGIFactory2* dxgiFactory; + result = dxgiAdapter->GetParent(SilkMarshal.GuidPtrOf(), (void**) &dxgiFactory); + + if (result.IsFailure) + result.Throw(); + + // Finally, create the swapchain + var coreWindow = (IUnknown*) Marshal.GetIUnknownForObject(Description.DeviceWindowHandle.NativeWindow); + + result = dxgiFactory->CreateSwapChainForCoreWindow(deviceAsIUnknown, coreWindow, &description, pRestrictToOutput: null, &swapChain); + + if (result.IsFailure) + result.Throw(); + + if (coreWindow != null) coreWindow->Release(); + if (dxgiFactory != null) dxgiFactory->Release(); + if (dxgiAdapter != null) dxgiAdapter->Release(); + if (dxgiDevice != null) dxgiDevice->Release(); break; } default: - throw new NotSupportedException(string.Format("Window context [{0}] not supported while creating SwapChain", Description.DeviceWindowHandle.Context)); + throw new NotSupportedException($"Window context [{Description.DeviceWindowHandle.Context}] not supported while creating SwapChain"); } - return swapChain; + return (IDXGISwapChain*) swapChain; } #else /// - /// Create the SwapChain on Windows. + /// Creates or reinitializes the Swap-Chain on the desktop Windows platform. /// - /// - private SwapChain CreateSwapChainForWindows() + /// The new or recreated . + /// + /// is or + /// the is invalid or zero. + /// + private IDXGISwapChain* CreateSwapChainForWindows() { var hwndPtr = Description.DeviceWindowHandle.Handle; - if (hwndPtr != IntPtr.Zero) + if (hwndPtr != 0) { return CreateSwapChainForDesktop(hwndPtr); } throw new InvalidOperationException($"The {nameof(WindowHandle)}.{nameof(WindowHandle.Handle)} must not be zero."); } - private SwapChain CreateSwapChainForDesktop(IntPtr handle) + private IDXGISwapChain* CreateSwapChainForDesktop(IntPtr handle) { #if STRIDE_GRAPHICS_API_DIRECT3D12 useFlipModel = true; @@ -460,84 +683,114 @@ private SwapChain CreateSwapChainForDesktop(IntPtr handle) bufferCount = 2; } - var description = new SwapChainDescription + var description = new SwapChainDesc { - ModeDescription = new ModeDescription(Description.BackBufferWidth, Description.BackBufferHeight, Description.RefreshRate.ToSharpDX(), (DXGI_Format)swapchainFormat), - BufferCount = bufferCount, // TODO: Do we really need this to be configurable by the user? - OutputHandle = handle, - SampleDescription = new SampleDescription((int)Description.MultisampleCount, 0), + BufferDesc = new ModeDesc + { + Width = (uint) Description.BackBufferWidth, + Height = (uint) Description.BackBufferHeight, + RefreshRate = Description.RefreshRate.ToSilk(), + Format = (Format) swapchainFormat + }, + BufferCount = (uint) bufferCount, // TODO: Do we really need this to be configurable by the user? + OutputWindow = handle, + SampleDesc = new SampleDesc(count: (uint) Description.MultisampleCount, quality: 0), SwapEffect = useFlipModel ? SwapEffect.FlipDiscard : SwapEffect.Discard, - Usage = Usage.BackBuffer | Usage.RenderTargetOutput, - IsWindowed = true, - Flags = GetSwapChainFlags(), + BufferUsage = DXGI.UsageBackBuffer | DXGI.UsageRenderTargetOutput, + Windowed = 1, + Flags = (uint) GetSwapChainFlags() }; + ComPtr newSwapChain = default; + var nativeFactory = GraphicsAdapterFactory.NativeFactory; + #if STRIDE_GRAPHICS_API_DIRECT3D11 - var newSwapChain = new SwapChain(GraphicsAdapterFactory.NativeFactory, GraphicsDevice.NativeDevice, description); + HResult result = nativeFactory.CreateSwapChain(GraphicsDevice.NativeDevice.AsIUnknown(), ref description, ref newSwapChain); #elif STRIDE_GRAPHICS_API_DIRECT3D12 - var newSwapChain = new SwapChain(GraphicsAdapterFactory.NativeFactory, GraphicsDevice.NativeCommandQueue, description); + HResult result = nativeFactory.CreateSwapChain(GraphicsDevice.NativeCommandQueue.AsIUnknown(), ref description, ref newSwapChain); #endif - var swapChain3 = newSwapChain.QueryInterface(); - if (swapChain3 != null) + if (result.IsFailure) + result.Throw(); + + // We need a IDXGISwapChain3 to enable output color space setting to support HDR outputs + result = newSwapChain.QueryInterface(out ComPtr swapChain3); + + if (result.IsSuccess) { - swapChain3.ColorSpace1 = (SharpDX.DXGI.ColorSpaceType)Description.OutputColorSpace; - swapChain3.Dispose(); + swapChain3.SetColorSpace1((Silk.NET.DXGI.ColorSpaceType) Description.OutputColorSpace); + swapChain3.Release(); } - //prevent normal alt-tab - GraphicsAdapterFactory.NativeFactory.MakeWindowAssociation(handle, WindowAssociationFlags.IgnoreAltEnter); + // Prevent switching between windowed and fullscreen modes by pressing Alt+ENTER + nativeFactory.MakeWindowAssociation(handle, DxgiConstants.WindowAssociation_NoAltEnter); if (Description.IsFullScreen) { // Before fullscreen switch - newSwapChain.ResizeTarget(ref description.ModeDescription); + newSwapChain.ResizeTarget(in description.BufferDesc); - // Switch to full screen - newSwapChain.IsFullScreen = true; + // Switch to fullscreen + newSwapChain.SetFullscreenState(Fullscreen: 1, ref NullRef()); - // This is really important to call ResizeBuffers AFTER switching to IsFullScreen - newSwapChain.ResizeBuffers(bufferCount, Description.BackBufferWidth, Description.BackBufferHeight, newFormat: default, description.Flags); + // It's really important to call ResizeBuffers AFTER switching to IsFullScreen + newSwapChain.ResizeBuffers((uint) bufferCount, + (uint) Description.BackBufferWidth, + (uint) Description.BackBufferHeight, + NewFormat: default, + description.Flags); } return newSwapChain; } - private SwapChainFlags GetSwapChainFlags() + /// + /// Returns the appropriate flags for the Swap-Chain given the configuration and system capabilities. + /// + /// The most appropriate s. + private SwapChainFlag GetSwapChainFlags() { - var flags = SwapChainFlags.None; + SwapChainFlag flags = 0; + if (Description.IsFullScreen) - flags |= SwapChainFlags.AllowModeSwitch; + flags |= SwapChainFlag.AllowModeSwitch; // From https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/variable-refresh-rate-displays // It is recommended to always use the tearing flag when it is supported. if (useFlipModel && tearingSupport) - flags |= SwapChainFlags.AllowTearing; + flags |= SwapChainFlag.AllowTearing; return flags; } #endif /// - /// Flip model does not support certain format, this method ensures it is in a supported format. - /// https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-flip-model - /// For HDR see: https://learn.microsoft.com/en-us/windows/win32/direct3darticles/high-dynamic-range + /// Ensures the provided pixel format is supported for a flip model Swap-Chain, as + /// certain formats are not supported when using the flip model. /// + /// The pixel format to convert to a format supported for a flip model Swap-Chain. /// - /// Will throw if the given format does not have a direct analog supported by the flip model + /// The given does not have a direct analog supported by + /// the flip model. /// - static PixelFormat ToSupportedFlipModelFormat(PixelFormat pixelFormat) + /// + /// To learn more about the DXGI flip model, see . + ///
+ /// For more information on HDR output, see . + ///
+ private static PixelFormat ToSupportedFlipModelFormat(PixelFormat pixelFormat) { var nonSRgb = pixelFormat.ToNonSRgb(); - switch (nonSRgb) + return nonSRgb switch { - case PixelFormat.R16G16B16A16_Float: // scRGB HDR, should use PresenterColorSpace.RgbFullG10NoneP709, gets converted by windows to display color space - case PixelFormat.R10G10B10A2_UNorm: // HDR10/BT.2100 HDR, should use PresenterColorSpace.RgbFullG2084NoneP2020, directly sent to display - case PixelFormat.B8G8R8A8_UNorm: - case PixelFormat.R8G8B8A8_UNorm: - return nonSRgb; - default: throw new ArgumentException($"Format '{pixelFormat}' is not supported when using flip swap", nameof(pixelFormat)); - } + PixelFormat.R16G16B16A16_Float or // scRGB HDR, should use PresenterColorSpace.RgbFullG10NoneP709, gets converted by Windows to display color space + PixelFormat.R10G10B10A2_UNorm or // HDR10 / BT.2100 HDR, should use PresenterColorSpace.RgbFullG2084NoneP2020, directly sent to display + PixelFormat.B8G8R8A8_UNorm or + PixelFormat.R8G8B8A8_UNorm => nonSRgb, + + _ => throw new ArgumentException($"Format '{pixelFormat}' is not supported when using a flip model swapchain", nameof(pixelFormat)) + }; } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D/Texture.Direct3D.cs b/sources/engine/Stride.Graphics/Direct3D/Texture.Direct3D.cs index c2d06c46fc..de4bfdabd3 100644 --- a/sources/engine/Stride.Graphics/Direct3D/Texture.Direct3D.cs +++ b/sources/engine/Stride.Graphics/Direct3D/Texture.Direct3D.cs @@ -2,18 +2,19 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_DIRECT3D11 + // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,156 +22,287 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + using System; -using System.Runtime.CompilerServices; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using Stride.Core; +using System.Diagnostics; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D11; + +using Stride.Core.UnsafeExtensions; + +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Graphics.ComPtrHelpers; namespace Stride.Graphics { - public partial class Texture + public unsafe partial class Texture { - private RenderTargetView renderTargetView; - private DepthStencilView depthStencilView; - internal bool HasStencil; - - private int TexturePixelSize => Format.SizeInBytes(); private const int TextureRowPitchAlignment = 1; private const int TextureSubresourceAlignment = 1; - internal DepthStencilView NativeDepthStencilView + private int TexturePixelSize => Format.SizeInBytes(); + + private ID3D11RenderTargetView* renderTargetView; + private ID3D11DepthStencilView* depthStencilView; + + /// + /// A value indicating whether the Texture is a Depth-Stencil Buffer with a stencil component. + /// + internal bool HasStencil; + + /// + /// Gets the internal Direct3D 11 Depth-Stencil View attached to this Texture resource. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeDepthStencilView { - get - { - return depthStencilView; - } + get => ToComPtr(depthStencilView); + private set { - depthStencilView = value; - if (IsDebugMode && depthStencilView != null) + // Private use: We don't handle AddRef() or Release() here because the ComPtr is strictly assigned + // and disposed by us. + // If in the future we need this to be public/internal/protected and it can be assigned from other + // places, this should manage the ComPtr lifetime appropriately. + + if (value.Handle == depthStencilView) + return; + + depthStencilView = value.Handle; + + if (IsDebugMode && depthStencilView is not null) { - depthStencilView.DebugName = string.Format("{0} DSV", Name); + NativeDepthStencilView.SetDebugName(Name is null ? null : $"{Name} DSV"); } } } /// - /// Gets the RenderTargetView attached to this GraphicsResource. - /// Note that only Texture, Texture3D, RenderTarget2D, RenderTarget3D, DepthStencil are using this ShaderResourceView + /// Gets the internal Direct3D 11 Render Target View attached to this Texture resource. /// - /// The device child. - internal RenderTargetView NativeRenderTargetView + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeRenderTargetView { - get - { - return renderTargetView; - } + get => ToComPtr(renderTargetView); + private set { - renderTargetView = value; - if (IsDebugMode && renderTargetView != null) + // Private use: We don't handle AddRef() or Release() here because the ComPtr is strictly assigned + // and disposed by us. + // If in the future we need this to be public/internal/protected and it can be assigned from other + // places, this should manage the ComPtr lifetime appropriately. + + if (value.Handle == renderTargetView) + return; + + renderTargetView = value.Handle; + + if (IsDebugMode && renderTargetView is not null) { - renderTargetView.DebugName = string.Format("{0} RTV", Name); + NativeRenderTargetView.SetDebugName(Name is null ? null : $"{Name} RTV"); } } } + + /// + /// Recreates the Texture from the specified data. + /// + /// + /// An array of structures pointing to the data for all the subresources to + /// initialize for the Texture. + /// public void Recreate(DataBox[] dataBoxes = null) { InitializeFromImpl(dataBoxes); } + /// + /// Checks if the specified supports binding a Depth-Stencil buffer + /// as a read-only Render Target. + /// + /// The graphics device. + /// + /// if the supports binding a Depth-Stencil buffer as a read-only Render Target View; + /// otherwise. + /// + /// public static bool IsDepthStencilReadOnlySupported(GraphicsDevice device) { - return device.Features.CurrentProfile >= GraphicsProfile.Level_11_0; + return device.Features.HasDepthAsReadOnlyRT; } /// - /// Initializes from a native SharpDX.Texture + /// Initializes the from a native . /// - /// The texture. - internal Texture InitializeFromImpl(Texture2D texture, bool isSrgb) + /// + /// The underlying native Texture. + /// Its reference count will be incremented by this method. + /// + /// + /// to treat the Texture's pixel format as if it were an sRGB format, even if it was created as non-sRGB; + /// to respect the Texture's original pixel format. + /// + /// This Texture after being initialized. + internal Texture InitializeFromImpl(ID3D11Texture2D* texture, bool treatAsSrgb) { - NativeDeviceChild = texture; - var newTextureDescription = ConvertFromNativeDescription(texture.Description); + var ptrTexture = ToComPtr(texture); + NativeDeviceChild = ptrTexture.AsDeviceChild(); + + SkipInit(out Texture2DDesc textureDesc); + ptrTexture.GetDesc(ref textureDesc); + + var newTextureDescription = ConvertFromNativeDescription(textureDesc); - // We might have created the swapchain as a non-srgb format (esp on Win10&RT) but we want it to behave like it is (esp. for the view and render target) - if(isSrgb) + // We might have created the swapchain as a non-sRGB format (specially on Win 10 & RT) but we want it to + // behave like it is (specially for the View and Render Target) + if (treatAsSrgb) newTextureDescription.Format = newTextureDescription.Format.ToSRgb(); + if (GraphicsDevice.IsDebugMode) + { + Name += " " + GetDebugName(in newTextureDescription); + } + return InitializeFrom(newTextureDescription); } - internal Texture InitializeFromImpl(ShaderResourceView srv) + /// + /// Initializes the from a native + /// as a Texture View. + /// + /// The underlying native Shader Resource View. + /// This Texture after being initialized. + /// + /// Creating a Texture from the specified Shader Resource View is not implemented. The is not supported. + /// + internal Texture InitializeFromImpl(ID3D11ShaderResourceView* srv) { - if (srv.Description.Dimension == ShaderResourceViewDimension.Texture2D) + SkipInit(out ShaderResourceViewDesc srvDescription); + srv->GetDesc(ref srvDescription); + + if (srvDescription.ViewDimension == D3DSrvDimension.D3D101SrvDimensionTexture2D) { - (srv as SharpDX.IUnknown)?.AddReference(); - NativeShaderResourceView = srv; - var dxTexture2D = new Texture2D(srv.Resource.NativePointer); - NativeDeviceChild = dxTexture2D; + NativeShaderResourceView = srv; // Implicit conversion to ComPtr calls AddRef() - var newTextureDescription = ConvertFromNativeDescription(dxTexture2D.Description); - var newTextureViewDescription = new TextureViewDescription(); - newTextureViewDescription.Format = (PixelFormat)srv.Description.Format; - newTextureViewDescription.Flags = newTextureDescription.Flags; + ComPtr resource = default; + srv->GetResource(ref resource); - return InitializeFrom(null, newTextureDescription, newTextureViewDescription, null); + HResult result = resource.QueryInterface(out ComPtr texture); + + if (result.IsFailure) + result.Throw(); + + resource.Release(); + + SetNativeDeviceChild(texture.AsDeviceChild()); + + SkipInit(out Texture2DDesc textureDesc); + texture.GetDesc(ref textureDesc); + + var newTextureDescription = ConvertFromNativeDescription(textureDesc); + var newTextureViewDescription = new TextureViewDescription + { + Format = (PixelFormat) srvDescription.Format, + Flags = newTextureDescription.Flags + }; + + return InitializeFrom(parentTexture: null, in newTextureDescription, in newTextureViewDescription, textureDatas: null); } else { - throw new NotImplementedException("Creating a texture from a SRV with dimension " + srv.Description.Dimension + " is not implemented"); + // TODO: Implement other view types? + throw new NotImplementedException($"Creating a texture from a SRV with dimension {srvDescription.ViewDimension} is not implemented"); } } - internal void SwapInternal(Texture other) + /// + /// Swaps the Texture's internal data with another Texture. + /// + /// The other Texture. + internal partial void SwapInternal(Texture other) { - var deviceChild = NativeDeviceChild; - NativeDeviceChild = other.NativeDeviceChild; - other.NativeDeviceChild = deviceChild; + (other.NativeDeviceChild, NativeDeviceChild) = (NativeDeviceChild, other.NativeDeviceChild); + (other.NativeShaderResourceView, NativeShaderResourceView) = (NativeShaderResourceView, other.NativeShaderResourceView); + (other.NativeUnorderedAccessView, NativeUnorderedAccessView) = (NativeUnorderedAccessView, other.NativeUnorderedAccessView); - var srv = NativeShaderResourceView; - NativeShaderResourceView = other.NativeShaderResourceView; - other.NativeShaderResourceView = srv; + var rtv = renderTargetView; + renderTargetView = other.renderTargetView; + other.renderTargetView = rtv; - var uav = NativeUnorderedAccessView; - NativeUnorderedAccessView = other.NativeUnorderedAccessView; - other.NativeUnorderedAccessView = uav; + var dsv = depthStencilView; + depthStencilView = other.depthStencilView; + other.depthStencilView = dsv; - Utilities.Swap(ref renderTargetView, ref other.renderTargetView); - Utilities.Swap(ref depthStencilView, ref other.depthStencilView); - Utilities.Swap(ref HasStencil, ref other.HasStencil); + (HasStencil, other.HasStencil) = (other.HasStencil, HasStencil); } - private void InitializeFromImpl(DataBox[] dataBoxes = null) + /// + /// Initializes the Texture from the specified data. + /// + /// + /// An array of structures pointing to the data for all the subresources to + /// initialize for the Texture. + /// + /// Invalid Texture share options () specified. + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + /// is not supported for Render Targets. + /// Multi-sampling is not supported for Unordered Access Views. + /// The Depth-Stencil format specified is not supported. + /// Cannot create a read-only Depth-Stencil View because the device does not support it. + /// + /// For a lower than , creating Shader Resource Views + /// for Depth-Stencil Textures is not supported, + /// + private partial void InitializeFromImpl(DataBox[] dataBoxes) { - if (ParentTexture != null) + // If it is a View, we point to the parent resource + if (ParentTexture is not null) { - NativeDeviceChild = ParentTexture.NativeDeviceChild; + SetNativeDeviceChild(ParentTexture.NativeDeviceChild); } - if (NativeDeviceChild == null) + if (NativeDeviceChild.IsNull()) { switch (Dimension) { case TextureDimension.Texture1D: - NativeDeviceChild = new Texture1D(GraphicsDevice.NativeDevice, ConvertToNativeDescription1D(), ConvertDataBoxes(dataBoxes)); + { + ComPtr texture1D = CreateTexture1D(dataBoxes); + SetNativeDeviceChild(texture1D.AsDeviceChild()); break; + } case TextureDimension.Texture2D: case TextureDimension.TextureCube: - NativeDeviceChild = new Texture2D(GraphicsDevice.NativeDevice, ConvertToNativeDescription2D(), ConvertDataBoxes(dataBoxes)); + { + ComPtr texture2D = CreateTexture2D(dataBoxes); + SetNativeDeviceChild(texture2D.AsDeviceChild()); break; + } case TextureDimension.Texture3D: - NativeDeviceChild = new Texture3D(GraphicsDevice.NativeDevice, ConvertToNativeDescription3D(), ConvertDataBoxes(dataBoxes)); + { + ComPtr texture3D = CreateTexture3D(dataBoxes); + SetNativeDeviceChild(texture3D.AsDeviceChild()); break; + } } GraphicsDevice.RegisterTextureMemoryUsage(SizeInBytes); } - if (NativeShaderResourceView == null) + if (NativeShaderResourceView.IsNull()) NativeShaderResourceView = GetShaderResourceView(ViewType, ArraySlice, MipLevel); + NativeUnorderedAccessView = GetUnorderedAccessView(ViewType, ArraySlice, MipLevel); NativeRenderTargetView = GetRenderTargetView(ViewType, ArraySlice, MipLevel); NativeDepthStencilView = GetDepthStencilView(out HasStencil); @@ -180,650 +312,816 @@ private void InitializeFromImpl(DataBox[] dataBoxes = null) SharedHandle = IntPtr.Zero; } #if STRIDE_GRAPHICS_API_DIRECT3D11 - else if ((textureDescription.Options & TextureOptions.SharedNthandle) != 0) + else if (textureDescription.Options.HasFlag(TextureOptions.SharedNtHandle) || + textureDescription.Options.HasFlag(TextureOptions.SharedKeyedMutex)) { - using var sharedResource1 = NativeDeviceChild.QueryInterface(); - var uniqueName = "Stride:" + Guid.NewGuid().ToString(); - SharedHandle = sharedResource1.CreateSharedHandle(uniqueName, SharpDX.DXGI.SharedResourceFlags.Write); + HResult result = NativeDeviceChild.QueryInterface(out ComPtr sharedResource1); + + if (result.IsFailure) + result.Throw(); + + var uniqueName = $"Stride:{Guid.NewGuid()}"; + + void* sharedHandle = null; + result = sharedResource1.CreateSharedHandle(pAttributes: in NullRef(), + dwAccess: DxgiConstants.SharedAccessResourceWrite, + uniqueName, ref sharedHandle); + if (result.IsFailure) + result.Throw(); + + sharedResource1.Release(); + + SharedHandle = new(sharedHandle); SharedNtHandleName = uniqueName; } #endif - else if ((textureDescription.Options & TextureOptions.Shared) != 0) { - using var sharedResource = NativeDeviceChild.QueryInterface(); - SharedHandle = sharedResource.SharedHandle; + else if (textureDescription.Options.HasFlag(TextureOptions.Shared)) + { + HResult result = NativeDeviceChild.QueryInterface(out ComPtr sharedResource); + + if (result.IsFailure) + result.Throw(); + + void* sharedHandle = null; + result = sharedResource.GetSharedHandle(ref sharedHandle); + + if (result.IsFailure) + result.Throw(); + + sharedResource.Release(); + + SharedHandle = new(sharedHandle); } else - { - throw new ArgumentOutOfRangeException("textureDescription.Options"); + { + // Argument `textureDescription` comes from the constructor + throw new ArgumentOutOfRangeException("textureDescription.Options", "The options specified for the Texture are not valid."); + } + + // + // Creates the internal Direct3D resource for a 1D Texture. + // + ComPtr CreateTexture1D(DataBox[] initialData) + { + ComPtr texture1D = default; + + Texture1DDesc description = ConvertToNativeDescription1D(); + ReadOnlySpan initiatDataPerSubresource = ConvertDataBoxes(initialData); + + HResult result = initiatDataPerSubresource.IsEmpty + ? NativeDevice.CreateTexture1D(in description, pInitialData: null, ref texture1D) + : NativeDevice.CreateTexture1D(in description, in initiatDataPerSubresource[0], ref texture1D); + + if (result.IsFailure) + result.Throw(); + + return texture1D; + } + + // + // Creates the internal Direct3D resource for a 2D Texture. + // + ComPtr CreateTexture2D(DataBox[] initialData) + { + ComPtr texture2D = default; + + Texture2DDesc description = ConvertToNativeDescription2D(); + ReadOnlySpan initiatDataPerSubresource = ConvertDataBoxes(initialData); + + HResult result = initiatDataPerSubresource.IsEmpty + ? NativeDevice.CreateTexture2D(in description, pInitialData: null, ref texture2D) + : NativeDevice.CreateTexture2D(in description, in initiatDataPerSubresource[0], ref texture2D); + + if (result.IsFailure) + result.Throw(); + + return texture2D; + } + + // + // Creates the internal Direct3D resource for a 3D Texture. + // + ComPtr CreateTexture3D(DataBox[] initialData) + { + ComPtr texture3D = default; + + Texture3DDesc description = ConvertToNativeDescription3D(); + ReadOnlySpan initiatDataPerSubresource = ConvertDataBoxes(initialData); + + HResult result = initiatDataPerSubresource.IsEmpty + ? NativeDevice.CreateTexture3D(in description, pInitialData: null, ref texture3D) + : NativeDevice.CreateTexture3D(in description, in initiatDataPerSubresource[0], ref texture3D); + + if (result.IsFailure) + result.Throw(); + + return texture3D; } } + /// + /// + /// This method releases all the native resources associated with the Texture: + /// + /// + /// If it is a Texture, this releases the underlying native texture resource and also the associated Views. + /// + /// + /// If it is a Texture View, it releases only the resources related to the View, not the parent Texture's. + /// + /// + /// protected internal override void OnDestroyed() { - // If it was a View, do not release reference - if (ParentTexture != null) + // If it was a View, do not release reference, just forget it + if (ParentTexture is not null) { - NativeDeviceChild = null; + UnsetNativeDeviceChild(); } - else if (GraphicsDevice != null) + else { - GraphicsDevice.RegisterTextureMemoryUsage(-SizeInBytes); + GraphicsDevice?.RegisterTextureMemoryUsage(-SizeInBytes); } - ReleaseComObject(ref renderTargetView); - ReleaseComObject(ref depthStencilView); + SafeRelease(ref depthStencilView); + SafeRelease(ref renderTargetView); base.OnDestroyed(); } - private void OnRecreateImpl() + /// + /// Perform Direct3D-specific recreation of the Texture. + /// + private partial void OnRecreateImpl() { - // Dependency: wait for underlying texture to be recreated - if (ParentTexture != null && ParentTexture.LifetimeState != GraphicsResourceLifetimeState.Active) + // Dependency: Wait for the underlying Texture to be recreated + if (ParentTexture is { LifetimeState: not GraphicsResourceLifetimeState.Active }) return; - // Render Target / Depth Stencil are considered as "dynamic" - if ((Usage == GraphicsResourceUsage.Immutable - || Usage == GraphicsResourceUsage.Default) - && !IsRenderTarget && !IsDepthStencil) + // Render Target / Depth Stencil are considered as "dynamic", i.e. nothing to reinitialize + if (Usage is GraphicsResourceUsage.Immutable or GraphicsResourceUsage.Default && + !IsRenderTarget && !IsDepthStencil) return; - if (ParentTexture == null && GraphicsDevice != null) + if (ParentTexture is null) { - GraphicsDevice.RegisterTextureMemoryUsage(-SizeInBytes); + GraphicsDevice?.RegisterTextureMemoryUsage(-SizeInBytes); } InitializeFromImpl(); } /// - /// Gets a specific from this texture. + /// Gets a specific from the Texture. /// - /// Type of the view slice. - /// The texture array slice index. - /// The mip map slice index. - /// An - private ShaderResourceView GetShaderResourceView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) + /// The desired View type of the Shader Resource View. + /// The index of the Texture array or depth slice. + /// The index of the mip-level. + /// An for the Texture. + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + private ComPtr GetShaderResourceView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) { if (!IsShaderResource) return null; - int arrayCount; - int mipCount; - GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out arrayCount, out mipCount); + GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out var mipCount); - // Create the view - var srvDescription = new ShaderResourceViewDescription() { Format = ComputeShaderResourceViewFormat() }; + var srvDescription = new ShaderResourceViewDesc { Format = ComputeShaderResourceViewFormat() }; - // Initialize for texture arrays or texture cube - if (this.ArraySize > 1) + // Initialize for Texture Array or Texture Cube + if (ArraySize > 1) { - // If texture cube - if (this.ViewDimension == TextureDimension.TextureCube) + if (ViewDimension == TextureDimension.TextureCube) { - srvDescription.Dimension = ShaderResourceViewDimension.TextureCube; - srvDescription.TextureCube.MipLevels = mipCount; - srvDescription.TextureCube.MostDetailedMip = mipIndex; + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexturecube; + srvDescription.TextureCube.MipLevels = (uint) mipCount; + srvDescription.TextureCube.MostDetailedMip = (uint) mipIndex; } - else + else // Regular Texture Array { - // Else regular Texture array - // Multisample? - if (IsMultisample) + if (IsMultiSampled) { if (Dimension != TextureDimension.Texture2D) { - throw new NotSupportedException("Multisample is only supported for 2D Textures"); + throw new NotSupportedException("Multi-sampling is only supported for 2D Textures"); } - srvDescription.Dimension = ShaderResourceViewDimension.Texture2DMultisampledArray; - srvDescription.Texture2DMSArray.ArraySize = arrayCount; - srvDescription.Texture2DMSArray.FirstArraySlice = arrayOrDepthSlice; + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture2Dmsarray; + srvDescription.Texture2DMSArray.ArraySize = (uint) arrayCount; + srvDescription.Texture2DMSArray.FirstArraySlice = (uint) arrayOrDepthSlice; } - else + else // Not multi-sampled { - srvDescription.Dimension = ViewDimension == TextureDimension.Texture2D ? ShaderResourceViewDimension.Texture2DArray : ShaderResourceViewDimension.Texture1DArray; - srvDescription.Texture2DArray.ArraySize = arrayCount; - srvDescription.Texture2DArray.FirstArraySlice = arrayOrDepthSlice; - srvDescription.Texture2DArray.MipLevels = mipCount; - srvDescription.Texture2DArray.MostDetailedMip = mipIndex; + if (ViewDimension == TextureDimension.Texture2D) + { + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture2Darray; + srvDescription.Texture2DArray.ArraySize = (uint) arrayCount; + srvDescription.Texture2DArray.FirstArraySlice = (uint) arrayOrDepthSlice; + srvDescription.Texture2DArray.MipLevels = (uint) mipCount; + srvDescription.Texture2DArray.MostDetailedMip = (uint) mipIndex; + } + else if (ViewDimension != TextureDimension.Texture1D) + { + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture1Darray; + srvDescription.Texture1DArray.ArraySize = (uint) arrayCount; + srvDescription.Texture1DArray.FirstArraySlice = (uint) arrayOrDepthSlice; + srvDescription.Texture1DArray.MipLevels = (uint) mipCount; + srvDescription.Texture1DArray.MostDetailedMip = (uint) mipIndex; + } } } } - else + else // Not a Texture Array or Texture Cube { - if (IsMultisample) + if (IsMultiSampled) { if (ViewDimension != TextureDimension.Texture2D) { - throw new NotSupportedException("Multisample is only supported for 2D Textures"); + throw new NotSupportedException("Multi-sampling is only supported for 2D Textures"); } - srvDescription.Dimension = ShaderResourceViewDimension.Texture2DMultisampled; + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture2Dms; } - else + else // Not multi-sampled { switch (ViewDimension) { case TextureDimension.Texture1D: - srvDescription.Dimension = ShaderResourceViewDimension.Texture1D; + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture1D; break; + case TextureDimension.Texture2D: - srvDescription.Dimension = ShaderResourceViewDimension.Texture2D; + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture2D; break; + case TextureDimension.Texture3D: - srvDescription.Dimension = ShaderResourceViewDimension.Texture3D; + srvDescription.ViewDimension = D3DSrvDimension.D3D101SrvDimensionTexture3D; break; + case TextureDimension.TextureCube: - throw new NotSupportedException("TextureCube dimension is expecting an arraysize > 1"); + throw new NotSupportedException("Texture Cubes must have an array size greater than 1"); } - // Use srvDescription.Texture as it matches also Texture and Texture3D memory layout - srvDescription.Texture1D.MipLevels = mipCount; - srvDescription.Texture1D.MostDetailedMip = mipIndex; + // Use srvDescription.Texture1D as it matches also Texture and Texture3D memory layout + srvDescription.Texture1D.MipLevels = (uint) mipCount; + srvDescription.Texture1D.MostDetailedMip = (uint) mipIndex; } } - // Default ShaderResourceView - return new ShaderResourceView(this.GraphicsDevice.NativeDevice, NativeResource, srvDescription); + ComPtr srv = default; + HResult result = NativeDevice.CreateShaderResourceView(NativeResource, in srvDescription, ref srv); + + if (result.IsFailure) + result.Throw(); + + return srv; } /// - /// Gets a specific from this texture. + /// Gets a specific from the Texture. /// - /// Type of the view slice. - /// The texture array slice index. - /// Index of the mip. - /// An - /// ViewSlice.MipBand is not supported for render targets - private RenderTargetView GetRenderTargetView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) + /// The desired View type of the Render Target View. + /// The index of the Texture array or depth slice. + /// The index of the mip-level. + /// An for the Texture. + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + /// is not supported for Render Targets. + private ComPtr GetRenderTargetView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) { if (!IsRenderTarget) return null; if (viewType == ViewType.MipBand) - throw new NotSupportedException("ViewSlice.MipBand is not supported for render targets"); + throw new NotSupportedException($"{nameof(ViewType)}.{nameof(ViewType.MipBand)} is not supported for Render Targets"); - int arrayCount; - int mipCount; - GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out arrayCount, out mipCount); + GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out var mipCount); - // Create the render target view - var rtvDescription = new RenderTargetViewDescription() { Format = (SharpDX.DXGI.Format)ViewFormat }; + var rtvDescription = new RenderTargetViewDesc { Format = (Format) ViewFormat }; - if (this.ArraySize > 1) + // Initialize for Texture Array or Texture Cube + if (ArraySize > 1) { - if (this.MultisampleCount > MultisampleCount.None) + if (MultisampleCount > MultisampleCount.None) { if (ViewDimension != TextureDimension.Texture2D) { - throw new NotSupportedException("Multisample is only supported for 2D Textures"); + throw new NotSupportedException("Multi-sampling is only supported for 2D Textures"); } - rtvDescription.Dimension = RenderTargetViewDimension.Texture2DMultisampledArray; - rtvDescription.Texture2DMSArray.ArraySize = arrayCount; - rtvDescription.Texture2DMSArray.FirstArraySlice = arrayOrDepthSlice; + rtvDescription.ViewDimension = RtvDimension.Texture2Dmsarray; + rtvDescription.Texture2DMSArray.ArraySize = (uint) arrayCount; + rtvDescription.Texture2DMSArray.FirstArraySlice = (uint) arrayOrDepthSlice; } - else + else // Not multi-sampled { if (ViewDimension == TextureDimension.Texture3D) { - throw new NotSupportedException("Texture Array is not supported for Texture3D"); + throw new NotSupportedException("Texture Array is not supported for 3D Textures"); } - rtvDescription.Dimension = Dimension == TextureDimension.Texture2D || Dimension == TextureDimension.TextureCube ? RenderTargetViewDimension.Texture2DArray : RenderTargetViewDimension.Texture1DArray; + rtvDescription.ViewDimension = Dimension is TextureDimension.Texture2D or TextureDimension.TextureCube + ? RtvDimension.Texture2Darray + : RtvDimension.Texture1Darray; - // Use rtvDescription.Texture1DArray as it matches also Texture memory layout - rtvDescription.Texture1DArray.ArraySize = arrayCount; - rtvDescription.Texture1DArray.FirstArraySlice = arrayOrDepthSlice; - rtvDescription.Texture1DArray.MipSlice = mipIndex; + // Use rtvDescription.Texture1DArray as it matches also Texture2DArray memory layout + rtvDescription.Texture1DArray.ArraySize = (uint) arrayCount; + rtvDescription.Texture1DArray.FirstArraySlice = (uint) arrayOrDepthSlice; + rtvDescription.Texture1DArray.MipSlice = (uint) mipIndex; } } - else + else // Regular Texture Array { - if (IsMultisample) + if (IsMultiSampled) { if (ViewDimension != TextureDimension.Texture2D) { - throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); + throw new NotSupportedException("Multi-sampling is only supported for 2D Render Target Textures"); } - rtvDescription.Dimension = RenderTargetViewDimension.Texture2DMultisampled; + rtvDescription.ViewDimension = RtvDimension.Texture2Dms; } - else + else // Not multi-sampled { switch (ViewDimension) { case TextureDimension.Texture1D: - rtvDescription.Dimension = RenderTargetViewDimension.Texture1D; - rtvDescription.Texture1D.MipSlice = mipIndex; + rtvDescription.ViewDimension = RtvDimension.Texture1D; + rtvDescription.Texture1D.MipSlice = (uint) mipIndex; break; + case TextureDimension.Texture2D: - rtvDescription.Dimension = RenderTargetViewDimension.Texture2D; - rtvDescription.Texture2D.MipSlice = mipIndex; + rtvDescription.ViewDimension = RtvDimension.Texture2D; + rtvDescription.Texture2D.MipSlice = (uint) mipIndex; break; + case TextureDimension.Texture3D: - rtvDescription.Dimension = RenderTargetViewDimension.Texture3D; - rtvDescription.Texture3D.DepthSliceCount = arrayCount; - rtvDescription.Texture3D.FirstDepthSlice = arrayOrDepthSlice; - rtvDescription.Texture3D.MipSlice = mipIndex; + rtvDescription.ViewDimension = RtvDimension.Texture3D; + rtvDescription.Texture3D.WSize = (uint) arrayCount; + rtvDescription.Texture3D.FirstWSlice = (uint) arrayOrDepthSlice; + rtvDescription.Texture3D.MipSlice = (uint) mipIndex; break; + case TextureDimension.TextureCube: - throw new NotSupportedException("TextureCube dimension is expecting an arraysize > 1"); + throw new NotSupportedException("Texture Cubes must have an array size greater than 1"); } } } - return new RenderTargetView(GraphicsDevice.NativeDevice, NativeResource, rtvDescription); + ComPtr rtv = default; + HResult result = NativeDevice.CreateRenderTargetView(NativeResource, &rtvDescription, ref rtv); + + if (result.IsFailure) + result.Throw(); + + return rtv; } /// - /// Gets a specific from this texture. + /// Gets a specific from the Texture. /// - /// The desired view type on the unordered resource - /// The texture array slice index. - /// Index of the mip. - /// An - private UnorderedAccessView GetUnorderedAccessView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) + /// The desired View type of the Unordered Access View. + /// The index of the Texture array or depth slice. + /// The index of the mip-level. + /// An for the Texture. + /// Multi-sampling is not supported for Unordered Access Views. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + private ComPtr GetUnorderedAccessView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) { if (!IsUnorderedAccess) return null; - if (IsMultisample) - throw new NotSupportedException("Multisampling is not supported for unordered access views"); + if (IsMultiSampled) + throw new NotSupportedException("Multi-sampling is not supported for Unordered Access Views"); - int arrayCount; - int mipCount; - GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out arrayCount, out mipCount); + GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out _); - var uavDescription = new UnorderedAccessViewDescription - { - Format = (SharpDX.DXGI.Format)ViewFormat, - }; + var uavDescription = new UnorderedAccessViewDesc { Format = (Format) ViewFormat }; if (ArraySize > 1) { switch (ViewDimension) { case TextureDimension.Texture1D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture1DArray; + uavDescription.ViewDimension = UavDimension.Texture1Darray; break; + case TextureDimension.TextureCube: case TextureDimension.Texture2D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture2DArray; + uavDescription.ViewDimension = UavDimension.Texture2Darray; break; + case TextureDimension.Texture3D: throw new NotSupportedException("Texture 3D is not supported for Texture Arrays"); } - uavDescription.Texture1DArray.ArraySize = arrayCount; - uavDescription.Texture1DArray.FirstArraySlice = arrayOrDepthSlice; - uavDescription.Texture1DArray.MipSlice = mipIndex; + uavDescription.Texture1DArray.ArraySize = (uint)arrayCount; + uavDescription.Texture1DArray.FirstArraySlice = (uint) arrayOrDepthSlice; + uavDescription.Texture1DArray.MipSlice = (uint) mipIndex; } - else + else // Not a Texture Array or Texture Cube { switch (ViewDimension) { case TextureDimension.Texture1D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture1D; - uavDescription.Texture1D.MipSlice = mipIndex; + uavDescription.ViewDimension = UavDimension.Texture1D; + uavDescription.Texture1D.MipSlice = (uint)mipIndex; break; + case TextureDimension.Texture2D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture2D; - uavDescription.Texture2D.MipSlice = mipIndex; + uavDescription.ViewDimension = UavDimension.Texture2D; + uavDescription.Texture2D.MipSlice = (uint) mipIndex; break; + case TextureDimension.Texture3D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture3D; - uavDescription.Texture3D.FirstWSlice = arrayOrDepthSlice; - uavDescription.Texture3D.MipSlice = mipIndex; - uavDescription.Texture3D.WSize = arrayCount; + uavDescription.ViewDimension = UavDimension.Texture3D; + uavDescription.Texture3D.WSize = (uint) arrayCount; + uavDescription.Texture3D.FirstWSlice = (uint) arrayOrDepthSlice; + uavDescription.Texture3D.MipSlice = (uint) mipIndex; break; + case TextureDimension.TextureCube: - throw new NotSupportedException("TextureCube dimension is expecting an array size > 1"); + throw new NotSupportedException("Texture Cubes must have an array size greater than 1"); } } - return new UnorderedAccessView(GraphicsDevice.NativeDevice, NativeResource, uavDescription); + ComPtr uav = default; + HResult result = NativeDevice.CreateUnorderedAccessView(NativeResource, &uavDescription, ref uav); + + if (result.IsFailure) + result.Throw(); + + return uav; } - private DepthStencilView GetDepthStencilView(out bool hasStencil) + /// + /// Gets a specific from the Texture. + /// + /// + /// When the method returns, contains a value indicating if the is a Depth format + /// that also contains Stencil data. + /// + /// An for the Texture. + /// The Depth-Stencil format specified is not supported. + /// Cannot create a read-only Depth-Stencil View because the device does not support it. + private ComPtr GetDepthStencilView(out bool hasStencil) { hasStencil = false; + if (!IsDepthStencil) return null; // Check that the format is supported if (ComputeShaderResourceFormatFromDepthFormat(ViewFormat) == PixelFormat.None) - throw new NotSupportedException("Depth stencil format [{0}] not supported".ToFormat(ViewFormat)); + throw new NotSupportedException($"The Depth-Stencil format [{ViewFormat}] is not supported"); // Setup the HasStencil flag hasStencil = IsStencilFormat(ViewFormat); - // Create a Depth stencil view on this texture2D - var depthStencilViewDescription = new DepthStencilViewDescription + // Create a Depth-Stencil View + var dsvDescription = new DepthStencilViewDesc { Format = ComputeDepthViewFormatFromTextureFormat(ViewFormat), - Flags = DepthStencilViewFlags.None, + Flags = 0 }; if (ArraySize > 1) { - depthStencilViewDescription.Dimension = DepthStencilViewDimension.Texture2DArray; - depthStencilViewDescription.Texture2DArray.ArraySize = ArraySize; - depthStencilViewDescription.Texture2DArray.FirstArraySlice = 0; - depthStencilViewDescription.Texture2DArray.MipSlice = 0; + dsvDescription.ViewDimension = DsvDimension.Texture2Darray; + dsvDescription.Texture2DArray.ArraySize = (uint) ArraySize; + dsvDescription.Texture2DArray.FirstArraySlice = 0; + dsvDescription.Texture2DArray.MipSlice = 0; } - else + else // Not a Texture Array or Texture Cube { - depthStencilViewDescription.Dimension = DepthStencilViewDimension.Texture2D; - depthStencilViewDescription.Texture2D.MipSlice = 0; + dsvDescription.ViewDimension = DsvDimension.Texture2D; + dsvDescription.Texture2D.MipSlice = 0; } if (MultisampleCount > MultisampleCount.None) - depthStencilViewDescription.Dimension = DepthStencilViewDimension.Texture2DMultisampled; + dsvDescription.ViewDimension = DsvDimension.Texture2Dms; if (IsDepthStencilReadOnly) { if (!IsDepthStencilReadOnlySupported(GraphicsDevice)) - throw new NotSupportedException("Cannot instantiate ReadOnly DepthStencilBuffer. Not supported on this device."); + throw new NotSupportedException("Cannot create a read-only Depth-Stencil View. Not supported on this device"); - // Create a Depth stencil view on this texture2D - depthStencilViewDescription.Flags = DepthStencilViewFlags.ReadOnlyDepth; + dsvDescription.Flags = (uint) DsvFlag.Depth; if (HasStencil) - depthStencilViewDescription.Flags |= DepthStencilViewFlags.ReadOnlyStencil; + dsvDescription.Flags |= (uint) DsvFlag.Stencil; } - return new DepthStencilView(GraphicsDevice.NativeDevice, NativeResource, depthStencilViewDescription); + ComPtr dsv = default; + HResult result = NativeDevice.CreateDepthStencilView(NativeResource, &dsvDescription, ref dsv); + + if (result.IsFailure) + result.Throw(); + + return dsv; } - internal static BindFlags GetBindFlagsFromTextureFlags(TextureFlags flags) + /// + /// Converts the specified to Silk.NET's . + /// + /// The flags to convert. + /// The corresponding . + private static BindFlag GetBindFlagsFromTextureFlags(TextureFlags flags) { - var result = BindFlags.None; - if ((flags & TextureFlags.ShaderResource) != 0) - result |= BindFlags.ShaderResource; - if ((flags & TextureFlags.RenderTarget) != 0) - result |= BindFlags.RenderTarget; - if ((flags & TextureFlags.UnorderedAccess) != 0) - result |= BindFlags.UnorderedAccess; - if ((flags & TextureFlags.DepthStencil) != 0) - result |= BindFlags.DepthStencil; + BindFlag result = 0; + + if (flags.HasFlag(TextureFlags.ShaderResource)) result |= BindFlag.ShaderResource; + if (flags.HasFlag(TextureFlags.RenderTarget)) result |= BindFlag.RenderTarget; + if (flags.HasFlag(TextureFlags.UnorderedAccess)) result |= BindFlag.UnorderedAccess; + if (flags.HasFlag(TextureFlags.DepthStencil)) result |= BindFlag.DepthStencil; return result; } - internal static unsafe SharpDX.DataBox[] ConvertDataBoxes(DataBox[] dataBoxes) + /// + /// Converts from a span of to equivalent s. + /// + /// The es to convert. + /// A span of . + private static unsafe ReadOnlySpan ConvertDataBoxes(ReadOnlySpan dataBoxes) { - if (dataBoxes == null || dataBoxes.Length == 0) - return null; + if (dataBoxes.IsEmpty) + return default; + + // NOTE: This conversion works only IF the memory layout of DataBox matches that of SubresourceData + Debug.Assert(sizeof(DataBox) == sizeof(SubresourceData)); - // TODO: PERF: return Unsafe.As(dataBoxes); - var sharpDXDataBoxes = new SharpDX.DataBox[dataBoxes.Length]; - Unsafe.As(dataBoxes).AsSpan().CopyTo(sharpDXDataBoxes.AsSpan()); - return sharpDXDataBoxes; + return dataBoxes.Cast(); } - private bool IsFlipped() + /// + /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. + /// + /// if the Texture is flipped; otherwise. + /// + /// For Direct3D, Textures are not flipped, meaning the first row is at the top and the last row is at the bottom. + /// + private partial bool IsFlipped() { return false; } - private Texture1DDescription ConvertToNativeDescription1D() + /// + /// Returns a native from the current . + /// + /// A Silk.NET's describing the Texture. + private Texture1DDesc ConvertToNativeDescription1D() { - var desc = new Texture1DDescription() + var desc = new Texture1DDesc { - Width = textureDescription.Width, + Width = (uint) textureDescription.Width, ArraySize = 1, - BindFlags = GetBindFlagsFromTextureFlags(textureDescription.Flags), - Format = (SharpDX.DXGI.Format)textureDescription.Format, - MipLevels = textureDescription.MipLevels, - Usage = (ResourceUsage)textureDescription.Usage, - CpuAccessFlags = GetCpuAccessFlagsFromUsage(textureDescription.Usage), - OptionFlags = (ResourceOptionFlags)textureDescription.Options, + BindFlags = (uint) GetBindFlagsFromTextureFlags(textureDescription.Flags), + Format = (Format) textureDescription.Format, + MipLevels = (uint) textureDescription.MipLevelCount, + Usage = (Usage) textureDescription.Usage, + CPUAccessFlags = (uint) GetCpuAccessFlagsFromUsage(textureDescription.Usage), + MiscFlags = (uint) textureDescription.Options }; return desc; } - private SharpDX.DXGI.Format ComputeShaderResourceViewFormat() + /// + /// Returns a for a Shader Resource View that is compatible with the + /// current Texture's parameters. + /// + /// The resulting Shader Resource View format. + private Format ComputeShaderResourceViewFormat() { - // Special case for DepthStencil ShaderResourceView that are bound as Float - var viewFormat = (SharpDX.DXGI.Format)ViewFormat; - if (IsDepthStencil) - { - viewFormat = (SharpDX.DXGI.Format)ComputeShaderResourceFormatFromDepthFormat(ViewFormat); - } + // Special case for Depth-Stencil Shader Resource Views that are bound as Float + var viewFormat = IsDepthStencil + ? (Format) ComputeShaderResourceFormatFromDepthFormat(ViewFormat) + : (Format) ViewFormat; return viewFormat; } - private static TextureDescription ConvertFromNativeDescription(Texture2DDescription description) + /// + /// Returns a from a Silk.NET's . + /// + /// A describing the Texture. + private static TextureDescription ConvertFromNativeDescription(Texture2DDesc description) { - var desc = new TextureDescription() + var desc = new TextureDescription { Dimension = TextureDimension.Texture2D, - Width = description.Width, - Height = description.Height, + Width = (int) description.Width, + Height = (int) description.Height, Depth = 1, - MultisampleCount = (MultisampleCount)description.SampleDescription.Count, - Format = (PixelFormat)description.Format, - MipLevels = description.MipLevels, - Usage = (GraphicsResourceUsage)description.Usage, - ArraySize = description.ArraySize, + MultisampleCount = (MultisampleCount) description.SampleDesc.Count, + Format = (PixelFormat) description.Format, + MipLevelCount = (int) description.MipLevels, + Usage = (GraphicsResourceUsage) description.Usage, + ArraySize = (int) description.ArraySize, Flags = TextureFlags.None, Options = TextureOptions.None }; - if ((description.BindFlags & BindFlags.RenderTarget) != 0) - desc.Flags |= TextureFlags.RenderTarget; - if ((description.BindFlags & BindFlags.UnorderedAccess) != 0) - desc.Flags |= TextureFlags.UnorderedAccess; - if ((description.BindFlags & BindFlags.DepthStencil) != 0) - desc.Flags |= TextureFlags.DepthStencil; - if ((description.BindFlags & BindFlags.ShaderResource) != 0) - desc.Flags |= TextureFlags.ShaderResource; + var bindFlags = (BindFlag) description.BindFlags; + + if (bindFlags.HasFlag(BindFlag.RenderTarget)) desc.Flags |= TextureFlags.RenderTarget; + if (bindFlags.HasFlag(BindFlag.UnorderedAccess)) desc.Flags |= TextureFlags.UnorderedAccess; + if (bindFlags.HasFlag(BindFlag.DepthStencil)) desc.Flags |= TextureFlags.DepthStencil; + if (bindFlags.HasFlag(BindFlag.ShaderResource)) desc.Flags |= TextureFlags.ShaderResource; - if ((description.OptionFlags & ResourceOptionFlags.Shared) != 0) + var miscFlags = (ResourceMiscFlag) description.MiscFlags; + + if (miscFlags.HasFlag(ResourceMiscFlag.Shared)) desc.Options |= TextureOptions.Shared; + #if STRIDE_GRAPHICS_API_DIRECT3D11 - if ((description.OptionFlags & ResourceOptionFlags.SharedKeyedmutex) != 0) - desc.Options |= TextureOptions.SharedKeyedmutex; - if ((description.OptionFlags & ResourceOptionFlags.SharedNthandle) != 0) - desc.Options |= TextureOptions.SharedNthandle; + if (miscFlags.HasFlag(ResourceMiscFlag.SharedKeyedmutex)) + desc.Options |= TextureOptions.SharedKeyedMutex; + if (miscFlags.HasFlag(ResourceMiscFlag.SharedNthandle)) + desc.Options |= TextureOptions.SharedNtHandle; #endif return desc; } - private Texture2DDescription ConvertToNativeDescription2D() + /// + /// Returns a native from the current . + /// + /// A Silk.NET's describing the Texture. + /// + /// For a lower than , creating Shader Resource Views + /// for Depth-Stencil Textures is not supported, + /// + /// + /// The specified pixel format is not supported for Depth-Stencil Textures. + /// + private Texture2DDesc ConvertToNativeDescription2D() { - var format = (SharpDX.DXGI.Format)textureDescription.Format; + var format = (Format) textureDescription.Format; var flags = textureDescription.Flags; - // If the texture is going to be bound on the depth stencil, for to use TypeLess format + // If the Texture is going to be bound as Depth-Stencil, use a typeless format if (IsDepthStencil) { if (IsShaderResource && GraphicsDevice.Features.CurrentProfile < GraphicsProfile.Level_10_0) { - throw new NotSupportedException($"ShaderResourceView for DepthStencil Textures are not supported for Graphics profile < 10.0 (Current: [{GraphicsDevice.Features.CurrentProfile}])"); + throw new NotSupportedException($"Shader Resource Views for Depth-Stencil Textures are not supported for Graphics profile < 10.0 (Current: [{GraphicsDevice.Features.CurrentProfile}])"); } else { - // Determine TypeLess Format and ShaderResourceView Format + // Determine a typeless format and a Shader Resource View Format if (GraphicsDevice.Features.CurrentProfile < GraphicsProfile.Level_10_0) { - switch (textureDescription.Format) + format = textureDescription.Format switch { - case PixelFormat.D16_UNorm: - format = SharpDX.DXGI.Format.D16_UNorm; - break; - case PixelFormat.D32_Float: - format = SharpDX.DXGI.Format.D32_Float; - break; - case PixelFormat.D24_UNorm_S8_UInt: - format = SharpDX.DXGI.Format.D24_UNorm_S8_UInt; - break; - case PixelFormat.D32_Float_S8X24_UInt: - format = SharpDX.DXGI.Format.D32_Float_S8X24_UInt; - break; - default: - throw new NotSupportedException($"Unsupported DepthFormat [{textureDescription.Format}] for depth buffer"); - } + PixelFormat.D16_UNorm => Silk.NET.DXGI.Format.FormatD16Unorm, + PixelFormat.D32_Float => Silk.NET.DXGI.Format.FormatD32Float, + PixelFormat.D24_UNorm_S8_UInt => Silk.NET.DXGI.Format.FormatD24UnormS8Uint, + PixelFormat.D32_Float_S8X24_UInt => Silk.NET.DXGI.Format.FormatD32FloatS8X24Uint, + + _ => throw new NotSupportedException($"Unsupported Depth Format [{textureDescription.Format}] for the Depth-Stencil Buffer") + }; } - else + else // GraphicsProfile.Level_10_0 or higher { - switch (textureDescription.Format) + format = textureDescription.Format switch { - case PixelFormat.D16_UNorm: - format = SharpDX.DXGI.Format.R16_Typeless; - break; - case PixelFormat.D32_Float: - format = SharpDX.DXGI.Format.R32_Typeless; - break; - case PixelFormat.D24_UNorm_S8_UInt: - //format = SharpDX.DXGI.Format.D24_UNorm_S8_UInt; - format = SharpDX.DXGI.Format.R24G8_Typeless; - break; - case PixelFormat.D32_Float_S8X24_UInt: - format = SharpDX.DXGI.Format.R32G8X24_Typeless; - break; - default: - throw new NotSupportedException($"Unsupported DepthFormat [{textureDescription.Format}] for depth buffer"); - } + PixelFormat.D16_UNorm => Silk.NET.DXGI.Format.FormatR16Typeless, + PixelFormat.D32_Float => Silk.NET.DXGI.Format.FormatR32Typeless, + PixelFormat.D24_UNorm_S8_UInt => Silk.NET.DXGI.Format.FormatR24G8Typeless,//Silk.NET.DXGI.Format.FormatD24UnormS8Uint + PixelFormat.D32_Float_S8X24_UInt => Silk.NET.DXGI.Format.FormatR32G8X24Typeless, + + _ => throw new NotSupportedException($"Unsupported Depth Format [{textureDescription.Format}] for the Depth-Stencil Buffer") + }; } } } int quality = 0; - if (GraphicsDevice.Features.CurrentProfile >= GraphicsProfile.Level_10_1 && textureDescription.IsMultisample) - quality = (int)StandardMultisampleQualityLevels.StandardMultisamplePattern; + if (GraphicsDevice.Features.CurrentProfile >= GraphicsProfile.Level_10_1 && textureDescription.IsMultiSampled) + quality = (int) StandardMultisampleQualityLevels.StandardMultisamplePattern; - var desc = new Texture2DDescription() + var desc = new Texture2DDesc { - Width = textureDescription.Width, - Height = textureDescription.Height, - ArraySize = textureDescription.ArraySize, - SampleDescription = new SharpDX.DXGI.SampleDescription((int)textureDescription.MultisampleCount, quality), - BindFlags = GetBindFlagsFromTextureFlags(flags), + Width = (uint) textureDescription.Width, + Height = (uint) textureDescription.Height, + ArraySize = (uint) textureDescription.ArraySize, + SampleDesc = new SampleDesc((uint) textureDescription.MultisampleCount, (uint) quality), + BindFlags = (uint) GetBindFlagsFromTextureFlags(flags), Format = format, - MipLevels = textureDescription.MipLevels, - Usage = (ResourceUsage)textureDescription.Usage, - CpuAccessFlags = GetCpuAccessFlagsFromUsage(textureDescription.Usage), - OptionFlags = (ResourceOptionFlags)textureDescription.Options, + MipLevels = (uint) textureDescription.MipLevelCount, + Usage = (Usage) textureDescription.Usage, + CPUAccessFlags = (uint) GetCpuAccessFlagsFromUsage(textureDescription.Usage), + MiscFlags = (uint) textureDescription.Options }; if (textureDescription.Dimension == TextureDimension.TextureCube) - desc.OptionFlags = ResourceOptionFlags.TextureCube; + desc.MiscFlags = (uint) ResourceMiscFlag.Texturecube; return desc; } - internal static PixelFormat ComputeShaderResourceFormatFromDepthFormat(PixelFormat format) + /// + /// Given a Depth Texture format, returns the corresponding Shader Resource View format. + /// + /// The depth format. + /// + /// The View format corresponding to , + /// or if no compatible format could be computed. + /// + internal static PixelFormat ComputeShaderResourceFormatFromDepthFormat(PixelFormat depthFormat) { - PixelFormat viewFormat; - - // Determine TypeLess Format and ShaderResourceView Format - switch (format) - { - case PixelFormat.R16_Typeless: - case PixelFormat.D16_UNorm: - viewFormat = PixelFormat.R16_Float; - break; - case PixelFormat.R32_Typeless: - case PixelFormat.D32_Float: - viewFormat = PixelFormat.R32_Float; - break; - case PixelFormat.R24G8_Typeless: - case PixelFormat.D24_UNorm_S8_UInt: - viewFormat = PixelFormat.R24_UNorm_X8_Typeless; - break; - case PixelFormat.R32_Float_X8X24_Typeless: - case PixelFormat.D32_Float_S8X24_UInt: - viewFormat = PixelFormat.R32_Float_X8X24_Typeless; - break; - default: - viewFormat = PixelFormat.None; - break; - } + var viewFormat = depthFormat switch + { + PixelFormat.R16_Typeless or PixelFormat.D16_UNorm => PixelFormat.R16_Float, + PixelFormat.R32_Typeless or PixelFormat.D32_Float => PixelFormat.R32_Float, + PixelFormat.R24G8_Typeless or PixelFormat.D24_UNorm_S8_UInt => PixelFormat.R24_UNorm_X8_Typeless, + PixelFormat.R32_Float_X8X24_Typeless or PixelFormat.D32_Float_S8X24_UInt => PixelFormat.R32_Float_X8X24_Typeless, + _ => PixelFormat.None + }; return viewFormat; } - internal static SharpDX.DXGI.Format ComputeDepthViewFormatFromTextureFormat(PixelFormat format) + /// + /// Given a pixel format, returns the corresponding Silk.NET's depth format. + /// + /// The pixel format. + /// The depth corresponding to . + /// + /// The does not have a corresponding depth format. + /// + internal static Format ComputeDepthViewFormatFromTextureFormat(PixelFormat format) { - SharpDX.DXGI.Format viewFormat; - - switch (format) - { - case PixelFormat.R16_Typeless: - case PixelFormat.D16_UNorm: - viewFormat = SharpDX.DXGI.Format.D16_UNorm; - break; - case PixelFormat.R32_Typeless: - case PixelFormat.D32_Float: - viewFormat = SharpDX.DXGI.Format.D32_Float; - break; - case PixelFormat.R24G8_Typeless: - case PixelFormat.D24_UNorm_S8_UInt: - viewFormat = SharpDX.DXGI.Format.D24_UNorm_S8_UInt; - break; - case PixelFormat.R32G8X24_Typeless: - case PixelFormat.D32_Float_S8X24_UInt: - viewFormat = SharpDX.DXGI.Format.D32_Float_S8X24_UInt; - break; - default: - throw new NotSupportedException($"Unsupported depth format [{format}]"); - } + var viewFormat = format switch + { + PixelFormat.R16_Typeless or PixelFormat.D16_UNorm => Silk.NET.DXGI.Format.FormatD16Unorm, + PixelFormat.R32_Typeless or PixelFormat.D32_Float => Silk.NET.DXGI.Format.FormatD32Float, + PixelFormat.R24G8_Typeless or PixelFormat.D24_UNorm_S8_UInt => Silk.NET.DXGI.Format.FormatD24UnormS8Uint, + PixelFormat.R32G8X24_Typeless or PixelFormat.D32_Float_S8X24_UInt => Silk.NET.DXGI.Format.FormatD32FloatS8X24Uint, + _ => throw new NotSupportedException($"Unsupported depth format [{format}]") + }; return viewFormat; } - private Texture3DDescription ConvertToNativeDescription3D() + /// + /// Returns a native from the current . + /// + /// A Silk.NET's describing the Texture. + private Texture3DDesc ConvertToNativeDescription3D() { - var desc = new Texture3DDescription() - { - Width = textureDescription.Width, - Height = textureDescription.Height, - Depth = textureDescription.Depth, - BindFlags = GetBindFlagsFromTextureFlags(textureDescription.Flags), - Format = (SharpDX.DXGI.Format)textureDescription.Format, - MipLevels = textureDescription.MipLevels, - Usage = (ResourceUsage)textureDescription.Usage, - CpuAccessFlags = GetCpuAccessFlagsFromUsage(textureDescription.Usage), - OptionFlags = (ResourceOptionFlags)textureDescription.Options, + var desc = new Texture3DDesc + { + Width = (uint) textureDescription.Width, + Height = (uint) textureDescription.Height, + Depth = (uint) textureDescription.Depth, + BindFlags = (uint) GetBindFlagsFromTextureFlags(textureDescription.Flags), + Format = (Format) textureDescription.Format, + MipLevels = (uint) textureDescription.MipLevelCount, + Usage = (Usage) textureDescription.Usage, + CPUAccessFlags = (uint) GetCpuAccessFlagsFromUsage(textureDescription.Usage), + MiscFlags = (uint) textureDescription.Options }; return desc; } /// - /// Check and modify if necessary the mipmap levels of the image (Troubles with DXT images whose resolution in less than 4x4 in DX9.x). + /// Checks a for invalid mip-levels and modifies the description if necessary. /// /// The graphics device. - /// The texture description. - /// The updated texture description. + /// The Texture description to check. + /// The updated Texture description. + /// + /// This check is to prevent issues with Direct3D 9.x where the driver may not be able to create mipmaps + /// whose resolution in less than 4x4 pixels. + /// private static TextureDescription CheckMipLevels(GraphicsDevice device, ref TextureDescription description) { - if (device.Features.CurrentProfile < GraphicsProfile.Level_10_0 && (description.Flags & TextureFlags.DepthStencil) == 0 && description.Format.IsCompressed()) + if (device.Features.CurrentProfile < GraphicsProfile.Level_10_0 && + description.Flags.HasFlag(TextureFlags.DepthStencil) && description.Format.IsCompressed()) { - description.MipLevels = Math.Min(CalculateMipCount(description.Width, description.Height), description.MipLevels); + description.MipLevelCount = Math.Min(CalculateMipCount(description.Width, description.Height), description.MipLevelCount); } return description; } /// - /// Calculates the mip level from a specified size. + /// Calculates the number of mip-levels that can be created for a specified size, taking into account + /// a minimum mip-level size. /// - /// The size. - /// The minimum size of the last mip. - /// The mip level. - /// Value must be > 0;size + /// The size in pixels. + /// The minimum size of the last mip-level. By default, this is 4 pixels. + /// The number of possible mip-levels. + /// + /// Both and must be greater than 0. + /// private static int CalculateMipCountFromSize(int size, int minimumSizeLastMip = 4) { - if (size <= 0) - { - throw new ArgumentOutOfRangeException("Value must be > 0", "size"); - } + // TODO: CountMips? - if (minimumSizeLastMip <= 0) - { - throw new ArgumentOutOfRangeException("Value must be > 0", "minimumSizeLastMip"); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minimumSizeLastMip); int level = 1; while ((size / 2) >= minimumSizeLastMip) @@ -835,31 +1133,61 @@ private static int CalculateMipCountFromSize(int size, int minimumSizeLastMip = } /// - /// Calculates the mip level from a specified width,height,depth. + /// Calculates the number of mip-levels that can be created for a specified size, taking into account + /// a minimum mip-level size. /// - /// The width. - /// The height. - /// The minimum size of the last mip. - /// The mip level. - /// Value must be > 0;size + /// The width in pixels. + /// The height in pixels. + /// The minimum size of the last mip-level. By default, this is 4 pixels. + /// The number of possible mip-levels. + /// + /// and must be greater than 0, and + /// must also be greater than 0. + /// private static int CalculateMipCount(int width, int height, int minimumSizeLastMip = 4) { - return Math.Min(CalculateMipCountFromSize(width, minimumSizeLastMip), CalculateMipCountFromSize(height, minimumSizeLastMip)); + return Math.Min(CalculateMipCountFromSize(width, minimumSizeLastMip), + CalculateMipCountFromSize(height, minimumSizeLastMip)); } + /// + /// Determines if the specified format is a Depth-Stencil format that also contains Stencil data. + /// + /// The pixel format to check. + /// + /// if is a Depth-Stencil format that also contains Stencil data; + /// otherwise, . + /// internal static bool IsStencilFormat(PixelFormat format) { - switch (format) + return format switch { - case PixelFormat.R24G8_Typeless: - case PixelFormat.D24_UNorm_S8_UInt: - case PixelFormat.R32G8X24_Typeless: - case PixelFormat.D32_Float_S8X24_UInt: - return true; - } + PixelFormat.R24G8_Typeless or + PixelFormat.D24_UNorm_S8_UInt or + PixelFormat.R32G8X24_Typeless or + PixelFormat.D32_Float_S8X24_UInt => true, - return false; + _ => false + }; + } + + /// + /// Gets the CPU access flags from the intended Texture usage. + /// + /// The intended usage of the Texture. + /// A combination of one or more flags. + private new CpuAccessFlag GetCpuAccessFlagsFromUsage(GraphicsResourceUsage usage) + { + return usage switch + { + // Depth-Stencil Textures may not be used in combination with CpuAccessFlags + // when the usage is Default + GraphicsResourceUsage.Default when IsDepthStencil => CpuAccessFlag.None, + + _ => GraphicsResourceBase.GetCpuAccessFlagsFromUsage(usage) + }; } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs index 58492b7a29..954e7156ef 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/Buffer.Direct3D12.cs @@ -1,53 +1,119 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; -using System.Collections.Generic; -using SharpDX; -using SharpDX.DXGI; -using SharpDX.Direct3D12; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; +using Silk.NET.DXGI; + +using D3D12Range = Silk.NET.Direct3D12.Range; + using Stride.Core.Mathematics; -using System.Runtime.CompilerServices; + +using static System.Runtime.CompilerServices.Unsafe; namespace Stride.Graphics { - public partial class Buffer + public unsafe partial class Buffer { - private SharpDX.Direct3D12.ResourceDescription nativeDescription; - internal long GPUVirtualAddress; + // Internal Direct3D 12 Resource description + private ResourceDesc nativeDescription; + + internal ulong GPUVirtualAddress; + /// - /// Initializes a new instance of the class. + /// Initializes this instance with the provided options. /// - /// The description. - /// Type of the buffer. - /// The view format. - /// The data pointer. - protected Buffer InitializeFromImpl(BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) + /// A structure describing the buffer characteristics. + /// A combination of flags determining how the Views over this buffer should behave. + /// + /// View format used if the buffer is used as a Shader Resource View, + /// or if not. + /// + /// The data pointer to the data to initialize the buffer with. + /// This same instance of already initialized. + /// + /// The Buffer is a Structured Buffer, but StructureByteStride is less than or equal to 0. + /// + /// + /// A Buffer with cannot be created with initial data + /// ( is not ). + /// + protected partial Buffer InitializeFromImpl(ref readonly BufferDescription description, BufferFlags bufferFlags, PixelFormat viewFormat, IntPtr dataPointer) { bufferDescription = description; - nativeDescription = ConvertToNativeDescription(GraphicsDevice, Description); - ViewFlags = viewFlags; - InitCountAndViewFormat(out this.elementCount, ref viewFormat); + nativeDescription = ConvertToNativeDescription(in description); + + ViewFlags = bufferFlags; + InitCountAndViewFormat(out elementCount, ref viewFormat); ViewFormat = viewFormat; + Recreate(dataPointer); - if (GraphicsDevice != null) + GraphicsDevice?.RegisterBufferMemoryUsage(SizeInBytes); + + return this; + + /// + /// Returns a Direct3D 12 Resource Description for the Buffer. + /// + static ResourceDesc ConvertToNativeDescription(ref readonly BufferDescription bufferDescription) { - GraphicsDevice.RegisterBufferMemoryUsage(SizeInBytes); + var flags = ResourceFlags.None; + var size = bufferDescription.SizeInBytes; + + // TODO: D3D12: For now, ensure size is multiple of ConstantBufferDataPlacementAlignment (for cbuffer views) + size = MathUtil.AlignUp(size, D3D12.DefaultResourcePlacementAlignment); + + if (bufferDescription.BufferFlags.HasFlag(BufferFlags.UnorderedAccess)) + flags |= ResourceFlags.AllowUnorderedAccess; + + return new ResourceDesc + { + Dimension = ResourceDimension.Buffer, + Width = (ulong) size, + Height = 1, + DepthOrArraySize = 1, + MipLevels = 1, + Alignment = D3D12.DefaultResourcePlacementAlignment, + + SampleDesc = { Count = 1, Quality = 0 }, + + Format = Format.FormatUnknown, + Layout = TextureLayout.LayoutRowMajor, + Flags = flags + }; } - return this; + /// + /// Determines the number of elements and the element format depending on the type of buffer and intended view format. + /// + void InitCountAndViewFormat(out int count, ref PixelFormat viewFormat) + { + if (Description.StructureByteStride == 0) + { + // TODO: The way to calculate the count is not always correct depending on the ViewFlags...etc. + count = ViewFlags.HasFlag(BufferFlags.RawBuffer) ? Description.SizeInBytes / sizeof(int) : + ViewFlags.HasFlag(BufferFlags.ShaderResource) ? Description.SizeInBytes / viewFormat.SizeInBytes() : + 0; + } + else + { + // Structured Buffer + count = Description.SizeInBytes / Description.StructureByteStride; + viewFormat = PixelFormat.None; + } + } } /// protected internal override void OnDestroyed() { - if (GraphicsDevice != null) - { - GraphicsDevice.RegisterBufferMemoryUsage(-SizeInBytes); - } + GraphicsDevice?.RegisterBufferMemoryUsage(-SizeInBytes); base.OnDestroyed(); } @@ -57,55 +123,67 @@ protected internal override bool OnRecreate() { base.OnRecreate(); - if (Description.Usage == GraphicsResourceUsage.Immutable - || Description.Usage == GraphicsResourceUsage.Default) + if (Description.Usage is GraphicsResourceUsage.Immutable or GraphicsResourceUsage.Default) return false; - Recreate(IntPtr.Zero); + Recreate(dataPointer: IntPtr.Zero); return true; } /// - /// Explicitly recreate buffer with given data. Usually called after a reset. + /// Recreates this buffer explicitly with the provided data. Usually called after the has been reset. /// - /// - /// - public unsafe void Recreate(IntPtr dataPointer) + /// + /// The data pointer to the data to use to recreate the buffer with. + /// Specify if no initial data is needed. + /// + /// + /// The Buffer is a Structured Buffer, but StructureByteStride is less than or equal to 0. + /// + /// + /// A Buffer with cannot be created with initial data + /// ( is not ). + /// + public void Recreate(IntPtr dataPointer) { - // TODO D3D12 where should that go longer term? should it be precomputed for future use? (cost would likely be additional check on SetDescriptorSets/Draw) + bool hasInitData = dataPointer != IntPtr.Zero; + + // TODO: D3D12: Where should that go longer term? Should it be precomputed for future use? (cost would likely be additional check on SetDescriptorSets/Draw) NativeResourceState = ResourceStates.Common; + var initialResourceState = NativeResourceState; var bufferFlags = bufferDescription.BufferFlags; - if ((bufferFlags & BufferFlags.ConstantBuffer) != 0) + if (bufferFlags.HasFlag(BufferFlags.ConstantBuffer)) NativeResourceState |= ResourceStates.VertexAndConstantBuffer; - if ((bufferFlags & BufferFlags.IndexBuffer) != 0) + if (bufferFlags.HasFlag(BufferFlags.IndexBuffer)) NativeResourceState |= ResourceStates.IndexBuffer; - if ((bufferFlags & BufferFlags.VertexBuffer) != 0) + if (bufferFlags.HasFlag(BufferFlags.VertexBuffer)) NativeResourceState |= ResourceStates.VertexAndConstantBuffer; - if ((bufferFlags & BufferFlags.ShaderResource) != 0) + if (bufferFlags.HasFlag(BufferFlags.ShaderResource)) NativeResourceState |= ResourceStates.PixelShaderResource | ResourceStates.NonPixelShaderResource; - if ((bufferFlags & BufferFlags.StructuredBuffer) != 0) + if (bufferFlags.HasFlag(BufferFlags.StructuredBuffer)) { if (bufferDescription.StructureByteStride <= 0) throw new ArgumentException("Element size cannot be less or equal 0 for structured buffer"); } - if ((bufferFlags & BufferFlags.ArgumentBuffer) == BufferFlags.ArgumentBuffer) + if (bufferFlags.HasFlag(BufferFlags.ArgumentBuffer)) NativeResourceState |= ResourceStates.IndirectArgument; var heapType = HeapType.Default; if (Usage == GraphicsResourceUsage.Staging) { - if (dataPointer != IntPtr.Zero) - throw new NotImplementedException("D3D12: Staging buffers can't be created with initial data."); + // Per our own definition of staging resource (read-back only) + if (hasInitData) + throw new InvalidOperationException("D3D12: Staging buffers can't be created with initial data."); heapType = HeapType.Readback; - NativeResourceState = ResourceStates.CopyDestination; + NativeResourceState = ResourceStates.CopyDest; } else if (Usage == GraphicsResourceUsage.Dynamic) { @@ -113,37 +191,71 @@ public unsafe void Recreate(IntPtr dataPointer) NativeResourceState = ResourceStates.GenericRead; } - // TODO D3D12 move that to a global allocator in bigger committed resources - NativeDeviceChild = GraphicsDevice.NativeDevice.CreateCommittedResource(new HeapProperties(heapType), HeapFlags.None, nativeDescription, dataPointer != IntPtr.Zero ? ResourceStates.CopyDestination : NativeResourceState); - GPUVirtualAddress = NativeResource.GPUVirtualAddress; + // TODO: D3D12: Move to a global allocator in bigger committed resources + var heap = new HeapProperties { Type = heapType }; + + // If the resource must be initialized with data, it is initially in the state + // CopyDest so we can copy from an upload buffer + //if (hasInitData) + // initialResourceState = ResourceStates.CopyDest; - if (dataPointer != IntPtr.Zero) + HResult result = GraphicsDevice.NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in nativeDescription, + initialResourceState, pOptimizedClearValue: null, + out ComPtr buffer); + if (result.IsFailure) + result.Throw(); + + NativeDeviceChild = buffer.AsDeviceChild(); + GPUVirtualAddress = NativeResource.GetGPUVirtualAddress(); + + if (hasInitData) { if (heapType == HeapType.Upload) { - var uploadMemory = NativeResource.Map(0); - Unsafe.CopyBlockUnaligned((void*) uploadMemory, (void*) dataPointer, (uint) SizeInBytes); - NativeResource.Unmap(0); + // An upload (dynamic) Buffer: We map and write the initial data, but leave the + // buffer in the same state so it can be mapped again anytime + void* uploadMemory = null; + result = NativeResource.Map(Subresource: 0, ref NullRef(), ref uploadMemory); + + if (result.IsFailure) + result.Throw(); + + CopyBlockUnaligned(uploadMemory, (void*) dataPointer, (uint) SizeInBytes); + + NativeResource.Unmap(Subresource: 0, pWrittenRange: ref NullRef()); } else { // Copy data in upload heap for later copy - // TODO D3D12 move that to a shared upload heap - SharpDX.Direct3D12.Resource uploadResource; - int uploadOffset; - var uploadMemory = GraphicsDevice.AllocateUploadBuffer(SizeInBytes, out uploadResource, out uploadOffset); - Unsafe.CopyBlockUnaligned((void*) uploadMemory, (void*) dataPointer, (uint) SizeInBytes); + // TODO: D3D12: Move that to a shared upload heap + var uploadMemory = GraphicsDevice.AllocateUploadBuffer(SizeInBytes, out var uploadResource, out var uploadOffset); + + CopyBlockUnaligned((void*) uploadMemory, (void*) dataPointer, (uint) SizeInBytes); - // TODO D3D12 lock NativeCopyCommandList usages + // TODO: D3D12: Lock NativeCopyCommandList usages + scoped ref var nullPipelineState = ref NullRef(); var commandList = GraphicsDevice.NativeCopyCommandList; - commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, null); + result = commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, pInitialState: ref nullPipelineState); + + if (result.IsFailure) + result.Throw(); + // Copy from upload heap to actual resource - commandList.CopyBufferRegion(NativeResource, 0, uploadResource, uploadOffset, SizeInBytes); + commandList.CopyBufferRegion(NativeResource, DstOffset: 0, uploadResource, (ulong) uploadOffset, (ulong) SizeInBytes); + + // Once initialized, transition the buffer to its final state + var resourceBarrier = new ResourceBarrier { Type = ResourceBarrierType.Transition }; + resourceBarrier.Transition.PResource = NativeResource; + resourceBarrier.Transition.Subresource = 0; + resourceBarrier.Transition.StateBefore = initialResourceState; + resourceBarrier.Transition.StateAfter = NativeResourceState; - // Switch resource to proper read state - commandList.ResourceBarrierTransition(NativeResource, 0, ResourceStates.CopyDestination, NativeResourceState); + commandList.ResourceBarrier(NumBarriers: 1, in resourceBarrier); - commandList.Close(); + result = commandList.Close(); + + if (result.IsFailure) + result.Throw(); GraphicsDevice.WaitCopyQueue(); } @@ -154,115 +266,88 @@ public unsafe void Recreate(IntPtr dataPointer) } /// - /// Gets a for a particular . + /// Gets a for a Shader Resource View over this Buffer + /// for a particular . /// /// The view format. - /// A for the particular view format. + /// A for the Shader Resource View. /// - /// The buffer must have been declared with . - /// The ShaderResourceView instance is kept by this buffer and will be disposed when this buffer is disposed. + /// The must have been declared with . + /// The Shader Resource View is kept by this Buffer and will be disposed when this Buffer is disposed. /// internal CpuDescriptorHandle GetShaderResourceView(PixelFormat viewFormat) { - var srv = new CpuDescriptorHandle(); - if ((ViewFlags & BufferFlags.ShaderResource) != 0) + CpuDescriptorHandle srv = default; + + if (ViewFlags.HasFlag(BufferFlags.ShaderResource)) { - var description = new ShaderResourceViewDescription + var description = new ShaderResourceViewDesc { Shader4ComponentMapping = 0x00001688, - Format = (SharpDX.DXGI.Format)viewFormat, - Dimension = SharpDX.Direct3D12.ShaderResourceViewDimension.Buffer, - Buffer = + Format = (Format) viewFormat, + ViewDimension = SrvDimension.Buffer, + Buffer = new() { - ElementCount = this.ElementCount, + NumElements = (uint) ElementCount, FirstElement = 0, - Flags = BufferShaderResourceViewFlags.None, - StructureByteStride = StructureByteStride, + Flags = BufferSrvFlags.None, + StructureByteStride = (uint) StructureByteStride } }; - if (((ViewFlags & BufferFlags.RawBuffer) == BufferFlags.RawBuffer)) - description.Buffer.Flags |= BufferShaderResourceViewFlags.Raw; + if (ViewFlags.HasFlag(BufferFlags.RawBuffer)) + description.Buffer.Flags |= BufferSrvFlags.Raw; - srv = GraphicsDevice.ShaderResourceViewAllocator.Allocate(1); - NativeDevice.CreateShaderResourceView(NativeResource, description, srv); + srv = GraphicsDevice.ShaderResourceViewAllocator.Allocate(); + NativeDevice.CreateShaderResourceView(NativeResource, in description, srv); } return srv; } + /// + /// Gets a for a Unordered Access View over this Buffer + /// for a particular . + /// + /// The view format. + /// A for the Unordered Access View. + /// + /// The must have been declared with . + /// The Render Target View is kept by this Buffer and will be disposed when this Buffer is disposed. + /// internal CpuDescriptorHandle GetUnorderedAccessView(PixelFormat viewFormat) { - var uav = new CpuDescriptorHandle(); - if ((ViewFlags & BufferFlags.UnorderedAccess) != 0) + CpuDescriptorHandle uav = default; + + if (ViewFlags.HasFlag(BufferFlags.UnorderedAccess)) { - var description = new UnorderedAccessViewDescription + var description = new UnorderedAccessViewDesc { - Format = (SharpDX.DXGI.Format)viewFormat, - Dimension = SharpDX.Direct3D12.UnorderedAccessViewDimension.Buffer, - Buffer = + Format = (Format) viewFormat, + ViewDimension = UavDimension.Buffer, + Buffer = new() { - ElementCount = this.ElementCount, + NumElements = (uint) ElementCount, FirstElement = 0, - Flags = BufferUnorderedAccessViewFlags.None, - StructureByteStride = StructureByteStride, - CounterOffsetInBytes = 0, + Flags = BufferUavFlags.None, + StructureByteStride = (uint) StructureByteStride, + CounterOffsetInBytes = 0 } }; - if ((ViewFlags & BufferFlags.RawBuffer) == BufferFlags.RawBuffer) + if (ViewFlags.HasFlag(BufferFlags.RawBuffer)) { - description.Buffer.Flags |= BufferUnorderedAccessViewFlags.Raw; - description.Format = Format.R32_Typeless; + description.Buffer.Flags |= BufferUavFlags.Raw; + description.Format = Format.FormatR32Typeless; } uav = GraphicsDevice.UnorderedAccessViewAllocator.Allocate(1); - // TODO: manage counter value here if buffer has 'Counter' or 'Append' flag + // TODO: Manage counter value here if Buffer has 'Counter' or 'Append' flag // if (Flags == BufferFlags.StructuredAppendBuffer || Flags == BufferFlags.StructuredCounterBuffer)) - NativeDevice.CreateUnorderedAccessView(NativeResource, null, description, uav); + NativeDevice.CreateUnorderedAccessView(NativeResource, pCounterResource: null, in description, uav); } return uav; - } - - private void InitCountAndViewFormat(out int count, ref PixelFormat viewFormat) - { - if (Description.StructureByteStride == 0) - { - // TODO: The way to calculate the count is not always correct depending on the ViewFlags...etc. - if ((ViewFlags & BufferFlags.RawBuffer) != 0) - { - count = Description.SizeInBytes / sizeof(int); - } - else if ((ViewFlags & BufferFlags.ShaderResource) != 0) - { - count = Description.SizeInBytes / viewFormat.SizeInBytes(); - } - else - { - count = 0; - } - } - else - { - // For structured buffer - count = Description.SizeInBytes / Description.StructureByteStride; - viewFormat = PixelFormat.None; - } - } - - private static SharpDX.Direct3D12.ResourceDescription ConvertToNativeDescription(GraphicsDevice graphicsDevice, BufferDescription bufferDescription) - { - var flags = ResourceFlags.None; - var size = bufferDescription.SizeInBytes; - - // TODO D3D12 for now, ensure size is multiple of ConstantBufferDataPlacementAlignment (for cbuffer views) - size = MathUtil.AlignUp(size, graphicsDevice.ConstantBufferDataPlacementAlignment); - - if ((bufferDescription.BufferFlags & BufferFlags.UnorderedAccess) != 0) - flags |= ResourceFlags.AllowUnorderedAccess; - - return SharpDX.Direct3D12.ResourceDescription.Buffer(size, flags); - } + }} } -} + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs index 2e52e571b1..480f0d56dd 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/CommandList.Direct3D12.cs @@ -1,61 +1,72 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using SharpDX; -using SharpDX.Direct3D12; -using SharpDX.Mathematics.Interop; +using System.Diagnostics; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D12; + using Stride.Core.Mathematics; +using Stride.Core.UnsafeExtensions; + +using D3D12Box = Silk.NET.Direct3D12.Box; +using D3D12Range = Silk.NET.Direct3D12.Range; +using D3D12Viewport = Silk.NET.Direct3D12.Viewport; +using SilkBox2I = Silk.NET.Maths.Box2D; + +using static System.Runtime.CompilerServices.Unsafe; namespace Stride.Graphics { - public partial class CommandList + public unsafe partial class CommandList { - private DescriptorHeapCache srvHeap; + // Descriptor heap for Shader Resource Views (SRV), Constant Buffers (CBV), and Unordered Access Views (UAV) + private DescriptorHeapWrapper srvHeap; private int srvHeapOffset = GraphicsDevice.SrvHeapSize; - private DescriptorHeapCache samplerHeap; + + // Descriptor heap for Samplers + private DescriptorHeapWrapper samplerHeap; private int samplerHeapOffset = GraphicsDevice.SamplerHeapSize; private PipelineState boundPipelineState; - private readonly DescriptorHeap[] descriptorHeaps = new DescriptorHeap[2]; + private readonly ID3D12DescriptorHeap*[] descriptorHeaps = new ID3D12DescriptorHeap*[2]; private readonly List resourceBarriers = new(16); - private readonly Dictionary srvMapping = new(); - private readonly Dictionary samplerMapping = new(); + // Mappings from CPU-side Descriptor Handles to GPU-side Descriptor Handles + private readonly Dictionary srvMapping = []; + private readonly Dictionary samplerMapping = []; - internal readonly Queue NativeCommandLists = new(); + internal readonly Queue> NativeCommandLists = new(); private CompiledCommandList currentCommandList; - private bool IsComputePipelineStateBound => boundPipelineState != null && boundPipelineState.IsCompute; + private bool IsComputePipelineStateBound => boundPipelineState?.IsCompute is true; + + /// + /// Creates a new . + /// + /// The Graphics Device. + /// The new instance of . public static CommandList New(GraphicsDevice device) { return new CommandList(device); } + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. private CommandList(GraphicsDevice device) : base(device) { Reset(); } - private void ResetCommandList() - { - if (NativeCommandLists.Count > 0) - { - currentCommandList.NativeCommandList = NativeCommandLists.Dequeue(); - currentCommandList.NativeCommandList.Reset(currentCommandList.NativeCommandAllocator, initialStateRef: null); - } - else - { - currentCommandList.NativeCommandList = GraphicsDevice.NativeDevice.CreateCommandList(CommandListType.Direct, currentCommandList.NativeCommandAllocator, initialState: null); - } - - currentCommandList.NativeCommandList.SetDescriptorHeaps(2, descriptorHeaps); - } - /// protected internal override void OnDestroyed() { @@ -65,13 +76,13 @@ protected internal override void OnDestroyed() // Available right now (NextFenceValue - 1) // TODO: Note that it won't be available right away because CommandAllocators is currently not using a PriorityQueue but a simple Queue - if (currentCommandList.NativeCommandAllocator != null) + if (currentCommandList.NativeCommandAllocator.IsNotNull()) { GraphicsDevice.CommandAllocators.RecycleObject(GraphicsDevice.NextFenceValue - 1, currentCommandList.NativeCommandAllocator); currentCommandList.NativeCommandAllocator = null; } - if (currentCommandList.NativeCommandList != null) + if (currentCommandList.NativeCommandList.IsNotNull()) { NativeCommandLists.Enqueue(currentCommandList.NativeCommandList); currentCommandList.NativeCommandList = null; @@ -79,15 +90,20 @@ protected internal override void OnDestroyed() while (NativeCommandLists.Count > 0) { - NativeCommandLists.Dequeue().Dispose(); + var commandList = NativeCommandLists.Dequeue(); + commandList.Release(); } base.OnDestroyed(); } - public void Reset() + + /// + /// Resets a Command List back to its initial state as if a new Command List was just created. + /// + public unsafe partial void Reset() { - if (currentCommandList.Builder != null) + if (currentCommandList.Builder is not null) return; FlushResourceBarriers(); @@ -108,17 +124,62 @@ public void Reset() ResetCommandList(); boundPipelineState = null; + + // + // Reset the command list state. + // + void ResetCommandList() + { + scoped ref var nullInitialPipelineState = ref NullRef(); + + if (NativeCommandLists.TryDequeue(out ComPtr nativeCommandList)) + { + currentCommandList.NativeCommandList = nativeCommandList; + + HResult result = currentCommandList.NativeCommandList.Reset(currentCommandList.NativeCommandAllocator, ref nullInitialPipelineState); + + if (result.IsFailure) + result.Throw(); + } + else + { + var commandAllocator = currentCommandList.NativeCommandAllocator; + HResult result = NativeDevice.CreateCommandList(nodeMask: 0, CommandListType.Direct, commandAllocator, ref nullInitialPipelineState, + out ComPtr commandList); + if (result.IsFailure) + result.Throw(); + + currentCommandList.NativeCommandList = commandList; + } + + currentCommandList.NativeCommandList.SetDescriptorHeaps(NumDescriptorHeaps: 2, in descriptorHeaps[0]); + } + } + + /// + /// Closes and executes the Command List. + /// + public partial void Flush() + { + var commandList = Close(); + GraphicsDevice.ExecuteCommandList(commandList); } /// - /// Closes the command list for recording and returns an executable token. + /// Indicates that recording to the Command List has finished. /// - /// The executable command list. - public CompiledCommandList Close() + /// + /// A representing the frozen list of recorded commands + /// that can be executed at a later time. + /// + public partial CompiledCommandList Close() { FlushResourceBarriers(); - currentCommandList.NativeCommandList.Close(); + HResult result = currentCommandList.NativeCommandList.Close(); + + if (result.IsFailure) + result.Throw(); // Staging resources not updated anymore foreach (var stagingResource in currentCommandList.StagingResources) @@ -127,25 +188,30 @@ public CompiledCommandList Close() } // Recycle heaps - ResetSrvHeap(false); - ResetSamplerHeap(false); + ResetSrvHeap(createNewHeap: false); + ResetSamplerHeap(createNewHeap: false); - var result = currentCommandList; + var commandList = currentCommandList; currentCommandList = default; - return result; + return commandList; } /// - /// Closes and executes the command list. + /// Flushes the current Command List and optionally waits for its completion. /// - public void Flush() - { - GraphicsDevice.ExecuteCommandList(Close()); - } - + /// + /// A value indicating whether to wait for the Command List execution to complete. + /// to wait for completion; otherwise, . + /// + /// + /// This method finalizes the current Command List, submits it for execution, and resets + /// the state for future commands. If is , + /// the method will block until the Command List execution is finished. + /// private void FlushInternal(bool wait) { - var fenceValue = GraphicsDevice.ExecuteCommandListInternal(Close()); + var commandList = Close(); + var fenceValue = GraphicsDevice.ExecuteCommandListInternal(commandList); if (wait) GraphicsDevice.WaitForFenceInternal(fenceValue); @@ -153,61 +219,86 @@ private void FlushInternal(bool wait) Reset(); // Restore states - if (boundPipelineState != null) + if (boundPipelineState is not null) SetPipelineState(boundPipelineState); - currentCommandList.NativeCommandList.SetDescriptorHeaps(2, descriptorHeaps); + + currentCommandList.NativeCommandList.SetDescriptorHeaps(NumDescriptorHeaps: 2, in descriptorHeaps[0]); SetRenderTargetsImpl(depthStencilBuffer, renderTargetCount, renderTargets); } - private void ClearStateImpl() - { - } + /// + /// Direct3D 12 implementation that clears and restores the state of the Graphics Device. + /// + private partial void ClearStateImpl() { } /// - /// Unbinds all depth-stencil buffer and render targets from the output-merger stage. + /// Unbinds the Depth-Stencil Buffer and all the Render Targets from the output-merger stage. /// - private void ResetTargetsImpl() - { - } + private void ResetTargetsImpl() { } /// - /// Binds a depth-stencil buffer and a set of render targets to the output-merger stage. See to learn how to use it. + /// Binds a Depth-Stencil Buffer and a set of Render Targets to the output-merger stage. /// - /// The depth stencil buffer. - /// The render targets. - /// renderTargetViews + /// The Depth-Stencil Buffer to bind. + /// The number of Render Targets to bind. + /// The Render Targets to bind. private void SetRenderTargetsImpl(Texture depthStencilBuffer, int renderTargetCount, Texture[] renderTargets) { - var renderTargetHandles = new CpuDescriptorHandle[renderTargetCount]; - for (int i = 0; i < renderTargetHandles.Length; ++i) + bool hasRenderTargetsToSet = renderTargetCount > 0 && renderTargets is { Length: > 0 }; + + var renderTargetHandles = stackalloc CpuDescriptorHandle[renderTargetCount]; + + for (int i = 0; i < renderTargetCount; ++i) { renderTargetHandles[i] = renderTargets[i].NativeRenderTargetView; } - currentCommandList.NativeCommandList.SetRenderTargets(renderTargetHandles, depthStencilBuffer?.NativeDepthStencilView); + + bool hasDepthStencilTargetToSet = depthStencilBuffer is not null; + scoped ref var depthStencilView = ref hasDepthStencilTargetToSet + ? ref depthStencilBuffer.NativeDepthStencilView + : ref NullRef(); + + currentCommandList.NativeCommandList.OMSetRenderTargets((uint) renderTargetCount, in renderTargetHandles[0], + RTsSingleHandleToDescriptorRange: false, + in depthStencilView); } /// - /// Sets the stream targets. + /// Sets the stream output Buffers. /// - /// The buffers. + /// + /// The Buffers to set for stream output. + /// Specify or an empty array to unset any bound output Buffer. + /// public void SetStreamTargets(params Buffer[] buffers) { + // TODO: Implement stream output buffers } /// - /// Gets or sets the 1st viewport. See to learn how to use it. + /// Sets the viewports to the rasterizer stage. /// - /// The viewport. private void SetViewportImpl() { if (!viewportDirty && !scissorsDirty) return; - var viewport = viewports[0]; + scoped ref var viewport = ref viewports[0]; + if (viewportDirty) { - currentCommandList.NativeCommandList.SetViewport(new RawViewportF { Width = viewport.Width, Height = viewport.Height, X = viewport.X, Y = viewport.Y, MinDepth = viewport.MinDepth, MaxDepth = viewport.MaxDepth }); - currentCommandList.NativeCommandList.SetScissorRectangles(new RawRectangle { Left = (int) viewport.X, Right = (int) viewport.X + (int) viewport.Width, Top = (int) viewport.Y, Bottom = (int) viewport.Y + (int) viewport.Height }); + // NOTE: We assume the same layout and size as DIRECT3D12_VIEWPORT struct + Debug.Assert(sizeof(Viewport) == sizeof(D3D12Viewport)); + scoped ref var d3dViewport = ref As(ref viewport); + + currentCommandList.NativeCommandList.RSSetViewports(NumViewports: 1, in d3dViewport); + + var scissorRect = new SilkBox2I + { + Min = { X = (int) viewport.X, Y = (int) viewport.Y }, + Max = { X = (int) (viewport.X + viewport.Width), Y = (int) (viewport.Y + viewport.Height) } + }; + currentCommandList.NativeCommandList.RSSetScissorRects(NumRects: 1, in scissorRect); viewportDirty = false; } @@ -216,160 +307,317 @@ private void SetViewportImpl() if (scissorsDirty) { // Use manual scissor - var scissor = scissors[0]; - currentCommandList.NativeCommandList.SetScissorRectangles(new RawRectangle { Left = scissor.Left, Right = scissor.Right, Top = scissor.Top, Bottom = scissor.Bottom }); + scoped ref var scissor = ref scissors[0]; + var scissorRect = new SilkBox2I + { + Min = { X = scissor.X, Y = scissor.Y }, + Max = { X = scissor.Right, Y = scissor.Bottom } + }; + currentCommandList.NativeCommandList.RSSetScissorRects(NumRects: 1, in scissorRect); } } else { // Use viewport // Always update, because either scissor or viewport was dirty and we use viewport size - currentCommandList.NativeCommandList.SetScissorRectangles(new RawRectangle { Left = (int) viewport.X, Right = (int) viewport.X + (int) viewport.Width, Top = (int) viewport.Y, Bottom = (int) viewport.Y + (int) viewport.Height }); + var scissorRect = new SilkBox2I + { + Min = { X = (int) viewport.X, Y = (int) viewport.Y }, + Max = { X = (int) (viewport.X + viewport.Width), Y = (int) (viewport.Y + viewport.Height) } + }; + currentCommandList.NativeCommandList.RSSetScissorRects(NumRects: 1, in scissorRect); } scissorsDirty = false; } /// - /// Prepares a draw call. This method is called before each Draw() method to setup the correct Primitive, InputLayout and VertexBuffers. + /// Direct3D 12 implementation that sets a scissor rectangle to the rasterizer stage. + /// + /// The scissor rectangle to set. + private unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) + { + // Do nothing. Direct3D 12 already sets the scissor rectangle as part of PrepareDraw() + } + + /// + /// Direct3D 12 implementation that sets one or more scissor rectangles to the rasterizer stage. + /// + /// The number of scissor rectangles to bind. + /// The set of scissor rectangles to bind. + private unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) + { + // Do nothing. Direct3D 12 already sets the scissor rectangles as part of PrepareDraw() + } + + /// + /// Prepares the Command List for a subsequent draw command. /// - /// Cannot GraphicsDevice.Draw*() without an effect being previously applied with Effect.Apply() method + /// + /// This method is called before each Draw() method to setup the correct Viewport. + /// private void PrepareDraw() { FlushResourceBarriers(); SetViewportImpl(); } + /// + /// Sets the reference value for Depth-Stencil tests. + /// + /// Reference value to perform against when doing a Depth-Stencil test. + /// public void SetStencilReference(int stencilReference) { - currentCommandList.NativeCommandList.StencilReference = stencilReference; + currentCommandList.NativeCommandList.OMSetStencilRef((uint) stencilReference); } + /// + /// Sets the blend factors for blending each of the RGBA components. + /// + /// + /// + /// A representing the blend factors for each RGBA component. + /// The blend factors modulate values for the pixel Shader, Render Target, or both. + /// + /// + /// If you have configured the Blend-State object with or , + /// the blending stage uses the blend factors specified by . + /// Otherwise, the blend factors will not be taken into account for the blend stage. + /// + /// + /// public void SetBlendFactor(Color4 blendFactor) { - currentCommandList.NativeCommandList.BlendFactor = ColorHelper.ConvertToVector4(blendFactor); + scoped Span blendFactorFloats = blendFactor.AsSpan(); + + currentCommandList.NativeCommandList.OMSetBlendFactor(blendFactorFloats); } + /// + /// Sets the configuration of the graphics pipeline which, among other things, control the shaders, input layout, + /// render states, and output settings. + /// + /// The Pipeline State object to set. Specify to use the default one. + /// public void SetPipelineState(PipelineState pipelineState) { - if (boundPipelineState != pipelineState && pipelineState?.CompiledState != null) + if (boundPipelineState != pipelineState && + pipelineState is { CompiledState.Handle: not null }) { // If scissor state changed, force a refresh scissorsDirty |= (boundPipelineState?.HasScissorEnabled ?? false) != pipelineState.HasScissorEnabled; - currentCommandList.NativeCommandList.PipelineState = pipelineState.CompiledState; + currentCommandList.NativeCommandList.SetPipelineState(pipelineState.CompiledState); + if (pipelineState.IsCompute) currentCommandList.NativeCommandList.SetComputeRootSignature(pipelineState.RootSignature); else currentCommandList.NativeCommandList.SetGraphicsRootSignature(pipelineState.RootSignature); + boundPipelineState = pipelineState; - currentCommandList.NativeCommandList.PrimitiveTopology = pipelineState.PrimitiveTopology; + currentCommandList.NativeCommandList.IASetPrimitiveTopology(pipelineState.PrimitiveTopology); } } + /// + /// Sets a Vertex Buffer for the input assembler stage of the pipeline. + /// + /// + /// The input slot for binding the Vertex Buffer; + /// The maximum number of input slots available depends on platform and graphics profile, usually 16 or 32. + /// + /// + /// The Vertex Buffer to set. It must have been created with . + /// + /// + /// The number of bytes between the first element of the Vertex Buffer and the first element that will be used. + /// + /// + /// The size (in bytes) of the elements that are to be used from the Vertex Buffer. + /// public void SetVertexBuffer(int index, Buffer buffer, int offset, int stride) { - currentCommandList.NativeCommandList.SetVertexBuffer(index, new VertexBufferView + if (buffer is null) { - BufferLocation = buffer.NativeResource.GPUVirtualAddress + offset, - StrideInBytes = stride, - SizeInBytes = buffer.SizeInBytes - offset - }); + scoped ref var nullVertexBufferView = ref NullRef(); + + // Unset the Vertex Buffer from the slot + currentCommandList.NativeCommandList.IASetVertexBuffers(StartSlot: (uint) index, NumViews: 1, in nullVertexBufferView); + return; + } + + var vertexBufferView = new VertexBufferView + { + BufferLocation = buffer.NativeResource.GetGPUVirtualAddress() + (ulong) offset, + StrideInBytes = (uint) stride, + SizeInBytes = (uint) (buffer.SizeInBytes - offset) + }; + currentCommandList.NativeCommandList.IASetVertexBuffers(StartSlot: (uint) index, NumViews: 1, in vertexBufferView); } - public void SetIndexBuffer(Buffer buffer, int offset, bool is32Bits) + /// + /// Sets the Index Buffer for the input assembler stage of the pipeline. + /// + /// + /// The Index Buffer to set. It must have been created with . + /// + /// Offset (in bytes) from the start of the Index Buffer to the first index to use. + /// + /// A value indicating if the Index Buffer elements are 32-bit indices (), or 16-bit (). + /// + public void SetIndexBuffer(Buffer buffer, int offset, bool is32Bits) // TODO: Use IndexElementSize? { - currentCommandList.NativeCommandList.SetIndexBuffer(buffer != null ? new IndexBufferView + if (buffer is null) { - BufferLocation = buffer.NativeResource.GPUVirtualAddress + offset, - Format = is32Bits ? SharpDX.DXGI.Format.R32_UInt : SharpDX.DXGI.Format.R16_UInt, - SizeInBytes = buffer.SizeInBytes - offset - } : null); + scoped ref var nullIndexBufferView = ref NullRef(); + + currentCommandList.NativeCommandList.IASetIndexBuffer(in nullIndexBufferView); + return; + } + + var indexBufferView = new IndexBufferView + { + BufferLocation = buffer.NativeResource.GetGPUVirtualAddress() + (ulong) offset, + Format = is32Bits ? Format.FormatR32Uint : Format.FormatR16Uint, + SizeInBytes = (uint) (buffer.SizeInBytes - offset) + }; + currentCommandList.NativeCommandList.IASetIndexBuffer(in indexBufferView); } + /// + /// Inserts a barrier that transitions a Graphics Resource to a new state, ensuring proper synchronization + /// between different GPU operations accessing the resource. + /// + /// The Graphics Resource to transition to a different state. + /// The new state of . public void ResourceBarrierTransition(GraphicsResource resource, GraphicsResourceState newState) { + Debug.Assert(resource is not null, "Resource must not be null."); + // Find parent resource - if (resource.ParentResource != null) + if (resource.ParentResource is not null) resource = resource.ParentResource; var targetState = (ResourceStates) newState; + if (resource.IsTransitionNeeded(targetState)) { - resourceBarriers.Add(new ResourceTransitionBarrier(resource.NativeResource, subresource: -1, resource.NativeResourceState, targetState)); + var transitionBarrier = new ResourceBarrier + { + Type = ResourceBarrierType.Transition, + Flags = ResourceBarrierFlags.None, + + Transition = new ResourceTransitionBarrier + { + PResource = resource.NativeResource, + Subresource = uint.MaxValue, + StateBefore = resource.NativeResourceState, + StateAfter = targetState + } + }; + + resourceBarriers.Add(transitionBarrier); resource.NativeResourceState = targetState; } } + /// + /// Flushes all pending Graphics Resource barriers. + /// + /// + /// This method processes all pending resource barriers, applying them. This is to to ensure + /// that all queued resource transitions are executed. + /// private unsafe void FlushResourceBarriers() { int count = resourceBarriers.Count; if (count == 0) return; - var barriers = stackalloc ResourceBarrier[count]; - for (int i = 0; i < count; i++) - barriers[i] = resourceBarriers[i]; + scoped Span barriers = stackalloc ResourceBarrier[count]; + resourceBarriers.CopyTo(barriers); + resourceBarriers.Clear(); - currentCommandList.NativeCommandList.ResourceBarrier(*barriers); + currentCommandList.NativeCommandList.ResourceBarrier(NumBarriers: (uint) count, barriers); } + /// + /// Binds an array of Descriptor Sets at the specified index in the current pipeline's Root Signature, + /// making shader resources available for rendering operations. + /// + /// + /// The starting slot where the Descriptor Sets will be bound. + /// + /// + /// An array of Descriptor Sets containing resource bindings (such as Textures, Samplers, and Constant Buffers) + /// to be used by the currently active Pipeline State. + /// public void SetDescriptorSets(int index, DescriptorSet[] descriptorSets) { RestartWithNewHeap: var descriptorTableIndex = 0; + for (int i = 0; i < descriptorSets.Length; ++i) { // Find what is already mapped - var descriptorSet = descriptorSets[i]; + scoped ref var descriptorSet = ref descriptorSets[i]; - var srvBindCount = boundPipelineState.SrvBindCounts[i]; - var samplerBindCount = boundPipelineState.SamplerBindCounts[i]; + var srvBindCount = boundPipelineState.SrvBindCountPerLayout[i]; + var samplerBindCount = boundPipelineState.SamplerBindCountPerLayout[i]; - if (srvBindCount > 0 && (IntPtr)descriptorSet.SrvStart.Ptr != IntPtr.Zero) + // Descriptors for SRVs, UAVs, and CBVs + if (srvBindCount > 0 && descriptorSet.SrvStart.Ptr != 0) { - - // Check if we need to copy them to shader visible descriptor heap - if (!srvMapping.TryGetValue(descriptorSet.SrvStart.Ptr, out var gpuSrvStart)) + // Check if we need to copy them to Shader-visible Descriptor heap + if (!srvMapping.TryGetValue(descriptorSet.SrvStart.Ptr, out GpuDescriptorHandle gpuSrvStart)) { var srvCount = descriptorSet.Description.SrvCount; // Make sure heap is big enough if (srvHeapOffset + srvCount > GraphicsDevice.SrvHeapSize) { - ResetSrvHeap(true); - currentCommandList.NativeCommandList.SetDescriptorHeaps(2, descriptorHeaps); + ResetSrvHeap(createNewHeap: true); + + currentCommandList.NativeCommandList.SetDescriptorHeaps(NumDescriptorHeaps: 2, in descriptorHeaps[0]); goto RestartWithNewHeap; } // Copy - NativeDevice.CopyDescriptorsSimple(srvCount, srvHeap.CPUDescriptorHandleForHeapStart + srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize, descriptorSet.SrvStart, DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); + var destHandle = new CpuDescriptorHandle(srvHeap.CPUDescriptorHandleForHeapStart.Ptr + + (nuint) (srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize)); + + NativeDevice.CopyDescriptorsSimple((uint) srvCount, destHandle, descriptorSet.SrvStart, DescriptorHeapType.CbvSrvUav); // Store mapping - srvMapping.Add(descriptorSet.SrvStart.Ptr, gpuSrvStart = srvHeap.GPUDescriptorHandleForHeapStart + srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize); + gpuSrvStart = new GpuDescriptorHandle(srvHeap.GPUDescriptorHandleForHeapStart.Ptr + + (nuint) (srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize)); + + srvMapping.Add(descriptorSet.SrvStart.Ptr, gpuSrvStart); // Bump srvHeapOffset += srvCount; } - // Bind resource tables (note: once per using stage, until we solve how to choose shader registers effect-wide at compile time) + // Bind resource tables + // NOTE: Done once per stage, until we solve how to choose shader registers effect-wide at compile time (TODO) if (IsComputePipelineStateBound) { for (int j = 0; j < srvBindCount; ++j) - currentCommandList.NativeCommandList.SetComputeRootDescriptorTable(descriptorTableIndex++, gpuSrvStart); + currentCommandList.NativeCommandList.SetComputeRootDescriptorTable((uint) descriptorTableIndex++, gpuSrvStart); } else { for (int j = 0; j < srvBindCount; ++j) - currentCommandList.NativeCommandList.SetGraphicsRootDescriptorTable(descriptorTableIndex++, gpuSrvStart); + currentCommandList.NativeCommandList.SetGraphicsRootDescriptorTable((uint) descriptorTableIndex++, gpuSrvStart); } } - if (samplerBindCount > 0 && (IntPtr) descriptorSet.SamplerStart.Ptr != IntPtr.Zero) + // Descriptors for Samplers + if (samplerBindCount > 0 && descriptorSet.SamplerStart.Ptr != 0) { - - // Check if we need to copy them to shader visible descriptor heap + // Check if we need to copy them to Shader-visible Descriptor heap if (!samplerMapping.TryGetValue(descriptorSet.SamplerStart.Ptr, out var gpuSamplerStart)) { var samplerCount = descriptorSet.Description.SamplerCount; @@ -377,439 +625,598 @@ public void SetDescriptorSets(int index, DescriptorSet[] descriptorSets) // Make sure heap is big enough if (samplerHeapOffset + samplerCount > GraphicsDevice.SamplerHeapSize) { - ResetSamplerHeap(true); - currentCommandList.NativeCommandList.SetDescriptorHeaps(2, descriptorHeaps); + ResetSamplerHeap(createNewHeap: true); + + currentCommandList.NativeCommandList.SetDescriptorHeaps(NumDescriptorHeaps: 2, in descriptorHeaps[0]); goto RestartWithNewHeap; } // Copy - NativeDevice.CopyDescriptorsSimple(samplerCount, samplerHeap.CPUDescriptorHandleForHeapStart + samplerHeapOffset * GraphicsDevice.SamplerHandleIncrementSize, descriptorSet.SamplerStart, DescriptorHeapType.Sampler); + var destHandle = new CpuDescriptorHandle(samplerHeap.CPUDescriptorHandleForHeapStart.Ptr + + (nuint) (samplerHeapOffset * GraphicsDevice.SamplerHandleIncrementSize)); + + NativeDevice.CopyDescriptorsSimple((uint) samplerCount, destHandle, descriptorSet.SamplerStart, DescriptorHeapType.Sampler); // Store mapping - samplerMapping.Add(descriptorSet.SamplerStart.Ptr, gpuSamplerStart = samplerHeap.GPUDescriptorHandleForHeapStart + samplerHeapOffset * GraphicsDevice.SamplerHandleIncrementSize); + gpuSamplerStart = new GpuDescriptorHandle(samplerHeap.GPUDescriptorHandleForHeapStart.Ptr + + (nuint) (samplerHeapOffset * GraphicsDevice.SamplerHandleIncrementSize)); + + samplerMapping.Add(descriptorSet.SamplerStart.Ptr, gpuSamplerStart); // Bump samplerHeapOffset += samplerCount; } - // Bind resource tables (note: once per using stage, until we solve how to choose shader registers effect-wide at compile time) + // Bind resource tables + // NOTE: Done once per stage, until we solve how to choose shader registers effect-wide at compile time (TODO) if (IsComputePipelineStateBound) { for (int j = 0; j < samplerBindCount; ++j) - currentCommandList.NativeCommandList.SetComputeRootDescriptorTable(descriptorTableIndex++, gpuSamplerStart); + currentCommandList.NativeCommandList.SetComputeRootDescriptorTable((uint) descriptorTableIndex++, gpuSamplerStart); } else { for (int j = 0; j < samplerBindCount; ++j) - currentCommandList.NativeCommandList.SetGraphicsRootDescriptorTable(descriptorTableIndex++, gpuSamplerStart); + currentCommandList.NativeCommandList.SetGraphicsRootDescriptorTable((uint) descriptorTableIndex++, gpuSamplerStart); } } } } + /// + /// Resets the current Descriptor heap for Shader Resource Views (SRV), optionally creating a new heap. + /// + /// + /// A value indicating whether to create a new SRV heap. + /// If , a new heap is retrieved from the pool; otherwise, the current heap is recycled. + /// + /// + /// This method manages the lifecycle of the SRV heap by either recycling the existing heap or + /// creating a new one from the pool. + /// The current heap is cleared and its resources are prepared for reuse if applicable. + /// The method also updates the Descriptor heap array to reference the new active SRV heap. + /// private void ResetSrvHeap(bool createNewHeap) { - if (srvHeap.Heap != null) + // If there is currently a heap, recycle it + if (srvHeap.Heap.IsNotNull()) { - currentCommandList.SrvHeaps.Add(srvHeap.Heap); - srvHeap.Heap = null; + currentCommandList.SrvHeaps.Add(srvHeap); + srvHeap.Heap = default; } + // If we need to create a new heap, get one from the pool if (createNewHeap) { - srvHeap = new DescriptorHeapCache(GraphicsDevice.SrvHeaps.GetObject()); + srvHeap = GraphicsDevice.SrvHeaps.GetObject(); srvHeapOffset = 0; srvMapping.Clear(); } + // Update the Descriptor heap array to reference the new active SRV heap descriptorHeaps[0] = srvHeap.Heap; } + /// + /// Resets the current Descriptor heap for Samplers, optionally creating a new heap. + /// + /// + /// A value indicating whether to create a new Samplers heap. + /// If , a new heap is retrieved from the pool; otherwise, the current heap is recycled. + /// + /// + /// This method manages the lifecycle of the Samplers heap by either recycling the existing heap or + /// creating a new one from the pool. + /// The current heap is cleared and its resources are prepared for reuse if applicable. + /// The method also updates the Descriptor heap array to reference the new active Samplers heap. + /// private void ResetSamplerHeap(bool createNewHeap) { - if (samplerHeap.Heap != null) + // If there is currently a heap, recycle it + if (samplerHeap.Heap.IsNotNull()) { - currentCommandList.SamplerHeaps.Add(samplerHeap.Heap); - samplerHeap.Heap = null; + currentCommandList.SamplerHeaps.Add(samplerHeap); + samplerHeap.Heap = default; } + // If we need to create a new heap, get one from the pool if (createNewHeap) { - samplerHeap = new DescriptorHeapCache(GraphicsDevice.SamplerHeaps.GetObject()); + samplerHeap = GraphicsDevice.SamplerHeaps.GetObject(); samplerHeapOffset = 0; samplerMapping.Clear(); } + // Update the Descriptor heap array to reference the new active Samplers heap descriptorHeaps[1] = samplerHeap.Heap; } - /// + /// + /// Dispatches a Compute Shader workload with the specified number of thread groups in each dimension. + /// + /// Number of thread groups in the X dimension. + /// Number of thread groups in the Y dimension. + /// Number of thread groups in the Z dimension. public void Dispatch(int threadCountX, int threadCountY, int threadCountZ) { - PrepareDraw(); + PrepareDraw(); // TODO: PrepareDraw for Compute dispatch? - currentCommandList.NativeCommandList.Dispatch(threadCountX, threadCountY, threadCountZ); + currentCommandList.NativeCommandList.Dispatch((uint) threadCountX, (uint) threadCountY, (uint) threadCountZ); } /// - /// Dispatches the specified indirect buffer. + /// Dispatches a Compute Shader workload using an Indirect Buffer, allowing the thread group count to be determined at runtime. /// - /// The indirect buffer. - /// The offset information bytes. + /// + /// A Buffer containing the dispatch parameters, structured as (threadCountX, threadCountY, threadCountZ). + /// + /// + /// The byte offset within the where the dispatch parameters are located. + /// + /// is . public void Dispatch(Buffer indirectBuffer, int offsetInBytes) { - throw new NotImplementedException(); + throw new NotImplementedException(); // TODO: Implement DispatchIndirect } /// - /// Draw non-indexed, non-instanced primitives. + /// Issues a non-indexed draw call, rendering a sequence of vertices directly from the bound Vertex Buffer. /// - /// Number of vertices to draw. - /// Index of the first vertex, which is usually an offset in a vertex buffer; it could also be used as the first vertex id generated for a shader parameter marked with the SV_TargetId system-value semantic. + /// The number of vertices to draw. + /// + /// Index of the first vertex in the Vertex Buffer to begin rendering from; + /// it could also be used as the first vertex id generated for a shader parameter marked with the SV_TargetId system-value semantic. + /// public void Draw(int vertexCount, int startVertexLocation = 0) { PrepareDraw(); - currentCommandList.NativeCommandList.DrawInstanced(vertexCount, 1, startVertexLocation, 0); + currentCommandList.NativeCommandList.DrawInstanced((uint) vertexCount, InstanceCount: 1, + (uint) startVertexLocation, StartInstanceLocation: 0); GraphicsDevice.FrameTriangleCount += (uint)vertexCount; GraphicsDevice.FrameDrawCalls++; } /// - /// Draw geometry of an unknown size. + /// Issues a draw call for geometry of unknown size, typically used with Vertex or Index Buffers populated via Stream Output. + /// The vertex count is inferred from the data written by the GPU. /// public void DrawAuto() { PrepareDraw(); - throw new NotImplementedException(); + throw new NotImplementedException(); // TODO: Implement DrawAuto } /// - /// Draw indexed, non-instanced primitives. + /// Issues an indexed non-instanced draw call using the currently bound Index Buffer. /// /// Number of indices to draw. - /// The location of the first index read by the GPU from the index buffer. - /// A value added to each index before reading a vertex from the vertex buffer. + /// The location of the first index read by the GPU from the Index Buffer. + /// A value added to each index before reading a vertex from the Vertex Buffer. public void DrawIndexed(int indexCount, int startIndexLocation = 0, int baseVertexLocation = 0) { PrepareDraw(); - currentCommandList.NativeCommandList.DrawIndexedInstanced(indexCount, instanceCount: 1, startIndexLocation, baseVertexLocation, startInstanceLocation: 0); - + currentCommandList.NativeCommandList.DrawIndexedInstanced((uint) indexCount, InstanceCount: 1, + (uint) startIndexLocation, baseVertexLocation, + StartInstanceLocation: 0); GraphicsDevice.FrameDrawCalls++; GraphicsDevice.FrameTriangleCount += (uint) indexCount; } /// - /// Draw indexed, instanced primitives. + /// Issues an indexed instanced draw call, rendering multiple instances of indexed geometry. /// - /// Number of indices read from the index buffer for each instance. + /// Number of indices read from the Index Buffer for each instance. /// Number of instances to draw. - /// The location of the first index read by the GPU from the index buffer. - /// A value added to each index before reading a vertex from the vertex buffer. - /// A value added to each index before reading per-instance data from a vertex buffer. + /// The location of the first index read by the GPU from the Index Buffer. + /// A value added to each index before reading a vertex from the Vertex Buffer. + /// A value added to each index before reading per-instance data from a Vertex Buffer. public void DrawIndexedInstanced(int indexCountPerInstance, int instanceCount, int startIndexLocation = 0, int baseVertexLocation = 0, int startInstanceLocation = 0) { PrepareDraw(); - currentCommandList.NativeCommandList.DrawIndexedInstanced(indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation); - + currentCommandList.NativeCommandList.DrawIndexedInstanced((uint) indexCountPerInstance, + (uint) instanceCount, + (uint) startIndexLocation, baseVertexLocation, + (uint) startInstanceLocation); GraphicsDevice.FrameDrawCalls++; GraphicsDevice.FrameTriangleCount += (uint) (indexCountPerInstance * instanceCount); } /// - /// Draw indexed, instanced, GPU-generated primitives. + /// Issues an indexed instanced draw call using an Indirect Arguments Buffer, allowing parameters to be determined at runtime. /// - /// A buffer containing the GPU generated primitives. - /// Offset in pBufferForArgs to the start of the GPU generated primitives. + /// A Buffer containing the draw parameters (index count, instance count, etc.). + /// + /// Byte offset within the where the draw arguments are located. + /// + /// is . public void DrawIndexedInstanced(Buffer argumentsBuffer, int alignedByteOffsetForArgs = 0) { - if (argumentsBuffer == null) throw new ArgumentNullException("argumentsBuffer"); + ArgumentNullException.ThrowIfNull(argumentsBuffer); PrepareDraw(); //NativeDeviceContext.DrawIndexedInstancedIndirect(argumentsBuffer.NativeBuffer, alignedByteOffsetForArgs); - throw new NotImplementedException(); + throw new NotImplementedException(); // TODO: Implement DrawIndexedInstancedIndirect GraphicsDevice.FrameDrawCalls++; } /// - /// Draw non-indexed, instanced primitives. + /// Issues a non-indexed instanced draw call, rendering multiple instances of geometry using a Vertex Buffer. /// - /// Number of vertices to draw. - /// Number of instances to draw. - /// Index of the first vertex. - /// A value added to each index before reading per-instance data from a vertex buffer. + /// The number of vertices to draw per instance. + /// The number of instances to draw. + /// The index of the first vertex in the Vertex Buffer. + /// A value added to each index before reading per-instance data from a Vertex Buffer. public void DrawInstanced(int vertexCountPerInstance, int instanceCount, int startVertexLocation = 0, int startInstanceLocation = 0) { PrepareDraw(); - currentCommandList.NativeCommandList.DrawInstanced(vertexCountPerInstance, instanceCount, startVertexLocation, startInstanceLocation); - + currentCommandList.NativeCommandList.DrawInstanced((uint) vertexCountPerInstance, (uint) instanceCount, + (uint) startVertexLocation, (uint) startInstanceLocation); GraphicsDevice.FrameDrawCalls++; GraphicsDevice.FrameTriangleCount += (uint) (vertexCountPerInstance * instanceCount); } /// - /// Draw instanced, GPU-generated primitives. + /// Issues a non-indexed instanced draw call using an Indirect Arguments Buffer, allowing parameters to be determined at runtime. /// - /// An arguments buffer - /// Offset in pBufferForArgs to the start of the GPU generated primitives. + /// A Buffer containing the draw parameters (vertex count, instance count, etc.). + /// + /// Byte offset within the where the draw arguments are located. + /// + /// is . public void DrawInstanced(Buffer argumentsBuffer, int alignedByteOffsetForArgs = 0) { - if (argumentsBuffer == null) throw new ArgumentNullException("argumentsBuffer"); + ArgumentNullException.ThrowIfNull(argumentsBuffer); PrepareDraw(); //NativeDeviceContext.DrawIndexedInstancedIndirect(argumentsBuffer.NativeBuffer, alignedByteOffsetForArgs); - throw new NotImplementedException(); + throw new NotImplementedException(); // TODO: Implement DrawInstancedIndirect GraphicsDevice.FrameDrawCalls++; } /// - /// Begins profiling. + /// Submits a GPU timestamp Query. /// - /// Color of the profile. - /// The name. - public void BeginProfile(Color4 profileColor, string name) + /// The owning the Query. + /// The index of the Query to write. + public void WriteTimestamp(QueryPool queryPool, int index) { - //currentCommandList.NativeCommandList.BeginEvent(); + currentCommandList.NativeCommandList.EndQuery(queryPool.NativeQueryHeap, Silk.NET.Direct3D12.QueryType.Timestamp, (uint) index); + + queryPool.PendingValue = queryPool.CompletedValue + 1; } /// - /// Ends profiling. + /// Marks the beginning of a profile section. /// - public void EndProfile() + /// A color that a profiling tool can use to display the event. + /// A descriptive name for the profile section. + /// + /// + /// Each call to must be matched with a corresponding call to , + /// which defines a region of code that can be identified with a name and possibly a color in a compatible + /// profiling tool. + /// + /// + /// A pair of calls to and can be nested inside a call + /// to and in an upper level in the call stack. + /// This allows to form a hierarchy of profile sections. + /// + /// + public void BeginProfile(Color4 profileColor, string name) { - //currentCommandList.NativeCommandList.EndEvent(); + //currentCommandList.NativeCommandList.BeginEvent(); // TODO: Implement profiling } /// - /// Submit a timestamp query. + /// Marks the end of a profile section previously started by a call to . /// - /// The QueryPool owning the query. - /// The query index. - public void WriteTimestamp(QueryPool queryPool, int index) + /// + public void EndProfile() { - currentCommandList.NativeCommandList.EndQuery(queryPool.NativeQueryHeap, SharpDX.Direct3D12.QueryType.Timestamp, index); - queryPool.PendingValue = queryPool.CompletedValue + 1; + //currentCommandList.NativeCommandList.EndEvent(); // TODO: Implement profiling } + // TODO: Unused, remove? public void ResetQueryPool(QueryPool queryPool) { } /// - /// Clears the specified depth stencil buffer. See to learn how to use it. + /// Clears the specified Depth-Stencil Buffer. /// - /// The depth stencil buffer. - /// The options. - /// The depth. - /// The stencil. - /// + /// The Depth-Stencil Buffer to clear. + /// + /// A combination of flags identifying what parts of the Depth-Stencil Buffer to clear. + /// + /// The depth value to use for clearing the Depth Buffer. + /// The stencil value to use for clearing the Stencil Buffer. + /// is . + /// Cannot clear a Stencil Buffer without a Stencil Buffer format. public void Clear(Texture depthStencilBuffer, DepthStencilClearOptions options, float depth = 1, byte stencil = 0) { + ArgumentNullException.ThrowIfNull(depthStencilBuffer); + ResourceBarrierTransition(GraphicsDevice.Presenter.DepthStencilBuffer, GraphicsResourceState.DepthWrite); FlushResourceBarriers(); - currentCommandList.NativeCommandList.ClearDepthStencilView(depthStencilBuffer.NativeDepthStencilView, (ClearFlags) options, depth, stencil); + + // Check that the Depth-Stencil Buffer has a Stencil if Clear Stencil is requested + if (options.HasFlag(DepthStencilClearOptions.Stencil)) + { + if (!depthStencilBuffer.HasStencil) + throw new InvalidOperationException(string.Format(FrameworkResources.NoStencilBufferForDepthFormat, depthStencilBuffer.ViewFormat)); + } + + scoped ref SilkBox2I nullRect = ref NullRef(); + + currentCommandList.NativeCommandList.ClearDepthStencilView(depthStencilBuffer.NativeDepthStencilView, + (ClearFlags) options, depth, stencil, + NumRects: 0, in nullRect); } /// - /// Clears the specified render target. See to learn how to use it. + /// Clears the specified Render Target. /// - /// The render target. - /// The color. - /// renderTarget - public unsafe void Clear(Texture renderTarget, Color4 color) + /// The Render Target to clear. + /// The color to use to clear the Render Target. + /// is . + public void Clear(Texture renderTarget, Color4 color) { + ArgumentNullException.ThrowIfNull(renderTarget); + ResourceBarrierTransition(renderTarget, GraphicsResourceState.RenderTarget); FlushResourceBarriers(); - currentCommandList.NativeCommandList.ClearRenderTargetView(renderTarget.NativeRenderTargetView, *(RawColor4*) &color); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearColorFloats = ref color.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearRenderTargetView(renderTarget.NativeRenderTargetView, ref clearColorFloats, + NumRects: 0, in nullRect); } /// - /// Clears a read-write Buffer. This buffer must have been created with read-write/unordered access. + /// Clears a Read-Write Buffer. /// - /// The buffer. - /// The value. - /// buffer - /// Expecting buffer supporting UAV;buffer - public unsafe void ClearReadWrite(Buffer buffer, Vector4 value) + /// The Buffer to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Buffer. + /// is . + /// must support Unordered Access. + public void ClearReadWrite(Buffer buffer, Vector4 value) { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (buffer.NativeUnorderedAccessView.Ptr == PointerSize.Zero) throw new ArgumentException("Expecting buffer supporting UAV", nameof(buffer)); + ArgumentNullException.ThrowIfNull(buffer); + + if (buffer.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); var cpuHandle = buffer.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); - currentCommandList.NativeCommandList.ClearUnorderedAccessViewFloat(gpuHandle, cpuHandle, buffer.NativeResource, *(RawVector4*) &value, numRects: 0, rectsRef: null); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearValue = ref value.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearUnorderedAccessViewFloat(gpuHandle, cpuHandle, buffer.NativeResource, + ref clearValue, NumRects: 0, in nullRect); } /// - /// Clears a read-write Buffer. This buffer must have been created with read-write/unordered access. + /// Clears a Read-Write Buffer. /// - /// The buffer. - /// The value. - /// buffer - /// Expecting buffer supporting UAV;buffer - public unsafe void ClearReadWrite(Buffer buffer, Int4 value) + /// The Buffer to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Buffer. + /// is . + /// must support Unordered Access. + public void ClearReadWrite(Buffer buffer, Int4 value) { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (buffer.NativeUnorderedAccessView.Ptr == PointerSize.Zero) throw new ArgumentException("Expecting buffer supporting UAV", nameof(buffer)); + ArgumentNullException.ThrowIfNull(buffer); + + if (buffer.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); var cpuHandle = buffer.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); - currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, buffer.NativeResource, *(RawInt4*) &value, numRects: 0, rectsRef: null); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearValue = ref value.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, buffer.NativeResource, + ref clearValue, NumRects: 0, in nullRect); } /// - /// Clears a read-write Buffer. This buffer must have been created with read-write/unordered access. + /// Clears a Read-Write Buffer. /// - /// The buffer. - /// The value. - /// buffer - /// Expecting buffer supporting UAV;buffer - public unsafe void ClearReadWrite(Buffer buffer, UInt4 value) + /// The Buffer to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Buffer. + /// is . + /// must support Unordered Access. + public void ClearReadWrite(Buffer buffer, UInt4 value) { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - if (buffer.NativeUnorderedAccessView.Ptr == PointerSize.Zero) throw new ArgumentException("Expecting buffer supporting UAV", nameof(buffer)); + ArgumentNullException.ThrowIfNull(buffer); + + if (buffer.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException("Expecting a Buffer supporting UAV", nameof(buffer)); var cpuHandle = buffer.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); - currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, buffer.NativeResource, *(RawInt4*) &value, numRects: 0, rectsRef: null); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearValue = ref value.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, buffer.NativeResource, + ref clearValue, NumRects: 0, in nullRect); } /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. + /// Clears a Read-Write Texture. /// - /// The texture. - /// The value. - /// texture - /// Expecting texture supporting UAV;texture - public unsafe void ClearReadWrite(Texture texture, Vector4 value) + /// The Texture to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Texture. + /// is . + /// must support Unordered Access. + public void ClearReadWrite(Texture texture, Vector4 value) { - if (texture == null) throw new ArgumentNullException(nameof(texture)); - if (texture.NativeUnorderedAccessView.Ptr == PointerSize.Zero) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ArgumentNullException.ThrowIfNull(texture); + + if (texture.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); var cpuHandle = texture.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); - currentCommandList.NativeCommandList.ClearUnorderedAccessViewFloat(gpuHandle, cpuHandle, texture.NativeResource, *(RawVector4*) &value, numRects: 0, rectsRef: null); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearValue = ref value.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearUnorderedAccessViewFloat(gpuHandle, cpuHandle, texture.NativeResource, + ref clearValue, NumRects: 0, in nullRect); } /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. + /// Clears a Read-Write Texture. /// - /// The texture. - /// The value. - /// texture - /// Expecting texture supporting UAV;texture - public unsafe void ClearReadWrite(Texture texture, Int4 value) + /// The Texture to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Texture. + /// is . + /// must support Unordered Access. + public void ClearReadWrite(Texture texture, Int4 value) { - if (texture == null) throw new ArgumentNullException(nameof(texture)); - if (texture.NativeUnorderedAccessView.Ptr == PointerSize.Zero) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ArgumentNullException.ThrowIfNull(texture); + + if (texture.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); var cpuHandle = texture.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); - currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, texture.NativeResource, *(RawInt4*) &value, numRects: 0, rectsRef: null); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearValue = ref value.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, texture.NativeResource, + ref clearValue, NumRects: 0, in nullRect); } /// - /// Clears a read-write Texture. This texture must have been created with read-write/unordered access. + /// Clears a Read-Write Texture. /// - /// The texture. - /// The value. - /// texture - /// Expecting texture supporting UAV;texture - public unsafe void ClearReadWrite(Texture texture, UInt4 value) + /// The Texture to clear. It must have been created with read-write / unordered access flags. + /// The value to use to clear the Texture. + /// is . + /// must support Unordered Access. + public void ClearReadWrite(Texture texture, UInt4 value) { - if (texture == null) throw new ArgumentNullException(nameof(texture)); - if (texture.NativeUnorderedAccessView.Ptr == PointerSize.Zero) throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); + ArgumentNullException.ThrowIfNull(texture); + + if (texture.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException("Expecting texture supporting UAV", nameof(texture)); var cpuHandle = texture.NativeUnorderedAccessView; var gpuHandle = GetGpuDescriptorHandle(cpuHandle); - currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, texture.NativeResource, *(RawInt4*) &value, numRects: 0, rectsRef: null); + + scoped ref SilkBox2I nullRect = ref NullRef(); + scoped ref var clearValue = ref value.AsSpan()[0]; + + currentCommandList.NativeCommandList.ClearUnorderedAccessViewUint(gpuHandle, cpuHandle, texture.NativeResource, + ref clearValue, NumRects: 0, in nullRect); } + /// + /// Retrieves the GPU Descriptor handle corresponding to the specified CPU Descriptor handle. + /// + /// The CPU descriptor handle for which the GPU descriptor handle is requested. + /// + /// A that corresponds to the provided . + /// If the mapping does not already exist, a new GPU Descriptor handle is created, and the mapping is stored. + /// private GpuDescriptorHandle GetGpuDescriptorHandle(CpuDescriptorHandle cpuHandle) { - if (!srvMapping.TryGetValue(cpuHandle.Ptr, out var result)) + if (!srvMapping.TryGetValue(cpuHandle.Ptr, out var resultGpuHandle)) { var srvCount = 1; // Make sure heap is big enough if (srvHeapOffset + srvCount > GraphicsDevice.SrvHeapSize) { - ResetSrvHeap(true); - currentCommandList.NativeCommandList.SetDescriptorHeaps(2, descriptorHeaps); + ResetSrvHeap(createNewHeap: true); + + currentCommandList.NativeCommandList.SetDescriptorHeaps(NumDescriptorHeaps: 2, in descriptorHeaps[0]); } // Copy - NativeDevice.CopyDescriptorsSimple(srvCount, srvHeap.CPUDescriptorHandleForHeapStart + srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize, cpuHandle, DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); + var destHandle = new CpuDescriptorHandle(srvHeap.CPUDescriptorHandleForHeapStart.Ptr + + (nuint) (srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize)); + + NativeDevice.CopyDescriptorsSimple((uint) srvCount, destHandle, cpuHandle, DescriptorHeapType.CbvSrvUav); // Store mapping - srvMapping.Add(cpuHandle.Ptr, result = srvHeap.GPUDescriptorHandleForHeapStart + srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize); + resultGpuHandle = new GpuDescriptorHandle(srvHeap.GPUDescriptorHandleForHeapStart.Ptr + + (nuint) (srvHeapOffset * GraphicsDevice.SrvHandleIncrementSize)); + + srvMapping.Add(cpuHandle.Ptr, resultGpuHandle); // Bump srvHeapOffset += srvCount; } - return result; + return resultGpuHandle; } - public unsafe void Copy(GraphicsResource source, GraphicsResource destination) + /// + /// Copies the data from a Graphics Resource to another. + /// Views are ignored and the full underlying data is copied. + /// + /// The source Graphics Resource. + /// The destination Graphics Resource. + /// is . + /// is . + /// + /// The source and destination Graphics Resources are of incompatible types. + /// + public void Copy(GraphicsResource source, GraphicsResource destination) { - // Copy texture -> texture - if (source is Texture sourceTexture && destination is Texture destinationTexture) + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(destination); + + // Copy Texture -> Texture + if (source is Texture sourceTexture && + destination is Texture destinationTexture) { + CopyBetweenTextures(sourceTexture, destinationTexture); + } + // Copy Buffer -> Buffer + else if (source is Buffer sourceBuffer && + destination is Buffer destinationBuffer) + { + CopyBetweenBuffers(sourceBuffer, destinationBuffer); + } + else throw new InvalidOperationException($"Cannot copy data between GraphicsResources of types [{source.GetType()}] and [{destination.GetType()}]."); + + // + // Copies the data from a Texture to another Texture. + // + void CopyBetweenTextures(Texture sourceTexture, Texture destinationTexture) + { + // Get the parent Textures in case these are Texture Views var sourceParent = sourceTexture.ParentTexture ?? sourceTexture; var destinationParent = destinationTexture.ParentTexture ?? destinationTexture; if (destinationTexture.Usage == GraphicsResourceUsage.Staging) { - // Copy staging texture -> staging texture + // Copy staging Texture -> staging Texture if (sourceTexture.Usage == GraphicsResourceUsage.Staging) { - var size = destinationTexture.ComputeBufferTotalSize(); - var destinationMapped = destinationTexture.NativeResource.Map(0); - var sourceMapped = sourceTexture.NativeResource.Map(0, new SharpDX.Direct3D12.Range { Begin = 0, End = size }); - - Unsafe.CopyBlockUnaligned((void*) destinationMapped, (void*) sourceMapped, (uint) size); - - sourceTexture.NativeResource.Unmap(0); - destinationTexture.NativeResource.Unmap(0); + CopyStagingTextureToStagingTexture(sourceTexture, destinationTexture); } - else + else // Copy Texture -> staging Texture { - ResourceBarrierTransition(sourceTexture, GraphicsResourceState.CopySource); - ResourceBarrierTransition(destinationTexture, GraphicsResourceState.CopyDestination); - FlushResourceBarriers(); - - int copyOffset = 0; - for (int arraySlice = 0; arraySlice < sourceParent.ArraySize; ++arraySlice) - { - for (int mipLevel = 0; mipLevel < sourceParent.MipLevels; ++mipLevel) - { - currentCommandList.NativeCommandList.CopyTextureRegion(new TextureCopyLocation(destinationTexture.NativeResource, - new PlacedSubResourceFootprint - { - Footprint = - { - Width = Texture.CalculateMipSize(destinationTexture.Width, mipLevel), - Height = Texture.CalculateMipSize(destinationTexture.Height, mipLevel), - Depth = Texture.CalculateMipSize(destinationTexture.Depth, mipLevel), - Format = (SharpDX.DXGI.Format)destinationTexture.Format, - RowPitch = destinationTexture.ComputeRowPitch(mipLevel), - }, - Offset = copyOffset, - }), 0, 0, 0, new TextureCopyLocation(sourceTexture.NativeResource, arraySlice * sourceParent.MipLevels + mipLevel), null); - - copyOffset += destinationTexture.ComputeSubresourceSize(mipLevel); - } - } + CopyTextureToStagingTexture(sourceTexture, sourceParent, destinationTexture); } // Fence for host access @@ -817,17 +1224,102 @@ public unsafe void Copy(GraphicsResource source, GraphicsResource destination) destinationParent.StagingBuilder = this; currentCommandList.StagingResources.Add(destinationParent); } - else + else // Copy Texture -> Texture + { + CopyTextureToTexture(sourceTexture, destinationTexture); + } + } + + // + // Copies the data from a staging Texture to another staging Texture. + // + void CopyStagingTextureToStagingTexture(Texture sourceTexture, Texture destinationTexture) + { + var size = destinationTexture.ComputeBufferTotalSize(); + + scoped ref var fullRange = ref NullRef(); + + void* destinationMapped = null; + HResult result = destinationTexture.NativeResource.Map(Subresource: 0, in fullRange, ref destinationMapped); + + if (result.IsFailure) + result.Throw(); + + void* sourceMapped = null; + var sourceRange = new D3D12Range { Begin = 0, End = (nuint) size }; + result = sourceTexture.NativeResource.Map(Subresource: 0, in sourceRange, ref sourceMapped); + + if (result.IsFailure) + result.Throw(); + + CopyBlockUnaligned(destinationMapped, sourceMapped, (uint) size); + + sourceTexture.NativeResource.Unmap(Subresource: 0, ref fullRange); + destinationTexture.NativeResource.Unmap(Subresource: 0, ref fullRange); + } + + // + // Copies the data from a Texture to a staging Texture. + // + void CopyTextureToStagingTexture(Texture sourceTexture, Texture sourceParent, Texture destinationTexture) + { + ResourceBarrierTransition(sourceTexture, GraphicsResourceState.CopySource); + ResourceBarrierTransition(destinationTexture, GraphicsResourceState.CopyDestination); + FlushResourceBarriers(); + + int copyOffset = 0; + for (int arraySlice = 0; arraySlice < sourceParent.ArraySize; ++arraySlice) { - ResourceBarrierTransition(sourceTexture, GraphicsResourceState.CopySource); - ResourceBarrierTransition(destinationTexture, GraphicsResourceState.CopyDestination); - FlushResourceBarriers(); + for (int mipLevel = 0; mipLevel < sourceParent.MipLevelCount; ++mipLevel) + { + var destRegion = new TextureCopyLocation + { + PResource = destinationTexture.NativeResource, + Type = TextureCopyType.PlacedFootprint, + PlacedFootprint = new() + { + Offset = (ulong) copyOffset, + Footprint = + { + Width = (uint) Texture.CalculateMipSize(destinationTexture.Width, mipLevel), + Height = (uint) Texture.CalculateMipSize(destinationTexture.Height, mipLevel), + Depth = (uint) Texture.CalculateMipSize(destinationTexture.Depth, mipLevel), + Format = (Format) destinationTexture.Format, + RowPitch = (uint) destinationTexture.ComputeRowPitch(mipLevel) + } + } + }; + var srcRegion = new TextureCopyLocation + { + PResource = sourceTexture.NativeResource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = (uint) (arraySlice * sourceParent.MipLevelCount + mipLevel) + }; + + currentCommandList.NativeCommandList.CopyTextureRegion(in destRegion, DstX: 0, DstY: 0, DstZ: 0, + in srcRegion, pSrcBox: null); - currentCommandList.NativeCommandList.CopyResource(destinationTexture.NativeResource, sourceTexture.NativeResource); + copyOffset += destinationTexture.ComputeSubResourceSize(mipLevel); + } } } - // Copy buffer -> buffer - else if (source is Buffer sourceBuffer && destination is Buffer destinationBuffer) + + // + // Copies the data from a Texture to another Texture. + // + void CopyTextureToTexture(Texture sourceTexture, Texture destinationTexture) + { + ResourceBarrierTransition(sourceTexture, GraphicsResourceState.CopySource); + ResourceBarrierTransition(destinationTexture, GraphicsResourceState.CopyDestination); + FlushResourceBarriers(); + + currentCommandList.NativeCommandList.CopyResource(destinationTexture.NativeResource, sourceTexture.NativeResource); + } + + // + // Copies the data from a Buffer to another Buffer. + // + void CopyBetweenBuffers(Buffer sourceBuffer, Buffer destinationBuffer) { ResourceBarrierTransition(sourceBuffer, GraphicsResourceState.CopySource); ResourceBarrierTransition(destinationBuffer, GraphicsResourceState.CopyDestination); @@ -843,166 +1335,588 @@ public unsafe void Copy(GraphicsResource source, GraphicsResource destination) currentCommandList.StagingResources.Add(destinationBuffer); } } - else - { - throw new NotImplementedException(); - } } - public void CopyMultisample(Texture sourceMultisampleTexture, int sourceSubResource, Texture destTexture, int destSubResource, PixelFormat format = PixelFormat.None) + /// + /// Copies the data from a multi-sampled Texture (which is resolved) to another Texture. + /// + /// The source multi-sampled Texture. + /// The sub-resource index of the source Texture. + /// The destination Texture. + /// The sub-resource index of the destination Texture. + /// + /// A that indicates how the multi-sampled Texture will be resolved to a single-sampled resource. + /// + /// is not a multi-sampled Texture. + /// is . + /// is . + /// + /// The and must have the same dimensions. + /// In addition, they must have compatible formats. There are three scenarios for this: + /// + /// + /// Scenario + /// Requirements + /// + /// + /// Source and destination are prestructured and typed + /// + /// Both the source and destination must have identical formats and that format must be specified in the parameter. + /// + /// + /// + /// One resource is prestructured and typed and the other is prestructured and typeless + /// + /// The typed resource must have a format that is compatible with the typeless resource (i.e. the typed resource is + /// and the typeless resource is ). + /// The format of the typed resource must be specified in the parameter. + /// + /// + /// + /// Source and destination are prestructured and typeless + /// + /// + /// Both the source and destination must have the same typeless format (i.e. both must have ), and the + /// parameter must specify a format that is compatible with the source and destination + /// (i.e. if both are then could be specified in the + /// parameter). + /// + /// + /// For example, given the format: + /// + /// The source (or destination) format could be . + /// The destination (or source) format could be . + /// + /// + /// + /// + /// + /// + public void CopyMultisample(Texture sourceMultiSampledTexture, int sourceSubResourceIndex, + Texture destinationTexture, int destinationSubResourceIndex, + PixelFormat format = PixelFormat.None) { - if (sourceMultisampleTexture == null) throw new ArgumentNullException(nameof(sourceMultisampleTexture)); - if (destTexture == null) throw new ArgumentNullException(nameof(destTexture)); - if (!sourceMultisampleTexture.IsMultisample) throw new ArgumentOutOfRangeException(nameof(sourceMultisampleTexture), "Source texture is not a MSAA texture"); + ArgumentNullException.ThrowIfNull(sourceMultiSampledTexture); + ArgumentNullException.ThrowIfNull(destinationTexture); - currentCommandList.NativeCommandList.ResolveSubresource(sourceMultisampleTexture.NativeResource, sourceSubResource, destTexture.NativeResource, destSubResource, (SharpDX.DXGI.Format)(format == PixelFormat.None ? destTexture.Format : format)); + if (!sourceMultiSampledTexture.IsMultiSampled) + throw new ArgumentException("Source Texture is not a MSAA Texture", nameof(sourceMultiSampledTexture)); + + currentCommandList.NativeCommandList.ResolveSubresource(sourceMultiSampledTexture.NativeResource, (uint) sourceSubResourceIndex, + destinationTexture.NativeResource, (uint) destinationSubResourceIndex, + (Format)(format == PixelFormat.None ? destinationTexture.Format : format)); } - public void CopyRegion(GraphicsResource source, int sourceSubresource, ResourceRegion? sourceRegion, GraphicsResource destination, int destinationSubResource, int dstX = 0, int dstY = 0, int dstZ = 0) + /// + /// Copies a region from a source Graphics Resource to a destination Graphics Resource. + /// + /// The source Graphics Resource to copy from. + /// The index of the sub-resource of to copy from. + /// + /// + /// An optional that defines the source sub-resource to copy from. + /// Specify the entire source sub-resource is copied. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// The destination Graphics Resource to copy to. + /// The index of the sub-resource of to copy to. + /// The X-coordinate of the upper left corner of the destination region. + /// The Y-coordinate of the upper left corner of the destination region. For a 1D sub-resource, this must be zero. + /// The Z-coordinate of the upper left corner of the destination region. For a 1D or 2D sub-resource, this must be zero. + /// is . + /// is . + /// Copying regions of staging resources is not currently supported. + /// + /// The Graphics Resources are incompatible. Cannot copy data between Buffers and Textures. + /// + /// + /// + /// The must be within the size of the source resource. + /// The destination offsets, (, , and ), + /// allow the source region to be offset when writing into the destination resource; + /// however, the dimensions of the source region and the offsets must be within the size of the resource. + /// + /// + /// If the resources are Buffers, all coordinates are in bytes; if the resources are Textures, all coordinates are in texels. + /// + /// + /// performs the copy on the GPU (similar to a memcpy by the CPU). As a consequence, + /// the source and destination resources: + /// + /// Must be different sub-resources (although they can be from the same Graphics Resource). + /// Must be the same type. + /// + /// Must have compatible formats (identical or from the same type group). For example, a Texture + /// can be copied to a Texture since both of these formats are in the + /// group. + /// + /// May not be currently mapped with . + /// + /// + /// + /// only supports copy; it doesn't support any stretch, color key, or blend. + /// + /// + public void CopyRegion(GraphicsResource source, int sourceSubResourceIndex, ResourceRegion? sourceRegion, + GraphicsResource destination, int destinationSubResourceIndex, int dstX = 0, int dstY = 0, int dstZ = 0) { - if (source is Texture sourceTexture && destination is Texture destinationTexture) + if (source is Texture sourceTexture && + destination is Texture destinationTexture) + { + CopyBetweenTextures(sourceTexture, destinationTexture); + } + else if (source is Buffer sourceBuffer && + destination is Buffer destinationBuffer) { - if (sourceTexture.Usage == GraphicsResourceUsage.Staging || destinationTexture.Usage == GraphicsResourceUsage.Staging) + CopyBetweenBuffers(sourceBuffer, destinationBuffer); + } + else throw new InvalidOperationException("Cannot copy data between Buffers and Textures."); + + // + // Copies a region from a Texture to another Texture. + // + void CopyBetweenTextures(Texture sourceTexture, Texture destinationTexture) + { + if (sourceTexture.Usage == GraphicsResourceUsage.Staging || + destinationTexture.Usage == GraphicsResourceUsage.Staging) { - throw new NotImplementedException("Copy region of staging resources is not supported yet"); + throw new NotImplementedException("Copy region of staging resources is not supported yet"); // TODO: Implement copy region for staging resources } ResourceBarrierTransition(source, GraphicsResourceState.CopySource); ResourceBarrierTransition(destination, GraphicsResourceState.CopyDestination); FlushResourceBarriers(); - currentCommandList.NativeCommandList.CopyTextureRegion( - new TextureCopyLocation(destination.NativeResource, destinationSubResource), - dstX, dstY, dstZ, - new TextureCopyLocation(source.NativeResource, sourceSubresource), - sourceRegion.HasValue - ? new SharpDX.Direct3D12.ResourceRegion - { - Left = sourceRegion.Value.Left, - Top = sourceRegion.Value.Top, - Front = sourceRegion.Value.Front, - Right = sourceRegion.Value.Right, - Bottom = sourceRegion.Value.Bottom, - Back = sourceRegion.Value.Back - } - : null); + var destRegion = new TextureCopyLocation + { + PResource = destination.NativeResource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = (uint) destinationSubResourceIndex + }; + var srcRegion = new TextureCopyLocation + { + PResource = source.NativeResource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = (uint) sourceSubResourceIndex + }; + + if (sourceRegion is ResourceRegion srcResourceRegion) + { + // NOTE: We assume the same layout and size as D3D12_BOX + Debug.Assert(sizeof(D3D12Box) == sizeof(ResourceRegion)); + var sourceBox = srcResourceRegion.BitCast(); + + currentCommandList.NativeCommandList.CopyTextureRegion(in destRegion, (uint) dstX, (uint) dstY, (uint) dstZ, + in srcRegion, in sourceBox); + } + else + { + currentCommandList.NativeCommandList.CopyTextureRegion(in destRegion, (uint) dstX, (uint) dstY, (uint) dstZ, + in srcRegion, pSrcBox: null); + } } - else if (source is Buffer sourceBuffer && destination is Buffer) + + // + // Copies a region from a Buffer to another Buffer. + // + void CopyBetweenBuffers(Buffer sourceBuffer, Buffer destinationBuffer) { ResourceBarrierTransition(source, GraphicsResourceState.CopySource); ResourceBarrierTransition(destination, GraphicsResourceState.CopyDestination); FlushResourceBarriers(); - currentCommandList.NativeCommandList.CopyBufferRegion(destination.NativeResource, dstX, - source.NativeResource, sourceRegion?.Left ?? 0, sourceRegion.HasValue ? sourceRegion.Value.Right - sourceRegion.Value.Left : sourceBuffer.SizeInBytes); - } - else - { - throw new InvalidOperationException("Cannot copy data between buffer and texture."); + currentCommandList.NativeCommandList.CopyBufferRegion(destinationBuffer.NativeResource, (ulong)dstX, + sourceBuffer.NativeResource, + SrcOffset: (ulong)(sourceRegion?.Left ?? 0), + NumBytes: sourceRegion.HasValue + ? (ulong)(sourceRegion.Value.Right - sourceRegion.Value.Left) + : (ulong) sourceBuffer.SizeInBytes); } } - /// - public void CopyCount(Buffer sourceBuffer, Buffer destBuffer, int offsetInBytes) + /// + /// Copies data from a Buffer holding variable length data to another Buffer. + /// + /// + /// A Structured Buffer created with either or . + /// These types of resources have hidden counters tracking "how many" records have been written. + /// + /// + /// A Buffer resource to copy the data to. Any Buffer that other copy commands, such as or , are able to write to. + /// + /// + /// The offset in bytes from the start of to write 32-bit + /// structure (vertex) count from . + /// The offset must be aligned to 4 bytes. + /// + /// is . + /// is . + public void CopyCount(Buffer sourceBuffer, Buffer destinationBuffer, int destinationOffsetInBytes) { - if (sourceBuffer == null) throw new ArgumentNullException(nameof(sourceBuffer)); - if (destBuffer == null) throw new ArgumentNullException(nameof(destBuffer)); + ArgumentNullException.ThrowIfNull(sourceBuffer); + ArgumentNullException.ThrowIfNull(destinationBuffer); - currentCommandList.NativeCommandList.CopyBufferRegion(destBuffer.NativeResource, offsetInBytes, sourceBuffer.NativeResource, 0, 4); + currentCommandList.NativeCommandList.CopyBufferRegion(destinationBuffer.NativeResource, (ulong) destinationOffsetInBytes, + sourceBuffer.NativeResource, SrcOffset: 0, NumBytes: sizeof(uint)); } - internal void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// is . + /// + /// Only s and s are supported. + /// + /// + /// + /// If is a Constant Buffer, it must be updated in full. + /// It is not possible to use this method to partially update a Constant Buffer. + /// + /// + /// A Graphics Resource cannot be used as a destination if: + /// + /// The resource was created with or . + /// The resource was created as a Depth-Stencil Buffer. + /// The resource is a Texture created with multi-sampling capability (see ). + /// + /// + /// + /// When returns, the application is free to change or even free the data pointed to by + /// because the method has already copied/snapped away the original contents. + /// + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData) { - ResourceRegion region; - if (resource is Texture texture) + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + fixed (byte* sourceDataPtr = sourceData) { - region = new ResourceRegion(left: 0, top: 0, front: 0, texture.Width, texture.Height, texture.Depth); + var sourceDataBox = new DataBox((nint) sourceDataPtr, rowPitch: 0, slicePitch: 0); + UpdateSubResource(resource, subResourceIndex, sourceDataBox); } - else if (resource is Buffer buffer) + } + + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// is . + /// + /// Only s and s are supported. + /// + /// + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData) + { + ResourceRegion region = resource switch { - region = new ResourceRegion(left: 0, top: 0, front: 0, buffer.SizeInBytes, bottom: 1, back: 1); - } - else + Texture texture => new ResourceRegion(left: 0, top: 0, front: 0, texture.Width, texture.Height, texture.Depth), + Buffer buffer => new ResourceRegion(left: 0, top: 0, front: 0, buffer.SizeInBytes, bottom: 1, back: 1), + + _ => throw new InvalidOperationException("Unknown resource type") + }; + + UpdateSubResource(resource, subResourceIndex, sourceData, region); + } + + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// is a , but its is not one of the supported types. + /// is of an unknown type and cannot be updated. + /// + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData, ResourceRegion region) + { + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + fixed (byte* sourceDataPtr = sourceData) { - throw new InvalidOperationException("Unknown resource type"); + var sourceDataBox = new DataBox((nint) sourceDataPtr, rowPitch: 0, slicePitch: 0); + UpdateSubResource(resource, subResourceIndex, sourceDataBox, region); } - - UpdateSubresource(resource, subResourceIndex, databox, region); } - internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// is a , but its is not one of the supported types. + /// is of an unknown type and cannot be updated. + /// + internal unsafe partial void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData, ResourceRegion region) { if (resource is Texture texture) + { + UpdateTexture(texture); + } + else if (resource is Buffer) + { + UpdateBuffer(); + } + else throw new InvalidOperationException("Unknown type of Graphics Resource"); + + // + // Updates a Texture with data from CPU memory. + // + void UpdateTexture(Texture texture) { var width = region.Right - region.Left; var height = region.Bottom - region.Top; var depth = region.Back - region.Front; - ResourceDescription resourceDescription; + SkipInit(out ResourceDesc resourceDescription); switch (texture.Dimension) { case TextureDimension.Texture1D: - resourceDescription = ResourceDescription.Texture1D((SharpDX.DXGI.Format) texture.Format, width, arraySize: 1, mipLevels: 1); + resourceDescription = texture.ConvertToNativeDescription1D(); + resourceDescription.Width = (ulong) width; + resourceDescription.DepthOrArraySize = 1; + resourceDescription.MipLevels = 1; break; + case TextureDimension.Texture2D: case TextureDimension.TextureCube: - resourceDescription = ResourceDescription.Texture2D((SharpDX.DXGI.Format) texture.Format, width, height, arraySize: 1, mipLevels: 1); + resourceDescription = texture.ConvertToNativeDescription2D(); + resourceDescription.Width = (ulong) width; + resourceDescription.Height = (uint) height; + resourceDescription.DepthOrArraySize = 1; + resourceDescription.MipLevels = 1; break; + case TextureDimension.Texture3D: - resourceDescription = ResourceDescription.Texture3D((SharpDX.DXGI.Format) texture.Format, width, height, (short) depth, mipLevels: 1); + resourceDescription = texture.ConvertToNativeDescription3D(); + resourceDescription.Width = (ulong) width; + resourceDescription.Height = (uint) height; + resourceDescription.DepthOrArraySize = (ushort) depth; + resourceDescription.MipLevels = 1; break; + default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(texture), "The Graphics Resource is a Texture, but its dimension is not one of the supported types."); } // TODO D3D12 allocate in upload heap (placed resources?) - var nativeUploadTexture = NativeDevice.CreateCommittedResource(new HeapProperties(CpuPageProperty.WriteBack, MemoryPool.L0), HeapFlags.None, - resourceDescription, - ResourceStates.GenericRead); + var heap = new HeapProperties + { + CPUPageProperty = CpuPageProperty.WriteBack, + MemoryPoolPreference = MemoryPool.L0, + CreationNodeMask = 1, + VisibleNodeMask = 1, + Type = HeapType.Custom + }; - GraphicsDevice.TemporaryResources.Enqueue(new KeyValuePair(GraphicsDevice.NextFenceValue, nativeUploadTexture)); + HResult result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, + in resourceDescription, ResourceStates.GenericRead, + pOptimizedClearValue: null, + out ComPtr nativeUploadTexture); + if (result.IsFailure) + result.Throw(); - nativeUploadTexture.WriteToSubresource(0, dstBoxRef: null, databox.DataPointer, databox.RowPitch, databox.SlicePitch); + GraphicsDevice.TemporaryResources.Enqueue((GraphicsDevice.NextFenceValue, nativeUploadTexture)); + + scoped ref var fullBox = ref NullRef(); + + result = nativeUploadTexture.WriteToSubresource(DstSubresource: 0, in fullBox, + (void*) sourceData.DataPointer, (uint) sourceData.RowPitch, (uint) sourceData.SlicePitch); + if (result.IsFailure) + result.Throw(); // Trigger copy ResourceBarrierTransition(resource, GraphicsResourceState.CopyDestination); FlushResourceBarriers(); - currentCommandList.NativeCommandList.CopyTextureRegion(new TextureCopyLocation(resource.NativeResource, subResourceIndex), region.Left, region.Top, region.Front, new TextureCopyLocation(nativeUploadTexture, 0), srcBoxRef: null); + + var destRegion = new TextureCopyLocation + { + PResource = resource.NativeResource, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = (uint) subResourceIndex + }; + var srcRegion = new TextureCopyLocation + { + PResource = nativeUploadTexture, + Type = TextureCopyType.SubresourceIndex, + SubresourceIndex = 0 + }; + currentCommandList.NativeCommandList.CopyTextureRegion(in destRegion, (uint) region.Left, (uint) region.Top, (uint) region.Front, + in srcRegion, in fullBox); } - else + + // + // Updates a Buffer with data from CPU memory. + // + void UpdateBuffer() { - if (resource is Buffer) - { - var uploadSize = region.Right - region.Left; - var uploadMemory = GraphicsDevice.AllocateUploadBuffer(region.Right - region.Left, out var uploadResource, out var uploadOffset); + var uploadSize = region.Right - region.Left; + var uploadMemory = GraphicsDevice.AllocateUploadBuffer(region.Right - region.Left, out var uploadResource, out var uploadOffset); - Unsafe.CopyBlockUnaligned((void*) uploadMemory, (void*) databox.DataPointer, (uint) uploadSize); + CopyBlockUnaligned((void*)uploadMemory, (void*)sourceData.DataPointer, (uint)uploadSize); - ResourceBarrierTransition(resource, GraphicsResourceState.CopyDestination); - FlushResourceBarriers(); - currentCommandList.NativeCommandList.CopyBufferRegion(resource.NativeResource, region.Left, uploadResource, uploadOffset, uploadSize); - } - else - { - throw new InvalidOperationException("Unknown resource type"); - } + ResourceBarrierTransition(resource, GraphicsResourceState.CopyDestination); + FlushResourceBarriers(); + + currentCommandList.NativeCommandList.CopyBufferRegion(pDstBuffer: resource.NativeResource, DstOffset: (ulong)region.Left, + uploadResource, (ulong)uploadOffset, (ulong)uploadSize); } } // TODO GRAPHICS REFACTOR what should we do with this? /// - /// Maps a subresource. + /// Maps a sub-resource of a Graphics Resource to be accessible from CPU memory, and in the process denies the GPU access to that sub-resource. /// - /// The resource. - /// Index of the sub resource. - /// The map mode. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// The offset information in bytes. - /// The length information in bytes. - /// Pointer to the sub resource to map. - public MappedResource MapSubresource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) + /// The Graphics Resource to map to CPU memory. + /// The index of the sub-resource to get access to. + /// A value of indicating the way the Graphics Resource should be mapped to CPU memory. + /// + /// A value indicating if this method will return immediately if the Graphics Resource is still being used by the GPU for writing + /// . The default value is , which means the method will wait until the GPU is done. + /// + /// + /// The offset in bytes from the beginning of the mapped memory of the sub-resource. + /// Defaults to 0, which means it is mapped from the beginning. + /// + /// + /// The length in bytes of the memory to map from the sub-resource. + /// Defaults to 0, which means the entire sub-resource is mapped. + /// + /// A structure pointing to the GPU resource mapped for CPU access. + /// is . + /// + /// Cannot map a sub-resource of a Graphics Resource that is not a or a . + /// + /// + /// The Graphics Resource is being updated by other Command List, but it is not yet finished. + /// + /// + /// Cannot use . Direct3D 12 does not support this mode. + /// + /// + /// Only CPU-accessible staging Graphics Resources admit reading / writing modes (, + /// , or ). + /// + /// + /// For s: + /// + /// Usage Instructions: + /// + /// + /// Ensure the was created with the correct usage. + /// For example, you should specify if you plan to update its contents frequently. + /// + /// This method can be called multiple times, and nested calls are supported. + /// + /// Use appropriate values when calling . + /// For example, indicates that the old data in the Buffer can be discarded. + /// + /// + /// + /// + /// Restrictions: + /// + /// + /// The returned by is not guaranteed to be consistent across different calls. + /// Applications should not rely on the address being the same unless is persistently nested. + /// + /// may invalidate the CPU cache to ensure that CPU reads reflect any modifications made by the GPU. + /// If your graphics API supports them, use fences for synchronization to ensure proper coordination between the CPU and GPU. + /// Ensure that the Buffer data is properly aligned to meet the requirements of your graphics API. + /// + /// Stick to simple usage models (e.g., for upload, , + /// for readback) unless advanced models are necessary for your application. + /// + /// + /// + /// + /// For s: + /// + /// Usage Instructions: + /// + /// + /// Ensure to use the correct data format when writing data to the Texture. + /// + /// Textures can have multiple mipmap levels. You must specify which level you want to map with . + /// + /// Use appropriate values when calling . + /// For example, is usually used to update dynamic Textures. + /// + /// + /// + /// + /// Restrictions: + /// + /// + /// Not all s are compatible with mapping operations. + /// + /// Concurrent access to a Texture both from the CPU and the GPU may not be allowed and might require careful synchronization. + /// Ensure that the Texture data is properly aligned to meet the requirements of your graphics API and the . + /// + /// + /// + /// For State Objects (like , , etc): + /// + /// Restrictions: + /// + /// + /// State Objects are not usually mapped nor directly updated. They are created with specific configurations and are treated + /// as immutable from now on. Instead, if you need changes, you can create a new State Object with the updated settings. + /// + /// + /// + /// + /// After updating the , call to release the CPU pointer and allow the GPU to access the updated data. + /// + public unsafe partial MappedResource MapSubResource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) { - if (resource == null) throw new ArgumentNullException("resource"); + ArgumentNullException.ThrowIfNull(resource); var rowPitch = 0; var depthStride = 0; @@ -1011,45 +1925,44 @@ public MappedResource MapSubresource(GraphicsResource resource, int subResourceI if (resource is Texture texture) { usage = texture.Usage; + if (lengthInBytes == 0) - lengthInBytes = texture.ComputeSubresourceSize(subResourceIndex); + lengthInBytes = texture.ComputeSubResourceSize(subResourceIndex); - rowPitch = texture.ComputeRowPitch(subResourceIndex % texture.MipLevels); - depthStride = texture.ComputeSlicePitch(subResourceIndex % texture.MipLevels); + rowPitch = texture.ComputeRowPitch(subResourceIndex % texture.MipLevelCount); + depthStride = texture.ComputeSlicePitch(subResourceIndex % texture.MipLevelCount); - if (texture.Usage == GraphicsResourceUsage.Staging) + if (usage == GraphicsResourceUsage.Staging) { - // Internally it's a buffer, so adapt resource index and offset - offsetInBytes = texture.ComputeBufferOffset(subResourceIndex, 0); + // Internally it's a Buffer, so adapt resource index and offset + offsetInBytes = texture.ComputeBufferOffset(subResourceIndex, depthSlice: 0); subResourceIndex = 0; } } - else + else if (resource is Buffer buffer) { - if (resource is Buffer buffer) - { - usage = buffer.Usage; - if (lengthInBytes == 0) - lengthInBytes = buffer.SizeInBytes; - } + usage = buffer.Usage; + + if (lengthInBytes == 0) + lengthInBytes = buffer.SizeInBytes; } + else throw new ArgumentException("Only Buffers and Textures can be mapped", nameof(resource)); - if (mapMode == MapMode.Read || mapMode == MapMode.ReadWrite || mapMode == MapMode.Write) + if (mapMode is MapMode.Read or MapMode.ReadWrite or MapMode.Write) { // Is non-staging ever possible for Read/Write? if (usage != GraphicsResourceUsage.Staging) - throw new InvalidOperationException(); + throw new ArgumentException("Read/Write/ReadWrite is only supported for staging resources", nameof(mapMode)); } - - if (mapMode == MapMode.WriteDiscard) + else if (mapMode == MapMode.WriteDiscard) { - throw new InvalidOperationException("Can't use WriteDiscard on Graphics API that don't support renaming"); + throw new ArgumentException($"Can't use {nameof(MapMode)}.{nameof(MapMode.WriteDiscard)} on Graphics APIs that don't support renaming", nameof(mapMode)); } if (mapMode != MapMode.WriteNoOverwrite) { // Need to wait? - if (!resource.StagingFenceValue.HasValue || !GraphicsDevice.IsFenceCompleteInternal(resource.StagingFenceValue.Value)) + if (resource.StagingFenceValue is null || !GraphicsDevice.IsFenceCompleteInternal(resource.StagingFenceValue.Value)) { if (doNotWait) { @@ -1058,42 +1971,76 @@ public MappedResource MapSubresource(GraphicsResource resource, int subResourceI // Need to flush? (i.e. part of) if (resource.StagingBuilder == this) - FlushInternal(false); + FlushInternal(wait: false); - if (!resource.StagingFenceValue.HasValue) + // If the resource is not being updated by this command list, we need to wait for the fence + if (resource.StagingFenceValue is null) throw new InvalidOperationException("CommandList updating the staging resource has not been submitted"); GraphicsDevice.WaitForFenceInternal(resource.StagingFenceValue.Value); } } - var mappedMemory = resource.NativeResource.Map(subResourceIndex) + offsetInBytes; - return new MappedResource(resource, subResourceIndex, new DataBox(mappedMemory, rowPitch, depthStride), offsetInBytes, lengthInBytes); + scoped ref var fullRange = ref NullRef(); + + void* mappedMemory = null; + HResult result = resource.NativeResource.Map((uint) subResourceIndex, pReadRange: in fullRange, ref mappedMemory); + + if (result.IsFailure) + result.Throw(); + + var mappedData = new DataBox((IntPtr)((byte*) mappedMemory + offsetInBytes), rowPitch, depthStride); + return new MappedResource(resource, subResourceIndex, mappedData, offsetInBytes, lengthInBytes); } // TODO GRAPHICS REFACTOR what should we do with this? - public void UnmapSubresource(MappedResource unmapped) + /// + /// Unmaps a sub-resource of a Graphics Resource, which was previously mapped to CPU memory with , + /// and in the process re-enables the GPU access to that sub-resource. + /// + /// + /// A structure identifying the sub-resource to unmap. + /// + public unsafe partial void UnmapSubResource(MappedResource unmapped) { - unmapped.Resource.NativeResource.Unmap(unmapped.SubResourceIndex); + scoped ref var fullRange = ref NullRef(); + + unmapped.Resource.NativeResource.Unmap((uint) unmapped.SubResourceIndex, pWrittenRange: in fullRange); } - // Contains a DescriptorHeap and cache its GPU and CPU pointers - struct DescriptorHeapCache + #region DescriptorHeapWrapper structure + + /// + /// Internal structure that wraps a and its cached GPU and CPU pointers. + /// + private struct DescriptorHeapWrapper { - public DescriptorHeapCache(DescriptorHeap heap) : this() + public ComPtr Heap; + + public CpuDescriptorHandle CPUDescriptorHandleForHeapStart; + public GpuDescriptorHandle GPUDescriptorHandleForHeapStart; + + + /// + /// Initializes a new instance of the struct with the specified heap. + /// + /// The Descriptor heap to wrap. + public DescriptorHeapWrapper(ComPtr heap) { Heap = heap; - if (heap != null) + + if (heap.IsNotNull()) { - CPUDescriptorHandleForHeapStart = heap.CPUDescriptorHandleForHeapStart; - GPUDescriptorHandleForHeapStart = heap.GPUDescriptorHandleForHeapStart; + CPUDescriptorHandleForHeapStart = heap.GetCPUDescriptorHandleForHeapStart(); + GPUDescriptorHandleForHeapStart = heap.GetGPUDescriptorHandleForHeapStart(); } } - public DescriptorHeap Heap; - public CpuDescriptorHandle CPUDescriptorHandleForHeapStart; - public GpuDescriptorHandle GPUDescriptorHandleForHeapStart; + public static implicit operator ComPtr(DescriptorHeapWrapper wrapper) => wrapper.Heap; + public static implicit operator DescriptorHeapWrapper(ComPtr heap) => new DescriptorHeapWrapper(heap); } + + #endregion } } diff --git a/sources/engine/Stride.Graphics/Direct3D12/CompiledCommandList.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/CompiledCommandList.Direct3D12.cs index 491467eaa9..4abdf03ac3 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/CompiledCommandList.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/CompiledCommandList.Direct3D12.cs @@ -1,19 +1,44 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System.Collections.Generic; -using SharpDX.Direct3D12; -namespace Stride.Graphics +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Stride.Graphics; + +public unsafe partial struct CompiledCommandList { - public partial struct CompiledCommandList - { - internal CommandList Builder; - internal GraphicsCommandList NativeCommandList; - internal CommandAllocator NativeCommandAllocator; - internal List SrvHeaps; - internal List SamplerHeaps; - internal List StagingResources; - } + /// + /// The Command List used to record commands. + /// + internal CommandList Builder; + + /// + /// The internal native Direct3D 12 Command List for graphics commands. + /// + internal ComPtr NativeCommandList; + /// + /// The internal native Direct3D 12 Command Allocator for graphics commands. + /// + internal ComPtr NativeCommandAllocator; + + /// + /// A list of native Direct3D 12 Descriptor Heaps for Shader Resource Views. + /// + internal List> SrvHeaps; + /// + /// A list of native Direct3D 12 Descriptor Heaps for Samplers. + /// + internal List> SamplerHeaps; + + /// + /// A list of Graphics Resources that are currently being used for staging data by this Command List. + /// + internal List StagingResources; } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/DescriptorPool.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/DescriptorPool.Direct3D12.cs index ae59729c3e..52e1ef1912 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/DescriptorPool.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/DescriptorPool.Direct3D12.cs @@ -1,28 +1,48 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 -using SharpDX.Direct3D12; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + using Stride.Shaders; +using static Stride.Graphics.ComPtrHelpers; + namespace Stride.Graphics { - public partial class DescriptorPool + public unsafe partial class DescriptorPool { - internal DescriptorHeap SrvHeap; - internal DescriptorHeap SamplerHeap; + private ID3D12DescriptorHeap* nativeSrvHeap; + private ID3D12DescriptorHeap* nativeSamplerHeap; - internal CpuDescriptorHandle SrvStart; - internal int SrvOffset; - internal int SrvCount; - internal CpuDescriptorHandle SamplerStart; - internal int SamplerOffset; - internal int SamplerCount; + /// + /// Gets the internal Direct3D 12 Descriptor Heap for Shader Resource Views. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr SrvHeap => ToComPtr(nativeSrvHeap); + + /// + /// Gets the internal Direct3D 12 Descriptor Heap for Samplers. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr SamplerHeap => ToComPtr(nativeSamplerHeap); + + internal CpuDescriptorHandle SrvStart; // CPU handle to the start of the Shader Resource View heap + internal int SrvOffset; // Offset in the SRV heap from SrvStart + internal int SrvCount; // Number of SRVs allocated in the pool + + internal CpuDescriptorHandle SamplerStart; // CPU handle to the start of the Sampler heap + internal int SamplerOffset; // Offset in the Sampler heap from SamplerStart + internal int SamplerCount; // Number of Samplers allocated in the pool - public void Reset() - { - SrvOffset = 0; - SamplerOffset = 0; - } private DescriptorPool(GraphicsDevice graphicsDevice, DescriptorTypeCount[] counts) : base(graphicsDevice) { @@ -37,34 +57,59 @@ private DescriptorPool(GraphicsDevice graphicsDevice, DescriptorTypeCount[] coun if (SrvCount > 0) { - SrvHeap = graphicsDevice.NativeDevice.CreateDescriptorHeap(new DescriptorHeapDescription + var descriptorHeapDesc = new DescriptorHeapDesc { - DescriptorCount = SrvCount, + NumDescriptors = (uint) SrvCount, Flags = DescriptorHeapFlags.None, - Type = DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView, - }); - SrvStart = SrvHeap.CPUDescriptorHandleForHeapStart; + Type = DescriptorHeapType.CbvSrvUav + }; + + HResult result = graphicsDevice.NativeDevice.CreateDescriptorHeap(in descriptorHeapDesc, + out ComPtr descriptorHeap); + if (result.IsFailure) + result.Throw(); + + nativeSrvHeap = descriptorHeap; + SrvStart = SrvHeap.GetCPUDescriptorHandleForHeapStart(); } if (SamplerCount > 0) { - SamplerHeap = graphicsDevice.NativeDevice.CreateDescriptorHeap(new DescriptorHeapDescription + var descriptorHeapDesc = new DescriptorHeapDesc { - DescriptorCount = SamplerCount, + NumDescriptors = (uint) SamplerCount, Flags = DescriptorHeapFlags.None, - Type = DescriptorHeapType.Sampler, - }); - SamplerStart = SamplerHeap.CPUDescriptorHandleForHeapStart; + Type = DescriptorHeapType.Sampler + }; + + HResult result = graphicsDevice.NativeDevice.CreateDescriptorHeap(in descriptorHeapDesc, + out ComPtr descriptorHeap); + if (result.IsFailure) + result.Throw(); + + nativeSamplerHeap = descriptorHeap; + SamplerStart = SamplerHeap.GetCPUDescriptorHandleForHeapStart(); } } + /// protected internal override void OnDestroyed() { - ReleaseComObject(ref SrvHeap); - ReleaseComObject(ref SamplerHeap); + SafeRelease(ref nativeSrvHeap); + SafeRelease(ref nativeSamplerHeap); base.OnDestroyed(); } + + /// + /// Clears the Descriptor Pool, resetting all allocated Descriptors. + /// + public void Reset() + { + SrvOffset = 0; + SamplerOffset = 0; + } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs index d9212a4693..a17bbb68b2 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/DescriptorSet.Direct3D12.cs @@ -1,129 +1,201 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; -using SharpDX; -using SharpDX.Direct3D12; -using Stride.Shaders; +using System.Diagnostics; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; namespace Stride.Graphics { - public partial struct DescriptorSet + public readonly unsafe partial struct DescriptorSet { - internal readonly GraphicsDevice Device; - internal readonly int[] BindingOffsets; - internal readonly DescriptorSetLayout Description; + private readonly ComPtr nativeDevice; + // Mapping of binding slot -> offset from start handle + /// + private readonly ReadOnlySpan BindingOffsets => IsValid ? Description!.BindingOffsets : default; + + /// + /// The description of the Descriptors in the Descriptor Set, their layout, and other associated metadata. + /// + internal readonly DescriptorSetLayout? Description; + + /// + /// Gets a value indicating if the Descriptor Set is valid. + /// + /// + /// A Descriptor Set is considered valid if it is allocated in a Descriptor Pool. + /// + public readonly bool IsValid => Description is not null; + + /// + /// A CPU-accessible handle to the Descriptors for Shader Resource Views (SRVs), Unordered Access Views (UAVs), + /// and Constant Buffer Views (CBVs), in the Descriptor Set. + /// + /// + /// The handle points to the first allocated Descriptor. If more than one Descriptor is allocated, they will + /// be contiguous to the first one. + /// internal readonly CpuDescriptorHandle SrvStart; + /// + /// A CPU-accessible handle to the Descriptors for Samplers in the Descriptor Set. + /// + /// + /// The handle points to the first allocated Descriptor. If more than one Descriptor is allocated, they will + /// be contiguous to the first one. + /// internal readonly CpuDescriptorHandle SamplerStart; - public bool IsValid => Description != null; - private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, DescriptorSetLayout desc) + /// + /// Initializes a new instance of the structure. + /// + /// The Graphics Device. + /// The pool where Descriptor Sets are allocated. + /// A description of the Graphics Resources in the Descriptor Set and their layout. + private DescriptorSet(GraphicsDevice graphicsDevice, DescriptorPool pool, DescriptorSetLayout layout) { - if (pool.SrvOffset + desc.SrvCount > pool.SrvCount || pool.SamplerOffset + desc.SamplerCount > pool.SamplerCount) + Debug.Assert(pool is not null); + Debug.Assert(layout is not null); + + // Not enough space in the pool for the new Descriptor Set? + if (pool.SrvOffset + layout.SrvCount > pool.SrvCount || + pool.SamplerOffset + layout.SamplerCount > pool.SamplerCount) { - // Eearly exit if OOM, IsValid should return false (TODO: different mechanism?) - Device = null; - BindingOffsets = null; + // Early exit if OOM, IsValid should return false + // TODO: different mechanism? + nativeDevice = null; Description = null; - SrvStart = new CpuDescriptorHandle(); - SamplerStart = new CpuDescriptorHandle(); + SrvStart = default; + SamplerStart = default; return; } - Device = graphicsDevice; - BindingOffsets = desc.BindingOffsets; - Description = desc; + Debug.Assert(graphicsDevice is not null); - // Store start CpuDescriptorHandle - SrvStart = desc.SrvCount > 0 ? (pool.SrvStart + graphicsDevice.SrvHandleIncrementSize * pool.SrvOffset) : new CpuDescriptorHandle(); - SamplerStart = desc.SamplerCount > 0 ? (pool.SamplerStart + graphicsDevice.SamplerHandleIncrementSize * pool.SamplerOffset) : new CpuDescriptorHandle(); + nativeDevice = graphicsDevice.NativeDevice; + Description = layout; + + // Store starting CpuDescriptorHandle for SRVs, UAVs, etc. + var startHandle = layout.SrvCount > 0 + ? pool.SrvStart.Ptr + (nuint) (graphicsDevice.SrvHandleIncrementSize * pool.SrvOffset) + : 0; + + SrvStart = new CpuDescriptorHandle(startHandle); + + // Store starting CpuDescriptorHandle for Samplers + startHandle = layout.SamplerCount > 0 + ? pool.SamplerStart.Ptr + (nuint) (graphicsDevice.SamplerHandleIncrementSize * pool.SamplerOffset) + : 0; + + SamplerStart = new CpuDescriptorHandle(startHandle); // Allocation is done, bump offsets - // TODO D3D12 thread safety? - pool.SrvOffset += desc.SrvCount; - pool.SamplerOffset += desc.SamplerCount; + // TODO: D3D12: Thread safety? + pool.SrvOffset += layout.SrvCount; + pool.SamplerOffset += layout.SamplerCount; } + /// - /// Sets a descriptor. + /// Sets the Descriptor in a specific slot. /// /// The slot. - /// The descriptor. + /// The Descriptor to set. public void SetValue(int slot, object value) { - var srv = value as GraphicsResource; - if (srv != null) + if (value is GraphicsResource srv) { SetShaderResourceView(slot, srv); } - else + else if (value is SamplerState sampler) { - var sampler = value as SamplerState; - if (sampler != null) - { - SetSamplerState(slot, sampler); - } + SetSamplerState(slot, sampler); } } /// - /// Sets a shader resource view descriptor. + /// Sets a Shader Resource View (SRV) Descriptor in a specific slot. /// /// The slot. - /// The shader resource view. + /// The Shader Resource View (SRV) to set. public void SetShaderResourceView(int slot, GraphicsResource shaderResourceView) { - if (shaderResourceView.NativeShaderResourceView.Ptr == PointerSize.Zero) + if (shaderResourceView.NativeShaderResourceView.Ptr == 0) return; - Device.NativeDevice.CopyDescriptorsSimple(1, SrvStart + BindingOffsets[slot], shaderResourceView.NativeShaderResourceView, DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); + + var destDescriptorRangeStart = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) BindingOffsets[slot]); + + nativeDevice.CopyDescriptorsSimple(NumDescriptors: 1, destDescriptorRangeStart, + shaderResourceView.NativeShaderResourceView, DescriptorHeapType.CbvSrvUav); } /// - /// Sets a sampler state descriptor. + /// Sets a Sampler State Descriptor in a specific slot. /// /// The slot. - /// The sampler state. + /// The Sampler State to set. public void SetSamplerState(int slot, SamplerState samplerState) { - // For now, immutable samplers appears in the descriptor set and should be ignored - // TODO GRAPHICS REFACTOR can't we just hide them somehow? + // For now, immutable Samplers appears in the Descriptor Set and should be ignored + // TODO: GRAPHICS REFACTOR: Can't we just hide them somehow? var bindingSlot = BindingOffsets[slot]; - if (bindingSlot == -1) + if (bindingSlot == DescriptorSetLayout.IMMUTABLE_SAMPLER_BINDING_OFFSET) return; - Device.NativeDevice.CopyDescriptorsSimple(1, SamplerStart + BindingOffsets[slot], samplerState.NativeSampler, DescriptorHeapType.Sampler); + var destDescriptorRangeStart = new CpuDescriptorHandle(SamplerStart.Ptr + (nuint) bindingSlot); + + nativeDevice.CopyDescriptorsSimple(NumDescriptors: 1, destDescriptorRangeStart, + samplerState.NativeSampler, DescriptorHeapType.Sampler); } /// - /// Sets a constant buffer view descriptor. + /// Sets a Constant Buffer View (CBV) Descriptor in a specific slot. /// /// The slot. - /// The constant buffer. - /// The constant buffer view start offset. - /// The constant buffer view size. + /// The Constant Buffer View to set. + /// The offset from the start of the Constant Buffer to create the View with. + /// The size of the Constant Buffer View. public void SetConstantBuffer(int slot, Buffer buffer, int offset, int size) { - var constantBufferDataPlacementAlignment = Device.ConstantBufferDataPlacementAlignment; - Device.NativeDevice.CreateConstantBufferView(new ConstantBufferViewDescription + // TODO: We should validate whether offset + size fits inside the Constant Buffer memory + + var cbufferViewDesc = new ConstantBufferViewDesc { - BufferLocation = buffer.GPUVirtualAddress + offset, - SizeInBytes = (size + constantBufferDataPlacementAlignment) / constantBufferDataPlacementAlignment * constantBufferDataPlacementAlignment, // CB size needs to be 256-byte aligned - }, SrvStart + BindingOffsets[slot]); + BufferLocation = buffer.GPUVirtualAddress + (ulong) offset, + + // Constant Buffer size needs to be 256-byte aligned + SizeInBytes = (uint) ((size + D3D12.ConstantBufferDataPlacementAlignment) / D3D12.ConstantBufferDataPlacementAlignment * D3D12.ConstantBufferDataPlacementAlignment) + }; + + var destDescriptorHandle = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) BindingOffsets[slot]); + + nativeDevice.CreateConstantBufferView(in cbufferViewDesc, destDescriptorHandle); } /// - /// Sets an unordered access view descriptor. + /// Sets a Unordered Access View (UAV) Descriptor in a specific slot. /// /// The slot. - /// The unordered access view. + /// The Unordered Access View to set. + /// The Graphics Resource does not have an Unordered Access View. public void SetUnorderedAccessView(int slot, GraphicsResource unorderedAccessView) { - if (unorderedAccessView.NativeUnorderedAccessView.Ptr == PointerSize.Zero) - throw new ArgumentException($"Resource \'{unorderedAccessView}\' has missing Unordered Access View."); - Device.NativeDevice.CopyDescriptorsSimple(1, SrvStart + BindingOffsets[slot], unorderedAccessView.NativeUnorderedAccessView, DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); + // TODO: Why this throws, but the SRVs just return? + + if (unorderedAccessView.NativeUnorderedAccessView.Ptr == 0) + throw new ArgumentException($"Resource '{unorderedAccessView}' has missing Unordered Access View."); + + var destDescriptorRangeStart = new CpuDescriptorHandle(SrvStart.Ptr + (nuint) BindingOffsets[slot]); + + nativeDevice.CopyDescriptorsSimple(NumDescriptors: 1, destDescriptorRangeStart, + unorderedAccessView.NativeUnorderedAccessView, DescriptorHeapType.CbvSrvUav); } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/DescriptorSetLayout.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/DescriptorSetLayout.Direct3D12.cs index 487b8908ec..d3345d7d3b 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/DescriptorSetLayout.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/DescriptorSetLayout.Direct3D12.cs @@ -1,39 +1,82 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System; using Stride.Shaders; #if STRIDE_GRAPHICS_API_DIRECT3D12 -namespace Stride.Graphics +namespace Stride.Graphics; + +public partial class DescriptorSetLayout { - public partial class DescriptorSetLayout - { - internal int SrvCount; - internal int SamplerCount; + /// + /// Predefined binding slot for immutable Sampler Descriptors (like the ones defined in Shaders). + /// + internal const int IMMUTABLE_SAMPLER_BINDING_OFFSET = -1; + + private readonly int[] bindingOffsets; + + /// + /// Gets the number of Shader Resource Views (SRVs) and Unordered Access Views (UAVs) in the Descriptor Set layout. + /// + internal int SrvCount { get; private set; } + + /// + /// Gets the number of Samplers in the Descriptor Set layout. + /// + internal int SamplerCount { get; private set; } + + /// + /// Gets a mapping the binding slots with the binding offset of the Descriptor in each slot. + /// + /// + /// + /// The binding offset refers to the offset from the starting handle (either + /// or ) where the Descriptor of each slot is located. + /// + /// + /// For the binding slots that represent immutable Samplers (like those defined in the Shader itself), + /// the binding offset is -1. + /// + /// + /// The offsets in this array starts with the binding offsets of the Samplers, then the + /// binding offsets of Shader Resource Views (SRVs), Unordered Access Views (UAVs), + /// and Constant Buffer Views (CBVs) follows. + /// + /// + internal ReadOnlySpan BindingOffsets => bindingOffsets; - // Need to remap for proper separation of samplers from the rest - internal int[] BindingOffsets; - private DescriptorSetLayout(GraphicsDevice device, DescriptorSetLayoutBuilder builder) + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// + /// A object containing the definitions of the bound Graphics Resources. + /// + private DescriptorSetLayout(GraphicsDevice device, DescriptorSetLayoutBuilder builder) + { + bindingOffsets = new int[builder.ElementCount]; + + int currentBindingOffset = 0; + foreach (var entry in builder.Entries) { - BindingOffsets = new int[builder.ElementCount]; - int currentBindingOffset = 0; - foreach (var entry in builder.Entries) + // We will both setup BindingOffsets and increment SamplerCount/SrvCount at the same time + if (entry.Class == EffectParameterClass.Sampler) { - // We will both setup BindingOffsets and increment SamplerCount/SrvCount at the same time - if (entry.Class == EffectParameterClass.Sampler) - { - for (int i = 0; i < entry.ArraySize; ++i) - BindingOffsets[currentBindingOffset++] = entry.ImmutableSampler != null ? -1 : SamplerCount++ * device.SamplerHandleIncrementSize; - } - else - { - for (int i = 0; i < entry.ArraySize; ++i) - BindingOffsets[currentBindingOffset++] = SrvCount++ * device.SrvHandleIncrementSize; - } + for (int i = 0; i < entry.ArraySize; ++i) + bindingOffsets[currentBindingOffset++] = entry.ImmutableSampler is not null + ? IMMUTABLE_SAMPLER_BINDING_OFFSET + : SamplerCount++ * device.SamplerHandleIncrementSize; + } + else // SRV or UAV + { + for (int i = 0; i < entry.ArraySize; ++i) + bindingOffsets[currentBindingOffset++] = SrvCount++ * device.SrvHandleIncrementSize; } } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs new file mode 100644 index 0000000000..49314e31fc --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Allocators.cs @@ -0,0 +1,101 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + +using System; +using System.Diagnostics; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +using static Stride.Graphics.ComPtrHelpers; + +namespace Stride.Graphics; + +public unsafe partial class GraphicsDevice +{ + /// + /// Allocate descriptor handles. + /// + internal class DescriptorAllocator(GraphicsDevice device, DescriptorHeapType descriptorHeapType) : IDisposable + { + // TODO: For now this is a simple bump alloc, but at some point we will have to make a real allocator with free + + private const int DescriptorPerHeap = 256; + + private readonly GraphicsDevice device = device; + private readonly DescriptorHeapType descriptorHeapType = descriptorHeapType; + + // Cached size of a descriptor handle for the given heap type + private readonly int descriptorSize = (int) device.NativeDevice.GetDescriptorHandleIncrementSize(descriptorHeapType); + + private ComPtr currentHeap; + private CpuDescriptorHandle currentHandle; + private int remainingHandles; + + + /// + public void Dispose() + { + SafeRelease(ref currentHeap); + } + + + /// + /// Allocates a specified number of Descriptors from the current descriptor heap. + /// + /// + /// The number of Descriptors to allocate. The default value is 1. Must be a positive integer. + /// A pointing to the first allocated Descriptor. + /// + /// If the current Descriptor heap does not have enough remaining handles to satisfy the + /// allocation, a new Descriptor heap is automatically created. + /// + public CpuDescriptorHandle Allocate(int count = 1) + { + Debug.Assert(count > 0, "Count must be a positive integer."); + + // If no current heap or not enough remaining handles, create a new heap + if (currentHeap.IsNull() || remainingHandles < count) + { + CreateNewHeap(); + } + + // The handle we return, pointing to the first allocated descriptor + var resultHandle = currentHandle; + // Move the current handle forward by the size of the allocated descriptors + currentHandle.Ptr += (nuint) (descriptorSize * count); + remainingHandles -= count; + + return resultHandle; + + // + // Creates a new Descriptor heap. + // + void CreateNewHeap() + { + Debug.Assert(device is not null, "Graphics Device must not be null."); + + var descriptorHeapDesc = new DescriptorHeapDesc + { + Flags = DescriptorHeapFlags.None, + Type = descriptorHeapType, + NumDescriptors = DescriptorPerHeap, + NodeMask = 1 + }; + + HResult result = device.NativeDevice.CreateDescriptorHeap(in descriptorHeapDesc, out ComPtr descriptorHeap); + + if (result.IsFailure) + result.Throw(); + + currentHeap = descriptorHeap; // TODO: What do we do with the previous heap? Should we release it? + + remainingHandles = DescriptorPerHeap; + currentHandle = descriptorHeap.GetCPUDescriptorHandleForHeapStart(); + } + } + } +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Pools.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Pools.cs new file mode 100644 index 0000000000..5abea1ecfa --- /dev/null +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.Pools.cs @@ -0,0 +1,247 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D12 + +using System; +using System.Collections.Generic; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Stride.Graphics; + +public unsafe partial class GraphicsDevice +{ + /// + /// Represents a pool of reusable Direct3D 12 resources. + /// + /// + /// The type of resource managed by the pool. This type must be a COM type implementing the + /// interface. + /// + /// + /// + /// This class provides functionality for managing a pool of reusable Direct3D 12 resources + /// to optimize resource allocation and reuse. + /// + /// + /// Derived classes must implement the and methods + /// to define how resources are created and reset for reuse. + /// + /// + /// This class is thread-safe and ensures proper synchronization when accessing the pool. + /// + /// + internal abstract class ResourcePool : IDisposable + where T : unmanaged, IComVtbl, IComVtbl + { + // A queue to hold live objects that will be reused when it is safe to do so + private readonly Queue liveObjects = new(); + + #region LiveObject structure + + /// + /// A live object with an associated fence value. + /// + /// The fence value that must be reached before the object can be reused. + /// The COM pointer to the object. + private readonly record struct LiveObject(ulong FenceValue, ComPtr Object) : IDisposable + { + /// + public void Dispose() + { + if (Object.IsNotNull()) + Object.Dispose(); + } + } + + #endregion + + /// + /// Gets the Graphics Device associated with the resource pool. + /// + protected GraphicsDevice GraphicsDevice { get; } + + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device to associate with this resource pool. + protected ResourcePool(GraphicsDevice graphicsDevice) + { + GraphicsDevice = graphicsDevice; + } + + /// + public void Dispose() + { + lock (liveObjects) + { + foreach (var (_, liveObject) in liveObjects) + { + liveObject.Dispose(); + } + liveObjects.Clear(); + } + } + + + /// + /// Retrieves an object from the pool, resetting it for reuse if necessary. + /// + /// + /// If a previously used object is available and its associated fence value indicates it + /// is ready for reuse, the method resets and returns that object. + /// Otherwise, a new object is created and returned. + /// + /// + /// A COM pointer to the retrieved object. If no reusable object is available, a new + /// object is created and returned. + /// + public ComPtr GetObject() + { + // TODO: D3D12: SpinLock + lock (liveObjects) + { + // Check if first pooled object is ready for reuse + if (liveObjects.TryPeek(out LiveObject liveObject)) + { + if (liveObject.FenceValue <= GraphicsDevice.nativeFence->GetCompletedValue()) + { + liveObjects.Dequeue(); + var reusableObject = liveObject.Object; + ResetObject(reusableObject); + + if (reusableObject.IsNull()) + { + // TODO: Incomplete? CreateObject? + } + + return reusableObject; + } + } + + // No pooled object ready to be used, let's create a new one + return CreateObject(); + } + } + + /// + /// Creates and returns a new object. + /// + /// A new instance of object of type . + /// + /// This method is abstract and must be implemented by a derived class to define how the + /// object of type is created. + /// + protected abstract ComPtr CreateObject(); + + /// + /// Resets the specified object to its initial state so it can be reused. + /// + /// A reference to the object to reset. + /// + /// This method is intended to be implemented by derived classes to define the specific + /// logic for resetting the object. + /// + protected abstract void ResetObject(ComPtr obj); + + /// + /// Recycles an object for future reuse once the specified fence value is reached. + /// + /// The fence value that must be reached before the object can be reused. + /// The object to be recycled. This object will be enqueued for reuse. + /// + /// This method is thread-safe and ensures that the object is added to the queue for + /// reuse in a synchronized manner. + /// + public void RecycleObject(ulong fenceValue, ComPtr obj) + { + // TODO D3D12: SpinLock + lock (liveObjects) + { + // Enqueue for reuse when the fence value is reached + liveObjects.Enqueue(new LiveObject(fenceValue, obj)); + } + } + } + + + /// + /// Internal pool of reusable s. + /// + /// The Graphics Device to associate with this resource pool. + /// + /// This class manages the lifecycle of instances, creating + /// new allocators as needed and resetting them for reuse. It is designed to optimize resource + /// usage in scenarios where multiple command allocators are required. + /// + internal class CommandAllocatorPool(GraphicsDevice graphicsDevice) : ResourcePool(graphicsDevice) + { + /// + protected override ComPtr CreateObject() + { + // No Command Allocator ready to be used, let's create a new one + HResult result = GraphicsDevice.NativeDevice.CreateCommandAllocator(CommandListType.Direct, out ComPtr commandAllocator); + + if (result.IsFailure) + result.Throw(); + + return commandAllocator; + } + + /// + protected override void ResetObject(ComPtr obj) + { + // Reset the Command Allocator to prepare it for reuse + HResult result = obj.Reset(); + + if (result.IsFailure) + result.Throw(); + } + } + + + /// + /// Internal pool of reusable s. + /// + /// The Graphics Device to associate with this resource pool. + /// The number of Descriptors for the pooled Descriptor heaps. + /// The type of the pooled Descriptor heaps. + /// + /// This class manages the lifecycle of instances, creating + /// new Descriptor Heaps as needed and resetting them for reuse. It is designed to optimize resource + /// usage in scenarios where multiple Descriptor Heaps are required. + /// + internal class HeapPool(GraphicsDevice graphicsDevice, int heapSize, DescriptorHeapType heapType) : ResourcePool(graphicsDevice) + { + private readonly int heapSize = heapSize; + private readonly DescriptorHeapType heapType = heapType; + + + /// + protected override ComPtr CreateObject() + { + // No heap ready to be used, let's create a new one + var descriptorHeapDesc = new DescriptorHeapDesc + { + Flags = DescriptorHeapFlags.ShaderVisible, + Type = heapType, + NumDescriptors = (uint) heapSize + }; + + HResult result = GraphicsDevice.NativeDevice.CreateDescriptorHeap(in descriptorHeapDesc, + out ComPtr descriptorHeap); + if (result.IsFailure) + result.Throw(); + + return descriptorHeap; + } + + /// + protected override void ResetObject(ComPtr obj) { } + } +} + +#endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs index 490873311a..618e62d013 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs @@ -2,79 +2,208 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; -using SharpDX.Direct3D12; -using Stride.Core.Collections; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D12; + +using Stride.Core; using Stride.Core.Threading; +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Graphics.ComPtrHelpers; + namespace Stride.Graphics { - public partial class GraphicsDevice + public unsafe partial class GraphicsDevice { - // D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT (not exposed by SharpDX) - internal readonly int ConstantBufferDataPlacementAlignment = 256; + internal readonly int ConstantBufferDataPlacementAlignment = D3D12.ConstantBufferDataPlacementAlignment; private const GraphicsPlatform GraphicPlatform = GraphicsPlatform.Direct3D12; - internal readonly ConcurrentPool> StagingResourceLists = new ConcurrentPool>(() => new List()); - internal readonly ConcurrentPool> DescriptorHeapLists = new ConcurrentPool>(() => new List()); + /// + /// Concurrent pool for lists of Graphics Resources that are used for staging operations. + /// + internal readonly ConcurrentPool> StagingResourceLists = new(() => []); + /// + /// Concurrent pool for lists of native Direct3D 12 Descriptor Heaps. + /// + internal readonly ConcurrentPool>> DescriptorHeapLists = new(() => []); private bool simulateReset = false; private string rendererName; - private SharpDX.Direct3D12.Device nativeDevice; - internal CommandQueue NativeCommandQueue; + private ID3D12Device* nativeDevice; + private ID3D12CommandQueue* nativeCommandQueue; + + /// + /// Gets the internal Direct3D 12 Device. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + public ComPtr NativeDevice => ToComPtr(nativeDevice); + + /// + /// Gets the internal Direct3D 12 Command Queue. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeCommandQueue => ToComPtr(nativeCommandQueue); + /// + /// The requested graphics profile for the Graphics Device. + /// internal GraphicsProfile RequestedProfile; - internal SharpDX.Direct3D.FeatureLevel CurrentFeatureLevel; + /// + /// The actual D3D feature level that the Graphics Device is using. + /// + internal D3DFeatureLevel CurrentFeatureLevel; - internal CommandQueue NativeCopyCommandQueue; - internal CommandAllocator NativeCopyCommandAllocator; - internal GraphicsCommandList NativeCopyCommandList; - private Fence nativeCopyFence; - private long nextCopyFenceValue = 1; + private ID3D12CommandQueue* nativeCopyCommandQueue; + + /// + /// Gets the internal Direct3D 12 Command Queue used for copy commands. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeCopyCommandQueue => ToComPtr(nativeCopyCommandQueue); + + private ID3D12CommandAllocator* nativeCopyCommandAllocator; + + /// + /// Gets the internal Direct3D 12 Command Allocator used for copy commands. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeCopyCommandAllocator => ToComPtr(nativeCopyCommandAllocator); + + private ID3D12GraphicsCommandList* nativeCopyCommandList; + + /// + /// Gets the internal Direct3D 12 Command List used for copy commands. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeCopyCommandList => ToComPtr(nativeCopyCommandList); + + // Fence used to synchronize the Copy Command Queue + private ID3D12Fence* nativeCopyFence; + private ulong nextCopyFenceValue = 1; + + /// + /// Represents the size, in bytes, of the resource heap dedicated to Shader Resource Views. + /// + internal const int SrvHeapSize = 2048; + /// + /// Represents the size, in bytes, of the resource heap dedicated to Samplers. + /// + internal const int SamplerHeapSize = 64; + /// + /// A pool used to manage and reuse Command Allocators. + /// + /// + /// This class provides functionality to allocate and recycle Command Allocators efficiently. + /// It helps reduce the overhead of creating new allocators by reusing existing ones. + /// internal CommandAllocatorPool CommandAllocators; + /// + /// A pool used to manage and reuse Descriptor heaps intended for SRVs and UAVs. + /// + /// + /// This class provides functionality to allocate and recycle Descriptor heaps for SRVs and UAVs efficiently. + /// It helps reduce the overhead of creating new heaps by reusing existing ones. + /// internal HeapPool SrvHeaps; + /// + /// A pool used to manage and reuse Descriptor heaps intended for Samplers. + /// + /// + /// This class provides functionality to allocate and recycle Descriptor heaps for Samplers efficiently. + /// It helps reduce the overhead of creating new heaps by reusing existing ones. + /// internal HeapPool SamplerHeaps; - internal const int SrvHeapSize = 2048; - internal const int SamplerHeapSize = 64; + /// + /// Allocator for Sampler Descriptors. + /// internal DescriptorAllocator SamplerAllocator; + /// + /// Allocator for Descriptors for Shader Resource Views (SRVs). + /// internal DescriptorAllocator ShaderResourceViewAllocator; + /// + /// Allocator for Descriptors for Unordered Access Views (UAVs). + /// internal DescriptorAllocator UnorderedAccessViewAllocator => ShaderResourceViewAllocator; + /// + /// Allocator for Descriptors for Depth-Stencil Views (DSVs). + /// internal DescriptorAllocator DepthStencilViewAllocator; + /// + /// Allocator for Descriptors for Render Target Views (RTVs). + /// internal DescriptorAllocator RenderTargetViewAllocator; - private SharpDX.Direct3D12.Resource nativeUploadBuffer; - private IntPtr nativeUploadBufferStart; - private int nativeUploadBufferOffset; + // Buffer prepared for uploading data from the CPU so it can later be copied from that Buffer + // to a destination Graphics Resource on the GPU. + // It's a single large Buffer that is reused for every upload operation. Functions like a bump allocator + // where the application can request a certain size and it is carved out of the large Buffer and returned. + private ID3D12Resource* nativeUploadBuffer; + private nint nativeUploadBufferMappedAddress; // Start address of the upload buffer mapped to CPU memory + private int nativeUploadBufferOffset; // Offset in bytes from the start of the upload buffer where data can be written + /// + /// The size in bytes of a Descriptor in a Descriptor heap for Shader Resource Views (SRVs), Unordered Acess Views (UAVs), etc. + /// internal int SrvHandleIncrementSize; + /// + /// The size in bytes of a Descriptor in a Descriptor heap for Samplers. + /// internal int SamplerHandleIncrementSize; - private Fence nativeFence; - private long lastCompletedFence; - internal long NextFenceValue = 1; - private AutoResetEvent fenceEvent = new AutoResetEvent(false); + // Lock object for the graphics-related commands fence + private readonly object nativeFenceLock = new(); + // Fence used to synchronize the Graphics Command Queue + private ID3D12Fence* nativeFence; + private ulong lastCompletedFence; - // Temporary or destroyed resources kept around until the GPU doesn't need them anymore - internal Queue> TemporaryResources = new Queue>(); + /// + /// The next fence value used to synchronize the Graphics Command Queue operations. + /// + internal ulong NextFenceValue = 1; - private readonly FastList nativeCommandLists = new FastList(); + // An event used to signal when the fence has been completed + private readonly AutoResetEvent fenceEvent = new(initialState: false); + + /// + /// Temporary or destroyed Graphics Resources that are kept around until the GPU doesn't need them anymore. + /// + internal Queue<(ulong FenceValue, object Resource)> TemporaryResources = new(); /// - /// The tick frquency of timestamp queries in Hertz. + /// Gets the tick frquency of timestamp queries, in hertz. /// public long TimestampFrequency { get; private set; } /// - /// Gets the status of this device. + /// Gets the current status of the Graphics Device. /// - /// The graphics device status. public GraphicsDeviceStatus GraphicsDeviceStatus { get @@ -85,55 +214,25 @@ public GraphicsDeviceStatus GraphicsDeviceStatus return GraphicsDeviceStatus.Reset; } - var result = NativeDevice.DeviceRemovedReason; - if (result == SharpDX.DXGI.ResultCode.DeviceRemoved) - { - return GraphicsDeviceStatus.Removed; - } + var result = (DxgiConstants.DeviceRemoveReason) nativeDevice->GetDeviceRemovedReason(); - if (result == SharpDX.DXGI.ResultCode.DeviceReset) + return result switch { - return GraphicsDeviceStatus.Reset; - } - - if (result == SharpDX.DXGI.ResultCode.DeviceHung) - { - return GraphicsDeviceStatus.Hung; - } - - if (result == SharpDX.DXGI.ResultCode.DriverInternalError) - { - return GraphicsDeviceStatus.InternalError; - } - - if (result == SharpDX.DXGI.ResultCode.InvalidCall) - { - return GraphicsDeviceStatus.InvalidCall; - } - - if (result.Code < 0) - { - return GraphicsDeviceStatus.Reset; - } - - return GraphicsDeviceStatus.Normal; + DxgiConstants.DeviceRemoveReason.DeviceRemoved => GraphicsDeviceStatus.Removed, + DxgiConstants.DeviceRemoveReason.DeviceReset => GraphicsDeviceStatus.Reset, + DxgiConstants.DeviceRemoveReason.DeviceHung => GraphicsDeviceStatus.Hung, + DxgiConstants.DeviceRemoveReason.DriverInternalError => GraphicsDeviceStatus.InternalError, + DxgiConstants.DeviceRemoveReason.InvalidCall => GraphicsDeviceStatus.InvalidCall, + + < 0 => GraphicsDeviceStatus.Reset, + _ => GraphicsDeviceStatus.Normal + }; } } - /// - /// Gets the native device. - /// - /// The native device. - internal SharpDX.Direct3D12.Device NativeDevice - { - get - { - return nativeDevice; - } - } /// - /// Marks context as active on the current thread. + /// Marks the Graphics Device Context as active on the current thread. /// public void Begin() { @@ -142,295 +241,470 @@ public void Begin() } /// - /// Enables profiling. + /// Enables or disables profiling. /// - /// if set to true [enabled flag]. - public void EnableProfile(bool enabledFlag) - { - } + /// to enable profiling; to disable it. + public void EnableProfile(bool enabledFlag) { } // TODO: Implement profiling with PIX markers? Currently, profiling is only implemented for OpenGL /// - /// Unmarks context as active on the current thread. + /// Marks the Graphics Device Context as inactive on the current thread. /// - public void End() - { - } + public void End() { } /// - /// Executes a deferred command list. + /// Executes a Compiled Command List. /// - /// The deferred command list. + /// The Compiled Command List to execute. + /// + /// A Compiled Command List is a list of commands that have been recorded for execution on the Graphics Device + /// at a later time. This method executes the commands in the list. This is known as deferred execution. + /// public void ExecuteCommandList(CompiledCommandList commandList) { ExecuteCommandListInternal(commandList); } /// - /// Executes multiple deferred command lists. + /// Executes multiple Compiled Command Lists. /// - /// Number of command lists to execute. - /// The deferred command lists. + /// The number of Compiled Command Lists to execute. + /// The Compiled Command Lists to execute. + /// is . + /// + /// is greater than the length of . + /// + /// + /// A Compiled Command List is a list of commands that have been recorded for execution on the Graphics Device + /// at a later time. This method executes the commands in the list. This is known as deferred execution. + /// public void ExecuteCommandLists(int count, CompiledCommandList[] commandLists) { - if (commandLists == null) throw new ArgumentNullException(nameof(commandLists)); - if (count > commandLists.Length) throw new ArgumentOutOfRangeException(nameof(count)); + ArgumentNullException.ThrowIfNull(commandLists); + + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, commandLists.Length); var fenceValue = NextFenceValue++; // Recycle resources + var commandListToExecute = stackalloc ID3D12CommandList*[count]; for (int index = 0; index < count; index++) { var commandList = commandLists[index]; - nativeCommandLists.Add(commandList.NativeCommandList); + commandListToExecute[index] = commandList.NativeCommandList.AsComPtr(); RecycleCommandListResources(commandList, fenceValue); } - // Submit and signal fence - NativeCommandQueue.ExecuteCommandLists(count, nativeCommandLists.Items); - NativeCommandQueue.Signal(nativeFence, fenceValue); + // Submit and signal the fence + nativeCommandQueue->ExecuteCommandLists((uint) count, commandListToExecute); - ReleaseTemporaryResources(); + HResult result = nativeCommandQueue->Signal(nativeFence, fenceValue); + + if (result.IsFailure) + result.Throw(); - nativeCommandLists.Clear(); + ReleaseTemporaryResources(); } + /// + /// Sets the Graphics Device to simulate a situation in which the device is lost and then reset. + /// public void SimulateReset() { simulateReset = true; } - private void InitializePostFeatures() - { - } + /// + /// Initializes the platform-specific features of the Graphics Device once it has been fully initialized. + /// + private unsafe partial void InitializePostFeatures() { } - private string GetRendererName() - { - return rendererName; - } + private partial string GetRendererName() => rendererName; /// - /// Initializes the specified device. + /// Initialize the platform-specific implementation of the Graphics Device. /// - /// The graphics profiles. + /// A non- list of the graphics profiles to try, in order of preference. /// The device creation flags. /// The window handle. - private void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) + private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) { - if (nativeDevice != null) + Debug.Assert(graphicsProfiles is not null && graphicsProfiles.Length > 0, "Graphics profiles must be provided and cannot be empty."); + + if (nativeDevice is not null) { // Destroy previous device ReleaseDevice(); } - rendererName = Adapter.NativeAdapter.Description.Description; + rendererName = Adapter.Description; - // Profiling is supported through pix markers + // Profiling is supported through PIX markers IsProfilingSupported = true; // Command lists are thread-safe and execute deferred IsDeferred = true; - bool isDebug = (deviceCreationFlags & DeviceCreationFlags.Debug) != 0; - if (isDebug) - { - SharpDX.Direct3D12.DebugInterface.Get().EnableDebugLayer(); - } + var d3d12 = D3D12.GetApi(); + + // The Debug Layer must be initialized before creating the device + if (IsDebugMode) + EnableDebugLayer(); - // Create Device D3D12 with feature Level based on profile + HResult result = default; + + // Create the Direct3D 12 Device with feature Level based on profile for (int index = 0; index < graphicsProfiles.Length; index++) { + // Map GraphicsProfiles to D3D12 FeatureLevels var graphicsProfile = graphicsProfiles[index]; - try - { - // D3D12 supports only feature level 11+ - var level = graphicsProfile.ToFeatureLevel(); - if (level < SharpDX.Direct3D.FeatureLevel.Level_11_0) - level = SharpDX.Direct3D.FeatureLevel.Level_11_0; + var featureLevel = graphicsProfile.ToFeatureLevel(); - nativeDevice = new SharpDX.Direct3D12.Device(Adapter.NativeAdapter, level); + // D3D12 supports only feature level 11+ + if (featureLevel < D3DFeatureLevel.Level110) + featureLevel = D3DFeatureLevel.Level110; - RequestedProfile = graphicsProfile; - CurrentFeatureLevel = level; - break; - } - catch (Exception) + result = d3d12.CreateDevice(Adapter.NativeAdapter.AsIUnknown(), featureLevel, out ComPtr device); + + if (result.IsFailure) { if (index == graphicsProfiles.Length - 1) - throw; + result.Throw(); + else + continue; } + + nativeDevice = device.DisposeBy(this); + + RequestedProfile = graphicsProfile; + CurrentFeatureLevel = featureLevel; + break; } - // Describe and create the command queue. - var queueDesc = new SharpDX.Direct3D12.CommandQueueDescription(SharpDX.Direct3D12.CommandListType.Direct); - NativeCommandQueue = nativeDevice.CreateCommandQueue(queueDesc); - //queueDesc.Type = CommandListType.Copy; - NativeCopyCommandQueue = nativeDevice.CreateCommandQueue(queueDesc); - TimestampFrequency = NativeCommandQueue.TimestampFrequency; + // Describe and create the direct (graphics) command queue + var queueDesc = new CommandQueueDesc { Type = CommandListType.Direct }; + + result = nativeDevice->CreateCommandQueue(in queueDesc, out ComPtr commandQueue); + + if (result.IsFailure) + result.Throw(); + + nativeCommandQueue = commandQueue.DisposeBy(this); + + // Describe and create the copy command queue + queueDesc.Type = CommandListType.Copy; + result = nativeDevice->CreateCommandQueue(in queueDesc, out ComPtr copyQueue); - SrvHandleIncrementSize = NativeDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); - SamplerHandleIncrementSize = NativeDevice.GetDescriptorHandleIncrementSize(DescriptorHeapType.Sampler); + if (result.IsFailure) + result.Throw(); - if (isDebug) + nativeCopyCommandQueue = copyQueue.DisposeBy(this); + + // Get the tick frequency of the timestamp queries + ulong timestampFreq = default; + nativeCommandQueue->GetTimestampFrequency(ref timestampFreq); + TimestampFrequency = (long) timestampFreq; + + // Cache the descriptor handle increment sizes + SrvHandleIncrementSize = (int) nativeDevice->GetDescriptorHandleIncrementSize(DescriptorHeapType.CbvSrvUav); + SamplerHandleIncrementSize = (int) nativeDevice->GetDescriptorHandleIncrementSize(DescriptorHeapType.Sampler); + + if (IsDebugMode) { - var debugDevice = nativeDevice.QueryInterfaceOrNull(); - if (debugDevice != null) + // Query the debug device interface to disable some irrelevant warnings + result = nativeDevice->QueryInterface(out ComPtr debugDevice); + + if (result.IsSuccess && debugDevice.IsNotNull()) { - var infoQueue = debugDevice.QueryInterfaceOrNull(); - if (infoQueue != null) + result = debugDevice.QueryInterface(out ComPtr infoQueue); + + if (result.IsSuccess && infoQueue.IsNotNull()) { - MessageId[] disabledMessages = + var disabledMessages = stackalloc MessageID[] { - // This happens when render target or depth stencil clear value is diffrent - // than provided during resource allocation. - MessageId.CleardepthstencilviewMismatchingclearvalue, - MessageId.ClearrendertargetviewMismatchingclearvalue, - - // This occurs when there are uninitialized descriptors in a descriptor table, - // even when a shader does not access the missing descriptors. - MessageId.InvalidDescriptorHandle, - + // These happens when a Render Target's or Depth-Stencil Buffer's clear values are different + // than the provided ones during resource allocation + MessageID.CleardepthstencilviewMismatchingclearvalue, + MessageID.ClearrendertargetviewMismatchingclearvalue, + + // This occurs when there are uninitialized Descriptors in a Descriptor Table, + // even when a Shader does not access the missing descriptors + MessageID.InvalidDescriptorHandle, + // These happen when capturing with VS diagnostics - MessageId.MapInvalidNullRange, - MessageId.UnmapInvalidNullRange, + MessageID.MapInvalidNullrange, + MessageID.UnmapInvalidNullrange }; // Disable irrelevant debug layer warnings - InfoQueueFilter filter = new InfoQueueFilter + Silk.NET.Direct3D12.InfoQueueFilter filter = new() { - DenyList = new InfoQueueFilterDescription + DenyList = new Silk.NET.Direct3D12.InfoQueueFilterDesc { - Ids = disabledMessages + NumIDs = 5, + PIDList = disabledMessages } }; - infoQueue.AddStorageFilterEntries(filter); + infoQueue.AddStorageFilterEntries(ref filter); - //infoQueue.SetBreakOnSeverity(MessageSeverity.Error, true); - //infoQueue.SetBreakOnSeverity(MessageSeverity.Warning, true); + //infoQueue.SetBreakOnSeverity(Silk.NET.Direct3D12.MessageSeverity.Error, true); + //infoQueue.SetBreakOnSeverity(Silk.NET.Direct3D12.MessageSeverity.Warning, true); - infoQueue.Dispose(); + infoQueue.Release(); } - debugDevice.Dispose(); + debugDevice.Release(); } } // Prepare pools CommandAllocators = new CommandAllocatorPool(this); - SrvHeaps = new HeapPool(this, SrvHeapSize, DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); + SrvHeaps = new HeapPool(this, SrvHeapSize, DescriptorHeapType.CbvSrvUav); SamplerHeaps = new HeapPool(this, SamplerHeapSize, DescriptorHeapType.Sampler); // Prepare descriptor allocators SamplerAllocator = new DescriptorAllocator(this, DescriptorHeapType.Sampler); - ShaderResourceViewAllocator = new DescriptorAllocator(this, DescriptorHeapType.ConstantBufferViewShaderResourceViewUnorderedAccessView); - DepthStencilViewAllocator = new DescriptorAllocator(this, DescriptorHeapType.DepthStencilView); - RenderTargetViewAllocator = new DescriptorAllocator(this, DescriptorHeapType.RenderTargetView); + ShaderResourceViewAllocator = new DescriptorAllocator(this, DescriptorHeapType.CbvSrvUav); + DepthStencilViewAllocator = new DescriptorAllocator(this, DescriptorHeapType.Dsv); + RenderTargetViewAllocator = new DescriptorAllocator(this, DescriptorHeapType.Rtv); // Prepare copy command list (start it closed, so that every new use start with a Reset) - NativeCopyCommandAllocator = NativeDevice.CreateCommandAllocator(CommandListType.Direct); - NativeCopyCommandList = NativeDevice.CreateCommandList(CommandListType.Direct, NativeCopyCommandAllocator, null); - NativeCopyCommandList.Close(); + result = nativeDevice->CreateCommandAllocator(CommandListType.Copy, out ComPtr commandAllocator); + + if (result.IsFailure) + result.Throw(); + + nativeCopyCommandAllocator = commandAllocator.DisposeBy(this); + + result = nativeDevice->CreateCommandList(nodeMask: 0, CommandListType.Copy, commandAllocator, pInitialState: ref NullRef(), + out ComPtr commandList); + if (result.IsFailure) + result.Throw(); + + nativeCopyCommandList = commandList.DisposeBy(this); + + commandList.Close(); - // Fence for next frame and resource cleaning - nativeFence = NativeDevice.CreateFence(0, FenceFlags.None); - nativeCopyFence = NativeDevice.CreateFence(0, FenceFlags.None); + // Fences for next frame and resource cleaning + result = nativeDevice->CreateFence(InitialValue: 0, FenceFlags.None, out ComPtr gfxFence); + + if (result.IsFailure) + result.Throw(); + + result = nativeDevice->CreateFence(InitialValue: 0, FenceFlags.None, out ComPtr copyFence); + + if (result.IsFailure) + result.Throw(); + + nativeFence = gfxFence.DisposeBy(this); + nativeCopyFence = copyFence.DisposeBy(this); + + // + // Enables the Direct3D 12 debug layer if available. + // + void EnableDebugLayer() + { + HResult result = d3d12.GetDebugInterface(out ComPtr debugInterface); + + if (result.IsSuccess && debugInterface.IsNotNull()) + { + debugInterface.EnableDebugLayer(); + debugInterface.Release(); + } + } } - internal IntPtr AllocateUploadBuffer(int size, out SharpDX.Direct3D12.Resource resource, out int offset, int alignment = 0) + /// + /// Allocates (or reuses) a Buffer prepared for uploading data from the CPU so it can later + /// be copied from that Buffer to a destination Graphics Resource on the GPU. + /// + /// The size of the requested upload buffer, in bytes. + /// When the method returns, contains a pointer to the allocated upload Buffer. + /// + /// When the method returns, contains the offset in bytes from the start of + /// where data can be written to. + /// + /// Optional alignment requisites on the returned writeable address. + /// + /// A pointer to the mapped Buffer memory that can be written. It already takes into account the + /// . + /// + internal IntPtr AllocateUploadBuffer(int size, out ComPtr resource, out int offset, int alignment = 0) { - // TODO D3D12 thread safety, should we simply use locks? + // TODO: D3D12: Thread safety, should we simply use locks? + + Debug.Assert(size > 0, "Size must be greater than zero."); + Debug.Assert(alignment >= 0, "Alignment must be zero or greater."); - // Align + // Ensure the correct alignment for the offset in the current upload buffer if (alignment > 0) nativeUploadBufferOffset = (nativeUploadBufferOffset + alignment - 1) / alignment * alignment; - if (nativeUploadBuffer == null || nativeUploadBufferOffset + size > nativeUploadBuffer.Description.Width) + // If we have no upload buffer ready or its size is insufficient + if (nativeUploadBuffer is null || (ulong)(nativeUploadBufferOffset + size) > nativeUploadBuffer->GetDesc().Width) { - if (nativeUploadBuffer != null) + // Unmap any upload buffer and put it in temporary resources that may be disposed in the future + if (nativeUploadBuffer is not null) { - nativeUploadBuffer.Unmap(0); - TemporaryResources.Enqueue(new KeyValuePair(NextFenceValue, nativeUploadBuffer)); + nativeUploadBuffer->Unmap(Subresource: 0, pWrittenRange: null); + + TemporaryResources.Enqueue((NextFenceValue, ToComPtr(nativeUploadBuffer))); + // TODO: Keep a separate temporary resource list for COM pointers to avoid boxing } // Allocate new buffer - // TODO D3D12 recycle old ones (using fences to know when GPU is done with them) - // TODO D3D12 ResourceStates.CopySource not working? - var bufferSize = Math.Max(4 * 1024*1024, size); - nativeUploadBuffer = NativeDevice.CreateCommittedResource(new HeapProperties(HeapType.Upload), HeapFlags.None, ResourceDescription.Buffer(bufferSize), ResourceStates.GenericRead); - nativeUploadBufferStart = nativeUploadBuffer.Map(0, new SharpDX.Direct3D12.Range()); + // TODO: D3D12: Recycle old ones (using fences to know when GPU is done with them) + // TODO: D3D12: ResourceStates.CopySource not working? + + var bufferSize = Math.Max(4 * 1024 * 1024, size); // 4 MB minimum size + + var heapProperties = new HeapProperties { Type = HeapType.Upload }; + var resourceDesc = new ResourceDesc + { + Dimension = ResourceDimension.Buffer, + Width = (ulong) bufferSize, + Height = 1, + DepthOrArraySize = 1, + MipLevels = 1, + Alignment = 0, + + SampleDesc = { Count = 1, Quality = 0 }, + + Format = Format.FormatUnknown, + Layout = TextureLayout.LayoutRowMajor, + Flags = ResourceFlags.None + }; + + HResult result = nativeDevice->CreateCommittedResource(in heapProperties, HeapFlags.None, in resourceDesc, + ResourceStates.GenericRead, pOptimizedClearValue: null, + out ComPtr uploadBuffer); + if (result.IsFailure) + result.Throw(); + + nativeUploadBuffer = uploadBuffer.DisposeBy(this); + + void* mappedBufferAddress = null; + result = uploadBuffer.Map(Subresource: 0, pReadRange: in NullRef(), ref mappedBufferAddress); + + if (result.IsFailure) + result.Throw(); + + nativeUploadBufferMappedAddress = (nint) mappedBufferAddress; nativeUploadBufferOffset = 0; } // Bump allocate - resource = nativeUploadBuffer; + resource = ToComPtr(nativeUploadBuffer); offset = nativeUploadBufferOffset; nativeUploadBufferOffset += size; - return nativeUploadBufferStart + offset; + return nativeUploadBufferMappedAddress + offset; } + /// + /// Waits for the Command Queue for copy commands to complete execution of the current Command List. + /// + /// + /// This method ensures that all commands submitted to the copy queue are fully executed + /// before proceeding. It signals the associated fence and waits for its completion, + /// throwing an exception if the operation fails. + /// internal void WaitCopyQueue() { - NativeCommandQueue.ExecuteCommandList(NativeCopyCommandList); - NativeCommandQueue.Signal(nativeCopyFence, nextCopyFenceValue); - NativeCommandQueue.Wait(nativeCopyFence, nextCopyFenceValue); + var commandList = (ID3D12CommandList*) nativeCopyCommandList; + nativeCopyCommandQueue->ExecuteCommandLists(NumCommandLists: 1, in commandList); + + nativeCopyCommandQueue->Signal(nativeCopyFence, nextCopyFenceValue); + + HResult result = nativeCopyCommandQueue->Wait(nativeCopyFence, nextCopyFenceValue); + + if (result.IsFailure) + result.Throw(); + nextCopyFenceValue++; } + /// + /// Releases and removes Graphics Resources that were marked for deletion but put temporarily on hold + /// until the GPU is done with them. + /// + /// + /// This method removes and releases the resources if they are determined to be complete based + /// on their associated fence values. If they have been signaled as complete, they are released. + /// This is performed in a thread-safe manner. + /// internal void ReleaseTemporaryResources() { lock (TemporaryResources) { // Release previous frame resources - while (TemporaryResources.Count > 0 && IsFenceCompleteInternal(TemporaryResources.Peek().Key)) + while (TemporaryResources.Count > 0 && IsFenceCompleteInternal(TemporaryResources.Peek().FenceValue)) { - var temporaryResource = TemporaryResources.Dequeue().Value; - //temporaryResource.Value.Dispose(); - var comObject = temporaryResource as SharpDX.ComObject; - if (comObject != null) - ((SharpDX.IUnknown)comObject).Release(); - else + var temporaryResource = TemporaryResources.Dequeue().Resource; + + if (temporaryResource is ComPtr resource) { - var referenceLink = temporaryResource as GraphicsResourceLink; - if (referenceLink != null) - { - referenceLink.ReferenceCount--; - } + resource.Release(); + } + else if (temporaryResource is GraphicsResourceLink referenceLink) + { + referenceLink.ReferenceCount--; } } } } - private void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) - { - } + /// + /// Makes Direct3D 12-specific adjustments to the Pipeline State objects created by the Graphics Device. + /// + /// A Pipeline State description that can be modified and adjusted. + private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) { } - protected void DestroyPlatformDevice() + /// + /// Releases the platform-specific Graphics Device and all its associated resources. + /// + protected partial void DestroyPlatformDevice() { ReleaseDevice(); } + /// + /// Disposes the Direct3D 12 Device and all its associated resources. + /// private void ReleaseDevice() { // Wait for completion of everything queued - NativeCommandQueue.Signal(nativeFence, NextFenceValue); - NativeCommandQueue.Wait(nativeFence, NextFenceValue); + nativeCommandQueue->Signal(nativeFence, NextFenceValue); - // Release command queue - NativeCommandQueue.Dispose(); - NativeCommandQueue = null; + HResult result = nativeCommandQueue->Wait(nativeFence, NextFenceValue); + + if (result.IsFailure) + result.Throw(); - NativeCopyCommandQueue.Dispose(); - NativeCopyCommandQueue = null; + nativeCopyCommandQueue->Signal(nativeCopyFence, nextCopyFenceValue); - NativeCopyCommandAllocator.Dispose(); - NativeCopyCommandList.Dispose(); + result = nativeCopyCommandQueue->Wait(nativeCopyFence, nextCopyFenceValue); - nativeUploadBuffer.Dispose(); + if (result.IsFailure) + result.Throw(); + + // Release command queue + SafeRelease(ref nativeCommandQueue); + NativeCommandQueue.RemoveDisposeBy(this); + + SafeRelease(ref nativeCopyCommandQueue); + NativeCopyCommandQueue.RemoveDisposeBy(this); + SafeRelease(ref nativeCopyCommandAllocator); + NativeCopyCommandAllocator.RemoveDisposeBy(this); + SafeRelease(ref nativeCopyCommandList); + NativeCopyCommandList.RemoveDisposeBy(this); + + SafeRelease(ref nativeUploadBuffer); // Release temporary resources ReleaseTemporaryResources(); - nativeFence.Dispose(); - nativeFence = null; - nativeCopyFence.Dispose(); - nativeCopyFence = null; + + SafeRelease(ref nativeFence); + ToComPtr(nativeFence).RemoveDisposeBy(this); + SafeRelease(ref nativeCopyFence); + ToComPtr(nativeCopyFence).RemoveDisposeBy(this); // Release pools CommandAllocators.Dispose(); @@ -445,29 +719,46 @@ private void ReleaseDevice() if (IsDebugMode) { - var debugDevice = NativeDevice.QueryInterfaceOrNull(); - if (debugDevice != null) + result = nativeDevice->QueryInterface(out ComPtr debugDevice); + + if (result.IsSuccess && debugDevice.IsNotNull()) { - debugDevice.ReportLiveDeviceObjects(SharpDX.Direct3D12.ReportingLevel.Detail); - debugDevice.Dispose(); + debugDevice.ReportLiveDeviceObjects(RldoFlags.Detail); + debugDevice.Release(); } } - nativeDevice.Dispose(); - nativeDevice = null; + SafeRelease(ref nativeDevice); + NativeDevice.RemoveDisposeBy(this); } + /// + /// Called when the Graphics Device is being destroyed. + /// internal void OnDestroyed() { } - internal long ExecuteCommandListInternal(CompiledCommandList commandList) + + /// + /// Executes a Compiled Command List. + /// + /// + /// The Compiled Command List to execute. + /// + /// + /// The fence value associated with the execution of the Command List. + /// This value can be used to track its completion. + /// + internal ulong ExecuteCommandListInternal(CompiledCommandList commandList) { var fenceValue = NextFenceValue++; // Submit and signal fence - NativeCommandQueue.ExecuteCommandList(commandList.NativeCommandList); - NativeCommandQueue.Signal(nativeFence, fenceValue); + var nativeCommandList = commandList.NativeCommandList.AsComPtr(); + nativeCommandQueue->ExecuteCommandLists(NumCommandLists: 1, ref nativeCommandList); + + nativeCommandQueue->Signal(nativeFence, fenceValue); // Recycle resources RecycleCommandListResources(commandList, fenceValue); @@ -475,7 +766,28 @@ internal long ExecuteCommandListInternal(CompiledCommandList commandList) return fenceValue; } - private void RecycleCommandListResources(CompiledCommandList commandList, long fenceValue) + /// + /// Recycles the resources associated with a Compiled Command List, making them available for reuse. + /// + /// The Compiled Command List whose resources are to be recycled. + /// + /// The fence value associated with the Command List, used to track resource usage and ensure proper + /// synchronization. + /// + /// + /// + /// This method releases and clears staging Graphics Resources, Descriptor heaps, and other + /// resources associated with the specified Compiled Command List. + /// + /// + /// It also enqueues the internal native Command List for reuse and recycles the Command Allocator. + /// + /// + /// Callers should ensure that the specified accurately reflects + /// the point at which the resources are no longer in use to avoid synchronization issues. + /// + /// + private void RecycleCommandListResources(CompiledCommandList commandList, ulong fenceValue) { // Set fence on staging textures foreach (var stagingResource in commandList.StagingResources) @@ -486,7 +798,7 @@ private void RecycleCommandListResources(CompiledCommandList commandList, long f StagingResourceLists.Release(commandList.StagingResources); commandList.StagingResources.Clear(); - // Recycle resources + // Recycle resources (SRVs, UAVs, Samplers, etc.) foreach (var heap in commandList.SrvHeaps) { SrvHeaps.RecycleObject(fenceValue, heap); @@ -501,210 +813,98 @@ private void RecycleCommandListResources(CompiledCommandList commandList, long f commandList.SamplerHeaps.Clear(); DescriptorHeapLists.Release(commandList.SamplerHeaps); - commandList.Builder.NativeCommandLists.Enqueue(commandList.NativeCommandList); + var nativeCommandList = commandList.NativeCommandList; + commandList.Builder.NativeCommandLists.Enqueue(nativeCommandList); + CommandAllocators.RecycleObject(fenceValue, commandList.NativeCommandAllocator); } - internal bool IsFenceCompleteInternal(long fenceValue) + /// + /// Determines whether the specified fence value has been completed by the graphics Command Queue. + /// + /// The fence value to check for completion. + /// + /// if the specified fence value has been completed; + /// otherwise, . + /// + /// + /// This method checks the completion status of a fence value by comparing it to the last + /// known completed fence value. It ensures thread safety by updating the last completed + /// fence value when necessary. + /// + internal bool IsFenceCompleteInternal(ulong fenceValue) { // Try to avoid checking the fence if possible if (fenceValue > lastCompletedFence) - lastCompletedFence = Math.Max(lastCompletedFence, nativeFence.CompletedValue); // Protect against race conditions + lastCompletedFence = Math.Max(lastCompletedFence, nativeFence->GetCompletedValue()); // Protect against race conditions return fenceValue <= lastCompletedFence; } - internal void WaitForFenceInternal(long fenceValue) + /// + /// Waits for the specified fence value to be signaled by the graphics Command Queue, + /// blocking the calling thread if necessary. + /// + /// + /// The fence value to wait for. Must be greater than the last completed fence value. + /// + /// + /// + /// This method ensures that the specified fence value has been reached before continuing execution. + /// If the fence value is already complete, the method returns immediately. Otherwise, it blocks the + /// calling thread until the fence value is signaled. + /// + /// + /// Note that this method uses a lock to synchronize access to the underlying native fence, + /// which may cause contention in multithreaded scenarios if multiple threads are waiting + /// on different fence values. + /// + /// + internal void WaitForFenceInternal(ulong fenceValue) { if (IsFenceCompleteInternal(fenceValue)) return; - // TODO D3D12 in case of concurrency, this lock could end up blocking too long a second thread with lower fenceValue then first one - lock (nativeFence) + // TODO: D3D12: in case of concurrency, this lock could end up blocking too long a second thread with lower fenceValue then first one + lock (nativeFenceLock) { - nativeFence.SetEventOnCompletion(fenceValue, fenceEvent.SafeWaitHandle.DangerousGetHandle()); + var waitHandle = fenceEvent.SafeWaitHandle.DangerousGetHandle(); + HResult result = nativeFence->SetEventOnCompletion(fenceValue, (void*) waitHandle); + + if (result.IsFailure) + result.Throw(); + fenceEvent.WaitOne(); lastCompletedFence = fenceValue; } } - internal void TagResource(GraphicsResourceLink resourceLink) + /// + /// Tags a Graphics Resource as no having alive references, meaning it should be safe to dispose it + /// or discard its contents during the next or SetData operation. + /// + /// + /// A object identifying the Graphics Resource along some related allocation information. + /// + internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink) { - var texture = resourceLink.Resource as Texture; - if (texture != null && texture.Usage == GraphicsResourceUsage.Dynamic) + Debug.Assert(resourceLink is { Resource: Texture or Buffer }, "Resource link cannot be null, and must be a Texture or a Buffer."); + + if (resourceLink.Resource is Texture { Usage: GraphicsResourceUsage.Dynamic }) { // Increase the reference count until GPU is done with the resource resourceLink.ReferenceCount++; - TemporaryResources.Enqueue(new KeyValuePair(NextFenceValue, resourceLink)); + TemporaryResources.Enqueue((NextFenceValue, resourceLink)); } - var buffer = resourceLink.Resource as Buffer; - if (buffer != null && buffer.Usage == GraphicsResourceUsage.Dynamic) + if (resourceLink.Resource is Buffer { Usage: GraphicsResourceUsage.Dynamic }) { // Increase the reference count until GPU is done with the resource resourceLink.ReferenceCount++; - TemporaryResources.Enqueue(new KeyValuePair(NextFenceValue, resourceLink)); - } - } - - internal abstract class ResourcePool : IDisposable where T : Pageable - { - protected readonly GraphicsDevice GraphicsDevice; - private readonly Queue> liveObjects = new Queue>(); - - protected ResourcePool(GraphicsDevice graphicsDevice) - { - GraphicsDevice = graphicsDevice; - } - - public void Dispose() - { - lock (liveObjects) - { - foreach (var liveObject in liveObjects) - { - liveObject.Value.Dispose(); - } - liveObjects.Clear(); - } - } - - public T GetObject() - { - // TODO D3D12: SpinLock - lock (liveObjects) - { - // Check if first allocator is ready for reuse - if (liveObjects.Count > 0) - { - var firstAllocator = liveObjects.Peek(); - if (firstAllocator.Key <= GraphicsDevice.nativeFence.CompletedValue) - { - liveObjects.Dequeue(); - ResetObject(firstAllocator.Value); - - if (firstAllocator.Value == null) - { - - } - - return firstAllocator.Value; - } - } - - return CreateObject(); - } - } - - protected abstract T CreateObject(); - - protected abstract void ResetObject(T obj); - - public void RecycleObject(long fenceValue, T obj) - { - // TODO D3D12: SpinLock - lock (liveObjects) - { - liveObjects.Enqueue(new KeyValuePair(fenceValue, obj)); - } - } - } - - internal class CommandAllocatorPool : ResourcePool - { - public CommandAllocatorPool(GraphicsDevice graphicsDevice) : base(graphicsDevice) - { - } - - protected override CommandAllocator CreateObject() - { - // No allocator ready to be used, let's create a new one - return GraphicsDevice.NativeDevice.CreateCommandAllocator(CommandListType.Direct); - } - - protected override void ResetObject(CommandAllocator obj) - { - obj.Reset(); - } - } - - internal class HeapPool : ResourcePool - { - private readonly int heapSize; - private readonly DescriptorHeapType heapType; - - public HeapPool(GraphicsDevice graphicsDevice, int heapSize, DescriptorHeapType heapType) : base(graphicsDevice) - { - this.heapSize = heapSize; - this.heapType = heapType; - } - - protected override DescriptorHeap CreateObject() - { - // No allocator ready to be used, let's create a new one - return GraphicsDevice.NativeDevice.CreateDescriptorHeap(new DescriptorHeapDescription - { - DescriptorCount = heapSize, - Flags = DescriptorHeapFlags.ShaderVisible, - Type = heapType, - }); - } - - protected override void ResetObject(DescriptorHeap obj) - { - } - } - - /// - /// Allocate descriptor handles. For now a simple bump alloc, but at some point we will have to make a real allocator with free - /// - internal class DescriptorAllocator : IDisposable - { - private const int DescriptorPerHeap = 256; - - private GraphicsDevice device; - private DescriptorHeapType descriptorHeapType; - private DescriptorHeap currentHeap; - private CpuDescriptorHandle currentHandle; - private int remainingHandles; - private readonly int descriptorSize; - - public DescriptorAllocator(GraphicsDevice device, DescriptorHeapType descriptorHeapType) - { - this.device = device; - this.descriptorHeapType = descriptorHeapType; - this.descriptorSize = device.NativeDevice.GetDescriptorHandleIncrementSize(descriptorHeapType); - } - - public void Dispose() - { - currentHeap?.Dispose(); - currentHeap = null; - } - - public CpuDescriptorHandle Allocate(int count) - { - if (currentHeap == null || remainingHandles < count) - { - currentHeap = device.NativeDevice.CreateDescriptorHeap(new DescriptorHeapDescription - { - Flags = DescriptorHeapFlags.None, - Type = descriptorHeapType, - DescriptorCount = DescriptorPerHeap, - NodeMask = 1, - }); - remainingHandles = DescriptorPerHeap; - currentHandle = currentHeap.CPUDescriptorHandleForHeapStart; - } - - var result = currentHandle; - - currentHandle.Ptr += descriptorSize; - remainingHandles -= count; - - return result; + TemporaryResources.Enqueue((NextFenceValue, resourceLink)); } } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDeviceFeatures.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDeviceFeatures.Direct3D12.cs index e65ce5a3c9..38a304a352 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsDeviceFeatures.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsDeviceFeatures.Direct3D12.cs @@ -1,18 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,99 +24,142 @@ // THE SOFTWARE. using System; -using System.Collections.Generic; -using SharpDX.DXGI; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.Direct3D12; +using System.Linq; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +using DXGIFormat = Silk.NET.DXGI.Format; + +using static System.Runtime.CompilerServices.Unsafe; -namespace Stride.Graphics +namespace Stride.Graphics; + +public unsafe partial struct GraphicsDeviceFeatures { - /// - /// Features supported by a . - /// - /// - /// This class gives also features for a particular format, using the operator this[dxgiFormat] on this structure. - /// - public partial struct GraphicsDeviceFeatures + private static readonly DXGIFormat[] ObsoleteFormatToExclude = + [ + DXGIFormat.FormatR1Unorm, + DXGIFormat.FormatB5G6R5Unorm, + DXGIFormat.FormatB5G5R5A1Unorm + ]; + + internal GraphicsDeviceFeatures(GraphicsDevice device) { - private static readonly List ObsoleteFormatToExcludes = new List() { Format.R1_UNorm, Format.B5G6R5_UNorm, Format.B5G5R5A1_UNorm }; + var nativeDevice = device.NativeDevice; - internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) - { - var nativeDevice = deviceRoot.NativeDevice; + HasSRgb = true; - HasSRgb = true; + mapFeaturesPerFormat = new FeaturesPerFormat[256]; - mapFeaturesPerFormat = new FeaturesPerFormat[256]; + // Set back the real GraphicsProfile that is used + // TODO D3D12 + RequestedProfile = device.RequestedProfile; + CurrentProfile = GraphicsProfileHelper.FromFeatureLevel(device.CurrentFeatureLevel); - // Set back the real GraphicsProfile that is used - // TODO D3D12 - RequestedProfile = deviceRoot.RequestedProfile; - CurrentProfile = GraphicsProfileHelper.FromFeatureLevel(deviceRoot.CurrentFeatureLevel); + // TODO D3D12 + HasComputeShaders = true; - // TODO D3D12 - HasComputeShaders = true; - HasDoublePrecision = nativeDevice.D3D12Options.DoublePrecisionFloatShaderOps; + SkipInit(out FeatureDataD3D12Options d3D12Options); + HResult result = nativeDevice.CheckFeatureSupport(Feature.D3D12Options, ref d3D12Options, (uint) SizeOf()); - // TODO D3D12 Confirm these are correct - // Some docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476876(v=vs.85).aspx - HasDepthAsSRV = true; - HasDepthAsReadOnlyRT = true; - HasMultisampleDepthAsSRV = true; + if (result.IsFailure) + result.Throw(); - HasResourceRenaming = false; + HasDoublePrecision = d3D12Options.DoublePrecisionFloatShaderOps; - HasMultiThreadingConcurrentResources = true; - HasDriverCommandLists = true; + // TODO D3D12 Confirm these are correct + // Some docs: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476876(v=vs.85).aspx + HasDepthAsSRV = true; + HasDepthAsReadOnlyRT = true; + HasMultiSampleDepthAsSRV = true; - // Check features for each DXGI.Format - foreach (var format in Enum.GetValues(typeof(SharpDX.DXGI.Format))) - { - var dxgiFormat = (SharpDX.DXGI.Format)format; - var maximumMultisampleCount = MultisampleCount.None; - var formatSupport = FormatSupport.None; + HasResourceRenaming = false; - if (!ObsoleteFormatToExcludes.Contains(dxgiFormat)) - { - SharpDX.Direct3D12.FeatureDataFormatSupport formatSupportData; - formatSupportData.Format = dxgiFormat; - formatSupportData.Support1 = FormatSupport1.None; - formatSupportData.Support2 = FormatSupport2.None; - if (nativeDevice.CheckFeatureSupport(SharpDX.Direct3D12.Feature.FormatSupport, ref formatSupportData)) - formatSupport = (FormatSupport)formatSupportData.Support1; - maximumMultisampleCount = GetMaximumMultisampleCount(nativeDevice, dxgiFormat); - } + HasMultiThreadingConcurrentResources = true; + HasDriverCommandLists = true; + + // Check features for each DXGI.Format + foreach (var format in Enum.GetValues()) + { + if (format == DXGIFormat.FormatForceUint) + continue; - mapFeaturesPerFormat[(int)dxgiFormat] = new FeaturesPerFormat((PixelFormat)dxgiFormat, maximumMultisampleCount, formatSupport); + var maximumMultisampleCount = MultisampleCount.None; + var formatSupport = FormatSupport.None; + var csFormatSupport = ComputeShaderFormatSupport.None; + + if (!ObsoleteFormatToExclude.Contains(format)) + { + CheckFormatSupport(format, out formatSupport, out csFormatSupport, out maximumMultisampleCount); } + + mapFeaturesPerFormat[(int) format] = new((PixelFormat) format, maximumMultisampleCount, csFormatSupport, formatSupport); } /// - /// Gets the maximum multisample count for a particular . + /// Gets the maximum sample count when enabling multi-sampling for a particular . /// - /// The device. - /// The pixelFormat. - /// The maximum multisample count for this pixel pixelFormat - private static MultisampleCount GetMaximumMultisampleCount(SharpDX.Direct3D12.Device device, SharpDX.DXGI.Format pixelFormat) + MultisampleCount GetMaximumMultisampleCount(DXGIFormat pixelFormat) { - SharpDX.Direct3D12.FeatureDataMultisampleQualityLevels qualityLevels; - qualityLevels.Format = pixelFormat; - qualityLevels.Flags = MultisampleQualityLevelFlags.None; - qualityLevels.QualityLevelCount = 0; + FeatureDataMultisampleQualityLevels qualityLevels = new() + { + Format = pixelFormat, + Flags = MultisampleQualityLevelFlags.None, + NumQualityLevels = 0 + }; + var sizeInBytes = (uint) SizeOf(); - int maxCount = 1; - for (int i = 8; i >= 1; i /= 2) + uint maxCount = 1; + for (uint i = 8; i >= 1; i /= 2) { qualityLevels.SampleCount = i; - if (device.CheckFeatureSupport(SharpDX.Direct3D12.Feature.MultisampleQualityLevels, ref qualityLevels)) + + HResult result = nativeDevice.CheckFeatureSupport(Feature.MultisampleQualityLevels, ref qualityLevels, sizeInBytes); + + if (result.IsSuccess) { maxCount = i; break; } } - return (MultisampleCount)maxCount; + return (MultisampleCount) maxCount; + } + + /// + /// Check the support the Direct3D device has for the specified format. + /// + void CheckFormatSupport(DXGIFormat format, + out FormatSupport formatSupport, + out ComputeShaderFormatSupport csFormatSupport, + out MultisampleCount maximumMultisampleCount) + { + FeatureDataFormatSupport formatSupportData = new() + { + Format = format, + Support1 = FormatSupport1.None, + Support2 = FormatSupport2.None + }; + + HResult result = nativeDevice.CheckFeatureSupport(Feature.FormatSupport, ref formatSupportData, (uint) SizeOf()); + + if (result.IsFailure) + { + // Format not supported + formatSupport = FormatSupport.None; + csFormatSupport = ComputeShaderFormatSupport.None; + + maximumMultisampleCount = MultisampleCount.None; + } + else + { + formatSupport = (FormatSupport) formatSupportData.Support1; + csFormatSupport = (ComputeShaderFormatSupport) formatSupportData.Support2; + + maximumMultisampleCount = GetMaximumMultisampleCount(format); + } } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsOutput.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsOutput.Direct3D12.cs index 41b0ebfe12..cfcbc90285 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsOutput.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsOutput.Direct3D12.cs @@ -1,19 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 // Copyright (c) 2010-2014 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,204 +25,340 @@ using System; using System.Collections.Generic; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.DXGI; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Silk.NET.Maths; +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D12; -using Stride.Core; using Stride.Core.Mathematics; +using Stride.Core.UnsafeExtensions; -using ResultCode = SharpDX.DXGI.ResultCode; +using Rectangle = Stride.Core.Mathematics.Rectangle; namespace Stride.Graphics { - /// - /// Provides methods to retrieve and manipulate an graphics output (a monitor), it is equivalent to . - /// - /// bb174546 - /// IDXGIOutput - /// IDXGIOutput - public partial class GraphicsOutput + public unsafe partial class GraphicsOutput { - private readonly int outputIndex; - private readonly Output output; - private readonly OutputDescription outputDescription; + private IDXGIOutput* dxgiOutput; + private readonly uint outputIndex; + + private readonly OutputDesc outputDescription; + + /// + /// Gets the native DXGI output. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeOutput => ComPtrHelpers.ToComPtr(dxgiOutput); + + /// + /// Gets the handle of the monitor associated with this . + /// + public nint MonitorHandle => outputDescription.Monitor; + /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// - /// The adapter. - /// Index of the output. - /// output - /// output - internal GraphicsOutput(GraphicsAdapter adapter, int outputIndex) + /// The Graphics Adapter this output is attached to. + /// + /// A COM pointer to the native interface. + /// The ownership is transferred to this instance, so the reference count is not incremented. + /// + /// The index of the output. + /// is . + internal GraphicsOutput(GraphicsAdapter adapter, ComPtr nativeOutput, uint outputIndex) { - if (adapter == null) throw new ArgumentNullException("adapter"); + ArgumentNullException.ThrowIfNull(adapter); + + Debug.Assert(nativeOutput.IsNotNull()); this.outputIndex = outputIndex; - this.adapter = adapter; - this.output = adapter.NativeAdapter.GetOutput(outputIndex).DisposeBy(this); - outputDescription = output.Description; + dxgiOutput = nativeOutput; + + Adapter = adapter; + + Unsafe.SkipInit(out OutputDesc outputDesc); + HResult result = nativeOutput.GetDesc(ref outputDesc); + + if (result.IsFailure) + result.Throw(); + + Name = SilkMarshal.PtrToString((nint) outputDesc.DeviceName, NativeStringEncoding.LPWStr); - unsafe + ref var rectangle = ref outputDesc.DesktopCoordinates; + DesktopBounds = new Rectangle { - var rectangle = outputDescription.DesktopBounds; - desktopBounds = *(Rectangle*)&rectangle; - } + Location = rectangle.Min.BitCast, Point>(), + Width = rectangle.Size.X, + Height = rectangle.Size.Y + }; + + outputDescription = outputDesc; } + /// + protected override void Destroy() + { + base.Destroy(); + + ComPtrHelpers.SafeRelease(ref dxgiOutput); + } + + /// - /// Find the display mode that most closely matches the requested display mode. + /// Finds the display mode that most closely matches the requested display mode. /// - /// The target profile, as available formats are different depending on the feature level.. - /// The mode. - /// Returns the closes display mode. - /// HRESULT IDXGIOutput::FindClosestMatchingMode([In] const DXGI_MODE_DESC* pModeToMatch,[Out] DXGI_MODE_DESC* pClosestMatch,[In, Optional] IUnknown* pConcernedDevice) - /// Direct3D devices require UNORM formats. This method finds the closest matching available display mode to the mode specified in pModeToMatch. Similarly ranked fields (i.e. all specified, or all unspecified, etc) are resolved in the following order. ScanlineOrdering Scaling Format Resolution RefreshRate When determining the closest value for a particular field, previously matched fields are used to filter the display mode list choices, and other fields are ignored. For example, when matching Resolution, the display mode list will have already been filtered by a certain ScanlineOrdering, Scaling, and Format, while RefreshRate is ignored. This ordering doesn't define the absolute ordering for every usage scenario of FindClosestMatchingMode, because the application can choose some values initially, effectively changing the order that fields are chosen. Fields of the display mode are matched one at a time, generally in a specified order. If a field is unspecified, FindClosestMatchingMode gravitates toward the values for the desktop related to this output. If this output is not part of the desktop, then the default desktop output is used to find values. If an application uses a fully unspecified display mode, FindClosestMatchingMode will typically return a display mode that matches the desktop settings for this output. Unspecified fields are lower priority than specified fields and will be resolved later than specified fields. - public DisplayMode FindClosestMatchingDisplayMode(GraphicsProfile[] targetProfiles, DisplayMode mode) + /// The target profiles, as available formats differ depending on the graphics profile. + /// + /// The desired display mode. + /// + /// Members of can be unspecified indicating no preference for that member. + /// + /// + /// A value of 0 for or indicates the value is unspecified. + /// If either Width or Height are 0, both must be 0. + /// + /// + /// A numerator and denominator of 0 in indicate it is unspecified. + /// + /// + /// A value of for indicates the pixel format is unspecified. + /// + /// + /// Returns the mode that most closely matches . + /// is empty and does not specify any graphics profile to test. + /// + /// Coult not create a device with any of the profiles specified in . + /// + /// + /// Direct3D devices require UNORM pixel formats. + /// + /// Unspecified fields are lower priority than specified fields and will be resolved later than specified fields. + /// Similarly ranked fields (i.e. all specified, or all unspecified, etc.) are resolved in the following order: Format, Width, Height, RefreshRate. + /// + /// + /// When determining the closest value for a particular field, previously matched fields are used to filter the display mode list choices, and other fields are ignored. + /// For example, when matching resolution, the display mode list will have already been filtered by a certain pixel format, while the refresh rate is ignored. + /// + /// + /// This ordering doesn't define the absolute ordering for every usage scenario of , because the application can choose some + /// values initially, effectively changing the order that fields are chosen. Fields of the display mode are matched one at a time, generally in a specified order. + /// If a field is unspecified, this method gravitates toward the values for the desktop related to this output. If this output is not part of the desktop, then + /// the default desktop output is used to find values. + /// + /// + /// If an application uses a fully unspecified display mode, will typically return a display mode that matches the + /// desktop settings for this output. + /// + /// + public DisplayMode FindClosestMatchingDisplayMode(ReadOnlySpan targetProfiles, DisplayMode modeToMatch) { - if (targetProfiles == null) throw new ArgumentNullException("targetProfiles"); + if (targetProfiles.IsEmpty) + throw new ArgumentNullException(nameof(targetProfiles)); + + var d3d12 = D3D12.GetApi(); - ModeDescription closestDescription; - SharpDX.Direct3D12.Device deviceTemp = null; - for (int i = 0; i < targetProfiles.Length; i++) + // NOTE: Assume the same underlying integer type + Debug.Assert(sizeof(GraphicsProfile) == sizeof(D3DFeatureLevel)); + var featureLevels = targetProfiles.Cast(); + + HResult result = default; + + var nativeAdapter = Adapter.NativeAdapter.AsIUnknown(); + ComPtr deviceTemp = null; + + for (int i = 0; i < featureLevels.Length; i++) { + var featureLevelToTry = featureLevels[i]; + // Create Device D3D12 with feature Level based on profile - try - { - deviceTemp = new SharpDX.Direct3D12.Device(Adapter.NativeAdapter, (FeatureLevel)targetProfiles[i]); + result = d3d12.CreateDevice(nativeAdapter, featureLevelToTry, out deviceTemp); + + if (result.IsSuccess) break; - } - catch (Exception) - { - } } - if (deviceTemp == null) - throw new InvalidOperationException("Could not create D3D12 graphics device"); + if (deviceTemp.IsNull() && result.IsFailure) + ThrowNoCompatibleProfile(result, Adapter, targetProfiles); - var description = new SharpDX.DXGI.ModeDescription() + Unsafe.SkipInit(out ModeDesc closestDescription); + ModeDesc modeDescription = new() { - Width = mode.Width, - Height = mode.Height, - RefreshRate = mode.RefreshRate.ToSharpDX(), - Format = (SharpDX.DXGI.Format)mode.Format, - Scaling = DisplayModeScaling.Unspecified, - ScanlineOrdering = DisplayModeScanlineOrder.Unspecified + Width = (uint) modeToMatch.Width, + Height = (uint) modeToMatch.Height, + RefreshRate = modeToMatch.RefreshRate.ToSilk(), + Format = (Format) modeToMatch.Format, + Scaling = ModeScaling.Unspecified, + ScanlineOrdering = ModeScanlineOrder.Unspecified }; - using (var device = deviceTemp) - output.GetClosestMatchingMode(device, description, out closestDescription); - return DisplayMode.FromDescription(closestDescription); - } + result = dxgiOutput->FindClosestMatchingMode(in modeDescription, ref closestDescription, deviceTemp); - /// - /// Retrieves the handle of the monitor associated with this . - /// - /// bb173068 - /// HMONITOR Monitor - /// HMONITOR Monitor - public IntPtr MonitorHandle { get { return outputDescription.MonitorHandle; } } + if (result.IsFailure) + result.Throw(); - /// - /// Gets the native output. - /// - /// The native output. - internal Output NativeOutput - { - get + deviceTemp.Release(); + + return DisplayMode.FromDescription(in closestDescription); + + /// + /// Logs and throws an exception reporting that no compatible profile was found among the specified ones. + /// + [DoesNotReturn] + static void ThrowNoCompatibleProfile(HResult result, GraphicsAdapter adapter, ReadOnlySpan targetProfiles) { - return output; + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to create Direct3D device using adapter '{adapter.Description}' with profiles: {string.Join(", ", targetProfiles.ToArray())}.\nException: {exception}"); + throw exception; } } /// - /// Enumerates all available display modes for this output and stores them in . + /// Enumerates all available display modes for this output and stores them in . /// private void InitializeSupportedDisplayModes() { + HResult result = default; + var modesAvailable = new List(); - var modesMap = new Dictionary(); + var knownModes = new Dictionary(); #if DIRECTX11_1 - var output1 = output.QueryInterface(); + using ComPtr output1 = dxgiOutput->QueryInterface(); #endif + const uint DisplayModeEnumerationFlags = DXGI.EnumModesInterlaced | DXGI.EnumModesScaling; - try + foreach (var format in Enum.GetValues()) { - const DisplayModeEnumerationFlags displayModeEnumerationFlags = DisplayModeEnumerationFlags.Interlaced | DisplayModeEnumerationFlags.Scaling; + if (format == Format.FormatForceUint) + continue; + + uint displayModeCount = 0; +#if DIRECTX11_1 + result = output1.GetDisplayModeList1(format, DisplayModeEnumerationFlags, ref displayModeCount, null); +#else + result = dxgiOutput->GetDisplayModeList(format, DisplayModeEnumerationFlags, ref displayModeCount, null); +#endif + if (result.IsFailure && result.Code != DxgiConstants.ErrorNotCurrentlyAvailable) + result.Throw(); + if (displayModeCount == 0) + continue; - foreach (var format in Enum.GetValues(typeof(SharpDX.DXGI.Format))) - { - var dxgiFormat = (Format)format; #if DIRECTX11_1 - var modes = output1.GetDisplayModeList1(dxgiFormat, displayModeEnumerationFlags); + Span displayModes = stackalloc ModeDesc1[(int) displayModeCount]; + result = output1.GetDisplayModeList1(format, DisplayModeEnumerationFlags, ref displayModeCount, ref displayModes[0]); #else - var modes = output.GetDisplayModeList(dxgiFormat, displayModeEnumerationFlags); + Span displayModes = stackalloc ModeDesc[(int) displayModeCount]; + result = dxgiOutput->GetDisplayModeList(format, DisplayModeEnumerationFlags, ref displayModeCount, ref displayModes[0]); #endif - foreach (var mode in modes) + for (int i = 0; i < displayModeCount; i++) + { + ref var mode = ref displayModes[i]; + + if (mode.Scaling != ModeScaling.Unspecified) + continue; + + var modeKey = HashCode.Combine(format, mode.Width, mode.Height, mode.RefreshRate.Numerator, mode.RefreshRate.Denominator); + + if (!knownModes.ContainsKey(modeKey)) { - if (mode.Scaling == DisplayModeScaling.Unspecified) - { - var key = format + ";" + mode.Width + ";" + mode.Height + ";" + mode.RefreshRate.Numerator + ";" + mode.RefreshRate.Denominator; - - DisplayMode oldMode; - if (!modesMap.TryGetValue(key, out oldMode)) - { - var displayMode = DisplayMode.FromDescription(mode); - - modesMap.Add(key, displayMode); - modesAvailable.Add(displayMode); - } - } + var displayMode = DisplayMode.FromDescription(in mode); + + knownModes.Add(modeKey, displayMode); + modesAvailable.Add(displayMode); } } } - catch (SharpDX.SharpDXException dxgiException) - { - if (dxgiException.ResultCode != ResultCode.NotCurrentlyAvailable) - throw; - } -#if DIRECTX11_1 - output1.Dispose(); -#endif supportedDisplayModes = modesAvailable.ToArray(); } /// - /// Initializes with the most appropiate mode from . + /// Initializes with the current , + /// the closest matching mode with the common formats or ), + /// or in no matching mode could be found. /// - /// It checks first for a mode with , - /// if it is not found - it checks for . private void InitializeCurrentDisplayMode() { - currentDisplayMode = TryFindMatchingDisplayMode(Format.R8G8B8A8_UNorm) - ?? TryFindMatchingDisplayMode(Format.B8G8R8A8_UNorm); + currentDisplayMode = GetCurrentDisplayMode() ?? + TryFindMatchingDisplayMode(Format.FormatR8G8B8A8Unorm) ?? + TryFindMatchingDisplayMode(Format.FormatB8G8R8A8Unorm); } /// - /// Tries to find a display mode that has the same size as the current associated with this instance - /// of the specified format. + /// Tries to get the current based on the . + /// + /// The current of the output, or if couldn't be determined. + private DisplayMode? GetCurrentDisplayMode() + { + var d3d12 = D3D12.GetApi(); + + // Try to create a dummy ID3D12Device with no consideration to Graphics Profiles, etc. + // We only want to get missing information about the current display irrespective of graphics profiles + var unspecifiedAdapter = ComPtrHelpers.NullComPtr(); + D3DFeatureLevel selectedLevel = 0; + HResult result = d3d12.CreateDevice(unspecifiedAdapter, selectedLevel, out ComPtr deviceTemp); + + if (result.IsFailure) + { + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to create Direct3D device using adapter '{Adapter.Description}'.\nException: {exception}"); + return null; + } + + Unsafe.SkipInit(out ModeDesc closestMatch); + var modeDesc = new ModeDesc + { + Width = (uint) DesktopBounds.Width, + Height = (uint) DesktopBounds.Height, + // Format and RefreshRate will be automatically filled if we pass reference to the Direct3D 12 device + Format = Format.FormatUnknown + }; + + result = dxgiOutput->FindClosestMatchingMode(in modeDesc, ref closestMatch, deviceTemp); + + if (result.IsFailure) + { + var exception = Marshal.GetExceptionForHR(result.Value)!; + Log.Error($"Failed to get current display mode. The resolution ({modeDesc.Width}x{modeDesc.Height}) " + + $"taken from the output is not correct.\nException: {exception}"); + return null; + } + + return DisplayMode.FromDescription(in closestMatch); + } + + /// + /// Tries to find a display mode with the specified format that has the same size as the current desktop size + /// of this . /// /// The format to match with. - /// A matched or null if nothing is found. - private DisplayMode TryFindMatchingDisplayMode(Format format) + /// A matched , or if nothing is found. + private DisplayMode? TryFindMatchingDisplayMode(Format format) { - var desktopBounds = outputDescription.DesktopBounds; + var desktopBounds = outputDescription.DesktopCoordinates; foreach (var supportedDisplayMode in SupportedDisplayModes) { - var width = desktopBounds.Right - desktopBounds.Left; - var height = desktopBounds.Bottom - desktopBounds.Top; + var width = desktopBounds.Size.X; + var height = desktopBounds.Size.Y; + var matchingFormat = (PixelFormat) format; - if (supportedDisplayMode.Width == width - && supportedDisplayMode.Height == height - && (Format)supportedDisplayMode.Format == format) + if (supportedDisplayMode.Width == width && + supportedDisplayMode.Height == height && + supportedDisplayMode.Format == matchingFormat) { - // Stupid DXGI, there is no way to get the DXGI.Format, nor the refresh rate. - return new DisplayMode((PixelFormat)format, width, height, supportedDisplayMode.RefreshRate); + // TODO: DXGI, there is no way to get the DXGI.Format, nor the refresh rate + return new DisplayMode(matchingFormat, width, height, supportedDisplayMode.RefreshRate); } } @@ -229,4 +366,5 @@ private DisplayMode TryFindMatchingDisplayMode(Format format) } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs index 496aee6891..ca4d55476e 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResource.Direct3D12.cs @@ -1,39 +1,69 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 -using System; -using SharpDX.Direct3D12; + +using Silk.NET.Direct3D12; namespace Stride.Graphics { - /// - /// GraphicsResource class - /// public abstract partial class GraphicsResource { + /// + /// A reference to the parent that owns this resource, if any. + /// internal GraphicsResource ParentResource; - internal long? StagingFenceValue; + /// + /// An optional fence value used to track the staging of this Graphics Resource. + /// + internal ulong? StagingFenceValue; + /// + /// The Command List being used to record commands for staging data into this Graphics Resource. + /// internal CommandList StagingBuilder; + + /// + /// A handle to the CPU-accessible Shader Resource View (SRV) Descriptor. + /// internal CpuDescriptorHandle NativeShaderResourceView; + /// + /// A handle to the CPU-accessible Unordered Access View (UAV) Descriptor. + /// internal CpuDescriptorHandle NativeUnorderedAccessView; + + /// + /// The current Direct3D 12 Resource State of the Graphics Resource. + /// internal ResourceStates NativeResourceState; - protected bool IsDebugMode => GraphicsDevice != null && GraphicsDevice.IsDebugMode; + /// + /// Gets a value indicating whether the Graphics Resource is in "Debug mode". + /// + /// + /// if the Graphics Resource is initialized in "Debug mode"; otherwise, . + /// + protected bool IsDebugMode => GraphicsDevice?.IsDebugMode == true; + /// - /// Returns true if resource state transition is needed in order to use resource in given state + /// Determines if the Graphics Resource needs to perform a state transition in order to reach the target state. /// - /// Destination resource state - /// True if need to perform a transition, otherwsie false + /// The destination Graphics Resource state. + /// + /// if a transition is needed to reach the target state; + /// otherwise, . + /// internal bool IsTransitionNeeded(ResourceStates targeState) { // If 'targeState' is a subset of 'before', then there's no need for a transition - // Note: COMMON is an oddball state that doesn't follow the RESOURE_STATE pattern of - // having exactly one bit set so we need to special case these - return NativeResourceState != targeState && ((NativeResourceState | targeState) != NativeResourceState || targeState == ResourceStates.Common); + + // NOTE: ResourceStates.Common is an oddball state that doesn't follow the ResourceStates + // pattern of having exactly one bit set so we need to special case these + return NativeResourceState != targeState && + ((NativeResourceState | targeState) != NativeResourceState || targeState == ResourceStates.Common); } } } - + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs index 2d628f0d0e..4bf90b80fb 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/GraphicsResourceBase.Direct3D12.cs @@ -1,93 +1,126 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; -using System.Collections.Generic; -using System.Diagnostics; -using SharpDX; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +using static Stride.Graphics.ComPtrHelpers; namespace Stride.Graphics { - /// - /// GraphicsResource class - /// - public abstract partial class GraphicsResourceBase + public abstract unsafe partial class GraphicsResourceBase { - private SharpDX.Direct3D12.DeviceChild nativeDeviceChild; - - protected internal SharpDX.Direct3D12.Resource NativeResource { get; private set; } + private ID3D12DeviceChild* nativeDeviceChild; + private ID3D12Resource* nativeResource; - private void Initialize() - { - } + /// + /// Gets the internal Direct3D 11 Resource. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr NativeResource => ToComPtr(nativeResource); /// - /// Gets or sets the device child. + /// Gets the internal Direct3D 12 Device Child. /// - /// The device child. - protected internal SharpDX.Direct3D12.DeviceChild NativeDeviceChild + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected internal ComPtr NativeDeviceChild { - get - { - return nativeDeviceChild; - } + get => ToComPtr(nativeDeviceChild); set { - nativeDeviceChild = value; - NativeResource = nativeDeviceChild as SharpDX.Direct3D12.Resource; - // Associate PrivateData to this DeviceResource - SetDebugName(GraphicsDevice, nativeDeviceChild, Name); + if (nativeDeviceChild == value.Handle) + return; + + var oldDeviceChild = nativeDeviceChild; + if (oldDeviceChild is not null) + oldDeviceChild->Release(); + + nativeDeviceChild = value.Handle; + + if (nativeDeviceChild is null) + return; + + nativeDeviceChild->AddRef(); + + HResult result = nativeDeviceChild->QueryInterface(out ComPtr d3d12Resource); + + // The device child can be something that is not a Direct3D resource actually, + // like a Sampler State, for example + if (result.IsSuccess) + { + nativeResource = d3d12Resource.Handle; + } + + NativeDeviceChild.SetDebugName(Name); } } /// - /// Associates the private data to the device child, useful to get the name in PIX debugger. + /// Sets the internal Direct3D 12 Device Child to without releasing it. + /// This is used when the Graphics Device is being destroyed, but the resource is a View. /// - internal static void SetDebugName(GraphicsDevice graphicsDevice, SharpDX.Direct3D12.DeviceChild deviceChild, string name) + protected internal void ForgetNativeChildWithoutReleasing() { + nativeDeviceChild = null; + nativeResource = null; } /// - /// Called when graphics device has been detected to be internally destroyed. + /// Gets the internal Direct3D 11 device () if the resource is attached to + /// a , or if not. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + protected ComPtr NativeDevice => GraphicsDevice?.NativeDevice ?? default; + + + // No Direct3D-specific initialization + private partial void Initialize() { } + + /// + /// Called when the has been detected to be internally destroyed, + /// or when the methad has been called. Raises the event. /// - protected internal virtual void OnDestroyed() + /// + /// This method releases the underlying native resources ( and ). + /// + protected internal virtual partial void OnDestroyed() { Destroyed?.Invoke(this, EventArgs.Empty); - if (nativeDeviceChild != null) + if (nativeDeviceChild is not null) { // Schedule the resource for destruction (as soon as we are done with it) - GraphicsDevice.TemporaryResources.Enqueue(new KeyValuePair(GraphicsDevice.NextFenceValue, nativeDeviceChild)); + GraphicsDevice.TemporaryResources.Enqueue((GraphicsDevice.NextFenceValue, NativeResource)); nativeDeviceChild = null; } - NativeResource = null; + + SafeRelease(ref nativeResource); } /// - /// Called when graphics device has been recreated. + /// Called when the has been recreated. /// - /// True if item transitioned to a state. + /// + /// if resource has transitioned to the state. + /// protected internal virtual bool OnRecreate() { return false; } - - protected SharpDX.Direct3D12.Device NativeDevice - { - get - { - return GraphicsDevice != null ? GraphicsDevice.NativeDevice : null; - } - } - - internal static void ReleaseComObject(ref T comObject) where T : class, IUnknown - { - // We can't put IUnknown as a constraint on the generic as it would break compilation (trying to import SharpDX in projects with InternalVisibleTo) - var refCountResult = comObject.Release(); - comObject = null; - } } } - + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/MappedResource.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/MappedResource.Direct3D12.cs index cf3245242a..9802800c56 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/MappedResource.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/MappedResource.Direct3D12.cs @@ -2,12 +2,22 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_DIRECT3D12 -namespace Stride.Graphics + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +namespace Stride.Graphics; + +public readonly unsafe partial struct MappedResource { - public partial struct MappedResource - { - internal SharpDX.Direct3D12.Resource UploadResource; - internal int UploadOffset; - } + /// + /// The Direct3D 12 Resource that has been mapped. + /// + internal readonly ComPtr UploadResource; + /// + /// The pointer to the mapped data in the Direct3D 12 Resource. + /// + internal readonly int UploadOffset; } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/PipelineState.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/PipelineState.Direct3D12.cs index e943e42b39..b6c1009cc7 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/PipelineState.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/PipelineState.Direct3D12.cs @@ -1,373 +1,710 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; +using System.Diagnostics; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Linq; -using SharpDX.Direct3D; -using SharpDX.Direct3D12; -using SharpDX.DXGI; -using Stride.Core; -using Stride.Core.Storage; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D12; + +using Stride.Core.UnsafeExtensions; +using Stride.Core.Mathematics; using Stride.Shaders; +using static Stride.Graphics.ComPtrHelpers; + namespace Stride.Graphics { - public partial class PipelineState + // Type aliases to aid readability in this file + using DescriptorRangesByShaderStageMap = Dictionary>; + + + public unsafe partial class PipelineState { - internal SharpDX.Direct3D12.PipelineState CompiledState; - internal SharpDX.Direct3D12.RootSignature RootSignature; - internal PrimitiveTopology PrimitiveTopology; - internal bool HasScissorEnabled; - internal bool IsCompute; - internal int[] SrvBindCounts; - internal int[] SamplerBindCounts; - - internal unsafe PipelineState(GraphicsDevice graphicsDevice, PipelineStateDescription pipelineStateDescription) : base(graphicsDevice) + private ID3D12PipelineState* compiledPipelineState; + private ID3D12RootSignature* nativeRootSignature; + + /// + /// Gets the internal Direct3D 12 Pipeline State that was compiled from the provided + /// . + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr CompiledState => ToComPtr(compiledPipelineState); + + /// + /// Gets the internal Direct3D 12 Root Signature. + /// + /// + /// If the reference is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr RootSignature => ToComPtr(nativeRootSignature); + + /// + /// Gets the Direct3D 12 indicating how vertices in the rendered geometry + /// are to be interpreted to form primitives. + /// + internal D3DPrimitiveTopology PrimitiveTopology { get; } + + /// + /// Gets a value indicating whether to enables scissor testing. + /// Pixels outside the active scissor rectangles are culled. + /// + /// + /// When enabled, only pixels inside the active scissor rectangles configured in the are rendered. + /// This is commonly used for UI rendering, partial redraws, or performance optimization. + /// + internal bool HasScissorEnabled { get; } + + /// + /// Gets a value indicating whether the Pipeline State represents the state of the compute pipeline. + /// + /// + /// if the Pipeline State represents the state of the compute pipeline; + /// if it represents the state of the graphics pipeline. + /// + internal bool IsCompute { get; } + + // Counts of Root Parameters to bind for each Descriptor Set layout + private readonly int[] srvBindCountPerLayout; + private readonly int[] samplerBindCountPerLayout; + + /// + /// A map of the number of Root Parameters to bind for each Descriptor Set layout + /// (corresponding to Graphics Resource groups (rgroups) in Effects / Shaders) for + /// Descriptors of Shader Resource Views (SRVs). + /// + internal ReadOnlySpan SrvBindCountPerLayout => srvBindCountPerLayout; + /// + /// A map of the number of Root Parameters to bind for each Descriptor Set layout + /// (corresponding to Graphics Resource groups (rgroups) in Effects / Shaders) for + /// Descriptors of Samplers. + /// + internal ReadOnlySpan SamplerBindCountPerLayout => samplerBindCountPerLayout; + + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// A description of the desired graphics pipeline configuration. + /// + /// The reflected Effect bytecode in specifies a Shader stage that is invalid. + /// + /// + /// specifies a that is invalid. + /// + /// + /// When configuring the Descriptors for the reflected Effect bytecode in , + /// an invalid was found. + /// + /// + /// Static Samplers can only have opaque black, opaque white or transparent black as border color. + /// + /// + /// The reflected Effect bytecode in specifies an + /// of an unsupported type. Only Shader Resource Views, Unordered Access Views, and Constant Buffer Views + /// are supported. + /// + internal PipelineState(GraphicsDevice graphicsDevice, PipelineStateDescription pipelineStateDescription) : base(graphicsDevice) { - if (pipelineStateDescription.RootSignature != null) + Debug.Assert(pipelineStateDescription is not null); + if (pipelineStateDescription.RootSignature is null) + return; + + var tempMemoryAllocations = new List(); + + var effectReflection = pipelineStateDescription.EffectBytecode.Reflection; + + var computeShader = pipelineStateDescription.EffectBytecode.Stages.FirstOrDefault(e => e.Stage == ShaderStage.Compute); + IsCompute = computeShader is not null; + + var effectDescriptorSetLayouts = pipelineStateDescription.RootSignature.EffectDescriptorSetReflection.Layouts; + + var rootSignatureParameters = new List(); + var immutableSamplers = new List(); + srvBindCountPerLayout = new int[effectDescriptorSetLayouts.Count]; + samplerBindCountPerLayout = new int[effectDescriptorSetLayouts.Count]; + + // For each Descriptor Set layout in the reflected Shader / Effect bytecode, which should correspond to rgroups or to "Globals" + for (int layoutIndex = 0; layoutIndex < effectDescriptorSetLayouts.Count; layoutIndex++) { - var effectReflection = pipelineStateDescription.EffectBytecode.Reflection; + var layout = effectDescriptorSetLayouts[layoutIndex]; + if (layout.Layout is null) + continue; + + // TODO: D3D12: For now, we don't control registers, so we simply generate one resource table per Shader stage and per Descriptor Set layout. + // We should switch to a model where we make sure VS/PS don't overlap for common Descriptors so that they can be shared + var srvDescriptorRanges = new DescriptorRangesByShaderStageMap(); + var samplerDescriptorRanges = new DescriptorRangesByShaderStageMap(); - var computeShader = pipelineStateDescription.EffectBytecode.Stages.FirstOrDefault(e => e.Stage == ShaderStage.Compute); - IsCompute = computeShader != null; + int descriptorSrvOffset = 0; + int descriptorSamplerOffset = 0; - var rootSignatureParameters = new List(); - var immutableSamplers = new List(); - SrvBindCounts = new int[pipelineStateDescription.RootSignature.EffectDescriptorSetReflection.Layouts.Count]; - SamplerBindCounts = new int[pipelineStateDescription.RootSignature.EffectDescriptorSetReflection.Layouts.Count]; - for (int layoutIndex = 0; layoutIndex < pipelineStateDescription.RootSignature.EffectDescriptorSetReflection.Layouts.Count; layoutIndex++) + foreach (var layoutBuilderEntry in layout.Layout.Entries) { - var layout = pipelineStateDescription.RootSignature.EffectDescriptorSetReflection.Layouts[layoutIndex]; - if (layout.Layout == null) - continue; + var isSampler = layoutBuilderEntry.Class == EffectParameterClass.Sampler; - // TODO D3D12 for now, we don't control register so we simply generate one resource table per shader stage and per descriptor set layout - // we should switch to a model where we make sure VS/PS don't overlap for common descriptors so that they can be shared - var srvDescriptorRanges = new Dictionary>(); - var samplerDescriptorRanges = new Dictionary>(); - - int descriptorSrvOffset = 0; - int descriptorSamplerOffset = 0; - foreach (var item in layout.Layout.Entries) + FindMatchingResourceBindings(layoutBuilderEntry, isSampler, + srvDescriptorRanges, ref descriptorSrvOffset, + samplerDescriptorRanges, ref descriptorSamplerOffset); + + // Move to next element (mirror what is done in DescriptorSetLayout) + if (isSampler) + { + if (layoutBuilderEntry.ImmutableSampler is null) + descriptorSamplerOffset += layoutBuilderEntry.ArraySize; + } + else { - var isSampler = item.Class == EffectParameterClass.Sampler; + descriptorSrvOffset += layoutBuilderEntry.ArraySize; + } + } - // Find matching resource bindings - foreach (var binding in effectReflection.ResourceBindings) - { - if (binding.Stage == ShaderStage.None || binding.KeyInfo.Key != item.Key) - continue; + // Prepare the Root Parameters for the Descriptor Tables containing the Descriptors for this layout, + // and update the count of the number of Root Parameters to bind for the layout for each of the + // Descriptor types (SRVs, UAVs, CBVs, etc., or Samplers) + PrepareDescriptorRanges(srvDescriptorRanges, ref srvBindCountPerLayout[layoutIndex]); + PrepareDescriptorRanges(samplerDescriptorRanges, ref samplerBindCountPerLayout[layoutIndex]); + } - List descriptorRanges; - { - var dictionary = isSampler ? samplerDescriptorRanges : srvDescriptorRanges; - if (dictionary.TryGetValue(binding.Stage, out descriptorRanges) == false) - { - descriptorRanges = dictionary[binding.Stage] = new List(); - } - } - - if (isSampler) - { - if (item.ImmutableSampler != null) - { - immutableSamplers.Add(new StaticSamplerDescription(ShaderStage2ShaderVisibility(binding.Stage), binding.SlotStart, 0) - { - // TODO D3D12 ImmutableSampler should only be a state description instead of a GPU object? - Filter = (Filter)item.ImmutableSampler.Description.Filter, - ComparisonFunc = (Comparison)item.ImmutableSampler.Description.CompareFunction, - BorderColor = ColorHelper.ConvertStatic(item.ImmutableSampler.Description.BorderColor), - AddressU = (SharpDX.Direct3D12.TextureAddressMode)item.ImmutableSampler.Description.AddressU, - AddressV = (SharpDX.Direct3D12.TextureAddressMode)item.ImmutableSampler.Description.AddressV, - AddressW = (SharpDX.Direct3D12.TextureAddressMode)item.ImmutableSampler.Description.AddressW, - MinLOD = item.ImmutableSampler.Description.MinMipLevel, - MaxLOD = item.ImmutableSampler.Description.MaxMipLevel, - MipLODBias = item.ImmutableSampler.Description.MipMapLevelOfDetailBias, - MaxAnisotropy = item.ImmutableSampler.Description.MaxAnisotropy, - }); - } - else - { - // Add descriptor range - descriptorRanges.Add(new DescriptorRange(DescriptorRangeType.Sampler, item.ArraySize, binding.SlotStart, 0, descriptorSamplerOffset)); - } - } - else - { - DescriptorRangeType descriptorRangeType; - switch (binding.Class) - { - case EffectParameterClass.ConstantBuffer: - descriptorRangeType = DescriptorRangeType.ConstantBufferView; - break; - case EffectParameterClass.ShaderResourceView: - descriptorRangeType = DescriptorRangeType.ShaderResourceView; - break; - case EffectParameterClass.UnorderedAccessView: - descriptorRangeType = DescriptorRangeType.UnorderedAccessView; - break; - default: - throw new NotImplementedException(); - } - - // Add descriptor range - descriptorRanges.Add(new DescriptorRange(descriptorRangeType, item.ArraySize, binding.SlotStart, 0, descriptorSrvOffset)); - } - } + PrepareRootSignatureDescription(rootSignatureParameters, immutableSamplers, out RootSignatureDesc rootSignatureDesc); - // Move to next element (mirror what is done in DescriptorSetLayout) - if (isSampler) - { - if (item.ImmutableSampler == null) - descriptorSamplerOffset += item.ArraySize; - } - else - { - descriptorSrvOffset += item.ArraySize; - } + var d3d12 = D3D12.GetApi(); + + using ComPtr rootSignatureBytes = default; + using ComPtr errorMessagesBlob = default; + + HResult result = d3d12.SerializeRootSignature(in rootSignatureDesc, D3DRootSignatureVersion.Version1, + ref rootSignatureBytes.GetPinnableReference(), ref errorMessagesBlob.GetPinnableReference()); + if (result.IsFailure) + result.Throw(); + + result = NativeDevice.CreateRootSignature(nodeMask: 0, rootSignatureBytes.GetBufferPointer(), rootSignatureBytes.GetBufferSize(), + out ComPtr rootSignature); + if (result.IsFailure) + result.Throw(); + + // Check if it should use compute pipeline state + if (IsCompute) + { + var nativePipelineStateDescription = new ComputePipelineStateDesc + { + CS = GetShaderBytecode(computeShader.Data), + Flags = PipelineStateFlags.None, + PRootSignature = rootSignature + }; + + result = NativeDevice.CreateComputePipelineState(in nativePipelineStateDescription, out ComPtr pipelineState); + + if (result.IsFailure) + result.Throw(); + + compiledPipelineState = pipelineState; + } + else // Graphics Pipeline State + { + var nativePipelineStateDescription = new GraphicsPipelineStateDesc + { + InputLayout = PrepareInputLayout(pipelineStateDescription.InputElements), + PRootSignature = rootSignature, + RasterizerState = CreateRasterizerState(pipelineStateDescription.RasterizerState), + BlendState = CreateBlendState(pipelineStateDescription.BlendState), + SampleMask = pipelineStateDescription.SampleMask, + DSVFormat = (Format) pipelineStateDescription.Output.DepthStencilFormat, + DepthStencilState = CreateDepthStencilState(pipelineStateDescription.DepthStencilState), + NumRenderTargets = (uint) pipelineStateDescription.Output.RenderTargetCount, + // TODO: D3D12: Hardcoded Stream-Output in PipelineState + StreamOutput = new StreamOutputDesc(), + PrimitiveTopologyType = GetPrimitiveTopologyType(pipelineStateDescription.PrimitiveType), + // TODO: D3D12: Hardcoded no Multi-Sampling in PipelineState + SampleDesc = new SampleDesc(1, 0) + }; + + // Disable Depth Buffer if no format specified + if (nativePipelineStateDescription.DSVFormat == Format.FormatUnknown) + nativePipelineStateDescription.DepthStencilState.DepthEnable = false; + + var rtvFormats = nativePipelineStateDescription.RTVFormats.AsSpan(); + Debug.Assert(sizeof(PixelFormat) == sizeof(Format)); + Debug.Assert(rtvFormats.Length == pipelineStateDescription.Output.RenderTargetFormats.Length); + pipelineStateDescription.Output.RenderTargetFormats.As().CopyTo(rtvFormats); + + foreach (var stage in pipelineStateDescription.EffectBytecode.Stages) + { + var shaderBytecode = GetShaderBytecode(stage.Data); + + switch (stage.Stage) + { + case ShaderStage.Vertex: nativePipelineStateDescription.VS = shaderBytecode; break; + case ShaderStage.Hull: nativePipelineStateDescription.HS = shaderBytecode; break; + case ShaderStage.Domain: nativePipelineStateDescription.DS = shaderBytecode; break; + case ShaderStage.Geometry: nativePipelineStateDescription.GS = shaderBytecode; break; + case ShaderStage.Pixel: nativePipelineStateDescription.PS = shaderBytecode; break; + + default: + throw new ArgumentOutOfRangeException(nameof(pipelineStateDescription), "Invalid Shader stage specified in the Effect bytecode."); } + } + + result = NativeDevice.CreateGraphicsPipelineState(in nativePipelineStateDescription, out ComPtr pipelineState); + + if (result.IsFailure) + result.Throw(); + + compiledPipelineState = pipelineState; + } + + nativeRootSignature = rootSignature; + PrimitiveTopology = (D3DPrimitiveTopology) pipelineStateDescription.PrimitiveType; + HasScissorEnabled = pipelineStateDescription.RasterizerState.ScissorTestEnable; + + FreeAllTempMemoryAllocations(); + + // + // Analyzes the shader reflection data to find matching resource bindings. + // + void FindMatchingResourceBindings(DescriptorSetLayoutBuilder.Entry layoutBuilderEntry, bool isSampler, + DescriptorRangesByShaderStageMap srvDescriptorRanges, + ref int descriptorSrvOffset, + DescriptorRangesByShaderStageMap samplerDescriptorRanges, + ref int descriptorSamplerOffset) + { + foreach (var binding in effectReflection.ResourceBindings) + { + if (binding.Stage == ShaderStage.None || binding.KeyInfo.Key != layoutBuilderEntry.Key) + continue; - foreach (var stage in srvDescriptorRanges) + var descriptorRangesDictionary = isSampler ? samplerDescriptorRanges : srvDescriptorRanges; + if (descriptorRangesDictionary.TryGetValue(binding.Stage, out var descriptorRanges) == false) { - if (stage.Value.Count > 0) - { - rootSignatureParameters.Add(new RootParameter(ShaderStage2ShaderVisibility(stage.Key), stage.Value.ToArray())); - SrvBindCounts[layoutIndex]++; - } + descriptorRanges = descriptorRangesDictionary[binding.Stage] = []; } - foreach (var stage in samplerDescriptorRanges) + + if (isSampler) { - if (stage.Value.Count > 0) + if (layoutBuilderEntry.ImmutableSampler is not null) { - rootSignatureParameters.Add(new RootParameter(ShaderStage2ShaderVisibility(stage.Key), stage.Value.ToArray())); - SamplerBindCounts[layoutIndex]++; + StaticSamplerDesc samplerDesc = new() + { + // TODO: D3D12: ImmutableSampler should only be a state description instead of a GPU object? + ShaderVisibility = GetShaderVisibilityForStage(binding.Stage), + ShaderRegister = (uint) binding.SlotStart, + RegisterSpace = 0, + Filter = (Filter) layoutBuilderEntry.ImmutableSampler.Description.Filter, + ComparisonFunc = (ComparisonFunc) layoutBuilderEntry.ImmutableSampler.Description.CompareFunction, + BorderColor = ConvertToStaticBorderColor(layoutBuilderEntry.ImmutableSampler.Description.BorderColor), + AddressU = (Silk.NET.Direct3D12.TextureAddressMode) layoutBuilderEntry.ImmutableSampler.Description.AddressU, + AddressV = (Silk.NET.Direct3D12.TextureAddressMode) layoutBuilderEntry.ImmutableSampler.Description.AddressV, + AddressW = (Silk.NET.Direct3D12.TextureAddressMode) layoutBuilderEntry.ImmutableSampler.Description.AddressW, + MinLOD = layoutBuilderEntry.ImmutableSampler.Description.MinMipLevel, + MaxLOD = layoutBuilderEntry.ImmutableSampler.Description.MaxMipLevel, + MipLODBias = layoutBuilderEntry.ImmutableSampler.Description.MipMapLevelOfDetailBias, + MaxAnisotropy = (uint) layoutBuilderEntry.ImmutableSampler.Description.MaxAnisotropy + }; + + immutableSamplers.Add(samplerDesc); + } + else // Normal Sampler State + { + // Add descriptor range + var descriptorRange = new DescriptorRange + { + RangeType = DescriptorRangeType.Sampler, + NumDescriptors = (uint) layoutBuilderEntry.ArraySize, + BaseShaderRegister = (uint) binding.SlotStart, + RegisterSpace = 0, + OffsetInDescriptorsFromTableStart = (uint) descriptorSamplerOffset + }; + descriptorRanges.Add(descriptorRange); } } - } - var rootSignatureDesc = new RootSignatureDescription(RootSignatureFlags.AllowInputAssemblerInputLayout, rootSignatureParameters.ToArray(), immutableSamplers.ToArray()); + else // Other bindings: SRVs, UAVs, or CBVs + { + var descriptorRangeType = binding.Class switch + { + EffectParameterClass.ConstantBuffer => DescriptorRangeType.Cbv, + EffectParameterClass.ShaderResourceView => DescriptorRangeType.Srv, + EffectParameterClass.UnorderedAccessView => DescriptorRangeType.Uav, - var rootSignature = NativeDevice.CreateRootSignature(0, rootSignatureDesc.Serialize()); + _ => throw new NotImplementedException("Only SRVs, UAVs, and CBVs are supported.") + }; - InputElement[] inputElements = null; - if (pipelineStateDescription.InputElements != null) - { - inputElements = new InputElement[pipelineStateDescription.InputElements.Length]; - for (int i = 0; i < inputElements.Length; ++i) - { - var inputElement = pipelineStateDescription.InputElements[i]; - inputElements[i] = new InputElement + // Add descriptor range + var descriptorRange = new DescriptorRange { - Format = (SharpDX.DXGI.Format)inputElement.Format, - AlignedByteOffset = inputElement.AlignedByteOffset, - SemanticName = inputElement.SemanticName, - SemanticIndex = inputElement.SemanticIndex, - Slot = inputElement.InputSlot, - Classification = (SharpDX.Direct3D12.InputClassification)inputElement.InputSlotClass, - InstanceDataStepRate = inputElement.InstanceDataStepRate, + RangeType = descriptorRangeType, + NumDescriptors = (uint) layoutBuilderEntry.ArraySize, + BaseShaderRegister = (uint) binding.SlotStart, + RegisterSpace = 0, + OffsetInDescriptorsFromTableStart = (uint) descriptorSrvOffset }; + descriptorRanges.Add(descriptorRange); } } + } - PrimitiveTopologyType primitiveTopologyType; - switch (pipelineStateDescription.PrimitiveType) + // + // Prepares a set of root parameters of the Root Signature. + // + void PrepareDescriptorRanges(DescriptorRangesByShaderStageMap descriptionRangesToPrepare, + ref int layoutBindCount) + { + foreach ((ShaderStage stage, List descriptorRanges) in descriptionRangesToPrepare) { - case PrimitiveType.Undefined: - primitiveTopologyType = PrimitiveTopologyType.Undefined; - break; - case PrimitiveType.PointList: - primitiveTopologyType = PrimitiveTopologyType.Point; - break; - case PrimitiveType.LineList: - case PrimitiveType.LineStrip: - case PrimitiveType.LineListWithAdjacency: - case PrimitiveType.LineStripWithAdjacency: - primitiveTopologyType = PrimitiveTopologyType.Line; - break; - case PrimitiveType.TriangleList: - case PrimitiveType.TriangleStrip: - case PrimitiveType.TriangleListWithAdjacency: - case PrimitiveType.TriangleStripWithAdjacency: - primitiveTopologyType = PrimitiveTopologyType.Triangle; - break; - default: - if (pipelineStateDescription.PrimitiveType >= PrimitiveType.PatchList && pipelineStateDescription.PrimitiveType < PrimitiveType.PatchList + 32) - primitiveTopologyType = PrimitiveTopologyType.Patch; - else - throw new ArgumentOutOfRangeException("pipelineStateDescription.PrimitiveType"); - break; - } + if (descriptorRanges.Count <= 0) + continue; - // Check if it should use compute pipeline state - if (IsCompute) - { - var nativePipelineStateDescription = new ComputePipelineStateDescription + // Allocate a Descriptor Table and copy the Descriptors for this ShaderStage + var descriptorTableSize = descriptorRanges.Count * sizeof(DescriptorRange); + var descriptorTableRanges = AllocateTempMemory(descriptorTableSize); + + CopyDescriptorRanges(descriptorRanges, descriptorTableRanges, descriptorTableSize); + + // Create a Root Parameter to reference the Descriptor Table + var rootParam = new RootParameter { - ComputeShader = computeShader.Data, - Flags = PipelineStateFlags.None, - RootSignaturePointer = rootSignature, + ShaderVisibility = GetShaderVisibilityForStage(stage), + ParameterType = RootParameterType.TypeDescriptorTable, + DescriptorTable = new() + { + NumDescriptorRanges = (uint) descriptorRanges.Count, + PDescriptorRanges = (DescriptorRange*) descriptorTableRanges + } }; + rootSignatureParameters.Add(rootParam); - CompiledState = NativeDevice.CreateComputePipelineState(nativePipelineStateDescription); + // Count how many Root Parameters we need to bind for the current layout and Descriptor type (Sampler, SRVs, etc.) + layoutBindCount++; } - else + } + + // + // Copies the `DescriptorRange` entries to the specified buffer to be passed to + // the Pipeline State description structure. + // + static void CopyDescriptorRanges(List descriptorRanges, + nint destDescriptorRangesBuffer, int destDescriptorRangesBufferSize) + { + var descriptorRangesItems = CollectionsMarshal.AsSpan(descriptorRanges); + var descriptorRangesData = descriptorRangesItems.Cast(); + + var destSpan = new Span((void*) destDescriptorRangesBuffer, destDescriptorRangesBufferSize); + + descriptorRangesData.CopyTo(destSpan); + } + + // + // Prepares the description structure needed to create the Root Signature. + // + void PrepareRootSignatureDescription(List rootParameters, + List immutableSamplers, + out RootSignatureDesc desc) + { + var rootParamsBufferSize = rootSignatureParameters.Count * sizeof(RootParameter); + var rootParamsBuffer = AllocateTempMemory(rootParamsBufferSize); + + CopyRootParameters(rootSignatureParameters, rootParamsBuffer, rootParamsBufferSize); + + var staticSamplersBufferSize = rootSignatureParameters.Count * sizeof(StaticSamplerDesc); + var staticSamplersBuffer = AllocateTempMemory(staticSamplersBufferSize); + + CopyStaticSamplers(immutableSamplers, staticSamplersBuffer, staticSamplersBufferSize); + + desc = new RootSignatureDesc { - var nativePipelineStateDescription = new GraphicsPipelineStateDescription - { - InputLayout = new InputLayoutDescription(inputElements), - RootSignature = rootSignature, - RasterizerState = CreateRasterizerState(pipelineStateDescription.RasterizerState), - BlendState = CreateBlendState(pipelineStateDescription.BlendState), - SampleMask = (int)pipelineStateDescription.SampleMask, - DepthStencilFormat = (SharpDX.DXGI.Format)pipelineStateDescription.Output.DepthStencilFormat, - DepthStencilState = CreateDepthStencilState(pipelineStateDescription.DepthStencilState), - RenderTargetCount = pipelineStateDescription.Output.RenderTargetCount, - // TODO D3D12 hardcoded - StreamOutput = new StreamOutputDescription(), - PrimitiveTopologyType = primitiveTopologyType, - // TODO D3D12 hardcoded - SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0), - }; + Flags = RootSignatureFlags.AllowInputAssemblerInputLayout, + NumParameters = (uint) rootSignatureParameters.Count, + PParameters = (RootParameter*) rootParamsBuffer, + NumStaticSamplers = (uint) immutableSamplers.Count, + PStaticSamplers = (StaticSamplerDesc*) staticSamplersBuffer + }; + } - // Disable depth buffer if no format specified - if (nativePipelineStateDescription.DepthStencilFormat == Format.Unknown) - nativePipelineStateDescription.DepthStencilState.IsDepthEnabled = false; + // + // Copies the `RootParameter` entries to the specified buffer to be passed to + // the Pipeline State description structure. + // + static void CopyRootParameters(List rootParameters, + nint rootParametersBuffer, int rootParametersBufferSize) + { + var rootParametersItems = CollectionsMarshal.AsSpan(rootParameters); + var rootParametersData = rootParametersItems.Cast(); - fixed (PixelFormat* renderTargetFormats = &pipelineStateDescription.Output.RenderTargetFormat0) - { - for (int i = 0; i < pipelineStateDescription.Output.RenderTargetCount; ++i) - nativePipelineStateDescription.RenderTargetFormats[i] = (SharpDX.DXGI.Format)renderTargetFormats[i]; - } + var destSpan = new Span((void*) rootParametersBuffer, rootParametersBufferSize); - foreach (var stage in pipelineStateDescription.EffectBytecode.Stages) - { - switch (stage.Stage) - { - case ShaderStage.Vertex: - nativePipelineStateDescription.VertexShader = stage.Data; - break; - case ShaderStage.Hull: - nativePipelineStateDescription.HullShader = stage.Data; - break; - case ShaderStage.Domain: - nativePipelineStateDescription.DomainShader = stage.Data; - break; - case ShaderStage.Geometry: - nativePipelineStateDescription.GeometryShader = stage.Data; - break; - case ShaderStage.Pixel: - nativePipelineStateDescription.PixelShader = stage.Data; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } + rootParametersData.CopyTo(destSpan); + } - CompiledState = NativeDevice.CreateGraphicsPipelineState(nativePipelineStateDescription); - } + // + // Copies the `StaticSamplerDesc` entries to the specified buffer to be passed to + // the Pipeline State description structure. + // + static void CopyStaticSamplers(List staticSamplers, + nint destStaticSamplersBuffer, int destStaticSamplersBufferSize) + { + var staticSamplersItems = CollectionsMarshal.AsSpan(staticSamplers); + var staticSamplersData = staticSamplersItems.Cast(); + + var destSpan = new Span((void*) destStaticSamplersBuffer, destStaticSamplersBufferSize); - RootSignature = rootSignature; - PrimitiveTopology = (PrimitiveTopology)pipelineStateDescription.PrimitiveType; - HasScissorEnabled = pipelineStateDescription.RasterizerState.ScissorTestEnable; + staticSamplersData.CopyTo(destSpan); } - } - protected internal override void OnDestroyed() - { - RootSignature?.Dispose(); - RootSignature = null; + // + // Returns a Direct3D 12 Shader Bytecode for the corresponding Shader bytecode bytes. + // + Silk.NET.Direct3D12.ShaderBytecode GetShaderBytecode(byte[] bytes) + { + var bytecodeBuffer = AllocateTempMemory(bytes.Length); - CompiledState?.Dispose(); - CompiledState = null; + var destSpan = new Span((void*) bytecodeBuffer, bytes.Length); + bytes.AsSpan().CopyTo(destSpan); - base.OnDestroyed(); - } + return new Silk.NET.Direct3D12.ShaderBytecode + { + BytecodeLength = (nuint) bytes.Length, + PShaderBytecode = (void*) bytecodeBuffer + }; + } - private unsafe SharpDX.Direct3D12.BlendStateDescription CreateBlendState(BlendStateDescription description) - { - var nativeDescription = new SharpDX.Direct3D12.BlendStateDescription(); + // + // Gets the corresponding Direct3D 12 primitive topology type from a Stride PrimitiveType. + // + static PrimitiveTopologyType GetPrimitiveTopologyType(PrimitiveType primitiveType) + { + return primitiveType switch + { + PrimitiveType.Undefined => PrimitiveTopologyType.Undefined, + + PrimitiveType.PointList => PrimitiveTopologyType.Point, + + PrimitiveType.LineList or + PrimitiveType.LineStrip or + PrimitiveType.LineListWithAdjacency or + PrimitiveType.LineStripWithAdjacency => PrimitiveTopologyType.Line, + + PrimitiveType.TriangleList or + PrimitiveType.TriangleStrip or + PrimitiveType.TriangleListWithAdjacency or + PrimitiveType.TriangleStripWithAdjacency => PrimitiveTopologyType.Triangle, - nativeDescription.AlphaToCoverageEnable = description.AlphaToCoverageEnable; - nativeDescription.IndependentBlendEnable = description.IndependentBlendEnable; + >= PrimitiveType.PatchList and < PrimitiveType.PatchList + 32 => PrimitiveTopologyType.Patch, - var renderTargets = &description.RenderTarget0; - for (int i = 0; i < 8; ++i) + _ => throw new ArgumentOutOfRangeException(nameof(primitiveType), "Invalid PrimitiveType in PipelineStateDescription.") + }; + } + + // + // Prepares the input layout description from the input elements. + // + InputLayoutDesc PrepareInputLayout(InputElementDescription[] inputElements) { - ref var renderTarget = ref renderTargets[i]; - ref var nativeRenderTarget = ref nativeDescription.RenderTarget[i]; - nativeRenderTarget.IsBlendEnabled = renderTarget.BlendEnable; - nativeRenderTarget.SourceBlend = (BlendOption)renderTarget.ColorSourceBlend; - nativeRenderTarget.DestinationBlend = (BlendOption)renderTarget.ColorDestinationBlend; - nativeRenderTarget.BlendOperation = (BlendOperation)renderTarget.ColorBlendFunction; - nativeRenderTarget.SourceAlphaBlend = (BlendOption)renderTarget.AlphaSourceBlend; - nativeRenderTarget.DestinationAlphaBlend = (BlendOption)renderTarget.AlphaDestinationBlend; - nativeRenderTarget.AlphaBlendOperation = (BlendOperation)renderTarget.AlphaBlendFunction; - nativeRenderTarget.RenderTargetWriteMask = (ColorWriteMaskFlags)renderTarget.ColorWriteChannels; + if (inputElements is null || inputElements.Length == 0) + { + return default; + } + + var inputElementsBufferSize = inputElements.Length * sizeof(InputElementDesc); + var inputElementsBuffer = AllocateTempMemory(inputElementsBufferSize); + + var dstInputElements = (InputElementDesc*) inputElementsBuffer; + + for (int i = 0; i < inputElements.Length; ++i) + { + ref var srcInputElement = ref pipelineStateDescription.InputElements[i]; + + var pSemanticName = SilkMarshal.StringToPtr(srcInputElement.SemanticName); + tempMemoryAllocations.Add(pSemanticName); + + dstInputElements[i] = new InputElementDesc + { + Format = (Format) srcInputElement.Format, + AlignedByteOffset = (uint) srcInputElement.AlignedByteOffset, + SemanticName = (byte*) pSemanticName, + SemanticIndex = (uint) srcInputElement.SemanticIndex, + InputSlot = (uint) srcInputElement.InputSlot, + InputSlotClass = (Silk.NET.Direct3D12.InputClassification) srcInputElement.InputSlotClass, + InstanceDataStepRate = (uint) srcInputElement.InstanceDataStepRate + }; + } + + return new InputLayoutDesc + { + NumElements = (uint) inputElements.Length, + PInputElementDescs = dstInputElements + }; } - return nativeDescription; - } + // + // Creates a Direct3D 12 Blend State description from the provided description. + // + BlendDesc CreateBlendState(BlendStateDescription description) + { + var nativeDescription = new BlendDesc + { + AlphaToCoverageEnable = description.AlphaToCoverageEnable, + IndependentBlendEnable = description.IndependentBlendEnable + }; - private SharpDX.Direct3D12.RasterizerStateDescription CreateRasterizerState(RasterizerStateDescription description) - { - SharpDX.Direct3D12.RasterizerStateDescription nativeDescription; - - nativeDescription.CullMode = (SharpDX.Direct3D12.CullMode)description.CullMode; - nativeDescription.FillMode = (SharpDX.Direct3D12.FillMode)description.FillMode; - nativeDescription.IsFrontCounterClockwise = description.FrontFaceCounterClockwise; - nativeDescription.DepthBias = description.DepthBias; - nativeDescription.SlopeScaledDepthBias = description.SlopeScaleDepthBias; - nativeDescription.DepthBiasClamp = description.DepthBiasClamp; - nativeDescription.IsDepthClipEnabled = description.DepthClipEnable; - //nativeDescription.IsScissorEnabled = description.ScissorTestEnable; - nativeDescription.IsMultisampleEnabled = description.MultisampleCount >= MultisampleCount.None; - nativeDescription.IsAntialiasedLineEnabled = description.MultisampleAntiAliasLine; - - nativeDescription.ConservativeRaster = ConservativeRasterizationMode.Off; - nativeDescription.ForcedSampleCount = 0; - - return nativeDescription; - } + var renderTargets = description.RenderTargets; + for (int i = 0; i < 8; ++i) + { + ref var renderTarget = ref renderTargets[i]; + ref var nativeRenderTarget = ref nativeDescription.RenderTarget[i]; + + nativeRenderTarget.BlendEnable = renderTarget.BlendEnable; + nativeRenderTarget.SrcBlend = (Silk.NET.Direct3D12.Blend)renderTarget.ColorSourceBlend; + nativeRenderTarget.DestBlend = (Silk.NET.Direct3D12.Blend)renderTarget.ColorDestinationBlend; + nativeRenderTarget.BlendOp = (BlendOp)renderTarget.ColorBlendFunction; + nativeRenderTarget.SrcBlendAlpha = (Silk.NET.Direct3D12.Blend)renderTarget.AlphaSourceBlend; + nativeRenderTarget.DestBlendAlpha = (Silk.NET.Direct3D12.Blend)renderTarget.AlphaDestinationBlend; + nativeRenderTarget.BlendOpAlpha = (BlendOp)renderTarget.AlphaBlendFunction; + nativeRenderTarget.RenderTargetWriteMask = (byte)renderTarget.ColorWriteChannels; + } - private SharpDX.Direct3D12.DepthStencilStateDescription CreateDepthStencilState(DepthStencilStateDescription description) - { - SharpDX.Direct3D12.DepthStencilStateDescription nativeDescription; + return nativeDescription; + } + + // + // Creates a Direct3D 12 Rasterizer State description from the provided description. + // + RasterizerDesc CreateRasterizerState(RasterizerStateDescription description) + { + RasterizerDesc nativeDescription = new() + { + CullMode = (Silk.NET.Direct3D12.CullMode) description.CullMode, + FillMode = (Silk.NET.Direct3D12.FillMode) description.FillMode, + FrontCounterClockwise = description.FrontFaceCounterClockwise, + DepthBias = description.DepthBias, + SlopeScaledDepthBias = description.SlopeScaleDepthBias, + DepthBiasClamp = description.DepthBiasClamp, + DepthClipEnable = description.DepthClipEnable, + MultisampleEnable = description.MultisampleCount >= MultisampleCount.None, + AntialiasedLineEnable = description.MultisampleAntiAliasLine, + + ConservativeRaster = ConservativeRasterizationMode.Off, + ForcedSampleCount = 0 + }; + + return nativeDescription; + } - nativeDescription.IsDepthEnabled = description.DepthBufferEnable; - nativeDescription.DepthComparison = (Comparison)description.DepthBufferFunction; - nativeDescription.DepthWriteMask = description.DepthBufferWriteEnable ? SharpDX.Direct3D12.DepthWriteMask.All : SharpDX.Direct3D12.DepthWriteMask.Zero; + // + // Creates a Direct3D 12 Depth-Stencil State from the provided description. + // + DepthStencilDesc CreateDepthStencilState(DepthStencilStateDescription description) + { + DepthStencilDesc nativeDescription = new() + { + DepthEnable = description.DepthBufferEnable, + DepthFunc = (ComparisonFunc) description.DepthBufferFunction, + DepthWriteMask = description.DepthBufferWriteEnable ? DepthWriteMask.All : DepthWriteMask.Zero, - nativeDescription.IsStencilEnabled = description.StencilEnable; - nativeDescription.StencilReadMask = description.StencilMask; - nativeDescription.StencilWriteMask = description.StencilWriteMask; + StencilEnable = description.StencilEnable, + StencilReadMask = description.StencilMask, + StencilWriteMask = description.StencilWriteMask, - nativeDescription.FrontFace.FailOperation = (SharpDX.Direct3D12.StencilOperation)description.FrontFace.StencilFail; - nativeDescription.FrontFace.PassOperation = (SharpDX.Direct3D12.StencilOperation)description.FrontFace.StencilPass; - nativeDescription.FrontFace.DepthFailOperation = (SharpDX.Direct3D12.StencilOperation)description.FrontFace.StencilDepthBufferFail; - nativeDescription.FrontFace.Comparison = (SharpDX.Direct3D12.Comparison)description.FrontFace.StencilFunction; + FrontFace = + { + StencilFailOp = (StencilOp) description.FrontFace.StencilFail, + StencilPassOp = (StencilOp) description.FrontFace.StencilPass, + StencilDepthFailOp = (StencilOp) description.FrontFace.StencilDepthBufferFail, + StencilFunc = (ComparisonFunc) description.FrontFace.StencilFunction + }, + BackFace = + { + StencilFailOp = (StencilOp) description.BackFace.StencilFail, + StencilPassOp = (StencilOp) description.BackFace.StencilPass, + StencilDepthFailOp = (StencilOp) description.BackFace.StencilDepthBufferFail, + StencilFunc = (ComparisonFunc) description.BackFace.StencilFunction + } + }; - nativeDescription.BackFace.FailOperation = (SharpDX.Direct3D12.StencilOperation)description.BackFace.StencilFail; - nativeDescription.BackFace.PassOperation = (SharpDX.Direct3D12.StencilOperation)description.BackFace.StencilPass; - nativeDescription.BackFace.DepthFailOperation = (SharpDX.Direct3D12.StencilOperation)description.BackFace.StencilDepthBufferFail; - nativeDescription.BackFace.Comparison = (SharpDX.Direct3D12.Comparison)description.BackFace.StencilFunction; + return nativeDescription; + } - return nativeDescription; - } + // + // Allocates a temporary block of memory of the specified size that must be freed by the + // end of the constructor calling . + // + nint AllocateTempMemory(int byteCount) + { + var allocatedBuffer = SilkMarshal.Allocate(byteCount); + tempMemoryAllocations.Add(allocatedBuffer); - private static ShaderVisibility ShaderStage2ShaderVisibility(ShaderStage stage) - { - switch (stage) + return allocatedBuffer; + } + + // + // Frees all the temporary memory blocks allocated during the creation and configuration of the + // pipeline state object. + // + void FreeAllTempMemoryAllocations() + { + foreach (var bufferPtr in tempMemoryAllocations) + SilkMarshal.Free(bufferPtr); + + tempMemoryAllocations.Clear(); + } + + // + // Converts a Color4 to its corresponding StaticBorderColor for a Sampler's border color. + // + static StaticBorderColor ConvertToStaticBorderColor(Color4 color) + { + if (color == Color4.Black) + { + return StaticBorderColor.OpaqueBlack; + } + else if (color == Color4.White) + { + return StaticBorderColor.OpaqueWhite; + } + else if (color == Color4.TransparentBlack) + { + return StaticBorderColor.TransparentBlack; + } + + throw new NotSupportedException("Static Samplers can only have opaque black, opaque white or transparent black as border color."); + } + + // + // Returns the Direct3D 12 Shader Visibility for a Stride's ShaderStage. + // + static ShaderVisibility GetShaderVisibilityForStage(ShaderStage stage) { - case ShaderStage.Vertex: return ShaderVisibility.Vertex; - case ShaderStage.Hull: return ShaderVisibility.Hull; - case ShaderStage.Domain: return ShaderVisibility.Domain; - case ShaderStage.Geometry: return ShaderVisibility.Geometry; - case ShaderStage.Pixel: return ShaderVisibility.Pixel; - case ShaderStage.Compute: return ShaderVisibility.All; - default: - throw new ArgumentOutOfRangeException(nameof(stage), stage, null); + return stage switch + { + ShaderStage.Vertex => ShaderVisibility.Vertex, + ShaderStage.Hull => ShaderVisibility.Hull, + ShaderStage.Domain => ShaderVisibility.Domain, + ShaderStage.Geometry => ShaderVisibility.Geometry, + ShaderStage.Pixel => ShaderVisibility.Pixel, + ShaderStage.Compute => ShaderVisibility.All, + + _ => throw new ArgumentOutOfRangeException(nameof(stage), stage, "Invalid ShaderStage.") + }; } } + + /// + protected internal override void OnDestroyed() + { + SafeRelease(ref nativeRootSignature); + SafeRelease(ref compiledPipelineState); + + base.OnDestroyed(); + } } } diff --git a/sources/engine/Stride.Graphics/Direct3D12/QueryPool.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/QueryPool.Direct3D12.cs index bf8670d5ad..4efbdf0128 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/QueryPool.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/QueryPool.Direct3D12.cs @@ -1,67 +1,166 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 + using System; using System.Runtime.CompilerServices; -using SharpDX.Direct3D12; + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D12; + +using Stride.Core.UnsafeExtensions; + +using static Stride.Graphics.ComPtrHelpers; namespace Stride.Graphics { - public partial class QueryPool + public unsafe partial class QueryPool { - private Resource readbackBuffer; - private Fence readbackFence; + private ID3D12Resource* readbackBuffer; + private ID3D12Fence* readbackFence; - internal long CompletedValue; - internal long PendingValue; - internal QueryHeap NativeQueryHeap; + private ID3D12QueryHeap* nativeQueryHeap; + /// + /// Gets the internal Direct3D 12 Query Heap. + /// + /// + /// If any of the references is going to be kept, use to increment the internal + /// reference count, and when no longer needed to release the object. + /// + internal ComPtr NativeQueryHeap => ToComPtr(nativeQueryHeap); + + internal ulong CompletedValue; + internal ulong PendingValue; + + + /// + /// Attempts to retrieve data from the in-flight GPU queries. + /// + /// + /// An array of values to be populated with the retrieved data. The array must have a length + /// equal to the number of queries performed (). + /// + /// if all data queries succeed; otherwise, . + /// + /// This method tries to perform reads for the multiple GPU queries in the pool and populates the provided array + /// with the results. If any query fails, the method returns and the array may contain + /// partial or uninitialized data. + /// public unsafe bool TryGetData(long[] dataArray) { + HResult result; + // If readback has completed, return the data from the staging buffer - if (readbackFence.CompletedValue == PendingValue) + if (readbackFence->GetCompletedValue() == PendingValue) { CompletedValue = PendingValue; - var mappedData = readbackBuffer.Map(0); - fixed (long* dataPointer = &dataArray[0]) - { - Unsafe.CopyBlockUnaligned(dataPointer, (void*) mappedData, (uint) QueryCount * 8); - } - readbackBuffer.Unmap(subresource: 0); + Silk.NET.Direct3D12.Range range = default; + void* mappedData = null; + + result = readbackBuffer->Map(Subresource: 0, in range, ref mappedData); + + if (result.IsFailure) + result.Throw(); + + ref var destData = ref dataArray.AsSpan().Cast()[0]; + ref var srcData = ref Unsafe.AsRef(mappedData); + + Unsafe.CopyBlockUnaligned(ref destData, ref srcData, byteCount: (uint) QueryCount * sizeof(long)); + + readbackBuffer->Unmap(Subresource: 0, pWrittenRange: in range); return true; } // Otherwise, queue readback var commandList = GraphicsDevice.NativeCopyCommandList; - commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, initialStateRef: null); - commandList.ResolveQueryData(NativeQueryHeap, SharpDX.Direct3D12.QueryType.Timestamp, startIndex: 0, QueryCount, readbackBuffer, alignedDestinationBufferOffset: 0); - commandList.Close(); + ref var nullPipelineState = ref Unsafe.NullRef(); + result = commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, pInitialState: ref nullPipelineState); + + if (result.IsFailure) + result.Throw(); + + commandList.ResolveQueryData(nativeQueryHeap, Silk.NET.Direct3D12.QueryType.Timestamp, + StartIndex: 0, (uint) QueryCount, readbackBuffer, AlignedDestinationBufferOffset: 0); + + result = commandList.Close(); - GraphicsDevice.NativeCommandQueue.ExecuteCommandList(GraphicsDevice.NativeCopyCommandList); - GraphicsDevice.NativeCommandQueue.Signal(readbackFence, PendingValue); + if (result.IsFailure) + result.Throw(); + + var copyCommandList = commandList.AsComPtr(); + var commandQueue = GraphicsDevice.NativeCommandQueue; + commandQueue.ExecuteCommandLists(NumCommandLists: 1, ref copyCommandList); + + result = commandQueue.Signal(readbackFence, PendingValue); + + if (result.IsFailure) + result.Throw(); return false; } - private void Recreate() + /// + /// Implementation in Direct3D 12 that recreates the queries in the pool. + /// + /// + /// Only GPU queries of type are supported. + /// + private unsafe partial void Recreate() { - var description = new QueryHeapDescription { Count = QueryCount }; + var description = new QueryHeapDesc + { + Count = (uint) QueryCount, + Type = QueryType switch + { + QueryType.Timestamp => QueryHeapType.Timestamp, + + _ => throw new NotImplementedException($"Query type {QueryType} not supported") + } + }; + + HResult result = NativeDevice.CreateQueryHeap(in description, out ComPtr queryHeap); + + if (result.IsFailure) + result.Throw(); - switch (QueryType) + nativeQueryHeap = queryHeap; + + var readbackBufferDesc = new ResourceDesc { - case QueryType.Timestamp: - description.Type = QueryHeapType.Timestamp; - break; + Dimension = ResourceDimension.Buffer, + Width = (uint) QueryCount * sizeof(long), + Height = 1, + DepthOrArraySize = 1, + MipLevels = 1, + Alignment = 0, - default: - throw new NotImplementedException(); - } + SampleDesc = { Count = 1, Quality = 0 }, + + Format = Format.FormatUnknown, + Layout = TextureLayout.LayoutRowMajor, + Flags = ResourceFlags.None + }; + + var heap = new HeapProperties { Type = HeapType.Readback }; + result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in readbackBufferDesc, ResourceStates.CopyDest, + pOptimizedClearValue: null, out ComPtr resource); + if (result.IsFailure) + result.Throw(); + + readbackBuffer = resource; + + result = NativeDevice.CreateFence(InitialValue: 0, FenceFlags.None, out ComPtr fence); + + if (result.IsFailure) + result.Throw(); + + readbackFence = fence; - NativeQueryHeap = NativeDevice.CreateQueryHeap(description); - readbackBuffer = NativeDevice.CreateCommittedResource(new HeapProperties(HeapType.Readback), HeapFlags.None, ResourceDescription.Buffer(QueryCount * 8), ResourceStates.CopyDestination); - readbackFence = NativeDevice.CreateFence(0, FenceFlags.None); CompletedValue = 0; PendingValue = 0; } @@ -69,18 +168,22 @@ private void Recreate() /// protected internal override void OnDestroyed() { - NativeQueryHeap.Dispose(); - readbackBuffer.Dispose(); - readbackFence.Dispose(); + SafeRelease(ref nativeQueryHeap); + SafeRelease(ref readbackBuffer); + SafeRelease(ref readbackFence); base.OnDestroyed(); } + /// + /// Resets the internal state to ensure the completed value is ahead of the current readback fence value. + /// internal void ResetInternal() { - if (CompletedValue <= readbackFence.CompletedValue) - CompletedValue = readbackFence.CompletedValue + 1; + if (CompletedValue <= readbackFence->GetCompletedValue()) + CompletedValue = readbackFence->GetCompletedValue() + 1; } } } + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/SamplerState.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/SamplerState.Direct3D12.cs index f1fddf17fb..54bbfcd4d6 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/SamplerState.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/SamplerState.Direct3D12.cs @@ -1,27 +1,36 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D12 -using System; -using SharpDX; -using SharpDX.Direct3D12; + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +using Silk.NET.Direct3D12; + using Stride.Core.Mathematics; namespace Stride.Graphics { - /// - /// Describes a sampler state used for texture sampling. - /// - public partial class SamplerState + public unsafe partial class SamplerState { + /// + /// Gets the internal Direct3D 12 CPU-accessible handle to the Sampler State object. + /// internal CpuDescriptorHandle NativeSampler; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The device. - /// The name. - /// The sampler state description. - private SamplerState(GraphicsDevice device, SamplerStateDescription samplerStateDescription) : base(device) + /// The Graphics Device. + /// + /// A structure describing the Sampler State + /// object to create. + /// + /// An optional name that can be used to identify the Sampler State. + private SamplerState(GraphicsDevice device, ref readonly SamplerStateDescription samplerStateDescription, string? name = null) + : base(device, name) { Description = samplerStateDescription; @@ -32,28 +41,32 @@ private SamplerState(GraphicsDevice device, SamplerStateDescription samplerState protected internal override bool OnRecreate() { base.OnRecreate(); + CreateNativeDeviceChild(); return true; } private void CreateNativeDeviceChild() { - SharpDX.Direct3D12.SamplerStateDescription nativeDescription; - - nativeDescription.AddressU = (SharpDX.Direct3D12.TextureAddressMode)Description.AddressU; - nativeDescription.AddressV = (SharpDX.Direct3D12.TextureAddressMode)Description.AddressV; - nativeDescription.AddressW = (SharpDX.Direct3D12.TextureAddressMode)Description.AddressW; - nativeDescription.BorderColor = ColorHelper.Convert(Description.BorderColor); - nativeDescription.ComparisonFunction = (SharpDX.Direct3D12.Comparison)Description.CompareFunction; - nativeDescription.Filter = (SharpDX.Direct3D12.Filter)Description.Filter; - nativeDescription.MaximumAnisotropy = Description.MaxAnisotropy; - nativeDescription.MaximumLod = Description.MaxMipLevel; - nativeDescription.MinimumLod = Description.MinMipLevel; - nativeDescription.MipLodBias = Description.MipMapLevelOfDetailBias; - - NativeSampler = GraphicsDevice.SamplerAllocator.Allocate(1); - GraphicsDevice.NativeDevice.CreateSampler(nativeDescription, NativeSampler); + var nativeDescription = new SamplerDesc + { + AddressU = (Silk.NET.Direct3D12.TextureAddressMode) Description.AddressU, + AddressV = (Silk.NET.Direct3D12.TextureAddressMode) Description.AddressV, + AddressW = (Silk.NET.Direct3D12.TextureAddressMode) Description.AddressW, + ComparisonFunc = (ComparisonFunc) Description.CompareFunction, + Filter = (Filter) Description.Filter, + MaxAnisotropy = (uint) Description.MaxAnisotropy, + MaxLOD = Description.MaxMipLevel, + MinLOD = Description.MinMipLevel, + MipLODBias = Description.MipMapLevelOfDetailBias + }; + Debug.Assert(sizeof(Color4) == (4 * sizeof(float))); + Unsafe.AsRef(nativeDescription.BorderColor) = Description.BorderColor; + + NativeSampler = GraphicsDevice.SamplerAllocator.Allocate(); + NativeDevice.CreateSampler(in nativeDescription, NativeSampler); } } -} +} + #endif diff --git a/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs b/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs index 79126f8f30..ee561a3df1 100644 --- a/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs +++ b/sources/engine/Stride.Graphics/Direct3D12/Texture.Direct3D12.cs @@ -2,6 +2,7 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_DIRECT3D12 + // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -21,41 +22,81 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using SharpDX.Direct3D12; -using SharpDX.Mathematics.Interop; -using Stride.Core; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D12; + +using Format = Silk.NET.DXGI.Format; + +using Stride.Core.Mathematics; + +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Graphics.ComPtrHelpers; namespace Stride.Graphics { - public partial class Texture + public unsafe partial class Texture { + private const int TextureRowPitchAlignment = D3D12.TextureDataPitchAlignment; + private const int TextureSubresourceAlignment = D3D12.TextureDataPlacementAlignment; + + private int TexturePixelSize => Format.SizeInBytes(); + + /// + /// A handle to the CPU-accessible Render Target View (RTV) Descriptor. + /// internal CpuDescriptorHandle NativeRenderTargetView; + /// + /// A handle to the CPU-accessible Depth-Stencil View (DSV) Descriptor. + /// internal CpuDescriptorHandle NativeDepthStencilView; + + /// + /// A value indicating whether the Texture is a Depth-Stencil Buffer with a stencil component. + /// public bool HasStencil; - private int TexturePixelSize => Format.SizeInBytes(); - // D3D12_TEXTURE_DATA_PITCH_ALIGNMENT (not exposed by SharpDX) - private const int TextureRowPitchAlignment = 256; - // D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT (not exposed by SharpDX) - private const int TextureSubresourceAlignment = 512; + /// + /// Recreates this Texture explicitly with the provided data. Usually called after the has been reset. + /// + /// + /// An array of structures that contain the initial data for the Texture's + /// sub-resources (the mip-levels in the mipmap chain). + /// Specify if no initial data is needed. + /// + /// + /// The Texture is initialized with and staging Textures + /// cannot be created with initial data ( must be or empty). + /// public void Recreate(DataBox[] dataBoxes = null) { InitializeFromImpl(dataBoxes); } + /// + /// Determines if a Graphics Device supports read-only Depth-Stencil Buffers. + /// + /// The Graphics Device to check. + /// + /// if the Graphics Device supports read-only Depth-Stencil Buffers; + /// otherwise. + /// public static bool IsDepthStencilReadOnlySupported(GraphicsDevice device) { return device.Features.CurrentProfile >= GraphicsProfile.Level_11_0; } - internal void SwapInternal(Texture other) + /// + /// Swaps the Texture's internal data with another Texture. + /// + /// The other Texture. + internal partial void SwapInternal(Texture other) { - (other.NativeDeviceChild, NativeDeviceChild) = (NativeDeviceChild, other.NativeDeviceChild); + (NativeDeviceChild, other.NativeDeviceChild) = (other.NativeDeviceChild, NativeDeviceChild); + (other.NativeShaderResourceView, NativeShaderResourceView) = (NativeShaderResourceView, other.NativeShaderResourceView); (other.NativeUnorderedAccessView, NativeUnorderedAccessView) = (NativeUnorderedAccessView, other.NativeUnorderedAccessView); @@ -68,132 +109,203 @@ internal void SwapInternal(Texture other) } /// - /// Initializes from a native SharpDX.Texture + /// Initializes the from a native . /// - /// The texture. - internal Texture InitializeFromImpl(Resource texture, bool isSrgb) + /// The underlying native Texture. + /// + /// to treat the Texture's pixel format as if it were an sRGB format, even if it was created as non-sRGB; + /// to respect the Texture's original pixel format. + /// + /// This Texture after being initialized. + internal Texture InitializeFromImpl(ID3D12Resource* texture, bool treatAsSrgb) { - NativeDeviceChild = texture; - var newTextureDescription = ConvertFromNativeDescription(texture.Description); + var ptrTexture = ToComPtr(texture); + NativeDeviceChild = ptrTexture.AsDeviceChild(); + + var newTextureDescription = ConvertFromNativeDescription(ptrTexture.GetDesc()); - // We might have created the swapchain as a non-srgb format (esp on Win10&RT) but we want it to behave like it is (esp. for the view and render target) - if (isSrgb) + // We might have created the swapchain as a non-sRGB format (specially on Win 10 & RT) but we want it to + // behave like it is (specially for the View and Render Target) + if (treatAsSrgb) newTextureDescription.Format = newTextureDescription.Format.ToSRgb(); return InitializeFrom(newTextureDescription); + + + // + // Converts a Silk.NET's ResourceDesc structure to a TextureDescription. + // + static TextureDescription ConvertFromNativeDescription(ResourceDesc description, bool isShaderResource = false) + { + var desc = new TextureDescription() + { + Dimension = TextureDimension.Texture2D, + Width = (int) description.Width, + Height = (int) description.Height, + Depth = 1, + MultisampleCount = (MultisampleCount) description.SampleDesc.Count, + Format = (PixelFormat) description.Format, + MipLevelCount = description.MipLevels, + Usage = GraphicsResourceUsage.Default, + ArraySize = description.DepthOrArraySize, + Flags = TextureFlags.None + }; + + if (description.Flags.HasFlag(ResourceFlags.AllowRenderTarget)) + desc.Flags |= TextureFlags.RenderTarget; + if (description.Flags.HasFlag(ResourceFlags.AllowUnorderedAccess)) + desc.Flags |= TextureFlags.UnorderedAccess; + if (description.Flags.HasFlag(ResourceFlags.AllowDepthStencil)) + desc.Flags |= TextureFlags.DepthStencil; + + if (!description.Flags.HasFlag(ResourceFlags.DenyShaderResource) && isShaderResource) + desc.Flags |= TextureFlags.ShaderResource; + + return desc; + } } - private unsafe void InitializeFromImpl(DataBox[] dataBoxes = null) + /// + /// Initializes the Texture from the specified data. + /// + /// + /// An array of structures pointing to the data for all the subresources to + /// initialize for the Texture. + /// + /// Invalid Texture share options () specified. + /// Invalid Texture dimension () specified. + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + /// is not supported for Render Targets. + /// Multi-sampling is not supported for Unordered Access Views. + /// The Depth-Stencil format specified is not supported. + /// Cannot create a read-only Depth-Stencil View because the device does not support it. + /// + /// For a lower than , creating Shader Resource Views + /// for Depth-Stencil Textures is not supported, + /// + /// + /// The Texture is initialized with and staging Textures + /// cannot be created with initial data ( must be or empty). + /// + private partial void InitializeFromImpl(DataBox[] dataBoxes) { - bool hasInitData = dataBoxes != null && dataBoxes.Length > 0; - - if (ParentTexture != null) + // If this is a view, get the underlying resource to copy data to + if (ParentTexture is not null) { ParentResource = ParentTexture; NativeDeviceChild = ParentTexture.NativeDeviceChild; } - if (NativeDeviceChild == null) + // If no underlying resource, we must create it and copy the init data to it if needed + if (NativeDeviceChild.IsNull()) { - ClearValue? clearValue = GetClearValue(); - - ResourceDescription nativeDescription; - switch (Dimension) + if (Usage == GraphicsResourceUsage.Staging) { - case TextureDimension.Texture1D: - nativeDescription = ConvertToNativeDescription1D(); - break; - case TextureDimension.Texture2D: - case TextureDimension.TextureCube: - nativeDescription = ConvertToNativeDescription2D(); - break; - case TextureDimension.Texture3D: - nativeDescription = ConvertToNativeDescription3D(); - break; - default: - throw new ArgumentOutOfRangeException(); + // Per our own definition of staging resource (read-back only) + if (dataBoxes?.Length > 0) + throw new NotSupportedException("D3D12: Staging textures can't be created with initial data."); + + // If it is a staging Texture, initialize it and finish + // (staging resources do not need to have views, they are only intermediate buffers to copy + // to a final destination resource) + InitializeStagingTexture(); + return; } - var initialResourceState = ResourceStates.GenericRead; + // Initialize the Texture as a regular Texture resource + InitializeTexture(dataBoxes); + } - var heapType = HeapType.Default; - var currentResourceState = initialResourceState; - if (Usage == GraphicsResourceUsage.Staging) - { - heapType = HeapType.Readback; - NativeResourceState = ResourceStates.CopyDestination; - int totalSize = ComputeBufferTotalSize(); - nativeDescription = ResourceDescription.Buffer(totalSize); + // Initialize the views on the resource + NativeShaderResourceView = GetShaderResourceView(ViewType, ArraySlice, MipLevel); + NativeRenderTargetView = GetRenderTargetView(ViewType, ArraySlice, MipLevel); + NativeDepthStencilView = GetDepthStencilView(out HasStencil); + NativeUnorderedAccessView = GetUnorderedAccessView(ViewType, ArraySlice, MipLevel); - // Staging textures on DirectX 12 use buffer internally - NativeDeviceChild = GraphicsDevice.NativeDevice.CreateCommittedResource(new HeapProperties(heapType), HeapFlags.None, nativeDescription, NativeResourceState); - if (hasInitData) - { - var commandList = GraphicsDevice.NativeCopyCommandList; - commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, initialStateRef: null); + // + // Initializes the Texture as a staging texture, which in Direct3D 12 is essentially a Buffer, + // and copies the initialization data to that Buffer. + // + void InitializeStagingTexture() + { + NativeResourceState = ResourceStates.CopyDest; - var uploadMemory = GraphicsDevice.AllocateUploadBuffer(totalSize, out var uploadResource, out var uploadOffset, TextureSubresourceAlignment); + int totalSize = ComputeBufferTotalSize(); + ResourceDesc nativeDescription = CreateDescriptionForBuffer((ulong) totalSize); - // Copy data to the upload buffer - int dataBoxIndex = 0; - var uploadMemoryMipStart = uploadMemory; - for (int arraySlice = 0; arraySlice < ArraySize; arraySlice++) - { - for (int mipLevel = 0; mipLevel < MipLevels; mipLevel++) - { - var databox = dataBoxes[dataBoxIndex++]; - var mipHeight = CalculateMipSize(Width, mipLevel); - var mipRowPitch = ComputeRowPitch(mipLevel); - - var uploadMemoryCurrent = uploadMemoryMipStart; - var dataPointerCurrent = databox.DataPointer; - for (int rowIndex = 0; rowIndex < mipHeight; rowIndex++) - { - Unsafe.CopyBlockUnaligned((void*) uploadMemoryCurrent, (void*) dataPointerCurrent, (uint) mipRowPitch); - uploadMemoryCurrent += mipRowPitch; - dataPointerCurrent += databox.RowPitch; - } - - uploadMemoryMipStart += ComputeSubresourceSize(mipLevel); - } - } + HeapProperties heap = new HeapProperties { Type = HeapType.Readback }; - // Copy from upload heap to actual resource - commandList.CopyBufferRegion(NativeResource, dstOffset: 0, uploadResource, uploadOffset, totalSize); + HResult result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in nativeDescription, NativeResourceState, pOptimizedClearValue: null, + out ComPtr stagingTextureResource); + if (result.IsFailure) + result.Throw(); - commandList.Close(); + NativeDeviceChild = stagingTextureResource.AsDeviceChild(); + } - StagingFenceValue = 0; - GraphicsDevice.WaitCopyQueue(); - } + // + // Initializes the Texture as a regular texture resource and copies the initial data + // to that new texture. + // + void InitializeTexture(DataBox[] initialData) + { + bool hasClearValue = GetClearValue(out ClearValue clearValue); + scoped ref readonly var pClearValue = ref hasClearValue + ? ref clearValue + : ref NullRef(); - return; - } + var nativeDescription = GetTextureDescription(Dimension); + var initialResourceState = ResourceStates.GenericRead; + var currentResourceState = initialResourceState; + + bool hasInitData = initialData?.Length > 0; + + // If the resource must be initialized with data, it is initially in the state + // CopyDest so we can copy from an upload buffer if (hasInitData) - currentResourceState = ResourceStates.CopyDestination; + currentResourceState = ResourceStates.CopyDest; // TODO D3D12 move that to a global allocator in bigger committed resources - NativeDeviceChild = GraphicsDevice.NativeDevice.CreateCommittedResource(new HeapProperties(heapType), HeapFlags.None, nativeDescription, currentResourceState, clearValue); + var heap = new HeapProperties { Type = HeapType.Default }; + + HResult result = NativeDevice.CreateCommittedResource(in heap, HeapFlags.None, in nativeDescription, currentResourceState, + in pClearValue, out ComPtr textureResource); + if (result.IsFailure) + result.Throw(); + + NativeDeviceChild = textureResource.AsDeviceChild(); GraphicsDevice.RegisterTextureMemoryUsage(SizeInBytes); if (hasInitData) { - // Trigger copy var commandList = GraphicsDevice.NativeCopyCommandList; - commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, initialStateRef: null); + scoped ref var nullPipelineState = ref NullRef(); + result = commandList.Reset(GraphicsDevice.NativeCopyCommandAllocator, pInitialState: ref nullPipelineState); - var placedSubresources = new PlacedSubResourceFootprint[dataBoxes.Length]; - var rowCounts = new int[dataBoxes.Length]; - var rowSizeInBytes = new long[dataBoxes.Length]; - GraphicsDevice.NativeDevice.GetCopyableFootprints(ref nativeDescription, 0, dataBoxes.Length, 0, placedSubresources, rowCounts, rowSizeInBytes, out var textureCopySize); + if (result.IsFailure) + result.Throw(); - var uploadMemory = GraphicsDevice.AllocateUploadBuffer((int)textureCopySize, out var uploadResource, out var uploadOffset, TextureSubresourceAlignment); + var subresourceCount = initialData.Length; + var placedSubresources = stackalloc PlacedSubresourceFootprint[subresourceCount]; + var rowCounts = stackalloc uint[subresourceCount]; + var rowSizeInBytes = stackalloc ulong[subresourceCount]; - for (int i = 0; i < dataBoxes.Length; ++i) + ulong textureCopySize = 0; + + NativeDevice.GetCopyableFootprints(in nativeDescription, FirstSubresource: 0, (uint) subresourceCount, + BaseOffset: 0, ref placedSubresources[0], ref rowCounts[0], ref rowSizeInBytes[0], + ref textureCopySize); + + nint uploadMemory = GraphicsDevice.AllocateUploadBuffer((int) textureCopySize, out var uploadResource, out var uploadOffset, + D3D12.TextureDataPlacementAlignment); + + for (int i = 0; i < subresourceCount; ++i) { - var databox = dataBoxes[i]; + var databox = initialData[i]; var dataPointer = databox.DataPointer; var rowCount = rowCounts[i]; @@ -201,27 +313,43 @@ private unsafe void InitializeFromImpl(DataBox[] dataBoxes = null) var rowSize = (int) rowSizeInBytes[i]; var destRowPitch = placedSubresources[i].Footprint.RowPitch; - // Memcpy data + // Copy the init data to the upload buffer for (int z = 0; z < sliceCount; ++z) { - var uploadMemoryCurrent = uploadMemory + (int)placedSubresources[i].Offset + z * destRowPitch * rowCount; + var uploadMemoryCurrent = uploadMemory + (int) placedSubresources[i].Offset + z * destRowPitch * rowCount; var dataPointerCurrent = dataPointer + z * databox.SlicePitch; for (int y = 0; y < rowCount; ++y) { - Unsafe.CopyBlockUnaligned((void*) uploadMemoryCurrent, (void*) dataPointerCurrent, (uint) rowSize); + CopyBlockUnaligned((void*) uploadMemoryCurrent, (void*) dataPointerCurrent, (uint) rowSize); uploadMemoryCurrent += destRowPitch; dataPointerCurrent += databox.RowPitch; } } // Adjust upload offset (circular dependency between GetCopyableFootprints and AllocateUploadBuffer) - placedSubresources[i].Offset += uploadOffset; + placedSubresources[i].Offset += (ulong) uploadOffset; - commandList.CopyTextureRegion(new TextureCopyLocation(NativeResource, i), 0, 0, 0, new TextureCopyLocation(uploadResource, placedSubresources[i]), null); + var dest = new TextureCopyLocation { Type = TextureCopyType.SubresourceIndex, PResource = NativeResource, SubresourceIndex = (uint) i }; + var src = new TextureCopyLocation { Type = TextureCopyType.PlacedFootprint, PResource = uploadResource, PlacedFootprint = placedSubresources[i] }; + + commandList.CopyTextureRegion(in dest, DstX: 0, DstY: 0, DstZ: 0, in src, pSrcBox: in NullRef()); } - commandList.ResourceBarrierTransition(NativeResource, ResourceStates.CopyDestination, initialResourceState); - commandList.Close(); + const uint D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES = 0xFFFFFFFF; + + // Once initialized, transition the Texture (and its subresources) to its final state + var resourceBarrier = new ResourceBarrier { Type = ResourceBarrierType.Transition }; + resourceBarrier.Transition.PResource = NativeResource; + resourceBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + resourceBarrier.Transition.StateBefore = ResourceStates.CopyDest; + resourceBarrier.Transition.StateAfter = initialResourceState; + + commandList.ResourceBarrier(1, in resourceBarrier); + + result = commandList.Close(); + + if (result.IsFailure) + result.Throw(); GraphicsDevice.WaitCopyQueue(); } @@ -229,604 +357,705 @@ private unsafe void InitializeFromImpl(DataBox[] dataBoxes = null) NativeResourceState = initialResourceState; } - NativeShaderResourceView = GetShaderResourceView(ViewType, ArraySlice, MipLevel); - NativeRenderTargetView = GetRenderTargetView(ViewType, ArraySlice, MipLevel); - NativeDepthStencilView = GetDepthStencilView(out HasStencil); - NativeUnorderedAccessView = GetUnorderedAccessView(ViewType, ArraySlice, MipLevel); - } - - protected internal override void OnDestroyed() - { - // If it was a View, do not release reference - if (ParentTexture != null) + // + // Gets the most appropriate clear value for the resource, or no clear value if the + // resource does not need one. + // + bool GetClearValue(out ClearValue clearValue) { - NativeDeviceChild = null; + if (IsDepthStencil) + { + clearValue = new ClearValue + { + Format = ComputeDepthViewFormatFromTextureFormat(ViewFormat), + DepthStencil = new DepthStencilValue + { + Depth = 1.0f, + Stencil = 0 + } + }; + return true; + } + else if (IsRenderTarget) + { + var clearColor = new ClearValue { Format = (Format) textureDescription.Format }; + AsRef(clearColor.Anonymous.Color) = Color4.Black; + clearValue = clearColor; + return true; + } + else + { + clearValue = default; + return false; + } } - else + + // + // Constructs the appropriate Direct3D 12 resource description for the specified texture type. + // + ResourceDesc GetTextureDescription(TextureDimension textureDimension) { - GraphicsDevice?.RegisterTextureMemoryUsage(-SizeInBytes); - } + return textureDimension switch + { + TextureDimension.Texture1D => ConvertToNativeDescription1D(), - base.OnDestroyed(); - } + TextureDimension.Texture2D or + TextureDimension.TextureCube => ConvertToNativeDescription2D(), - private void OnRecreateImpl() - { - // Dependency: wait for underlying texture to be recreated - if (ParentTexture != null && ParentTexture.LifetimeState != GraphicsResourceLifetimeState.Active) - return; + TextureDimension.Texture3D => ConvertToNativeDescription3D(), - // Render Target / Depth Stencil are considered as "dynamic" - if ((Usage == GraphicsResourceUsage.Immutable - || Usage == GraphicsResourceUsage.Default) - && !IsRenderTarget && !IsDepthStencil) - return; + _ => throw new ArgumentOutOfRangeException(nameof(textureDimension)) + }; + } - if (ParentTexture == null && GraphicsDevice != null) + // + // Creates a description for a Buffer with the specified size. + // + ResourceDesc CreateDescriptionForBuffer(ulong bufferSize) { - GraphicsDevice.RegisterTextureMemoryUsage(-SizeInBytes); + return new ResourceDesc + { + Dimension = ResourceDimension.Buffer, + Width = bufferSize, + Height = 1, + DepthOrArraySize = 1, + MipLevels = 1, + + Format = Silk.NET.DXGI.Format.FormatUnknown, + SampleDesc = { Count = 1, Quality = 0 }, + + Flags = ResourceFlags.None, + Layout = TextureLayout.LayoutRowMajor, + Alignment = 0 + }; } - InitializeFromImpl(); - } - - /// - /// Gets a specific from this texture. - /// - /// Type of the view slice. - /// The texture array slice index. - /// The mip map slice index. - /// An - private CpuDescriptorHandle GetShaderResourceView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) - { - if (!IsShaderResource) - return new CpuDescriptorHandle(); + // + // Gets a specific Shader Resource View from the Texture. + // + CpuDescriptorHandle GetShaderResourceView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) + { + if (!IsShaderResource) + return default; - GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out var mipCount); + GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out var mipCount); - // Create the view - // TODO D3D12 Shader4ComponentMapping is now set to default value D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING (0x00001688); need better control - var srvDescription = new ShaderResourceViewDescription() { Shader4ComponentMapping = 0x00001688, Format = ComputeShaderResourceViewFormat() }; + // Create the view + // TODO: D3D12: Shader4ComponentMapping is now set to default value D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING (0x00001688); need better control + var srvDescription = new ShaderResourceViewDesc + { + Shader4ComponentMapping = 0b1_011_010_001_000, // D3D12_ENCODE_SHADER_4_COMPONENT_MAPPING(0,1,2,3) + Format = ComputeShaderResourceViewFormat() + }; - // Initialize for texture arrays or texture cube - if (ArraySize > 1) - { - // If texture cube - if (Dimension == TextureDimension.TextureCube && viewType == ViewType.Full) + // Initialize for Texture Arrays or Texture Cube + if (ArraySize > 1) { - srvDescription.Dimension = ShaderResourceViewDimension.TextureCube; - srvDescription.TextureCube.MipLevels = mipCount; - srvDescription.TextureCube.MostDetailedMip = mipIndex; + // If Texture Cube + if (Dimension == TextureDimension.TextureCube && viewType == ViewType.Full) + { + srvDescription.ViewDimension = SrvDimension.Texturecube; + srvDescription.TextureCube.MipLevels = (uint) mipCount; + srvDescription.TextureCube.MostDetailedMip = (uint) mipIndex; + } + else // Texture array + { + if (IsMultiSampled) + { + if (Dimension != TextureDimension.Texture2D) + { + throw new NotSupportedException("Multisample is only supported for 2D Textures"); + } + srvDescription.ViewDimension = SrvDimension.Texture2Dmsarray; + srvDescription.Texture2DMSArray.ArraySize = (uint) arrayCount; + srvDescription.Texture2DMSArray.FirstArraySlice = (uint) arrayOrDepthSlice; + } + else + { + srvDescription.ViewDimension = Dimension is TextureDimension.Texture2D or TextureDimension.TextureCube + ? SrvDimension.Texture2Darray + : SrvDimension.Texture1Darray; + + srvDescription.Texture2DArray.ArraySize = (uint) arrayCount; + srvDescription.Texture2DArray.FirstArraySlice = (uint) arrayOrDepthSlice; + srvDescription.Texture2DArray.MipLevels = (uint) mipCount; + srvDescription.Texture2DArray.MostDetailedMip = (uint) mipIndex; + } + } } - else + else // Regular Texture (1D, 2D, 3D) { - // Else regular Texture array - // Multisample? - if (IsMultisample) + if (IsMultiSampled) { if (Dimension != TextureDimension.Texture2D) { throw new NotSupportedException("Multisample is only supported for 2D Textures"); } - - srvDescription.Dimension = ShaderResourceViewDimension.Texture2DMultisampledArray; - srvDescription.Texture2DMSArray.ArraySize = arrayCount; - srvDescription.Texture2DMSArray.FirstArraySlice = arrayOrDepthSlice; + srvDescription.ViewDimension = SrvDimension.Texture2Dms; } - else + else // Non-multisampled Texture { - srvDescription.Dimension = Dimension == TextureDimension.Texture2D || Dimension == TextureDimension.TextureCube ? ShaderResourceViewDimension.Texture2DArray : ShaderResourceViewDimension.Texture1DArray; - srvDescription.Texture2DArray.ArraySize = arrayCount; - srvDescription.Texture2DArray.FirstArraySlice = arrayOrDepthSlice; - srvDescription.Texture2DArray.MipLevels = mipCount; - srvDescription.Texture2DArray.MostDetailedMip = mipIndex; + switch (Dimension) + { + case TextureDimension.Texture1D: + srvDescription.ViewDimension = SrvDimension.Texture1D; + break; + + case TextureDimension.Texture2D: + srvDescription.ViewDimension = SrvDimension.Texture2D; + break; + + case TextureDimension.Texture3D: + srvDescription.ViewDimension = SrvDimension.Texture3D; + break; + + case TextureDimension.TextureCube: + throw new NotSupportedException("A Texture Cube must have an ArraySize > 1"); + } + // Use srvDescription.Texture1D as it matches also Texture2D and Texture3D memory layout + srvDescription.Texture1D.MipLevels = (uint) mipCount; + srvDescription.Texture1D.MostDetailedMip = (uint) mipIndex; } } + + var descriptorHandle = GraphicsDevice.ShaderResourceViewAllocator.Allocate(1); + + NativeDevice.CreateShaderResourceView(NativeResource, in srvDescription, descriptorHandle); + return descriptorHandle; } - else + + // + // Gets a specific Render Target View from the Texture. + // + CpuDescriptorHandle GetRenderTargetView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) { - if (IsMultisample) + if (!IsRenderTarget) + return default; + + if (viewType == ViewType.MipBand) + throw new NotSupportedException($"The view type [{nameof(ViewType)}.{nameof(ViewType.MipBand)}] is not supported for Render Targets"); + + GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out _); + + var rtvDescription = new RenderTargetViewDesc { Format = (Format) ViewFormat }; + + // Initialize for Texture Arrays or Texture Cube + if (ArraySize > 1) { - if (Dimension != TextureDimension.Texture2D) + if (MultisampleCount > MultisampleCount.None) { - throw new NotSupportedException("Multisample is only supported for 2D Textures"); + if (Dimension != TextureDimension.Texture2D) + { + throw new NotSupportedException("Multisample is only supported for 2D Textures"); + } + rtvDescription.ViewDimension = RtvDimension.Texture2Dmsarray; + rtvDescription.Texture2DMSArray.ArraySize = (uint)arrayCount; + rtvDescription.Texture2DMSArray.FirstArraySlice = (uint)arrayOrDepthSlice; + } + else // Non-multisampled + { + if (Dimension == TextureDimension.Texture3D) + { + throw new NotSupportedException("Texture Array is not supported for 3D Textures"); + } + rtvDescription.ViewDimension = Dimension is TextureDimension.Texture2D or TextureDimension.TextureCube + ? RtvDimension.Texture2Darray + : RtvDimension.Texture1Darray; + + // Use rtvDescription.Texture1DArray as it matches also Texture memory layout + rtvDescription.Texture1DArray.ArraySize = (uint) arrayCount; + rtvDescription.Texture1DArray.FirstArraySlice = (uint) arrayOrDepthSlice; + rtvDescription.Texture1DArray.MipSlice = (uint) mipIndex; } - - srvDescription.Dimension = ShaderResourceViewDimension.Texture2DMultisampled; } - else + else // Regular Texture (1D, 2D, 3D) { - switch (Dimension) + if (IsMultiSampled) { - case TextureDimension.Texture1D: - srvDescription.Dimension = ShaderResourceViewDimension.Texture1D; - break; - case TextureDimension.Texture2D: - srvDescription.Dimension = ShaderResourceViewDimension.Texture2D; - break; - case TextureDimension.Texture3D: - srvDescription.Dimension = ShaderResourceViewDimension.Texture3D; - break; - case TextureDimension.TextureCube: - throw new NotSupportedException("TextureCube dimension is expecting an arraysize > 1"); + if (Dimension != TextureDimension.Texture2D) + { + throw new NotSupportedException("Multisample is only supported for 2D Render Target Textures"); + } + rtvDescription.ViewDimension = RtvDimension.Texture2Dms; + } + else // Non-multisampled Texture + { + switch (Dimension) + { + case TextureDimension.Texture1D: + rtvDescription.ViewDimension = RtvDimension.Texture1D; + rtvDescription.Texture1D.MipSlice = (uint) mipIndex; + break; + + case TextureDimension.Texture2D: + rtvDescription.ViewDimension = RtvDimension.Texture2D; + rtvDescription.Texture2D.MipSlice = (uint) mipIndex; + break; + + case TextureDimension.Texture3D: + rtvDescription.ViewDimension = RtvDimension.Texture3D; + rtvDescription.Texture3D.WSize = (uint) arrayCount; + rtvDescription.Texture3D.FirstWSlice = (uint) arrayOrDepthSlice; + rtvDescription.Texture3D.MipSlice = (uint) mipIndex; + break; + + case TextureDimension.TextureCube: + throw new NotSupportedException("A Texture Cube must have an ArraySize > 1"); + } } - // Use srvDescription.Texture as it matches also Texture and Texture3D memory layout - srvDescription.Texture1D.MipLevels = mipCount; - srvDescription.Texture1D.MostDetailedMip = mipIndex; } + + var descriptorHandle = GraphicsDevice.RenderTargetViewAllocator.Allocate(1); + + NativeDevice.CreateRenderTargetView(NativeResource, in rtvDescription, descriptorHandle); + return descriptorHandle; } - // Default ShaderResourceView - var descriptorHandle = GraphicsDevice.ShaderResourceViewAllocator.Allocate(1); - NativeDevice.CreateShaderResourceView(NativeResource, srvDescription, descriptorHandle); - return descriptorHandle; - } + // + // Gets a Depth-Stencil View from the Texture. + // + CpuDescriptorHandle GetDepthStencilView(out bool hasStencil) + { + hasStencil = false; - /// - /// Gets a specific from this texture. - /// - /// Type of the view slice. - /// The texture array slice index. - /// Index of the mip. - /// An - /// ViewSlice.MipBand is not supported for render targets - private CpuDescriptorHandle GetRenderTargetView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) - { - if (!IsRenderTarget) - return new CpuDescriptorHandle(); + if (!IsDepthStencil) + return default; - if (viewType == ViewType.MipBand) - throw new NotSupportedException("ViewSlice.MipBand is not supported for render targets"); - GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out _); + // Check that the format is supported + if (ComputeShaderResourceFormatFromDepthFormat(ViewFormat) == PixelFormat.None) + throw new NotSupportedException($"Depth-Stencil format [{ViewFormat}] not supported"); - // Create the render target view - var rtvDescription = new RenderTargetViewDescription() { Format = (SharpDX.DXGI.Format) ViewFormat }; + hasStencil = IsStencilFormat(ViewFormat); - if (ArraySize > 1) - { - if (MultisampleCount > MultisampleCount.None) + // Create a Depth-Stencil View on this Texture + var depthStencilViewDescription = new DepthStencilViewDesc { - if (Dimension != TextureDimension.Texture2D) - { - throw new NotSupportedException("Multisample is only supported for 2D Textures"); - } + Format = ComputeDepthViewFormatFromTextureFormat(ViewFormat), + Flags = DsvFlags.None + }; - rtvDescription.Dimension = RenderTargetViewDimension.Texture2DMultisampledArray; - rtvDescription.Texture2DMSArray.ArraySize = arrayCount; - rtvDescription.Texture2DMSArray.FirstArraySlice = arrayOrDepthSlice; + // Initialize for Texture Arrays or Texture Cube + if (ArraySize > 1) + { + depthStencilViewDescription.ViewDimension = DsvDimension.Texture2Darray; + depthStencilViewDescription.Texture2DArray.ArraySize = (uint) ArraySize; + depthStencilViewDescription.Texture2DArray.FirstArraySlice = 0; + depthStencilViewDescription.Texture2DArray.MipSlice = 0; } - else + else // Regular Texture (2D) { - if (Dimension == TextureDimension.Texture3D) - { - throw new NotSupportedException("Texture Array is not supported for Texture3D"); - } + depthStencilViewDescription.ViewDimension = DsvDimension.Texture2D; + depthStencilViewDescription.Texture2D.MipSlice = 0; + } + + if (MultisampleCount > MultisampleCount.None) + depthStencilViewDescription.ViewDimension = DsvDimension.Texture2Dms; - rtvDescription.Dimension = Dimension == TextureDimension.Texture2D || Dimension == TextureDimension.TextureCube ? RenderTargetViewDimension.Texture2DArray : RenderTargetViewDimension.Texture1DArray; + if (IsDepthStencilReadOnly) + { + if (!IsDepthStencilReadOnlySupported(GraphicsDevice)) + throw new NotSupportedException("Cannot initialize a read-only Depth-Stencil Buffer. Not supported on this device."); - // Use rtvDescription.Texture1DArray as it matches also Texture memory layout - rtvDescription.Texture1DArray.ArraySize = arrayCount; - rtvDescription.Texture1DArray.FirstArraySlice = arrayOrDepthSlice; - rtvDescription.Texture1DArray.MipSlice = mipIndex; + // Create a Depth-Stencil View on this 2D Texture + depthStencilViewDescription.Flags = DsvFlags.ReadOnlyDepth; + if (HasStencil) + depthStencilViewDescription.Flags |= DsvFlags.ReadOnlyStencil; } + + var descriptorHandle = GraphicsDevice.DepthStencilViewAllocator.Allocate(1); + + NativeDevice.CreateDepthStencilView(NativeResource, in depthStencilViewDescription, descriptorHandle); + return descriptorHandle; } - else + + // + // Gets a specific Unordered Access View from the Texture. + // + CpuDescriptorHandle GetUnorderedAccessView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) { - if (IsMultisample) + if (!IsUnorderedAccess) + return default; + + if (IsMultiSampled) + throw new NotSupportedException("Multi-sampling is not supported for Unordered Access Views"); + + GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out _); + + var uavDescription = new UnorderedAccessViewDesc + { + Format = (Format) ViewFormat + }; + + // Initialize for Texture Arrays or Texture Cube + if (ArraySize > 1) { - if (Dimension != TextureDimension.Texture2D) + switch (Dimension) { - throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); - } + case TextureDimension.Texture1D: + uavDescription.ViewDimension = UavDimension.Texture1Darray; + break; - rtvDescription.Dimension = RenderTargetViewDimension.Texture2DMultisampled; + case TextureDimension.TextureCube: + case TextureDimension.Texture2D: + uavDescription.ViewDimension = UavDimension.Texture2Darray; + break; + + case TextureDimension.Texture3D: + throw new NotSupportedException("Texture 3D is not supported for Texture Arrays"); + } + uavDescription.Texture2DArray.ArraySize = (uint) arrayCount; + uavDescription.Texture2DArray.FirstArraySlice = (uint)arrayOrDepthSlice; + uavDescription.Texture2DArray.MipSlice = (uint) mipIndex; } - else + else // Regular Texture (1D, 2D, 3D) { switch (Dimension) { case TextureDimension.Texture1D: - rtvDescription.Dimension = RenderTargetViewDimension.Texture1D; - rtvDescription.Texture1D.MipSlice = mipIndex; + uavDescription.ViewDimension = UavDimension.Texture1D; + uavDescription.Texture1D.MipSlice = (uint) mipIndex; break; + case TextureDimension.Texture2D: - rtvDescription.Dimension = RenderTargetViewDimension.Texture2D; - rtvDescription.Texture2D.MipSlice = mipIndex; + uavDescription.ViewDimension = UavDimension.Texture2D; + uavDescription.Texture2D.MipSlice = (uint) mipIndex; break; + case TextureDimension.Texture3D: - rtvDescription.Dimension = RenderTargetViewDimension.Texture3D; - rtvDescription.Texture3D.DepthSliceCount = arrayCount; - rtvDescription.Texture3D.FirstDepthSlice = arrayOrDepthSlice; - rtvDescription.Texture3D.MipSlice = mipIndex; + uavDescription.ViewDimension = UavDimension.Texture3D; + uavDescription.Texture3D.FirstWSlice = (uint) arrayOrDepthSlice; + uavDescription.Texture3D.MipSlice = (uint) mipIndex; + uavDescription.Texture3D.WSize = (uint) arrayCount; break; + case TextureDimension.TextureCube: - throw new NotSupportedException("TextureCube dimension is expecting an arraysize > 1"); + throw new NotSupportedException("A Texture Cube must have an ArraySize > 1"); } } - } - var descriptorHandle = GraphicsDevice.RenderTargetViewAllocator.Allocate(1); - NativeDevice.CreateRenderTargetView(NativeResource, rtvDescription, descriptorHandle); - return descriptorHandle; - } + var descriptorHandle = GraphicsDevice.UnorderedAccessViewAllocator.Allocate(1); - private CpuDescriptorHandle GetDepthStencilView(out bool hasStencil) - { - hasStencil = false; - if (!IsDepthStencil) - return new CpuDescriptorHandle(); - - // Check that the format is supported - if (ComputeShaderResourceFormatFromDepthFormat(ViewFormat) == PixelFormat.None) - throw new NotSupportedException("Depth stencil format [{0}] not supported".ToFormat(ViewFormat)); - - // Setup the HasStencil flag - hasStencil = IsStencilFormat(ViewFormat); + NativeDevice.CreateUnorderedAccessView(NativeResource, pCounterResource: null, in uavDescription, descriptorHandle); + return descriptorHandle; + } - // Create a Depth stencil view on this texture2D - var depthStencilViewDescription = new DepthStencilViewDescription + // + // Returns a DXGI format for a Shader Resource View that is compatible with the + // current Texture's parameters. + // + Format ComputeShaderResourceViewFormat() { - Format = ComputeDepthViewFormatFromTextureFormat(ViewFormat), - Flags = DepthStencilViewFlags.None, - }; + // Special case for Depth-Stencil Shader Resource View that are bound as Float + var viewFormat = IsDepthStencil + ? (Format) ComputeShaderResourceFormatFromDepthFormat(ViewFormat) + : (Format) ViewFormat; - if (ArraySize > 1) - { - depthStencilViewDescription.Dimension = DepthStencilViewDimension.Texture2DArray; - depthStencilViewDescription.Texture2DArray.ArraySize = ArraySize; - depthStencilViewDescription.Texture2DArray.FirstArraySlice = 0; - depthStencilViewDescription.Texture2DArray.MipSlice = 0; + return viewFormat; } - else + + // + // Given a pixel format, returns the corresponding Silk.NET's depth format. + // + static Format ComputeDepthViewFormatFromTextureFormat(PixelFormat format) { - depthStencilViewDescription.Dimension = DepthStencilViewDimension.Texture2D; - depthStencilViewDescription.Texture2D.MipSlice = 0; - } + var viewFormat = format switch + { + PixelFormat.R16_Typeless or PixelFormat.D16_UNorm => Silk.NET.DXGI.Format.FormatD16Unorm, + PixelFormat.R32_Typeless or PixelFormat.D32_Float => Silk.NET.DXGI.Format.FormatD32Float, + PixelFormat.R24G8_Typeless or PixelFormat.D24_UNorm_S8_UInt => Silk.NET.DXGI.Format.FormatD24UnormS8Uint, + PixelFormat.R32G8X24_Typeless or PixelFormat.D32_Float_S8X24_UInt => Silk.NET.DXGI.Format.FormatD32FloatS8X24Uint, - if (MultisampleCount > MultisampleCount.None) - depthStencilViewDescription.Dimension = DepthStencilViewDimension.Texture2DMultisampled; + _ => throw new NotSupportedException($"Unsupported depth format [{format}]") + }; + return viewFormat; + } - if (IsDepthStencilReadOnly) + // + // Determines if a format is a Depth-Stencil format that also contains Stencil data. + // + static bool IsStencilFormat(PixelFormat format) { - if (!IsDepthStencilReadOnlySupported(GraphicsDevice)) - throw new NotSupportedException("Cannot instantiate ReadOnly DepthStencilBuffer. Not supported on this device."); + return format switch + { + PixelFormat.R24G8_Typeless or + PixelFormat.D24_UNorm_S8_UInt or + PixelFormat.R32G8X24_Typeless or + PixelFormat.D32_Float_S8X24_UInt => true, - // Create a Depth stencil view on this texture2D - depthStencilViewDescription.Flags = DepthStencilViewFlags.ReadOnlyDepth; - if (HasStencil) - depthStencilViewDescription.Flags |= DepthStencilViewFlags.ReadOnlyStencil; + _ => false + }; } - - var descriptorHandle = GraphicsDevice.DepthStencilViewAllocator.Allocate(1); - NativeDevice.CreateDepthStencilView(NativeResource, depthStencilViewDescription, descriptorHandle); - return descriptorHandle; } - private CpuDescriptorHandle GetUnorderedAccessView(ViewType viewType, int arrayOrDepthSlice, int mipIndex) + /// + /// + /// This method releases all the native resources associated with the Texture: + /// + /// + /// If it is a Texture, this releases the underlying native texture resource and also the associated Views. + /// + /// + /// If it is a Texture View, it releases only the resources related to the View, not the parent Texture's. + /// + /// + /// + protected internal override void OnDestroyed() { - if (!IsUnorderedAccess) - return new CpuDescriptorHandle(); - - if (IsMultisample) - throw new NotSupportedException("Multisampling is not supported for unordered access views"); - GetViewSliceBounds(viewType, ref arrayOrDepthSlice, ref mipIndex, out var arrayCount, out _); - - // Create a Unordered Access view on this texture2D - var uavDescription = new UnorderedAccessViewDescription + // If it was a View, do not release reference + if (ParentTexture is not null) { - Format = (SharpDX.DXGI.Format) ViewFormat - }; - - if (ArraySize > 1) + ForgetNativeChildWithoutReleasing(); + } + else { - switch (Dimension) - { - case TextureDimension.Texture1D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture1DArray; - break; - case TextureDimension.TextureCube: - case TextureDimension.Texture2D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture2DArray; - break; - case TextureDimension.Texture3D: - throw new NotSupportedException("Texture 3D is not supported for Texture Arrays"); + GraphicsDevice?.RegisterTextureMemoryUsage(-SizeInBytes); + } - } + base.OnDestroyed(); + } - uavDescription.Texture2DArray.ArraySize = arrayCount; - uavDescription.Texture2DArray.FirstArraySlice = arrayOrDepthSlice; - uavDescription.Texture2DArray.MipSlice = mipIndex; - } - else + /// + /// Perform Direct3D 12-specific recreation of the Texture. + /// + /// Invalid Texture share options () specified. + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + /// is not supported for Render Targets. + /// Multi-sampling is not supported for Unordered Access Views. + /// The Depth-Stencil format specified is not supported. + /// Cannot create a read-only Depth-Stencil View because the device does not support it. + private partial void OnRecreateImpl() + { + // Dependency: Wait for underlying texture to be recreated + if (ParentTexture is not null && ParentTexture.LifetimeState != GraphicsResourceLifetimeState.Active) + return; + + // Render Target / Depth Stencil are considered as "dynamic" + if (Usage is GraphicsResourceUsage.Immutable or GraphicsResourceUsage.Default && + !IsRenderTarget && !IsDepthStencil) + return; + + if (ParentTexture is null) { - switch (Dimension) - { - case TextureDimension.Texture1D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture1D; - uavDescription.Texture1D.MipSlice = mipIndex; - break; - case TextureDimension.Texture2D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture2D; - uavDescription.Texture2D.MipSlice = mipIndex; - break; - case TextureDimension.Texture3D: - uavDescription.Dimension = UnorderedAccessViewDimension.Texture3D; - uavDescription.Texture3D.FirstWSlice = arrayOrDepthSlice; - uavDescription.Texture3D.MipSlice = mipIndex; - uavDescription.Texture3D.WSize = arrayCount; - break; - case TextureDimension.TextureCube: - throw new NotSupportedException("TextureCube dimension is expecting an array size > 1"); - } + GraphicsDevice?.RegisterTextureMemoryUsage(-SizeInBytes); } - var descriptorHandle = GraphicsDevice.UnorderedAccessViewAllocator.Allocate(1); - NativeDevice.CreateUnorderedAccessView(NativeResource, null, uavDescription, descriptorHandle); - return descriptorHandle; + InitializeFromImpl(); } - internal static ResourceFlags GetBindFlagsFromTextureFlags(TextureFlags flags) + /// + /// Converts the specified to Silk.NET's . + /// + /// The flags to convert. + /// The corresponding . + private static ResourceFlags GetBindFlagsFromTextureFlags(TextureFlags flags) { var result = ResourceFlags.None; - if ((flags & TextureFlags.RenderTarget) != 0) + if (flags.HasFlag(TextureFlags.RenderTarget)) result |= ResourceFlags.AllowRenderTarget; - if ((flags & TextureFlags.UnorderedAccess) != 0) + if (flags.HasFlag(TextureFlags.UnorderedAccess)) result |= ResourceFlags.AllowUnorderedAccess; - if ((flags & TextureFlags.DepthStencil) != 0) + if (flags.HasFlag(TextureFlags.DepthStencil)) { result |= ResourceFlags.AllowDepthStencil; - if ((flags & TextureFlags.ShaderResource) == 0) + if (!flags.HasFlag(TextureFlags.ShaderResource)) result |= ResourceFlags.DenyShaderResource; } return result; } - internal static SharpDX.DataBox[] ConvertDataBoxes(DataBox[] dataBoxes) - { - if (dataBoxes == null || dataBoxes.Length == 0) - return null; - var sharpDXDataBoxes = new SharpDX.DataBox[dataBoxes.Length]; - dataBoxes.AsSpan().CopyTo(Unsafe.As(sharpDXDataBoxes)); - return sharpDXDataBoxes; - } - - private bool IsFlipped() + /// + /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. + /// + /// if the Texture is flipped; otherwise. + /// + /// For Direct3D, Textures are not flipped, meaning the first row is at the top and the last row is at the bottom. + /// + private partial bool IsFlipped() { return false; } - private ResourceDescription ConvertToNativeDescription1D() - { - return ResourceDescription.Texture1D((SharpDX.DXGI.Format) textureDescription.Format, textureDescription.Width, (short) textureDescription.ArraySize, (short) textureDescription.MipLevels, GetBindFlagsFromTextureFlags(textureDescription.Flags)); - } - - private SharpDX.DXGI.Format ComputeShaderResourceViewFormat() - { - // Special case for DepthStencil ShaderResourceView that are bound as Float - var viewFormat = (SharpDX.DXGI.Format)ViewFormat; - if (IsDepthStencil) - { - viewFormat = (SharpDX.DXGI.Format)ComputeShaderResourceFormatFromDepthFormat(ViewFormat); - } - - return viewFormat; - } - - private static TextureDescription ConvertFromNativeDescription(ResourceDescription description, bool isShaderResource = false) + /// + /// Returns a native describing a 1D Texture from the current . + /// + /// A Silk.NET's describing the Texture. + internal ResourceDesc ConvertToNativeDescription1D() { - var desc = new TextureDescription() + return new ResourceDesc { - Dimension = TextureDimension.Texture2D, - Width = (int) description.Width, - Height = description.Height, - Depth = 1, - MultisampleCount = (MultisampleCount) description.SampleDescription.Count, - Format = (PixelFormat) description.Format, - MipLevels = description.MipLevels, - Usage = GraphicsResourceUsage.Default, - ArraySize = description.DepthOrArraySize, - Flags = TextureFlags.None + Dimension = ResourceDimension.Texture1D, + Width = (ulong) textureDescription.Width, + Height = 1, + DepthOrArraySize = (ushort) textureDescription.ArraySize, + MipLevels = (ushort) textureDescription.MipLevelCount, + + Format = (Format) textureDescription.Format, + SampleDesc = { Count = 1, Quality = 0 }, + + Flags = GetBindFlagsFromTextureFlags(textureDescription.Flags), + Layout = TextureLayout.LayoutUnknown, + Alignment = 0 }; - - if ((description.Flags & ResourceFlags.AllowRenderTarget) != 0) - desc.Flags |= TextureFlags.RenderTarget; - if ((description.Flags & ResourceFlags.AllowUnorderedAccess) != 0) - desc.Flags |= TextureFlags.UnorderedAccess; - if ((description.Flags & ResourceFlags.AllowDepthStencil) != 0) - desc.Flags |= TextureFlags.DepthStencil; - if ((description.Flags & ResourceFlags.DenyShaderResource) == 0 && isShaderResource) - desc.Flags |= TextureFlags.ShaderResource; - - return desc; } - private ResourceDescription ConvertToNativeDescription2D() + /// + /// Returns a native describing a 2D Texture from the current . + /// + /// A Silk.NET's describing the Texture. + /// + /// For a lower than , creating Shader Resource Views + /// for Depth-Stencil Textures is not supported, + /// + /// + /// The specified pixel format is not supported for Depth-Stencil Textures. + /// + internal ResourceDesc ConvertToNativeDescription2D() { - var format = (SharpDX.DXGI.Format) textureDescription.Format; + var format = (Format) textureDescription.Format; var flags = textureDescription.Flags; - // If the texture is going to be bound on the depth stencil, for to use TypeLess format + // If the Texture is going to be bound on the Depth-Stencil, use Typeless format if (IsDepthStencil) { if (IsShaderResource && GraphicsDevice.Features.CurrentProfile < GraphicsProfile.Level_10_0) { - throw new NotSupportedException(string.Format("ShaderResourceView for DepthStencil Textures are not supported for Graphics profile < 10.0 (Current: [{0}])", GraphicsDevice.Features.CurrentProfile)); + throw new NotSupportedException($"Creating Shader Resource Views for Depth-Stencil Buffers are not supported for Graphics Profiles < 10.0 (Current: [{GraphicsDevice.Features.CurrentProfile}])"); } else { - // Determine TypeLess Format and ShaderResourceView Format + // Determine Typeless Format and Shader Resource View Format if (GraphicsDevice.Features.CurrentProfile < GraphicsProfile.Level_10_0) { - switch (textureDescription.Format) + format = textureDescription.Format switch { - case PixelFormat.D16_UNorm: - format = SharpDX.DXGI.Format.D16_UNorm; - break; - case PixelFormat.D32_Float: - format = SharpDX.DXGI.Format.D32_Float; - break; - case PixelFormat.D24_UNorm_S8_UInt: - format = SharpDX.DXGI.Format.D24_UNorm_S8_UInt; - break; - case PixelFormat.D32_Float_S8X24_UInt: - format = SharpDX.DXGI.Format.D32_Float_S8X24_UInt; - break; - default: - throw new NotSupportedException(string.Format("Unsupported DepthFormat [{0}] for depth buffer", textureDescription.Format)); - } + PixelFormat.D16_UNorm => Silk.NET.DXGI.Format.FormatD16Unorm, + PixelFormat.D32_Float => Silk.NET.DXGI.Format.FormatD32Float, + PixelFormat.D24_UNorm_S8_UInt => Silk.NET.DXGI.Format.FormatD24UnormS8Uint, + PixelFormat.D32_Float_S8X24_UInt => Silk.NET.DXGI.Format.FormatD32FloatS8X24Uint, + + _ => throw new NotSupportedException($"Unsupported Depth format [{textureDescription.Format}] for Depth Buffer") + }; } - else + else // GraphicsProfile >= 10.0 { - switch (textureDescription.Format) + format = textureDescription.Format switch { - case PixelFormat.D16_UNorm: - format = SharpDX.DXGI.Format.R16_Typeless; - break; - case PixelFormat.D32_Float: - format = SharpDX.DXGI.Format.R32_Typeless; - break; - case PixelFormat.D24_UNorm_S8_UInt: - //format = SharpDX.DXGI.Format.D24_UNorm_S8_UInt; - format = SharpDX.DXGI.Format.R24G8_Typeless; - break; - case PixelFormat.D32_Float_S8X24_UInt: - format = SharpDX.DXGI.Format.R32G8X24_Typeless; - break; - default: - throw new NotSupportedException(string.Format("Unsupported DepthFormat [{0}] for depth buffer", textureDescription.Format)); - } - } - } - } - - return ResourceDescription.Texture2D(format, textureDescription.Width, textureDescription.Height, (short)textureDescription.ArraySize, (short)textureDescription.MipLevels, (short)textureDescription.MultisampleCount, 0, GetBindFlagsFromTextureFlags(flags)); - } + PixelFormat.D16_UNorm => Silk.NET.DXGI.Format.FormatR16Typeless, + PixelFormat.D32_Float => Silk.NET.DXGI.Format.FormatR32Typeless, + PixelFormat.D24_UNorm_S8_UInt => Silk.NET.DXGI.Format.FormatR24G8Typeless, + PixelFormat.D32_Float_S8X24_UInt => Silk.NET.DXGI.Format.FormatR32G8X24Typeless, - internal ClearValue? GetClearValue() - { - if (IsDepthStencil) - { - return new ClearValue - { - Format = ComputeDepthViewFormatFromTextureFormat(ViewFormat), - DepthStencil = new DepthStencilValue - { - Depth = 1.0f, - Stencil = 0, + _ => throw new NotSupportedException($"Unsupported Depth format [{textureDescription.Format}] for Depth Buffer") + }; } - }; + } } - if (IsRenderTarget) + return new ResourceDesc { - return new ClearValue - { - Format = (SharpDX.DXGI.Format) textureDescription.Format, - Color = new RawVector4(0, 0, 0, 1), - }; - } - - return null; + Dimension = ResourceDimension.Texture2D, + Width = (ulong) textureDescription.Width, + Height = (uint) textureDescription.Height, + DepthOrArraySize = (ushort) textureDescription.ArraySize, + MipLevels = (ushort) textureDescription.MipLevelCount, + + Format = format, + SampleDesc = { Count = (uint) textureDescription.MultisampleCount, Quality = 0 }, + + Flags = GetBindFlagsFromTextureFlags(flags), + Layout = TextureLayout.LayoutUnknown, + Alignment = 0 + }; } + /// + /// Given a Depth Texture format, returns the corresponding Shader Resource View format. + /// + /// The depth format. + /// + /// The View format corresponding to , + /// or if no compatible format could be computed. + /// internal static PixelFormat ComputeShaderResourceFormatFromDepthFormat(PixelFormat format) { - PixelFormat viewFormat; - - // Determine TypeLess Format and ShaderResourceView Format - switch (format) + var viewFormat = format switch { - case PixelFormat.D16_UNorm: - viewFormat = PixelFormat.R16_Float; - break; - case PixelFormat.D32_Float: - viewFormat = PixelFormat.R32_Float; - break; - case PixelFormat.D24_UNorm_S8_UInt: - viewFormat = PixelFormat.R24_UNorm_X8_Typeless; - break; - case PixelFormat.D32_Float_S8X24_UInt: - viewFormat = PixelFormat.R32_Float_X8X24_Typeless; - break; - default: - viewFormat = PixelFormat.None; - break; - } + PixelFormat.D16_UNorm => PixelFormat.R16_Float, + PixelFormat.D32_Float => PixelFormat.R32_Float, + PixelFormat.D24_UNorm_S8_UInt => PixelFormat.R24_UNorm_X8_Typeless, + PixelFormat.D32_Float_S8X24_UInt => PixelFormat.R32_Float_X8X24_Typeless, + _ => PixelFormat.None + }; return viewFormat; } - internal static SharpDX.DXGI.Format ComputeDepthViewFormatFromTextureFormat(PixelFormat format) + /// + /// Returns a native describing a 3D Texture from the current . + /// + /// A Silk.NET's describing the Texture. + internal ResourceDesc ConvertToNativeDescription3D() { - SharpDX.DXGI.Format viewFormat; - - switch (format) + return new ResourceDesc { - case PixelFormat.R16_Typeless: - case PixelFormat.D16_UNorm: - viewFormat = SharpDX.DXGI.Format.D16_UNorm; - break; - case PixelFormat.R32_Typeless: - case PixelFormat.D32_Float: - viewFormat = SharpDX.DXGI.Format.D32_Float; - break; - case PixelFormat.R24G8_Typeless: - case PixelFormat.D24_UNorm_S8_UInt: - viewFormat = SharpDX.DXGI.Format.D24_UNorm_S8_UInt; - break; - case PixelFormat.R32G8X24_Typeless: - case PixelFormat.D32_Float_S8X24_UInt: - viewFormat = SharpDX.DXGI.Format.D32_Float_S8X24_UInt; - break; - default: - throw new NotSupportedException(string.Format("Unsupported depth format [{0}]", format)); - } - - return viewFormat; - } - - private ResourceDescription ConvertToNativeDescription3D() - { - return ResourceDescription.Texture3D((SharpDX.DXGI.Format) textureDescription.Format, textureDescription.Width, textureDescription.Height, (short) textureDescription.Depth, (short) textureDescription.MipLevels, GetBindFlagsFromTextureFlags(textureDescription.Flags)); + Dimension = ResourceDimension.Texture3D, + Width = (ulong) textureDescription.Width, + Height = (uint) textureDescription.Height, + DepthOrArraySize = (ushort) textureDescription.Depth, + MipLevels = (ushort) textureDescription.MipLevelCount, + + Format = (Format) textureDescription.Format, + SampleDesc = { Count = 1, Quality = 0 }, + + Flags = GetBindFlagsFromTextureFlags(textureDescription.Flags), + Layout = TextureLayout.LayoutUnknown, + Alignment = 0 + }; } /// - /// Check and modify if necessary the mipmap levels of the image (Troubles with DXT images whose resolution in less than 4x4 in DX9.x). + /// Checks a for invalid mip-levels and modifies the description if necessary. /// /// The graphics device. - /// The texture description. - /// The updated texture description. + /// The Texture description to check. + /// The updated Texture description. + /// + /// This check is to prevent issues with Direct3D 9.x where the driver may not be able to create mipmaps + /// whose resolution in less than 4x4 pixels. + /// private static TextureDescription CheckMipLevels(GraphicsDevice device, ref TextureDescription description) { - if (device.Features.CurrentProfile < GraphicsProfile.Level_10_0 && (description.Flags & TextureFlags.DepthStencil) == 0 && description.Format.IsCompressed()) + // Troubles with DXT images whose resolution in less than 4x4 in DX9.x + // TODO: Stale comment? + + if (device.Features.CurrentProfile < GraphicsProfile.Level_10_0 && + !description.Flags.HasFlag(TextureFlags.DepthStencil) && description.Format.IsCompressed()) { - description.MipLevels = Math.Min(CalculateMipCount(description.Width, description.Height), description.MipLevels); + description.MipLevelCount = Math.Min(CalculateMipCount(description.Width, description.Height), description.MipLevelCount); } return description; } /// - /// Calculates the mip level from a specified size. + /// Calculates the number of mip-levels that can be created for a specified size, taking into account + /// a minimum mip-level size. /// - /// The size. - /// The minimum size of the last mip. - /// The mip level. - /// Value must be > 0;size + /// The size in pixels. + /// The minimum size of the last mip-level. By default, this is 4 pixels. + /// The number of possible mip-levels. + /// + /// Both and must be greater than 0. + /// private static int CalculateMipCountFromSize(int size, int minimumSizeLastMip = 4) { - if (size <= 0) - { - throw new ArgumentOutOfRangeException("Value must be > 0", "size"); - } - - if (minimumSizeLastMip <= 0) - { - throw new ArgumentOutOfRangeException("Value must be > 0", "minimumSizeLastMip"); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(size); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minimumSizeLastMip); int level = 1; while ((size / 2) >= minimumSizeLastMip) @@ -838,31 +1067,22 @@ private static int CalculateMipCountFromSize(int size, int minimumSizeLastMip = } /// - /// Calculates the mip level from a specified width,height,depth. + /// Calculates the number of mip-levels that can be created for a specified size, taking into account + /// a minimum mip-level size. /// - /// The width. - /// The height. - /// The minimum size of the last mip. - /// The mip level. - /// Value must be > 0;size + /// The width in pixels. + /// The height in pixels. + /// The minimum size of the last mip-level. By default, this is 4 pixels. + /// The number of possible mip-levels. + /// + /// and must be greater than 0, and + /// must also be greater than 0. + /// private static int CalculateMipCount(int width, int height, int minimumSizeLastMip = 4) { return Math.Min(CalculateMipCountFromSize(width, minimumSizeLastMip), CalculateMipCountFromSize(height, minimumSizeLastMip)); } - - internal static bool IsStencilFormat(PixelFormat format) - { - switch (format) - { - case PixelFormat.R24G8_Typeless: - case PixelFormat.D24_UNorm_S8_UInt: - case PixelFormat.R32G8X24_Typeless: - case PixelFormat.D32_Float_S8X24_UInt: - return true; - } - - return false; - } } } + #endif diff --git a/sources/engine/Stride.Graphics/DisplayMode.cs b/sources/engine/Stride.Graphics/DisplayMode.cs index db2aee7012..2db2c273b7 100644 --- a/sources/engine/Stride.Graphics/DisplayMode.cs +++ b/sources/engine/Stride.Graphics/DisplayMode.cs @@ -1,71 +1,35 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Globalization; namespace Stride.Graphics { /// - /// Describes the display mode. + /// Describes a display mode. /// - public partial class DisplayMode + /// The pixel format of this display mode. + /// The screen width, in pixels. + /// The screen height, in pixels. + /// The refresh rate, in Hz. + public readonly partial record struct DisplayMode(PixelFormat Format, int Width, int Height, Rational RefreshRate) { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the record. /// - /// The format. - /// The width. - /// The height. - /// The refresh rate. - public DisplayMode(PixelFormat format, int width, int height, Rational refreshRate) - { - Format = format; - Width = width; - Height = height; - RefreshRate = refreshRate; - } + /// The pixel format of this display mode. + /// The screen width, in pixels. + /// The screen height, in pixels. + /// The refresh rate, in Hz. + public DisplayMode(PixelFormat format, int width, int height, uint refreshRate) + : this(format, width, height, new Rational((int) refreshRate, 1)) { } - /// - /// Gets the aspect ratio used by the graphics device. - /// - public float AspectRatio - { - get - { - if ((Height != 0) && (Width != 0)) - { - return ((float)Width) / Height; - } - return 0f; - } - } - - /// - /// Gets a value indicating the surface format of the display mode. - /// - public readonly PixelFormat Format; - - /// - /// Gets a value indicating the screen width, in pixels. - /// - public readonly int Width; - - /// - /// Gets a value indicating the screen height, in pixels. - /// - public readonly int Height; - - /// - /// Gets a value indicating the refresh rate - /// - public readonly Rational RefreshRate; /// - /// Retrieves a string representation of this object. + /// Gets the aspect ratio of this display mode. /// - /// - public override string ToString() - { - return string.Format(CultureInfo.InvariantCulture, "[Width:{0} Height:{1} Format:{2} AspectRatio:{3}]", Width, Height, Format, AspectRatio); - } + /// + /// The aspect ratio is the ratio of the display mode's in relation to the , + /// i.e. Width / Height. + /// + public readonly float AspectRatio => (Height != 0) && (Width != 0) ? (float) Width / Height : 0; } } diff --git a/sources/engine/Stride.Graphics/EffectDescriptorSetReflection.cs b/sources/engine/Stride.Graphics/EffectDescriptorSetReflection.cs index 1d9ae2584f..ecef86adc0 100644 --- a/sources/engine/Stride.Graphics/EffectDescriptorSetReflection.cs +++ b/sources/engine/Stride.Graphics/EffectDescriptorSetReflection.cs @@ -1,90 +1,158 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Collections.Generic; using System.Linq; -using Stride.Graphics; + using Stride.Shaders; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A reflection object that describes the Descriptor Sets and their layouts for an Effect / Shader +/// based on reflection data extracted from its bytecode. +///
+/// This includes the bindings for Graphics Resources such as Textures, Buffers, and Sampler States. +///
+public class EffectDescriptorSetReflection { - public class EffectDescriptorSetReflection + /// + /// Gets the default Descriptor Set slot name used for the Graphics Resources in this Effect / Shader when no slot name is specified. + /// + internal string DefaultSetSlot { get; } + + /// + /// Gets a list of Descriptor Set layouts that describe the bindings for Graphics Resources. + /// + internal List Layouts { get; } = []; + + + /// + /// Creates a new Effect Descriptor Set reflection object. + /// + /// The Graphics Device used to create Sampler States and manage bindings. + /// The Effect / Shader bytecode containing reflection data for resource bindings and Sampler States. + /// + /// A list of Descriptor Set slot names to be processed. + /// This usually comes from the resource groups (rgroups) in the Effects / Shaders, plus a "Globals" slot + /// for those Descriptor Set layouts that have no specified a slot / group name. + /// + /// + /// The default Descriptor Set slot name used for the Graphics Resources in this Effect / Shader when no slot name is specified. + /// Usually, this is "Globals". + /// + /// + /// The new instance of containing the Descriptor Set layouts and default set + /// slot information. + /// + public static EffectDescriptorSetReflection New(GraphicsDevice graphicsDevice, EffectBytecode effectBytecode, List effectDescriptorSetSlots, string defaultSetSlot) { - public static EffectDescriptorSetReflection New(GraphicsDevice graphicsDevice, EffectBytecode effectBytecode, List effectDescriptorSetSlots, string defaultSetSlot) + var descriptorSetLayouts = new EffectDescriptorSetReflection(defaultSetSlot); + + // Find resource groups + // TODO: We should precompute most of that at compile time in BytecodeReflection. Jjust waiting for format to be more stable + foreach (var effectDescriptorSetSlot in effectDescriptorSetSlots) { - // Find resource groups - // TODO: We should precompute most of that at compile time in BytecodeReflection - // just waiting for format to be more stable - var descriptorSetLayouts = new EffectDescriptorSetReflection { DefaultSetSlot = defaultSetSlot }; - foreach (var effectDescriptorSetSlot in effectDescriptorSetSlots) + // Find all resources related to this slot name + // NOTE: Ordering is mirrored by GLSL layout in Vulkan + var descriptorSetLayoutBuilder = new DescriptorSetLayoutBuilder(); + bool hasBindings = false; + + var resourceBindingsBySlot = effectBytecode.Reflection.ResourceBindings + // Resource bindings of a group with the same name as the slot, + // or to no group/to Globals group if default slot is used + .Where(x => x.ResourceGroup == effectDescriptorSetSlot || + (effectDescriptorSetSlot == defaultSetSlot && (x.ResourceGroup is null or "Globals"))) + .GroupBy(x => (x.KeyInfo.Key, x.Class, x.Type, ElementType: x.ElementType.Type, x.SlotCount, x.LogicalGroup)) + // NOTE: Putting Constant Buffers first for now + .OrderBy(x => x.Key.Class == EffectParameterClass.ConstantBuffer ? 0 : 1); + + foreach (var resourceBinding in resourceBindingsBySlot) { - // Find all resources related to this slot name - // NOTE: Ordering is mirrored by GLSL layout in Vulkan - var descriptorSetLayoutBuilder = new DescriptorSetLayoutBuilder(); - bool hasBindings = false; - foreach (var resourceBinding in effectBytecode.Reflection.ResourceBindings - .Where(x => x.ResourceGroup == effectDescriptorSetSlot || (effectDescriptorSetSlot == defaultSetSlot && (x.ResourceGroup == null || x.ResourceGroup == "Globals"))) - .GroupBy(x => new { Key = x.KeyInfo.Key, Class = x.Class, Type = x.Type, ElementType = x.ElementType.Type, SlotCount = x.SlotCount, LogicalGroup = x.LogicalGroup }) - .OrderBy(x => x.Key.Class == EffectParameterClass.ConstantBuffer ? 0 : 1)) // Note: Putting cbuffer first for now + SamplerState samplerState = null; + if (resourceBinding.Key.Class == EffectParameterClass.Sampler) { - SamplerState samplerState = null; - if (resourceBinding.Key.Class == EffectParameterClass.Sampler) - { - var matchingSamplerState = effectBytecode.Reflection.SamplerStates.FirstOrDefault(x => x.Key == resourceBinding.Key.Key); - if (matchingSamplerState != null) - samplerState = SamplerState.New(graphicsDevice, matchingSamplerState.Description); - } - hasBindings = true; - - descriptorSetLayoutBuilder.AddBinding(resourceBinding.Key.Key, resourceBinding.Key.LogicalGroup, resourceBinding.Key.Class, resourceBinding.Key.Type, resourceBinding.Key.ElementType, resourceBinding.Key.SlotCount, samplerState); + var matchingSamplerState = effectBytecode.Reflection.SamplerStates.FirstOrDefault(x => x.Key == resourceBinding.Key.Key); + if (matchingSamplerState is not null) + samplerState = SamplerState.New(graphicsDevice, matchingSamplerState.Description); } + hasBindings = true; - descriptorSetLayouts.AddLayout(effectDescriptorSetSlot, hasBindings ? descriptorSetLayoutBuilder : null); + descriptorSetLayoutBuilder.AddBinding(resourceBinding.Key.Key, resourceBinding.Key.LogicalGroup, resourceBinding.Key.Class, resourceBinding.Key.Type, resourceBinding.Key.ElementType, resourceBinding.Key.SlotCount, samplerState); } - return descriptorSetLayouts; + descriptorSetLayouts.AddLayout(effectDescriptorSetSlot, hasBindings ? descriptorSetLayoutBuilder : null); } - internal string DefaultSetSlot { get; private set; } - - internal List Layouts { get; } = new List(); + return descriptorSetLayouts; + } - public DescriptorSetLayoutBuilder GetLayout(string name) - { - foreach (var entry in Layouts) - { - if (entry.Name == name) - return entry.Layout; - } + private EffectDescriptorSetReflection(string defaultSetSlot) + { + DefaultSetSlot = defaultSetSlot; + } - return null; - } - public int GetLayoutIndex(string name) + /// + /// Gets the layout of the Descriptor Set with the given name. + /// + /// The name of the Descriptor Set. + /// + /// A object that describes the Graphics Resource bindings + /// and their layout in the Descriptor Set with the provided , or + /// if no Descriptor Set with that name exists. + /// + public DescriptorSetLayoutBuilder? GetLayout(string name) + { + foreach (var entry in Layouts) { - for (int index = 0; index < Layouts.Count; index++) - { - if (Layouts[index].Name == name) - return index; - } - - return -1; + if (entry.Name == name) + return entry.Layout; } - public void AddLayout(string descriptorSetName, DescriptorSetLayoutBuilder descriptorSetLayoutBuilder) + return null; + } + + /// + /// Gets the index of the Descriptor Set with the given name. + /// + /// The name of the Descriptor Set. + /// + /// The zero-based index of the Descriptor Set with the provided , or + /// -1 if no Descriptor Set with that name exists. + /// + public int GetLayoutIndex(string name) + { + for (int index = 0; index < Layouts.Count; index++) { - Layouts.Add(new LayoutEntry(descriptorSetName, descriptorSetLayoutBuilder)); + if (Layouts[index].Name == name) + return index; } - internal struct LayoutEntry - { - public string Name; - public DescriptorSetLayoutBuilder Layout; + return -1; + } - public LayoutEntry(string name, DescriptorSetLayoutBuilder layout) - { - Name = name; - Layout = layout; - } - } + /// + /// Adds a new Descriptor Set layout for the given name. + /// + /// The slot name given to the Descriptor Set layout in the Effect / Shader. + /// + /// A that describes the Graphics Resource bindings and their associated + /// metadata and layout. + /// + public void AddLayout(string descriptorSetName, DescriptorSetLayoutBuilder descriptorSetLayoutBuilder) + { + Layouts.Add(new LayoutEntry(descriptorSetName, descriptorSetLayoutBuilder)); } + + + /// + /// A structure associating a Descriptor Set layout with its name. + /// + /// The Descriptor Set name. + /// + /// A that describes the Graphics Resource bindings and their associated + /// metadata and layout. + /// + internal readonly record struct LayoutEntry(string Name, DescriptorSetLayoutBuilder Layout); } diff --git a/sources/engine/Stride.Graphics/FastTextRenderer.cs b/sources/engine/Stride.Graphics/FastTextRenderer.cs index 3a315d83b0..323136d224 100644 --- a/sources/engine/Stride.Graphics/FastTextRenderer.cs +++ b/sources/engine/Stride.Graphics/FastTextRenderer.cs @@ -95,7 +95,7 @@ private unsafe void Initialize(GraphicsContext graphicsContext, int maxCharacter // Map and build the indice buffer indexBuffer = graphicsContext.Allocator.GetTemporaryBuffer(new BufferDescription(indexBufferSize, BufferFlags.IndexBuffer, GraphicsResourceUsage.Dynamic)); - var mappedIndices = graphicsContext.CommandList.MapSubresource(indexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, indexBufferSize); + var mappedIndices = graphicsContext.CommandList.MapSubResource(indexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, indexBufferSize); var indexPointer = mappedIndices.DataBox.DataPointer; var i = 0; @@ -110,7 +110,7 @@ private unsafe void Initialize(GraphicsContext graphicsContext, int maxCharacter *(int*)(indexPointer + IndexStride * i++) = c * 4 + 2; } - graphicsContext.CommandList.UnmapSubresource(mappedIndices); + graphicsContext.CommandList.UnmapSubResource(mappedIndices); indexBufferBinding = new IndexBufferBinding(Buffer.Index.New(graphicsContext.CommandList.GraphicsDevice, new ReadOnlySpan((void*)indexPointer, indexBufferSize)), true, indexBufferLength); @@ -194,7 +194,7 @@ public unsafe void End([NotNull] GraphicsContext graphicsContext) activeVertexBufferIndex = ++activeVertexBufferIndex >= VertexBufferCount ? 0 : activeVertexBufferIndex; // Map the vertex buffer to write to - var mappedVertexBuffer = graphicsContext.CommandList.MapSubresource(vertexBuffers[activeVertexBufferIndex], 0, MapMode.WriteDiscard); + var mappedVertexBuffer = graphicsContext.CommandList.MapSubResource(vertexBuffers[activeVertexBufferIndex], 0, MapMode.WriteDiscard); var mappedVertexData = new Span(mappedVertexBuffer.DataBox.DataPointer.ToPointer(), VertexBufferLength); // Note that we're using Vector4 instead of VertexPosition2DTexture for hardware acceleration, size of the two struct must match obviously // We don't have to clear the buffer since we're writing all used data through GraphicsFastTextRendererGenerateVertices @@ -211,7 +211,7 @@ public unsafe void End([NotNull] GraphicsContext graphicsContext) } // Unmap the vertex buffer - graphicsContext.CommandList.UnmapSubresource(mappedVertexBuffer); + graphicsContext.CommandList.UnmapSubResource(mappedVertexBuffer); // Update pipeline state pipelineState.State.SetDefaults(); diff --git a/sources/engine/Stride.Graphics/FillMode.cs b/sources/engine/Stride.Graphics/FillMode.cs index ba9c14a932..d82ceae462 100644 --- a/sources/engine/Stride.Graphics/FillMode.cs +++ b/sources/engine/Stride.Graphics/FillMode.cs @@ -1,26 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Determines the fill mode to use when rendering triangles. +/// +/// +/// This enumeration is part of a Rasterizer State object description (see ). +/// +[DataContract] +public enum FillMode : int { /// - ///

Determines the fill mode to use when rendering triangles.

+ /// Draw lines connecting the vertices. ///
- /// - ///

This enumeration is part of a rasterizer-state object description (see ).

- ///
- [DataContract] - public enum FillMode : int - { - /// - ///

Draw lines connecting the vertices. Adjacent vertices are not drawn.

- ///
- Wireframe = unchecked((int)2), + Wireframe = 2, - /// - ///

Fill the triangles formed by the vertices. Adjacent vertices are not drawn.

- ///
- Solid = unchecked((int)3), - } + /// + /// Fill the triangles formed by the vertices. + /// + Solid = 3 } diff --git a/sources/engine/Stride.Graphics/Font/CharacterSpecification.cs b/sources/engine/Stride.Graphics/Font/CharacterSpecification.cs index 7a090a0d01..fb4f35534f 100644 --- a/sources/engine/Stride.Graphics/Font/CharacterSpecification.cs +++ b/sources/engine/Stride.Graphics/Font/CharacterSpecification.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System; using System.Collections.Generic; using Stride.Core.Mathematics; @@ -10,7 +11,7 @@ namespace Stride.Graphics.Font /// /// A character of a specific font with a specific size. /// - internal class CharacterSpecification + internal class CharacterSpecification : IEquatable { public CharacterSpecification(char character, string fontName, Vector2 size, FontStyle style, FontAntiAliasMode antiAliasMode) { @@ -73,16 +74,47 @@ public char Character set { Glyph.Character = value; } } - public override bool Equals(object obj) + + /// + public override bool Equals(object? obj) { - return Equals(this, (CharacterSpecification)obj); + return obj is CharacterSpecification characterSpecification && Equals(characterSpecification); + } + + /// + public bool Equals(CharacterSpecification? other) + { + if (other is null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + return Character == other.Character + && FontName == other.FontName + && Size == other.Size + && Style == other.Style + && AntiAlias == other.AntiAlias; } - public static bool Equals(CharacterSpecification left, CharacterSpecification right) + /// + /// Determines whether two instances are equal. + /// + /// The first instance to compare. + /// The second instance to compare. + /// if the specified instances are equal; otherwise, . + public static bool Equals(CharacterSpecification? left, CharacterSpecification? right) { - return left.Character == right.Character && left.FontName == right.FontName && left.Size == right.Size && left.Style == right.Style && left.AntiAlias == right.AntiAlias; + if ((left is null) != (right is null)) + return false; + + if (left is not null) + return left.Equals(right); + + return false; } + /// public override int GetHashCode() { return Character.GetHashCode(); diff --git a/sources/engine/Stride.Graphics/Font/FontCacheManager.cs b/sources/engine/Stride.Graphics/Font/FontCacheManager.cs index 573996f598..a113edb18d 100644 --- a/sources/engine/Stride.Graphics/Font/FontCacheManager.cs +++ b/sources/engine/Stride.Graphics/Font/FontCacheManager.cs @@ -87,7 +87,7 @@ public void UploadCharacterBitmap(CommandList commandList, CharacterSpecificatio { var dataBox = new DataBox(character.Bitmap.Buffer, character.Bitmap.Pitch, character.Bitmap.Pitch * character.Bitmap.Rows); var region = new ResourceRegion(character.Glyph.Subrect.Left, character.Glyph.Subrect.Top, 0, character.Glyph.Subrect.Right, character.Glyph.Subrect.Bottom, 1); - commandList.UpdateSubresource(cacheTextures[0], 0, dataBox, region); + commandList.UpdateSubResource(cacheTextures[0], 0, dataBox, region); } // update the glyph data diff --git a/sources/engine/Stride.Graphics/FormatSupport.cs b/sources/engine/Stride.Graphics/FormatSupport.cs index c9f68dcd49..749bbd2ce2 100644 --- a/sources/engine/Stride.Graphics/FormatSupport.cs +++ b/sources/engine/Stride.Graphics/FormatSupport.cs @@ -1,161 +1,174 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Flags specifying which resources and features are supported for a given +/// for a . +/// +/// +/// For more information, see . +/// +[Flags] +public enum FormatSupport : int { + None = 0, + + /// + /// The format can be used as a Buffer resource. + /// + Buffer = 1, + + /// + /// The format can be used as a Vertex Buffer in input assembly. + /// + InputAssemblyVertexBuffer = 2, + + /// + /// The format can be used as an Index Buffer in input assembly. + /// + InputAssemblyIndexBuffer = 4, + + /// + /// The format can be used as a Stream-output Buffer. + /// + StreamOutputBuffer = 8, + + /// + /// The format can be used as a 1D Texture. + /// + Texture1D = 16, + + /// + /// The format can be used as a 2D Texture. + /// + Texture2D = 32, + + /// + /// The format can be used as a 3D Texture. + /// + Texture3D = 64, + + /// + /// The format can be used as a Cube Texture. + /// + TextureCube = 128, + + /// + /// The format can be loaded in a Shader. + /// + ShaderLoad = 256, + + /// + /// The format can be sampled in a Shader. + /// + ShaderSample = 512, + + /// + /// The format can be used for comparison sampling in a Shader. + /// + ShaderSampleComparison = 1024, + + /// + /// The format can be used for monochrome text sampling in a Shader. + /// + ShaderSampleMonoText = 2048, + + /// + /// The format supports mipmaps. + /// + Mip = 4096, + + /// + /// The format supports automatic mipmap generation. + /// + MipAutogen = 8192, + + /// + /// The format can be used as a Render Target. + /// + RenderTarget = 16384, + /// - ///

Which resources are supported for a given format and given device (see and ).

- ///
- [Flags] - public enum FormatSupport : int - { - /// - /// No documentation. - /// - Buffer = unchecked((int)1), - - /// - /// No documentation. - /// - InputAssemblyVertexBuffer = unchecked((int)2), - - /// - /// No documentation. - /// - InputAssemblyIndexBuffer = unchecked((int)4), - - /// - /// No documentation. - /// - StreamOutputBuffer = unchecked((int)8), - - /// - /// No documentation. - /// - Texture1D = unchecked((int)16), - - /// - /// No documentation. - /// - Texture2D = unchecked((int)32), - - /// - /// No documentation. - /// - Texture3D = unchecked((int)64), - - /// - /// No documentation. - /// - TextureCube = unchecked((int)128), - - /// - /// No documentation. - /// - ShaderLoad = unchecked((int)256), - - /// - /// No documentation. - /// - ShaderSample = unchecked((int)512), - - /// - /// No documentation. - /// - ShaderSampleComparison = unchecked((int)1024), - - /// - /// No documentation. - /// - ShaderSampleMonoText = unchecked((int)2048), - - /// - /// No documentation. - /// - Mip = unchecked((int)4096), - - /// - /// No documentation. - /// - MipAutogen = unchecked((int)8192), - - /// - /// No documentation. - /// - RenderTarget = unchecked((int)16384), - - /// - /// No documentation. - /// - Blendable = unchecked((int)32768), - - /// - /// No documentation. - /// - DepthStencil = unchecked((int)65536), - - /// - /// No documentation. - /// - CpuLockable = unchecked((int)131072), - - /// - /// No documentation. - /// - MultisampleResolve = unchecked((int)262144), - - /// - /// No documentation. - /// - Display = unchecked((int)524288), - - /// - /// No documentation. - /// - CastWithinBitLayout = unchecked((int)1048576), - - /// - /// No documentation. - /// - MultisampleRendertarget = unchecked((int)2097152), - - /// - /// No documentation. - /// - MultisampleLoad = unchecked((int)4194304), - - /// - /// No documentation. - /// - ShaderGather = unchecked((int)8388608), - - /// - /// No documentation. - /// - BackBufferCast = unchecked((int)16777216), - - /// - /// No documentation. - /// - TypedUnorderedAccessView = unchecked((int)33554432), - - /// - /// No documentation. - /// - ShaderGatherComparison = unchecked((int)67108864), - - DecoderOutput = 134217728, - - VideoProcessorOutput = 268435456, - - VideoProcessorInput = 536870912, - - VideoEncoder = 1073741824, - - /// - /// None. - /// - None = unchecked((int)0), - } + /// The format supports blending as a Render Target. + /// + Blendable = 32768, + + /// + /// The format can be used as a Depth-Stencil Buffer. + /// + DepthStencil = 65536, + + /// + /// The format can be locked for CPU access. + /// + CpuLockable = 131072, + + /// + /// The format supports multisample resolve operations. + /// + MultisampleResolve = 262144, + + /// + /// The format can be used for display scan-out (to present to the screen). + /// + Display = 524288, + + /// + /// The format can be cast within a bit layout. + /// + CastWithinBitLayout = 1048576, + + /// + /// The format can be used as a multisample Render Target. + /// + MultisampleRendertarget = 2097152, + + /// + /// The format supports multisample load operations. + /// + MultisampleLoad = 4194304, + + /// + /// The format supports gather operations in a Shader. + /// + ShaderGather = 8388608, + + /// + /// The format can be cast to a Back-Buffer. + /// + BackBufferCast = 16777216, + + /// + /// The format supports typed Unordered Access Views. + /// + TypedUnorderedAccessView = 33554432, + + /// + /// The format supports gather comparison operations in a Shader. + /// + ShaderGatherComparison = 67108864, + + /// + /// The format can be used as a decoder output. + /// + DecoderOutput = 134217728, + + /// + /// The format can be used as a video processor output. + /// + VideoProcessorOutput = 268435456, + + /// + /// The format can be used as a video processor input. + /// + VideoProcessorInput = 536870912, + + /// + /// The format can be used as a video encoder. + /// + VideoEncoder = 1073741824 } diff --git a/sources/engine/Stride.Graphics/GraphicsAdapter.cs b/sources/engine/Stride.Graphics/GraphicsAdapter.cs index 46b1db9eba..e47ab93dc5 100644 --- a/sources/engine/Stride.Graphics/GraphicsAdapter.cs +++ b/sources/engine/Stride.Graphics/GraphicsAdapter.cs @@ -1,40 +1,39 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; using Stride.Core; namespace Stride.Graphics { /// - /// This class represents a graphics adapter. + /// Represents a display subsystem (including one or more GPUs, DACs and video memory). + /// A display subsystem is often referred to as a video card, however, on some machines the display subsystem is part of the motherboard. /// + /// + /// To enumerate the s that are available in the system, see . + /// public sealed partial class GraphicsAdapter : ComponentBase { - private readonly GraphicsOutput[] outputs; + private readonly GraphicsOutput[] graphicsOutputs; /// - /// Gets the attached to this adapter + /// Gets the s attached to this adapter. /// - /// The attached to this adapter. - public GraphicsOutput[] Outputs - { - get - { - return outputs; - } - } + public ReadOnlySpan Outputs => graphicsOutputs; + + /// + /// Gets the unique identifier of this . + /// + public long AdapterUid { get; } + /// - /// Return the description of this adapter + /// Returns the description of this . /// - /// public override string ToString() { return Description; } - - /// - /// The unique id in the form of string of this device - /// - public string AdapterUid { get; internal set; } } } diff --git a/sources/engine/Stride.Graphics/GraphicsAdapterFactory.cs b/sources/engine/Stride.Graphics/GraphicsAdapterFactory.cs index 85f26f74dd..517e76247d 100644 --- a/sources/engine/Stride.Graphics/GraphicsAdapterFactory.cs +++ b/sources/engine/Stride.Graphics/GraphicsAdapterFactory.cs @@ -1,22 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; using Stride.Core; namespace Stride.Graphics { /// - /// Factory for . + /// Static factory for obtaining the available s in the system. /// public static partial class GraphicsAdapterFactory { - private static readonly object StaticLock = new object(); - private static ObjectCollector staticCollector = new ObjectCollector(); - private static bool isInitialized = false; + private static readonly object StaticLock = new(); + + private static ObjectCollector staticCollector; + + private static bool isInitialized; private static GraphicsAdapter[] adapters; private static GraphicsAdapter defaultAdapter; /// - /// Initializes the GraphicsAdapter. On Desktop and WinRT, this is done statically. + /// Initializes the . On Desktop and WinRT, this is done statically. /// public static void Initialize() { @@ -31,7 +35,8 @@ public static void Initialize() } /// - /// Perform a and to re-initialize all adapters informations. + /// s and s the + /// to re-initialize all adapters informations. /// public static void Reset() { @@ -43,7 +48,7 @@ public static void Reset() } /// - /// Dispose all statically cached value by this instance. + /// Dispose all statically cached adapter information in the . /// public static void Dispose() { @@ -57,9 +62,9 @@ public static void Dispose() } /// - /// Collection of available adapters on the system. + /// Gets a collection of the available s on the system. /// - public static GraphicsAdapter[] Adapters + public static ReadOnlySpan Adapters { get { @@ -72,9 +77,12 @@ public static GraphicsAdapter[] Adapters } /// - /// Gets the default adapter. This property can be null. + /// Gets the default . /// - public static GraphicsAdapter Default + /// + /// The default . This property can be . + /// + public static GraphicsAdapter DefaultAdapter { get { diff --git a/sources/engine/Stride.Graphics/GraphicsDevice.cs b/sources/engine/Stride.Graphics/GraphicsDevice.cs index 5773789da0..1baa9c279d 100644 --- a/sources/engine/Stride.Graphics/GraphicsDevice.cs +++ b/sources/engine/Stride.Graphics/GraphicsDevice.cs @@ -1,120 +1,204 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; using System.Threading; using Stride.Core; -using Stride.Core.Diagnostics; -using Stride.Core.Mathematics; -using Stride.Rendering; namespace Stride.Graphics { /// - /// Used for GPU resources creation (buffers, textures, states, shaders), and manipulations. + /// A virtual adapter that can be used for creating GPU resources (buffers, textures, states, shaders, etc), + /// and to manipulate s. /// public partial class GraphicsDevice : ComponentBase { - internal readonly Dictionary CachedPipelineStates = new Dictionary(); - - internal readonly Dictionary CachedSamplerStates = new Dictionary(); + internal readonly Dictionary CachedPipelineStates = []; + internal readonly Dictionary CachedSamplerStates = []; /// - /// Gets the features supported by this graphics device. + /// Gets the features supported by the Graphics Device. /// public GraphicsDeviceFeatures Features; - internal HashSet Resources = new HashSet(); + /// + /// The set of Graphics Resources currently managed by the Graphics Device. + /// + internal HashSet Resources = []; - internal readonly bool NeedWorkAroundForUpdateSubResource; + /// + /// The currently active Effect being used by the Graphics Device. + /// internal Effect CurrentEffect; - private readonly List sharedDataToDispose = new List(); + private readonly List sharedDataToDispose = []; private readonly Dictionary sharedDataPerDevice; + private GraphicsPresenter presenter; + /// + /// The default Pipeline State object used by the Graphics Device. + /// internal PipelineState DefaultPipelineState; + /// + /// The main Command List used by the Graphics Device. + /// internal CommandList InternalMainCommandList; - internal PrimitiveQuad PrimitiveQuad; + /// + /// A cached primitive object that can be used to draw quads (rectangles composed of two triangles). + /// + internal PrimitiveQuad PrimitiveQuad; // TODO: This is not a quad, but a fullscreen triangle! Maybe this class should be renamed? + private ColorSpace colorSpace; - public uint FrameTriangleCount; + /// + /// The number of triangles drawn in the last frame. + /// + public uint FrameTriangleCount; // TODO: Public mutable fields? + /// + /// The number of draw calls made in the last frame. + /// public uint FrameDrawCalls; + private long bufferMemory; private long textureMemory; /// - /// Gets the GPU memory currently allocated to buffers in bytes. + /// Gets the amount of GPU memory currently allocated to Buffers, in bytes. /// public long BuffersMemory => Interlocked.Read(ref bufferMemory); /// - /// Gets the GPU memory currently allocated to texture in bytes. + /// Gets the amount of GPU memory currently allocated to Textures, in bytes. /// public long TextureMemory => Interlocked.Read(ref textureMemory); /// - /// Gets the type of the platform that graphics device is using. + /// Gets the graphics platform (and the graphics API) the Graphics Device is using. /// public static GraphicsPlatform Platform => GraphicPlatform; + /// + /// Gets a string that identifies the underlying device used by the Graphics Device to render. + /// + /// + /// In the case of Direct3D and Vulkan, for example, this will return the name of the Graphics Adapter + /// (e.g. "nVIDIA GeForce RTX 2080"). Other platforms may return a different string. + /// public string RendererName => GetRendererName(); + /// + private partial string GetRendererName(); + + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The graphics adapter. - /// The graphics profile. - /// The device creation flags. - /// The window handle. - protected GraphicsDevice(GraphicsAdapter adapter, GraphicsProfile[] profile, DeviceCreationFlags deviceCreationFlags, WindowHandle windowHandle) + /// The physical Graphics Adapter for which to create a Graphics Device. + /// + /// + /// A list of the graphics profiles to try, in order of preference. This parameter cannot be , + /// but if an empty array is passed, the default fallback profiles will be used. + /// + /// + /// The default fallback profiles are: , , + /// , , , and + /// . + /// + /// + /// + /// A combination of flags that determines how the Graphics Device will be created. + /// + /// + /// The specifying the window the Graphics Device will present to, + /// or if the device should not depend on a window. + /// + /// is . + /// is . + protected GraphicsDevice(GraphicsAdapter adapter, GraphicsProfile[] graphicsProfiles, DeviceCreationFlags creationFlags, WindowHandle windowHandle) { // Create shared data - sharedDataPerDevice = new Dictionary(); + sharedDataPerDevice = []; - Recreate(adapter, profile, deviceCreationFlags, windowHandle); + Recreate(adapter, graphicsProfiles, creationFlags, windowHandle); // Helpers PrimitiveQuad = new PrimitiveQuad(this); } - public void Recreate(GraphicsAdapter adapter, GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, WindowHandle windowHandle) + + /// + /// Tries to create or reinitialize the Graphics Device. + /// + /// The physical Graphics Adapter for which to recreate the Graphics Device. + /// + /// + /// A list of the graphics profiles to try, in order of preference. This parameter cannot be , + /// but if an empty array is passed, the default fallback profiles will be used. + /// + /// + /// The default fallback profiles are: , , + /// , , , and + /// . + /// + /// + /// + /// A combination of flags that determines how the Graphics Device will be created. + /// + /// + /// The specifying the window the Graphics Device will present to, + /// or if the device should not depend on a window. + /// + /// is . + /// is . + public void Recreate(GraphicsAdapter adapter, GraphicsProfile[] graphicsProfiles, DeviceCreationFlags creationFlags, WindowHandle windowHandle) { - if (adapter == null) throw new ArgumentNullException("adapter"); - if (graphicsProfiles == null) throw new ArgumentNullException("graphicsProfiles"); + ArgumentNullException.ThrowIfNull(adapter); + ArgumentNullException.ThrowIfNull(graphicsProfiles); // TODO: Why different from Array.Empty? Adapter = adapter; - IsDebugMode = (deviceCreationFlags & DeviceCreationFlags.Debug) != 0; + IsDebugMode = creationFlags.HasFlag(DeviceCreationFlags.Debug); // Default fallback if (graphicsProfiles.Length == 0) - graphicsProfiles = new[] { GraphicsProfile.Level_11_0, GraphicsProfile.Level_10_1, GraphicsProfile.Level_10_0, GraphicsProfile.Level_9_3, GraphicsProfile.Level_9_2, GraphicsProfile.Level_9_1 }; + graphicsProfiles = [ GraphicsProfile.Level_11_0, GraphicsProfile.Level_10_1, GraphicsProfile.Level_10_0, GraphicsProfile.Level_9_3, GraphicsProfile.Level_9_2, GraphicsProfile.Level_9_1 ]; // Initialize this instance - InitializePlatformDevice(graphicsProfiles, deviceCreationFlags, windowHandle); + InitializePlatformDevice(graphicsProfiles, creationFlags, windowHandle); - // Create a new graphics device + // Checks the features supported by the new Graphics Device Features = new GraphicsDeviceFeatures(this); + // Initialize the internal states of the new Graphics Device SamplerStates = new SamplerStateFactory(this); var defaultPipelineStateDescription = new PipelineStateDescription(); defaultPipelineStateDescription.SetDefaults(); AdjustDefaultPipelineStateDescription(ref defaultPipelineStateDescription); - DefaultPipelineState = PipelineState.New(this, ref defaultPipelineStateDescription); + DefaultPipelineState = PipelineState.New(this, defaultPipelineStateDescription); InitializePostFeatures(); } + /// + /// Initialize the platform-specific implementation of the Graphics Device. + /// + /// A non- list of the graphics profiles to try, in order of preference. + /// The device creation flags. + /// The window handle. + private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle); + + /// + /// Initializes the platform-specific features of the Graphics Device once it has been fully initialized. + /// + private unsafe partial void InitializePostFeatures(); + + /// protected override void Destroy() { - // Clear shared data - for (int index = sharedDataToDispose.Count - 1; index >= 0; index--) - sharedDataToDispose[index].Dispose(); - sharedDataPerDevice.Clear(); - SamplerStates.Dispose(); SamplerStates = null; @@ -122,6 +206,8 @@ protected override void Destroy() PrimitiveQuad.Dispose(); + DisposeSharedData(); + // Notify listeners Disposing?.Invoke(this, EventArgs.Empty); @@ -130,7 +216,7 @@ protected override void Destroy() { foreach (var resource in Resources) { - // Destroy leftover resources (note: should not happen if ResumeManager.OnDestroyed has properly been called) + // Destroy leftover resources (NOTE: Thus should not happen if ResumeManager.OnDestroyed has properly been called) if (resource.LifetimeState != GraphicsResourceLifetimeState.Destroyed) { resource.OnDestroyed(); @@ -143,158 +229,216 @@ protected override void Destroy() Resources.Clear(); } + // Destroy all the associated resources first (Command Lists, Pipeline States, etc) + base.Destroy(); + DestroyPlatformDevice(); - base.Destroy(); + // + // Disposes all the shared data created by this Graphics Device. + // + void DisposeSharedData() + { + // Disposes in reverse order so that the last created data is disposed first + for (int index = sharedDataToDispose.Count - 1; index >= 0; index--) + sharedDataToDispose[index].Dispose(); + + sharedDataToDispose.Clear(); + sharedDataPerDevice.Clear(); + } } /// - /// Occurs while this component is disposing and before it is disposed. + /// Releases the platform-specific Graphics Device and all its associated resources. + /// + protected partial void DestroyPlatformDevice(); + + + /// + /// Occurs while this component is disposing but before it is disposed. /// public event EventHandler Disposing; /// - /// A delegate called to create shareable data. See remarks. + /// A delegate called to create shareable data. /// /// Type of the data to create. /// A new instance of the data to share. /// - /// Because this method is being called from a lock region, this method should not be time consuming. + /// Because this method is being called from a region, this method should not be time consuming. /// public delegate T CreateSharedData(GraphicsDevice device) where T : class, IDisposable; /// - /// Gets the adapter this instance is attached to. + /// Gets the physical Graphics Adapter the Graphics Device is attached to. /// public GraphicsAdapter Adapter { get; private set; } /// - /// Gets a value indicating whether this instance is in debug mode. + /// Gets a value indicating whether the Graphics Device is in "Debug mode". /// /// - /// true if this instance is debug; otherwise, false. + /// if the Graphics Device is initialized in "Debug mode"; otherwise, . /// public bool IsDebugMode { get; private set; } /// - /// Indicates wether this device allows for concurrent building and deferred submission of CommandLists + /// Gets a value indicating wether the Graphics Device allows for concurrent building and deferred submission + /// of s. /// /// - /// true if this instance is deferred; otherwise, false. + /// if the Graphics Device allows deferred execution; otherwise, . /// public bool IsDeferred { get; private set; } /// - /// Gets a value indicating whether this instance supports GPU markers and profiling. + /// Gets a value indicating whether the Graphics Device supports GPU markers and profiling. /// + /// + /// if the Graphics Device allows profiling and creating GPU markers; otherwise, . + /// public bool IsProfilingSupported { get; private set; } /// - /// Gets the default color space. + /// Gets or sets the default color space of the Graphics Device. /// /// The default color space. public ColorSpace ColorSpace { - get { return Features.HasSRgb ? colorSpace : ColorSpace.Gamma; } - set - { - colorSpace = value; - } + get => Features.HasSRgb ? colorSpace : ColorSpace.Gamma; + set => colorSpace = value; } /// - /// Gets or sets the current presenter used to display the frame. + /// Gets or sets the current presenter used to display frames with the Graphics Device. /// - /// The current presenter. + /// The current Graphics Presenter. public virtual GraphicsPresenter Presenter { - get - { - return presenter; - } - set - { - presenter = value; - } + get => presenter; + set => presenter = value; } /// - /// Gets the factory. + /// Gets the factory that can be used to retrieve commonly used Sampler States. /// - /// - /// The factory. - /// public SamplerStateFactory SamplerStates { get; private set; } + // TODO: Unused? /// - /// Gets the index of the thread. + /// Gets the index of the thread. /// - /// The index of the thread. public int ThreadIndex { get; internal set; } /// - /// Gets the shader profile. + /// Gets the graphics profile the Graphics Device is using, which determines the available features. /// - /// The shader profile. + /// The graphics profile. internal GraphicsProfile? ShaderProfile { get; set; } + /// - /// Initializes a new instance of the class. + /// Creates a new . /// - /// The creation flags. - /// The graphics profiles. - /// - /// An instance of - /// + /// + /// A combination of flags that determines how the Graphics Device will be created. + /// + /// + /// + /// A list of the graphics profiles to try, in order of preference. This parameter cannot be , + /// but if an empty array is passed, the default fallback profiles will be used. + /// + /// + /// The default fallback profiles are: , , + /// , , , and + /// . + /// + /// + /// The new instance of . + /// is . public static GraphicsDevice New(DeviceCreationFlags creationFlags = DeviceCreationFlags.None, params GraphicsProfile[] graphicsProfiles) { - return New(GraphicsAdapterFactory.Default, creationFlags, graphicsProfiles); + return New(GraphicsAdapterFactory.DefaultAdapter, creationFlags, graphicsProfiles); } /// - /// Initializes a new instance of the class. + /// Creates a new . /// - /// The adapter. - /// The creation flags. - /// The graphics profiles. - /// An instance of + /// + /// The Graphics Adapter the new device will use, or to use the system's default adapter. + /// + /// + /// A combination of flags that determines how the Graphics Device will be created. + /// + /// + /// + /// A list of the graphics profiles to try, in order of preference. This parameter cannot be , + /// but if an empty array is passed, the default fallback profiles will be used. + /// + /// + /// The default fallback profiles are: , , + /// , , , and + /// . + /// + /// + /// The new instance of . + /// is . + /// is . public static GraphicsDevice New(GraphicsAdapter adapter, DeviceCreationFlags creationFlags = DeviceCreationFlags.None, params GraphicsProfile[] graphicsProfiles) { - return new GraphicsDevice(adapter ?? GraphicsAdapterFactory.Default, graphicsProfiles, creationFlags, null); + return new GraphicsDevice(adapter ?? GraphicsAdapterFactory.DefaultAdapter, graphicsProfiles, creationFlags, windowHandle: null); } /// - /// Initializes a new instance of the class. + /// Creates a new . /// - /// The adapter. - /// The creation flags. - /// The window handle. - /// The graphics profiles. - /// An instance of + /// + /// The Graphics Adapter the new device will use, or to use the system's default adapter. + /// + /// + /// A combination of flags that determines how the Graphics Device will be created. + /// + /// + /// The specifying the window the Graphics Device will present to, + /// or if the device should not depend on a window. + /// + /// + /// + /// A list of the graphics profiles to try, in order of preference. This parameter cannot be , + /// but if an empty array is passed, the default fallback profiles will be used. + /// + /// + /// The default fallback profiles are: , , + /// , , , and + /// . + /// + /// + /// The new instance of . + /// is . + /// is . public static GraphicsDevice New(GraphicsAdapter adapter, DeviceCreationFlags creationFlags = DeviceCreationFlags.None, WindowHandle windowHandle = null, params GraphicsProfile[] graphicsProfiles) { - return new GraphicsDevice(adapter ?? GraphicsAdapterFactory.Default, graphicsProfiles, creationFlags, windowHandle); + return new GraphicsDevice(adapter ?? GraphicsAdapterFactory.DefaultAdapter, graphicsProfiles, creationFlags, windowHandle); } + /// - /// Gets a shared data for this device context with a delegate to create the shared data if it is not present. + /// Gets a shared data object for the Graphics Device context with a delegate to create the shared data if it is not present. /// - /// Type of the shared data to get/create. - /// Type of the data to share. - /// The key of the shared data. - /// The shared data creator. + /// Type of the shared data to get or create. + /// The key that identifies the shared data. + /// A delegate that will be called to create the shared data. /// - /// An instance of the shared data. The shared data will be disposed by this instance. + /// An instance of the shared data. It will be disposed by this instance. /// public T GetOrCreateSharedData(object key, CreateSharedData sharedDataCreator) where T : class, IDisposable { lock (sharedDataPerDevice) { - IDisposable localValue; - if (!sharedDataPerDevice.TryGetValue(key, out localValue)) + if (!sharedDataPerDevice.TryGetValue(key, out IDisposable localValue)) { localValue = sharedDataCreator(this); - if (localValue == null) + if (localValue is null) { return null; } @@ -302,18 +446,41 @@ public T GetOrCreateSharedData(object key, CreateSharedData sharedDataCrea sharedDataToDispose.Add(localValue); sharedDataPerDevice.Add(key, localValue); } - return (T)localValue; + return (T) localValue; } } + /// + /// Adds or subtracts to the texture memory amount the Graphics Device has allocated. + /// + /// The texture memory delta: positive to increase, negative to decrease. internal void RegisterTextureMemoryUsage(long memoryChange) { Interlocked.Add(ref textureMemory, memoryChange); } + /// + /// Adds or subtracts to the buffer memory amount the Graphics Device has allocated. + /// + /// The buffer memory delta: positive to increase, negative to decrease. internal void RegisterBufferMemoryUsage(long memoryChange) { Interlocked.Add(ref bufferMemory, memoryChange); } + + /// + /// Makes platform-specific adjustments to the Pipeline State objects created by the Graphics Device. + /// + /// A Pipeline State description that can be modified and adjusted. + private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription); + + /// + /// Tags a Graphics Resource as no having alive references, meaning it should be safe to dispose it + /// or discard its contents during the next or SetData operation. + /// + /// + /// A object identifying the Graphics Resource along some related allocation information. + /// + internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink); } } diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceException.cs b/sources/engine/Stride.Graphics/GraphicsDeviceException.cs new file mode 100644 index 0000000000..435fa97cbf --- /dev/null +++ b/sources/engine/Stride.Graphics/GraphicsDeviceException.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace Stride.Graphics; + +/// +/// An exception that is thrown when a Graphics Device operation fails. +/// +/// +public class GraphicsDeviceException : GraphicsException +{ + private const string DefaultMessage = "An error occurred in the Graphics Device."; + + /// + /// Gets the status of the Graphics Device when the exception occurred. + /// + public GraphicsDeviceStatus Status { get; } = GraphicsDeviceStatus.Normal; + + + /// + /// Initializes a new instance of the class with a default message and status. + /// + public GraphicsDeviceException() : base(DefaultMessage) { } + + /// + /// Initializes a new instance of the class with a specified error message and status. + /// + /// + /// The error message that explains the reason for the exception. + /// Specify to use the default message. + /// + /// The status of the Graphics Device when the exception occurred. + public GraphicsDeviceException(string? message, GraphicsDeviceStatus status = GraphicsDeviceStatus.Normal) + : base(message ?? DefaultMessage) + { + Status = status; + } + + /// + /// Initializes a new instance of the class with a specified error message, status, + /// and a reference to the inner exception that is the cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// Specify to use the default message. + /// + /// + /// The exception that is the cause of the current exception, or a reference if no inner exception is specified. + /// + public GraphicsDeviceException(string? message, Exception? innerException, GraphicsDeviceStatus status = GraphicsDeviceStatus.Normal) + : base(message ?? DefaultMessage, innerException) + { + Status = status; + } +} diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs b/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs index e2cc76df86..d47d13cbdc 100644 --- a/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs +++ b/sources/engine/Stride.Graphics/GraphicsDeviceExtensions.cs @@ -2,100 +2,153 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; +using System.Runtime.CompilerServices; using Stride.Core.Mathematics; + using Stride.Rendering; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a set of extension methods for the class. +/// +public static class GraphicsDeviceExtensions { + #region DrawQuad / DrawTexture Helpers + + // TODO: Should this be named DrawFullScreen or DrawFullScreenTriangle instead? + /// - /// Extensions for the + /// Draws a full-screen triangle with the specified effect. /// - public static class GraphicsDeviceExtensions + /// The graphics context used for drawing. + /// The Effect instance to apply when drawing the triangle. + /// is . + public static void DrawQuad(this GraphicsContext graphicsContext, EffectInstance effectInstance) { - /// - /// Draws a fullscreen quad with the specified effect and parameters. - /// - /// The graphics context used for drawing. - /// The effect instance to apply when drawing the quad. - /// Thrown when is null. - public static void DrawQuad(this GraphicsContext graphicsContext, EffectInstance effectInstance) - { - if (effectInstance == null) throw new ArgumentNullException("effectInstance"); + ArgumentNullException.ThrowIfNull(effectInstance); - // Draw a full screen quad - graphicsContext.CommandList.GraphicsDevice.PrimitiveQuad.Draw(graphicsContext, effectInstance); - } + // Draw a full screen triangle using the provided effect instance. + graphicsContext.CommandList.GraphicsDevice.PrimitiveQuad.Draw(graphicsContext, effectInstance); + } - #region DrawQuad/DrawTexture Helpers - /// - /// Draws a full screen quad. An must be applied before calling this method. - /// - public static void DrawQuad(this CommandList commandList) - { - commandList.GraphicsDevice.PrimitiveQuad.Draw(commandList); - } + /// + /// Draws a full-screen triangle with the currently applied . + /// + /// The Command List to use to draw the full-screen triangle. + public static void DrawQuad(this CommandList commandList) + { + commandList.GraphicsDevice.PrimitiveQuad.Draw(commandList); + } - /// - /// Draws a fullscreen texture using a sampler. See to learn how to use it. - /// - /// The texture. Expecting an instance of . - /// The flag to apply effect states. - public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, BlendStateDescription? blendState = null) - { - graphicsContext.DrawTexture(texture, null, Color4.White, blendState); - } + /// + /// Draws a full-screen Texture using a Sampler. + /// + /// The graphics context used for drawing. + /// The Texture to draw. + /// + /// An optional Blend State to use when drawing the Texture. + /// Specify to use . + /// + public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, BlendStateDescription? blendState = null) + { + graphicsContext.DrawTexture(texture, samplerState: null, color: Color4.White, blendState); + } - /// - /// Draws a fullscreen texture using the specified sampler. See to learn how to use it. - /// - /// The texture. Expecting an instance of . - /// The sampler. - /// The flag to apply effect states. - public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, SamplerState sampler, BlendStateDescription? blendState = null) - { - graphicsContext.DrawTexture(texture, sampler, Color4.White, blendState); - } + /// + /// Draws a full-screen Texture using the specified sampler. + /// + /// The graphics context used for drawing. + /// The Texture to draw. + /// + /// The Sampler State to use for sampling the texture. + /// Specify to use the default Sampler State . + /// + /// + /// An optional Blend State to use when drawing the Texture. + /// Specify to use . + /// + public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, SamplerState samplerState, BlendStateDescription? blendState = null) + { + graphicsContext.DrawTexture(texture, samplerState, Color4.White, blendState); + } - /// - /// Draws a fullscreen texture using a sampler - /// and the texture color multiplied by a custom color. See to learn how to use it. - /// - /// The texture. Expecting an instance of . - /// The color. - /// The flag to apply effect states. - public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, Color4 color, BlendStateDescription? blendState = null) - { - graphicsContext.DrawTexture(texture, null, color, blendState); - } + /// + /// Draws a full-screen tinted Texture using a Sampler. + /// + /// The graphics context used for drawing. + /// The Texture to draw. + /// + /// The color to tint the Texture with. The final color will be the texture color multiplied by the specified color. + /// + /// + /// An optional Blend State to use when drawing the Texture. + /// Specify to use . + /// + public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, Color4 color, BlendStateDescription? blendState = null) + { + graphicsContext.DrawTexture(texture, samplerState: null, color, blendState); + } - /// - /// Draws a fullscreen texture using the specified sampler - /// and the texture color multiplied by a custom color. See to learn how to use it. - /// - /// The texture. Expecting an instance of . - /// The sampler. - /// The color. - /// The flag to apply effect states. - public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, SamplerState sampler, Color4 color, BlendStateDescription? blendState = null) - { - graphicsContext.CommandList.GraphicsDevice.PrimitiveQuad.Draw(graphicsContext, texture, sampler, color, blendState); - } - #endregion + /// + /// Draws a full-screen tinted Texture using the specified Sampler. + /// + /// The graphics context used for drawing. + /// The Texture to draw. + /// + /// The Sampler State to use for sampling the texture. + /// Specify to use the default Sampler State . + /// + /// + /// The color to tint the Texture with. The final color will be the texture color multiplied by the specified color. + /// + /// + /// An optional Blend State to use when drawing the Texture. + /// Specify to use . + /// + public static void DrawTexture(this GraphicsContext graphicsContext, Texture texture, SamplerState samplerState, Color4 color, BlendStateDescription? blendState = null) + { + graphicsContext.CommandList.GraphicsDevice.PrimitiveQuad.Draw(graphicsContext, texture, samplerState, color, blendState); + } - public static Texture GetSharedWhiteTexture(this GraphicsDevice device) - { - return device.GetOrCreateSharedData("WhiteTexture", CreateWhiteTexture); - } + #endregion - private static Texture CreateWhiteTexture(GraphicsDevice device) + /// + /// Gets or creates a shared 2x2 white Texture for the specified Graphics Device. + /// + /// The Graphics Device for which to retrieve the shared white Texture. + /// A consisting of 2x2 white pixels. + public static Texture GetSharedWhiteTexture(this GraphicsDevice device) + { + return device.GetOrCreateSharedData("WhiteTexture", CreateWhiteTexture); + + // + // Creates a 2x2 white Texture that can be shared across multiple graphics contexts. + // + static unsafe Texture CreateWhiteTexture(GraphicsDevice device) { const int Size = 2; - var whiteData = new Color[Size * Size]; - for (int i = 0; i < Size * Size; i++) - whiteData[i] = Color.White; - return Texture.New2D(device, Size, Size, PixelFormat.R8G8B8A8_UNorm, whiteData); + Span textureData = stackalloc Color[Size * Size]; + textureData.Fill(Color.White); + + Unsafe.SkipInit(out DataBox dataBox); + fixed (Color* textureDataPtr = textureData) + { + var rowPitch = Size * sizeof(Color); + var slicePitch = rowPitch * Size; + dataBox = new DataBox((nint) textureDataPtr, rowPitch, slicePitch); + } + + var texture = device.IsDebugMode + ? new Texture(device, "WhiteTexture") + : new Texture(device); + + var textureDescription = TextureDescription.New2D(Size, Size, PixelFormat.R8G8B8A8_UNorm); + texture.InitializeFrom(textureDescription, [ dataBox ]); // TODO: Use the Span overload when available + + return texture; } } } diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceFactory.cs b/sources/engine/Stride.Graphics/GraphicsDeviceFactory.cs deleted file mode 100644 index 5291885660..0000000000 --- a/sources/engine/Stride.Graphics/GraphicsDeviceFactory.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics -{ - /// - /// Factory for . - /// - /*public abstract class GraphicsDeviceFactory : ComponentBase - { - private GraphicsAdapterFactory AdapterFactory { get; set; } - - internal GraphicsDeviceFactory(GraphicsAdapterFactory adapterFactory) - { - AdapterFactory = adapterFactory; - } - - /// - /// Initializes a new instance of the class using the default GraphicsAdapter - /// and the Level10 . - /// - /// An instance of - public GraphicsDevice New() - { - return New(GraphicsProfile.Level10); - } - - /// - /// Initializes a new instance of the class using the default GraphicsAdapter. - /// - /// The graphics profile. - /// An instance of - public GraphicsDevice New(GraphicsProfile graphicsProfile) - { - return New(null, graphicsProfile); - } - - /// - /// Initializes a new instance of the class using the Level10 . - /// - /// The GraphicsAdapter to use with this graphics device. - /// An instance of - public GraphicsDevice New(GraphicsAdapter adapter) - { - return New(adapter, GraphicsProfile.Level10); - } - - /// - /// Initializes a new instance of the class. - /// - /// The GraphicsAdapter to use with this graphics device. - /// The graphics profile. - /// An instance of - public GraphicsDevice New(GraphicsAdapter adapter, GraphicsProfile graphicsProfile) - { - return New(adapter ?? AdapterFactory.Default, graphicsProfile, null); - } - - /// - /// Initializes a new instance of the class. - /// - /// The graphics profile. - /// The presentation parameters. - /// An instance of - public GraphicsDevice New(GraphicsProfile graphicsProfile, - PresentationParameters presentationParameters) - { - return New(AdapterFactory.Default, graphicsProfile, presentationParameters); - } - - /// - /// Initializes a new instance of the class. - /// - /// The GraphicsAdapter to use with this graphics device. null is for default. - /// The graphics profile. - /// The presentation parameters. - /// An instance of - public abstract GraphicsDevice New(GraphicsAdapter adapter, GraphicsProfile graphicsProfile, - PresentationParameters presentationParameters); - }*/ -} diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceFeatures.cs b/sources/engine/Stride.Graphics/GraphicsDeviceFeatures.cs index 18c8b42f01..ecd769e5b0 100644 --- a/sources/engine/Stride.Graphics/GraphicsDeviceFeatures.cs +++ b/sources/engine/Stride.Graphics/GraphicsDeviceFeatures.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,137 +21,199 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; -using System.Collections.Generic; +namespace Stride.Graphics; -namespace Stride.Graphics +/// +/// Contains information about the general features supported by a , as well as +/// supported features specific to a particular pixel format or data format. +/// +/// +/// To obtain information about the supported features for a particular format, use the indexer. +/// +public partial struct GraphicsDeviceFeatures { + private readonly FeaturesPerFormat[] mapFeaturesPerFormat; + + /// + /// The requested profile when the was created. + /// + /// + public GraphicsProfile RequestedProfile; + /// - /// Features supported by a . + /// The current profile of the current , which determines its supported features. /// /// - /// This class gives also features for a particular format, using the operator this[dxgiFormat] on this structure. + /// This may differ from if the could not be created + /// with that requested profile. This one represents the closest supported profile. /// - public partial struct GraphicsDeviceFeatures - { - private readonly FeaturesPerFormat[] mapFeaturesPerFormat; + /// + public GraphicsProfile CurrentProfile; - /// - /// Features level of the current device. - /// - public GraphicsProfile RequestedProfile; - /// - /// Features level of the current device. - /// - public GraphicsProfile CurrentProfile; + /// + /// The maximum number of miplevels a Texture can have. + /// + /// + public readonly int MaximumMipLevels; - /// - /// Boolean indicating if this device supports compute shaders, unordered access on structured buffers and raw structured buffers. - /// - public readonly bool HasComputeShaders; + /// + /// The maximum size of a resource, in megabytes. + /// + /// + public readonly int ResourceSizeInMegabytes; - /// - /// Boolean indicating if this device supports shaders double precision calculations. - /// - public readonly bool HasDoublePrecision; + /// + /// The maximum number of slices/array elements for a one-dimensional (1D) Texture Array. + /// + /// + public readonly int MaximumTexture1DArraySize; - /// - /// Boolean indicating if this device supports concurrent resources in multithreading scenarios. - /// - public readonly bool HasMultiThreadingConcurrentResources; + /// + /// The maximum number of slices/array elements for a two-dimensional (2D) Texture Array. + /// + /// + public readonly int MaximumTexture2DArraySize; - /// - /// Boolean indicating if this device supports command lists in multithreading scenarios. - /// - public readonly bool HasDriverCommandLists; + /// + /// The maximum size in texels for a one-dimensional (1D) Texture. + /// + /// + public readonly int MaximumTexture1DSize; - /// - /// Boolean indicating if this device supports SRGB texture and render targets. - /// - public readonly bool HasSRgb; + /// + /// The maximum size (width or height) in texels for a two-dimensional (2D) Texture. + /// + /// + public readonly int MaximumTexture2DSize; - /// - /// Boolean indicating if the Depth buffer can also be used as ShaderResourceView for some passes. - /// - public readonly bool HasDepthAsSRV; + /// + /// The maximum size (width, height, or depth) in texels for a three-dimensional (3D) Texture. + /// + /// + public readonly int MaximumTexture3DSize; + + /// + /// The maximum size (width or height) in texels for a Texture Cube. + /// + /// + public readonly int MaximumTextureCubeSize; + + + /// + /// A value indicating if the supports Compute Shaders, unordered access on Structured Buffers, + /// and Raw Structured Buffers. + /// + /// + /// + public readonly bool HasComputeShaders; + + /// + /// A value indicating if the supports double precision operations in shaders. + /// + public readonly bool HasDoublePrecision; + + /// + /// A value indicating if the supports concurrent Resources in multi-threading scenarios. + /// + public readonly bool HasMultiThreadingConcurrentResources; + + /// + /// A value indicating if the supports Command Lists in multi-threading scenarios. + /// + /// + public readonly bool HasDriverCommandLists; + + /// + /// A value indicating if the supports sRGB Textures and Render Targets. + /// + /// + public readonly bool HasSRgb; + + /// + /// A value indicating if the Depth Buffer can also be used as a Shader Resource View and bound for shader passes. + /// + /// + public readonly bool HasDepthAsSRV; + + /// + /// A value indicating if the Depth Buffer can directly be used as a read-only Render Target. + /// + /// + public readonly bool HasDepthAsReadOnlyRT; + + /// + /// A value indicating if a multi-sampled Depth Buffer can directly be used as a Shader Resource View. + /// + /// + public readonly bool HasMultiSampleDepthAsSRV; + + /// + /// A value indicating if the graphics API supports resource renaming + /// (with either or with full size). + /// + public readonly bool HasResourceRenaming; + + + /// + /// Queries the features the supports for the specified . + /// + /// The pixel format. + /// + /// A structure indicating the features supported for . + /// + public readonly FeaturesPerFormat this[PixelFormat pixelFormat] => mapFeaturesPerFormat[(int) pixelFormat]; + +#if STRIDE_GRAPHICS_API_OPENGL + // Defined here to avoid CS0282 warning if defined in GraphicsDeviceFeatures.OpenGL.cs + internal string Vendor; + internal string Renderer; + internal System.Collections.Generic.IList SupportedExtensions; +#endif + + /// + /// Contains information about the features a supports for a particular . + /// + public readonly struct FeaturesPerFormat + { + internal FeaturesPerFormat(PixelFormat format, MultisampleCount maximumMultisampleCount, ComputeShaderFormatSupport computeShaderFormatSupport, FormatSupport formatSupport) + { + Format = format; + MultisampleCountMax = maximumMultisampleCount; + ComputeShaderFormatSupport = computeShaderFormatSupport; + FormatSupport = formatSupport; + } /// - /// Boolean indicating if the Depth buffer can directly be used as a read only RenderTarget + /// The pixel format. /// - public readonly bool HasDepthAsReadOnlyRT; + public readonly PixelFormat Format; /// - /// Boolean indicating if the multi-sampled Depth buffer can directly be used as a ShaderResourceView + /// The maximum sample count when multisampling for a particular . /// - public readonly bool HasMultisampleDepthAsSRV; + public readonly MultisampleCount MultisampleCountMax; /// - /// Boolean indicating if the graphics API supports resource renaming (with either `CommandList.UpdateSubresource` with full size). + /// The unordered resource support options for a Compute Shader resource using the . /// - public readonly bool HasResourceRenaming; + public readonly ComputeShaderFormatSupport ComputeShaderFormatSupport; /// - /// Gets the for the specified . + /// The support flags for a particular . /// - /// The dxgi format. - /// Features for the specific format. - public FeaturesPerFormat this[PixelFormat dxgiFormat] - { - get { return this.mapFeaturesPerFormat[(int)dxgiFormat]; } - } + public readonly FormatSupport FormatSupport; -#if STRIDE_GRAPHICS_API_OPENGL - // Defined here to avoid CS0282 warning if defined in GraphicsDeviceFeatures.OpenGL.cs - internal string Vendor; - internal string Renderer; - internal IList SupportedExtensions; -#endif - /// - /// The features exposed for a particular format. - /// - public struct FeaturesPerFormat + /// + public override readonly string ToString() { - //internal FeaturesPerFormat(PixelFormat format, MultisampleCount maximumMultisampleCount, ComputeShaderFormatSupport computeShaderFormatSupport, FormatSupport formatSupport) - internal FeaturesPerFormat(PixelFormat format, MultisampleCount maximumMultisampleCount, FormatSupport formatSupport) - { - Format = format; - this.MultisampleCountMax = maximumMultisampleCount; - //ComputeShaderFormatSupport = computeShaderFormatSupport; - FormatSupport = formatSupport; - } - - /// - /// The . - /// - public readonly PixelFormat Format; - - /// - /// Gets the maximum multisample count for a particular . - /// - public readonly MultisampleCount MultisampleCountMax; - - /// - /// Gets the unordered resource support options for a compute shader resource. - /// - //public readonly ComputeShaderFormatSupport ComputeShaderFormatSupport; - - /// - /// Support of a given format on the installed video device. - /// - public readonly FormatSupport FormatSupport; - - public override string ToString() - { - //return string.Format("Format: {0}, MultisampleCountMax: {1}, ComputeShaderFormatSupport: {2}, FormatSupport: {3}", Format, this.MSAALevelMax, ComputeShaderFormatSupport, FormatSupport); - return string.Format("Format: {0}, MultisampleCountMax: {1}, FormatSupport: {2}", Format, this.MultisampleCountMax, FormatSupport); - } + return $"Format: {Format}, MultisampleCountMax: {MultisampleCountMax}, ComputeShaderFormatSupport: {ComputeShaderFormatSupport}, FormatSupport: {FormatSupport}"; } + } - public override string ToString() - { - return string.Format("Level: {0}, HasComputeShaders: {1}, HasDoublePrecision: {2}, HasMultiThreadingConcurrentResources: {3}, HasDriverCommandLists: {4}", RequestedProfile, HasComputeShaders, HasDoublePrecision, HasMultiThreadingConcurrentResources, this.HasDriverCommandLists); - } + public override readonly string ToString() + { + return $"Level: {RequestedProfile}, HasComputeShaders: {HasComputeShaders}, HasDoublePrecision: {HasDoublePrecision}, HasMultiThreadingConcurrentResources: {HasMultiThreadingConcurrentResources}, HasDriverCommandLists: {HasDriverCommandLists}"; } } diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceServiceLocal.cs b/sources/engine/Stride.Graphics/GraphicsDeviceServiceLocal.cs index d0965e1b44..d64912bdda 100644 --- a/sources/engine/Stride.Graphics/GraphicsDeviceServiceLocal.cs +++ b/sources/engine/Stride.Graphics/GraphicsDeviceServiceLocal.cs @@ -8,22 +8,29 @@ namespace Stride.Graphics { /// - /// A default implementation of + /// A default simple implementation of that is used by + /// some systems that only need quick access to the . /// + /// + /// For a full-fledged implementation of that manages + /// correctly the device life-cycle and provides many more features, see GraphicsDeviceManager + /// in the Stride.Games namespace. + /// public class GraphicsDeviceServiceLocal : IGraphicsDeviceService { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The graphics device. - public GraphicsDeviceServiceLocal(GraphicsDevice graphicsDevice) : this(null, graphicsDevice) + public GraphicsDeviceServiceLocal(GraphicsDevice graphicsDevice) + : this(registry: null, graphicsDevice) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The registry. + /// The registry of registered services. /// The graphics device. public GraphicsDeviceServiceLocal(IServiceRegistry registry, GraphicsDevice graphicsDevice) { @@ -32,11 +39,17 @@ public GraphicsDeviceServiceLocal(IServiceRegistry registry, GraphicsDevice grap // We provide an empty `add' and `remove' to avoid a warning about unused events that we have // to implement as they are part of the IGraphicsDeviceService definition. + + /// public event EventHandler DeviceCreated { add { } remove { } } + /// public event EventHandler DeviceDisposing { add { } remove { } } + /// public event EventHandler DeviceReset { add { } remove { } } + /// public event EventHandler DeviceResetting { add { } remove { } } + /// public GraphicsDevice GraphicsDevice { get; private set; } } } diff --git a/sources/engine/Stride.Graphics/GraphicsDeviceStatus.cs b/sources/engine/Stride.Graphics/GraphicsDeviceStatus.cs index 642404a18b..8869a3b9d7 100644 --- a/sources/engine/Stride.Graphics/GraphicsDeviceStatus.cs +++ b/sources/engine/Stride.Graphics/GraphicsDeviceStatus.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,41 +21,44 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes the current status of a . +/// +public enum GraphicsDeviceStatus { /// - /// Describes the current status of a . + /// The Graphics Device is running normally. /// - public enum GraphicsDeviceStatus - { - /// - /// The device is running fine. - /// - Normal, + Normal, - /// - /// The video card has been physically removed from the system, or a driver upgrade for the video card has occurred. The application should destroy and recreate the device. - /// - Removed, + /// + /// The video card has been physically removed from the system, or a driver upgrade for the video card has occurred. + /// The application should destroy and recreate the Graphics Device. + /// + Removed, - /// - /// The application's device failed due to badly formed commands sent by the application. This is an design-time issue that should be investigated and fixed. - /// - Hung, + /// + /// The application's Graphics Device failed due to badly formed commands sent by the application. + /// This is an design-time issue that should be investigated and fixed. + /// + Hung, - /// - /// The device failed due to a badly formed command. This is a run-time issue; The application should destroy and recreate the device. - /// - Reset, + /// + /// The Graphics Device failed due to a badly formed command. + /// This is a run-time issue; The application should destroy and recreate the Graphics Device. + /// + Reset, - /// - /// The driver encountered a problem and was put into the device removed state. - /// - InternalError, + /// + /// The driver encountered a problem and was put into the Graphics Device removed state. + /// + InternalError, - /// - /// The application provided invalid parameter data; this must be debugged and fixed before the application is released. - /// - InvalidCall, - } + /// + /// The application provided invalid parameter data; + /// this must be debugged and fixed before the application is released. + /// + InvalidCall } diff --git a/sources/engine/Stride.Graphics/GraphicsException.cs b/sources/engine/Stride.Graphics/GraphicsException.cs index 001916bdb5..3f6f225dcc 100644 --- a/sources/engine/Stride.Graphics/GraphicsException.cs +++ b/sources/engine/Stride.Graphics/GraphicsException.cs @@ -1,27 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// An exception that is thrown when a graphics operation fails. +/// +/// +/// This is the base class for all exceptions related to graphics operations. Look at more specific exceptions +/// to better understand the nature of the error. +/// +public class GraphicsException : Exception { - public class GraphicsException : Exception - { - public GraphicsException() - { - } + private const string DefaultMessage = "An error occurred in the graphics subsystem."; - public GraphicsException(string message, GraphicsDeviceStatus status = GraphicsDeviceStatus.Normal) - : base(message) - { - Status = status; - } + /// + /// Initializes a new instance of the class with a default message. + /// + public GraphicsException() : base(DefaultMessage) { } - public GraphicsException(string message, Exception innerException, GraphicsDeviceStatus status = GraphicsDeviceStatus.Normal) - : base(message, innerException) - { - Status = status; - } + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public GraphicsException(string message) : base(message) { } - public GraphicsDeviceStatus Status { get; private set; } - } + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// + /// The error message that explains the reason for the exception. + /// Specify to use the default message. + /// + /// + /// The exception that is the cause of the current exception, or a reference if no inner exception is specified. + /// + public GraphicsException(string? message, Exception? innerException) : base(message ?? DefaultMessage, innerException) { } } diff --git a/sources/engine/Stride.Graphics/GraphicsFactory.cs b/sources/engine/Stride.Graphics/GraphicsFactory.cs deleted file mode 100644 index ec5e453e5b..0000000000 --- a/sources/engine/Stride.Graphics/GraphicsFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; - -namespace Stride.Graphics -{ - /// - /// Root factory for all Graphics components. - /// - public class GraphicsFactory : ComponentBase - { - /// - /// GraphicsApi key. - /// TODO not the best place to store this identifier. Move it to GraphicsFactory? - /// - public const string GraphicsApi = "GraphicsApi"; - } -} diff --git a/sources/engine/Stride.Graphics/GraphicsMarshal.cs b/sources/engine/Stride.Graphics/GraphicsMarshal.cs new file mode 100644 index 0000000000..68280872b5 --- /dev/null +++ b/sources/engine/Stride.Graphics/GraphicsMarshal.cs @@ -0,0 +1,179 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 + +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +#if STRIDE_GRAPHICS_API_DIRECT3D11 +using Silk.NET.Direct3D11; +#elif STRIDE_GRAPHICS_API_DIRECT3D12 +using Silk.NET.Direct3D12; +#endif + +namespace Stride.Graphics; + +/// +/// An unsafe class that provides a set of methods to access the underlying native DXGI and Direct3D +/// devices and resources from its Stride counterparts. +/// +public static unsafe class GraphicsMarshal +{ + /// + /// Gets the underlying DXGI swap-chain. + /// + /// The Stride Graphics Presenter. + /// + /// A COM pointer to a instance representing the native DirectX swap-chain, + /// or if the does not encapsulate a DXGI swap-chain. + /// + public static ComPtr? GetNativeSwapChain(GraphicsPresenter presenter) + => presenter is SwapChainGraphicsPresenter swapChainPresenter + ? swapChainPresenter.NativeSwapChain + : null; + +#if STRIDE_GRAPHICS_API_DIRECT3D11 + /// + /// Gets the underlying Direct3D 11 native device. + /// + /// The Stride Graphics Device. + /// + /// A COM pointer to a instance representing the native Direct3D 11 device. + /// + public static ComPtr GetNativeDevice(GraphicsDevice device) => device.NativeDevice; + + /// + /// Gets the underlying Direct3D 11 native device context. + /// + /// The Stride Graphics Device. + /// + /// A COM pointer to a instance representing the native Direct3D 11 command queue. + /// + public static ComPtr GetNativeDeviceContext(GraphicsDevice device) => device.NativeDeviceContext; + + /// + /// Gets the underlying Direct3D 11 native resource. + /// + /// The Stride Graphics Resource. + /// + /// A COM pointer to a instance representing the native Direct3D 11 resource. + /// + public static ComPtr GetNativeResource(GraphicsResource resource) => resource.NativeResource; + + /// + /// Gets the underlying Direct3D 11 native shader resource view. + /// + /// The Stride Graphics Resource. + /// + /// A COM pointer to a instance representing the native Direct3D 11 + /// shader resource view on the resource. + /// + public static ComPtr GetNativeShaderResourceView(GraphicsResource resource) => resource.NativeShaderResourceView; + + /// + /// Gets the underlying Direct3D 11 native render target view. + /// + /// The Stride Texture. + /// + /// A COM pointer to a instance representing the native Direct3D 11 + /// render target view on the Texture. + /// + public static ComPtr GetNativeRenderTargetView(Texture texture) => texture.NativeRenderTargetView; + + /// + /// Creates a from a Direct3D 11 texture. + /// + /// The in use. + /// The Direct3D 11 texture. + /// + /// to call on the texture; + /// to not call it, effectively taking ownership of the texture. + /// A value indicating whether to set the format of the Texture to sRGB. + /// A Stride . + public static Texture CreateTextureFromNative(GraphicsDevice device, ID3D11Texture2D* dxTexture2D, bool takeOwnership, bool isSRgb = false) + { + var texture = new Texture(device); + + if (takeOwnership) + { + dxTexture2D->AddRef(); + } + + texture.InitializeFromImpl(dxTexture2D, isSRgb); + + return texture; + } + +#elif STRIDE_GRAPHICS_API_DIRECT3D12 + /// + /// Gets the underlying Direct3D 12 native device. + /// + /// The Stride Graphics Device. + /// + /// A COM pointer to a instance representing the native Direct3D 12 device. + /// + public static ComPtr GetNativeDevice(GraphicsDevice device) => device.NativeDevice; + + /// + /// Gets the underlying Direct3D 12 native command queue. + /// + /// The Stride Graphics Device. + /// + /// A COM pointer to a instance representing the native Direct3D 12 command queue. + /// + public static ComPtr GetNativeCommandQueue(GraphicsDevice device) => device.NativeCommandQueue; + + /// + /// Gets the underlying Direct3D 12 native resource. + /// + /// The Stride Graphics Resource. + /// + /// A COM pointer to a instance representing the native Direct3D 12 resource. + /// + public static ComPtr GetNativeResource(GraphicsResource resource) => resource.NativeResource; + + /// + /// Gets the underlying Direct3D 12 native shader resource view CPU-accessible handle. + /// + /// The Stride Graphics Resource. + /// + /// A representing the native Direct3D 12 handle. + /// + public static CpuDescriptorHandle GetNativeShaderResourceView(GraphicsResource resource) => resource.NativeShaderResourceView; + + /// + /// Gets the underlying Direct3D 12 native render target view CPU-accessible handle. + /// + /// The Stride Texture. + /// + /// A representing the native Direct3D 12 handle. + /// + public static CpuDescriptorHandle GetNativeRenderTargetView(Texture texture) => texture.NativeRenderTargetView; + + /// + /// Creates a from a Direct3D 12 texture resource. + /// + /// The in use. + /// The Direct3D 12 texture resource. + /// + /// to call on the texture; + /// to not call it, effectively taking ownership of the resource. + /// A value indicating whether to set the format of the Texture to sRGB. + /// A Stride . + public static Texture CreateTextureFromNative(GraphicsDevice device, ID3D12Resource* dxTexture2D, bool takeOwnership, bool isSRgb = false) + { + var texture = new Texture(device); + + if (takeOwnership) + { + dxTexture2D->AddRef(); + } + + texture.InitializeFromImpl(dxTexture2D, isSRgb); + + return texture; + } +#endif +} + +#endif diff --git a/sources/engine/Stride.Graphics/GraphicsOutput.cs b/sources/engine/Stride.Graphics/GraphicsOutput.cs index 2f10c44ad0..81173d879e 100644 --- a/sources/engine/Stride.Graphics/GraphicsOutput.cs +++ b/sources/engine/Stride.Graphics/GraphicsOutput.cs @@ -1,85 +1,66 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; using Stride.Core; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; namespace Stride.Graphics { - public partial class GraphicsOutput : ComponentBase + /// + /// Represents an output (such as a monitor) attached to a . + /// + public sealed partial class GraphicsOutput : ComponentBase { private static readonly Logger Log = GlobalLogger.GetLogger(typeof(GraphicsOutput).FullName); - private readonly object lockModes = new object(); - private readonly GraphicsAdapter adapter; - private DisplayMode currentDisplayMode; + + private readonly object lockModes = new(); + + private DisplayMode? currentDisplayMode; private DisplayMode[] supportedDisplayModes; - private readonly Rectangle desktopBounds; + /// - /// Default constructor to initialize fields that are not explicitly set to avoid warnings at compile time. + /// Gets the this output is attached to. /// - internal GraphicsOutput() - { - adapter = null; - supportedDisplayModes = null; - desktopBounds = Rectangle.Empty; - } + public GraphicsAdapter Adapter { get; } /// - /// Gets the current display mode. + /// Gets the current display mode of this . /// - /// The current display mode. - public DisplayMode CurrentDisplayMode + public DisplayMode? CurrentDisplayMode { get { lock (lockModes) { if (currentDisplayMode == null) - { InitializeCurrentDisplayMode(); - } } return currentDisplayMode; } } /// - /// Returns a collection of supported display modes for this . + /// Returns a collection of the supported display modes for this . /// - public DisplayMode[] SupportedDisplayModes + public ReadOnlySpan SupportedDisplayModes { get { lock (lockModes) { if (supportedDisplayModes == null) - { InitializeSupportedDisplayModes(); - } } return supportedDisplayModes; } } /// - /// Gets the desktop bounds of the current output. - /// - public Rectangle DesktopBounds - { - get - { - return desktopBounds; - } - } - - /// - /// Gets the adapter this output is attached. + /// Gets the desktop bounds of the current . /// - /// The adapter. - public GraphicsAdapter Adapter - { - get { return adapter; } - } + public Rectangle DesktopBounds { get; } } } diff --git a/sources/engine/Stride.Graphics/GraphicsPresenter.cs b/sources/engine/Stride.Graphics/GraphicsPresenter.cs index c77644eec7..5326b0be46 100644 --- a/sources/engine/Stride.Graphics/GraphicsPresenter.cs +++ b/sources/engine/Stride.Graphics/GraphicsPresenter.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,242 +23,411 @@ using System; using Stride.Core; -using Stride.Core.ReferenceCounting; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Represents an abstraction over a Swap-Chain. +/// +/// +/// +/// A Swap-Chain is a collection of same-sized buffers (one front-buffer, +/// one or more -usually one- Back-Buffer, and an optional Depth-Stencil Buffer) +/// that are used to present the final rendered image to the screen. +/// +/// +/// In order to create a new , a +/// should have been initialized first. +/// +/// +/// +/// +public abstract class GraphicsPresenter : ComponentBase { /// - /// This class is a frontend to and . + /// A tag property that allows to override the property's value + /// with a forced one. /// /// - /// In order to create a new , a should have been initialized first. + /// + /// If the value of this tag property is not the given interval will be + /// used during a operation. + /// + /// + /// This is currently only supported by the Direct3D graphics implementation. + /// /// - public abstract class GraphicsPresenter : ComponentBase + internal static readonly PropertyKey ForcedPresentInterval = new(name: nameof(ForcedPresentInterval), ownerType: typeof(GraphicsDevice)); + + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// + /// The parameters describing the buffers the will present to. + /// + /// is . + /// is . + /// The Depth-Stencil format specified is not supported. + protected GraphicsPresenter(GraphicsDevice device, PresentationParameters presentationParameters) { - /// - /// If not null the given interval will be used during a operation. - /// - /// - /// This is currently only supported by the Direct3D graphics implementation. - /// - internal static readonly PropertyKey ForcedPresentInterval = new PropertyKey(nameof(ForcedPresentInterval), typeof(GraphicsDevice)); - - private Texture depthStencilBuffer; - - /// - /// Initializes a new instance of the class. - /// - /// The device. - /// - protected GraphicsPresenter(GraphicsDevice device, PresentationParameters presentationParameters) - { - GraphicsDevice = device; - var description = presentationParameters.Clone(); + ArgumentNullException.ThrowIfNull(device); + ArgumentNullException.ThrowIfNull(presentationParameters); - description.BackBufferFormat = NormalizeBackBufferFormat(description.BackBufferFormat); + GraphicsDevice = device; + var description = presentationParameters.Clone(); - Description = description; + description.BackBufferFormat = NormalizeBackBufferFormat(description.BackBufferFormat); - ProcessPresentationParameters(); + Description = description; - // Creates a default DepthStencilBuffer. - CreateDepthStencilBuffer(); - } + ProcessPresentationParameters(); - /// - /// Gets the graphics device. - /// - /// The graphics device. - public GraphicsDevice GraphicsDevice { get; private set; } + // Creates a default Depth-Stencil Buffer + CreateDepthStencilBuffer(); + } - /// - /// Gets the description of this presenter. - /// - public PresentationParameters Description { get; private set; } - /// - /// Gets the default back buffer for this presenter. - /// - public abstract Texture BackBuffer { get; } + /// + /// Gets the Graphics Device the Graphics Presenter is associated to. + /// + /// + /// The Graphics Device that will be used for managing the Buffers and Textures, and starting/ending a frame. + /// + public GraphicsDevice GraphicsDevice { get; } - // Temporarily here until we can move WindowsMixedRealityGraphicsPresenter to Stride.VirtualReality (currently not possible because Stride.Games creates it) - // This allows to keep Stride.Engine platform-independent - internal Texture LeftEyeBuffer { get; set; } + /// + /// Gets the parameters describing the Resources and behavior of the Graphics Presenter. + /// + public PresentationParameters Description { get; } - internal Texture RightEyeBuffer { get; set; } + /// + /// Gets the default Back-Buffer for the Graphics Presenter. + /// + /// + /// The where rendering will happen, which will then be presented to the front-buffer. + /// + public abstract Texture BackBuffer { get; } - /// - /// Gets the default depth stencil buffer for this presenter. - /// - public Texture DepthStencilBuffer - { - get - { - return depthStencilBuffer; - } - - protected set - { - depthStencilBuffer = value; - } - } + // TODO: Temporarily here until we can move WindowsMixedRealityGraphicsPresenter to Stride.VirtualReality (currently not possible because Stride.Games creates it) + // This allows to keep Stride.Engine platform-independent + internal Texture LeftEyeBuffer { get; set; } + internal Texture RightEyeBuffer { get; set; } - /// - /// Gets the underlying native presenter (can be a or or null, depending on the platform). - /// - /// The native presenter. - public abstract object NativePresenter { get; } - - /// - /// Gets or sets fullscreen mode for this presenter. - /// - /// true if this instance is full screen; otherwise, false. - /// This method is only valid on Windows Desktop and has no effect on Windows Metro. - public abstract bool IsFullScreen { get; set; } - - /// - /// Gets or sets the . Default is to wait for one vertical blanking. - /// - /// The present interval. - public PresentInterval PresentInterval - { - get { return Description.PresentationInterval; } - set { Description.PresentationInterval = value; } - } + /// + /// Gets the default Depth-Stencil Buffer for the Graphics Presenter. + /// + /// + /// The where depth (Z) values will be written, and optionally also + /// stencil masking values. + /// + /// + /// If the Depth-Stencil Buffer is the one created by this Graphics Presenter, it is attached to it its lifetime + /// is managed by this instance. + /// If a derived class needs to set a custom Depth-Stencil Buffer, it should first call + /// to release the current one. + /// + public Texture DepthStencilBuffer { get; protected set; } - public virtual void BeginDraw(CommandList commandList) - { - } + /// + /// Gets the underlying native presenter. + /// + /// + /// The native presenter. Depending on platform, for exmaple, it can be a + /// or or . + /// + public abstract object NativePresenter { get; } - public virtual void EndDraw(CommandList commandList, bool present) - { - } + /// + /// Gets or sets a value indicating if the Graphics Presenter is in full-screen mode. + /// + /// + /// if the presentation will be in full screen; otherwise, . + /// + /// This property is only valid on Windows Desktop. It has no effect on Windows Metro. + public abstract bool IsFullScreen { get; set; } - /// - /// Presents the Backbuffer to the screen. - /// - public abstract void Present(); - - /// - /// Resizes the current presenter, by resizing the back buffer and the depth stencil buffer. - /// - /// - /// - /// - public void Resize(int width, int height, PixelFormat format) - { - GraphicsDevice.Begin(); + /// + /// Gets or sets the presentation interval of the Graphics Presenter. + /// + /// + /// A value of indicating how often the display should be updated. + /// The default value is , which is to wait for one vertical blanking. + /// + public PresentInterval PresentInterval + { + get => Description.PresentationInterval; + set => Description.PresentationInterval = value; + } - Description.BackBufferWidth = width; - Description.BackBufferHeight = height; - Description.BackBufferFormat = NormalizeBackBufferFormat(format); + /// + /// Marks the beginning of a frame that will be presented later by the Graphics Presenter. + /// + /// The Command List where rendering commands will be registered. + /// + /// When overriden in a derived class, this method should prepare the Graphics Presenter to receive + /// graphics commands to be executed at the beginning of the current frame. + /// + public virtual void BeginDraw(CommandList commandList) + { + } - ResizeBackBuffer(width, height, format); - ResizeDepthStencilBuffer(width, height, format); + /// + /// Marks the end of a frame that will be presented later by the Graphics Presenter. + /// + /// The Command List where rendering commands will be registered. + /// + /// A value indicating whether the frame will be presented, i.e. if the Back-Buffer will be shown to the screen. + /// + /// + /// When overriden in a derived class, this method should prepare the Graphics Presenter to receive + /// graphics commands to be executed at the end of the current frame. + /// + public virtual void EndDraw(CommandList commandList, bool present) + { + } - GraphicsDevice.End(); - } + /// + /// Presents the Back-Buffer to the screen. + /// + /// + /// An unexpected error occurred while presenting. Check the status of the Graphics Device + /// for more information (). + /// + public abstract void Present(); - /// - /// Sets the output color space of the presenter and the format for the backbuffer. Currently only supported by the DirectX backend. - /// Use the following combinations:
- /// Render to SDR Display with gamma 2.2: with , , , .
- /// Render to HDR Display in scRGB (standard linear), windows DWM will do the color conversion: with
- /// Render to HDR Display in HDR10/BT.2100, no windows DWM conversion, rendering needs to be in the Display color space: with
- ///
- /// - /// - public void SetOutputColorSpace(ColorSpaceType colorSpace, PixelFormat format) - { - GraphicsDevice.Begin(); + /// + /// Resizes the Back-Buffer and the Depth-Stencil Buffer. + /// + /// The new width of the buffers of the Graphics Presenter, in pixels. + /// The new height of the buffers of the Graphics Presenter, in pixels. + /// + /// The new preferred pixel format for the Back-Buffer. The specified format may be overriden + /// depending on Graphics Device features and configuration (for example, to use sRGB when appropriate). + /// + /// + /// The specified pixel or size is not supported by the Graphics Device. + /// + public void Resize(int width, int height, PixelFormat format) + { + GraphicsDevice.Begin(); - Description.BackBufferFormat = NormalizeBackBufferFormat(format); - - // new resources - ResizeBackBuffer(Description.BackBufferWidth, Description.BackBufferHeight, format); - ResizeDepthStencilBuffer(Description.BackBufferWidth, Description.BackBufferHeight, depthStencilBuffer.ViewFormat); + Description.BackBufferWidth = width; + Description.BackBufferHeight = height; + Description.BackBufferFormat = NormalizeBackBufferFormat(format); - // recreate swapchain - OnDestroyed(); - Description.OutputColorSpace = colorSpace; - OnRecreated(); + ResizeBackBuffer(width, height, format); + ResizeDepthStencilBuffer(width, height, DepthStencilBuffer.ViewFormat); - GraphicsDevice.End(); - } + GraphicsDevice.End(); + } - private PixelFormat NormalizeBackBufferFormat(PixelFormat backBufferFormat) - { - // If we are creating a GraphicsPresenter with - if (GraphicsDevice.Features.HasSRgb && GraphicsDevice.ColorSpace == ColorSpace.Linear) - { - // If the device support SRgb and ColorSpace is linear, we use automatically a SRgb backbuffer - return backBufferFormat.ToSRgb(); - } - else - { - // If the device does not support SRgb or the ColorSpace is Gamma, but the backbuffer format asked is SRgb, convert it to non SRgb - return backBufferFormat.ToNonSRgb(); - } - } + /// + /// Sets the output color space of the Graphics Presenter and the pixel format to use for the Back-Buffer. + /// + /// The output color space the Graphics Presenter should use. + /// The pixel format to use for the Back-Buffer. + /// + /// + /// The output color space can be used to render to HDR monitors. + /// + /// + /// Use the following combinations: + /// + /// + /// For rendering to a SDR display with gamma 2.2 + /// + /// Set a color space of with a Back-Buffer format , + /// , , or . + /// + /// + /// + /// For rendering to a HDR display in scRGB (standard linear), and letting the Windows DWM do the color conversion + /// + /// Set a color space of with a Back-Buffer format . + /// + /// + /// + /// + /// For rendering to a HDR display in HDR10 / BT.2100, with no color conversion by the Windows DWM, rendering needs to happen in the + /// same color space as the display. + /// + /// + /// Set a color space of with a Back-Buffer format . + /// + /// + /// + /// + /// + /// Note that this is currently only supported in Stride when using the Direct3D Graphics API. + /// For more information about High Dynamic Range (HDR) rendering, see + /// . + /// + /// + /// + /// The specified pixel or size is not supported by the Graphics Device. + /// + public void SetOutputColorSpace(ColorSpaceType colorSpace, PixelFormat format) + { + GraphicsDevice.Begin(); - protected abstract void ResizeBackBuffer(int width, int height, PixelFormat format); + Description.BackBufferFormat = NormalizeBackBufferFormat(format); - protected abstract void ResizeDepthStencilBuffer(int width, int height, PixelFormat format); + ResizeBackBuffer(Description.BackBufferWidth, Description.BackBufferHeight, format); + ResizeDepthStencilBuffer(Description.BackBufferWidth, Description.BackBufferHeight, DepthStencilBuffer.ViewFormat); - protected void ReleaseCurrentDepthStencilBuffer() - { - if (DepthStencilBuffer != null) - { - depthStencilBuffer.RemoveDisposeBy(this); - } - } + // We need to recreate the Swap Chain + OnDestroyed(); + Description.OutputColorSpace = colorSpace; + OnRecreated(); + + GraphicsDevice.End(); + } - protected override void Destroy() + /// + /// Normalizes the Back-Buffer format to take into account the color space and sRGB format. + /// + /// The current Back-Buffer format. + /// The normalized pixel format. + private PixelFormat NormalizeBackBufferFormat(PixelFormat backBufferFormat) + { + if (GraphicsDevice.Features.HasSRgb && GraphicsDevice.ColorSpace == ColorSpace.Linear) { - OnDestroyed(); - base.Destroy(); + // If the device support sRGB and ColorSpace is linear, we use automatically a sRGB backbuffer + return backBufferFormat.ToSRgb(); } - - /// - /// Called when [destroyed]. - /// - protected internal virtual void OnDestroyed() + else { + // If the device does not support sRGB or the ColorSpace is Gamma, but the backbuffer format asked is sRGB, convert it to non sRGB + return backBufferFormat.ToNonSRgb(); } + } - /// - /// Called when [recreated]. - /// - public virtual void OnRecreated() - { - } + /// + /// Resizes the Back-Buffer. + /// + /// The new width of the Back-Buffer, in pixels. + /// The new height of the Back-Buffer, in pixels. + /// The new pixel format for the Back-Buffer. + /// + /// The specified pixel or size is not supported by the Graphics Device. + /// + /// + /// When implementing this method, the derived class should resize the Back-Buffer to the specified + /// size and format. + /// + protected abstract void ResizeBackBuffer(int width, int height, PixelFormat format); - protected virtual void ProcessPresentationParameters() - { - } + /// + /// Resizes the Depth-Stencil Buffer. + /// + /// The new width of the Depth-Stencil Buffer, in pixels. + /// The new height of the Depth-Stencil Buffer, in pixels. + /// The new pixel format for the Depth-Stencil Buffer. + /// + /// The specified depth or size is not supported by the Graphics Device. + /// + /// + /// When implementing this method, the derived class should resize the Depth-Stencil Buffer to the specified + /// size and format. + /// + protected abstract void ResizeDepthStencilBuffer(int width, int height, PixelFormat format); + + /// + /// Detaches the current Depth-Stencil Buffer from the Graphics Presenter. + /// + /// + /// If is the Depth-Stencil Buffer created by this Graphics Presenter, + /// it is attached to it its lifetime is managed by this instance. + /// If a derived class needs to set a custom Depth-Stencil Buffer, it should first call this method + /// to detach the current one. + /// + protected void DetachDepthStencilBuffer() + { + DepthStencilBuffer?.RemoveDisposeBy(this); + } + + /// + protected override void Destroy() + { + OnDestroyed(); + base.Destroy(); + } + + /// + /// Called when the Graphics Presenter has been destroyed. + /// + /// + /// When overriden in a derived class, this method allows to perform additional cleanup + /// and release of associated resources. + /// + protected internal virtual void OnDestroyed() + { + } + + /// + /// Called when the Graphics Presenter has been reinitialized. + /// + /// + /// When overriden in a derived class, this method allows to perform additional resource + /// creation, configuration, and initialization. + /// + /// + /// is or + /// the is invalid or zero. + /// + public virtual void OnRecreated() + { + } - /// - /// Creates the depth stencil buffer. - /// - protected virtual void CreateDepthStencilBuffer() + /// + /// Processes and adjusts the Presentation Parameters before initializing the Graphics Presenter. + /// + /// + /// When overriden in a derived class, this method allows to modify the specified Presentation Parameters + /// before initializing the internal buffers and resources. + /// + protected virtual void ProcessPresentationParameters() + { + } + + /// + /// Creates the Depth-Stencil Buffer. + /// + /// The Depth-Stencil format specified is not supported. + /// + /// + /// When overriden in a derived class, this method allows to create a custom Depth-Stencil Buffer + /// when initializing the Graphics Presenter. + /// + /// + /// By default, if a depth format has been specified, a Depth-Stencil Buffer is created with the same + /// size as the Back-Buffer. + /// + /// + protected virtual void CreateDepthStencilBuffer() + { + // If no Depth-Stencil Buffer, just return + if (Description.DepthStencilFormat == PixelFormat.None) + return; + + // Creates the Depth-Stencil Buffer + var flags = TextureFlags.DepthStencil; + if (GraphicsDevice.Features.CurrentProfile >= GraphicsProfile.Level_10_0 && + Description.MultisampleCount == MultisampleCount.None) { - // If no depth stencil buffer, just return - if (Description.DepthStencilFormat == PixelFormat.None) - return; - - // Creates the depth stencil buffer. - var flags = TextureFlags.DepthStencil; - if (GraphicsDevice.Features.CurrentProfile >= GraphicsProfile.Level_10_0 && Description.MultisampleCount == MultisampleCount.None) - { - flags |= TextureFlags.ShaderResource; - } - - // Create texture description - var depthTextureDescription = TextureDescription.New2D(Description.BackBufferWidth, Description.BackBufferHeight, Description.DepthStencilFormat, flags); - depthTextureDescription.MultisampleCount = Description.MultisampleCount; - - var depthTexture = Texture.New(GraphicsDevice, depthTextureDescription); - DepthStencilBuffer = depthTexture.DisposeBy(this); + flags |= TextureFlags.ShaderResource; } + + var depthTextureDescription = TextureDescription.New2D(Description.BackBufferWidth, Description.BackBufferHeight, Description.DepthStencilFormat, flags); + depthTextureDescription.MultisampleCount = Description.MultisampleCount; + + var depthTexture = GraphicsDevice.IsDebugMode + ? new Texture(GraphicsDevice, name: "Depth-Stencil Buffer") + : new Texture(GraphicsDevice); + + depthTexture.InitializeFrom(depthTextureDescription); + DepthStencilBuffer = depthTexture.DisposeBy(this); } } diff --git a/sources/engine/Stride.Graphics/GraphicsResource.cs b/sources/engine/Stride.Graphics/GraphicsResource.cs index 2b5261af66..4ecddf3bd1 100644 --- a/sources/engine/Stride.Graphics/GraphicsResource.cs +++ b/sources/engine/Stride.Graphics/GraphicsResource.cs @@ -1,22 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + namespace Stride.Graphics -{ +{ /// - /// GraphicsResource abstract class + /// Represents an abstract resource that depends on a . /// public abstract partial class GraphicsResource : GraphicsResourceBase { - protected GraphicsResource() - { - } + /// + /// Initializes a new instance of the class. + /// + protected GraphicsResource() { } - protected GraphicsResource(GraphicsDevice device) : base(device) - { - } + /// + /// Initializes a new instance of the class attached to a graphics device. + /// + /// The this resource belongs to. + protected GraphicsResource(GraphicsDevice device) : base(device) { } - protected GraphicsResource(GraphicsDevice device, string name) : base(device, name) - { - } + /// + /// Initializes a new instance of the class attached to a graphics device. + /// + /// The this resource belongs to. + /// + /// A string to use as a name for identifying the resource. Useful when debugging. + /// Specify to use the type's name instead. + /// + protected GraphicsResource(GraphicsDevice device, string? name) : base(device, name) { } } } diff --git a/sources/engine/Stride.Graphics/GraphicsResourceAllocator.cs b/sources/engine/Stride.Graphics/GraphicsResourceAllocator.cs index 0e00148ab0..0a74d557e6 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceAllocator.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceAllocator.cs @@ -8,423 +8,551 @@ namespace Stride.Graphics { + #region Convenience aliases + + // Convenience aliases to aid readability and reduce verbosity + + using TextureCache = GraphicsResourceAllocator.ResourceCache; + using BufferCache = GraphicsResourceAllocator.ResourceCache; + using QueryPoolCache = GraphicsResourceAllocator.ResourceCache; + + using CreateTextureDelegate = GraphicsResourceAllocator.CreateResourceDelegate; + using CreateBufferDelegate = GraphicsResourceAllocator.CreateResourceDelegate; + using CreateQueryPoolDelegate = GraphicsResourceAllocator.CreateResourceDelegate; + + #endregion + + /// - /// A allocator tracking usage reference and allowing to recycle unused resources based on a recycle policy. + /// A allocator tracking usage references and allowing to recycle unused resources based on a recycle policy. /// /// - /// This class is threadsafe. Accessing a member will lock globally this instance. + /// This class is thread-safe. Accessing any member will acquire a global lock on this instance. /// public class GraphicsResourceAllocator : ComponentBase { // TODO: Check if we should introduce an enum for the kind of scope (per DrawCore, per Frame...etc.) // TODO: Add statistics method (number of objects allocated...etc.) - private readonly object thisLock = new object(); - private readonly Dictionary> textureCache = new Dictionary>(); - private readonly Dictionary> bufferCache = new Dictionary>(); - private readonly Dictionary> queryPoolCache = new Dictionary>(); - private readonly Func getTextureDefinitionDelegate; - private readonly Func getBufferDescriptionDelegate; - private readonly Func getQueryPoolDescriptionDelegate; - private readonly Func createTextureDelegate; - private readonly Func createBufferDelegate; - private readonly Func createQueryPoolDelegate; + #region Internal types + + /// + /// Internal type for a cache of Graphics Resources associated with their description. + /// + /// The type of an object that describes the characteristics of the cached Graphics Resource. + protected internal sealed class ResourceCache : Dictionary>; /// - /// Initializes a new instance of the class. + /// A description of a GPU queries pool. /// - /// The graphics device. + /// The type of the pooled GPU queries. + /// The number of queries in the pool. + protected internal record struct QueryPoolDescription(QueryType QueryType, int QueryCount); + + /// + /// Represents a method that creates a Graphics Resource of type based on the specified + /// description and pixel format. + /// + /// The type of the Graphics Resource to be created. + /// The type of the description used to define the Graphics Resource. + /// The description that specifies the details of the Graphics Resource to be created. + /// The data format to be used for SRVs on the Graphics Resource. + /// + /// A new instance of created based on the provided description and SRV data format. + /// + protected internal delegate TResource CreateResourceDelegate(TDescription description, PixelFormat viewFormat); + + /// + /// Represents a method that retrieves a description of a Graphics Resource. + /// + /// The type of the Graphics Resource for which the description is retrieved. + /// The type of the description returned for the Graphics Resource. + /// The Graphics Resource for which the description is to be retrieved. + /// The description of the specified Graphics Resource. + protected internal delegate TDescription GetDescriptionDelegate(TResource resource); + + #endregion + + private readonly object thisLock = new(); + + private readonly TextureCache textureCache = []; // Cache for Textures by their TextureDescription + private readonly BufferCache bufferCache = []; // Cache for Buffers by their BufferDescription + private readonly QueryPoolCache queryPoolCache = []; // Cache for QueryPools by their QueryPoolDescription + + private readonly CreateTextureDelegate createTextureDelegate; + private readonly CreateBufferDelegate createBufferDelegate; + private readonly CreateQueryPoolDelegate createQueryPoolDelegate; + + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// is . public GraphicsResourceAllocator(GraphicsDevice graphicsDevice) { GraphicsDevice = graphicsDevice ?? throw new ArgumentNullException(nameof(graphicsDevice)); - getTextureDefinitionDelegate = GetTextureDefinition; - getBufferDescriptionDelegate = GetBufferDescription; - getQueryPoolDescriptionDelegate = GetQueryPoolDefinition; + // We cache the delegates to avoid allocations on each call. They can't be static because they + // can be overridden in derived classes createTextureDelegate = CreateTexture; createBufferDelegate = CreateBuffer; createQueryPoolDelegate = CreateQueryPool; - RecyclePolicy = DefaultRecyclePolicy; } + + /// + /// Gets or sets the Graphics Device. + /// + private GraphicsDevice GraphicsDevice { get; } + /// - /// Gets or sets the graphics device. + /// Gets the services registry. /// - /// The graphics device. - private GraphicsDevice GraphicsDevice { get; set; } + public IServiceRegistry Services { get; private set; } // TODO: Never set, never read, remove this property? /// - /// Gets the services registry. + /// Gets or sets the default recycle policy. /// - /// The services registry. - public IServiceRegistry Services { get; private set; } + /// The recycle policy to apply by default by the Graphics Resource Allocator. + /// + /// + /// A recycle policy is a delegate that inspects a Graphics Resource and some allocation and access + /// information, and determines if it should be recycled (disposed) or not. + /// + /// + /// The default recycle policy () is to always recycle a Graphics Resource + /// irrespective of its state. + /// + /// + public GraphicsResourceRecyclePolicyDelegate RecyclePolicy { get; set; } = DefaultRecyclePolicy; /// - /// Gets or sets the default recycle policy. Default is always recycle no matter the state of the resources. + /// The default recycle policy that always removes all allocated Graphics Resources. /// - /// The default recycle policy. - public GraphicsResourceRecyclePolicyDelegate RecyclePolicy { get; set; } + private static bool DefaultRecyclePolicy(GraphicsResourceLink resourceLink) => true; + /// - /// Recycles unused resources (with a == 0 ) with the . By Default, no recycle policy installed. + /// Recycles unused resources (those with a of 0) + /// with the . /// + /// + /// If no recycle policy is set (it is ), this method does not recycle anything. + /// public void Recycle() { - if (RecyclePolicy != null) + if (RecyclePolicy is not null) { Recycle(RecyclePolicy); } } /// - /// Recycles unused resource with the specified recycle policy. + /// Recycles unused resources (those with a of 0) + /// with the specified recycle policy. /// /// The recycle policy. - /// recyclePolicy + /// is . public void Recycle(GraphicsResourceRecyclePolicyDelegate recyclePolicy) { - if (recyclePolicy == null) throw new ArgumentNullException("recyclePolicy"); + ArgumentNullException.ThrowIfNull(recyclePolicy); - // Global lock to be threadsafe. + // Global lock to be thread-safe lock (thisLock) { Recycle(textureCache, recyclePolicy); Recycle(bufferCache, recyclePolicy); } + + /// + /// Recycles the specified cache. + /// + static void Recycle(ResourceCache cache, GraphicsResourceRecyclePolicyDelegate recyclePolicy) + { + foreach (var resourceList in cache.Values) + { + for (int i = resourceList.Count - 1; i >= 0; i--) + { + var resourceLink = resourceList[i]; + if (resourceLink.ReferenceCount == 0) + { + if (recyclePolicy(resourceLink)) + { + resourceLink.Resource.Dispose(); + resourceList.RemoveAt(i); + } + // Reset the access count + resourceLink.AccessCountSinceLastRecycle = 0; + } + } + } + } } + + /// + /// Returns a description for a specified . + /// + private static BufferDescription GetBufferDescription(Buffer buffer) => buffer.Description; + /// + /// Returns a description for a specified . + /// + private static TextureDescription GetTextureDescription(Texture texture) => texture.Description; + /// + /// Returns a description for a specified . + /// + private static QueryPoolDescription GetQueryPoolDescription(QueryPool queryPool) => new(queryPool.QueryType, queryPool.QueryCount); + + /// - /// Gets a texture for the specified description. + /// Gets a temporary Texture with the specified description. /// - /// The description. - /// A texture + /// A description of the needed characteristics of the Texture. + /// A temporary Texture with the specified . public Texture GetTemporaryTexture(TextureDescription description) { - // Global lock to be threadsafe. + // Global lock to be thread-safe lock (thisLock) { - return GetTemporaryResource(textureCache, description, createTextureDelegate, getTextureDefinitionDelegate, PixelFormat.None); + return GetTemporaryResource(textureCache, description, createTextureDelegate, GetTextureDescription, PixelFormat.None); } } /// - /// Gets a texture for the specified description. + /// Gets a temporary Buffer with the specified description. /// - /// The description. - /// The pixel format seen by the shader - /// A texture + /// A description of the needed characteristics of the Buffer. + /// + /// The data format for the View that will be seen by Shaders. + /// Specify to use the default format of the Buffer. + /// + /// A temporary Buffer with the specified . public Buffer GetTemporaryBuffer(BufferDescription description, PixelFormat viewFormat = PixelFormat.None) { - // Global lock to be threadsafe. + // Global lock to be thread-safe lock (thisLock) { - return GetTemporaryResource(bufferCache, description, createBufferDelegate, getBufferDescriptionDelegate, viewFormat); + return GetTemporaryResource(bufferCache, description, createBufferDelegate, GetBufferDescription, viewFormat); } } + /// + /// Gets a Query Pool for a specific number of GPU Queries of a given type. + /// + /// The type of the Queries. + /// The needed number of Queries. + /// A temporary Query Pool with the specified types and count. public QueryPool GetQueryPool(QueryType queryType, int queryCount) { - // Global lock to be threadsafe. + // Global lock to be thread-safe lock (thisLock) { - return GetTemporaryResource(queryPoolCache, new QueryPoolDescription(queryType, queryCount), createQueryPoolDelegate, getQueryPoolDescriptionDelegate, PixelFormat.None); + return GetTemporaryResource(queryPoolCache, new QueryPoolDescription(queryType, queryCount), createQueryPoolDelegate, GetQueryPoolDescription, PixelFormat.None); } } + /// - /// Increments the reference to a temporary resource. + /// Adds a reference to a Graphics Resource tracked by this allocator. /// - /// + /// The Graphics Resource. It's internal reference count will be increased. + /// + /// Thrown if was not allocated by this allocator. + /// public void AddReference(GraphicsResource resource) { - // Global lock to be threadsafe. + // Global lock to be thread-safe lock (thisLock) { - UpdateReference(resource, 1); + UpdateReference(resource, +1); } } /// - /// Decrements the reference to a temporary resource. + /// Removes a reference to a Graphics Resource tracked by this allocator. /// - /// + /// The Graphics Resource. It's internal reference count will be decreased. + /// + /// Thrown if was not allocated by this allocator. + /// public void ReleaseReference(GraphicsResourceBase resource) { - // Global lock to be threadsafe. + // Global lock to be thread-safe lock (thisLock) { UpdateReference(resource, -1); } } + /// - /// Creates a texture for output. + /// Creates a Texture. /// - /// The description. - /// The pixel format seen by the shader - /// Texture. + /// A description of the needed characteristics of the Texture. + /// + /// The pixel format for the View that will be seen by Shaders. + /// Specify to use the default format of the Texture. + /// + /// A Texture with the specified . protected virtual Texture CreateTexture(TextureDescription description, PixelFormat viewFormat) { return Texture.New(GraphicsDevice, description); } /// - /// Creates a temporary buffer. + /// Creates a Buffer. /// - /// The description. - /// The shader view format on the buffer - /// Buffer. + /// A description of the needed characteristics of the Buffer. + /// + /// The data format for the View that will be seen by Shaders. + /// Specify to use the default format of the Buffer. + /// + /// A Buffer with the specified . protected virtual Buffer CreateBuffer(BufferDescription description, PixelFormat viewFormat) { return Buffer.New(GraphicsDevice, description, viewFormat); } + /// + /// Creates a Query Pool for a specific number of GPU Queries of a given type. + /// + /// + /// A description of the needed characteristics of the Query Pool, like the number of GPU Queries and their type. + /// + /// Ignored for Query Pools, as they do not have a format. + /// A Query Pool with the specified types and count. protected virtual QueryPool CreateQueryPool(QueryPoolDescription description, PixelFormat viewFormat) { return QueryPool.New(GraphicsDevice, description.QueryType, description.QueryCount); } + + /// + /// Disposes the current Graphics Resource allocator by disposing all the associated resources and clearing all the caches. + /// protected override void Destroy() { lock (thisLock) { - DisposeCache(textureCache, true); - DisposeCache(bufferCache, true); - DisposeCache(queryPoolCache, true); + DisposeCache(textureCache); + DisposeCache(bufferCache); + DisposeCache(queryPoolCache); } base.Destroy(); - } - private void DisposeCache(Dictionary> cache, bool clearKeys) - { - foreach (var resourceList in cache.Values) + /// + /// Disposes and removes all the resources in a resource cache. + /// + static void DisposeCache(ResourceCache cache) { + foreach (var resourceList in cache.Values) foreach (var resource in resourceList) { resource.Resource.Dispose(); } - if (!clearKeys) - { - resourceList.Clear(); - } - } - if (clearKeys) - { cache.Clear(); } } - private BufferDescription GetBufferDescription(Buffer buffer) - { - return buffer.Description; - } - private TextureDescription GetTextureDefinition(Texture texture) - { - return texture.Description; - } - - private QueryPoolDescription GetQueryPoolDefinition(QueryPool queryPool) - { - return new QueryPoolDescription(queryPool.QueryType, queryPool.QueryCount); - } + /// + /// Gets a temporary Graphics Resource with the specified description. + /// + /// The type of the Graphics Resource. + /// The type of an object that describes the characteristics of the Graphics Resource. + /// The cache of allocated Graphics Resources of the intended type. + /// A description of the needed characteristics of the Graphics Resource. + /// + /// A delegate that creates a Graphics Resource given a description and a data format for SRVs. + /// See , , or for examples. + /// + /// + /// A delegate that retrieves the actual description of a Graphics Resource. + /// See , , or for examples. + /// + /// + /// The data format for the View that will be seen by Shaders. + /// Specify to use the default format of the Graphics Resource. + /// Some Graphics Resources may not have a View Format, like Query Pools. + /// + /// A temporary Graphics Resource with the specified . + private TResource GetTemporaryResource( + ResourceCache cache, + TDescription description, + CreateResourceDelegate createResource, + GetDescriptionDelegate getDescription, + PixelFormat viewFormat) - private TResource GetTemporaryResource(Dictionary> cache, TKey description, Func creator, Func getDefinition, PixelFormat viewFormat) where TResource : GraphicsResourceBase - where TKey : struct + where TDescription : struct { - // For a specific description, get allocated textures - List resourceLinks; - if (!cache.TryGetValue(description, out resourceLinks)) - { - resourceLinks = new List(); - cache.Add(description, resourceLinks); - } + // For a specific description, get allocated resources + List resourceLinks = GetOrCreateCache(description); - // Find a texture available + // Find an available resource (non-referenced, but not disposed) foreach (var resourceLink in resourceLinks) { if (resourceLink.ReferenceCount == 0) { - UpdateCounter(resourceLink, 1); - return (TResource)resourceLink.Resource; + UpdateCounter(resourceLink, +1); + return (TResource) resourceLink.Resource; } } - // If no texture available, then creates a new one - var newResource = creator(description, viewFormat); - newResource.Name = string.Format("{0}{1}-{2}", Name == null ? string.Empty : string.Format("{0}-", Name), newResource.Name == null ? newResource.GetType().Name : Name, resourceLinks.Count); + // If no resources are available, then create a new one + var newResource = createResource(description, viewFormat); - // Description may be altered when creating a resource (based on HW limitations...etc.) so we get the actual description - var realDescription = getDefinition(newResource); + string allocatorName = string.IsNullOrWhiteSpace(Name) ? string.Empty : $"{Name}-"; + string resourceName = string.IsNullOrWhiteSpace(newResource.Name) ? newResource.GetType().Name : Name; - // For a specific description, get allocated textures - if (!cache.TryGetValue(realDescription, out resourceLinks)) - { - resourceLinks = new List(); - cache.Add(description, resourceLinks); - } + newResource.Name = $"{allocatorName}{resourceName}-{resourceLinks.Count}"; + + // Description may be altered when creating a resource (based on hardware limitations, etc.) + // We get here its actual final description + var realDescription = getDescription(newResource); - // Add the texture to the allocated textures - // Start RefCount == 1, because we don't want this texture to be available if a post FxProcessor is calling - // several times this GetTemporaryTexture method. + // Get or create the resource cache for the new description + resourceLinks = GetOrCreateCache(realDescription); + + // Add the resource to the allocated resources + // Start with RefCount == 1, because we don't want this resource to be available if a post-FX processor is calling + // several times this GetTemporaryTexture method. var newResourceLink = new GraphicsResourceLink(newResource) { ReferenceCount = 1 }; resourceLinks.Add(newResourceLink); return newResource; - } - /// - /// Recycles the specified cache. - /// - /// The type of the t key. - /// The cache. - /// The recycle policy. - private void Recycle(Dictionary> cache, GraphicsResourceRecyclePolicyDelegate recyclePolicy) - { - foreach (var resourceList in cache.Values) + // + // Gets or creates a cache for allocated Graphics Resources matching the specified description. + // + List GetOrCreateCache(TDescription description) { - for (int i = resourceList.Count - 1; i >= 0; i--) + // For a specific description, get allocated resources + if (!cache.TryGetValue(description, out List resourceLinks)) { - var resourceLink = resourceList[i]; - if (resourceLink.ReferenceCount == 0) - { - if (recyclePolicy(resourceLink)) - { - resourceLink.Resource.Dispose(); - resourceList.RemoveAt(i); - } - // Reset the access count - resourceLink.AccessCountSinceLastRecycle = 0; - } + // If no resources are allocated for this description, create a new list + cache.Add(description, resourceLinks = []); } + return resourceLinks; } } + + /// + /// Updates the reference count for a specified Graphics Resource. + /// + /// + /// The Graphics Resource whose reference count is to be updated. + /// Must be a , , or . + /// + /// + /// The change in the reference count. Positive values increase the count, while negative values decrease it. + /// + /// + /// Thrown if is not a , , or , + /// or if the Graphics Resource was not allocated by this allocator. + /// + /// + /// The is invalid. It cannot make the reference count of the negative. + /// private void UpdateReference(GraphicsResourceBase resource, int referenceDelta) { - if (resource == null) - { + if (resource is null) return; - } bool resourceFound = false; - switch (resource) + resourceFound = resource switch { - case Texture texture: - resourceFound = UpdateReferenceCount(textureCache, texture, getTextureDefinitionDelegate, referenceDelta); - break; - - case Buffer buffer: - resourceFound = UpdateReferenceCount(bufferCache, buffer, getBufferDescriptionDelegate, referenceDelta); - break; + Texture texture => UpdateReferenceCount(textureCache, texture, GetTextureDescription, referenceDelta), + Buffer buffer => UpdateReferenceCount(bufferCache, buffer, GetBufferDescription, referenceDelta), + QueryPool queryPool => UpdateReferenceCount(queryPoolCache, queryPool, GetQueryPoolDescription, referenceDelta), - case QueryPool queryPool: - resourceFound = UpdateReferenceCount(queryPoolCache, queryPool, getQueryPoolDescriptionDelegate, referenceDelta); - break; - - default: - throw new ArgumentException("Unsupported graphics resource. Only Textures, Buffers and QueryPools are supported", nameof(resource)); - } + _ => throw new ArgumentException("Unsupported Graphics Resource. Only Textures, Buffers and QueryPools are supported", nameof(resource)) + }; if (!resourceFound) - { - throw new ArgumentException("The resource was not allocated by this allocator"); - } + throw new ArgumentException("The Graphics Resource was not allocated by this allocator", nameof(resource)); } - private bool UpdateReferenceCount(Dictionary> cache, TResource resource, Func getDefinition, int deltaCount) + /// + /// Updates the reference count for a specified Graphics Resource. + /// + /// The type of the Graphics Resource. + /// The type of an object that describes the characteristics of the Graphics Resource. + /// The cache of allocated Graphics Resources of the intended type. + /// The Graphics Resource whose reference count is to be updated. + /// + /// A delegate that retrieves the actual description of a Graphics Resource. + /// See , , or for examples. + /// + /// + /// The change in the reference count. Positive values increase the count, while negative values decrease it. + /// + /// + /// if the reference count for was updated; otherwise. + /// + /// + /// The is invalid. It cannot make the reference count of the negative. + /// + private bool UpdateReferenceCount( + ResourceCache cache, + TResource resource, + GetDescriptionDelegate getDescription, + int referenceDelta) + where TResource : GraphicsResourceBase - where TKey : struct + where TDescription : struct { - if (resource == null) + if (resource is null || referenceDelta == 0) { return false; } - List resourceLinks; - if (cache.TryGetValue(getDefinition(resource), out resourceLinks)) + // Check if the resource is known by this allocator + if (cache.TryGetValue(getDescription(resource), out List resourceLinks)) { foreach (var resourceLink in resourceLinks) { if (resourceLink.Resource == resource) { - UpdateCounter(resourceLink, deltaCount); + UpdateCounter(resourceLink, referenceDelta); return true; } } } + // Resource not found in the cache return false; } - private void UpdateCounter(GraphicsResourceLink resourceLink, int deltaCount) + /// + /// Updates the reference count for a specified Graphics Resource. + /// + /// The Graphics Resource whose reference count is to be updated. + /// + /// The change in the reference count. Positive values increase the count, while negative values decrease it. + /// + /// + /// The is invalid. It cannot make the reference count of the negative. + /// + private void UpdateCounter(GraphicsResourceLink resourceLink, int referenceDelta) { - if ((resourceLink.ReferenceCount + deltaCount) < 0) + if ((resourceLink.ReferenceCount + referenceDelta) < 0) { - throw new InvalidOperationException("Invalid decrement on reference count (must be >=0 after decrement). Current reference count: [{0}] Decrement: [{1}]".ToFormat(resourceLink.ReferenceCount, deltaCount)); + throw new ArgumentException("Invalid delta on reference count. It must be non-negative after updating. " + + $"Current reference count: [{resourceLink.ReferenceCount}] Delta: [{referenceDelta}]"); } - resourceLink.ReferenceCount += deltaCount; + resourceLink.ReferenceCount += referenceDelta; resourceLink.AccessTotalCount++; resourceLink.AccessCountSinceLastRecycle++; resourceLink.LastAccessTime = DateTime.Now; + // If no alive references are left, we can tag the resource for discarding on next Map if (resourceLink.ReferenceCount == 0) - GraphicsDevice.TagResource(resourceLink); - } - - /// - /// The default recycle policy is always going to remove all allocated textures. - /// - /// The resource link. - /// true if XXXX, false otherwise. - private static bool DefaultRecyclePolicy(GraphicsResourceLink resourceLink) - { - return true; - } - - protected struct QueryPoolDescription : IEquatable - { - public QueryType QueryType; - - public int QueryCount; - - public QueryPoolDescription(QueryType queryType, int queryCount) - { - QueryType = queryType; - QueryCount = queryCount; - } - - public bool Equals(QueryPoolDescription other) - { - return QueryType == other.QueryType && QueryCount == other.QueryCount; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is QueryPoolDescription && Equals((QueryPoolDescription)obj); - } - - public override int GetHashCode() - { - unchecked - { - return ((int)QueryType * 397) ^ QueryCount; - } - } - - public static bool operator ==(QueryPoolDescription left, QueryPoolDescription right) - { - return left.Equals(right); - } - - public static bool operator !=(QueryPoolDescription left, QueryPoolDescription right) - { - return !left.Equals(right); - } + GraphicsDevice.TagResourceAsNotAlive(resourceLink); } } } diff --git a/sources/engine/Stride.Graphics/GraphicsResourceBase.cs b/sources/engine/Stride.Graphics/GraphicsResourceBase.cs index 86acd92abf..5c394b0c51 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceBase.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceBase.cs @@ -1,116 +1,152 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Base class for a Graphics Resource. +/// +public partial class GraphicsResourceBase : ComponentBase { - public partial class GraphicsResourceBase : ComponentBase + /// + /// Lifetime state of the Graphics Resource. + /// + internal GraphicsResourceLifetimeState LifetimeState; + + /// + /// A that will be invoked when the is reset and the + /// Graphics Resource needs to be created / loaded again. + /// + public Action Reload; + + /// + /// Gets the the Graphics Resource depends on. + /// + public GraphicsDevice GraphicsDevice { get; private set; } + + /// + /// Raised when the internal Graphics Resource gets destroyed. + /// + /// + /// This event is useful when user-allocated handles associated with the internal resource need to be released. + /// + public event EventHandler Destroyed; + + + /// + /// Initializes a new instance of the class. + /// + protected GraphicsResourceBase() : this(device: null, name: null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The graphics device. + protected GraphicsResourceBase(GraphicsDevice device) : this(device, name: null) { } + + /// + /// Initializes a new instance of the class. + /// + /// The graphics device. + /// + /// A name that can be used to identify the Graphics Resource. + /// Specify to use the type's name instead. + /// + protected GraphicsResourceBase(GraphicsDevice device, string? name) : base(name) { - internal GraphicsResourceLifetimeState LifetimeState; - public Action Reload; - - /// - /// Gets the graphics device attached to this instance. - /// - /// The graphics device. - public GraphicsDevice GraphicsDevice - { - get; - private set; - } + AttachToGraphicsDevice(device); + } - /// - /// Raised when the internal graphics resource gets destroyed. - /// This event is useful when user allocated handles associated with the internal resource need to be released. - /// - public event EventHandler Destroyed; - - /// - /// Initializes a new instance of the class. - /// - protected GraphicsResourceBase() - : this(null, null) - { - } - /// - /// Initializes a new instance of the class. - /// - /// The device. - protected GraphicsResourceBase(GraphicsDevice device) : this(device, null) - { - } + /// + /// Registers this Graphics Resource with the specified . + /// + /// The graphics device. + internal void AttachToGraphicsDevice(GraphicsDevice device) + { + GraphicsDevice = device; - /// - /// Initializes a new instance of the class. - /// - /// The device. - /// The name. - protected GraphicsResourceBase(GraphicsDevice device, string name) : base(name) + if (device is not null) { - AttachToGraphicsDevice(device); + // Add this Graphics Resource to the device's resources + var resources = device.Resources; + lock (resources) + { + resources.Add(this); + } } - internal void AttachToGraphicsDevice(GraphicsDevice device) - { - GraphicsDevice = device; + Initialize(); + } - if (device != null) - { - // Add GraphicsResourceBase to device resources - var resources = device.Resources; - lock (resources) - { - resources.Add(this); - } - } + /// + /// Perform platform-specific initialization of the Graphics Resource. + /// + private partial void Initialize(); - Initialize(); - } + /// + /// Called when the is inactive (put in the background and rendering is paused). + /// By default, it does nothing. + /// + /// + /// This method may be overriden in derived classes to voluntarily release objects that can be easily recreated, + /// such as Dynamic Buffers and Frame Buffers / Render Targets. + /// + /// + /// if the Graphics Resource has transitioned to the state. + /// + protected internal virtual bool OnPause() + { + return false; + } - /// - /// Called when graphics device is inactive (put in the background and rendering is paused). - /// It should voluntarily release objects that can be easily recreated, such as FBO and dynamic buffers. - /// - /// True if item transitioned to a state. - protected internal virtual bool OnPause() - { - return false; - } + /// + /// Called when the has resumed from either a paused or destroyed state. + /// By default, it does nothing. + /// + /// + /// This method may be overriden in derived classes to recreate the Graphics Resource if possible. + /// + protected internal virtual void OnResume() + { + } - /// - /// Called when graphics device is resumed from either paused or destroyed state. - /// If possible, resource should be recreated. - /// - protected internal virtual void OnResume() - { - } + /// + /// Disposes the resources associated with the Graphics Resource, removes itself from + /// the 's resource registry, and transitions to the + /// state. + /// + protected override void Destroy() + { + var device = GraphicsDevice; - /// - protected override void Destroy() + if (device is not null) { - var device = GraphicsDevice; - - if (device != null) + // Remove this Graphics Resource from the GraphicsDevice's resources + var resources = device.Resources; + lock (resources) { - // Remove GraphicsResourceBase from device resources - var resources = device.Resources; - lock (resources) - { - resources.Remove(this); - } - if (LifetimeState != GraphicsResourceLifetimeState.Destroyed) - { - OnDestroyed(); - LifetimeState = GraphicsResourceLifetimeState.Destroyed; - } + resources.Remove(this); } + if (LifetimeState != GraphicsResourceLifetimeState.Destroyed) + { + OnDestroyed(); + LifetimeState = GraphicsResourceLifetimeState.Destroyed; + } + } - // No need for reload anymore, allow it to be GC - Reload = null; + // No need for reload anymore, allow the delegate to be GC + Reload = null; - base.Destroy(); - } + base.Destroy(); } + + /// + /// Called when the has been detected to be internally destroyed, + /// or when the methad has been called. Raises the event. + /// + protected internal virtual partial void OnDestroyed(); } diff --git a/sources/engine/Stride.Graphics/GraphicsResourceFactoryBase.cs b/sources/engine/Stride.Graphics/GraphicsResourceFactoryBase.cs index cf243acb73..5fde520e0f 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceFactoryBase.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceFactoryBase.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2011 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,18 +23,25 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Base class for all Graphics Resource factories. +/// +public abstract class GraphicsResourceFactoryBase : ComponentBase { /// - /// Base factory for all Graphics resources. + /// Gets or sets the Graphics Device the created resources depend on. /// - public class GraphicsResourceFactoryBase : ComponentBase - { - protected internal GraphicsDevice GraphicsDevice { get; set; } + protected internal GraphicsDevice GraphicsDevice { get; set; } - protected internal GraphicsResourceFactoryBase(GraphicsDevice device) - { - GraphicsDevice = device; - } + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + protected internal GraphicsResourceFactoryBase(GraphicsDevice device) + { + GraphicsDevice = device; } } diff --git a/sources/engine/Stride.Graphics/GraphicsResourceLifetimeState.cs b/sources/engine/Stride.Graphics/GraphicsResourceLifetimeState.cs new file mode 100644 index 0000000000..0b77e2fa64 --- /dev/null +++ b/sources/engine/Stride.Graphics/GraphicsResourceLifetimeState.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Graphics +{ + /// + /// Describes the lifetime state of a graphics resource. + /// + public enum GraphicsResourceLifetimeState + { + /// + /// The resource is active and available for use. + /// + Active = 0, + + /// + /// The resource is in a reduced state (partially or completely destroyed) because application is in the background. + /// Context should still be alive. + /// + /// + /// This is useful for freeing dynamic resources such as Frame Buffers / Render Targets, that could be easily restored when application is resumed. + /// + Paused = 1, + + /// + /// The resource has been destroyed due to the graphics device being destroyed. + /// It will need to be recreated or reloaded when rendering resumes. + /// + Destroyed = 2, + + // Not sure if this one will be useful yet (in case of async reloading?) + // Reloading = 3, + } +} diff --git a/sources/engine/Stride.Graphics/GraphicsResourceLink.cs b/sources/engine/Stride.Graphics/GraphicsResourceLink.cs index 28543d3568..d798ada433 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceLink.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceLink.cs @@ -3,49 +3,47 @@ using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// An object linking a Graphics Resource allocated by a +/// and providing allocation information. +/// +public sealed class GraphicsResourceLink { /// - /// A resource allocated by providing allocation informations. + /// Initializes a new instance of the class. /// - public sealed class GraphicsResourceLink + /// The allocated Graphics Resource. + /// is . + internal GraphicsResourceLink(GraphicsResourceBase resource) { - /// - /// Initializes a new instance of the class. - /// - /// The graphics resource. - /// resource - internal GraphicsResourceLink(GraphicsResourceBase resource) - { - Resource = resource ?? throw new ArgumentNullException(nameof(resource)); - } - - /// - /// The graphics resource. - /// - public GraphicsResourceBase Resource { get; } - - /// - /// Gets the last time this resource was accessed. - /// - /// The last access time. - public DateTime LastAccessTime { get; internal set; } - - /// - /// Gets the total count of access to this resource (include Increment and Decrement) - /// - /// The access total count. - public long AccessTotalCount { get; internal set; } - - /// - /// Gets the access count since last recycle policy was run. - /// - /// The access count since last recycle. - public long AccessCountSinceLastRecycle { get; internal set; } - - /// - /// The number of active reference to this resource. - /// - public int ReferenceCount { get; internal set; } + Resource = resource ?? throw new ArgumentNullException(nameof(resource)); } + + + /// + /// Gets the allocated Graphics Resource. + /// + public GraphicsResourceBase Resource { get; } + + /// + /// Gets the last time the Graphics Resource was accessed. + /// + public DateTime LastAccessTime { get; internal set; } + + /// + /// Gets the total number of times the Graphics Resource has been accessed (including Increment and Decrement). + /// + public long AccessTotalCount { get; internal set; } + + /// + /// Gets the number of times the Graphics Resource has been accessed since the last recycle policy was run. + /// + public long AccessCountSinceLastRecycle { get; internal set; } + + /// + /// Gets the number of active references to the Graphics Resource. + /// + public int ReferenceCount { get; internal set; } } diff --git a/sources/engine/Stride.Graphics/GraphicsResourceRecyclePolicyDelegate.cs b/sources/engine/Stride.Graphics/GraphicsResourceRecyclePolicyDelegate.cs index 23fa2fcc1c..5b222d554e 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceRecyclePolicyDelegate.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceRecyclePolicyDelegate.cs @@ -1,12 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics -{ - /// - /// A recycle policy to check whether the specified resource must be disposed from a . - /// - /// The resource link. - /// true if the specified resource must be disposed and remove from the , false otherwise. - public delegate bool GraphicsResourceRecyclePolicyDelegate(GraphicsResourceLink resourceLink); -} +namespace Stride.Graphics; + +/// +/// A recycle policy to check whether a Graphics Resource tracked by a +/// must be disposed / recycled. +/// +/// The Graphics Resource link. +/// +/// if the specified Graphics Resource must be disposed and removed from its allocator; +/// otherwise. +/// +public delegate bool GraphicsResourceRecyclePolicyDelegate(GraphicsResourceLink resourceLink); diff --git a/sources/engine/Stride.Graphics/GraphicsResourceState.cs b/sources/engine/Stride.Graphics/GraphicsResourceState.cs index b07f8757b6..c215a8d928 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceState.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceState.cs @@ -1,31 +1,217 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +using System; + +namespace Stride.Graphics; + +/// +/// Defines constants that specify the state of a Graphics Resource regarding how the resource is being used. +/// +[Flags] +public enum GraphicsResourceState { + // From D3D12_RESOURCE_STATES in d3d12.h + + /// + /// The application should transition to this state only for accessing a Graphics Resource across different graphics engine types. + /// + /// Specifically, a Graphics Resource must be in the Common state before being used on a Copy Queue + /// (when previously used on a Direct / Compute Queue), and before being used on a Direct / Compute Queue + /// (when previously used on Copy Queue). This restriction doesn't exist when accessing data between Direct + /// and Compute queues. + /// + /// + /// The Common state can be used for all usages on a Copy Queue using the implicit state transitions. + /// For more information, read about Multi-engine synchronization. + /// + /// + /// Additionally, Textures must be in the Common state for CPU access to be legal, assuming the Texture + /// was created in a CPU-visible heap in the first place. + /// + /// + Common = 0, + + /// + /// Synonymous with the flag. + /// + Present = 0, + + /// + /// A Graphics Sub-Resource must be in this state when it is accessed by the GPU as a Vertex Buffer or Constant Buffer. + /// + /// This is a read-only state. + /// + /// + VertexAndConstantBuffer = 1, + + /// + /// A Graphics Sub-Resource must be in this state when it is accessed by the 3D pipeline as an Index Buffer. + /// + /// This is a read-only state. + /// + /// + IndexBuffer = 2, + + /// + /// The Graphics Resource is used as a Render Target. + /// + /// A Graphics Sub-Resource must be in this state when it is rendered to, + /// or when it is cleared with . + /// + /// + /// This is a write-only state. To read from a Render Target as a Shader Resource, the Graphics Resource must be in either + /// or state. + /// + /// + RenderTarget = 4, + /// - /// Describes the lifetime state of a graphics resource. - /// - public enum GraphicsResourceLifetimeState - { - /// - /// Resource is active and available for use. - /// - Active = 0, - - /// - /// Resource is in a reduced state (partially or completely destroyed) because application is in the background. - /// Context should still be alive. - /// This is useful for freeing dynamic resources such as FBO, that could be easily restored when application is resumed. - /// - Paused = 1, - - /// - /// Resource has been destroyed due to graphics device being destroyed. - /// It will need to be recreated or reloaded when rendering resume. - /// - Destroyed = 2, - - // Not sure if this one will be useful yet (in case of async reloading?) - // Reloading = 3, - } + /// The Graphics Resource is used for Unordered Access. + /// + /// A Graphics Sub-Resource must be in this state when it is accessed by the GPU via an Unordered Access View. + /// A Graphics Sub-Resource must also be in this state when it is cleared with . + /// + /// + /// This is a read / write state. + /// + /// + UnorderedAccess = 8, + + /// + /// This state should be used for + /// when the flags (see ) indicate a given Graphics Sub-Resource should be cleared + /// (otherwise the Graphics Sub-Resource state doesn't matter), or when using it in a writable Depth-Stencil View + /// when the Pipeline State has depth write enabled (see ). + /// + /// This state is mutually exclusive with other states. + /// + /// + DepthWrite = 0x10, + + /// + /// This state should be used when the Graphics Sub-Resource is in a read-only Depth-Stencil View, + /// or when depth write is disabled (see ). + /// + /// It can be combined with other read states (for example, ), such that the Graphics Resource + /// can be used for the Depth or Stencil test, and accessed by a Shader within the same draw call. + /// + /// + /// Using it when depth will be written by a draw call or clear command is invalid. + /// + /// + DepthRead = 0x20, + + /// + /// The Graphics Resource is used with a Shader other than the Pixel Shader. + /// + /// A Graphics Sub-Resource must be in this state before being read by any stage (except for the Pixel Shader stage) + /// via a Shader Resource View. + /// You can still use the Graphics Resource in a Pixel Shader with this flag as long as it also has the flag set. + /// + /// + /// This is a read-only state. + /// + /// + NonPixelShaderResource = 0x40, + + /// + /// The Graphics Resource is used with a Pixel Shader. + /// + /// A Graphics Sub-Resource must be in this state before being read by the Pixel Shader via a Shader Resource View. + /// + /// + /// This is a read-only state. + /// + /// + PixelShaderResource = 0x80, + + /// + /// The Graphics Resource can be used in both Pixel and non-Pixel Shaders. + /// + /// + /// This value is a combination of the and flags, + /// allowing it to be used in scenarios where both types of Shader Resources are required. + /// + AllShaderResource = NonPixelShaderResource | PixelShaderResource, // 0x40 | 0x80 + + /// + /// The Graphics Resource is used with Stream Output. + /// + /// A Graphics Sub-Resource must be in this state when it is accessed by the 3D pipeline as a Stream-Out target. + /// + /// + /// This is a write-only state. + /// + /// + StreamOut = 0x100, + + /// + /// The Graphics Resource is used as an Indirect Argument. + /// + /// Graphics Sub-Resources must be in this state when they are used as the Argument Buffer passed to a + /// indirect drawing method like or . + /// + /// + /// This is a read-only state. + /// + /// + IndirectArgument = 0x200, + + /// + /// The Graphics Resource is used for Predication. + /// + /// + /// Predication is a feature that enables the GPU rather than the CPU to determine to not draw, copy, or dispatch an object. + /// + /// The typical use of predication is with occlusion; if a bounding box is drawn and is occluded, there is obviously no point + /// in drawing the object itself. In this situation, the drawing of the object can be "predicated", enabling its removal from + /// actual rendering by the GPU. + /// + /// + Predication = 0x200, + + /// + /// The Graphics Resource is used as the destination in a copy operation. + /// + /// Graphics Sub-Resources must be in this state when they are used as the destination of a copy operation, + /// or a blt operation. + /// + /// + /// This is a write-only state. + /// + /// + CopyDestination = 0x400, + + /// + /// The Graphics Resource is used as the source in a copy operation. + /// + /// Graphics Sub-Resources must be in this state when they are used as the source of a copy operation, + /// or a blt operation. + /// + /// + /// This is a read-only state. + /// + /// + CopySource = 0x800, + + /// + /// This state is the required starting state for an upload heap. It is a combination of other read-state bits. + /// + /// The application should generally avoid transitioning to GenericRead when possible, since that + /// can result in premature cache flushes, or Graphics Resource layout changes (for example, compress / decompress), + /// causing unnecessary pipeline stalls. You should instead transition resources only to the actually-used states. + /// + /// + GenericRead = VertexAndConstantBuffer | IndexBuffer | NonPixelShaderResource | PixelShaderResource | IndirectArgument | CopySource, // 0x1 | 0x2 | 0x40 | 0x80 | 0x200 | 0x800 + + /// + /// The Graphics Resource is used as the destination in a resolve operation. + /// + ResolveDestination = 0x1000, + + /// + /// The Graphics Resource is used as the source in a resolve operation. + /// + ResolveSource = 0x2000 } diff --git a/sources/engine/Stride.Graphics/GraphicsResourceUsage.cs b/sources/engine/Stride.Graphics/GraphicsResourceUsage.cs index 2cec7ac113..db5424062a 100644 --- a/sources/engine/Stride.Graphics/GraphicsResourceUsage.cs +++ b/sources/engine/Stride.Graphics/GraphicsResourceUsage.cs @@ -5,29 +5,40 @@ namespace Stride.Graphics { /// - /// Identifies expected resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the GPU. + /// Identifies the intended use of a resource during rendering. The usage directly reflects whether a resource + /// is accessible by the CPU and/or the GPU. /// [DataContract] public enum GraphicsResourceUsage { /// - /// A resource that requires read and write access by the GPU. This is likely to be the most common usage choice. + /// A resource that requires read and write access by the GPU. This is likely to be the most common usage choice. /// - Default = unchecked((int)0), + Default = 0, /// - /// A resource that can only be read by the GPU. It cannot be written by the GPU, and cannot be accessed at all by the CPU. This type of resource must be initialized when it is created, since it cannot be changed after creation. + /// A resource that can only be read by the GPU. + /// It cannot be written by the GPU, and cannot be accessed at all by the CPU. /// - Immutable = unchecked((int)1), + /// + /// This type of resource must be initialized when it is created, since it cannot be changed after creation. + /// + Immutable = 1, /// - /// A resource that is accessible by both the GPU (read only) and the CPU (write only). A dynamic resource is a good choice for a resource that will be updated by the CPU at least once per frame. To update a dynamic resource, use a Map method. + /// A resource that is accessible by both the GPU (read-only) and the CPU (write-only). /// - Dynamic = unchecked((int)2), + /// + /// A dynamic resource is a good choice for a resource that will be updated by the CPU at least + /// once per frame. + /// + /// To update a dynamic resource, use a Map method. + /// + Dynamic = 2, /// - /// A resource that supports data transfer (copy) from the GPU to the CPU. + /// A resource that supports data transfer (copy) from the GPU to the CPU. /// - Staging = unchecked((int)3), + Staging = 3 } } diff --git a/sources/engine/Stride.Graphics/IGraphicsDeviceService.cs b/sources/engine/Stride.Graphics/IGraphicsDeviceService.cs index a7e4f7b248..2ad74d188d 100644 --- a/sources/engine/Stride.Graphics/IGraphicsDeviceService.cs +++ b/sources/engine/Stride.Graphics/IGraphicsDeviceService.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,34 +26,34 @@ namespace Stride.Graphics { /// - /// Service providing method to access GraphicsDevice life-cycle. + /// Service providing access to the and events that can be + /// subscribed to be notified of when the device is created, reset, or disposed. /// public interface IGraphicsDeviceService { /// - /// Occurs when a device is created. + /// Occurs when a device is created. /// event EventHandler DeviceCreated; /// - /// Occurs when a device is disposing. + /// Occurs when a device is being disposed. /// event EventHandler DeviceDisposing; /// - /// Occurs when a device is reseted. + /// Occurs when a device has been reset. /// event EventHandler DeviceReset; /// - /// Occurs when a device is resetting. + /// Occurs when a device is going to be reset. /// event EventHandler DeviceResetting; /// - /// Gets the current graphcs device. + /// Gets the current graphcs device. /// - /// The graphics device. GraphicsDevice GraphicsDevice { get; } } } diff --git a/sources/engine/Stride.Graphics/IndexBufferBinding.cs b/sources/engine/Stride.Graphics/IndexBufferBinding.cs index 3db5988235..bf8e339a3c 100644 --- a/sources/engine/Stride.Graphics/IndexBufferBinding.cs +++ b/sources/engine/Stride.Graphics/IndexBufferBinding.cs @@ -1,31 +1,118 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; + using Stride.Core.Serialization; -using Stride.Core.Serialization.Serializers; namespace Stride.Graphics { + /// + /// Binding structure that specifies an Index Buffer for a Graphics Device. + /// [DataSerializer(typeof(IndexBufferBinding.Serializer))] - public class IndexBufferBinding + public class IndexBufferBinding : IEquatable { - public IndexBufferBinding(Buffer indexBuffer, bool is32Bit, int count, int indexOffset = 0) + /// + /// Initializes a new instance of the class. + /// + /// The Index Buffer to bind. + /// + /// A value indicating if the indices are 16-bit (), or 32-bit (). + /// + /// The number of indices in the Buffer to use. + /// + /// The offset (in number of indices) from the beginning of the Buffer to the first index to use. + /// Default is 0, meaning the first index in the Buffer will be used. + /// + /// + /// is . + /// + public IndexBufferBinding(Buffer indexBuffer, bool is32Bit, int indexCount, int indexOffset = 0) { - if (indexBuffer == null) throw new ArgumentNullException("indexBuffer"); + ArgumentNullException.ThrowIfNull(indexBuffer); + Buffer = indexBuffer; - Is32Bit = is32Bit; + Is32Bit = is32Bit; // TODO: Should we use the enum IndexElementSize instead? Offset = indexOffset; - Count = count; + Count = indexCount; } + + /// + /// Gets the Index Buffer to bind. + /// public Buffer Buffer { get; private set; } + + /// + /// Gets a value indicating if the Buffer contains 32-bit indices. + /// + /// + /// if the Buffer contains 32-bit indices; + /// if the Buffer contains 16-bit indices. + /// public bool Is32Bit { get; private set; } + + /// + /// Gets the offset (in number of indices) from the beginning of the to the first index to use. + /// public int Offset { get; private set; } + /// + /// Gets the number of indices in the Buffer to use. + /// public int Count { get; private set; } + + /// + public bool Equals(IndexBufferBinding other) + { + if (other is null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + return Buffer.Equals(other.Buffer) + && Offset == other.Offset + && Count == other.Count + && Is32Bit == other.Is32Bit; + } + + /// + public override bool Equals(object obj) + { + return obj is IndexBufferBinding ibb && Equals(ibb); + } + + public static bool operator ==(IndexBufferBinding left, IndexBufferBinding right) + { + return left.Equals(right); + } + + public static bool operator !=(IndexBufferBinding left, IndexBufferBinding right) + { + return !(left == right); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Buffer, Is32Bit, Count, Offset); + } + + #region Serializer + + /// + /// Provides functionality to serialize and deserialize objects. + /// internal class Serializer : DataSerializer { + /// + /// Serializes or deserializes a object. + /// + /// The object to serialize or deserialize. + /// public override void Serialize(ref IndexBufferBinding indexBufferBinding, ArchiveMode mode, SerializationStream stream) { if (mode == ArchiveMode.Deserialize) @@ -46,5 +133,7 @@ public override void Serialize(ref IndexBufferBinding indexBufferBinding, Archiv } } } + + #endregion } } diff --git a/sources/engine/Stride.Graphics/IndexElementSize.cs b/sources/engine/Stride.Graphics/IndexElementSize.cs index a7b2c05721..b22be7efc2 100644 --- a/sources/engine/Stride.Graphics/IndexElementSize.cs +++ b/sources/engine/Stride.Graphics/IndexElementSize.cs @@ -1,13 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Specifies the size of indices in an Index Buffer. +/// +public enum IndexElementSize { /// - /// Size of an index + /// The indices are 16-bit integers (2 bytes). /// - public enum IndexElementSize - { - Int16 = 2, - Int32 = 4, - } + Int16 = 2, + + /// + /// The indices are 32-bit integers (4 bytes). + /// + Int32 = 4 } diff --git a/sources/engine/Stride.Graphics/InputClassification.cs b/sources/engine/Stride.Graphics/InputClassification.cs index 75c9f7102c..d514305686 100644 --- a/sources/engine/Stride.Graphics/InputClassification.cs +++ b/sources/engine/Stride.Graphics/InputClassification.cs @@ -1,10 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Specifies whether the data of a Input Element is per-vertex or per-instance. +/// +public enum InputClassification { - public enum InputClassification - { - Vertex, - Instance, - } + /// + /// The data is per-vertex, meaning it represents a standard vertex attribute. + /// + Vertex, + + /// + /// The data is per-instance, meaning it represents information about a particular geometry instance + /// in an instancing scenario. + /// + Instance } diff --git a/sources/engine/Stride.Graphics/InputElementDescription.cs b/sources/engine/Stride.Graphics/InputElementDescription.cs index 9758a67db5..168dfb1d47 100644 --- a/sources/engine/Stride.Graphics/InputElementDescription.cs +++ b/sources/engine/Stride.Graphics/InputElementDescription.cs @@ -1,59 +1,110 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes a single input element for the definition of a vertex format layout. +/// +/// +/// Each input element defines how a portion of a vertex or instance is read from a Vertex Buffer +/// and mapped to a shader input. +///
+/// This structure is typically used to define the layout of vertex data when creating an input layout object. +///
+public struct InputElementDescription : IEquatable { - public struct InputElementDescription : IEquatable + /// + /// The semantic name associated with this element, such as "POSITION", "TEXCOORD", or "NORMAL". + /// + /// + /// This name must match the semantic used in the Vertex Shader input signature. + /// + public string SemanticName; + + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// + /// For example, a 4x4 matrix might be passed as four "TEXCOORD" elements with indices 0 through 3. + /// + public int SemanticIndex; + + /// + /// The data format of the element, such as or . + /// + /// + /// This must match the format expected by the Shader and the layout of the Vertex Buffer. + /// + public PixelFormat Format; + + /// + /// The input slot index from which this element is read. + /// + /// + /// Multiple Vertex Buffers can be bound to different input slots. This value selects which one to use for this element. + /// + public int InputSlot; + + /// + /// The byte offset from the start of the vertex to this element. + /// + /// + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + public int AlignedByteOffset; + + /// + /// Specifies whether the data is per-vertex or per-instance. + /// + /// + /// Use for standard vertex attributes, + /// or for instancing. + /// + public InputClassification InputSlotClass; + + /// + /// The number of instances to draw using the same per-instance data before advancing to the next element. + /// + /// + /// This must be 0 for per-vertex data. For instanced data, a value of 1 means the data advances every instance. + /// + public int InstanceDataStepRate; + + + /// + public readonly bool Equals(InputElementDescription other) + { + return string.Equals(SemanticName, other.SemanticName) + && SemanticIndex == other.SemanticIndex + && Format == other.Format + && InputSlot == other.InputSlot + && AlignedByteOffset == other.AlignedByteOffset + && InputSlotClass == other.InputSlotClass + && InstanceDataStepRate == other.InstanceDataStepRate; + } + + /// + public override readonly bool Equals(object obj) + { + return obj is InputElementDescription iedesc && Equals(iedesc); + } + + public static bool operator ==(InputElementDescription left, InputElementDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(InputElementDescription left, InputElementDescription right) + { + return !left.Equals(right); + } + + /// + public override readonly int GetHashCode() { - public string SemanticName; - public int SemanticIndex; - public PixelFormat Format; - public int InputSlot; - public int AlignedByteOffset; - public InputClassification InputSlotClass; - public int InstanceDataStepRate; - - public bool Equals(InputElementDescription other) - { - return string.Equals(SemanticName, other.SemanticName) - && SemanticIndex == other.SemanticIndex - && Format == other.Format - && InputSlot == other.InputSlot - && AlignedByteOffset == other.AlignedByteOffset - && InputSlotClass == other.InputSlotClass - && InstanceDataStepRate == other.InstanceDataStepRate; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is InputElementDescription && Equals((InputElementDescription)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = SemanticName.GetHashCode(); - hashCode = (hashCode * 397) ^ SemanticIndex; - hashCode = (hashCode * 397) ^ (int)Format; - hashCode = (hashCode * 397) ^ InputSlot; - hashCode = (hashCode * 397) ^ AlignedByteOffset; - hashCode = (hashCode * 397) ^ (int)InputSlotClass; - hashCode = (hashCode * 397) ^ InstanceDataStepRate; - return hashCode; - } - } - - public static bool operator ==(InputElementDescription left, InputElementDescription right) - { - return left.Equals(right); - } - - public static bool operator !=(InputElementDescription left, InputElementDescription right) - { - return !left.Equals(right); - } + return HashCode.Combine(SemanticName, SemanticIndex, Format, InputSlot, AlignedByteOffset, InputSlotClass, InstanceDataStepRate); } } diff --git a/sources/engine/Stride.Graphics/MapMode.cs b/sources/engine/Stride.Graphics/MapMode.cs index d79370238a..c94b08e4e5 100644 --- a/sources/engine/Stride.Graphics/MapMode.cs +++ b/sources/engine/Stride.Graphics/MapMode.cs @@ -1,50 +1,50 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Describes how the CPU is accessing a with the method. +/// +public enum MapMode { /// - /// Describes how the CPU is accessing a with the method. + /// The Graphics Resource is mapped for reading. /// - public enum MapMode - { - /// - /// Resource is mapped for reading. - /// - /// - /// The resource must have been created with usage . - /// - Read = 1, + /// + /// The Graphics Resource must have been created with . + /// + Read = 1, - /// - /// Resource is mapped for writing. - /// - /// - /// The resource must have been created with usage or . - /// - Write = 2, + /// + /// The Graphics Resource is mapped for writing. + /// + /// + /// The Graphics Resource must have been created with or . + /// + Write = 2, - /// - /// Resource is mapped for read-write. - /// - /// - /// The resource must have been created with usage . - /// - ReadWrite = 3, + /// + /// The Graphics Resource is mapped for reading and writing. + /// + /// + /// The Graphics Resource must have been created with . + /// + ReadWrite = 3, - /// - /// Resource is mapped for writing; the previous contents of the resource will be undefined. - /// - /// - /// The resource must have been created with usage . - /// - WriteDiscard = 4, + /// + /// The Graphics Resource is mapped for writing, making the previous contents of the resource undefined. + /// + /// + /// The Graphics Resource must have been created with . + /// + WriteDiscard = 4, - /// - /// Resource is mapped for writing; the existing contents of the resource cannot be overwritten. - /// - /// - /// This flag is only valid on vertex and index buffers. - /// - WriteNoOverwrite = 5, - } + /// + /// The Graphics Resource is mapped for writing, ensuring the existing contents of the resource cannot be overwritten. + /// + /// + /// This flag is only valid on Vertex Buffers and Index Buffers. + /// + WriteNoOverwrite = 5 } diff --git a/sources/engine/Stride.Graphics/MappedResource.cs b/sources/engine/Stride.Graphics/MappedResource.cs index 8806b29c63..69f8d6a70d 100644 --- a/sources/engine/Stride.Graphics/MappedResource.cs +++ b/sources/engine/Stride.Graphics/MappedResource.cs @@ -1,67 +1,68 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// A GPU resource mapped for CPU access. This is returned by using . +/// +public readonly partial struct MappedResource { /// - /// A GPU resource mapped for CPU access. This is returned by using + /// Initializes a new instance of the struct. /// - public partial struct MappedResource + /// The Graphics Resource mapped for CPU access. + /// Index of the mapped sub-resource. + /// The data box specifying how the data is laid out in memory. + internal MappedResource(GraphicsResource resource, int subResourceIndex, DataBox dataBox) : this() { - /// - /// Initializes a new instance of the struct. - /// - /// The resource. - /// Index of the sub resource. - /// The data box. - internal MappedResource(GraphicsResource resource, int subResourceIndex, DataBox dataBox) : this() - { - Resource = resource; - SubResourceIndex = subResourceIndex; - DataBox = dataBox; - OffsetInBytes = 0; - SizeInBytes = -1; - } + Resource = resource; + SubResourceIndex = subResourceIndex; + DataBox = dataBox; + OffsetInBytes = 0; + SizeInBytes = -1; + } - /// - /// Initializes a new instance of the struct. - /// - /// The resource. - /// Index of the sub resource. - /// The data box. - /// Offset since the beginning of the buffer. - /// Size of the mapped resource. - internal MappedResource(GraphicsResource resource, int subResourceIndex, DataBox dataBox, int offsetInBytes, int sizeInBytes) : this() - { - Resource = resource; - SubResourceIndex = subResourceIndex; - DataBox = dataBox; - OffsetInBytes = offsetInBytes; - SizeInBytes = sizeInBytes; - } + /// + /// Initializes a new instance of the struct. + /// + /// The Graphics Resource mapped for CPU access. + /// Index of the mapped sub-resource. + /// The data box specifying how the data is laid out in memory. + /// The offset since the beginning of the buffer, in bytes. + /// The size of the mapped resource, in bytes. + internal MappedResource(GraphicsResource resource, int subResourceIndex, DataBox dataBox, int offsetInBytes, int sizeInBytes) : this() + { + Resource = resource; + SubResourceIndex = subResourceIndex; + DataBox = dataBox; + OffsetInBytes = offsetInBytes; + SizeInBytes = sizeInBytes; + } - /// - /// The resource mapped. - /// - public readonly GraphicsResource Resource; - /// - /// The subresource index. - /// - public readonly int SubResourceIndex; + /// + /// The resource mapped for CPU access. + /// + public readonly GraphicsResource Resource; - /// - /// The data box - /// - public readonly DataBox DataBox; + /// + /// The sub-resource index. + /// + public readonly int SubResourceIndex; - /// - /// the offset of the mapped resource since the beginning of the buffer - /// - public readonly int OffsetInBytes; + /// + /// The data box specifying how the data is laid out in memory. + /// + public readonly DataBox DataBox; - /// - /// the size of the mapped resource - /// - public readonly int SizeInBytes; - } + /// + /// The offset of the mapped resource since the beginning of the buffer, in bytes. + /// + public readonly int OffsetInBytes; + + /// + /// The size of the mapped resource, in bytes. + /// + public readonly int SizeInBytes; } diff --git a/sources/engine/Stride.Graphics/MultisampleLevel.cs b/sources/engine/Stride.Graphics/MultisampleLevel.cs index df57e6577a..4309b492a2 100644 --- a/sources/engine/Stride.Graphics/MultisampleLevel.cs +++ b/sources/engine/Stride.Graphics/MultisampleLevel.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,31 +21,30 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines the level of multisampling. +/// +public enum MultisampleCount { /// - /// Multisample count level. + /// No multisampling. /// - public enum MultisampleCount - { - /// - /// No multisample. - /// - None = 1, + None = 1, - /// - /// Multisample count of 2 pixels. - /// - X2 = 2, + /// + /// Multisampling using two samples per pixel. + /// + X2 = 2, - /// - /// Multisample count of 4 pixels. - /// - X4 = 4, + /// + /// Multisampling using four samples per pixel. + /// + X4 = 4, - /// - /// Multisample count of 8 pixels. - /// - X8 = 8, - } + /// + /// Multisampling using eight samples per pixel. + /// + X8 = 8 } diff --git a/sources/engine/Stride.Graphics/MutablePipelineState.cs b/sources/engine/Stride.Graphics/MutablePipelineState.cs index 4b9942eaa6..dc890b96dd 100644 --- a/sources/engine/Stride.Graphics/MutablePipelineState.cs +++ b/sources/engine/Stride.Graphics/MutablePipelineState.cs @@ -1,35 +1,72 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; +using System.Diagnostics; + using Stride.Core; namespace Stride.Graphics { + using DictionaryOfPipelineStatesByDescription = Dictionary; + + /// + /// A convenience class that allows to compose a Pipeline State by modifying its configuration + /// (as objects are immutable once compiled), and only compile it + /// into a Pipeline State object when needed. + /// + /// + /// To minimize the creation of objects, they are cached internally + /// per Graphics Device based on their description, so subsequent uses of a Pipeline State with + /// the same description uses the same instance instead of allocating a new one. + /// public class MutablePipelineState { private readonly GraphicsDevice graphicsDevice; - private readonly Dictionary cache; - public PipelineStateDescription State; + + // Per-device cache for already known compiled Pipeline States + private readonly DictionaryOfPipelineStatesByDescription cache; /// - /// Current compiled state. + /// Gets the description of the current Pipeline State. /// - public PipelineState CurrentState; + public PipelineStateDescription State { get; } = new(); + /// + /// Gets the current compiled Pipeline State. + /// + /// + /// The current compiled Pipeline State. + /// If this instance have not compiled any Pipeline State, the value will be . + /// + public PipelineState? CurrentState { get; private set; } + + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. public MutablePipelineState(GraphicsDevice graphicsDevice) { this.graphicsDevice = graphicsDevice; - cache = graphicsDevice.GetOrCreateSharedData(typeof(MutablePipelineStateCache), device => new MutablePipelineStateCache()).Cache; + // Create a per-device cache for Pipeline States + cache = graphicsDevice.GetOrCreateSharedData(typeof(PipelineStateCache), device => new PipelineStateCache()); - State = new PipelineStateDescription(); + // Start with the default Pipeline State State.SetDefaults(); } + /// - /// Determine and updates from . + /// Determines if the mutable Pipeline State has changed, and updates the . /// + /// + /// This method uses the current mutable Pipeline State to look for a cached + /// with the same description. If found, it is returned. If not, a new Pipeline State object is compiled + /// from the current description (), and cached for subsequent uses. + /// public void Update() { // Hash current state @@ -38,34 +75,39 @@ public void Update() // Find existing PipelineState object PipelineState pipelineState; - // TODO GRAPHICS REFACTOR We could avoid lock by adding them to a ThreadLocal (or RenderContext) and merge at end of frame + // TODO: GRAPHICS REFACTOR: We could avoid lock by adding them to a ThreadLocal (or RenderContext) and merge at end of frame lock (cache) { if (!cache.TryGetValue(hashedState, out pipelineState)) { - // Otherwise, instantiate it - // First, make an copy + // Otherwise, add it to the cache (a clone, so we can still keep mutating our copy) hashedState = new PipelineStateDescriptionWithHash(State.Clone()); - cache.Add(hashedState, pipelineState = PipelineState.New(graphicsDevice, ref State)); + cache.Add(hashedState, pipelineState = PipelineState.New(graphicsDevice, State)); } } CurrentState = pipelineState; } - private class MutablePipelineStateCache : IDisposable - { - public readonly Dictionary Cache = new Dictionary(); + #region PipelineStateCache + /// + /// A cache of compiled Pipeline States identified by their description (hashed). + /// + [DebuggerDisplay("", Name = $"{nameof(MutablePipelineState)}::{nameof(PipelineStateCache)}")] + private class PipelineStateCache : DictionaryOfPipelineStatesByDescription, IDisposable + { + /// public void Dispose() { - foreach (var pipelineState in Cache) + foreach (var pipelineState in Values) { - ((IReferencable)pipelineState.Value).Release(); + ((IReferencable) pipelineState).Release(); } - - Cache.Clear(); + Clear(); } } + + #endregion } } diff --git a/sources/engine/Stride.Graphics/Null/Buffer.Null.cs b/sources/engine/Stride.Graphics/Null/Buffer.Null.cs index 93afc47a4d..46b094e0ef 100644 --- a/sources/engine/Stride.Graphics/Null/Buffer.Null.cs +++ b/sources/engine/Stride.Graphics/Null/Buffer.Null.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL using System; @@ -10,13 +10,18 @@ namespace Stride.Graphics public partial class Buffer { /// - /// Initializes a new instance of the class. + /// Initializes this instance with the provided options. /// - /// The description. - /// Type of the buffer. - /// The view format. - /// The data pointer. - protected Buffer InitializeFromImpl(BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) + /// A structure describing the buffer characteristics. + /// A combination of flags determining how the Views over this buffer should behave. + /// + /// View format used if the buffer is used as a Shader Resource View, + /// or if not. + /// + /// The data pointer to the data to initialize the buffer with. + /// This same instance of already initialized. + /// Element size (StructureByteStride) must be greater than zero for Structured Buffers. + protected partial Buffer InitializeFromImpl(ref readonly BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) { bufferDescription = description; ViewFlags = viewFlags; @@ -37,5 +42,5 @@ public void Recreate(IntPtr dataPointer) } } } - -#endif + +#endif diff --git a/sources/engine/Stride.Graphics/Null/CommandList.Null.cs b/sources/engine/Stride.Graphics/Null/CommandList.Null.cs index 5e9d3f529e..d023da94db 100644 --- a/sources/engine/Stride.Graphics/Null/CommandList.Null.cs +++ b/sources/engine/Stride.Graphics/Null/CommandList.Null.cs @@ -1,8 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL +using System; using Stride.Core.Mathematics; namespace Stride.Graphics @@ -30,35 +31,38 @@ private CommandList(GraphicsDevice device) : base(device) } /// - /// Reset command List. + /// Resets a Command List back to its initial state as if a new Command List was just created. /// - public void Reset() + public unsafe partial void Reset() { NullHelper.ToImplement(); } /// - /// Closes the command list for recording and returns an executable token. + /// Indicates that recording to the Command List has finished. /// - /// The executable command list. - public CompiledCommandList Close() + /// + /// A representing the frozen list of recorded commands + /// that can be executed at a later time. + /// + public partial CompiledCommandList Close() { NullHelper.ToImplement(); - return default(CompiledCommandList); + return default; } /// - /// Closes and executes the command list. + /// Closes and executes the Command List. /// - public void Flush() + public partial void Flush() { NullHelper.ToImplement(); } /// - /// Clear internal state of the command list. + /// Platform-specific implementation that clears and restores the state of the Graphics Device. /// - private void ClearStateImpl() + private partial void ClearStateImpl() { NullHelper.ToImplement(); } @@ -82,15 +86,25 @@ private void SetRenderTargetsImpl(Texture depthStencilBuffer, int renderTargetCo NullHelper.ToImplement(); } - unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) + /// + /// Platform-specific implementation that sets a scissor rectangle to the rasterizer stage. + /// + /// The scissor rectangle to set. + private unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) { NullHelper.ToImplement(); } - unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) + /// + /// Platform-specific implementation that sets one or more scissor rectangles to the rasterizer stage. + /// + /// The number of scissor rectangles to bind. + /// The set of scissor rectangles to bind. + private unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) { NullHelper.ToImplement(); } + /// /// Sets the stream targets. /// @@ -369,37 +383,111 @@ public void CopyCount(Buffer sourceBuffer, Buffer destBuffer, int offsetInBytes) NullHelper.ToImplement(); } - internal void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox) - { - NullHelper.ToImplement(); - } - - internal void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData) { NullHelper.ToImplement(); } /// - /// Maps a subresource. + /// Copies data from memory to a sub-resource created in non-mappable memory. /// - /// The resource. - /// Index of the sub resource. - /// The map mode. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// The offset information in bytes. - /// The length information in bytes. - /// Pointer to the sub resource to map. - public MappedResource MapSubresource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData) { NullHelper.ToImplement(); - return default(MappedResource); } + // TODO GRAPHICS REFACTOR what should we do with this? + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData, ResourceRegion region) + { + NullHelper.ToImplement(); + } + + // TODO GRAPHICS REFACTOR what should we do with this? + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + internal unsafe partial void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox sourceData, ResourceRegion region) + { + NullHelper.ToImplement(); + } + + // TODO GRAPHICS REFACTOR what should we do with this? + /// + /// Maps a sub-resource of a Graphics Resource to be accessible from CPU memory, and in the process denies the GPU access to that sub-resource. + /// + /// The Graphics Resource to map to CPU memory. + /// The index of the sub-resource to get access to. + /// A value of indicating the way the Graphics Resource should be mapped to CPU memory. + /// + /// A value indicating if this method will return immediately if the Graphics Resource is still being used by the GPU for writing + /// . The default value is , which means the method will wait until the GPU is done. + /// + /// + /// The offset in bytes from the beginning of the mapped memory of the sub-resource. + /// Defaults to 0, which means it is mapped from the beginning. + /// + /// + /// The length in bytes of the memory to map from the sub-resource. + /// Defaults to 0, which means the entire sub-resource is mapped. + /// + /// A structure pointing to the GPU resource mapped for CPU access. + public unsafe partial MappedResource MapSubResource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) + { + NullHelper.ToImplement(); + return default; + } + + // TODO GRAPHICS REFACTOR what should we do with this? /// - /// Unmap resource. + /// Unmaps a sub-resource of a Graphics Resource, which was previously mapped to CPU memory with , + /// and in the process re-enables the GPU access to that sub-resource. /// - /// The mapped resource. - public void UnmapSubresource(MappedResource mapped) + /// + /// A structure identifying the sub-resource to unmap. + /// + public unsafe partial void UnmapSubResource(MappedResource mappedResource) { NullHelper.ToImplement(); } diff --git a/sources/engine/Stride.Graphics/Null/GraphicsAdapter.Null.cs b/sources/engine/Stride.Graphics/Null/GraphicsAdapter.Null.cs index 075026ca8b..9013a90c04 100644 --- a/sources/engine/Stride.Graphics/Null/GraphicsAdapter.Null.cs +++ b/sources/engine/Stride.Graphics/Null/GraphicsAdapter.Null.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL namespace Stride.Graphics { @@ -61,5 +61,6 @@ public bool IsProfileSupported(GraphicsProfile graphicsProfile) return false; } } -} +} + #endif diff --git a/sources/engine/Stride.Graphics/Null/GraphicsAdapterFactory.Null.cs b/sources/engine/Stride.Graphics/Null/GraphicsAdapterFactory.Null.cs index 4fa0176e7a..6b0cb0e870 100644 --- a/sources/engine/Stride.Graphics/Null/GraphicsAdapterFactory.Null.cs +++ b/sources/engine/Stride.Graphics/Null/GraphicsAdapterFactory.Null.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL namespace Stride.Graphics { @@ -14,5 +14,6 @@ private static void InitializeInternal() { } } -} -#endif +} + +#endif diff --git a/sources/engine/Stride.Graphics/Null/GraphicsDevice.Null.cs b/sources/engine/Stride.Graphics/Null/GraphicsDevice.Null.cs index 847709bf82..54332314b6 100644 --- a/sources/engine/Stride.Graphics/Null/GraphicsDevice.Null.cs +++ b/sources/engine/Stride.Graphics/Null/GraphicsDevice.Null.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL namespace Stride.Graphics { @@ -28,22 +28,25 @@ internal void OnDestroyed() NullHelper.ToImplement(); } - /// - /// Increases usage of if its usage is dynamic. + //// + /// Tags a Graphics Resource as no having alive references, meaning it should be safe to dispose it + /// or discard its contents during the next or SetData operation. /// - /// The resource link. - internal void TagResource(GraphicsResourceLink resourceLink) + /// + /// A object identifying the Graphics Resource along some related allocation information. + /// + internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink) { NullHelper.ToImplement(); } /// - /// Initializes this device. + /// Initialize the platform-specific implementation of the Graphics Device. /// - /// The graphics profiles. + /// A non- list of the graphics profiles to try, in order of preference. /// The device creation flags. /// The window handle. - private void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) + private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) { NullHelper.ToImplement(); } @@ -97,40 +100,42 @@ public void End() } /// - /// Adjust default pipeline state description. + /// Makes platform-specific adjustments to the Pipeline State objects created by the Graphics Device. /// - /// The pipeline state description to be adjusted. - private void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) + /// A Pipeline State description that can be modified and adjusted. + private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) { NullHelper.ToImplement(); } /// - /// Initialize post features. + /// Initializes the platform-specific features of the Graphics Device once it has been fully initialized. /// - private void InitializePostFeatures() + private unsafe partial void InitializePostFeatures() { NullHelper.ToImplement(); } /// - /// Name of the renderer for the current device. + /// Gets a string that identifies the underlying device used by the Graphics Device to render. /// - /// Name of renderer. - private string GetRendererName() + /// + /// In the case of Direct3D and Vulkan, for example, this will return the name of the Graphics Adapter + /// (e.g. "nVIDIA GeForce RTX 2080"). Other platforms may return a different string. + /// + private partial string GetRendererName() { NullHelper.ToImplement(); return rendererName; } /// - /// Destroy device. + /// Releases the platform-specific Graphics Device and all its associated resources. /// - /// Called from - private void DestroyPlatformDevice() + protected partial void DestroyPlatformDevice() { NullHelper.ToImplement(); } } -} +} #endif diff --git a/sources/engine/Stride.Graphics/Null/GraphicsDeviceFeatures.Null.cs b/sources/engine/Stride.Graphics/Null/GraphicsDeviceFeatures.Null.cs index c103a62111..c81e1d45c4 100644 --- a/sources/engine/Stride.Graphics/Null/GraphicsDeviceFeatures.Null.cs +++ b/sources/engine/Stride.Graphics/Null/GraphicsDeviceFeatures.Null.cs @@ -5,22 +5,19 @@ namespace Stride.Graphics { - /// - /// Features supported by a . - /// - /// This class gives also features for a particular format, using the operator this[Format] on this structure. public partial struct GraphicsDeviceFeatures { internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) { NullHelper.ToImplement(); + mapFeaturesPerFormat = new FeaturesPerFormat[256]; for (int i = 0; i < mapFeaturesPerFormat.Length; i++) - mapFeaturesPerFormat[i] = new FeaturesPerFormat((PixelFormat)i, MultisampleCount.None, FormatSupport.None); + mapFeaturesPerFormat[i] = new FeaturesPerFormat((PixelFormat)i, MultisampleCount.None, ComputeShaderFormatSupport.None, FormatSupport.None); HasComputeShaders = true; HasDepthAsReadOnlyRT = false; HasDepthAsSRV = true; - HasMultisampleDepthAsSRV = false; + HasMultiSampleDepthAsSRV = false; HasDoublePrecision = true; HasDriverCommandLists = true; HasMultiThreadingConcurrentResources = true; @@ -31,4 +28,5 @@ internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) } } } + #endif diff --git a/sources/engine/Stride.Graphics/Null/GraphicsOutput.Null.cs b/sources/engine/Stride.Graphics/Null/GraphicsOutput.Null.cs index 3f90b862ba..cb8ecb1891 100644 --- a/sources/engine/Stride.Graphics/Null/GraphicsOutput.Null.cs +++ b/sources/engine/Stride.Graphics/Null/GraphicsOutput.Null.cs @@ -3,8 +3,6 @@ #if STRIDE_GRAPHICS_API_NULL -using System; - namespace Stride.Graphics { /// @@ -21,7 +19,7 @@ public partial class GraphicsOutput public DisplayMode FindClosestMatchingDisplayMode(GraphicsProfile[] targetProfiles, DisplayMode mode) { NullHelper.ToImplement(); - return default(DisplayMode); + return default; } /// @@ -43,4 +41,5 @@ private void InitializeCurrentDisplayMode() } } } + #endif diff --git a/sources/engine/Stride.Graphics/Null/GraphicsResourceBase.Null.cs b/sources/engine/Stride.Graphics/Null/GraphicsResourceBase.Null.cs index 2b9954cf88..e26d240f20 100644 --- a/sources/engine/Stride.Graphics/Null/GraphicsResourceBase.Null.cs +++ b/sources/engine/Stride.Graphics/Null/GraphicsResourceBase.Null.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL + +using System; namespace Stride.Graphics { @@ -11,17 +13,18 @@ namespace Stride.Graphics public abstract partial class GraphicsResourceBase { /// - /// Initializes this instance. + /// Perform platform-specific initialization of the Graphics Resource. /// - private void Initialize() + private partial void Initialize() { NullHelper.ToImplement(); } /// - /// Called when graphics device has been detected to be internally destroyed. + /// Called when the has been detected to be internally destroyed, + /// or when the methad has been called. Raises the event. /// - protected internal virtual void OnDestroyed() + protected internal virtual partial void OnDestroyed() { Destroyed?.Invoke(this, EventArgs.Empty); NullHelper.ToImplement(); @@ -37,5 +40,5 @@ protected internal virtual bool OnRecreate() return false; } } -} -#endif +} +#endif diff --git a/sources/engine/Stride.Graphics/Null/QueryPool.Null.cs b/sources/engine/Stride.Graphics/Null/QueryPool.Null.cs new file mode 100644 index 0000000000..79316d73d6 --- /dev/null +++ b/sources/engine/Stride.Graphics/Null/QueryPool.Null.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#if STRIDE_GRAPHICS_API_NULL + +namespace Stride.Graphics +{ + public partial class QueryPool + { + /// + /// Platform-specific implementation that recreates the queries in the pool. + /// + private unsafe partial void Recreate() + { + NullHelper.ToImplement(); + } + } +} + +#endif diff --git a/sources/engine/Stride.Graphics/Null/SamplerState.Null.cs b/sources/engine/Stride.Graphics/Null/SamplerState.Null.cs index f7e9f93541..6736f8decd 100644 --- a/sources/engine/Stride.Graphics/Null/SamplerState.Null.cs +++ b/sources/engine/Stride.Graphics/Null/SamplerState.Null.cs @@ -1,16 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL -using System; + +#if STRIDE_GRAPHICS_API_NULL namespace Stride.Graphics { public partial class SamplerState { - private SamplerState(GraphicsDevice graphicsDevice, SamplerStateDescription samplerStateDescription) + private SamplerState(GraphicsDevice graphicsDevice, ref readonly SamplerStateDescription samplerStateDescription, string? name = null) { - throw new NotImplementedException(); + NullHelper.ToImplement(); } } -} -#endif +} + +#endif diff --git a/sources/engine/Stride.Graphics/Null/Texture.Null.cs b/sources/engine/Stride.Graphics/Null/Texture.Null.cs index 5996d03cab..11c9aefb70 100644 --- a/sources/engine/Stride.Graphics/Null/Texture.Null.cs +++ b/sources/engine/Stride.Graphics/Null/Texture.Null.cs @@ -1,13 +1,10 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_NULL +#if STRIDE_GRAPHICS_API_NULL namespace Stride.Graphics { - /// - /// Base class for texture resources. - /// public partial class Texture { /// @@ -38,22 +35,40 @@ public static bool IsDepthStencilReadOnlySupported(GraphicsDevice device) return false; } - internal void SwapInternal(Texture other) + /// + /// Swaps the Texture's internal data with another Texture. + /// + /// The other Texture. + internal partial void SwapInternal(Texture other) { NullHelper.ToImplement(); } - private void InitializeFromImpl(DataBox[] dataBoxes = null) + /// + /// Initializes the Texture from the specified data. + /// + /// + /// An array of structures pointing to the data for all the subresources to + /// initialize for the Texture. + /// + private partial void InitializeFromImpl(DataBox[] dataBoxes) { NullHelper.ToImplement(); } - private void OnRecreateImpl() + /// + /// Perform platform-specific recreation of the Texture. + /// + private partial void OnRecreateImpl() { NullHelper.ToImplement(); } - private bool IsFlipped() + /// + /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. + /// + /// if the Texture is flipped; otherwise. + private partial bool IsFlipped() { NullHelper.ToImplement(); return false; @@ -65,5 +80,5 @@ internal static PixelFormat ComputeShaderResourceFormatFromDepthFormat(PixelForm return format; } } -} +} #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/BlendState.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/BlendState.OpenGL.cs index 9d5be9ab90..6cb567a367 100644 --- a/sources/engine/Stride.Graphics/OpenGL/BlendState.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/BlendState.OpenGL.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_OPENGL +#if STRIDE_GRAPHICS_API_OPENGL using System; namespace Stride.Graphics @@ -21,33 +21,33 @@ class BlendState internal unsafe BlendState(BlendStateDescription blendStateDescription, bool hasRenderTarget) { - var renderTargets = &blendStateDescription.RenderTarget0; + var renderTargets = &blendStateDescription.RenderTargets[0]; for (int i = 1; i < 8; ++i) { if (renderTargets[i].BlendEnable || renderTargets[i].ColorWriteChannels != ColorWriteChannels.All) throw new NotSupportedException(); } - ColorWriteChannels = blendStateDescription.RenderTarget0.ColorWriteChannels; + ColorWriteChannels = blendStateDescription.RenderTargets[0].ColorWriteChannels; if (!hasRenderTarget) ColorWriteChannels = 0; - blendEnable = blendStateDescription.RenderTarget0.BlendEnable; + blendEnable = blendStateDescription.RenderTargets[0].BlendEnable; - blendEquationModeColor = ToOpenGL(blendStateDescription.RenderTarget0.ColorBlendFunction); - blendEquationModeAlpha = ToOpenGL(blendStateDescription.RenderTarget0.AlphaBlendFunction); - blendFactorSrcColor = ToOpenGL(blendStateDescription.RenderTarget0.ColorSourceBlend); - blendFactorSrcAlpha = ToOpenGL(blendStateDescription.RenderTarget0.AlphaSourceBlend); - blendFactorDestColor = ToOpenGL(blendStateDescription.RenderTarget0.ColorDestinationBlend); - blendFactorDestAlpha = ToOpenGL(blendStateDescription.RenderTarget0.AlphaDestinationBlend); + blendEquationModeColor = ToOpenGL(blendStateDescription.RenderTargets[0].ColorBlendFunction); + blendEquationModeAlpha = ToOpenGL(blendStateDescription.RenderTargets[0].AlphaBlendFunction); + blendFactorSrcColor = ToOpenGL(blendStateDescription.RenderTargets[0].ColorSourceBlend); + blendFactorSrcAlpha = ToOpenGL(blendStateDescription.RenderTargets[0].AlphaSourceBlend); + blendFactorDestColor = ToOpenGL(blendStateDescription.RenderTargets[0].ColorDestinationBlend); + blendFactorDestAlpha = ToOpenGL(blendStateDescription.RenderTargets[0].AlphaDestinationBlend); - blendEquationHash = (uint)blendStateDescription.RenderTarget0.ColorBlendFunction - | ((uint)blendStateDescription.RenderTarget0.AlphaBlendFunction << 8); + blendEquationHash = (uint)blendStateDescription.RenderTargets[0].ColorBlendFunction + | ((uint)blendStateDescription.RenderTargets[0].AlphaBlendFunction << 8); - blendFuncHash = (uint)blendStateDescription.RenderTarget0.ColorSourceBlend - | ((uint)blendStateDescription.RenderTarget0.AlphaSourceBlend << 8) - | ((uint)blendStateDescription.RenderTarget0.ColorDestinationBlend << 16) - | ((uint)blendStateDescription.RenderTarget0.AlphaDestinationBlend << 24); + blendFuncHash = (uint)blendStateDescription.RenderTargets[0].ColorSourceBlend + | ((uint)blendStateDescription.RenderTargets[0].AlphaSourceBlend << 8) + | ((uint)blendStateDescription.RenderTargets[0].ColorDestinationBlend << 16) + | ((uint)blendStateDescription.RenderTargets[0].AlphaDestinationBlend << 24); } public static BlendEquationModeEXT ToOpenGL(BlendFunction blendFunction) @@ -147,5 +147,5 @@ internal void RestoreColorMask(GL GL) (ColorWriteChannels & ColorWriteChannels.Alpha) != 0); } } -} +} #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/Buffer.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/Buffer.OpenGL.cs index 0201358671..d0c3db6256 100644 --- a/sources/engine/Stride.Graphics/OpenGL/Buffer.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/Buffer.OpenGL.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_OPENGL +#if STRIDE_GRAPHICS_API_OPENGL using System; using System.Diagnostics; using System.Runtime.InteropServices; @@ -25,7 +25,7 @@ public unsafe partial class Buffer /// Type of the buffer. /// The view format. /// The data pointer. - protected Buffer InitializeFromImpl(BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) + protected partial Buffer InitializeFromImpl(ref readonly BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) { bufferDescription = description; ViewFlags = viewFlags; @@ -143,7 +143,7 @@ protected void Init(IntPtr dataPointer) GL.TexParameter(TextureTarget, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge); GL.TexParameter(TextureTarget, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge); - UpdateTextureSubresource(dataPointer, 0, 0, SizeInBytes); + UpdateTextureSubResource(dataPointer, 0, 0, SizeInBytes); GL.BindTexture(TextureTarget, 0); @@ -181,7 +181,7 @@ protected void Init(IntPtr dataPointer) } } - internal void UpdateTextureSubresource(IntPtr dataPointer, int subresouceLevel, int offset, int count) + internal void UpdateTextureSubResource(IntPtr dataPointer, int subresouceLevel, int offset, int count) { // If overwriting everything, create a new texture if (offset == 0 && count == SizeInBytes) @@ -226,5 +226,5 @@ internal void UpdateTextureSubresource(IntPtr dataPointer, int subresouceLevel, } } } -} +} #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs index ebf19281b7..5fb8e47e38 100644 --- a/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/CommandList.OpenGL.cs @@ -83,18 +83,16 @@ private CommandList(GraphicsDevice device) : base(device) ClearState(); } - public void Reset() - { - } + // No OpenGL-specific implementation + public unsafe partial void Reset() { } - public void Flush() - { - - } + // No OpenGL-specific implementation + public partial void Flush() { } - public CompiledCommandList Close() + // No OpenGL-specific implementation + public partial CompiledCommandList Close() { - return default(CompiledCommandList); + return default; } public void Clear(Texture depthStencilBuffer, DepthStencilClearOptions options, float depth = 1, byte stencil = 0) @@ -280,7 +278,10 @@ public unsafe void ClearReadWrite(Texture texture, UInt4 value) #endif } - private void ClearStateImpl() + /// + /// OpenGL-specific implementation that clears and restores the state of the Graphics Device. + /// + private partial void ClearStateImpl() { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -322,7 +323,7 @@ private void ClearStateImpl() /// The region of the source to copy. /// The destination into which to copy the data /// This might alter some states such as currently bound texture. - public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, ResourceRegion? regionSource, GraphicsResource destination, int destinationSubResource, int dstX = 0, int dstY = 0, int dstZ = 0) + public unsafe void CopyRegion(GraphicsResource source, int sourceSubResource, ResourceRegion? regionSource, GraphicsResource destination, int destinationSubResource, int dstX = 0, int dstY = 0, int dstZ = 0) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -341,9 +342,9 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re if (destTexture.ParentTexture != null) destTexture = sourceTexture.ParentTexture; - var sourceWidth = Texture.CalculateMipSize(sourceTexture.Description.Width, sourceSubresource % sourceTexture.MipLevels); - var sourceHeight = Texture.CalculateMipSize(sourceTexture.Description.Height, sourceSubresource % sourceTexture.MipLevels); - var sourceDepth = Texture.CalculateMipSize(sourceTexture.Description.Depth, sourceSubresource % sourceTexture.MipLevels); + var sourceWidth = Texture.CalculateMipSize(sourceTexture.Description.Width, sourceSubResource % sourceTexture.MipLevelCount); + var sourceHeight = Texture.CalculateMipSize(sourceTexture.Description.Height, sourceSubResource % sourceTexture.MipLevelCount); + var sourceDepth = Texture.CalculateMipSize(sourceTexture.Description.Depth, sourceSubResource % sourceTexture.MipLevelCount); var sourceRegion = regionSource.HasValue ? regionSource.Value : new ResourceRegion(0, 0, 0, sourceWidth, sourceHeight, sourceDepth); var sourceRectangle = new Rectangle(sourceRegion.Left, sourceRegion.Top, sourceRegion.Right - sourceRegion.Left, sourceRegion.Bottom - sourceRegion.Top); @@ -366,9 +367,9 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re GL.BindBuffer(BufferTargetARB.CopyReadBuffer, sourceTexture.PixelBufferObjectId); GL.BindBuffer(BufferTargetARB.CopyWriteBuffer, destTexture.PixelBufferObjectId); GL.CopyBufferSubData(CopyBufferSubDataTarget.CopyReadBuffer, CopyBufferSubDataTarget.CopyWriteBuffer, - (IntPtr)sourceTexture.ComputeBufferOffset(sourceSubresource, 0), + (IntPtr)sourceTexture.ComputeBufferOffset(sourceSubResource, 0), (IntPtr)destTexture.ComputeBufferOffset(destinationSubResource, 0), - (UIntPtr)destTexture.ComputeSubresourceSize(destinationSubResource)); + (UIntPtr)destTexture.ComputeSubResourceSize(destinationSubResource)); } else { @@ -385,7 +386,7 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re for (int depthSlice = sourceRegion.Front; depthSlice < sourceRegion.Back; ++depthSlice) { - attachmentType = GraphicsDevice.UpdateFBO(FramebufferTarget.Framebuffer, new GraphicsDevice.FBOTexture(sourceTexture, sourceSubresource / sourceTexture.MipLevels + depthSlice, sourceSubresource % sourceTexture.MipLevels)); + attachmentType = GraphicsDevice.UpdateFBO(FramebufferTarget.Framebuffer, new GraphicsDevice.FBOTexture(sourceTexture, sourceSubResource / sourceTexture.MipLevelCount + depthSlice, sourceSubResource % sourceTexture.MipLevelCount)); GL.BindBuffer(BufferTargetARB.PixelPackBuffer, destTexture.PixelBufferObjectId); GL.ReadPixels(sourceRectangle.Left, sourceRectangle.Top, (uint)sourceRectangle.Width, (uint)sourceRectangle.Height, destTexture.TextureFormat, destTexture.TextureType, (void*)destTexture.ComputeBufferOffset(destinationSubResource, depthSlice)); @@ -426,10 +427,10 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re for (int depthSlice = sourceRegion.Front; depthSlice < sourceRegion.Back; ++depthSlice) { // Note: In practice, either it's a 2D texture array and its arrayslice can be non zero, or it's a 3D texture and it's depthslice can be non-zero, but not both at the same time - attachmentType = GraphicsDevice.UpdateFBO(FramebufferTarget.Framebuffer, new GraphicsDevice.FBOTexture(sourceTexture, sourceSubresource / sourceTexture.MipLevels + depthSlice, sourceSubresource % sourceTexture.MipLevels)); + attachmentType = GraphicsDevice.UpdateFBO(FramebufferTarget.Framebuffer, new GraphicsDevice.FBOTexture(sourceTexture, sourceSubResource / sourceTexture.MipLevelCount + depthSlice, sourceSubResource % sourceTexture.MipLevelCount)); - var arraySlice = destinationSubResource / destTexture.MipLevels; - var mipLevel = destinationSubResource % destTexture.MipLevels; + var arraySlice = destinationSubResource / destTexture.MipLevelCount; + var mipLevel = destinationSubResource % destTexture.MipLevelCount; switch (destTexture.TextureTarget) { @@ -642,7 +643,7 @@ public void Copy(GraphicsResource source, GraphicsResource destination) var sourceTexture = source as Texture; if (sourceTexture != null) { - subresourceCount = sourceTexture.ArraySize * sourceTexture.MipLevels; + subresourceCount = sourceTexture.ArraySize * sourceTexture.MipLevelCount; } // Copy each subresource @@ -909,7 +910,7 @@ public void ResetQueryPool(QueryPool queryPool) { } - public unsafe MappedResource MapSubresource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) + public unsafe partial MappedResource MapSubResource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -946,14 +947,14 @@ public unsafe MappedResource MapSubresource(GraphicsResource resource, int subRe if (resource is Texture texture) { if (lengthInBytes == 0) - lengthInBytes = texture.ComputeSubresourceSize(subResourceIndex); + lengthInBytes = texture.ComputeSubResourceSize(subResourceIndex); if (mapMode == MapMode.Read) { if (texture.Description.Usage != GraphicsResourceUsage.Staging) throw new NotSupportedException("Only staging textures can be mapped."); - var mipLevel = subResourceIndex % texture.MipLevels; + var mipLevel = subResourceIndex % texture.MipLevelCount; if (doNotWait) { @@ -973,16 +974,16 @@ public unsafe MappedResource MapSubresource(GraphicsResource resource, int subRe // Create a temporary unpack pixel buffer // TODO: Pool/allocator? (it's an upload buffer basically) - var pixelBufferObjectId = texture.GeneratePixelBufferObject(BufferTargetARB.PixelUnpackBuffer, PixelStoreParameter.UnpackAlignment, BufferUsageARB.DynamicCopy, texture.ComputeSubresourceSize(subResourceIndex)); + var pixelBufferObjectId = texture.GeneratePixelBufferObject(BufferTargetARB.PixelUnpackBuffer, PixelStoreParameter.UnpackAlignment, BufferUsageARB.DynamicCopy, texture.ComputeSubResourceSize(subResourceIndex)); return MapTexture(texture, false, BufferTargetARB.PixelUnpackBuffer, pixelBufferObjectId, subResourceIndex, mapMode, offsetInBytes, lengthInBytes); } } - throw new NotSupportedException("MapSubresource not implemented for type " + resource.GetType()); + throw new NotSupportedException("MapSubResource not implemented for type " + resource.GetType()); } - public void UnmapSubresource(MappedResource unmapped) + public unsafe partial void UnmapSubResource(MappedResource unmapped) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -1010,8 +1011,8 @@ public void UnmapSubresource(MappedResource unmapped) GL.BindTexture(texture.TextureTarget, texture.TextureId); - var mipLevel = unmapped.SubResourceIndex % texture.MipLevels; - var arraySlice = unmapped.SubResourceIndex / texture.MipLevels; + var mipLevel = unmapped.SubResourceIndex % texture.MipLevelCount; + var arraySlice = unmapped.SubResourceIndex / texture.MipLevelCount; // Bind buffer to texture switch (texture.TextureTarget) @@ -1056,22 +1057,22 @@ public void UnmapSubresource(MappedResource unmapped) } else // neither texture nor buffer { - throw new NotImplementedException("UnmapSubresource not implemented for type " + unmapped.Resource.GetType()); + throw new NotImplementedException("UnmapSubResource not implemented for type " + unmapped.Resource.GetType()); } } } - private unsafe MappedResource MapTexture(Texture texture, bool adjustOffsetForSubresource, BufferTargetARB bufferTarget, uint pixelBufferObjectId, int subResourceIndex, MapMode mapMode, int offsetInBytes, int lengthInBytes) + private unsafe MappedResource MapTexture(Texture texture, bool adjustOffsetForSubResource, BufferTargetARB bufferTarget, uint pixelBufferObjectId, int subResourceIndex, MapMode mapMode, int offsetInBytes, int lengthInBytes) { - int mipLevel = subResourceIndex % texture.MipLevels; + int mipLevel = subResourceIndex % texture.MipLevelCount; GL.BindBuffer(bufferTarget, pixelBufferObjectId); - var mapResult = (IntPtr)GL.MapBufferRange(bufferTarget, (IntPtr)offsetInBytes + (adjustOffsetForSubresource ? texture.ComputeBufferOffset(subResourceIndex, 0) : 0), (UIntPtr)lengthInBytes, mapMode.ToOpenGLMask()); + var mapResult = (IntPtr)GL.MapBufferRange(bufferTarget, (IntPtr)offsetInBytes + (adjustOffsetForSubResource ? texture.ComputeBufferOffset(subResourceIndex, 0) : 0), (UIntPtr)lengthInBytes, mapMode.ToOpenGLMask()); GL.BindBuffer(bufferTarget, 0); return new MappedResource(texture, subResourceIndex, new DataBox { DataPointer = mapResult, SlicePitch = texture.ComputeSlicePitch(mipLevel), RowPitch = texture.ComputeRowPitch(mipLevel) }, offsetInBytes, lengthInBytes) { - PixelBufferObjectId = pixelBufferObjectId, + PixelBufferObjectId = pixelBufferObjectId }; } @@ -1180,7 +1181,7 @@ internal unsafe void PreDraw() if (samplerStateChanged && texture != null) { // TODO: Include hasMipmap in samplerStateChanged - bool hasMipmap = texture.Description.MipLevels > 1; + bool hasMipmap = texture.Description.MipLevelCount > 1; samplerState.Apply(hasMipmap, boundSamplerState, texture.TextureTarget); texture.BoundSamplerState = samplerState; @@ -1268,7 +1269,7 @@ public void SetSamplerState(ShaderStage stage, int slot, SamplerState samplerSta samplerStates[slot] = samplerState; } - unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) + private unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -1276,7 +1277,7 @@ unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) GL.Scissor(scissorRectangle.Left, scissorRectangle.Top, (uint)scissorRectangle.Width, (uint)scissorRectangle.Height); } - unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) + private unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -1343,7 +1344,7 @@ internal void SetUnorderedAccessView(ShaderStage stage, int slot, GraphicsResour throw new NotSupportedException(); } - + /// /// Unsets an unordered access view from the shader pipeline. /// @@ -1353,7 +1354,7 @@ internal void UnsetUnorderedAccessView(GraphicsResource unorderedAccessView) #if DEBUG GraphicsDevice.EnsureContextActive(); #endif - + //throw new NotImplementedException(); } @@ -1475,7 +1476,45 @@ public void UnsetRenderTargets() SetRenderTargets(null, null); } - internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// is . + /// + /// + /// If is a Constant Buffer, it must be updated in full. + /// It is not possible to use this method to partially update a Constant Buffer. + /// + /// + /// A Graphics Resource cannot be used as a destination if: + /// + /// The resource was created with or . + /// The resource was created as a Depth-Stencil Buffer. + /// The resource is a Texture created with multi-sampling capability (see ). + /// + /// + /// + /// When returns, the application is free to change or even free the data pointed to by + /// because the method has already copied/snapped away the original contents. + /// + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData) + { + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + fixed (byte* sourceDataPtr = sourceData) + { + UpdateSubResource(resource, subResourceIndex, new DataBox((nint) sourceDataPtr, sourceData.Length, 0)); + } + } + + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox databox) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -1495,7 +1534,7 @@ internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourc GL.BindTexture(buffer.TextureTarget, buffer.TextureId); boundShaderResourceViews[0] = null; // bound active texture 0 has changed - buffer.UpdateTextureSubresource(databox.DataPointer, 0, 0, buffer.ElementCount); + buffer.UpdateTextureSubResource(databox.DataPointer, 0, 0, buffer.ElementCount); } else { @@ -1520,8 +1559,8 @@ internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourc boundShaderResourceViews[0] = null; // bound active texture 0 has changed var desc = texture.Description; - var mipLevel = subResourceIndex % texture.MipLevels; - var arraySlice = subResourceIndex / texture.MipLevels; + var mipLevel = subResourceIndex % texture.MipLevelCount; + var arraySlice = subResourceIndex / texture.MipLevelCount; switch (texture.TextureTarget) { #if !STRIDE_GRAPHICS_API_OPENGLES @@ -1542,18 +1581,51 @@ internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourc GL.TexSubImage2D(Texture.GetTextureTargetForDataSet2D(texture.TextureTarget, arraySlice), mipLevel, 0, 0, (uint)desc.Width, (uint)desc.Height, texture.TextureFormat, texture.TextureType, (void*)databox.DataPointer); break; default: - throw new NotImplementedException("UpdateSubresource not implemented for texture target " + texture.TextureTarget); + throw new NotImplementedException("UpdateSubResource not implemented for texture target " + texture.TextureTarget); break; } } else // neither texture nor buffer { - throw new NotImplementedException("UpdateSubresource not implemented for type " + resource.GetType()); + throw new NotImplementedException("UpdateSubResource not implemented for type " + resource.GetType()); } } } - internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData, ResourceRegion region) + { + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + fixed (byte* sourceDataPtr = sourceData) + { + UpdateSubResource(resource, subResourceIndex, new DataBox((nint)sourceDataPtr, sourceData.Length, 0), region); + } + } + + internal unsafe partial void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) { #if DEBUG GraphicsDevice.EnsureContextActive(); @@ -1639,7 +1711,7 @@ internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourc GL.BindTexture(buffer.TextureTarget, buffer.TextureId); boundShaderResourceViews[0] = null; // bound active texture 0 has changed - buffer.UpdateTextureSubresource(databox.DataPointer, 0, region.Left, region.Right - region.Left); + buffer.UpdateTextureSubResource(databox.DataPointer, 0, region.Left, region.Right - region.Left); } else { diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsAdapter.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsAdapter.OpenGL.cs index 1b23ed6e86..5f92a159f8 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsAdapter.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsAdapter.OpenGL.cs @@ -2,7 +2,7 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_OPENGL -using System.Linq; + using Silk.NET.SDL; using Stride.Graphics.OpenGL; @@ -21,7 +21,8 @@ public unsafe partial class GraphicsAdapter internal GraphicsAdapter() { - outputs = new [] { new GraphicsOutput() }; + // TODO OpenGL: Implement GraphicsOutput for OpenGL + graphicsOutputs = [ new GraphicsOutput() ]; // set default values int detectedVersion = 100; diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs index 5f61cded93..4b64a6e7f2 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsDevice.OpenGL.cs @@ -20,6 +20,7 @@ #if STRIDE_UI_SDL using Silk.NET.SDL; using WindowState = Stride.Graphics.SDL.FormWindowState; +using System.Diagnostics; #endif namespace Stride.Graphics @@ -83,7 +84,7 @@ public partial class GraphicsDevice private int contextBeginCounter = 0; // TODO: Use some LRU scheme to clean up FBOs if not used frequently anymore. - internal Dictionary existingFBOs = new Dictionary(); + internal Dictionary existingFBOs = new Dictionary(); private static GraphicsDevice _currentGraphicsDevice = null; @@ -132,7 +133,7 @@ public static GraphicsDevice Current internal float[] SquareVertices = { 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, + 0.0f, 1.0f, 1.0f, 1.0f, }; @@ -219,7 +220,7 @@ internal Buffer GetSquareBuffer() { if (SquareBuffer == null) { - SquareBuffer = Buffer.New(this, SquareVertices, BufferFlags.VertexBuffer); + SquareBuffer = Buffer.Vertex.New(this, SquareVertices); } return SquareBuffer; @@ -319,6 +320,10 @@ private uint CreateCopyProgram(bool srgb, out int offsetLocation, out int scaleL return program; } + /// + /// Enables or disables profiling. + /// + /// to enable profiling; to disable it. public void EnableProfile(bool enabledFlag) { ProfileEnabled = true; @@ -358,7 +363,7 @@ internal uint FindOrCreateFBO(GraphicsResourceBase graphicsResource, int subreso var texture = graphicsResource as Texture; if (texture != null) { - return FindOrCreateFBO(new FBOTexture(texture, subresource / texture.MipLevels, subresource % texture.MipLevels)); + return FindOrCreateFBO(new FBOTexture(texture, subresource / texture.MipLevelCount, subresource % texture.MipLevelCount)); } throw new NotSupportedException(); @@ -486,7 +491,7 @@ void BindColorAttachment(FramebufferTarget framebufferTarget, int i, FBOTexture { FramebufferAttachment attachment = FramebufferAttachment.ColorAttachment0 + i; - if (renderTarget.Texture.IsMultisample) + if (renderTarget.Texture.IsMultiSampled) { if (renderTarget.Texture.IsRenderbuffer) { @@ -521,7 +526,7 @@ internal void UpdateFBOColorAttachment(FramebufferTarget framebufferTarget, int case TextureTarget.TextureCubeMap: // We don't make use of the "TextureTarget.Texture2DMultisample" enum value on purpose, because it // allows for better code sharing between OpenGL ES and OpenGL. We simply use "TextureTarget.Texture2D" - // and check the value of "IsMultisample" instead. This is because OpenGL ES doesn't support + // and check the value of "IsMultiSampled" instead. This is because OpenGL ES doesn't support // multisample textures, but only multisample renderbuffers. BindColorAttachment(framebufferTarget, i, renderTarget); break; @@ -553,7 +558,7 @@ internal FramebufferAttachment UpdateFBODepthStencilAttachment(FramebufferTarget else { var textureTarget2d = TextureTarget.Texture2D; - if (depthStencilBuffer.Texture.IsMultisample) + if (depthStencilBuffer.Texture.IsMultiSampled) { #if STRIDE_GRAPHICS_API_OPENGLES throw new NotSupportedException("Multisample textures are not supported on OpenGL ES."); @@ -621,14 +626,17 @@ private void OnApplicationResumed(object sender, EventArgs e) } } - private string renderer; + private string rendererName; - private string GetRendererName() - { - return renderer; - } + private partial string GetRendererName() => rendererName; - protected unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, WindowHandle windowHandle) + /// + /// Initialize the OpenGL-specific implementation of the Graphics Device. + /// + /// A non- list of the graphics profiles to try, in order of preference. + /// The device creation flags. + /// The window handle. + private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) { // set default values version = 100; @@ -650,7 +658,7 @@ protected unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfile // check what is actually created currentVersion = Adapter.OpenGLVersion; - renderer = Adapter.OpenGLRenderer; + rendererName = Adapter.OpenGLRenderer; #if STRIDE_PLATFORM_ANDROID || STRIDE_PLATFORM_IOS //gameWindow.Load += OnApplicationResumed; @@ -658,8 +666,11 @@ protected unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfile #endif #if STRIDE_UI_SDL + Debug.Assert(windowHandle is WindowHandle); + var handle = (WindowHandle) windowHandle; + // NOTE : This null handling is specific for Linux AssetCompiler #2504 (refactor required?) - gameWindow = windowHandle?.NativeWindow as SDL.Window ?? new SDL.Window(""); + gameWindow = handle?.NativeWindow as SDL.Window ?? new SDL.Window(""); var SDL = Graphics.SDL.Window.SDL; @@ -706,7 +717,10 @@ protected unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfile DefaultSamplerState = SamplerState.New(this, new SamplerStateDescription(TextureFilter.MinPointMagMipLinear, TextureAddressMode.Wrap) { MaxAnisotropy = 1 }).DisposeBy(this); } - private unsafe void InitializePostFeatures() + /// + /// Initializes the platform-specific features of the Graphics Device once it has been fully initialized. + /// + private unsafe partial void InitializePostFeatures() { // Create and bind default VAO GL.GenVertexArrays(1, out defaultVAO); @@ -742,7 +756,11 @@ private unsafe void InitializePostFeatures() InternalMainCommandList = CommandList.New(this); } - private void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) + /// + /// Makes OpenGL-specific adjustments to the Pipeline State objects created by the Graphics Device. + /// + /// A Pipeline State description that can be modified and adjusted. + private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) { } @@ -755,7 +773,10 @@ private static void DebugCallback(GLEnum source, GLEnum type, int id, GLEnum sev } } - protected void DestroyPlatformDevice() + /// + /// Releases the platform-specific Graphics Device and all its associated resources. + /// + protected partial void DestroyPlatformDevice() { // Hack: Reset the lock so that UseOpenGLCreationContext works (even if locked by previously called OnApplicationPaused, which might have been done in an unaccessible event thread) // TODO: Does it work with Tegra3? @@ -801,7 +822,14 @@ internal void OnDestroyed() //boundProgram = 0; } - internal void TagResource(GraphicsResourceLink resourceLink) + /// + /// Tags a Graphics Resource as no having alive references, meaning it should be safe to dispose it + /// or discard its contents during the next or SetData operation. + /// + /// + /// A object identifying the Graphics Resource along some related allocation information. + /// + internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink) { if (resourceLink.Resource is GraphicsResource resource) resource.DiscardNextMap = true; @@ -937,7 +965,7 @@ public static implicit operator FBOTexture(Texture texture) return new FBOTexture(texture, arraySlice, mipLevel); } - + public bool Equals(FBOTexture other) { return Texture == other.Texture && ArraySlice == other.ArraySlice && MipLevel == other.MipLevel; diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsDeviceFeatures.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsDeviceFeatures.OpenGL.cs index d336c27bb5..88a9f5118d 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsDeviceFeatures.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsDeviceFeatures.OpenGL.cs @@ -1,18 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_OPENGL + // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,8 +22,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; -using System.Collections.Generic; + using Stride.Graphics.OpenGL; using Stride.Core.Diagnostics; @@ -36,7 +37,7 @@ namespace Stride.Graphics public partial struct GraphicsDeviceFeatures { private const GetPName GL_MAX_SAMPLES = (GetPName)36183; // We define this constant here because it is not contained within OpenTK... - + private static Logger logger = GlobalLogger.GetLogger(nameof(GraphicsDeviceFeatures)); private void EnumerateMSAASupportPerFormat(GraphicsDevice deviceRoot) @@ -71,7 +72,7 @@ private void EnumerateMSAASupportPerFormat(GraphicsDevice deviceRoot) for (int i = 0; i < mapFeaturesPerFormat.Length; i++) { // TODO: This ignores the supported multisample capabilities of each render target format. But I don't know how to query this in OpenGL (assuming it's even possible at all). - mapFeaturesPerFormat[i] = new FeaturesPerFormat((PixelFormat)i, actualMultisampleCount, FormatSupport.None); + mapFeaturesPerFormat[i] = new FeaturesPerFormat((PixelFormat)i, actualMultisampleCount, ComputeShaderFormatSupport.None, FormatSupport.None); } } @@ -113,7 +114,7 @@ internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) HasDepthAsSRV = true; HasDepthAsReadOnlyRT = true; - HasMultisampleDepthAsSRV = true; + HasMultiSampleDepthAsSRV = true; deviceRoot.HasDepthClamp = SupportedExtensions.Contains("GL_ARB_depth_clamp"); @@ -132,7 +133,7 @@ internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) HasDepthAsSRV = true; HasDepthAsReadOnlyRT = true; - HasMultisampleDepthAsSRV = true; + HasMultiSampleDepthAsSRV = true; deviceRoot.HasDepthClamp = true; @@ -155,4 +156,5 @@ internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) } } } + #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsOutput.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsOutput.OpenGL.cs index 3ceaeddf9f..9f561800ef 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsOutput.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsOutput.OpenGL.cs @@ -8,6 +8,9 @@ namespace Stride.Graphics { public partial class GraphicsOutput { + // TODO OpenGL: Implement GraphicsOutput for OpenGL + internal GraphicsOutput() { } // Needed by GraphicsAdapter.OpenGL for creating a Graphics Output without arguments + public DisplayMode FindClosestMatchingDisplayMode(GraphicsProfile[] targetProfiles, DisplayMode mode) { return mode; diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsResource.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsResource.OpenGL.cs index 3211fd37da..2873aff8e2 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsResource.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsResource.OpenGL.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_OPENGL +#if STRIDE_GRAPHICS_API_OPENGL namespace Stride.Graphics { @@ -19,7 +19,11 @@ public partial class GraphicsResource internal PixelFormatGl TextureFormat; internal PixelType TextureType; internal int TexturePixelSize; + + + // No OpenGL-specific implementation + protected internal override void OnDestroyed() { } } } - + #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/GraphicsResourceBase.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/GraphicsResourceBase.OpenGL.cs index 2b92fe05bf..261e6d6d1d 100644 --- a/sources/engine/Stride.Graphics/OpenGL/GraphicsResourceBase.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/GraphicsResourceBase.OpenGL.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_OPENGL +#if STRIDE_GRAPHICS_API_OPENGL using System; namespace Stride.Graphics @@ -12,16 +12,20 @@ public partial class GraphicsResourceBase { protected internal GL GL; - private void Initialize() + + /// + /// Perform OpenGL-specific initialization of the Graphics Resource. + /// + private partial void Initialize() { GL = GraphicsDevice?.GL; } - + /// - /// Called when graphics device has been detected to be internally destroyed. + /// Called when the has been detected to be internally destroyed, + /// or when the methad has been called. Raises the event. /// - /// - protected internal virtual void OnDestroyed() + protected internal virtual partial void OnDestroyed() { Destroyed?.Invoke(this, EventArgs.Empty); } @@ -36,5 +40,5 @@ protected internal virtual bool OnRecreate() } } } - + #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/MappedResource.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/MappedResource.OpenGL.cs index e25eb3e262..c1fa72bab6 100644 --- a/sources/engine/Stride.Graphics/OpenGL/MappedResource.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/MappedResource.OpenGL.cs @@ -2,11 +2,13 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_OPENGL + namespace Stride.Graphics { public partial struct MappedResource { - internal uint PixelBufferObjectId; + internal uint PixelBufferObjectId { get; init; } } } + #endif diff --git a/sources/engine/Stride.Graphics/OpenGL/PipelineState.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/PipelineState.OpenGL.cs index de95c753af..9c260a3e50 100644 --- a/sources/engine/Stride.Graphics/OpenGL/PipelineState.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/PipelineState.OpenGL.cs @@ -43,7 +43,7 @@ private PipelineState(GraphicsDevice graphicsDevice, PipelineStateDescription pi var rootSignature = pipelineStateDescription.RootSignature; if (rootSignature != null && effectBytecode != null) - ResourceBinder.Compile(graphicsDevice, rootSignature.EffectDescriptorSetReflection, effectBytecode); + ResourceBinder.Compile(rootSignature.EffectDescriptorSetReflection, effectBytecode); // Vertex attributes if (pipelineStateDescription.InputElements != null) @@ -113,7 +113,7 @@ public bool Equals(VertexAttribsKey other) public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; - return obj is VertexAttribsKey && Equals((VertexAttribsKey)obj); + return obj is VertexAttribsKey vertexAttribsKey && Equals(vertexAttribsKey); } public override int GetHashCode() diff --git a/sources/engine/Stride.Graphics/OpenGL/QueryPool.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/QueryPool.OpenGL.cs index 3bb360dc56..cd8d8aafb3 100644 --- a/sources/engine/Stride.Graphics/OpenGL/QueryPool.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/QueryPool.OpenGL.cs @@ -46,7 +46,10 @@ protected internal override void OnDestroyed() base.OnDestroyed(); } - private void Recreate() + /// + /// Platform-specific implementation that recreates the queries in the pool. + /// + private unsafe partial void Recreate() { switch (QueryType) { diff --git a/sources/engine/Stride.Graphics/OpenGL/SamplerState.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/SamplerState.OpenGL.cs index 31721b290f..44b6d62c7a 100644 --- a/sources/engine/Stride.Graphics/OpenGL/SamplerState.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/SamplerState.OpenGL.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#if STRIDE_GRAPHICS_API_OPENGL +#if STRIDE_GRAPHICS_API_OPENGL using System; using Stride.Core.Mathematics; @@ -28,7 +28,8 @@ public partial class SamplerState private DepthFunction compareFunc; private TextureCompareMode compareMode; - private SamplerState(GraphicsDevice device, SamplerStateDescription samplerStateDescription) : base(device) + private SamplerState(GraphicsDevice device, ref readonly SamplerStateDescription samplerStateDescription, string? name = null) + : base(device, name) { Description = samplerStateDescription; @@ -142,4 +143,4 @@ internal void Apply(bool hasMipmap, SamplerState oldSamplerState, TextureTarget } } -#endif +#endif diff --git a/sources/engine/Stride.Graphics/OpenGL/Texture.OpenGL.cs b/sources/engine/Stride.Graphics/OpenGL/Texture.OpenGL.cs index 6faf21b9c8..234facdf06 100644 --- a/sources/engine/Stride.Graphics/OpenGL/Texture.OpenGL.cs +++ b/sources/engine/Stride.Graphics/OpenGL/Texture.OpenGL.cs @@ -39,7 +39,11 @@ public static bool IsDepthStencilReadOnlySupported(GraphicsDevice device) return true; } - internal void SwapInternal(Texture other) + /// + /// Swaps the Texture's internal data with another Texture. + /// + /// The other Texture. + internal partial void SwapInternal(Texture other) { (other.DepthPitch, DepthPitch) = (DepthPitch, other.DepthPitch); @@ -71,7 +75,10 @@ public void Recreate(DataBox[] dataBoxes = null) InitializeFromImpl(dataBoxes); } - private void OnRecreateImpl() + /// + /// Perform OpenGL-specific recreation of the Texture. + /// + private partial void OnRecreateImpl() { // Dependency: wait for underlying texture to be recreated if (ParentTexture != null && ParentTexture.LifetimeState != GraphicsResourceLifetimeState.Active) @@ -153,7 +160,14 @@ private void CopyParentAttributes() pixelBufferObjectId = ParentTexture.PixelBufferObjectId; } - private void InitializeFromImpl(DataBox[] dataBoxes = null) + /// + /// Initializes the Texture from the specified data. + /// + /// + /// An array of structures pointing to the data for all the subresources to + /// initialize for the Texture. + /// + private partial void InitializeFromImpl(DataBox[] dataBoxes) { if (ParentTexture != null) { @@ -198,7 +212,7 @@ private void InitializeFromImpl(DataBox[] dataBoxes = null) IsRenderbuffer = !Description.IsShaderResource; // Force to renderbuffer if MSAA is on because we don't support MSAA textures ATM (and they don't exist on OpenGL ES). - if (Description.IsMultisample) + if (Description.IsMultiSampled) { // TODO: Ideally the caller of this method should be aware of this "force to renderbuffer", // because the caller won't be able to bind it as a texture. @@ -215,16 +229,16 @@ private void InitializeFromImpl(DataBox[] dataBoxes = null) GL.BindTexture(TextureTarget, TextureId); SetFilterMode(); - if (Description.MipLevels == 0) + if (Description.MipLevelCount == 0) throw new NotImplementedException(); var setSize = TextureSetSize(TextureTarget); for (var arrayIndex = 0; arrayIndex < Description.ArraySize; ++arrayIndex) { - int offsetArray = arrayIndex * Description.MipLevels; + int offsetArray = arrayIndex * Description.MipLevelCount; - for (int mipLevel = 0; mipLevel < Description.MipLevels; ++mipLevel) + for (int mipLevel = 0; mipLevel < Description.MipLevelCount; ++mipLevel) { DataBox dataBox; Int3 dimensions = new Int3(CalculateMipSize(Description.Width, mipLevel), @@ -316,7 +330,7 @@ private void SetFilterMode() } } #if STRIDE_GRAPHICS_API_OPENGLES - else if (Description.MipLevels <= 1) + else if (Description.MipLevelCount <= 1) { GL.TexParameter(TextureTarget, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest); // TODO: Why does this use the nearest filter for minification? Using Linear filtering would result in a smoother appearance for minified textures. GL.TexParameter(TextureTarget, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear); @@ -324,7 +338,7 @@ private void SetFilterMode() #endif GL.TexParameter(TextureTarget, TextureParameterName.TextureBaseLevel, 0); - GL.TexParameter(TextureTarget, TextureParameterName.TextureMaxLevel, Description.MipLevels - 1); + GL.TexParameter(TextureTarget, TextureParameterName.TextureMaxLevel, Description.MipLevelCount - 1); } private void CreateRenderbuffer(int width, int height, int multisampleCount, InternalFormat internalFormat, out uint textureID) @@ -332,7 +346,7 @@ private void CreateRenderbuffer(int width, int height, int multisampleCount, Int GL.GenRenderbuffers(1, out textureID); GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, textureID); - if (Description.IsMultisample) + if (Description.IsMultiSampled) { #if !STRIDE_PLATFORM_IOS // MSAA is not supported on iOS currently because OpenTK doesn't expose "GL.BlitFramebuffer()" on iOS for some reason. @@ -362,7 +376,7 @@ private void CreateTexture1D(bool compressed, int width, int mipLevel, DataBox d private void CreateTexture2D(bool compressed, int width, int height, int mipLevel, int arrayIndex, DataBox dataBox) { - if (IsMultisample) + if (IsMultiSampled) { throw new InvalidOperationException("Currently if multisampling is on, a renderbuffer will be created (not a texture) in any case and this code will not be reached." + "Therefore if this place is reached, it means something went wrong. Once multisampling has been implemented for OpenGL textures, you can remove this exception."); @@ -556,7 +570,11 @@ internal static PixelFormat ComputeShaderResourceFormatFromDepthFormat(PixelForm return format; } - private bool IsFlipped() + /// + /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. + /// + /// if the Texture is flipped; otherwise. + private partial bool IsFlipped() { return GraphicsDevice.WindowProvidedRenderTexture == this; } @@ -583,8 +601,8 @@ private unsafe void UploadInitialData(BufferTargetARB bufferTarget, DataBox[] da { for (var arrayIndex = 0; arrayIndex < Description.ArraySize; ++arrayIndex) { - var offsetArray = arrayIndex * Description.MipLevels; - for (int i = 0; i < Description.MipLevels; ++i) + var offsetArray = arrayIndex * Description.MipLevelCount; + for (int i = 0; i < Description.MipLevelCount; ++i) { var data = IntPtr.Zero; diff --git a/sources/engine/Stride.Graphics/PipelineState.cs b/sources/engine/Stride.Graphics/PipelineState.cs index 59f05f52f0..f76e8724fd 100644 --- a/sources/engine/Stride.Graphics/PipelineState.cs +++ b/sources/engine/Stride.Graphics/PipelineState.cs @@ -3,33 +3,52 @@ using Stride.Core.ReferenceCounting; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// +/// A Pipeline State object encapsulates the complete pipeline configuration, +/// including Shaders, input layout, Render States, and output settings. +/// It represents an atomic, immutable collection of states that can be efficiently bound and unbound as +/// a single unit during rendering operations. +/// +/// +/// An instance of this class can represent either the state of the graphics pipeline, +/// or the state of the compute pipeline (in the platforms that support compute). +/// +/// +public partial class PipelineState : GraphicsResourceBase { - public partial class PipelineState : GraphicsResourceBase + // TODO: Unused? Vulkan backend has 'inputBindingCount', but does not write to this property. + public int InputBindingCount { get; private set; } + + + /// + /// Creates a new Pipeline State object from the provided description. + /// + /// The Graphics Device. + /// A description of the desired graphics pipeline configuration. + /// A new instance of . + public static PipelineState New(GraphicsDevice graphicsDevice, PipelineStateDescription pipelineStateDescription) { - public int InputBindingCount { get; private set; } + // Hash the current state + var hashedState = new PipelineStateDescriptionWithHash(pipelineStateDescription); - public static PipelineState New(GraphicsDevice graphicsDevice, ref PipelineStateDescription pipelineStateDescription) + // Store SamplerState in a cache (D3D seems to have quite bad concurrency when using CreateSampler while rendering) + PipelineState pipelineState; + lock (graphicsDevice.CachedPipelineStates) { - // Hash the current state - var hashedState = new PipelineStateDescriptionWithHash(pipelineStateDescription); - - // Store SamplerState in a cache (D3D seems to have quite bad concurrency when using CreateSampler while rendering) - PipelineState pipelineState; - lock (graphicsDevice.CachedPipelineStates) + if (graphicsDevice.CachedPipelineStates.TryGetValue(hashedState, out pipelineState)) + { + // TODO: Appropriate destroy + pipelineState.AddReferenceInternal(); + } + else { - if (graphicsDevice.CachedPipelineStates.TryGetValue(hashedState, out pipelineState)) - { - // TODO: Appropriate destroy - pipelineState.AddReferenceInternal(); - } - else - { - pipelineState = new PipelineState(graphicsDevice, pipelineStateDescription); - graphicsDevice.CachedPipelineStates.Add(hashedState, pipelineState); - } + pipelineState = new PipelineState(graphicsDevice, pipelineStateDescription); + graphicsDevice.CachedPipelineStates.Add(hashedState, pipelineState); } - return pipelineState; } + return pipelineState; } } diff --git a/sources/engine/Stride.Graphics/PipelineStateDescription.cs b/sources/engine/Stride.Graphics/PipelineStateDescription.cs index 4f07cbec63..fe22912db2 100644 --- a/sources/engine/Stride.Graphics/PipelineStateDescription.cs +++ b/sources/engine/Stride.Graphics/PipelineStateDescription.cs @@ -4,135 +4,189 @@ using System; using Stride.Shaders; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes the configuration of the graphics pipeline that will be encapsulated by a object. +/// This includes Shaders, input layout, Render States, and output settings. +/// +/// +public sealed class PipelineStateDescription : IEquatable { - public class PipelineStateDescription : IEquatable + // Root Signature + + /// + /// A definition of the Shader resources and their bindings, including Constant Buffers, Textures, and Samplers + /// that will be accessed by the Shader programs. + /// + public RootSignature RootSignature; + + // Effect / Shader + + /// + /// Compiled Shader programs (vertex, pixel, geometry, etc.) that define the programmable stages of the graphics pipeline. + /// + public EffectBytecode EffectBytecode; + + // Rendering States + + /// + /// A description of the Blend State, which controls how the output color and alpha values of rendered pixels + /// are blended with existing pixels in the bound Render Targets. + /// + public BlendStateDescription BlendState; + + /// + /// A 32-bit mask that determines which samples in a multi-sampled Render Target are updated during rendering operations. + /// + public uint SampleMask = 0xFFFFFFFF; + + /// + /// A description of the Rasterizer State, which controls how primitives are rasterized into pixels, + /// including settings for culling, fill mode, depth bias, and scissor testing. + /// + public RasterizerStateDescription RasterizerState; + + /// + /// A description of the Depth-Stencil State, which controls how Depth and Stencil Buffers are used during rendering, + /// including comparison functions, write masks, and stencil operations. + /// + public DepthStencilStateDescription DepthStencilState; + + // Input layout + + /// + /// A description of the input layout for the Vertex Buffer, which defines how vertex data is structured. + /// The array describes per-vertex attributes such as position, normal, texture coordinates, + /// and their respective formats. + /// + public InputElementDescription[] InputElements; + + /// + /// Specifies how vertices should be interpreted to form primitives (points, lines, triangles, etc.) + /// + public PrimitiveType PrimitiveType; + + // Output + + /// + /// A description of the output configuration for the graphics pipeline, such as the format and count of Render Targets, + /// along with the Depth-Stencil Buffer format used for rendering output. + /// + public RenderOutputDescription Output; + + + /// + /// Creates a new object that is a copy of the current instance. + /// + /// A new object that is a copy of this instance. + public unsafe PipelineStateDescription Clone() { - // Root Signature - public RootSignature RootSignature; - - // Effect/Shader - public EffectBytecode EffectBytecode; - - // Rendering States - public BlendStateDescription BlendState; - public uint SampleMask = 0xFFFFFFFF; - public RasterizerStateDescription RasterizerState; - public DepthStencilStateDescription DepthStencilState; + return new PipelineStateDescription + { + RootSignature = RootSignature, + EffectBytecode = EffectBytecode, + BlendState = BlendState, + SampleMask = SampleMask, + RasterizerState = RasterizerState, + DepthStencilState = DepthStencilState, - // Input layout - public InputElementDescription[] InputElements; + InputElements = (InputElementDescription[]) InputElements.Clone(), - public PrimitiveType PrimitiveType; + PrimitiveType = PrimitiveType, - public RenderOutputDescription Output; + Output = Output + }; + } - public unsafe PipelineStateDescription Clone() - { - InputElementDescription[] inputElements; - if (InputElements != null) - { - inputElements = new InputElementDescription[InputElements.Length]; - for (int i = 0; i < inputElements.Length; ++i) - inputElements[i] = InputElements[i]; - } - else - { - inputElements = null; - } + /// + /// Sets default values for this Pipeline State Description. + /// + /// + /// For more information about the default values, see the individual state descriptions: + /// , , and + /// . + /// + public void SetDefaults() + { + BlendState.SetDefaults(); + RasterizerState.SetDefaults(); + DepthStencilState.SetDefaults(); + } - return new PipelineStateDescription - { - RootSignature = RootSignature, - EffectBytecode = EffectBytecode, - BlendState = BlendState, - SampleMask = SampleMask, - RasterizerState = RasterizerState, - DepthStencilState = DepthStencilState, - InputElements = inputElements, + /// + public bool Equals(PipelineStateDescription other) + { + if (other is null) + return false; - PrimitiveType = PrimitiveType, + if (ReferenceEquals(this, other)) + return true; - Output = Output, - }; - } + if (!(RootSignature == other.RootSignature + && EffectBytecode == other.EffectBytecode + && BlendState.Equals(other.BlendState) + && SampleMask == other.SampleMask + && RasterizerState.Equals(other.RasterizerState) + && DepthStencilState.Equals(other.DepthStencilState) + && PrimitiveType == other.PrimitiveType + && Output == other.Output)) + return false; - public void SetDefaults() - { - BlendState.SetDefaults(); - RasterizerState.SetDefault(); - DepthStencilState.SetDefault(); - } + if ((InputElements is not null) != (other.InputElements is not null)) + return false; - public bool Equals(PipelineStateDescription other) + if (InputElements is not null) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - if (!(RootSignature == other.RootSignature - && EffectBytecode == other.EffectBytecode - && BlendState.Equals(other.BlendState) - && SampleMask == other.SampleMask - && RasterizerState.Equals(other.RasterizerState) - && DepthStencilState.Equals(other.DepthStencilState) - && PrimitiveType == other.PrimitiveType - && Output == other.Output)) + if (InputElements.Length != other.InputElements.Length) return false; - if ((InputElements != null) != (other.InputElements != null)) - return false; - if (InputElements != null) + for (int i = 0; i < InputElements.Length; ++i) { - if (InputElements.Length != other.InputElements.Length) + if (!InputElements[i].Equals(other.InputElements[i])) return false; - for (int i = 0; i < InputElements.Length; ++i) - { - if (!InputElements[i].Equals(other.InputElements[i])) - return false; - } } - - return true; } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((PipelineStateDescription)obj); - } + return true; + } - public override int GetHashCode() - { - unchecked - { - var hashCode = RootSignature != null ? RootSignature.GetHashCode() : 0; - hashCode = (hashCode * 397) ^ (EffectBytecode != null ? EffectBytecode.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ BlendState.GetHashCode(); - hashCode = (hashCode * 397) ^ (int)SampleMask; - hashCode = (hashCode * 397) ^ RasterizerState.GetHashCode(); - hashCode = (hashCode * 397) ^ DepthStencilState.GetHashCode(); - if (InputElements != null) - { - foreach (var inputElement in InputElements) - hashCode = (hashCode * 397) ^ inputElement.GetHashCode(); - } - - hashCode = (hashCode * 397) ^ (int)PrimitiveType; - hashCode = (hashCode * 397) ^ Output.GetHashCode(); - return hashCode; - } - } + /// + public override bool Equals(object obj) + { + return obj is PipelineStateDescription pipelineStateDescription && Equals(pipelineStateDescription); + } - public static bool operator ==(PipelineStateDescription left, PipelineStateDescription right) + /// + public override int GetHashCode() + { + HashCode hashCode1 = new(); + hashCode1.Add(RootSignature); + hashCode1.Add(EffectBytecode); + hashCode1.Add(BlendState); + hashCode1.Add(SampleMask); + hashCode1.Add(RasterizerState); + hashCode1.Add(DepthStencilState); + + if (InputElements is not null) { - return Equals(left, right); + foreach (var inputElement in InputElements) + hashCode1.Add(inputElement); } - public static bool operator !=(PipelineStateDescription left, PipelineStateDescription right) - { - return !Equals(left, right); - } + hashCode1.Add(PrimitiveType); + hashCode1.Add(Output); + return hashCode1.ToHashCode(); + } + + public static bool operator ==(PipelineStateDescription left, PipelineStateDescription right) + { + return Equals(left, right); + } + + public static bool operator !=(PipelineStateDescription left, PipelineStateDescription right) + { + return !Equals(left, right); } } diff --git a/sources/engine/Stride.Graphics/PipelineStateDescriptionWithHash.cs b/sources/engine/Stride.Graphics/PipelineStateDescriptionWithHash.cs index 376a867acb..8d63a93d35 100644 --- a/sources/engine/Stride.Graphics/PipelineStateDescriptionWithHash.cs +++ b/sources/engine/Stride.Graphics/PipelineStateDescriptionWithHash.cs @@ -1,43 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Utility structure encapsulating the description for a and a hash that allows +/// Stride to easily determine equality. +/// +/// +/// +internal readonly struct PipelineStateDescriptionWithHash(PipelineStateDescription state) : IEquatable { - internal struct PipelineStateDescriptionWithHash : IEquatable + public readonly int Hash = state.GetHashCode(); + public readonly PipelineStateDescription State = state; + + + /// + public readonly bool Equals(PipelineStateDescriptionWithHash other) + { + return Hash == other.Hash + && (State is null) == (other.State is null) + && (State?.Equals(other.State) ?? true); + } + + /// + public override readonly bool Equals(object obj) + { + return obj is PipelineStateDescriptionWithHash other && Equals(other); + } + + /// + public override readonly int GetHashCode() => Hash; + + public static bool operator ==(PipelineStateDescriptionWithHash left, PipelineStateDescriptionWithHash right) + { + return left.Equals(right); + } + + public static bool operator !=(PipelineStateDescriptionWithHash left, PipelineStateDescriptionWithHash right) { - public readonly int Hash; - public readonly PipelineStateDescription State; - - public PipelineStateDescriptionWithHash(PipelineStateDescription state) - { - Hash = state.GetHashCode(); - State = state; - } - - public bool Equals(PipelineStateDescriptionWithHash other) - { - return Hash == other.Hash && (State == null) == (other.State == null) && (State?.Equals(other.State) ?? true); - } - - public override bool Equals(object obj) - { - return obj is PipelineStateDescriptionWithHash other && Equals(other); - } - - public override int GetHashCode() - { - return Hash; - } - - public static bool operator ==(PipelineStateDescriptionWithHash left, PipelineStateDescriptionWithHash right) - { - return left.Equals(right); - } - - public static bool operator !=(PipelineStateDescriptionWithHash left, PipelineStateDescriptionWithHash right) - { - return !left.Equals(right); - } + return !left.Equals(right); } } diff --git a/sources/engine/Stride.Graphics/PresentInterval.cs b/sources/engine/Stride.Graphics/PresentInterval.cs index a4e03ebb00..b5df550209 100644 --- a/sources/engine/Stride.Graphics/PresentInterval.cs +++ b/sources/engine/Stride.Graphics/PresentInterval.cs @@ -1,31 +1,45 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Defines the relationship between the 's refresh rate and the rate +/// at which Present operations are completed. +/// +public enum PresentInterval { + // NOTE: Values should not be changed, as they are directly used by Present method (0: no frame to wait, 1: one frame...etc.) + /// - /// Defines flags that describe the relationship between the adapter refresh rate and the rate at which Present operations are completed. + /// The runtime updates the window client area immediately, + /// and might do so more than once during the adapter refresh period. + /// Present operations might be affected immediately. /// - public enum PresentInterval - { - // NOTE: Values should not be changed, as they are directly used by Present method (0: no frame to wait, 1: one frame...etc.) + /// + /// This option is always available for both windowed and full-screen swap chains. + /// + Immediate = 0, - /// - /// The runtime updates the window client area immediately, and might do so more than once during the adapter refresh period. Present operations might be affected immediately. This option is always available for both windowed and full-screen swap chains. - /// - Immediate = 0, + /// + /// The default value. Equivalent to setting . + /// + Default = One, - /// - /// Equivalent to setting One. - /// - Default = One, + /// + /// The driver waits for the vertical retrace period + /// (the runtime will beam trace to prevent tearing). This is commonly known as V-Sync. + /// Present operations are not affected more frequently than the screen refresh rate; + /// the runtime completes one Present operation per adapter refresh period, at most. + /// + /// + /// This option is always available for both windowed and full-screen swap chains. + /// + One = 1, - /// - /// The driver waits for the vertical retrace period (the runtime will beam trace to prevent tearing). Present operations are not affected more frequently than the screen refresh rate; the runtime completes one Present operation per adapter refresh period, at most. This option is always available for both windowed and full-screen swap chains. - /// - One = 1, - /// - /// The driver waits for the vertical retrace period. Present operations are not affected more frequently than every second screen refresh. - /// - Two = 2, - } + /// + /// The driver waits for the vertical retrace period. + /// Present operations are not affected more frequently than every second screen refresh. + /// + Two = 2 } diff --git a/sources/engine/Stride.Graphics/PresentationParameters.cs b/sources/engine/Stride.Graphics/PresentationParameters.cs index cf5fbb40d1..cf4c0a8aa5 100644 --- a/sources/engine/Stride.Graphics/PresentationParameters.cs +++ b/sources/engine/Stride.Graphics/PresentationParameters.cs @@ -1,186 +1,349 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -using Stride.Core.Mathematics; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes how a will display to the screen. +/// +public sealed class PresentationParameters : IEquatable { + #region Default values + + private const int DefaultBackBufferWidth = 800; + private const int DefaultBackBufferHeight = 480; + private const PixelFormat DefaultBackBufferFormat = PixelFormat.R8G8B8A8_UNorm; + private const PixelFormat DefaultDepthStencilFormat = PixelFormat.D24_UNorm_S8_UInt; + private const MultisampleCount DefaultMultisampleCount = MultisampleCount.None; + private const PresentInterval DefaultPresentationInterval = PresentInterval.Immediate; + private const bool DefaultIsFullScreen = false; + private const int DefaultRefreshRate = 60; // Hz + private const ColorSpace DefaultColorSpace = ColorSpace.Linear; + private const ColorSpaceType DefaultOutputColorSpace = ColorSpaceType.Rgb_Full_G22_None_P709; // Default RGB output for monitors with a standard gamma of 2.2 + + #endregion + + /// + /// A specifying the display format. + /// + public PixelFormat BackBufferFormat; + + /// + /// The height of the back-buffer, in pixels. + /// + /// + /// Both and + /// determine both the screen resolution (if in full-screen mode) or the window size + /// (if in windowed-mode). + /// + public int BackBufferHeight; + + /// + /// The width of the back-buffer, in pixels. + /// + /// + /// Both and + /// determine both the screen resolution (if in full-screen mode) or the window size + /// (if in windowed-mode). + /// + public int BackBufferWidth; + + /// + /// A specifying the depth-stencil format. + /// + /// + /// + /// The Depth Buffer (also known as Z-buffer) is used to determine the depth of each pixel + /// so close geometry correctly occludes farther geometry. The format determines the precission + /// of the depth buffer. + /// + /// + /// The Stencil Buffer is used to store additional information for each pixel, such as + /// marking or discarding specific pixels for different effects. + /// + /// + /// This format determines both because usually the stencil buffer is a part of the depth buffer + /// reserved for other uses. + /// + /// + /// Some examples are , where the depth buffer uses 24 bits + /// and the stencil buffer uses 8 bits, for a total of 32 bits per pixel, or + /// , which uses 32 bits for the depth buffer and no bits for the stencil buffer. + /// + /// + public PixelFormat DepthStencilFormat; + + /// + /// The window object or handle where the presentation will occur. + /// + /// + /// A window object is platform-dependent: + /// + /// + /// Windows Desktop + /// + /// This could be a low-level window/control handle (), or + /// directly a Windows Forms' Form or Control object. + /// + /// + /// + /// Windows Metro + /// + /// This could be a SwapChainBackgroundPanel or SwapChainPanel object. + /// + /// + /// + /// + public WindowHandle DeviceWindowHandle; + + /// + /// A value indicating whether the application must render in full-screen mode () + /// or inside a window (). + /// + public bool IsFullScreen; + + /// + /// A indicating the number of sample locations during multi-sampling. + /// + /// + /// + /// The multi-sampling is applied to the back-buffer to reduce the aliasing artifacts. This is + /// known as Multi-Sampling Anti-Aliasing (MSAA). + /// + /// + /// The higher the number of samples, the aliasing patterns will be less visible, but it will result + /// in more memory being consumed, and costlier rasterization. + /// + /// + /// If is selected, no multi-sampling will be applied. + /// Common values include (minimal anti-aliasing) and + /// (high-quality anti-aliasing). + /// Higher values increase GPU workload. + /// + /// + public MultisampleCount MultisampleCount; + + /// + /// A value of determining the maximum rate + /// at which the Swap Chain's back buffers can be presented to the front buffer. + /// + public PresentInterval PresentationInterval; + + /// + /// The refresh rate of the screen, in hertz. + /// + /// + /// + /// The Refresh Rate is the number of times per second the screen is refreshed, + /// i.e. the number of frames per second the monitor can display. + /// + /// + /// The value is represented as a , so it can represent both usual integer + /// refresh rates (e.g. 60Hz) and fractional refresh rates (e.g. 59.94Hz). + /// + /// + /// Usually, the refresh rate is only respected when rendering in full-screen mode (i.e. when + /// is set to ). + /// + /// + /// Common refresh rates include 60Hz, 120Hz, and 144Hz, depending on monitor capabilities. + /// + /// + public Rational RefreshRate; + + /// + /// The index of the preferred output (monitor) to use when switching to full-screen mode. + /// + /// + /// This parameter does not have any effect when windowed mode is used + /// ( is ). + /// + public int PreferredFullScreenOutputIndex; + + /// + /// The color space to use for presenting the frame to the screen. + /// + /// + /// + /// The Color Space defines how colors are represented and displayed on the screen. + /// Common values include: + /// + /// + /// + /// + /// + /// It usually represents sRGB, the standard RGB color space used in most monitors and applications. + /// It offers a limited range of colors suitable for general purposes. + /// + /// + /// + /// + /// + /// A linear color space suitable for high-dynamic-range color values like HDR10, supporting a wider + /// range of brightness and colors. This is commonly used in modern HDR displays for enhanced image quality. + /// + /// + /// + /// + /// Choosing the appropriate color space affects the visual quality of your application. + /// For example, sRGB is recommended for compatibility, while HDR10 may enhance visuals in + /// games or applications designed for HDR content. + /// + /// + public ColorSpace ColorSpace; + /// - /// Describess how data will be displayed to the screen. + /// The color space type used for the Graphics Presenter output. /// - public class PresentationParameters : IEquatable + /// + /// + /// The output color space can be used to render to HDR monitors. + /// Consult the documentation of the enum for more details. + /// + /// + /// Note that this is currently only supported in Stride when using the Direct3D Graphics API. + /// For more information about High Dynamic Range (HDR) rendering, see + /// . + /// + /// + public ColorSpaceType OutputColorSpace; + + + /// + /// Initializes a new instance of the class with default values. + /// + /// + /// The returned instance will be configured with the following default values: + /// + /// + /// A back buffer resolution of 800x480 pixels, with a 32-bits-per-pixel integer format + /// (). + /// + /// + /// A 24-bit integer depth buffer with an additional 8-bit stencil buffer + /// (). + /// + /// No multi-sampling. + /// + /// Assuming a linear color space () and an output color space + /// , which is the default RGB output for monitors + /// with a standard gamma of 2.2. + /// + /// + /// A windowed presentation at 60 Hz with no V-Sync (). + /// + /// + /// + public PresentationParameters() + { + BackBufferWidth = DefaultBackBufferWidth; + BackBufferHeight = DefaultBackBufferHeight; + BackBufferFormat = DefaultBackBufferFormat; + PresentationInterval = DefaultPresentationInterval; + DepthStencilFormat = DefaultDepthStencilFormat; + MultisampleCount = DefaultMultisampleCount; + IsFullScreen = DefaultIsFullScreen; + RefreshRate = DefaultRefreshRate; + ColorSpace = DefaultColorSpace; + OutputColorSpace = DefaultOutputColorSpace; + } + + /// + /// Initializes a new instance of the class with default values, but + /// with the specified back buffer size, using , and window handle. + /// + /// The width of the back buffer, in pixels. + /// The height of the back buffer, in pixels. + /// The window handle. + /// + public PresentationParameters(int backBufferWidth, int backBufferHeight, WindowHandle windowHandle) + : this(backBufferWidth, backBufferHeight, windowHandle, PixelFormat.R8G8B8A8_UNorm) + { + } + + /// + /// Initializes a new instance of the class with default values, + /// but with the specified back buffer size, pixel format, and window handle. + /// + /// The width of the back buffer, in pixels. + /// The height of the back buffer, in pixels. + /// The back buffer format. + /// The window handle. + /// + public PresentationParameters(int backBufferWidth, int backBufferHeight, WindowHandle windowHandle, PixelFormat backBufferFormat) + : this() + { + BackBufferWidth = backBufferWidth; + BackBufferHeight = backBufferHeight; + DeviceWindowHandle = windowHandle; + BackBufferFormat = backBufferFormat; + } + + + /// + /// Creates a new object that is a copy of the current instance. + /// + /// A new object that is a copy of this instance. + public PresentationParameters Clone() + { + return (PresentationParameters) MemberwiseClone(); + } + + /// + public bool Equals(PresentationParameters other) + { + if (other is null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + return BackBufferFormat == other.BackBufferFormat + && BackBufferHeight == other.BackBufferHeight + && BackBufferWidth == other.BackBufferWidth + && DepthStencilFormat == other.DepthStencilFormat + && Equals(DeviceWindowHandle, other.DeviceWindowHandle) + && IsFullScreen == other.IsFullScreen + && MultisampleCount == other.MultisampleCount + && PresentationInterval == other.PresentationInterval + && RefreshRate.Equals(other.RefreshRate) + && PreferredFullScreenOutputIndex == other.PreferredFullScreenOutputIndex + && ColorSpace == other.ColorSpace; + } + + /// + public override bool Equals(object obj) + { + return obj is PresentationParameters parameters && Equals(parameters); + } + + /// + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(BackBufferFormat); + hash.Add(BackBufferHeight); + hash.Add(BackBufferWidth); + hash.Add(DepthStencilFormat); + hash.Add(DeviceWindowHandle); + hash.Add(IsFullScreen); + hash.Add(MultisampleCount); + hash.Add(PresentationInterval); + hash.Add(RefreshRate); + hash.Add(PreferredFullScreenOutputIndex); + hash.Add(ColorSpace); + return hash.ToHashCode(); + } + + public static bool operator ==(PresentationParameters left, PresentationParameters right) + { + return Equals(left, right); + } + + public static bool operator !=(PresentationParameters left, PresentationParameters right) { - #region Fields - - /// - /// A structure describing the display format. - /// - public PixelFormat BackBufferFormat; - - /// - /// A value that describes the resolution height. - /// - public int BackBufferHeight; - - /// - /// A value that describes the resolution width. - /// - public int BackBufferWidth; - - /// - /// Gets or sets the depth stencil format - /// - public PixelFormat DepthStencilFormat; - - /// - /// A Window object. See remarks. - /// - /// - /// A window object is platform dependent: - ///
    - ///
  • On Windows Desktop: This could a low level window/control handle (IntPtr), or directly a Winform Control object.
  • - ///
  • On Windows Metro: This could be SwapChainBackgroundPanel or SwapChainPanel object.
  • - ///
- ///
- public WindowHandle DeviceWindowHandle; - - /// - /// Gets or sets a value indicating whether the application is in full screen mode. - /// - public bool IsFullScreen; - - /// - /// Gets or sets a value indicating the number of sample locations during multisampling. - /// - public MultisampleCount MultisampleCount; - - /// - /// Gets or sets the maximum rate at which the swap chain's back buffers can be presented to the front buffer. - /// - public PresentInterval PresentationInterval; - - /// - /// A structure describing the refresh rate in hertz - /// - public Rational RefreshRate; - - /// - /// The output (monitor) index to use when switching to fullscreen mode. Doesn't have any effect when windowed mode is used. - /// - public int PreferredFullScreenOutputIndex; - - /// - /// The colorspace of the rendering pipeline. - /// - public ColorSpace ColorSpace; - - /// - /// The colorspace type used for the swapchain output. Currently only supported by the DirectX backend. - /// - public ColorSpaceType OutputColorSpace; - - - #endregion - - #region Constructors and Destructors - - /// - /// Initializes a new instance of the class with default values. - /// - public PresentationParameters() - { - BackBufferWidth = 800; - BackBufferHeight = 480; - BackBufferFormat = PixelFormat.R8G8B8A8_UNorm; - PresentationInterval = PresentInterval.Immediate; - DepthStencilFormat = PixelFormat.D24_UNorm_S8_UInt; - MultisampleCount = MultisampleCount.None; - IsFullScreen = false; - RefreshRate = new Rational(60, 1); // by default - ColorSpace = ColorSpace.Linear; - OutputColorSpace = ColorSpaceType.RgbFullG22NoneP709; // default rgb output for monitors with a standard gamma of 2.2 - } - - /// - /// Initializes a new instance of the class with . - /// - /// Width of the back buffer. - /// Height of the back buffer. - /// The device window handle. - public PresentationParameters(int backBufferWidth, int backBufferHeight, WindowHandle deviceWindowHandle) - : this(backBufferWidth, backBufferHeight, deviceWindowHandle, PixelFormat.R8G8B8A8_UNorm) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Width of the back buffer. - /// Height of the back buffer. - /// The device window handle. - /// The back buffer format. - public PresentationParameters(int backBufferWidth, int backBufferHeight, WindowHandle deviceWindowHandle, PixelFormat backBufferFormat) - : this() - { - BackBufferWidth = backBufferWidth; - BackBufferHeight = backBufferHeight; - DeviceWindowHandle = deviceWindowHandle; - BackBufferFormat = backBufferFormat; - } - - #endregion - - #region Methods - - public PresentationParameters Clone() - { - return (PresentationParameters)MemberwiseClone(); - } - - public bool Equals(PresentationParameters other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return BackBufferFormat == other.BackBufferFormat && BackBufferHeight == other.BackBufferHeight && BackBufferWidth == other.BackBufferWidth && DepthStencilFormat == other.DepthStencilFormat && Equals(DeviceWindowHandle, other.DeviceWindowHandle) && IsFullScreen == other.IsFullScreen && MultisampleCount == other.MultisampleCount && PresentationInterval == other.PresentationInterval && RefreshRate.Equals(other.RefreshRate) && PreferredFullScreenOutputIndex == other.PreferredFullScreenOutputIndex && ColorSpace == other.ColorSpace; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((PresentationParameters)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = (int)BackBufferFormat; - hashCode = (hashCode * 397) ^ BackBufferHeight; - hashCode = (hashCode * 397) ^ BackBufferWidth; - hashCode = (hashCode * 397) ^ (int)DepthStencilFormat; - hashCode = (hashCode * 397) ^ (DeviceWindowHandle != null ? DeviceWindowHandle.GetHashCode() : 0); - hashCode = (hashCode * 397) ^ IsFullScreen.GetHashCode(); - hashCode = (hashCode * 397) ^ (int)MultisampleCount; - hashCode = (hashCode * 397) ^ (int)PresentationInterval; - hashCode = (hashCode * 397) ^ RefreshRate.GetHashCode(); - hashCode = (hashCode * 397) ^ PreferredFullScreenOutputIndex; - hashCode = (hashCode * 397) ^ (int)ColorSpace; - return hashCode; - } - } - - public static bool operator ==(PresentationParameters left, PresentationParameters right) - { - return Equals(left, right); - } - - public static bool operator !=(PresentationParameters left, PresentationParameters right) - { - return !Equals(left, right); - } - - #endregion + return !Equals(left, right); } } diff --git a/sources/engine/Stride.Graphics/PrimitiveQuad.cs b/sources/engine/Stride.Graphics/PrimitiveQuad.cs index 6d536322df..ef5cdf1aae 100644 --- a/sources/engine/Stride.Graphics/PrimitiveQuad.cs +++ b/sources/engine/Stride.Graphics/PrimitiveQuad.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core; using Stride.Core.Mathematics; using Stride.Rendering; @@ -9,33 +8,39 @@ namespace Stride.Graphics { /// - /// Primitive quad use to draw an effect on a quad (fullscreen by default). This is directly accessible from the method. + /// A primitive triangle that can be used to draw a Texture or an Effect, + /// commonly used to draw full-screen. + /// This is directly accessible from the method. /// public class PrimitiveQuad : ComponentBase { - /// - /// The pipeline state. - /// private readonly MutablePipelineState pipelineState; private readonly EffectInstance simpleEffect; private readonly SharedData sharedData; - private const int QuadCount = 3; + /// + /// The definition of the layout of the vertices used to draw the triangle. + /// public static readonly VertexDeclaration VertexDeclaration = VertexPositionNormalTexture.Layout; + /// + /// The type of primitives used to draw the triangle. + /// public static readonly PrimitiveType PrimitiveType = PrimitiveType.TriangleList; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The graphics device. - /// The effect. + /// The Graphics Device. public PrimitiveQuad(GraphicsDevice graphicsDevice) { GraphicsDevice = graphicsDevice; - sharedData = GraphicsDevice.GetOrCreateSharedData("PrimitiveQuad::VertexBuffer", d => new SharedData(GraphicsDevice)); - simpleEffect = new EffectInstance(new Effect(GraphicsDevice, SpriteEffect.Bytecode)); + sharedData = graphicsDevice.GetOrCreateSharedData(SharedData.SharedDataKey, static device => new SharedData(device)); + + var spriteEffect = new Effect(graphicsDevice, SpriteEffect.Bytecode).DisposeBy(this); + simpleEffect = new EffectInstance(spriteEffect).DisposeBy(this); simpleEffect.Parameters.Set(SpriteBaseKeys.MatrixTransform, Matrix.Identity); simpleEffect.UpdateEffect(graphicsDevice); @@ -45,32 +50,40 @@ public PrimitiveQuad(GraphicsDevice graphicsDevice) pipelineState.State.PrimitiveType = PrimitiveType; } + /// - /// Gets the graphics device. + /// Gets the Graphics Device. /// - /// The graphics device. - public GraphicsDevice GraphicsDevice { get; private set; } + public GraphicsDevice GraphicsDevice { get; } /// - /// Gets the parameters used. + /// Gets the parameters used by the default Effect used to draw. /// - /// The parameters. public ParameterCollection Parameters => simpleEffect.Parameters; + /// - /// Draws a quad. The effect must have been applied before calling this method with pixel shader having the signature float2:TEXCOORD. + /// Draws a full-screen triangle. /// - /// + /// The Command List to use for drawing. + /// + /// An Effect with a Pixel Shader having the signature float2 : TEXCOORD + /// must have been applied before calling this method. + /// public void Draw(CommandList commandList) { - commandList.SetVertexBuffer(0, sharedData.VertexBuffer.Buffer, sharedData.VertexBuffer.Offset, sharedData.VertexBuffer.Stride); - commandList.Draw(QuadCount); + commandList.SetVertexBuffer(index: 0, sharedData.VertexBuffer.Buffer, sharedData.VertexBuffer.Offset, sharedData.VertexBuffer.Stride); + commandList.Draw(SharedData.VertexCount); } /// - /// Draws a quad. The effect must have been applied before calling this method with pixel shader having the signature float2:TEXCOORD. + /// Draws a full-screen triangle. /// - /// + /// The graphics context to use for drawing. + /// + /// The Effect instance to use for drawing. + /// It must define a Pixel Shader having the signature float2 : TEXCOORD. + /// public void Draw(GraphicsContext graphicsContext, EffectInstance effectInstance) { effectInstance.UpdateEffect(GraphicsDevice); @@ -83,30 +96,44 @@ public void Draw(GraphicsContext graphicsContext, EffectInstance effectInstance) graphicsContext.CommandList.SetPipelineState(pipelineState.CurrentState); - // Apply the effect effectInstance.Apply(graphicsContext); Draw(graphicsContext.CommandList); } /// - /// Draws a quad with a texture. This Draw method is using the current effect bound to this instance. + /// Draws a full-screen triangle with a Texture. /// - /// The texture. - /// The flag to apply effect states. + /// The graphics context to use for drawing. + /// The Texture to draw. + /// + /// An optional Blend State to use when drawing the Texture. + /// Specify to use . + /// + /// + /// The Texture will be sampled using the default Sampler State . + /// public void Draw(GraphicsContext graphicsContext, Texture texture, BlendStateDescription? blendState = null) { - Draw(graphicsContext, texture, null, Color.White, blendState); + Draw(graphicsContext, texture, samplerState: null, Color.White, blendState); } /// - /// Draws a quad with a texture. This Draw method is using a simple pixel shader that is sampling the texture. + /// Draws a full-screen triangle with a tinted Texture. /// - /// The texture to draw. - /// State of the sampler. If null, default sampler is . - /// The color. - /// The flag to apply effect states. - /// Expecting a Texture;texture + /// The graphics context to use for drawing. + /// The Texture to draw. + /// + /// The Sampler State to use for sampling the texture. + /// Specify to use the default Sampler State . + /// + /// + /// The color to tint the Texture with. The final color will be the texture color multiplied by the specified color. + /// + /// + /// An optional Blend State to use when drawing the Texture. + /// Specify to use . + /// public void Draw(GraphicsContext graphicsContext, Texture texture, SamplerState samplerState, Color4 color, BlendStateDescription? blendState = null) { pipelineState.State.RootSignature = simpleEffect.RootSignature; @@ -114,9 +141,10 @@ public void Draw(GraphicsContext graphicsContext, Texture texture, SamplerState pipelineState.State.BlendState = blendState ?? BlendStates.Default; pipelineState.State.Output.CaptureState(graphicsContext.CommandList); pipelineState.Update(); + graphicsContext.CommandList.SetPipelineState(pipelineState.CurrentState); - // Make sure that we are using our vertex shader + // Make sure that we are using our Vertex Shader simpleEffect.Parameters.Set(SpriteEffectKeys.Color, color); simpleEffect.Parameters.Set(TexturingKeys.Texture0, texture); simpleEffect.Parameters.Set(TexturingKeys.Sampler, samplerState ?? GraphicsDevice.SamplerStates.LinearClamp); @@ -124,35 +152,53 @@ public void Draw(GraphicsContext graphicsContext, Texture texture, SamplerState Draw(graphicsContext.CommandList); - // TODO ADD QUICK UNBIND FOR SRV + // TODO: ADD QUICK UNBIND FOR SRV //GraphicsDevice.Context.PixelShader.SetShaderResource(0, null); } + /// - /// Internal structure used to store VertexBuffer and VertexInputLayout. + /// Internal class used to store the Vertex Buffer and Vertex Input Layout. /// private class SharedData : ComponentBase { + public const string SharedDataKey = $"{nameof(PrimitiveQuad)}::VertexBuffer"; + /// - /// The vertex buffer + /// The Vertex Buffer. /// public readonly VertexBufferBinding VertexBuffer; - - private static readonly VertexPositionNormalTexture[] QuadsVertices = - { + + /// + /// The number of vertices in the triangle. + /// + public const int VertexCount = 3; + + // TODO: This is not a quad, but a fullscreen triangle! Maybe this class should be renamed? + private static readonly VertexPositionNormalTexture[] TriangleVertices = + [ + // Position Normal Texture Coordinates new VertexPositionNormalTexture(new Vector3(-1, 1, 0), new Vector3(0, 0, 1), new Vector2(0, 0)), new VertexPositionNormalTexture(new Vector3(+3, 1, 0), new Vector3(0, 0, 1), new Vector2(2, 0)), new VertexPositionNormalTexture(new Vector3(-1, -3, 0), new Vector3(0, 0, 1), new Vector2(0, 2)), - }; + ]; + - public SharedData(GraphicsDevice device) + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + public SharedData(GraphicsDevice device) : base(name: SharedDataKey) { - var vertexBuffer = Buffer.Vertex.New(device, QuadsVertices).DisposeBy(this); - + var vertexBuffer = Buffer.Vertex.New(device, TriangleVertices).DisposeBy(this); + vertexBuffer.Name = device.IsDebugMode + ? $"{SharedDataKey} ({vertexBuffer.Name})" + : SharedDataKey; + // Register reload - vertexBuffer.Reload = (graphicsResource, services) => ((Buffer)graphicsResource).Recreate(QuadsVertices); + vertexBuffer.Reload = (graphicsResource, services) => ((Buffer) graphicsResource).Recreate(TriangleVertices); - VertexBuffer = new VertexBufferBinding(vertexBuffer, VertexDeclaration, QuadsVertices.Length, VertexPositionNormalTexture.Size); + VertexBuffer = new VertexBufferBinding(vertexBuffer, VertexDeclaration, TriangleVertices.Length, VertexPositionNormalTexture.Size); } } } diff --git a/sources/engine/Stride.Graphics/PrimitiveType.cs b/sources/engine/Stride.Graphics/PrimitiveType.cs index bf6181558e..b2a19c1065 100644 --- a/sources/engine/Stride.Graphics/PrimitiveType.cs +++ b/sources/engine/Stride.Graphics/PrimitiveType.cs @@ -1,65 +1,116 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Values that indicate how the graphics pipeline interprets input vertex data. +/// +[DataContract] +public enum PrimitiveType { /// - /// Defines how vertex data is ordered. + /// The graphics pipeline has not been initialized with a specific primitive topology. + ///
+ /// The Input-Assembler stage will not function properly unless a primitive type is defined. ///
- [DataContract] - public enum PrimitiveType - { - /// - /// No documentation. - /// - Undefined = unchecked((int)0), + Undefined = 0, - /// - /// No documentation. - /// - PointList = unchecked((int)1), + /// + /// The data is ordered as a sequence of points, with no connectivity between them. + ///
+ /// This is useful for rendering particles or point clouds. + ///
+ /// The count may be any positive integer. + ///
+ PointList = 1, - /// - /// The data is ordered as a sequence of line segments; each line segment is described by two new vertices. The count may be any positive integer. - /// - LineList = unchecked((int)2), + /// + /// The data is ordered as a sequence of line segments; + /// each line segment is described by two new vertices. + ///
+ /// The count may be any positive even integer. + ///
+ LineList = 2, - /// - /// The data is ordered as a sequence of line segments; each line segment is described by one new vertex and the last vertex from the previous line seqment. The count may be any positive integer. - /// - LineStrip = unchecked((int)3), + /// + /// The data is ordered as a sequence of line segments; + /// each line segment is described by one new vertex and the last vertex from the previous line seqment. + ///
+ /// The count may be any positive integer greater than 2. + ///
+ LineStrip = 3, - /// - /// The data is ordered as a sequence of triangles; each triangle is described by three new vertices. Back-face culling is affected by the current winding-order render state. - /// - TriangleList = unchecked((int)4), + /// + /// The data is ordered as a sequence of triangles; + /// each triangle is described by three new vertices. Most commonly used for rendering 3D models. + ///
+ /// The count may be any positive integer multiple of 3. + ///
+ /// Back-face culling is affected by the current winding-order Render State. + ///
+ TriangleList = 4, - /// - /// The data is ordered as a sequence of triangles; each triangle is described by two new vertices and one vertex from the previous triangle. The back-face culling flag is flipped automatically on even-numbered - /// - TriangleStrip = unchecked((int)5), + /// + /// The data is ordered as a sequence of triangles; + /// each triangle is described by two new vertices and one vertex from the previous triangle. + ///
+ /// It is usually more space-efficient than a triangle list due to reduced vertex redundancy. + ///
+ /// The back-face culling flag is flipped automatically on even-numbered triangles. + ///
+ TriangleStrip = 5, - /// - /// No documentation. - /// - LineListWithAdjacency = unchecked((int)10), + /// + /// The data is ordered as a sequence of line segments, the same as , + /// but including adjacency information that can be useful for Geometry Shaders; + /// each line segment is described by two new vertices. + ///
+ /// The count may be any positive even integer. + ///
+ LineListWithAdjacency = 10, - /// - /// No documentation. - /// - LineStripWithAdjacency = unchecked((int)11), + /// + /// The data is ordered as a sequence of line segments, the same as , + /// but including adjacency information that can be useful for Geometry Shaders; + /// each line segment is described by one new vertex and the last vertex from the previous line seqment. + ///
+ /// The count may be any positive integer greater than 2. + ///
+ LineStripWithAdjacency = 11, - /// - /// No documentation. - /// - TriangleListWithAdjacency = unchecked((int)12), + /// + /// The data is ordered as a sequence of triangles, the same as , + /// but including adjacency information that can be useful for Geometry Shaders; + /// each triangle is described by three new vertices. Most commonly used for rendering 3D models. + ///
+ /// The count may be any positive integer multiple of 3. + ///
+ /// Back-face culling is affected by the current winding-order Render State. + ///
+ TriangleListWithAdjacency = 12, - /// - /// No documentation. - /// - TriangleStripWithAdjacency = unchecked((int)13), + /// + /// The data is ordered as a sequence of triangles, the same as , + /// but including adjacency information that can be useful for Geometry Shaders; + /// each triangle is described by two new vertices and one vertex from the previous triangle. + ///
+ /// It is usually more space-efficient than a triangle list due to reduced vertex redundancy. + ///
+ /// The back-face culling flag is flipped automatically on even-numbered triangles. + ///
+ TriangleStripWithAdjacency = 13, - PatchList = unchecked((int)33), - } + /// + /// The data is ordered as a sequence of patches, each one being a group of control points. + /// This is used for tessellation, where the Hull Shader processes each patch and determines tesselation factors, + /// optionally outputting more control point data. + ///
+ /// To indicate the number of control points in each patch, you can use the helper method . + ///
+ /// These topologies unlock a lot of flexibility for surfaces like terrain, curved surfaces, or even animation rigs. + ///
+ PatchList = 33 } diff --git a/sources/engine/Stride.Graphics/PrimitiveTypeExtensions.cs b/sources/engine/Stride.Graphics/PrimitiveTypeExtensions.cs index f3ea0182ff..abc1ee0da0 100644 --- a/sources/engine/Stride.Graphics/PrimitiveTypeExtensions.cs +++ b/sources/engine/Stride.Graphics/PrimitiveTypeExtensions.cs @@ -1,24 +1,35 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines extensions and helpers for . +/// +public static class PrimitiveTypeExtensions { - public static class PrimitiveTypeExtensions + /// + /// Interpret the input vertex data type as a patch list for tesselation with a + /// specific number of control points. + /// + /// The number of control points. It must be a value in the range 1 to 32, inclusive. + /// + /// A value that represents a patch list with the specified number of control points. + /// + /// Control points apply only to . + /// must be in the range 1 to 32, inclusive." + public static PrimitiveType ControlPointCount(this PrimitiveType primitiveType, int controlPoints) { - /// - /// Interpret the vertex data as a patch list. - /// - /// Number of control points. Value must be in the range 1 to 32. - public static PrimitiveType ControlPointCount(this PrimitiveType primitiveType, int controlPoints) - { - if (primitiveType != PrimitiveType.PatchList) - throw new ArgumentException("Control points apply only to PrimitiveType.PatchList", "primitiveType"); + if (primitiveType != PrimitiveType.PatchList) + throw new ArgumentException($"Control points apply only to {nameof(PrimitiveType)}.{nameof(PrimitiveType.PatchList)}", nameof(primitiveType)); + + const int MIN_CONTROL_POINTS = 1, MAX_CONTROL_POINTS = 32; - if (controlPoints < 1 || controlPoints > 32) - throw new ArgumentException("Value must be in between 1 and 32", "controlPoints"); + if (controlPoints < MIN_CONTROL_POINTS || controlPoints > MAX_CONTROL_POINTS) + throw new ArgumentOutOfRangeException(nameof(controlPoints), $"Value must be in between {MIN_CONTROL_POINTS} and {MAX_CONTROL_POINTS}"); - return PrimitiveType.PatchList + controlPoints - 1; - } + return PrimitiveType.PatchList + controlPoints - 1; } } diff --git a/sources/engine/Stride.Graphics/QueryPool.cs b/sources/engine/Stride.Graphics/QueryPool.cs index ae83be400b..f9ede1888e 100644 --- a/sources/engine/Stride.Graphics/QueryPool.cs +++ b/sources/engine/Stride.Graphics/QueryPool.cs @@ -1,50 +1,62 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A pool holding asynchronous GPU Queries of a specific type. +/// +/// +public partial class QueryPool : GraphicsResourceBase { /// - /// A pool holding queries with a specific . + /// Gets the types of asynchronous GPU Queries in the pool. + /// + public QueryType QueryType { get; } + + /// + /// Gets the capacity of the pool. /// - public partial class QueryPool : GraphicsResourceBase + public int QueryCount { get; } + + + /// + /// Creates a new . + /// + /// The Graphics Device. + /// The type of GPU Queries to contain in the pool. + /// The capacity of the pool. + /// An new instance of of the specified . + public static QueryPool New(GraphicsDevice graphicsDevice, QueryType queryType, int queryCount) { - /// - /// for this pool. - /// - public QueryType QueryType { get; } - - /// - /// Capacity of this pool. - /// - public int QueryCount { get; } - - /// - /// Creates a new instance. - /// - /// The . - /// The of the pool. - /// The capacity of the pool. - /// An instance of a new - public static QueryPool New(GraphicsDevice graphicsDevice, QueryType queryType, int queryCount) - { - return new QueryPool(graphicsDevice, queryType, queryCount); - } - - protected QueryPool(GraphicsDevice graphicsDevice, QueryType queryType, int queryCount) : base(graphicsDevice) - { - QueryType = queryType; - QueryCount = queryCount; - - Recreate(); - } - - /// - protected internal override bool OnRecreate() - { - base.OnRecreate(); - - Recreate(); - return true; - } + return new QueryPool(graphicsDevice, queryType, queryCount); } -} \ No newline at end of file + + /// + /// Initializes a new instance of the class. + /// + /// The Graphics Device. + /// The type of GPU Queries to contain in the pool. + /// The capacity of the pool. + protected QueryPool(GraphicsDevice graphicsDevice, QueryType queryType, int queryCount) : base(graphicsDevice) + { + QueryType = queryType; + QueryCount = queryCount; + + Recreate(); + } + + /// + protected internal override bool OnRecreate() + { + base.OnRecreate(); + + Recreate(); + return true; + } + + /// + /// Platform-specific implementation that recreates the queries in the pool. + /// + private unsafe partial void Recreate(); +} diff --git a/sources/engine/Stride.Graphics/QueryType.cs b/sources/engine/Stride.Graphics/QueryType.cs index d1a20e85a2..240c600dc4 100644 --- a/sources/engine/Stride.Graphics/QueryType.cs +++ b/sources/engine/Stride.Graphics/QueryType.cs @@ -1,9 +1,16 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Defines the types of a GPU query. +/// +public enum QueryType { - public enum QueryType - { - Timestamp = 0, - } + /// + /// Represents a timestamp value, typically used to indicate a point in time. + /// Used for measuring the time taken by GPU operations. + /// + Timestamp = 0 } diff --git a/sources/engine/Stride.Graphics/RasterizerStateDescription.cs b/sources/engine/Stride.Graphics/RasterizerStateDescription.cs index ca21bc5379..7c4906a59c 100644 --- a/sources/engine/Stride.Graphics/RasterizerStateDescription.cs +++ b/sources/engine/Stride.Graphics/RasterizerStateDescription.cs @@ -6,142 +6,211 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A description of a Rasterizer State, which defines how primitives are rasterized to the Render Targets. +/// +/// +/// This structure controls fill mode, primitive culling, multisampling, depth bias, and clipping. +/// +/// +[DataContract] +[StructLayout(LayoutKind.Sequential)] +public struct RasterizerStateDescription : IEquatable { /// - /// Describes a rasterizer state. + /// Initializes a new instance of the structure. /// - [DataContract] - [StructLayout(LayoutKind.Sequential)] - public struct RasterizerStateDescription : IEquatable + /// The cull mode. + public RasterizerStateDescription(CullMode cullMode) : this() { - /// - /// Initializes a new instance of the class. - /// - /// The cull mode. - public RasterizerStateDescription(CullMode cullMode) : this() - { - SetDefault(); - CullMode = cullMode; - } - - /// - /// Determines the fill mode to use when rendering (see ). - /// - public FillMode FillMode; - - /// - /// Indicates triangles facing the specified direction are not drawn (see ). - /// - public CullMode CullMode; - - /// - /// Determines if a triangle is front- or back-facing. If this parameter is true, then a triangle will be considered front-facing if its vertices are counter-clockwise on the render target and considered back-facing if they are clockwise. If this parameter is false then the opposite is true. - /// - public bool FrontFaceCounterClockwise; - - /// - /// Depth value added to a given pixel. - /// - public int DepthBias; - - /// - /// Gets or sets the depth bias for polygons, which is the amount of bias to apply to the depth of a primitive to alleviate depth testing problems for primitives of similar depth. The default value is 0. - /// - public float DepthBiasClamp; - - /// - /// Scalar on a given pixel's slope. - /// - public float SlopeScaleDepthBias; - - /// - /// Enable clipping based on distance. - /// - public bool DepthClipEnable; - - /// - /// Enable scissor-rectangle culling. All pixels ouside an active scissor rectangle are culled. - /// - public bool ScissorTestEnable; - - /// - /// Multisample level. - /// - public MultisampleCount MultisampleCount; - - /// - /// Enable line antialiasing; only applies if doing line drawing and MultisampleEnable is false. - /// - public bool MultisampleAntiAliasLine; - - /// - /// Sets default values for this instance. - /// - public void SetDefault() - { - CullMode = CullMode.Back; - FillMode = FillMode.Solid; - DepthClipEnable = true; - FrontFaceCounterClockwise = false; - ScissorTestEnable = false; - MultisampleCount = MultisampleCount.None; - MultisampleAntiAliasLine = false; - DepthBias = 0; - DepthBiasClamp = 0f; - SlopeScaleDepthBias = 0f; - } - - /// - /// Gets default values for this instance. - /// - public static RasterizerStateDescription Default - { - get - { - var desc = new RasterizerStateDescription(); - desc.SetDefault(); - return desc; - } - } - - public bool Equals(RasterizerStateDescription other) - { - return FillMode == other.FillMode && CullMode == other.CullMode && FrontFaceCounterClockwise == other.FrontFaceCounterClockwise && DepthBias == other.DepthBias && DepthBiasClamp.Equals(other.DepthBiasClamp) && SlopeScaleDepthBias.Equals(other.SlopeScaleDepthBias) && DepthClipEnable == other.DepthClipEnable && ScissorTestEnable == other.ScissorTestEnable && MultisampleCount == other.MultisampleCount && MultisampleAntiAliasLine == other.MultisampleAntiAliasLine; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is RasterizerStateDescription && Equals((RasterizerStateDescription)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = (int)FillMode; - hashCode = (hashCode * 397) ^ (int)CullMode; - hashCode = (hashCode * 397) ^ FrontFaceCounterClockwise.GetHashCode(); - hashCode = (hashCode * 397) ^ DepthBias; - hashCode = (hashCode * 397) ^ DepthBiasClamp.GetHashCode(); - hashCode = (hashCode * 397) ^ SlopeScaleDepthBias.GetHashCode(); - hashCode = (hashCode * 397) ^ DepthClipEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ ScissorTestEnable.GetHashCode(); - hashCode = (hashCode * 397) ^ (int)MultisampleCount; - hashCode = (hashCode * 397) ^ MultisampleAntiAliasLine.GetHashCode(); - return hashCode; - } - } - - public static bool operator ==(RasterizerStateDescription left, RasterizerStateDescription right) - { - return left.Equals(right); - } - - public static bool operator !=(RasterizerStateDescription left, RasterizerStateDescription right) - { - return !left.Equals(right); - } + SetDefaults(); + CullMode = cullMode; + } + + + /// + /// Specifies how primitives are filled during rasterization (e.g., solid or wireframe). + /// + /// + /// Common values include for standard rendering and for debugging geometry. + /// Wireframe mode is especially useful for visualizing mesh topology or detecting overdraw. + /// + public FillMode FillMode; + + /// + /// Specifies which triangle facing direction should be culled (not rendered) during rasterization. + /// The facing direction is determined by the setting. + /// + /// + /// This property determines whether front-facing or back-facing triangles are culled. + /// A triangle's facing is defined by the winding order of its vertices and the value of . + /// For example, if FrontFaceCounterClockwise is (clockwise is front-facing), + /// and CullMode is set to , then counter-clockwise triangles will be culled. + /// + public CullMode CullMode; + + /// + /// Determines the winding order used to identify front-facing triangles. + /// + /// If , triangles with vertices ordered counter-clockwise on the render target are considered front-facing. + /// If , triangles with clockwise winding are considered front-facing. + /// + /// This setting affects how determines which triangles to cull. + /// + /// + /// This setting defines the convention for front-facing triangles. Combined with the value, + /// it determines whether front-facing or back-facing triangles are culled during rasterization. + /// For example, if FrontFaceCounterClockwise is (the default in Direct3D), + /// and CullMode is set to , then triangles with clockwise winding will be culled. + /// + public bool FrontFaceCounterClockwise; + + /// + /// Constant depth bias added to each pixel's depth value. + /// + /// + /// This value is added to the depth of each pixel and is typically used to resolve Z-fighting, + /// such as when rendering decals or wireframe overlays on top of solid geometry. + /// The actual depth offset depends on the Depth Buffer format and the slope of the primitive. + /// + public int DepthBias; + + /// + /// Maximum depth bias that can be applied to a pixel. + /// + /// + /// Clamps the total depth bias applied to a pixel, after combining and . + /// This is useful to prevent excessive biasing on steep slopes or when using large bias values. + /// + public float DepthBiasClamp; + + /// + /// Scalar applied to a primitive's slope to compute a variable depth bias. + /// Helps offset depth values based on surface angle. + /// + /// + /// This value is multiplied by the maximum slope of the primitive to compute a variable depth bias. + /// It helps reduce Z-fighting on surfaces that are nearly parallel to the view direction. + /// Often used in conjunction with for shadow mapping or coplanar geometry. + /// + public float SlopeScaleDepthBias; + + /// + /// Enables or disables clipping of geometry based on the depth (Z) value. + /// When enabled, primitives outside the near and far clip planes are discarded. + /// + /// + /// When enabled, geometry outside the near and far clip planes is discarded. + /// Disabling this can be useful for special effects like infinite projection or stencil shadows, + /// but may lead to incorrect depth ordering if not handled carefully. + /// + public bool DepthClipEnable; + + // TODO: D3D12: In Direct3D 12, Scissor rectangles are set through the Command List dynamically, not through immutable Render States + + /// + /// Enables scissor testing. Pixels outside the active scissor rectangle are culled. + /// + /// + /// When enabled, only pixels inside the active scissor rectangle are rendered. + /// This is commonly used for UI rendering, partial redraws, or performance optimization. + /// + public bool ScissorTestEnable; + + /// + /// Specifies the number of samples used for multisample anti-aliasing (MSAA). + /// + /// + /// Higher sample counts improve edge smoothness but increase memory and processing cost. + /// + public MultisampleCount MultisampleCount; + + /// + /// Enables antialiasing for lines when MSAA is disabled. Only affects line rendering. + /// + /// + /// This only affects line primitives, and has no effect when is greater than 1. + /// + public bool MultisampleAntiAliasLine; + + + /// + /// Sets default values for this Rasterizer State description. + /// + /// + /// The default values are: + /// + /// : Rasterize filled triangles (). + /// : Cull back-facing primitives (). + /// : Consider front-facing the primitives whose vertices are ordered clockwise (). + /// : Clip primitives outside the near and far clipping planes (). + /// Scissor testing disabled. + /// No multisampling () and no antialiased lines. + /// No , , or . + /// + /// + public void SetDefaults() + { + FillMode = FillMode.Solid; + CullMode = CullMode.Back; + FrontFaceCounterClockwise = false; + DepthClipEnable = true; + ScissorTestEnable = false; + MultisampleCount = MultisampleCount.None; + MultisampleAntiAliasLine = false; + DepthBias = 0; + DepthBiasClamp = 0f; + SlopeScaleDepthBias = 0f; + } + + + /// + public readonly bool Equals(RasterizerStateDescription other) + { + return FillMode == other.FillMode + && CullMode == other.CullMode + && FrontFaceCounterClockwise == other.FrontFaceCounterClockwise + && DepthBias == other.DepthBias + && DepthBiasClamp.Equals(other.DepthBiasClamp) + && SlopeScaleDepthBias.Equals(other.SlopeScaleDepthBias) + && DepthClipEnable == other.DepthClipEnable + && ScissorTestEnable == other.ScissorTestEnable + && MultisampleCount == other.MultisampleCount + && MultisampleAntiAliasLine == other.MultisampleAntiAliasLine; + } + + /// + public override readonly bool Equals(object obj) + { + return obj is RasterizerStateDescription rsdesc && Equals(rsdesc); + } + + public static bool operator ==(RasterizerStateDescription left, RasterizerStateDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(RasterizerStateDescription left, RasterizerStateDescription right) + { + return !left.Equals(right); + } + + /// + public override readonly int GetHashCode() + { + var hash = new HashCode(); + hash.Add(FillMode); + hash.Add(CullMode); + hash.Add(FrontFaceCounterClockwise); + hash.Add(DepthBias); + hash.Add(DepthBiasClamp); + hash.Add(SlopeScaleDepthBias); + hash.Add(DepthClipEnable); + hash.Add(ScissorTestEnable); + hash.Add(MultisampleCount); + hash.Add(MultisampleAntiAliasLine); + return hash.ToHashCode(); } } diff --git a/sources/engine/Stride.Graphics/RasterizerStates.cs b/sources/engine/Stride.Graphics/RasterizerStates.cs index c724536394..0d5622f4a8 100644 --- a/sources/engine/Stride.Graphics/RasterizerStates.cs +++ b/sources/engine/Stride.Graphics/RasterizerStates.cs @@ -1,42 +1,54 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a set of built-in s for common rasterizer configurations. +/// +public static class RasterizerStates { - /// - /// Known values for . - /// - public static class RasterizerStates + static RasterizerStates() { - /// - /// Built-in rasterizer state object with settings for culling primitives with clockwise winding order. - /// - public static readonly RasterizerStateDescription CullFront = new RasterizerStateDescription(CullMode.Front); - - /// - /// Built-in rasterizer state object with settings for culling primitives with counter-clockwise winding order. - /// - public static readonly RasterizerStateDescription CullBack = new RasterizerStateDescription(CullMode.Back); - - /// - /// Built-in rasterizer state object with settings for not culling any primitives. - /// - public static readonly RasterizerStateDescription CullNone = new RasterizerStateDescription(CullMode.None); - - /// - /// Built-in rasterizer state object for wireframe rendering with settings for culling primitives with clockwise winding order. - /// - public static readonly RasterizerStateDescription WireframeCullFront = new RasterizerStateDescription(CullMode.Front) { FillMode = FillMode.Wireframe }; - - /// - /// Built-in rasterizer state object for wireframe with settings for culling primitives with counter-clockwise winding order. - /// - public static readonly RasterizerStateDescription WireframeCullBack = new RasterizerStateDescription(CullMode.Back) { FillMode = FillMode.Wireframe }; - - /// - /// Built-in rasterizer state object for wireframe with settings for not culling any primitives. - /// - public static readonly RasterizerStateDescription Wireframe = new RasterizerStateDescription(CullMode.None) { FillMode = FillMode.Wireframe }; + var defaultState = new RasterizerStateDescription(); + defaultState.SetDefaults(); + Default = defaultState; } + + + /// + /// A built-in Rasterizer State object with default settings. + /// + /// + public static readonly RasterizerStateDescription Default; + + /// + /// A built-in Rasterizer State object with settings for culling primitives with clockwise winding order. + /// + public static readonly RasterizerStateDescription CullFront = new(CullMode.Front); + + /// + /// A built-in Rasterizer State object with settings for culling primitives with counter-clockwise winding order. + /// + public static readonly RasterizerStateDescription CullBack = new(CullMode.Back); + + /// + /// A built-in Rasterizer State object with settings for not culling any primitives. + /// + public static readonly RasterizerStateDescription CullNone = new(CullMode.None); + + /// + /// A built-in Rasterizer State object for wireframe rendering with settings for culling primitives with clockwise winding order. + /// + public static readonly RasterizerStateDescription WireframeCullFront = new(CullMode.Front) { FillMode = FillMode.Wireframe }; + + /// + /// A built-in Rasterizer State object for wireframe with settings for culling primitives with counter-clockwise winding order. + /// + public static readonly RasterizerStateDescription WireframeCullBack = new(CullMode.Back) { FillMode = FillMode.Wireframe }; + + /// + /// A built-in Rasterizer State object for wireframe with settings for not culling any primitives. + /// + public static readonly RasterizerStateDescription Wireframe = new(CullMode.None) { FillMode = FillMode.Wireframe }; } diff --git a/sources/engine/Stride.Graphics/Rational.cs b/sources/engine/Stride.Graphics/Rational.cs index 1567d8bb12..f7933b6a0f 100644 --- a/sources/engine/Stride.Graphics/Rational.cs +++ b/sources/engine/Stride.Graphics/Rational.cs @@ -1,65 +1,86 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Represents a rational number. +/// +/// The numerator (top) of the rational number. +/// The denominator (bottom) of the rational number. +/// +/// The structure operates under the following rules: +/// +/// 0/0 is legal and will be interpreted as 0/1. +/// 0/anything is interpreted as zero. +/// If you are representing a whole number, the denominator should be 1. +/// +/// +public partial struct Rational(int numerator, int denominator) : IEquatable { /// - ///

Represents a rational number.

+ /// An value representing the top of the rational number. + ///
+ public int Numerator = numerator; + + /// + /// An value representing the bottom of the rational number. + /// + public int Denominator = denominator; + + + /// + /// Returns a new from an integer value. /// - /// - ///

The structure operates under the following rules:

  • 0/0 is legal and will be interpreted as 0/1.
  • 0/anything is interpreted as zero.
  • If you are representing a whole number, the denominator should be 1.
- ///
- public partial struct Rational : IEquatable + /// The integer value. + /// The integer as a rational number with denominator 1. + public static implicit operator Rational(int value) { - public Rational(int numerator, int denominator) - { - Numerator = numerator; - Denominator = denominator; - } + return new Rational(value, denominator: 1); + } - /// - ///

An unsigned integer value representing the top of the rational number.

- ///
- public int Numerator; - /// - ///

An unsigned integer value representing the bottom of the rational number.

- ///
- public int Denominator; + /// + /// Returns the string representation of this , including its approximate real value. + /// + /// The string representation of this . + public override string ToString() + { + return Denominator == 1 + ? Numerator.ToString() + : string.Format("{0}/{1} = {2}", Numerator, Denominator, (float) Numerator / Denominator); + } - public override string ToString() - { - return string.Format("{0}/{1} = {2}", Numerator, Denominator, (float)Numerator / Denominator); - } + /// + public readonly bool Equals(Rational other) + { + return Numerator == other.Numerator && Denominator == other.Denominator; + } - public bool Equals(Rational other) - { - return Numerator == other.Numerator && Denominator == other.Denominator; - } + /// + public override readonly bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is Rational && Equals((Rational)obj); - } + return obj is Rational rational && Equals(rational); + } - public override int GetHashCode() - { - unchecked - { - return (Numerator * 397) ^ Denominator; - } - } + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(Numerator, Denominator); + } - public static bool operator ==(Rational left, Rational right) - { - return left.Equals(right); - } + public static bool operator ==(Rational left, Rational right) + { + return left.Equals(right); + } - public static bool operator !=(Rational left, Rational right) - { - return !left.Equals(right); - } + public static bool operator !=(Rational left, Rational right) + { + return !left.Equals(right); } } diff --git a/sources/engine/Stride.Graphics/RenderOutputDescription.cs b/sources/engine/Stride.Graphics/RenderOutputDescription.cs index 12f7a1e91e..631985609a 100644 --- a/sources/engine/Stride.Graphics/RenderOutputDescription.cs +++ b/sources/engine/Stride.Graphics/RenderOutputDescription.cs @@ -3,49 +3,153 @@ using System; using System.ComponentModel; +using System.Runtime.InteropServices; + using Stride.Core; +using Stride.Core.UnsafeExtensions; namespace Stride.Graphics { /// - /// Describes render targets and depth stencil output formats. + /// Describes the output formats of the Render Targets and the Depth-Stencil Buffer. /// [DataContract] + [StructLayout(LayoutKind.Sequential)] // Sequential so RenderTargetFormats are all contiguous public struct RenderOutputDescription : IEquatable { - // Render targets - [DefaultValue(0)] - public int RenderTargetCount; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat0; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat1; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat2; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat3; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat4; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat5; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat6; - [DefaultValue(PixelFormat.None)] - public PixelFormat RenderTargetFormat7; - - [DefaultValue(PixelFormat.None)] - public PixelFormat DepthStencilFormat; - - [DefaultValue(MultisampleCount.None)] - public MultisampleCount MultisampleCount; - - /// - /// Enable scissor-rectangle culling. All pixels ouside an active scissor rectangle are culled. - /// - [DefaultValue(false)] - public bool ScissorTestEnable; - - public RenderOutputDescription(PixelFormat renderTargetFormat, PixelFormat depthStencilFormat = PixelFormat.None, MultisampleCount multisampleCount = MultisampleCount.None) : this() + #region Default values + + /// + /// Default value for . + /// + public const int DefaultRenderTargetCount = 0; + /// + /// Default value for the Render Targets pixel formats ( to ). + /// + public const PixelFormat DefaultRenderTargetFormat = PixelFormat.None; + /// + /// Default value for . + /// + public const PixelFormat DefaultDepthStencilFormat = PixelFormat.None; + /// + /// Default value for . + /// + public const Graphics.MultisampleCount DefaultMultiSampleCount = Graphics.MultisampleCount.None; + /// + /// Default value for . + /// + public const bool DefaultScissorTestEnable = false; + + #endregion + + /// + /// The maximum number of Render Targets configurable by the graphics pipeline. + /// + public const int MaximumRenderTargetCount = 8; + + /// + /// The number of Render Targets. + /// + [DefaultValue(DefaultRenderTargetCount)] + public int RenderTargetCount = DefaultRenderTargetCount; + + /// + /// Gets the pixel formats of the Render Targets. + /// + /// + /// There is a maximum of eight Render Targets. + /// If a Render Target is set to , it is considered disabled. + /// + public readonly Span RenderTargetFormats + // Trickery so the compiler allows us to return a non-readonly Span<> from a readonly property + => MemoryMarshal.CreateReadOnlySpan(in RenderTargetFormat0, MaximumRenderTargetCount).AsSpan(); + + /// + /// The pixel format of the Render Target at index 0. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat0 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 1. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat1 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 2. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat2 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 3. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat3 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 4. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat4 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 5. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat5 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 6. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat6 = DefaultRenderTargetFormat; + /// + /// The pixel format of the Render Target at index 7. + /// + [DefaultValue(DefaultRenderTargetFormat)] + public PixelFormat RenderTargetFormat7 = DefaultRenderTargetFormat; + + /// + /// The depth format of the Depth-Stencil Buffer. + /// + /// + /// Specify to disable the Depth-Stencil Buffer. + /// + [DefaultValue(DefaultDepthStencilFormat)] + public PixelFormat DepthStencilFormat = DefaultDepthStencilFormat; + + /// + /// The number of samples to use when multi-sampling. + /// + /// + /// Specify to disable multi-sampling. + /// + [DefaultValue(DefaultMultiSampleCount)] + public MultisampleCount MultisampleCount = DefaultMultiSampleCount; + + /// + /// A value indicating whether to enable scissor-rectangle culling. + /// All pixels ouside an active scissor rectangle are culled. + /// + [DefaultValue(DefaultScissorTestEnable)] + public bool ScissorTestEnable = DefaultScissorTestEnable; + + + /// + /// Initializes a new instance of the structure. + /// + /// + /// The pixel format of the Render Target. + /// Specify to disable the Render Targets. + /// + /// + /// The depth format of the Depth-Stencil Buffer. + /// Specify to disable the Depth-Stencil Buffer. + /// + /// + /// The number of samples to use when multi-sampling. + /// Specify to disable multi-sampling. + /// + public RenderOutputDescription(PixelFormat renderTargetFormat, + PixelFormat depthStencilFormat = PixelFormat.None, + MultisampleCount multisampleCount = MultisampleCount.None) + : this() { RenderTargetCount = renderTargetFormat != PixelFormat.None ? 1 : 0; RenderTargetFormat0 = renderTargetFormat; @@ -53,63 +157,91 @@ public RenderOutputDescription(PixelFormat renderTargetFormat, PixelFormat depth MultisampleCount = multisampleCount; } + /// + /// Initializes a new instance of the structure. + /// + /// + /// The pixel formats for up to 8 Render Targets. + /// If a Render Target is set to , it is considered disabled. + /// Specify an empty span or all set to to disable the Render Targets. + /// + /// + /// The depth format of the Depth-Stencil Buffer. + /// Specify to disable the Depth-Stencil Buffer. + /// + /// + /// The number of samples to use when multi-sampling. + /// Specify to disable multi-sampling. + /// + /// + /// Cannot specify the format for more than 8 Render Targets in . + /// + public RenderOutputDescription(ReadOnlySpan renderTargetFormats, + PixelFormat depthStencilFormat = PixelFormat.None, + MultisampleCount multisampleCount = MultisampleCount.None) + : this() + { + if (renderTargetFormats.Length > 8) + throw new ArgumentOutOfRangeException(nameof(renderTargetFormats), "Cannot specify the format for more than 8 Render Targets."); + + int lastSetRenderTarget = -1; + for (int i = 0; i < MaximumRenderTargetCount; i++) + if (renderTargetFormats[i] != PixelFormat.None) + lastSetRenderTarget = i; + + RenderTargetCount = lastSetRenderTarget + 1; // +1 so 0 if -1, 1 if 0, etc. + renderTargetFormats.CopyTo(RenderTargetFormats); + DepthStencilFormat = depthStencilFormat; + MultisampleCount = multisampleCount; + } + + + /// + /// Captures the description of the pipeline render output from a Command List. + /// + /// The Command List from which to capture the pipeline render output configuration. public unsafe void CaptureState(CommandList commandList) { - DepthStencilFormat = commandList.DepthStencilBuffer != null ? commandList.DepthStencilBuffer.ViewFormat : PixelFormat.None; - MultisampleCount = commandList.DepthStencilBuffer != null ? commandList.DepthStencilBuffer.MultisampleCount : MultisampleCount.None; + DepthStencilFormat = commandList.DepthStencilBuffer?.ViewFormat ?? PixelFormat.None; + MultisampleCount = commandList.DepthStencilBuffer?.MultisampleCount ?? MultisampleCount.None; ScissorTestEnable = !commandList.Scissor.IsEmpty; RenderTargetCount = commandList.RenderTargetCount; - fixed (PixelFormat* renderTargetFormat0 = &RenderTargetFormat0) + + for (int i = 0; i < RenderTargetCount; ++i) { - var renderTargetFormat = renderTargetFormat0; - for (int i = 0; i < RenderTargetCount; ++i) - { - *renderTargetFormat++ = commandList.RenderTargets[i].ViewFormat; - MultisampleCount = commandList.RenderTargets[i].MultisampleCount; // multisample should all be equal - } + RenderTargetFormats[i] = commandList.RenderTargets[i].ViewFormat; + MultisampleCount = commandList.RenderTargets[i].MultisampleCount; // Multi-sampling should all be equal } } - public bool Equals(RenderOutputDescription other) + + /// + public readonly bool Equals(RenderOutputDescription other) { return RenderTargetCount == other.RenderTargetCount - && RenderTargetFormat0 == other.RenderTargetFormat0 - && RenderTargetFormat1 == other.RenderTargetFormat1 - && RenderTargetFormat2 == other.RenderTargetFormat2 - && RenderTargetFormat3 == other.RenderTargetFormat3 - && RenderTargetFormat4 == other.RenderTargetFormat4 - && RenderTargetFormat5 == other.RenderTargetFormat5 - && RenderTargetFormat6 == other.RenderTargetFormat6 - && RenderTargetFormat7 == other.RenderTargetFormat7 - && DepthStencilFormat == other.DepthStencilFormat - && ScissorTestEnable == other.ScissorTestEnable; + && RenderTargetFormats.SequenceEqual(other.RenderTargetFormats) + && DepthStencilFormat == other.DepthStencilFormat + && ScissorTestEnable == other.ScissorTestEnable; } - public override bool Equals(object obj) + /// + public override readonly bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; - return obj is RenderOutputDescription && Equals((RenderOutputDescription)obj); + return obj is RenderOutputDescription description && Equals(description); } - public override int GetHashCode() + /// + public override readonly int GetHashCode() { - unchecked - { - var hashCode = RenderTargetCount; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat0; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat1; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat2; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat3; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat4; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat5; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat6; - hashCode = (hashCode * 397) ^ (int)RenderTargetFormat7; - hashCode = (hashCode * 397) ^ (int)DepthStencilFormat; - hashCode = (hashCode * 397) ^ (ScissorTestEnable ? 1 : 0); - return hashCode; - } + var hashCode = new HashCode(); + hashCode.Add(RenderTargetCount); + for (int i = 0; i < MaximumRenderTargetCount; i++) + hashCode.Add(RenderTargetFormats[i]); + hashCode.Add(DepthStencilFormat); + hashCode.Add(ScissorTestEnable); + return hashCode.ToHashCode(); } public static bool operator ==(RenderOutputDescription left, RenderOutputDescription right) diff --git a/sources/engine/Stride.Graphics/RenderTargetGraphicsPresenter.cs b/sources/engine/Stride.Graphics/RenderTargetGraphicsPresenter.cs index 56aee6d3c8..ac47fc2660 100644 --- a/sources/engine/Stride.Graphics/RenderTargetGraphicsPresenter.cs +++ b/sources/engine/Stride.Graphics/RenderTargetGraphicsPresenter.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,88 +21,101 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -namespace Stride.Graphics +using System; + +namespace Stride.Graphics; + +/// +/// A simple wrapping a Render-Target Texture +/// where drawing will occur but no actual presentation will happen. +/// +/// +/// This class is useful when configuring an application that will be rendering to a +/// Texture instead of the screen. +/// +public class RenderTargetGraphicsPresenter : GraphicsPresenter { + private Texture backBuffer; + + /// - /// Graphics presenter for SwapChain. + /// Initializes a new instance of the class. /// - public class RenderTargetGraphicsPresenter : GraphicsPresenter + /// The Graphics Device. + /// + /// A two-dimensional Texture that serves as a Render Target to draw into. + /// + /// The format of the Depth-Stencil buffer + public RenderTargetGraphicsPresenter(GraphicsDevice device, Texture renderTarget, PixelFormat depthFormat = PixelFormat.None) + : base(device, CreatePresentationParameters(renderTarget, depthFormat)) { - private Texture backBuffer; + PresentInterval = Description.PresentationInterval; - public RenderTargetGraphicsPresenter(GraphicsDevice device, Texture renderTarget, PixelFormat depthFormat = PixelFormat.None) - : base(device, CreatePresentationParameters(renderTarget, depthFormat)) - { - PresentInterval = Description.PresentationInterval; - // Initialize the swap chain - SetBackBuffer(renderTarget); - } + // Initialize the swap-chain + SetBackBuffer(renderTarget); + } - private static PresentationParameters CreatePresentationParameters(Texture renderTarget2D, PixelFormat depthFormat) + private static PresentationParameters CreatePresentationParameters(Texture renderTarget2D, PixelFormat depthFormat) + { + return new PresentationParameters { - return new PresentationParameters() - { - BackBufferWidth = renderTarget2D.Width, - BackBufferHeight = renderTarget2D.Height, - BackBufferFormat = renderTarget2D.ViewFormat, - DepthStencilFormat = depthFormat, - DeviceWindowHandle = null, - IsFullScreen = true, - MultisampleCount = renderTarget2D.MultisampleCount, - PresentationInterval = PresentInterval.One, - RefreshRate = new Rational(60, 1), - }; - } + BackBufferWidth = renderTarget2D.Width, + BackBufferHeight = renderTarget2D.Height, + BackBufferFormat = renderTarget2D.ViewFormat, + DepthStencilFormat = depthFormat, + DeviceWindowHandle = null, + IsFullScreen = true, + MultisampleCount = renderTarget2D.MultisampleCount, + PresentationInterval = PresentInterval.One, + RefreshRate = 60 + }; + } - public override Texture BackBuffer - { - get - { - return backBuffer; - } - } - /// - /// Sets the back buffer. - /// - /// The back buffer. - public void SetBackBuffer(Texture backBuffer) - { - this.backBuffer = backBuffer.EnsureRenderTarget(); - } + /// + public override Texture BackBuffer => backBuffer; - public override object NativePresenter - { - get - { - return backBuffer; - } - } + /// + /// Sets the Back-Buffer where the frame must be rendered. + /// + /// The Render Target to use as Back-Buffer. + public void SetBackBuffer(Texture backBuffer) + { + this.backBuffer = backBuffer.EnsureRenderTarget(); + } - public override bool IsFullScreen - { - get - { - return true; - } + /// + public override object NativePresenter => backBuffer; - set - { - } - } + /// + /// + /// A always returns + /// for this property, and this value cannot be modified. + /// + public override bool IsFullScreen + { + get => true; + set { } + } - public override void Present() - { - } + /// + /// + /// A does nothing for this method; it wraps + /// an internal Render-Target Texture to draw to, and not a swap-chain buffer. + /// + public override void Present() + { + } - protected override void ResizeBackBuffer(int width, int height, PixelFormat format) - { - throw new System.NotImplementedException(); - } + /// + protected override void ResizeBackBuffer(int width, int height, PixelFormat format) + { + throw new NotImplementedException(); + } - protected override void ResizeDepthStencilBuffer(int width, int height, PixelFormat format) - { - throw new System.NotImplementedException(); - } + /// + protected override void ResizeDepthStencilBuffer(int width, int height, PixelFormat format) + { + throw new NotImplementedException(); } } diff --git a/sources/engine/Stride.Graphics/Rendering/EffectInstance.cs b/sources/engine/Stride.Graphics/Rendering/EffectInstance.cs index 8b1689c796..9bb7538a50 100644 --- a/sources/engine/Stride.Graphics/Rendering/EffectInstance.cs +++ b/sources/engine/Stride.Graphics/Rendering/EffectInstance.cs @@ -68,7 +68,7 @@ public bool UpdateEffect(GraphicsDevice graphicsDevice) // Update reflection and rearrange buffers/resources var layoutNames = effect.Bytecode.Reflection.ResourceBindings.Select(x => x.ResourceGroup ?? "Globals").Distinct().ToList(); - descriptorReflection = EffectDescriptorSetReflection.New(graphicsDevice, effect.Bytecode, layoutNames, "Globals"); + descriptorReflection = EffectDescriptorSetReflection.New(graphicsDevice, effect.Bytecode, layoutNames, defaultSetSlot: "Globals"); RootSignature?.Dispose(); RootSignature = RootSignature.New(graphicsDevice, descriptorReflection); diff --git a/sources/engine/Stride.Graphics/ResourceBinder.cs b/sources/engine/Stride.Graphics/ResourceBinder.cs index 221329a246..b295aee1d5 100644 --- a/sources/engine/Stride.Graphics/ResourceBinder.cs +++ b/sources/engine/Stride.Graphics/ResourceBinder.cs @@ -1,26 +1,51 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_OPENGL + using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Stride.Core; + using Stride.Shaders; namespace Stride.Graphics { + /// + /// Provides functionality for compiling and binding Shader resource bindings to Descriptor Sets. + /// + /// + /// The structure is used to process Descriptor Set layouts and Effect bytecode + /// to generate binding operations that map Shader resources to Descriptor Set entries. + /// These binding operations can then be used to bind Graphics Resources to the graphics pipeline during rendering. + /// internal struct ResourceBinder { + // Binding operations organized by Descriptor Sets: + // Each element corresponds to a Descriptor Set, and each Descriptor Set contains an array of binding operations. private BindingOperation[][] descriptorSetBindings; - public void Compile(GraphicsDevice graphicsDevice, EffectDescriptorSetReflection descriptorSetLayouts, EffectBytecode effectBytecode) + + /// + /// Processes the Descriptor Set layouts and an object to generate + /// binding operations that map Shader Resource bindings to Descriptor Set entries. + ///
+ /// The resulting bindings are stored internally and can be used for rendering operations. + ///
+ /// + /// An object containing reflection data for the Descriptor Set layouts, + /// which provides information about the layout structure. + /// + /// The bytecode of the Effect, including reflection data for resource bindings. + public void Compile(EffectDescriptorSetReflection descriptorSetLayouts, EffectBytecode effectBytecode) { descriptorSetBindings = new BindingOperation[descriptorSetLayouts.Layouts.Count][]; + for (int setIndex = 0; setIndex < descriptorSetLayouts.Layouts.Count; setIndex++) { var layout = descriptorSetLayouts.Layouts[setIndex].Layout; - if (layout == null) + if (layout is null) continue; var bindingOperations = new List(); @@ -37,67 +62,89 @@ public void Compile(GraphicsDevice graphicsDevice, EffectDescriptorSetReflection if (resourceBinding.KeyInfo.Key == layoutEntry.Key) { + // Create a binding operation for this resource (Descriptor) bindingOperations.Add(new BindingOperation { EntryIndex = resourceIndex, Class = resourceBinding.Class, Stage = resourceBinding.Stage, SlotStart = resourceBinding.SlotStart, - ImmutableSampler = layoutEntry.ImmutableSampler, + ImmutableSampler = layoutEntry.ImmutableSampler }); } } } + // Store the binding operations for this Descriptor Set descriptorSetBindings[setIndex] = bindingOperations.Count > 0 ? bindingOperations.ToArray() : null; } } - public void BindResources(CommandList commandList, DescriptorSet[] descriptorSets) + + /// + /// Binds the resources from the specified Descriptor Sets to the graphics pipeline. + /// + /// The Command List. + /// + /// An array of Descriptor Sets containing the Graphics Resources to bind. + /// Each Descriptor Set corresponds to a set of binding operations. They must have been compiled + /// previously using . + /// + /// + /// Thrown if a binding operation specifies an unsupported effect parameter type. + /// + /// + /// This method iterates through the Descriptor Sets and performs the binding operations needed + /// to bind the Descriptors (like Constant Buffers, Samplers, Shader Resource Views, and Unordered Access Views) + /// to the graphics pipeline. + /// + public readonly void BindResources(CommandList commandList, DescriptorSet[] descriptorSets) { for (int setIndex = 0; setIndex < descriptorSetBindings.Length; setIndex++) { var bindingOperations = descriptorSetBindings[setIndex]; - if (bindingOperations == null) + if (bindingOperations is null) continue; var descriptorSet = descriptorSets[setIndex]; ref var bindingOperation = ref MemoryMarshal.GetArrayDataReference(bindingOperations); ref var end = ref Unsafe.Add(ref bindingOperation, bindingOperations.Length); + for (; Unsafe.IsAddressLessThan(ref bindingOperation, ref end); bindingOperation = ref Unsafe.Add(ref bindingOperation, 1)) { var value = descriptorSet.HeapObjects[descriptorSet.DescriptorStartOffset + bindingOperation.EntryIndex]; + switch (bindingOperation.Class) { case EffectParameterClass.ConstantBuffer: - { - commandList.SetConstantBuffer(bindingOperation.Stage, bindingOperation.SlotStart, (Buffer)value.Value); - break; - } + commandList.SetConstantBuffer(bindingOperation.Stage, bindingOperation.SlotStart, (Buffer) value.Value); + break; + case EffectParameterClass.Sampler: - { - commandList.SetSamplerState(bindingOperation.Stage, bindingOperation.SlotStart, bindingOperation.ImmutableSampler ?? (SamplerState)value.Value); - break; - } + commandList.SetSamplerState(bindingOperation.Stage, bindingOperation.SlotStart, bindingOperation.ImmutableSampler ?? (SamplerState) value.Value); + break; + case EffectParameterClass.ShaderResourceView: - { - commandList.UnsetUnorderedAccessView(value.Value as GraphicsResource); - commandList.SetShaderResourceView(bindingOperation.Stage, bindingOperation.SlotStart, (GraphicsResource)value.Value); - break; - } + commandList.UnsetUnorderedAccessView(value.Value as GraphicsResource); + commandList.SetShaderResourceView(bindingOperation.Stage, bindingOperation.SlotStart, (GraphicsResource) value.Value); + break; + case EffectParameterClass.UnorderedAccessView: - { - commandList.SetUnorderedAccessView(bindingOperation.Stage, bindingOperation.SlotStart, (GraphicsResource)value.Value, value.Offset); - break; - } + commandList.SetUnorderedAccessView(bindingOperation.Stage, bindingOperation.SlotStart, (GraphicsResource) value.Value, value.Offset); + break; + default: - throw new ArgumentOutOfRangeException(); + throw new NotSupportedException($"Binding operation on an unsupported Effect parameter type [{bindingOperation.Class}]"); } } } } - internal struct BindingOperation + + /// + /// Represents a binding operation used to configure shader parameters and resources. + /// + private struct BindingOperation { public int EntryIndex; public EffectParameterClass Class; @@ -107,4 +154,5 @@ internal struct BindingOperation } } } + #endif diff --git a/sources/engine/Stride.Graphics/ResourceGroupBufferUploader.cs b/sources/engine/Stride.Graphics/ResourceGroupBufferUploader.cs index 748f5fb689..28ff413a21 100644 --- a/sources/engine/Stride.Graphics/ResourceGroupBufferUploader.cs +++ b/sources/engine/Stride.Graphics/ResourceGroupBufferUploader.cs @@ -75,13 +75,13 @@ public unsafe void Apply(CommandList commandList, ResourceGroup[] resourceGroups { if (hasResourceRenaming) { - var mappedConstantBuffer = commandList.MapSubresource(preallocatedBuffer, 0, MapMode.WriteDiscard); + var mappedConstantBuffer = commandList.MapSubResource(preallocatedBuffer, 0, MapMode.WriteDiscard); Unsafe.CopyBlockUnaligned((void*)mappedConstantBuffer.DataBox.DataPointer, (void*)resourceGroup.ConstantBuffer.Data, (uint)resourceGroup.ConstantBuffer.Size); - commandList.UnmapSubresource(mappedConstantBuffer); + commandList.UnmapSubResource(mappedConstantBuffer); } else { - commandList.UpdateSubresource(preallocatedBuffer, 0, new DataBox(resourceGroup.ConstantBuffer.Data, resourceGroup.ConstantBuffer.Size, 0)); + commandList.UpdateSubResource(preallocatedBuffer, 0, new DataBox(resourceGroup.ConstantBuffer.Data, resourceGroup.ConstantBuffer.Size, 0)); } } diff --git a/sources/engine/Stride.Graphics/ResourceRegion.cs b/sources/engine/Stride.Graphics/ResourceRegion.cs index be44908f2a..c642c2aa59 100644 --- a/sources/engine/Stride.Graphics/ResourceRegion.cs +++ b/sources/engine/Stride.Graphics/ResourceRegion.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,55 +23,78 @@ using System.Runtime.InteropServices; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a 3D box with integer coordinates, represented as the coordinates of its minimum (left, top, front) +/// and maximum (right, bottom, back) corners. +/// +/// +/// The values for , , and are each one pixel +/// past the end of the pixels that are included in the box region. +/// +/// That is, the values for , , and are included +/// in the box region while the values for , , and +/// are excluded from the box region. +/// +/// For example, for a box that is one pixel wide, where (Right - Left) == 1, the box region includes +/// the left pixel but not the right pixel. +/// +/// +/// Initializes a new resource region structure from its coordinates. +/// +/// The X position of the left hand side of the box. +/// The Y position of the top of the box. +/// The Z position of the front of the box. +/// The X position of the right hand side of the box. +/// The Y position of the bottom of the box. +/// The Z position of the back of the box. +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public partial struct ResourceRegion(int left, int top, int front, int right, int bottom, int back) { /// - ///

Defines a 3D box.

+ /// The X position of the left hand side of the box. ///
- /// - ///

The following diagram shows a 3D box, where the origin is the left, front, top corner.

The values for right, bottom, and back are each one pixel past the end of the pixels that are included in the box region. That is, the values for left, top, and front are included in the box region while the values for right, bottom, and back are excluded from the box region. For example, for a box that is one pixel wide, (right - left) == 1; the box region includes the left pixel but not the right pixel.

- ///
- [StructLayout(LayoutKind.Sequential, Pack = 0)] - public partial struct ResourceRegion - { - public ResourceRegion(int left, int top, int front, int right, int bottom, int back) - { - Left = left; - Top = top; - Front = front; - Right = right; - Bottom = bottom; - Back = back; - } + public int Left = left; - /// - ///

The x position of the left hand side of the box.

- ///
- public int Left; + /// + /// The Y position of the top of the box. + /// + public int Top = top; - /// - ///

The y position of the top of the box.

- ///
- public int Top; + /// + /// The Z position of the front of the box. + /// + public int Front = front; - /// - ///

The z position of the front of the box.

- ///
- public int Front; + /// + /// The X position of the right hand side of the box. + /// + public int Right = right; - /// - ///

The x position of the right hand side of the box.

- ///
- public int Right; + /// + /// The Y position of the bottom of the box. + /// + public int Bottom = bottom; - /// - ///

The y position of the bottom of the box.

- ///
- public int Bottom; + /// + /// The Z position of the back of the box. + /// + public int Back = back; + + + /// + /// Gets the width of the box (i.e. - ). + /// + public readonly int Width => Right - Left; - /// - ///

The z position of the back of the box.

- ///
- public int Back; - } + /// + /// Gets the height of the box (i.e. - ). + /// + public readonly int Height => Bottom - Top; + + /// + /// Gets the depth of the box (i.e. - ). + /// + public readonly int Depth => Back - Front; } diff --git a/sources/engine/Stride.Graphics/ResourceStates.cs b/sources/engine/Stride.Graphics/ResourceStates.cs deleted file mode 100644 index 73ef5ede91..0000000000 --- a/sources/engine/Stride.Graphics/ResourceStates.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; - -namespace Stride.Graphics -{ - [Flags] - public enum GraphicsResourceState - { - Common = 0, - Present = 0, - VertexAndConstantBuffer = 1, - IndexBuffer = 2, - RenderTarget = 4, - UnorderedAccess = 8, - DepthWrite = 16, - DepthRead = 32, - NonPixelShaderResource = 64, - PixelShaderResource = 128, - StreamOut = 256, - IndirectArgument = 512, - Predication = 512, - CopyDestination = 1024, - CopySource = 2048, - GenericRead = 2755, - ResolveDestination = 4096, - ResolveSource = 8192, - } -} diff --git a/sources/engine/Stride.Graphics/RootSignature.cs b/sources/engine/Stride.Graphics/RootSignature.cs index 7844129cca..de1680044b 100644 --- a/sources/engine/Stride.Graphics/RootSignature.cs +++ b/sources/engine/Stride.Graphics/RootSignature.cs @@ -1,33 +1,53 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Shaders; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Represents a Root Signature, used to specify how Graphics Resources, such as Textures and Buffers, +/// are bound to the graphics pipeline (i.e. how will be bound together). +/// +/// +/// +/// A Root Signature links Command Lists to the Graphics Resources the Shaders require. +/// It is similar to a function signature, it determines the types of data the Shaders should expect, but does not define +/// the actual memory or data. +/// +/// +/// The Graphics Resources are described by Descriptors and bundled in s. +/// An defines the layout of the Graphics Resources, bind slot names, +/// and other metadata. +/// +/// +public class RootSignature : GraphicsResourceBase { /// - /// Describes how will be bound together. + /// Gets a description of the layout, types, etc. of the Graphics Resources to bind. + /// + internal EffectDescriptorSetReflection EffectDescriptorSetReflection { get; } + + + /// + /// Creates a new Root Signature from the given . /// - public class RootSignature : GraphicsResourceBase + /// The . + /// A description of the layout of the Graphics Resources to bind. + /// The new Root Signature. + public static RootSignature New(GraphicsDevice graphicsDevice, EffectDescriptorSetReflection effectDescriptorSetReflection) { - internal readonly EffectDescriptorSetReflection EffectDescriptorSetReflection; + return new RootSignature(graphicsDevice, effectDescriptorSetReflection); + } - public static RootSignature New(GraphicsDevice graphicsDevice, EffectDescriptorSetReflection effectDescriptorSetReflection) - { - return new RootSignature(graphicsDevice, effectDescriptorSetReflection); - } + private RootSignature(GraphicsDevice graphicsDevice, EffectDescriptorSetReflection effectDescriptorSetReflection) + : base(graphicsDevice) + { + EffectDescriptorSetReflection = effectDescriptorSetReflection; + } - private RootSignature(GraphicsDevice graphicsDevice, EffectDescriptorSetReflection effectDescriptorSetReflection) - : base(graphicsDevice) - { - this.EffectDescriptorSetReflection = effectDescriptorSetReflection; - } - protected internal override bool OnRecreate() - { - return true; - } + /// + protected internal override bool OnRecreate() + { + return true; } } diff --git a/sources/engine/Stride.Graphics/SamplerState.cs b/sources/engine/Stride.Graphics/SamplerState.cs index 5c2a9d4735..2feb061153 100644 --- a/sources/engine/Stride.Graphics/SamplerState.cs +++ b/sources/engine/Stride.Graphics/SamplerState.cs @@ -1,65 +1,77 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.ReferenceCounting; -namespace Stride.Graphics -{ - public partial class SamplerState : GraphicsResourceBase - { - /// - /// Gets the sampler state description. - /// - public readonly SamplerStateDescription Description; +namespace Stride.Graphics; - // For FakeSamplerState. - protected SamplerState() - { - } +/// +/// A graphics object that describes a Sampler State, which determines +/// how to sample Texture data. +/// +public partial class SamplerState : GraphicsResourceBase +{ + /// + /// The description of the Sampler State. + /// + public readonly SamplerStateDescription Description; - // For FakeSamplerState. - private SamplerState(SamplerStateDescription description) - { - Description = description; - } - public static SamplerState New(GraphicsDevice graphicsDevice, SamplerStateDescription samplerStateDescription) + /// + /// Creates a new . + /// + /// The . + /// + /// A structure describing the Sampler State + /// object to create. + /// + /// An optional name that can be used to identify the Sampler State. + /// A new Sampler State object. + public static SamplerState New(GraphicsDevice device, ref readonly SamplerStateDescription description, string? name = null) + { + // Store SamplerState in a cache (D3D seems to have quite bad concurrency when using CreateSampler while rendering) + SamplerState samplerState; + lock (device.CachedSamplerStates) { - // Store SamplerState in a cache (D3D seems to have quite bad concurrency when using CreateSampler while rendering) - SamplerState samplerState; - lock (graphicsDevice.CachedSamplerStates) + if (device.CachedSamplerStates.TryGetValue(description, out samplerState)) { - if (graphicsDevice.CachedSamplerStates.TryGetValue(samplerStateDescription, out samplerState)) - { - // TODO: Appropriate destroy - samplerState.AddReferenceInternal(); - } - else - { - samplerState = new SamplerState(graphicsDevice, samplerStateDescription); - graphicsDevice.CachedSamplerStates.Add(samplerStateDescription, samplerState); - } + // TODO: Appropriate destroy + samplerState.AddReferenceInternal(); } - return samplerState; - } - - /// - /// Create a new fake sampler state for serialization. - /// - /// The description of the sampler state - /// The fake sampler state - public static SamplerState NewFake(SamplerStateDescription description) - { - return new SamplerState(description); - } - - protected override void Destroy() - { - lock (GraphicsDevice.CachedSamplerStates) + else { - GraphicsDevice.CachedSamplerStates.Remove(Description); + samplerState = new SamplerState(device, in description, name); + device.CachedSamplerStates.Add(description, samplerState); } + } + return samplerState; + } + + /// + /// Creates a new fake for serialization. + /// + /// + /// A structure describing the Sampler State + /// object to create. + /// + /// A new fake Sampler State object. + public static SamplerState NewFake(SamplerStateDescription description) + { + return new SamplerState(description); + } + private SamplerState(SamplerStateDescription description) + { + Description = description; + } - base.Destroy(); + /// + protected override void Destroy() + { + lock (GraphicsDevice.CachedSamplerStates) + { + GraphicsDevice.CachedSamplerStates.Remove(Description); } + + base.Destroy(); } } diff --git a/sources/engine/Stride.Graphics/SamplerStateFactory.cs b/sources/engine/Stride.Graphics/SamplerStateFactory.cs index 443165a9f8..b629626a0f 100644 --- a/sources/engine/Stride.Graphics/SamplerStateFactory.cs +++ b/sources/engine/Stride.Graphics/SamplerStateFactory.cs @@ -1,67 +1,69 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A factory for creating instances. +/// Contains pre-created Sampler States for commonly used configurations. +/// +/// +/// To access these default Sampler States, you can access them through . +/// +public class SamplerStateFactory : GraphicsResourceFactoryBase { /// - /// Base factory for . + /// Initializes a new instance of the class. /// - public class SamplerStateFactory : GraphicsResourceFactoryBase + /// The Graphics Device. + internal SamplerStateFactory(GraphicsDevice device) : base(device) { - /// - /// Initializes a new instance of the class. - /// - /// The device. - internal SamplerStateFactory(GraphicsDevice device) : base(device) - { - PointWrap = SamplerState.New(device, new SamplerStateDescription(TextureFilter.Point, TextureAddressMode.Wrap)).DisposeBy(this); - PointWrap.Name = "SamplerState.PointWrap"; - - PointClamp = SamplerState.New(device, new SamplerStateDescription(TextureFilter.Point, TextureAddressMode.Clamp)).DisposeBy(this); - PointClamp.Name = "SamplerState.PointClamp"; - - LinearWrap = SamplerState.New(device, new SamplerStateDescription(TextureFilter.Linear, TextureAddressMode.Wrap)).DisposeBy(this); - LinearWrap.Name = "SamplerState.LinearWrap"; - - LinearClamp = SamplerState.New(device, new SamplerStateDescription(TextureFilter.Linear, TextureAddressMode.Clamp)).DisposeBy(this); - LinearClamp.Name = "SamplerState.LinearClamp"; + PointWrap = CreateSamplerState("SamplerState.PointWrap", TextureFilter.Point, TextureAddressMode.Wrap); + PointClamp = CreateSamplerState("SamplerState.PointClamp", TextureFilter.Point, TextureAddressMode.Clamp); + LinearWrap = CreateSamplerState("SamplerState.LinearWrap", TextureFilter.Linear, TextureAddressMode.Wrap); + LinearClamp = CreateSamplerState("SamplerState.LinearClamp", TextureFilter.Linear, TextureAddressMode.Clamp); + AnisotropicWrap = CreateSamplerState("SamplerState.AnisotropicWrap", TextureFilter.Anisotropic, TextureAddressMode.Wrap); + AnisotropicClamp = CreateSamplerState("SamplerState.AnisotropicClamp", TextureFilter.Anisotropic, TextureAddressMode.Clamp); - AnisotropicWrap = SamplerState.New(device, new SamplerStateDescription(TextureFilter.Anisotropic, TextureAddressMode.Wrap)).DisposeBy(this); - AnisotropicWrap.Name = "SamplerState.AnisotropicWrap"; - AnisotropicClamp = SamplerState.New(device, new SamplerStateDescription(TextureFilter.Anisotropic, TextureAddressMode.Clamp)).DisposeBy(this); - AnisotropicClamp.Name = "SamplerState.AnisotropicClamp"; + SamplerState CreateSamplerState(string name, TextureFilter filter, TextureAddressMode addressMode) + { + var description = new SamplerStateDescription(filter, addressMode); + var samplerState = SamplerState.New(device, in description, name).DisposeBy(this); + return samplerState; } + } + - /// - /// Default state for point filtering with texture coordinate wrapping. - /// - public readonly SamplerState PointWrap; + /// + /// Default Sampler State for point filtering with texture coordinate wrapping. + /// + public readonly SamplerState PointWrap; - /// - /// Default state for point filtering with texture coordinate clamping. - /// - public readonly SamplerState PointClamp; + /// + /// Default Sampler State for point filtering with texture coordinate clamping. + /// + public readonly SamplerState PointClamp; - /// - /// Default state for linear filtering with texture coordinate wrapping. - /// - public readonly SamplerState LinearWrap; + /// + /// Default Sampler State for linear filtering with texture coordinate wrapping. + /// + public readonly SamplerState LinearWrap; - /// - /// Default state for linear filtering with texture coordinate clamping. - /// - public readonly SamplerState LinearClamp; + /// + /// Default Sampler State for linear filtering with texture coordinate clamping. + /// + public readonly SamplerState LinearClamp; - /// - /// Default state for anisotropic filtering with texture coordinate wrapping. - /// - public readonly SamplerState AnisotropicWrap; + /// + /// Default Sampler State for anisotropic filtering with texture coordinate wrapping. + /// + public readonly SamplerState AnisotropicWrap; - /// - /// Default state for anisotropic filtering with texture coordinate clamping. - /// - public readonly SamplerState AnisotropicClamp; - } + /// + /// Default Sampler State for anisotropic filtering with texture coordinate clamping. + /// + public readonly SamplerState AnisotropicClamp; } diff --git a/sources/engine/Stride.Graphics/Shaders/TexturingKeys.cs b/sources/engine/Stride.Graphics/Shaders/TexturingKeys.cs index 17f4ae962d..68fc2fe411 100644 --- a/sources/engine/Stride.Graphics/Shaders/TexturingKeys.cs +++ b/sources/engine/Stride.Graphics/Shaders/TexturingKeys.cs @@ -1,80 +1,86 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Stride.Core.Mathematics; using Stride.Graphics; -namespace Stride.Rendering +namespace Stride.Rendering; + +/// +/// Common keys used for texturing in Stride rendering. +/// +public partial class TexturingKeys { - public partial class TexturingKeys + static TexturingKeys() { - static TexturingKeys() - { - DefaultTextures = new ReadOnlyCollection>(new List>() - { - Texture0, - Texture1, - Texture2, - Texture3, - Texture4, - Texture5, - Texture6, - Texture7, - Texture8, - Texture9, - }); - TextureCubes = new ReadOnlyCollection>(new List>() - { - TextureCube0, - TextureCube1, - TextureCube2, - TextureCube3, - }); - Textures3D = new ReadOnlyCollection>(new List>() - { - Texture3D0, - Texture3D1, - Texture3D2, - Texture3D3, - }); + DefaultTextures = new ReadOnlyCollection>( + [ + Texture0, + Texture1, + Texture2, + Texture3, + Texture4, + Texture5, + Texture6, + Texture7, + Texture8, + Texture9, + ]); + TextureCubes = new ReadOnlyCollection>( + [ + TextureCube0, + TextureCube1, + TextureCube2, + TextureCube3, + ]); + Textures3D = new ReadOnlyCollection>( + [ + Texture3D0, + Texture3D1, + Texture3D2, + Texture3D3, + ]); + + TexturesTexelSize = new ReadOnlyCollection>( + [ + Texture0TexelSize, + Texture1TexelSize, + Texture2TexelSize, + Texture3TexelSize, + Texture4TexelSize, + Texture5TexelSize, + Texture6TexelSize, + Texture7TexelSize, + Texture8TexelSize, + Texture9TexelSize, + ]); + } - TexturesTexelSize = new ReadOnlyCollection>(new List>() - { - Texture0TexelSize, - Texture1TexelSize, - Texture2TexelSize, - Texture3TexelSize, - Texture4TexelSize, - Texture5TexelSize, - Texture6TexelSize, - Texture7TexelSize, - Texture8TexelSize, - Texture9TexelSize, - }); - } - /// - /// Default textures used by this class (, ...etc.) - /// - public static readonly IReadOnlyList> DefaultTextures; + /// + /// Parameter keys for the default Textures (, ...etc.) + /// + public static readonly IReadOnlyList> DefaultTextures; - /// - /// The cube textures used by this class (, ...etc.) - /// - public static readonly IReadOnlyList> TextureCubes; + /// + /// Parameter keys for the Cube Textures (, ...etc.) + /// + public static readonly IReadOnlyList> TextureCubes; - /// - /// The 3d textures used by this class (, ...etc.) - /// - public static readonly IReadOnlyList> Textures3D; + /// + /// Parameter keys for the 3D Textures (, ...etc.) + /// + public static readonly IReadOnlyList> Textures3D; - /// - /// Default textures size used by this class (, ...etc.) - /// - public static readonly IReadOnlyList> TexturesTexelSize; - } + /// + /// Parameter keys for the texel size for the default Textures (, ...etc.) + /// + /// + /// The texel size is a vector that contains the width and height of a pixel of a Texture in UV space, + /// i.e. (1.0 / sizeOfTheTexture). + /// + public static readonly IReadOnlyList> TexturesTexelSize; } diff --git a/sources/engine/Stride.Graphics/StencilOperation.cs b/sources/engine/Stride.Graphics/StencilOperation.cs index 4245e0ffe4..17ad433586 100644 --- a/sources/engine/Stride.Graphics/StencilOperation.cs +++ b/sources/engine/Stride.Graphics/StencilOperation.cs @@ -1,46 +1,56 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Specifies the operation to perform on the stencil part of the Depth-Stencil Buffer when rasterizing primitives. +/// +/// +/// This enumeration is part of a Depth-Stencil State object description (see ). +/// +[DataContract] +public enum StencilOperation { /// - /// TODO Comments - /// - [DataContract] - public enum StencilOperation - { - /// - /// - /// - Keep = 1, - /// - /// - /// - Zero = 2, - /// - /// - /// - Replace = 3, - /// - /// - /// - IncrementSaturation = 4, - /// - /// - /// - DecrementSaturation = 5, - /// - /// - /// - Invert = 6, - /// - /// - /// - Increment = 7, - /// - /// - /// - Decrement = 8, - } + /// Keeps the current value of the stencil buffer, without modifying it. + ///
+ Keep = 1, + + /// + /// Sets the value of the stencil buffer to zero. + /// + Zero = 2, + + /// + /// Sets the value of the stencil buffer to a specified value when the stencil test passes. + /// + Replace = 3, + + /// + /// Increments the value of the stencil buffer by one, saturating at the maximum value allowed (usually 255 for 8-bit buffers). + /// + IncrementSaturation = 4, + + /// + /// Decrements the value of the stencil buffer by one, saturating at zero (usually 0 for 8-bit buffers). + /// + DecrementSaturation = 5, + + /// + /// Inverts all bits in the stencil buffer value, effectively flipping each bit from 0 to 1 and from 1 to 0. + /// + Invert = 6, + + /// + /// Increments the value of the stencil buffer by one, wrapping around to zero if it exceeds the maximum value. + /// + Increment = 7, + + /// + /// Decrements the value of the stencil buffer by one, wrapping around to the maximum value if it goes below zero. + /// + Decrement = 8 } diff --git a/sources/engine/Stride.Graphics/Stride.Graphics.csproj b/sources/engine/Stride.Graphics/Stride.Graphics.csproj index 22129ba830..6ddd9d2506 100644 --- a/sources/engine/Stride.Graphics/Stride.Graphics.csproj +++ b/sources/engine/Stride.Graphics/Stride.Graphics.csproj @@ -44,10 +44,10 @@ - - - + + + @@ -63,4 +63,4 @@ - \ No newline at end of file + diff --git a/sources/engine/Stride.Graphics/Texture.Extensions.cs b/sources/engine/Stride.Graphics/Texture.Extensions.cs index a364e16f78..9e2710c56e 100644 --- a/sources/engine/Stride.Graphics/Texture.Extensions.cs +++ b/sources/engine/Stride.Graphics/Texture.Extensions.cs @@ -1,95 +1,114 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -#pragma warning disable SA1649 // File name should match first type name + using System; using System.IO; -namespace Stride.Graphics +namespace Stride.Graphics; + +public static class TextureExtensions { - public static class TextureExtensions + /// + /// Creates a Shader Resource View for a . + /// + /// The Texture to create a Shader Resource View for. + /// + /// One of the values of indicating which sub-resources from the Texture's mip hierarchy + /// and array slices (if a Texture Array) the Shader Resource View can access. + /// + /// + /// The index of the array slice. It is zero-based, so the first index is 0. + /// If the Texture is not a Texture Array, specify 0. + /// + /// + /// The index of the mip level. It is zero-based, so the first index is 0. + /// If the Texture has no mip-chain, specify 0. + /// + /// A new representing the Texture View bound to . + public static Texture ToTextureView(this Texture texture, ViewType viewType, int arraySlice, int mipLevel) { - public static Texture ToTextureView(this Texture texture, ViewType type, int arraySlice, int mipLevel) - { - var viewDescription = texture.ViewDescription; - viewDescription.Type = type; - viewDescription.ArraySlice = arraySlice; - viewDescription.MipLevel = mipLevel; - return texture.ToTextureView(viewDescription); - } + var viewDescription = texture.ViewDescription; + viewDescription.Type = viewType; + viewDescription.ArraySlice = arraySlice; + viewDescription.MipLevel = mipLevel; + return texture.ToTextureView(viewDescription); + } - /// - /// Gets a view on this depth stencil texture as a readonly depth stencil texture. - /// - /// A new texture object that is bouded to the requested view. - public static Texture ToDepthStencilReadOnlyTexture(this Texture texture) - { - if (!texture.IsDepthStencil) - throw new NotSupportedException("This texture is not a valid depth stencil texture"); + /// + /// Creates a Shader Resource View that is read-only on a Depth-Stencil Texture. + /// + /// The Texture to create a read-only Depth-Stencil Texture View for. + /// A new representing the Texture View bound to . + public static Texture ToDepthStencilReadOnlyTexture(this Texture texture) + { + if (!texture.IsDepthStencil) + throw new NotSupportedException("This Texture is not a valid Depth-Stencil Texture"); - var viewDescription = texture.ViewDescription; - viewDescription.Flags = TextureFlags.DepthStencilReadOnly; - return texture.ToTextureView(viewDescription); - } + var viewDescription = texture.ViewDescription; + viewDescription.Flags = TextureFlags.DepthStencilReadOnly; + return texture.ToTextureView(viewDescription); + } - /// - /// Creates a new texture that can be used as a ShaderResource from an existing depth texture. - /// - /// - public static Texture CreateDepthTextureCompatible(this Texture texture) - { - if (!texture.IsDepthStencil) - throw new NotSupportedException("This texture is not a valid depth stencil texture"); + /// + /// Creates a Shader Resource View on a Depth-Stencil Texture. + /// + /// The Texture to create a Depth-Stencil Texture View for. + /// A new representing the Texture View bound to . + public static Texture CreateDepthTextureCompatible(this Texture texture) + { + if (!texture.IsDepthStencil) + throw new NotSupportedException("This Texture is not a valid Depth-Stencil Texture"); - var description = texture.Description; - description.Format = Texture.ComputeShaderResourceFormatFromDepthFormat(description.Format); // TODO: review this - if (description.Format == PixelFormat.None) - throw new NotSupportedException("This depth stencil format is not supported"); + var description = texture.Description; + description.Format = Texture.ComputeShaderResourceFormatFromDepthFormat(description.Format); // TODO: review this + if (description.Format == PixelFormat.None) + throw new NotSupportedException("This Depth-Stencil format is not supported"); - description.Flags = TextureFlags.ShaderResource; - return Texture.New(texture.GraphicsDevice, description); - } + description.Flags = TextureFlags.ShaderResource; + return Texture.New(texture.GraphicsDevice, description); + } - public static Texture EnsureRenderTarget(this Texture texture) + /// + /// Verifies that a given is a Render Target. + /// + /// + /// + /// + public static Texture EnsureRenderTarget(this Texture texture) + { + if (texture is not null && !texture.IsRenderTarget) { - if (texture != null && !texture.IsRenderTarget) - { - throw new ArgumentException("Texture must be a RenderTarget", "texture"); - } - return texture; + throw new ArgumentException("The Texture must be a Render Target", nameof(texture)); } + return texture; + } - /// - /// Creates a texture from an image file data (png, dds, ...). - /// - /// The graphics device in which to create the texture - /// The image file data - /// The texture - public static Texture FromFileData(GraphicsDevice graphicsDevice, byte[] data) - { - Texture result; + /// + /// Creates a from image file data. + /// + /// The graphics device in which to create the Texture. + /// The image file data. + /// The created Texture. + public static Texture FromFileData(GraphicsDevice graphicsDevice, byte[] data) + { + Texture result; - var loadAsSRgb = graphicsDevice.ColorSpace == ColorSpace.Linear; + var loadAsSRgb = graphicsDevice.ColorSpace == ColorSpace.Linear; - using (var imageStream = new MemoryStream(data)) - { - using (var image = Image.Load(imageStream, loadAsSRgb)) - { - result = Texture.New(graphicsDevice, image); - } - } + using (var imageStream = new MemoryStream(data)) + { + using var image = Image.Load(imageStream, loadAsSRgb); + result = Texture.New(graphicsDevice, image); + } - result.Reload = (graphicsResource, services) => - { - using (var imageStream = new MemoryStream(data)) - { - using (var image = Image.Load(imageStream, loadAsSRgb)) - { - ((Texture)graphicsResource).Recreate(image.ToDataBox()); - } - } - }; + result.Reload = (graphicsResource, services) => + { + using var imageStream = new MemoryStream(data); + using var image = Image.Load(imageStream, loadAsSRgb); - return result; - } + ((Texture)graphicsResource).Recreate(image.ToDataBox()); + }; + + return result; } } diff --git a/sources/engine/Stride.Graphics/Texture.Extensions1D.cs b/sources/engine/Stride.Graphics/Texture.Extensions1D.cs index b4c2750658..62bfca3693 100644 --- a/sources/engine/Stride.Graphics/Texture.Extensions1D.cs +++ b/sources/engine/Stride.Graphics/Texture.Extensions1D.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,81 +23,133 @@ using System; -using Stride.Core; +namespace Stride.Graphics; -namespace Stride.Graphics +public partial class Texture { - public partial class Texture + /// + /// Creates a new one-dimensional (1D) with a single mipmap. + /// + /// The . + /// The width of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new one-dimensional Texture. + public static Texture New1D(GraphicsDevice device, int width, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - /// - /// Creates a new 1D with a single mipmap. - /// - /// The . - /// The width. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// - /// A new instance of 1D class. - /// - public static Texture New1D(GraphicsDevice device, int width, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New1D(device, width, false, format, textureFlags, arraySize, usage); - } + return New1D(device, width, MipMapCount.One, format, textureFlags, arraySize, usage); + } - /// - /// Creates a new 1D . - /// - /// The . - /// The width. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// - /// A new instance of 1D class. - /// - public static Texture New1D(GraphicsDevice device, int width, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New(device, TextureDescription.New1D(width, mipCount, format, textureFlags, arraySize, usage)); - } + /// + /// Creates a new one-dimensional (1D) . + /// + /// The . + /// The width of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new one-dimensional Texture. + public static Texture New1D(GraphicsDevice device, int width, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + var description = TextureDescription.New1D(width, mipCount, format, textureFlags, arraySize, usage); - /// - /// Creates a new 1D with a single level of mipmap. - /// - /// Type of the initial data to upload to the texture - /// The . - /// The width. - /// Describes the format to use. - /// Texture data. Size of must be equal to sizeof(Format) * width - /// true if the texture needs to support unordered read write. - /// The usage. - /// A new instance of class. - /// - /// The first dimension of mipMapTextures describes the number of array (Texture Array), second dimension is the mipmap, the third is the texture data for a particular mipmap. - /// - public static unsafe Texture New1D(GraphicsDevice device, int width, PixelFormat format, T[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged - { - fixed (T* texture = textureData) - return New(device, TextureDescription.New1D(width, format, textureFlags, usage), new[] { GetDataBox(format, width, 1, 1, textureData, (nint)texture) }); - } + return New(device, description); + } - /// - /// Creates a new 1D with a single level of mipmap. - /// - /// The . - /// The width. - /// Describes the format to use. - /// Data ptr - /// true if the texture needs to support unordered read write. - /// The usage. - /// A new instance of 1D class. - /// The first dimension of mipMapTextures describes the number of array (Texture Array), second dimension is the mipmap, the third is the texture data for a particular mipmap. - public static Texture New1D(GraphicsDevice device, int width, PixelFormat format, IntPtr dataPtr, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + /// + /// Creates a new one-dimensional (1D) with initial data and a single mipmap. + /// + /// Type of the initial data to upload to the Texture. + /// The . + /// The width of the Texture in texels. + /// The format to use. + /// + /// + /// The initial data array to upload to the Texture. + /// It must have a size (in bytes, not elements) equal to the size of the times the . + /// + /// + /// See for calculating the size of a pixel format. + /// + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// A new one-dimensional Texture. + public static unsafe Texture New1D(GraphicsDevice device, int width, PixelFormat format, T[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + { + fixed (T* texture = textureData) { - return New(device, TextureDescription.New1D(width, format, textureFlags, usage), new[] { new DataBox(dataPtr, 0, 0), }); + var dataBox = GetDataBox(format, width, height: 1, depth: 1, textureData, (nint) texture); + var description = TextureDescription.New1D(width, format, textureFlags, usage); + + return New(device, description, [dataBox]); } } + + /// + /// Creates a new one-dimensional (1D) with initial data and a single mipmap. + /// + /// The . + /// The width of the Texture in texels. + /// The format to use. + /// A pointer to the data to upload to the Texture. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// A new one-dimensional Texture. + public static Texture New1D(GraphicsDevice device, int width, PixelFormat format, IntPtr dataPtr, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + { + var dataBox = new DataBox(dataPtr, rowPitch: 0, slicePitch: 0); + var description = TextureDescription.New1D(width, format, textureFlags, usage); + + return New(device, description, [dataBox]); + } } diff --git a/sources/engine/Stride.Graphics/Texture.Extensions2D.cs b/sources/engine/Stride.Graphics/Texture.Extensions2D.cs index a1cb175ac3..7604a3a723 100644 --- a/sources/engine/Stride.Graphics/Texture.Extensions2D.cs +++ b/sources/engine/Stride.Graphics/Texture.Extensions2D.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,99 +21,189 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; +namespace Stride.Graphics; -using Stride.Core; - -namespace Stride.Graphics +public partial class Texture { - public partial class Texture + /// + /// Creates a new two-dimensional (2D) with a single mipmap. + /// + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// + /// A combination of flags indicating options about the creation of the Texture, like creating it as a shared resource. + /// The default value is . + /// + /// A new two-dimensional Texture. + public static Texture New2D(GraphicsDevice device, int width, int height, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, TextureOptions options = TextureOptions.None) { - /// - /// Creates a new 2D with a single mipmap. - /// - /// The . - /// The width. - /// The height. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// A new instance of 2D class. - public static Texture New2D(GraphicsDevice device, int width, int height, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, TextureOptions options = TextureOptions.None) - { - return New2D(device, width, height, false, format, textureFlags, arraySize, usage, options); - } + return New2D(device, width, height, MipMapCount.One, format, textureFlags, arraySize, usage, options); + } - /// - /// Creates a new 2D . - /// - /// The . - /// The width. - /// The height. - /// Describes the format to use. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// A new instance of 2D class. - public static Texture New2D(GraphicsDevice device, int width, int height, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, TextureOptions options = TextureOptions.None) - { - return new Texture(device).InitializeFrom(TextureDescription.New2D(width, height, mipCount, format, textureFlags, arraySize, usage, MultisampleCount.None, options)); - } + /// + /// Creates a new two-dimensional (2D) . + /// + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The format to use. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// + /// A combination of flags indicating options about the creation of the Texture, like creating it as a shared resource. + /// The default value is . + /// + /// A new two-dimensional Texture. + public static Texture New2D(GraphicsDevice device, int width, int height, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, TextureOptions options = TextureOptions.None) + { + var description = TextureDescription.New2D(width, height, mipCount, format, textureFlags, arraySize, usage, MultisampleCount.None, options); - /// - /// Creates a new 2D with a single level of mipmap. - /// - /// Type of the pixel data to upload to the texture. - /// The . - /// The width. - /// The height. - /// Describes the format to use. - /// The texture data for a single mipmap and a single array slice. See remarks - /// true if the texture needs to support unordered read write. - /// The usage. - /// A new instance of 2D class. - /// - /// Each value in textureData is a pixel in the destination texture. - /// - public static unsafe Texture New2D(GraphicsDevice device, int width, int height, PixelFormat format, T[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable, TextureOptions options = TextureOptions.None) where T : unmanaged - { - fixed (T* texture = textureData) - return New2D(device, width, height, 1, format, new[] { GetDataBox(format, width, height, 1, textureData, (nint)texture) }, textureFlags, 1, usage, MultisampleCount.None, options); - } + return new Texture(device).InitializeFrom(description); + } - /// - /// Creates a new 2D . - /// - /// The . - /// The width. - /// The height. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// Texture datas through an array of - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// The multisample count. - /// The options, e.g. sharing - /// - /// A new instance of 2D class. - /// - public static Texture New2D( - GraphicsDevice device, - int width, - int height, - MipMapCount mipCount, - PixelFormat format, - DataBox[] textureData, - TextureFlags textureFlags = TextureFlags.ShaderResource, - int arraySize = 1, - GraphicsResourceUsage usage = GraphicsResourceUsage.Default, - MultisampleCount multisampleCount = MultisampleCount.None, - TextureOptions options = TextureOptions.None) + /// + /// Creates a new two-dimensional (2D) with initial data and a single mipmap. + /// + /// Type of the initial data to upload to the Texture. + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The format to use. + /// + /// + /// The initial data array to upload to the Texture for a single mipmap and a single array slice. + /// It must have a size (in bytes, not elements) equal to the size of the times + /// ( * ). + /// + /// + /// See for calculating the size of a pixel format. + /// + /// + /// Each value in the data array will be a texel in the destination Texture. + /// + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// + /// A combination of flags indicating options about the creation of the Texture, like creating it as a shared resource. + /// The default value is . + /// + /// A new two-dimensional Texture. + public static unsafe Texture New2D(GraphicsDevice device, int width, int height, PixelFormat format, T[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable, TextureOptions options = TextureOptions.None) where T : unmanaged + { + fixed (T* texture = textureData) { - return new Texture(device).InitializeFrom(TextureDescription.New2D(width, height, mipCount, format, textureFlags, arraySize, usage, multisampleCount, options), textureData); + var dataBox = GetDataBox(format, width, height, depth: 1, textureData, (nint) texture); + var description = TextureDescription.New1D(width, format, textureFlags, usage); + + return New2D(device, width, height, MipMapCount.One, format, [dataBox], textureFlags, arraySize: 1, usage, MultisampleCount.None, options); } } + + /// + /// Creates a new two-dimensional (2D) with initial data. + /// + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// An array of pointing to the initial Texture data for each of the mipmaps. + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// + /// The number of samples per texel the Texture will have. + /// The default value is , indicating a non-multisampled Texture. + /// + /// + /// A combination of flags indicating options about the creation of the Texture, like creating it as a shared resource. + /// The default value is . + /// + /// A new two-dimensional Texture. + public static Texture New2D( + GraphicsDevice device, + int width, + int height, + MipMapCount mipCount, + PixelFormat format, + DataBox[] textureData, + TextureFlags textureFlags = TextureFlags.ShaderResource, + int arraySize = 1, + GraphicsResourceUsage usage = GraphicsResourceUsage.Default, + MultisampleCount multisampleCount = MultisampleCount.None, + TextureOptions options = TextureOptions.None) + { + var description = TextureDescription.New2D(width, height, mipCount, format, textureFlags, arraySize, usage, multisampleCount, options); + + return new Texture(device).InitializeFrom(description, textureData); + } } diff --git a/sources/engine/Stride.Graphics/Texture.Extensions3D.cs b/sources/engine/Stride.Graphics/Texture.Extensions3D.cs index ba37e311f8..560a5f9816 100644 --- a/sources/engine/Stride.Graphics/Texture.Extensions3D.cs +++ b/sources/engine/Stride.Graphics/Texture.Extensions3D.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,90 +21,151 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using System; +namespace Stride.Graphics; -using Stride.Core; - -namespace Stride.Graphics +public partial class Texture { - public partial class Texture + /// + /// Creates a new three-dimensional (3D) (also known as a volume texture) with a single mipmap. + /// + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The depth of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new three-dimensional Texture. + public static Texture New3D(GraphicsDevice device, int width, int height, int depth, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - /// - /// Creates a new 3D with a single mipmap. - /// - /// The . - /// The width. - /// The height. - /// The depth. - /// Describes the format to use. - /// The usage. - /// true if the texture needs to support unordered read write. - /// - /// A new instance of 3D class. - /// - public static Texture New3D(GraphicsDevice device, int width, int height, int depth, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New3D(device, width, height, depth, false, format, textureFlags, usage); - } + return New3D(device, width, height, depth, MipMapCount.One, format, textureFlags, usage); + } - /// - /// Creates a new 3D . - /// - /// The . - /// The width. - /// The height. - /// The depth. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// The usage. - /// true if the texture needs to support unordered read write. - /// - /// A new instance of 3D class. - /// - public static Texture New3D(GraphicsDevice device, int width, int height, int depth, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return new Texture(device).InitializeFrom(TextureDescription.New3D(width, height, depth, mipCount, format, textureFlags, usage)); - } + /// + /// Creates a new three-dimensional (3D) (also known as a volume texture). + /// + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The depth of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new three-dimensional Texture. + public static Texture New3D(GraphicsDevice device, int width, int height, int depth, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + var description = TextureDescription.New3D(width, height, depth, mipCount, format, textureFlags, usage); - /// - /// Creates a new 3D with texture data for the firs map. - /// - /// Type of the data to upload to the texture - /// The . - /// The width. - /// The height. - /// The depth. - /// Describes the format to use. - /// The usage. - /// The texture data, width * height * depth datas - /// true if the texture needs to support unordered read write. - /// A new instance of 3D class. - /// - /// The first dimension of mipMapTextures describes the number of is an array ot Texture3D Array - /// - public static unsafe Texture New3D(GraphicsDevice device, int width, int height, int depth, PixelFormat format, T[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged - { - fixed (T* texture = textureData) - return New3D(device, width, height, depth, 1, format, new[] { GetDataBox(format, width, height, depth, textureData, (nint)texture) }, textureFlags, usage); - } + return new Texture(device).InitializeFrom(description); + } - /// - /// Creates a new 3D . - /// - /// The . - /// The width. - /// The height. - /// The depth. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// The usage. - /// true if the texture needs to support unordered read write. - /// - /// A new instance of 3D class. - /// - public static Texture New3D(GraphicsDevice device, int width, int height, int depth, MipMapCount mipCount, PixelFormat format, DataBox[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + /// + /// Creates a new three-dimensional (3D) (also known as a volume texture) with initial data + /// and a single mipmap. + /// + /// Type of the initial data to upload to the Texture. + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The depth of the Texture in texels. + /// The format to use. + /// + /// + /// The initial data array to upload to the Texture for a single mipmap and a single array slice. + /// It must have a size (in bytes, not elements) equal to the size of the times + /// ( * * ). + /// + /// + /// See for calculating the size of a pixel format. + /// + /// + /// Each value in the data array will be a texel in the destination Texture. + /// + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// A new three-dimensional Texture. + public static unsafe Texture New3D(GraphicsDevice device, int width, int height, int depth, PixelFormat format, T[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + { + fixed (T* texture = textureData) { - return new Texture(device).InitializeFrom(TextureDescription.New3D(width, height, depth, mipCount, format, textureFlags, usage), textureData); + var dataBox = GetDataBox(format, width, height, depth, textureData, (nint) texture); + + return New3D(device, width, height, depth, MipMapCount.One, format, [dataBox], textureFlags, usage); } } + + /// + /// Creates a new three-dimensional (3D) (also known as a volume texture) with initial data. + /// + /// The . + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The depth of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// An array of pointing to the initial data to upload for each of the mipmaps. + /// The first element will be the largest mipmap, and each successive element is for the smaller ones. + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new three-dimensional Texture. + public static Texture New3D(GraphicsDevice device, int width, int height, int depth, MipMapCount mipCount, PixelFormat format, DataBox[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + var description = TextureDescription.New3D(width, height, depth, mipCount, format, textureFlags, usage); + + return new Texture(device).InitializeFrom(description, textureData); + } } diff --git a/sources/engine/Stride.Graphics/Texture.ExtensionsCube.cs b/sources/engine/Stride.Graphics/Texture.ExtensionsCube.cs index 0b02448f5f..585bbfa164 100644 --- a/sources/engine/Stride.Graphics/Texture.ExtensionsCube.cs +++ b/sources/engine/Stride.Graphics/Texture.ExtensionsCube.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,88 +23,165 @@ using System; -using Stride.Core; +namespace Stride.Graphics; -namespace Stride.Graphics +public partial class Texture { - public partial class Texture + /// + /// Creates a new cube-map composed of six two-dimensional (2D) s. + /// + /// The . + /// + /// The size in texels of the faces of the cube Texture. + /// As the Texture is a cube, this single value specifies both the width and the height. + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new cube-map Texture. + public static Texture NewCube(GraphicsDevice device, int size, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - /// - /// Creates a new Cube . - /// - /// The . - /// The size (in pixels) of the top-level faces of the cube texture. - /// Describes the format to use. - /// The texture flags. - /// The usage. - /// A new instance of 2D class. - public static Texture NewCube(GraphicsDevice device, int size, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return NewCube(device, size, false, format, textureFlags, usage); - } + return NewCube(device, size, MipMapCount.One, format, textureFlags, usage); + } - /// - /// Creates a new Cube . - /// - /// The . - /// The size (in pixels) of the top-level faces of the cube texture. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// The texture flags. - /// The usage. - /// A new instance of 2D class. - public static Texture NewCube(GraphicsDevice device, int size, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return new Texture(device).InitializeFrom(TextureDescription.NewCube(size, mipCount, format, textureFlags, usage)); - } + /// + /// Creates a new cube-map composed of six two-dimensional (2D) s. + /// + /// The . + /// + /// The size in texels of the faces of the cube Texture. + /// As the Texture is a cube, this single value specifies both the width and the height. + /// + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new cube-map Texture. + public static Texture NewCube(GraphicsDevice device, int size, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + var description = TextureDescription.NewCube(size, mipCount, format, textureFlags, usage); - /// - /// Creates a new Cube from a initial data.. - /// - /// Type of a pixel data - /// The . - /// The size (in pixels) of the top-level faces of the cube texture. - /// Describes the format to use. - /// an array of 6 textures. See remarks - /// The texture flags. - /// The usage. - /// A new instance of Cube class. - /// Invalid texture datas. First dimension must be equal to 6;textureData - /// The first dimension of mipMapTextures describes the number of array (TextureCube Array), the second is the texture data for a particular cube face. - public static unsafe Texture NewCube(GraphicsDevice device, int size, PixelFormat format, T[][] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged - { - if (textureData.Length != 6) - throw new ArgumentException("Invalid texture datas. First dimension must be equal to 6", "textureData"); + return new Texture(device).InitializeFrom(description); + } - var dataBoxes = new DataBox[6]; + /// + /// Creates a new cube-map composed of six two-dimensional (2D) s from a initial data. + /// + /// Type of the initial data to upload to the Texture. + /// The . + /// + /// The size in texels of the faces of the cube Texture. + /// As the Texture is a cube, this single value specifies both the width and the height. + /// + /// The format to use. + /// + /// + /// The initial data array to upload to the Texture for a single mipmap. It is an array of arrays (two dimensions): + /// + /// The first dimension of the array is the index of the cube face. There must be exactly six. + /// + /// The second dimension is the data for each of the cube faces. It must have a size (in bytes, not elements) equal to the size of the times + /// ( * ). + /// + /// + /// + /// + /// See for calculating the size of a pixel format. + /// + /// + /// Each value in the data array of each of the cube faces will be a texel in the destination Texture. + /// + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// A new cube-map Texture. + /// + /// The Texture data is invalid. The first dimension of array must be equal to 6. + /// + public static unsafe Texture NewCube(GraphicsDevice device, int size, PixelFormat format, T[][] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) where T : unmanaged + { + if (textureData.Length != 6) + throw new ArgumentException("Invalid texture datas. First dimension must be equal to 6", nameof(textureData)); - for (var i = 0; i < 6; i++) - { - fixed (void* texture = textureData[i]) - dataBoxes[i] = GetDataBox(format, size, size, 1, textureData[0], (nint)texture); - } + var dataBoxes = new DataBox[6]; - return new Texture(device).InitializeFrom(TextureDescription.NewCube(size, format, textureFlags, usage), dataBoxes); + for (var i = 0; i < 6; i++) + { + fixed (void* texture = textureData[i]) + dataBoxes[i] = GetDataBox(format, size, size, 1, textureData[0], (nint)texture); } - /// - /// Creates a new Cube from a initial data.. - /// - /// The . - /// The size (in pixels) of the top-level faces of the cube texture. - /// Describes the format to use. - /// an array of 6 textures. See remarks - /// The texture flags. - /// The usage. - /// A new instance of Cube class. - /// Invalid texture datas. First dimension must be equal to 6;textureData - /// The first dimension of mipMapTextures describes the number of array (TextureCube Array), the second is the texture data for a particular cube face. - public static Texture NewCube(GraphicsDevice device, int size, PixelFormat format, DataBox[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) - { - if (textureData.Length != 6) - throw new ArgumentException("Invalid texture datas. First dimension must be equal to 6", "textureData"); + var description = TextureDescription.NewCube(size, format, textureFlags, usage); - return new Texture(device).InitializeFrom(TextureDescription.NewCube(size, format, textureFlags, usage), textureData); - } + return new Texture(device).InitializeFrom(description, dataBoxes); + } + + /// + /// Creates a new cube-map composed of six two-dimensional (2D) s from a initial data. + /// + /// The . + /// + /// The size in texels of the faces of the cube Texture. + /// As the Texture is a cube, this single value specifies both the width and the height. + /// + /// The format to use. + /// + /// An array of pointing to the initial Texture data for each of cube faces. + /// There must be exactly six. + /// + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// A new cube-map Texture. + /// + /// The Texture data is invalid. There must be exactly six elements in the array. + /// + /// The first dimension of mipMapTextures describes the number of array (TextureCube Array), the second is the texture data for a particular cube face. + public static Texture NewCube(GraphicsDevice device, int size, PixelFormat format, DataBox[] textureData, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + { + if (textureData.Length != 6) + throw new ArgumentException("Invalid texture datas. First dimension must be equal to 6", nameof(textureData)); + + var description = TextureDescription.NewCube(size, format, textureFlags, usage); + + return new Texture(device).InitializeFrom(description, textureData); } } diff --git a/sources/engine/Stride.Graphics/Texture.cs b/sources/engine/Stride.Graphics/Texture.cs index 3cab88d66a..fb763881fa 100644 --- a/sources/engine/Stride.Graphics/Texture.cs +++ b/sources/engine/Stride.Graphics/Texture.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -26,20 +26,29 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using Stride.Core; + using Stride.Core.Annotations; using Stride.Core.Mathematics; using Stride.Core.ReferenceCounting; using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; using Stride.Graphics.Data; -using Utilities = Stride.Core.Utilities; namespace Stride.Graphics { /// - /// Class used for all Textures (1D, 2D, 3D, DepthStencil, RenderTargets...etc.) + /// All-in-one GPU Texture that is able to represent many types of textures (1D, 2D, 3D, Depth-Stencil Buffers, Render Targets, etc), + /// as well as Texture Views over a parent Texture. /// + /// + /// constains static methods for creating new Textures by specifying all their characteristics. + /// + /// Also look for the following static methods that aid in the creation of specific kinds of buffers: + /// (for one-dimensional Textures), (for two-dimensional Textures), + /// (for three-dimensional Textures), and (for six-sided two-dimensional Cube-maps). + /// + /// Consult the documentation of your graphics API for more information on each kind of Texture. + /// [ReferenceSerializer, DataSerializerGlobal(typeof(ReferenceSerializer), Profile = "Content")] [ContentSerializer(typeof(TextureContentSerializer))] [ContentSerializer(typeof(TextureImageSerializer))] @@ -54,348 +63,227 @@ public sealed partial class Texture : GraphicsResource private Size3? fullQualitySize; /// - /// Common description for the original texture. See remarks. + /// Gets the common description for the original Texture. /// - /// - /// This field and the properties in TextureDessciption must be considered as readonly when accessing from this instance. - /// - public TextureDescription Description - { - get - { - return textureDescription; - } - } + public ref readonly TextureDescription Description => ref textureDescription; /// - /// Gets the view description. + /// Gets the view description. /// - /// The view description. - public TextureViewDescription ViewDescription - { - get - { - return textureViewDescription; - } - } + public ref readonly TextureViewDescription ViewDescription => ref textureViewDescription; /// - /// The dimension of a texture. + /// Gets the type of the Texture. /// public TextureDimension Dimension - { - get - { - // TODO: What's the point of storing the dimensions? - // We could just as well generate the "TextureDimension" based on the "TextureTarget" property, - // because "TextureDimension" is fully dependent on the "TextureTarget" property. - // E.g. "Texture2D" and "Texture2DMultisample" both return "TextureDimension.Texture2D". - return textureDescription.Dimension; - } - } + // TODO: What's the point of storing the dimensions? + // We could just as well generate the "TextureDimension" based on the "TextureTarget" property, + // because "TextureDimension" is fully dependent on the "TextureTarget" property. + // E.g. "Texture2D" and "Texture2DMultisample" both return "TextureDimension.Texture2D". + // TODO: Stale comment? + => textureDescription.Dimension; /// - /// The width of this texture view. + /// Gets the width of the Texture View. /// - /// The width of the view. public int ViewWidth { get; private set; } /// - /// The height of this texture view. + /// Gets the height of the Texture View. /// - /// The height of the view. public int ViewHeight { get; private set; } /// - /// The depth of this texture view. + /// Gets the depth of the Texture View. /// - /// The view depth. public int ViewDepth { get; private set; } /// - /// The format of this texture view. + /// Gets the pixel format of the Texture View. /// - /// The view format. - public PixelFormat ViewFormat - { - get - { - return textureViewDescription.Format; - } - } + public PixelFormat ViewFormat => textureViewDescription.Format; /// - /// The format of this texture view. + /// Gets a combination of flags describing the type of Texture View and how it can be bound to the graphics pipeline. /// - /// The type of the view. - public TextureFlags ViewFlags - { - get - { - return textureViewDescription.Flags; - } - } + public TextureFlags ViewFlags => textureViewDescription.Flags; /// - /// The format of this texture view. + /// Gets a value indicating which sub-resources of the Texture are accessible through the Texture View. /// - /// The type of the view. - public ViewType ViewType - { - get - { - return textureViewDescription.Type; - } - } + public ViewType ViewType => textureViewDescription.Type; /// - /// The dimension of the texture view. + /// Gets the type of the Texture View. /// public TextureDimension ViewDimension - { - get => Dimension == TextureDimension.TextureCube && ViewType != ViewType.Full ? TextureDimension.Texture2D : Dimension; - } + => Dimension == TextureDimension.TextureCube && ViewType != ViewType.Full + ? TextureDimension.Texture2D // For cube-maps, if not full, the View is over a single cube face + : Dimension; /// - /// The miplevel index of this texture view. + /// Gets the index of the mip-level the Texture View is referencing. /// - /// The mip level. - public int MipLevel - { - get - { - return textureViewDescription.MipLevel; - } - } + /// The index of the mip-level the Texture View references. The first (largest) mipLevel is always index 0. + /// + /// See for more information on how and + /// determines which sub-resources to select based on . + /// + public int MipLevel => textureViewDescription.MipLevel; /// - /// The array index of this texture view. + /// Gets the index of the array slice the Texture View is referencing. /// - /// The array slice. - public int ArraySlice - { - get - { - return textureViewDescription.ArraySlice; - } - } + /// + /// The array index the Texture View references. + /// If the parent Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0. + /// + /// + /// See for more information on how and + /// determines which sub-resources to select based on . + /// + public int ArraySlice => textureViewDescription.ArraySlice; /// - /// The width of the texture. + /// Gets the width of the Texture. /// - /// The width. - public int Width - { - get - { - return textureDescription.Width; - } - } + /// The width of the Texture in pixels. + public int Width => textureDescription.Width; /// - /// The height of the texture. + /// Gets the height of the Texture. /// - /// The height. - public int Height - { - get - { - return textureDescription.Height; - } - } + /// The height of the Texture in pixels. + public int Height => textureDescription.Height; /// - /// The depth of the texture. + /// Gets the depth of the Texture. /// - /// The depth. - public int Depth - { - get - { - return textureDescription.Depth; - } - } + /// The depth of the Texture in pixels. + public int Depth => textureDescription.Depth; /// - /// Number of textures in the array. + /// Gets the number of Textures in the array (if this is a Texture Array). /// - /// The size of the array. - /// This field is only valid for 1D, 2D and Cube . - public int ArraySize - { - get - { - return textureDescription.ArraySize; - } - } + /// The largestSize of the array. If this is not a Texture Array, it will return 1. + /// This field is only valid for 1D, 2D and Cube s. + public int ArraySize => textureDescription.ArraySize; /// - /// The maximum number of mipmap levels in the texture. + /// Gets the maximum number of mip-Levels in the Texture. /// - /// The mip levels. - public int MipLevels - { - get - { - return textureDescription.MipLevels; - } - } + public int MipLevelCount => textureDescription.MipLevelCount; /// - /// Texture format (see ) + /// Gets the pixel format of the Texture. /// - /// The format. - public PixelFormat Format - { - get - { - return textureDescription.Format; - } - } + public PixelFormat Format => textureDescription.Format; /// - /// Structure that specifies multisampling parameters for the texture. + /// Gets the multisampling for the Texture. /// - /// The multi sample level. - /// This field is only valid for a 2D . - public MultisampleCount MultisampleCount - { - get - { - return textureDescription.MultisampleCount; - } - } + /// A value of specifying the number of samples per pixel. + /// This field is only valid for 2D s. + public MultisampleCount MultisampleCount => textureDescription.MultisampleCount; /// - /// Value that identifies how the texture is to be read from and written to. + /// Gets the intended usage of the Texture. /// - public GraphicsResourceUsage Usage - { - get - { - return textureDescription.Usage; - } - } + /// A value that identifies how the Texture is to be read from and written to. + public GraphicsResourceUsage Usage => textureDescription.Usage; /// - /// Texture flags. + /// Gets a combination of flags indicating how the Texture can be bound to the graphics pipeline. /// - public TextureFlags Flags - { - get - { - return textureDescription.Flags; - } - } + public TextureFlags Flags => textureDescription.Flags; /// - /// Resource options for DirectX 11 textures. + /// Gets a combination of flags indicating special options for the Texture, like sharing. /// - public TextureOptions Options - { - get - { - return textureDescription.Options; - } - } + public TextureOptions Options => textureDescription.Options; /// - /// The shared handle if created with TextureOption.Shared or TextureOption.SharedNthandle, IntPtr.Zero otherwise. + /// Gets a handle that identifies the Texture as a shared resource when it has the + /// option or . /// + /// + /// A handle that identifies the shared Texture, or if it is not a shared resource. + /// public IntPtr SharedHandle { get; private set; } = IntPtr.Zero; #if STRIDE_GRAPHICS_API_DIRECT3D11 /// - /// Gets the name of the shared Nt handle when created with TextureOption.SharedNthandle. + /// Gets the name of the shared NT handle that identifies the Texture as a shared resource + /// when it has the option . /// - public string SharedNtHandleName { get; private set; } = string.Empty; + /// + /// The name of the NT handle of the shared Texture, or if it is not a shared resource. + /// + public string SharedNtHandleName { get; private set; } = string.Empty; #endif /// - /// Gets a value indicating whether this instance is a render target. + /// Gets a value indicating if the Texture View is a Render Target View. /// - /// true if this instance is render target; otherwise, false. - public bool IsRenderTarget - { - get - { - return (ViewFlags & TextureFlags.RenderTarget) != 0; - } - } + /// + /// if the Texture View is a Render Target View; otherwise, . + /// + public bool IsRenderTarget => ViewFlags.HasFlag(TextureFlags.RenderTarget); /// - /// Gets a value indicating whether this instance is a depth stencil. + /// Gets a value indicating if the Texture View is a Depth-Stencil View. /// - /// true if this instance is a depth stencil; otherwise, false. - public bool IsDepthStencil - { - get - { - return (ViewFlags & TextureFlags.DepthStencil) != 0; - } - } + /// + /// if the Texture View is a Depth-Stencil View; otherwise, . + /// + public bool IsDepthStencil => ViewFlags.HasFlag(TextureFlags.DepthStencil); /// - /// Gets a value indicating whether this instance is a depth stencil readonly. + /// Gets a value indicating if the Texture View is a read-only Depth-Stencil View. /// - /// true if this instance is a depth stencil readonly; otherwise, false. - public bool IsDepthStencilReadOnly - { - get - { - return (ViewFlags & TextureFlags.DepthStencilReadOnly) == TextureFlags.DepthStencilReadOnly; - } - } + /// + /// if the Texture View is a read-only Depth-Stencil View; otherwise, . + /// + public bool IsDepthStencilReadOnly => (ViewFlags & TextureFlags.DepthStencilReadOnly) == TextureFlags.DepthStencilReadOnly; /// - /// Gets a value indicating whether this instance is a shader resource. + /// Gets a value indicating if the Texture View is a Shader Resource View. /// - /// true if this instance is a shader resource; otherwise, false. - public bool IsShaderResource - { - get - { - return (ViewFlags & TextureFlags.ShaderResource) != 0; - } - } + /// + /// if the Texture View is a Shader Resource View; otherwise, . + /// + public bool IsShaderResource => ViewFlags.HasFlag(TextureFlags.ShaderResource); /// - /// Gets a value indicating whether this instance is a shader resource. + /// Gets a value indicating if the Texture View is an Unordered Access View. /// - /// true if this instance is a shader resource; otherwise, false. - public bool IsUnorderedAccess - { - get - { - return (ViewFlags & TextureFlags.UnorderedAccess) != 0; - } - } + /// + /// if the Texture View is an Unordered Access View; otherwise, . + /// + public bool IsUnorderedAccess => ViewFlags.HasFlag(TextureFlags.UnorderedAccess); /// - /// Gets a value indicating whether this instance is a multi sample texture. + /// Gets a value indicating if the Texture is a multi-sampled Texture. /// - /// true if this instance is multi sample texture; otherwise, false. - public bool IsMultisample - { - get - { - return this.MultisampleCount > MultisampleCount.None; - } - } + /// + /// if the Texture is multi-sampled; otherwise, . + /// + public bool IsMultiSampled => MultisampleCount > MultisampleCount.None; /// - /// Gets a boolean indicating whether this is a using a block compress format (BC1, BC2, BC3, BC4, BC5, BC6H, BC7). + /// Gets a value indicating if the Texture is a using a block compress format (BC1, BC2, BC3, BC4, BC5, BC6H, BC7). /// - public bool IsBlockCompressed { get; private set; } + /// + public bool IsBlockCompressed => Description.Format.IsCompressed(); /// - /// Gets the size of this texture. + /// Gets the largestSize of the Texture or Texture View. /// - /// The size. - public Size3 Size => new Size3(ViewWidth, ViewHeight, ViewDepth); - + /// The largestSize of the Texture or Texture View as (Width, Height, Depth). + public Size3 Size => new(ViewWidth, ViewHeight, ViewDepth); + /// - /// When texture streaming is activated, the size of the texture when loaded at full quality. + /// Gets the largestSize of the Texture when loaded at full quality when texture streaming is enabled. /// public Size3 FullQualitySize { @@ -403,54 +291,92 @@ public Size3 FullQualitySize internal set => fullQualitySize = value; } - /// - /// The width stride in bytes (number of bytes per row). + /// + /// Gets the width stride of the Texture in bytes (y.e. the number of bytes per row). /// internal int RowStride { get; private set; } /// - /// The underlying parent texture (if this is a view). + /// Gets the underlying parent Texture if this is a Texture View. /// internal Texture ParentTexture { get; private set; } /// - /// Returns the total memory allocated by the texture in bytes. + /// Gets the total amount of memory allocated by the Texture in bytes. /// internal int SizeInBytes { get; private set; } private MipMapDescription[] mipmapDescriptions; - public Texture() - { - } + + // Needed for serialization + public Texture() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The device. - internal Texture(GraphicsDevice device) : base(device) - { - } + /// The graphics device. + internal Texture(GraphicsDevice device) : base(device) { } + + /// + /// Initializes a new instance of the class. + /// + /// The graphics device. + /// + /// A name that can be used to identify the Texture. + /// Specify to use the type's name instead. + /// + internal Texture(GraphicsDevice device, string? name) : base(device, name) { } + + /// protected override void Destroy() { base.Destroy(); - if (ParentTexture != null) - { - ParentTexture.ReleaseInternal(); - } + + ParentTexture?.ReleaseInternal(); } + /// protected internal override bool OnRecreate() { base.OnRecreate(); + OnRecreateImpl(); return true; } + /// + /// Perform platform-specific recreation of the Texture. + /// + private partial void OnRecreateImpl(); + + /// + /// Initializes the Texture from a and, optionally, initial data. + /// + /// The description of the Texture's characteristics. + /// Initial data to upload to the Texture, or if no initial data is provided. + /// The current Texture already initialized. + /// + /// The Texture's and the are not compatible. The parent Texture must include all + /// the flags defined by the Texture View. + /// + /// + /// The is not supported for the specified . Check the + /// for information about supported pixel formats and the compatible + /// multi-sample counts. + /// + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + /// is not supported for Render Targets. + /// Multi-sampling is not supported for Unordered Access Views. + /// The Depth-Stencil format specified is not supported. + /// Cannot create a read-only Depth-Stencil View because the device does not support it. internal Texture InitializeFrom(TextureDescription description, DataBox[] textureDatas = null) { - return InitializeFrom(null, description, new TextureViewDescription(), textureDatas); + var viewDescription = new TextureViewDescription(); + return InitializeFrom(parentTexture: null, in description, in viewDescription, textureDatas); } #if STRIDE_PLATFORM_ANDROID //&& USE_GLES_EXT_OES_TEXTURE @@ -461,31 +387,110 @@ internal Texture InitializeForExternalOES() } #endif - internal Texture InitializeFrom(TextureDescription description, TextureViewDescription viewDescription, DataBox[] textureDatas = null) - { - return InitializeFrom(null, description, viewDescription, textureDatas); - } - - internal Texture InitializeFrom(Texture parentTexture, TextureViewDescription viewDescription, DataBox[] textureDatas = null) - { - return InitializeFrom(parentTexture, parentTexture.Description, viewDescription, textureDatas); - } - - internal Texture InitializeFrom(Texture parentTexture, TextureDescription description, TextureViewDescription viewDescription, DataBox[] textureDatas = null) + /// + /// Initializes the Texture View from a Texture's and, optionally, initial data. + /// Also initializes a Texture View over the resource. + /// + /// The description of the Texture's characteristics. + /// The description of the Texture View's characteristics. + /// + /// Initial data to upload through the Texture View, or if no initial data is provided. + /// + /// The current Texture already initialized. + /// + /// + /// The Texture's and the are not compatible. The parent Texture must include all + /// the flags defined by the Texture View, or + /// + /// + /// The is not supported for the specified . Check the + /// for information about supported pixel formats and the compatible + /// multi-sample counts. + /// + /// + internal Texture InitializeFrom(ref readonly TextureDescription description, + ref readonly TextureViewDescription viewDescription, + DataBox[] textureDatas = null) + { + return InitializeFrom(parentTexture: null, in description, in viewDescription, textureDatas); + } + + /// + /// Initializes the Texture View for a Texture and, optionally, uploads initial data. + /// + /// The parent Texture. + /// The description of the Texture View's characteristics. + /// + /// Initial data to upload through the Texture View, or if no initial data is provided. + /// + /// The current Texture View already initialized. + /// + /// + /// The Texture's and the are not compatible. The parent Texture must include all + /// the flags defined by the Texture View, or + /// + /// + /// The is not supported for the specified . Check the + /// for information about supported pixel formats and the compatible + /// multi-sample counts. + /// + /// + internal Texture InitializeFrom(Texture parentTexture, ref readonly TextureViewDescription viewDescription, DataBox[] textureDatas = null) + { + return InitializeFrom(parentTexture, in parentTexture.Description, in viewDescription, textureDatas); + } + + /// + /// Initializes the Texture or Texture View and, optionally, uploads initial data. + /// + /// + /// The parent Texture to initialize a Texture View over, or if no parent Texture is specified, + /// in which case this may be a "root" resource and a view over it all-in-one. + /// + /// + /// The description of the characteristics of the Texture to create, + /// or the parent Texture's characteristics if this is a Texture View. + /// + /// The description of the Texture View's characteristics. + /// + /// Initial data to upload through the Texture View, or if no initial data is provided. + /// + /// The current Texture View already initialized. + /// Invalid Texture share options () specified. + /// + /// The Texture's and the are not compatible. The parent Texture must include all + /// the flags defined by the Texture View. + /// + /// + /// The is not supported for the specified . Check the + /// for information about supported pixel formats and the compatible + /// multi-sample counts. + /// + /// Multi-sampling is only supported for 2D Textures. + /// A Texture Cube must have an array size greater than 1. + /// Texture Arrays are not supported for 3D Textures. + /// is not supported for Render Targets. + /// Multi-sampling is not supported for Unordered Access Views. + /// The Depth-Stencil format specified is not supported. + /// Cannot create a read-only Depth-Stencil View because the device does not support it. + internal Texture InitializeFrom(Texture parentTexture, + ref readonly TextureDescription description, + ref readonly TextureViewDescription viewDescription, + DataBox[] textureDatas = null) { ParentTexture = parentTexture; ParentTexture?.AddReferenceInternal(); textureDescription = description; textureViewDescription = viewDescription; - IsBlockCompressed = description.Format.IsCompressed(); RowStride = ComputeRowPitch(0); mipmapDescriptions = Image.CalculateMipMapDescription(description); - SizeInBytes = ArraySize * mipmapDescriptions?.Sum(desc => desc.MipmapSize) ?? 0; + SizeInBytes = ArraySize * mipmapDescriptions?.Sum(mip => mip.MipmapSize) ?? 0; ViewWidth = Math.Max(1, Width >> MipLevel); ViewHeight = Math.Max(1, Height >> MipLevel); ViewDepth = Math.Max(1, Depth >> MipLevel); + if (ViewFormat == PixelFormat.None) { textureViewDescription.Format = description.Format; @@ -495,18 +500,22 @@ internal Texture InitializeFrom(Texture parentTexture, TextureDescription descri textureViewDescription.Flags = description.Flags; } - // Check that the view is compatible with the parent texture + // Check that the Texture View flags are compatible with the parent Texture's flags var filterViewFlags = (TextureFlags)((int)ViewFlags & (~DepthStencilReadOnlyFlags)); if ((Flags & filterViewFlags) != filterViewFlags) { - throw new NotSupportedException("Cannot create a texture view with flags [{0}] from the parent texture [{1}] as the parent texture must include all flags defined by the view".ToFormat(ViewFlags, Flags)); + throw new NotSupportedException( + $"Cannot create a Texture View with flags [{ViewFlags}] from the parent Texture with flags [{Flags}]. " + + $"The parent Texture must include all the flags defined by the Texture View"); } - if (IsMultisample) + if (IsMultiSampled) { var maxCount = GraphicsDevice.Features[Format].MultisampleCountMax; if (maxCount < MultisampleCount) - throw new NotSupportedException($"Cannot create a texture with format {Format} and multisample level {MultisampleCount}. Maximum supported level is {maxCount}"); + throw new NotSupportedException( + $"Cannot create a Texture with format {Format} and multi-sample level {MultisampleCount}. " + + $"The maximum supported level is {maxCount}"); } InitializeFromImpl(textureDatas); @@ -515,7 +524,20 @@ internal Texture InitializeFrom(Texture parentTexture, TextureDescription descri } /// - /// Releases the texture data. + /// Initializes the Texture with no initial data. + /// + private void InitializeFromImpl() => InitializeFromImpl(dataBoxes: null); + + /// + /// Performs platform-dependent initialization of the Texture. + /// + /// + /// An array of pointing to the data to initialize the Texture's sub-resources. + /// + private partial void InitializeFromImpl(DataBox[] dataBoxes); + + /// + /// Releases the Texture data. /// public void ReleaseData() { @@ -523,39 +545,58 @@ public void ReleaseData() OnDestroyed(); // Clean description - textureDescription = new TextureDescription(); - textureViewDescription = new TextureViewDescription(); + textureDescription = default; + textureViewDescription = default; ViewWidth = ViewHeight = ViewDepth = 0; SizeInBytes = 0; mipmapDescriptions = null; } + /// - /// Gets a view on this texture for a particular , array index (or zIndex for Texture3D), and mipmap index. + /// Gets a Texture View on this Texture. /// - /// The view description. - /// A new texture object that is bouded to the requested view. + /// The description of the Texture View to create. + /// A new that represents the requested Texture View. + /// + /// + /// The Texture's and the are not compatible. The parent Texture must include all + /// the flags defined by the Texture View, or + /// + /// + /// The is not supported for the specified . Check the + /// for information about supported pixel formats and the compatible + /// multi-sample counts. + /// + /// public Texture ToTextureView(TextureViewDescription viewDescription) { - return new Texture(GraphicsDevice).InitializeFrom(ParentTexture ?? this, viewDescription); + var texture = GraphicsDevice.IsDebugMode + ? new Texture(GraphicsDevice, Name + " " + GetViewDebugName(in viewDescription)) + : new Texture(GraphicsDevice); + + return texture.InitializeFrom(ParentTexture ?? this, in viewDescription); } /// - /// Gets the mipmap description of this instance for the specified mipmap level. + /// Gets the description a specific mipLevel of the Texture. /// - /// The mipmap. - /// A description of a particular mipmap for this texture. - public MipMapDescription GetMipMapDescription(int mipmap) + /// The mipmap level to query. + /// A describing the requested mipmap. + public ref readonly MipMapDescription GetMipMapDescription(int mipLevel) { - return mipmapDescriptions[mipmap]; + return ref mipmapDescriptions[mipLevel]; } /// - /// Calculates the size of a particular mip. + /// Calculates the largestSize of a specific mip-level. /// - /// The size. - /// The mip level. - /// System.Int32. + /// The original full largestSize from which to calculate mip-level largestSize, in texels. + /// The mip-level index. + /// The largestSize of the specified , in texels. + /// + /// Each mip-level becomes progressively smaller as grows. + /// public static int CalculateMipSize(int size, int mipLevel) { mipLevel = Math.Min(mipLevel, Image.CountMips(size)); @@ -563,328 +604,677 @@ public static int CalculateMipSize(int size, int mipLevel) } /// - /// Calculates the number of miplevels for a Texture 1D. - /// - /// The width of the texture. - /// A , set to true to calculates all mipmaps, to false to calculate only 1 miplevel, or > 1 to calculate a specific amount of levels. - /// The number of miplevels. - public static int CalculateMipLevels(int width, MipMapCount mipLevels) - { - if (mipLevels > 1) - { - int maxMips = CountMips(width); - if (mipLevels > maxMips) - throw new InvalidOperationException($"MipLevels must be <= {maxMips}"); - } - else if (mipLevels == 0) + /// Counts the number of mip-levels for a one-dimensional Texture. + /// + /// The width of the Texture, in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The number of mip-levels that can be created for . + /// + /// is greater than the maximum number of possible mip-levels for the provided . + /// + public static int CountMipLevels(int width, MipMapCount mipLevels) + { + switch (mipLevels.Count) { - mipLevels = CountMips(width); - } - else - { - mipLevels = 1; + case > 1: // Specific number + { + var maxMipLevels = CountMipLevels(width); + ArgumentOutOfRangeException.ThrowIfGreaterThan(mipLevels.Count, maxMipLevels, nameof(mipLevels)); + return mipLevels.Count; + } + case 0: + return CountMipLevels(width); // All mips + + default: + return 1; // Single mip } - return mipLevels; } /// - /// Calculates the number of miplevels for a Texture 2D. + /// Counts the number of mip-levels for a one-dimensional Texture. /// - /// The width of the texture. - /// The height of the texture. - /// A , set to true to calculates all mipmaps, to false to calculate only 1 miplevel, or > 1 to calculate a specific amount of levels. - /// The number of miplevels. - public static int CalculateMipLevels(int width, int height, MipMapCount mipLevels) + /// The width of the Texture, in texels. + /// The number of mip-levels that can be created for . + public static int CountMipLevels(int width) { - if (mipLevels > 1) - { - int maxMips = CountMips(width, height); - if (mipLevels > maxMips) - throw new InvalidOperationException($"MipLevels must be <= {maxMips}"); - } - else if (mipLevels == 0) + int mipLevels = 1; + + while (width > 1) { - mipLevels = CountMips(width, height); + ++mipLevels; + + width >>= 1; } - else + + return mipLevels; + } + + /// + /// Counts the number of mip-levels for a two-dimensional Texture. + /// + /// The width of the Texture, in texels. + /// The height of the Texture, in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The number of mip-levels that can be created for and . + /// + /// is greater than the maximum number of possible mip-levels for the provided + /// and . + /// + public static int CountMipLevels(int width, int height, MipMapCount mipLevels) + { + switch (mipLevels.Count) { - mipLevels = 1; + case > 1: // Specific number + { + var maxMipLevels = CountMipLevels(width, height); + ArgumentOutOfRangeException.ThrowIfGreaterThan(mipLevels.Count, maxMipLevels, nameof(mipLevels)); + return mipLevels.Count; + } + case 0: + return CountMipLevels(width, height); // All mips + + default: + return 1; // Single mip } - return mipLevels; } /// - /// Calculates the number of miplevels for a Texture 2D. + /// Counts the number of mip-levels for a two-dimensional Texture. /// - /// The width of the texture. - /// The height of the texture. - /// The depth of the texture. - /// A , set to true to calculates all mipmaps, to false to calculate only 1 miplevel, or > 1 to calculate a specific amount of levels. - /// The number of miplevels. - public static int CalculateMipLevels(int width, int height, int depth, MipMapCount mipLevels) + /// The width of the Texture, in texels. + /// The height of the Texture, in texels. + /// The number of mip-levels that can be created for and . + public static int CountMipLevels(int width, int height) { - if (mipLevels > 1) - { - if (!MathUtil.IsPow2(width) || !MathUtil.IsPow2(height) || !MathUtil.IsPow2(depth)) - throw new InvalidOperationException("Width/Height/Depth must be power of 2"); + return CountMipLevels(Math.Max(width, height)); + } - int maxMips = CountMips(width, height, depth); - if (mipLevels > maxMips) - throw new InvalidOperationException($"MipLevels must be <= {maxMips}"); - } - else if (mipLevels == 0) + /// + /// Counts the number of mip-levels for a three-dimensional Texture. + /// + /// The width of the Texture, in texels. + /// The height of the Texture, in texels. + /// The depth of the Texture, in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// + /// The number of mip-levels that can be created for , , + /// and . + /// + /// + /// is greater than the maximum number of possible mip-levels for the provided + /// , , and . + /// + /// + /// , , and must all be + /// a power of two. + /// + /// + /// The dimensions must be a power of two (2^n). + /// + public static int CountMipLevels(int width, int height, int depth, MipMapCount mipLevels) + { + switch (mipLevels.Count) { - if (!MathUtil.IsPow2(width) || !MathUtil.IsPow2(height) || !MathUtil.IsPow2(depth)) - throw new InvalidOperationException("Width/Height/Depth must be power of 2"); + case > 1: // Specific number + { + if (!int.IsPow2(width) || !int.IsPow2(height) || !int.IsPow2(depth)) + throw new InvalidOperationException("Width/Height/Depth must be power of 2"); - mipLevels = CountMips(width, height, depth); - } - else - { - mipLevels = 1; + var maxMipLevels = CountMipLevels(width, height, depth); + ArgumentOutOfRangeException.ThrowIfGreaterThan(mipLevels.Count, maxMipLevels, nameof(mipLevels)); + return mipLevels.Count; + } + case 0: + if (!int.IsPow2(width) || !int.IsPow2(height) || !int.IsPow2(depth)) + throw new InvalidOperationException("Width/Height/Depth must be power of 2"); + + return CountMipLevels(width, height, depth); // All mips + + default: + return 1; // Single mip } - return mipLevels; } /// - /// Gets the absolute sub-resource index from the array and mip slice. + /// Counts the number of mip-levels for a three-dimensional Texture. /// - /// The array slice index. - /// The mip slice index. - /// A value equals to arraySlice * Description.MipLevels + mipSlice. - public int GetSubResourceIndex(int arraySlice, int mipSlice) + /// The width of the Texture, in texels. + /// The height of the Texture, in texels. + /// The depth of the Texture, in texels. + /// + /// The number of mip-levels that can be created for , , + /// and . + /// + public static int CountMipLevels(int width, int height, int depth) { - return arraySlice * MipLevels + mipSlice; + return CountMipLevels(Math.Max(width, Math.Max(height, depth))); } /// - /// Calculates the expected width of a texture using a specified type. + /// Returns the absolute sub-resource index from an array slice and mip-level. /// - /// The type of the T pixel data. - /// The expected width - /// If the size is invalid - public int CalculateWidth(int mipLevel = 0) where TData : unmanaged + /// The array index. + /// The mip slice index. + /// The sub-resource absolute index, calculated as arrayIndex * Description.MipLevelCount + mipLevel. + public int GetSubResourceIndex(int arrayIndex, int mipLevel) { - var widthOnMip = CalculateMipSize((int)Width, mipLevel); - var rowStride = widthOnMip * Format.SizeInBytes(); + return arrayIndex * MipLevelCount + mipLevel; + } - var dataStrideInBytes = Unsafe.SizeOf() * widthOnMip; - var width = ((double)rowStride / dataStrideInBytes) * widthOnMip; - if (Math.Abs(width - (int)width) > double.Epsilon) + /// + /// Calculates the expected width of the Texture for a specific mip-level, in elements. + /// + /// The type of the pixel data. + /// + /// The mip-level for which to calculate the width. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// The expected width of the Texture for the mip-level specified by , y.e. the + /// number of elements across the width of the Texture. + /// + /// + /// + /// The expected width of the Texture depends both on the largestSize of and the largestSize + /// of . It's relation (which must be an integer ratio) defines how they + /// alter the original mip-level width. + /// + /// + /// For example, for a Texture with a width of 100 pixels and format (4 bytes per pixel), + /// this method allows to interpret the width as different types: + /// + /// int widthAsUInts = texture.CalculateWidth<uint>(); // 100 uints + /// int widthAsBytes = texture.CalculateWidth<byte>(); // 400 bytes + /// int widthAsFloats = texture.CalculateWidth<float>(); // 100 floats + /// + /// + /// + /// + /// The largestSize of and the largestSize of does not match. + /// The ratio between the two must be an integer, or else there would be remaining bytes. + /// + public unsafe int CalculateWidth(int mipLevel = 0) where TData : unmanaged + { + var mipWidth = CalculateMipSize(Width, mipLevel); + + var rowStride = mipWidth * Format.SizeInBytes(); + var dataStrideInBytes = mipWidth * sizeof(TData); + + var (width, rem) = Math.DivRem(rowStride * mipWidth, dataStrideInBytes); + + if (rem != 0) throw new ArgumentException("sizeof(TData) / sizeof(Format) * Width is not an integer"); - return (int)width; + return width; } /// - /// Calculates the number of pixel data this texture is requiring for a particular mip level. + /// Calculates the number of pixel elements of type the Texture requires + /// for a particular mip-level. /// - /// The type of the T pixel data. - /// The mip level. - /// The number of pixel data. - /// This method is used to allocated a texture data buffer to hold pixel datas: var textureData = new T[ texture.CalculatePixelCount<T>() ] ;. + /// The type of the pixel data. + /// + /// The mip-level for which to calculate the width. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// The expected number of elements of the Texture for the mip-level specified by . + /// + /// + /// This method can be used to allocate a Texture data buffer to hold pixel data of type as follows: + /// + /// var textureData = new TData[ texture.CalculatePixelDataCount<TData>() ]; + /// + /// + /// + /// The largestSize of and the largestSize of does not match. + /// The ratio between the two must be an integer, or else there would be remaining bytes. + /// + /// public int CalculatePixelDataCount(int mipLevel = 0) where TData : unmanaged { - return CalculateWidth(mipLevel) * CalculateMipSize(Height, mipLevel) * CalculateMipSize(Depth, mipLevel); + return CalculateWidth(mipLevel) + * CalculateMipSize(Height, mipLevel) + * CalculateMipSize(Depth, mipLevel); } + #region Debug + /// - /// Gets the content of this texture to an array of data. + /// Generates a debug-friendly name for the Texture based on its usage, flags, etc. /// - /// The type of the T data. - /// The command list. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// The texture data. - /// - /// This method is only working when called from the main thread that is accessing the main . - /// This method creates internally a stagging resource, copies to it and map it to memory. Use method with explicit staging resource - /// for optimal performances. - public TData[] GetData(CommandList commandList, int arraySlice = 0, int mipSlice = 0) where TData : unmanaged + /// The description of the Texture. + /// A string representing the debug name of the Texture. + private static string GetDebugName(ref readonly TextureDescription textureDescription) { - var toData = new TData[this.CalculatePixelDataCount(mipSlice)]; - GetData(commandList, toData, arraySlice, mipSlice); - return toData; + var textureUsage = textureDescription.Usage; + var arraySize = textureDescription.ArraySize; + var textureDimension = textureDescription.Dimension; + var multiSampleCount = textureDescription.MultisampleCount; + var flags = textureDescription.Flags; + var width = textureDescription.Width; + var height = textureDescription.Height; + var depth = textureDescription.Depth; + var format = textureDescription.Format; + + return GetDebugName(textureUsage, textureDimension, width, height, depth, arraySize, flags, format, multiSampleCount); } /// - /// Copies the content of this texture to an array of data. + /// Generates a debug-friendly name for the Texture based on its usage, flags, etc. /// - /// The type of the T data. - /// The command list. - /// The destination buffer to receive a copy of the texture datas. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// true if data was correctly retrieved, false if flag was true and the resource is still being used by the GPU for writing. - /// - /// This method is only working when called from the main thread that is accessing the main . - /// This method creates internally a stagging resource if this texture is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource - /// for optimal performances. - public bool GetData(CommandList commandList, TData[] toData, int arraySlice = 0, int mipSlice = 0, bool doNotWait = false) where TData : unmanaged + /// The usage of the Texture. + /// The dimension of the Texture. + /// The width of the Texture. + /// The height of the Texture. + /// The depth of the Texture. + /// The number of array slices in the Texture. + /// The flags of the Texture. + /// The pixel format of the Texture. + /// The multi-sample count of the Texture. + /// A string representing the debug name of the Texture. + private static string GetDebugName(GraphicsResourceUsage textureUsage, TextureDimension textureDimension, + int width, int height = 1, int depth = 1, int arraySize = 1, + TextureFlags textureFlags = TextureFlags.None, PixelFormat format = PixelFormat.None, + MultisampleCount multiSampleCount = MultisampleCount.None) { - // Get data from this resource - if (Usage == GraphicsResourceUsage.Staging) + var usage = textureUsage != GraphicsResourceUsage.Default + ? $"{textureUsage} " + : string.Empty; + + var dimension = textureDimension switch { - // Directly if this is a staging resource - return GetData(commandList, this, toData, arraySlice, mipSlice, doNotWait); - } - else + TextureDimension.Texture1D when arraySize > 1 => $"1D Texture Array ({arraySize} elements)", + TextureDimension.Texture1D => "1D Texture", + TextureDimension.Texture2D when arraySize > 1 => $"2D Texture Array ({arraySize} elements)", + TextureDimension.Texture2D => "2D Texture", + TextureDimension.Texture3D => "3D Texture", + TextureDimension.TextureCube when arraySize > 1 => $"Cube Texture Array ({arraySize} elements)", + TextureDimension.TextureCube => "Cube Texture", + + _ => "Texture" + }; + + var msaa = multiSampleCount > MultisampleCount.None + ? $" MSAA {multiSampleCount}" + : string.Empty; + + var flags = textureFlags switch { - // Unefficient way to use the Copy method using dynamic staging texture - using (var throughStaging = this.ToStaging()) - return GetData(commandList, throughStaging, toData, arraySlice, mipSlice, doNotWait); - } + TextureFlags.ShaderResource => "SRV ", + TextureFlags.RenderTarget => "Render Target ", + TextureFlags.UnorderedAccess => "UAV ", + TextureFlags.DepthStencil => "Depth-Stencil ", + + _ => string.Empty + }; + + var size = string.Empty; + if (textureDimension is TextureDimension.Texture1D) + size = $"{width}"; + else if (textureDimension is TextureDimension.Texture2D or TextureDimension.TextureCube) + size = $"{width}x{height}"; + else if (textureDimension is TextureDimension.Texture3D) + size = $"{width}x{height}x{depth}"; + + return $"{usage}{flags}{dimension}{msaa} ({size}, {format})"; } /// - /// Copies the content of this texture from GPU memory to an array of data on CPU memory using a specific staging resource. + /// Generates a debug-friendly name for a View on a Texture based on its type, flags, etc. /// - /// The type of the T data. - /// The command list. - /// The staging texture used to transfer the texture to. - /// To data. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// true if data was correctly retrieved, false if flag was true and the resource is still being used by the GPU for writing. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length - /// - /// This method is only working when called from the main thread that is accessing the main . - /// - public unsafe bool GetData(CommandList commandList, Texture stagingTexture, TData[] toData, int arraySlice = 0, int mipSlice = 0, bool doNotWait = false) where TData : unmanaged + /// The description of the Texture View. + /// A string representing the debug name of the Texture View. + private static string GetViewDebugName(ref readonly TextureViewDescription viewDescription) { - return GetData(commandList, stagingTexture, toData.AsSpan(), arraySlice, mipSlice, doNotWait); + var viewType = viewDescription.Type; + var flags = viewDescription.Flags; + var format = viewDescription.Format; + var arraySlice = viewDescription.ArraySlice; + var mipLevel = viewDescription.MipLevel; + + return GetViewDebugName(viewType, flags, format, arraySlice, mipLevel); } /// - /// Copies the content an array of data on CPU memory to this texture into GPU memory using the specified (The graphics device could be deffered). + /// Generates a debug-friendly name for a View on a Texture based on its type, flags, etc. /// - /// The type of the T data. - /// The command list. - /// The data to copy from. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// Destination region - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length - /// - /// See unmanaged documentation for usage and restrictions. - /// - public unsafe void SetData(CommandList commandList, TData[] fromData, int arraySlice = 0, int mipSlice = 0, ResourceRegion? region = null) where TData : unmanaged + /// The type of the Texture View. + /// Flags describing how the Texture View can be bound to the graphics pipeline. + /// The pixel format of the Texture View. + /// The index of the array slice the Texture View is referencing. + /// The index of the mip-level the Texture View is referencing. + /// A string representing the debug name of the Texture View. + private static string GetViewDebugName(ViewType viewType, TextureFlags viewFlags, PixelFormat format, int arraySlice, int mipLevel) { - SetData(commandList, fromData.AsSpan(), arraySlice, mipSlice, region); + var type = viewType switch + { + ViewType.MipBand => "Mip Band ", + ViewType.ArrayBand => "Array Band ", + ViewType.Single => "Single ", + + _ => string.Empty + }; + + var flags = viewFlags switch + { + TextureFlags.ShaderResource => "ShaderResourceView", + TextureFlags.RenderTarget => "RenderTargetView", + TextureFlags.UnorderedAccess => "UnorderedAccessView", + TextureFlags.DepthStencil => "DepthStencilView", + + _ => string.Empty + }; + + var mipAndSlice = viewType != ViewType.Full + ? $" (Mip {mipLevel}, Slice {arraySlice})" + : string.Empty; + + var viewFormat = format != PixelFormat.None + ? $" [{format}]" + : string.Empty; + + return $"{type}{flags}{mipAndSlice}{viewFormat}"; } /// - /// Copies the content of this texture from GPU memory to a pointer on CPU memory using a specific staging resource. + /// Generates a debug-friendly name for a View on a Texture based on its type, flags, etc. /// - /// The command list. - /// The staging texture used to transfer the texture to. - /// The pointer to data in CPU memory. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// true if data was correctly retrieved, false if flag was true and the resource is still being used by the GPU for writing. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + /// The description of the Texture. + /// The description of the Texture View. + /// A string representing the debug name of the Texture View. + private static string GetViewDebugName(ref readonly TextureDescription textureDescription, + ref readonly TextureViewDescription viewDescription) + { + var debugName = GetDebugName(in textureDescription); + var viewDebugName = GetViewDebugName(in viewDescription); + + return string.IsNullOrWhiteSpace(viewDebugName) + ? debugName + : debugName + " " + viewDebugName; + } + + #endregion + + #region GetData: Reading data from the Texture + + // TODO: Some methods are marked as main-thread only. This is true for some platforms, but for all? + + /// + /// Copies the contents of the Texture from GPU memory to CPU memory. + /// + /// The type of the pixel data. + /// The where to register the command. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to get the data from. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// An array of with the Texture's data. /// - /// This method is only working when called from the main thread that is accessing the main . + /// This method is only working when called from the main thread that is accessing the main . + /// + /// This method creates internally a staging Resource, copies the data into it, and maps it to CPU memory. + /// You can use one of the methods that specify an explicit staging Resource for better performance. + /// /// - [Obsolete("Use span instead")] - public unsafe bool GetData(CommandList commandList, Texture stagingTexture, DataPointer toData, int arraySlice = 0, int mipSlice = 0, bool doNotWait = false) + public TData[] GetData(CommandList commandList, int arrayIndex = 0, int mipLevel = 0) where TData : unmanaged { - return GetData(commandList, stagingTexture, new Span((void*)toData.Pointer, toData.Size), arraySlice, mipSlice, doNotWait); + var toData = new TData[CalculatePixelDataCount(mipLevel)]; + GetData(commandList, toData, arrayIndex, mipLevel); + return toData; } /// - /// Copies the content of this texture from GPU memory to a pointer on CPU memory using a specific staging resource. - /// - /// The command list. - /// The staging texture used to transfer the texture to. - /// The pointer to data in CPU memory. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// if set to true this method will return immediately if the resource is still being used by the GPU for writing. Default is false - /// true if data was correctly retrieved, false if flag was true and the resource is still being used by the GPU for writing. - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + /// Copies the contents of the Texture from GPU memory to CPU memory. + ///
+ /// The type of the pixel data. + /// The where to register the command. + /// The destination buffer to copy the Texture data into. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to get the data from. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// If this method will return immediately if the resource is still being used by the GPU for writing. + /// makes this method wait until the operation is complete. The default value is (wait). + /// + /// + /// if data was correctly retrieved; + /// if flag was and the resource is still being used by the GPU for writing. + /// /// - /// This method is only working when called from the main thread that is accessing the main . + /// This method is only working when called from the main thread that is accessing the main . + /// + /// This method creates internally a staging Resource, copies the data into it, and maps it to CPU memory. + /// You can use one of the methods that specify an explicit staging Resource for better performance. + /// /// - public unsafe bool GetData(CommandList commandList, Texture stagingTexture, Span toData, int arraySlice = 0, int mipSlice = 0, bool doNotWait = false) where T : unmanaged + public bool GetData(CommandList commandList, TData[] toData, int arrayIndex = 0, int mipLevel = 0, bool doNotWait = false) where TData : unmanaged + { + if (Usage == GraphicsResourceUsage.Staging) + { + // Get the data directly if this is a staging Resource + return GetData(commandList, stagingTexture: this, toData, arrayIndex, mipLevel, doNotWait); + } + else + { + // Inefficient way to use the Copy method using a dynamic staging Texture + using var throughStaging = ToStaging(); + return GetData(commandList, throughStaging, toData, arrayIndex, mipLevel, doNotWait); + } + } + + /// + /// Copies the contents of the Texture from GPU memory to CPU memory using a specific staging Resource. + /// + /// The type of the pixel data. + /// The where to register the command. + /// The staging Texture used to transfer the Texture contents to. + /// The destination buffer to copy the Texture data into. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to get the data from. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// If this method will return immediately if the resource is still being used by the GPU for writing. + /// makes this method wait until the operation is complete. The default value is (wait). + /// + /// + /// if data was correctly retrieved; + /// if flag was and the resource is still being used by the GPU for writing. + /// + /// + /// The length of the destination buffer is not compatible with the expected largestSize for the data + /// at and . + /// + public unsafe bool GetData(CommandList commandList, Texture stagingTexture, TData[] toData, int arrayIndex = 0, int mipLevel = 0, bool doNotWait = false) where TData : unmanaged + { + return GetData(commandList, stagingTexture, toData.AsSpan(), arrayIndex, mipLevel, doNotWait); + } + + /// + /// Copies the contents of the Texture from GPU memory to CPU memory using a specific staging Resource. + /// + /// The where to register the command. + /// The staging Texture used to transfer the Texture contents to. + /// A ptrFromData to the data buffer in CPU memory to copy the Texture contents into. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to get the data from. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// If this method will return immediately if the resource is still being used by the GPU for writing. + /// makes this method wait until the operation is complete. The default value is (wait). + /// + /// + /// if data was correctly retrieved; + /// if flag was and the resource is still being used by the GPU for writing. + /// + /// + /// The length of the destination buffer is not compatible with the expected largestSize for the data + /// at and . + /// + [Obsolete("This method is obsolete. Use the Span-based methods instead")] + public unsafe bool GetData(CommandList commandList, Texture stagingTexture, DataPointer toData, int arrayIndex = 0, int mipLevel = 0, bool doNotWait = false) + { + return GetData(commandList, stagingTexture, new Span((void*)toData.Pointer, toData.Size), arrayIndex, mipLevel, doNotWait); + } + + /// + /// Copies the contents of the Texture from GPU memory to CPU memory using a specific staging Resource. + /// + /// The where to register the command. + /// The staging Texture used to transfer the Texture contents to. + /// The data buffer in CPU memory to copy the Texture contents into. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to get the data from. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// If this method will return immediately if the resource is still being used by the GPU for writing. + /// makes this method wait until the operation is complete. The default value is (wait). + /// + /// + /// if data was correctly retrieved; + /// if flag was and the resource is still being used by the GPU for writing. + /// + /// + /// The length of the destination buffer is not compatible with the expected largestSize for the data + /// at and . + /// + public unsafe bool GetData(CommandList commandList, Texture stagingTexture, Span toData, int arrayIndex = 0, int mipLevel = 0, bool doNotWait = false) where T : unmanaged { - if (stagingTexture == null) throw new ArgumentNullException("stagingTexture"); - var device = GraphicsDevice; - //var deviceContext = device.NativeDeviceContext; + ArgumentNullException.ThrowIfNull(stagingTexture); - // Get mipmap description for the specified mipSlice - var mipmap = this.GetMipMapDescription(mipSlice); + // Get a description for the specified mip-level + ref readonly var mipmap = ref GetMipMapDescription(mipLevel); - // Copy height, depth int height = mipmap.HeightPacked; int depth = mipmap.Depth; - - // Calculate depth stride based on mipmap level int rowStride = mipmap.RowStride; - - // Depth Stride int textureDepthStride = mipmap.DepthStride; - - // MipMap Stride int mipMapSize = mipmap.MipmapSize; int destLengthInBytes = toData.Length * sizeof(T); - // Check size validity of data to copy to if (destLengthInBytes > mipMapSize) - throw new ArgumentException($"Size of toData ({destLengthInBytes} bytes) is not compatible expected size ({mipMapSize} bytes) : Width * Height * Depth * sizeof(PixelFormat) size in bytes"); + throw new ArgumentException($"The length of the destination buffer ({destLengthInBytes} bytes) is not compatible with " + + $"the expected largestSize ({mipMapSize} bytes) : Width * Height * Depth * sizeof(Format) largestSize in bytes"); - // Copy the actual content of the texture to the staging resource + // Copy the actual content of the texture to the staging Resource if (!ReferenceEquals(this, stagingTexture)) commandList.Copy(this, stagingTexture); - // Calculate the subResourceIndex for a Texture - int subResourceIndex = this.GetSubResourceIndex(arraySlice, mipSlice); + int subResourceIndex = GetSubResourceIndex(arrayIndex, mipLevel); - // Map the staging resource to a CPU accessible memory - var mappedResource = commandList.MapSubresource(stagingTexture, subResourceIndex, MapMode.Read, doNotWait); + // Map the staging Resource to CPU-accessible memory + var mappedResource = commandList.MapSubResource(stagingTexture, subResourceIndex, MapMode.Read, doNotWait); - // Box can be empty if DoNotWait is set to true, return false if empty + // Box can be empty if `doNotWait` is true: Return false if empty var box = mappedResource.DataBox; if (box.IsEmpty) - { return false; - } - // If depth == 1 (Texture, Texture or TextureCube), then depthStride is not used - var boxDepthStride = this.Depth == 1 ? box.SlicePitch : textureDepthStride; + // If depth == 1 (like for 1D, 2D, or Cube), then depthStride is not used + var boxDepthStride = Depth == 1 ? box.SlicePitch : textureDepthStride; var isFlippedTexture = IsFlipped(); // The fast way: If same stride, we can directly copy the whole texture in one shot if (box.RowPitch == rowStride && boxDepthStride == textureDepthStride && !isFlippedTexture) { - fixed(void* destPtr = toData) - Unsafe.CopyBlockUnaligned(destPtr, (void*)box.DataPointer, (uint)mipMapSize); + fixed (void* destPtr = toData) + Unsafe.CopyBlockUnaligned(destPtr, (void*) box.DataPointer, (uint) mipMapSize); } else { // Otherwise, the long way by copying each scanline - var sourcePerDepthPtr = (byte*)box.DataPointer; + var sourcePerDepthPtr = (byte*) box.DataPointer; fixed (T* ptr = toData) { - byte* destPtr = (byte*)ptr; + byte* destPtr = (byte*) ptr; + // Iterate on all depths for (int j = 0; j < depth; j++) { var sourcePtr = sourcePerDepthPtr; - // Iterate on each line + // Iterate on each line if (isFlippedTexture) { sourcePtr += box.RowPitch * (height - 1); for (int i = height - 1; i >= 0; i--) { // Copy a single row - Unsafe.CopyBlockUnaligned(destPtr, sourcePtr, (uint)rowStride); + Unsafe.CopyBlockUnaligned(destPtr, sourcePtr, (uint) rowStride); sourcePtr -= box.RowPitch; destPtr += rowStride; } @@ -894,7 +1284,7 @@ public unsafe bool GetData(CommandList commandList, Texture stagingTexture, S for (int i = 0; i < height; i++) { // Copy a single row - Unsafe.CopyBlockUnaligned(destPtr, sourcePtr, (uint)rowStride); + Unsafe.CopyBlockUnaligned(destPtr, sourcePtr, (uint) rowStride); sourcePtr += box.RowPitch; destPtr += rowStride; } @@ -904,251 +1294,371 @@ public unsafe bool GetData(CommandList commandList, Texture stagingTexture, S } } - // Make sure that we unmap the resource in case of an exception - commandList.UnmapSubresource(mappedResource); + commandList.UnmapSubResource(mappedResource); return true; } - /// - /// Copies the content an data on CPU memory to this texture into GPU memory. - /// - /// The . - /// The data to copy from. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// Destination region - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + #endregion + + #region SetData: Writing data into the Texture + + /// + /// Copies the contents of an array of data on CPU memory into the Texture in GPU memory. + /// + /// The type of the pixel data. + /// The where to register the command. + /// The data buffer to copy from. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to copy the data to. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// An optional describing the region of data of the Texture to copy into. + /// Specify to copy to the whole sub-Resource at /. + /// + /// + /// The length of is not compatible with the expected largestSize for the data + /// at and . + /// This can also occur when the stride is different from the optimal stride, and is not the same largestSize as + /// the largestSize of . + /// + /// + /// can only be specified (non-) for Textures with . + /// + /// + /// The largestSize (in any of its dimensions) cannot be greater than the mip-level largestSize. + /// /// - /// See unmanaged documentation for usage and restrictions. + /// See and for more information about + /// usage and restrictions. /// - [Obsolete("Use span instead")] - public unsafe void SetData(CommandList commandList, DataPointer fromData, int arraySlice = 0, int mipSlice = 0, ResourceRegion? region = null) - { - SetData(commandList, new Span((void*)fromData.Pointer, fromData.Size), arraySlice, mipSlice, region); - } - - /// - /// Copies the content an data on CPU memory to this texture into GPU memory. - /// - /// The . - /// The data to copy from. - /// The array slice index. This value must be set to 0 for Texture 3D. - /// The mip slice index. - /// Destination region - /// When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length + public unsafe void SetData(CommandList commandList, TData[] fromData, int arrayIndex = 0, int mipLevel = 0, ResourceRegion? region = null) where TData : unmanaged + { + SetData(commandList, fromData.AsSpan(), arrayIndex, mipLevel, region); + } + + /// + /// Copies the contents of a data buffer on CPU memory into the Texture in GPU memory. + /// + /// The where to register the command. + /// A ptrFromData to the data buffer to copy from. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to copy the data to. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// An optional describing the region of data of the Texture to copy into. + /// Specify to copy to the whole sub-Resource at /. + /// + /// + /// The length of is not compatible with the expected largestSize for the data + /// at and . + /// This can also occur when the stride is different from the optimal stride, and is not the same largestSize as + /// the largestSize of . + /// + /// + /// can only be specified (non-) for Textures with . + /// + /// + /// The largestSize (in any of its dimensions) cannot be greater than the mip-level largestSize. + /// /// - /// See unmanaged documentation for usage and restrictions. + /// See and for more information about + /// usage and restrictions. /// - public unsafe void SetData(CommandList commandList, Span fromData, int arraySlice = 0, int mipSlice = 0, ResourceRegion? region = null) where T : unmanaged + [Obsolete("This method is obsolete. Use the Span-based methods instead")] + public unsafe void SetData(CommandList commandList, DataPointer fromData, int arrayIndex = 0, int mipLevel = 0, ResourceRegion? region = null) + { + SetData(commandList, new ReadOnlySpan((void*) fromData.Pointer, fromData.Size), arrayIndex, mipLevel, region); + } + + /// + /// Copies the contents of a data buffer on CPU memory into the Texture in GPU memory. + /// + /// The type of the pixel data. + /// The where to register the command. + /// The data buffer to copy from. + /// + /// + /// The array index. + /// If the Texture is not a Texture Array or a Cube-map, only a single array slice will exist with index 0, which is the default value. + /// + /// This index must be 0 for a three-dimensional Texture. + /// + /// + /// The mip-level to copy the data to. + /// By default, the first mip-level at index 0 is selected, which is the most detailed one. + /// + /// + /// An optional describing the region of data of the Texture to copy into. + /// Specify to copy to the whole sub-Resource at /. + /// + /// + /// The length of is not compatible with the expected largestSize for the data + /// at and . + /// This can also occur when the stride is different from the optimal stride, and is not the same largestSize as + /// the largestSize of . + /// + /// + /// can only be specified (non-) for Textures with . + /// + /// + /// The largestSize (in any of its dimensions) cannot be greater than the mip-level largestSize. + /// + /// + /// See and for more information about + /// usage and restrictions. + /// + public unsafe void SetData(CommandList commandList, ReadOnlySpan fromData, int arrayIndex = 0, int mipLevel = 0, ResourceRegion? region = null) where TData : unmanaged { - if (commandList == null) throw new ArgumentNullException("commandList"); - if (region.HasValue && this.Usage != GraphicsResourceUsage.Default) - throw new ArgumentException("Region is only supported for textures with ResourceUsage.Default"); + ArgumentNullException.ThrowIfNull(commandList); + + if (region.HasValue && Usage != GraphicsResourceUsage.Default) + throw new ArgumentException($"A region can only be specified for Textures with {nameof(GraphicsResourceUsage)}.{nameof(GraphicsResourceUsage.Default)}", nameof(region)); - // Get mipmap description for the specified mipSlice - var mipMapDesc = this.GetMipMapDescription(mipSlice); + // Get a description for the specified mip-level + ref readonly var mipmap = ref GetMipMapDescription(mipLevel); - int width = mipMapDesc.Width; - int height = mipMapDesc.Height; - int depth = mipMapDesc.Depth; + int width = mipmap.Width; + int height = mipmap.Height; + int depth = mipmap.Depth; // If we are using a region, then check that parameters are fine - if (region.HasValue) + if (region is ResourceRegion regionToCheck) { - int newWidth = region.Value.Right - region.Value.Left; - int newHeight = region.Value.Bottom - region.Value.Top; - int newDepth = region.Value.Back - region.Value.Front; - if (newWidth > width) - throw new ArgumentException($"Region width [{newWidth}] cannot be greater than mipmap width [{width}]", "region"); - if (newHeight > height) - throw new ArgumentException($"Region height [{newHeight}] cannot be greater than mipmap height [{height}]", "region"); - if (newDepth > depth) - throw new ArgumentException($"Region depth [{newDepth}] cannot be greater than mipmap depth [{depth}]", "region"); - - width = newWidth; - height = newHeight; - depth = newDepth; + if (regionToCheck.Width > width) + throw new ArgumentOutOfRangeException(nameof(region), $"The region's width [{regionToCheck.Width}] cannot be greater than the mip-level's width [{width}]"); + if (regionToCheck.Height > height) + throw new ArgumentOutOfRangeException(nameof(region), $"The region's height [{regionToCheck.Height}] cannot be greater than the mip-level's height [{height}]"); + if (regionToCheck.Depth > depth) + throw new ArgumentOutOfRangeException(nameof(region), $"The region's depth [{regionToCheck.Depth}] cannot be greater than the mip-level's depth [{depth}]"); + + width = regionToCheck.Width; + height = regionToCheck.Height; + depth = regionToCheck.Depth; } - // Size per pixel var sizePerElement = Format.SizeInBytes(); - // Calculate depth stride based on mipmap level - int rowStride; - - // Depth Stride - int textureDepthStride; - - // Compute Actual pitch - Image.ComputePitch(this.Format, width, height, out rowStride, out textureDepthStride, out width, out height); + // Compute actual pitch + Image.ComputePitch(Format, width, height, out var rowStride, out var textureDepthStride, out width, out height); // Size Of actual texture data int sizeOfTextureData = textureDepthStride * depth; - int fromDataSizeInBytes = fromData.Length * sizeof(T); + int fromDataSizeInBytes = fromData.Length * sizeof(TData); - // Check size validity of data to copy to + // Check largestSize validity of data to copy from if (fromDataSizeInBytes < sizeOfTextureData) - throw new ArgumentException($"Size of fromData ({fromDataSizeInBytes} bytes) is not compatible expected size must be at least {sizeOfTextureData} bytes : Width * Height * Depth * sizeof(PixelFormat) size in bytes"); + throw new ArgumentException($"The length of the source data buffer ({fromDataSizeInBytes} bytes) is not compatible with the expected largestSize " + + $"of at least {sizeOfTextureData} bytes : Width * Height * Depth * sizeof(Format) largestSize in bytes"); - // Calculate the subResourceIndex for a Texture - int subResourceIndex = this.GetSubResourceIndex(arraySlice, mipSlice); + int subResourceIndex = GetSubResourceIndex(arrayIndex, mipLevel); - fixed (void* pointer = fromData) + fixed (void* ptrFromData = fromData) { - // If this texture is declared as default usage, we use UpdateSubresource that supports sub resource region. - if (this.Usage == GraphicsResourceUsage.Default) + // If the Texture is declared as Default usage, we use UpdateSubresource that supports sub-Resource region + if (Usage == GraphicsResourceUsage.Default) { - // If using a specific region, we need to handle this case - if (region.HasValue) + if (region is ResourceRegion validRegion) { - var regionValue = region.Value; - nint sourceDataPtr = (nint)pointer; - - // Workaround when using region with a deferred context and a device that does not support CommandList natively - // see http://blogs.msdn.com/b/chuckw/archive/2010/07/28/known-issue-direct3d-11-updatesubresource-and-deferred-contexts.aspx - if (commandList.GraphicsDevice.NeedWorkAroundForUpdateSubResource) - { - if (IsBlockCompressed) - { - regionValue.Left /= 4; - regionValue.Right /= 4; - regionValue.Top /= 4; - regionValue.Bottom /= 4; - } - sourceDataPtr = new IntPtr((byte*)sourceDataPtr - (regionValue.Front * textureDepthStride) - (regionValue.Top * rowStride) - (regionValue.Left * sizePerElement)); - } - commandList.UpdateSubresource(this, subResourceIndex, new DataBox(sourceDataPtr, rowStride, textureDepthStride), regionValue); + commandList.UpdateSubResource(resource: this, subResourceIndex, new DataBox((nint) ptrFromData, rowStride, textureDepthStride), validRegion); } else { - commandList.UpdateSubresource(this, subResourceIndex, new DataBox((nint)pointer, rowStride, textureDepthStride)); + commandList.UpdateSubResource(resource: this, subResourceIndex, new DataBox((nint) ptrFromData, rowStride, textureDepthStride)); } } else { - var mappedResource = commandList.MapSubresource(this, subResourceIndex, this.Usage == GraphicsResourceUsage.Dynamic ? MapMode.WriteDiscard : MapMode.Write); + var mappedResource = commandList.MapSubResource(resource: this, subResourceIndex, Usage == GraphicsResourceUsage.Dynamic ? MapMode.WriteDiscard : MapMode.Write); var box = mappedResource.DataBox; - // If depth == 1 (Texture, Texture or TextureCube), then depthStride is not used - var boxDepthStride = this.Depth == 1 ? box.SlicePitch : textureDepthStride; + // If depth == 1 (like for 1D, 2D, or Cube), then depthStride is not used + var boxDepthStride = Depth == 1 ? box.SlicePitch : textureDepthStride; // The fast way: If same stride, we can directly copy the whole texture in one shot if (box.RowPitch == rowStride && boxDepthStride == textureDepthStride) { - Unsafe.CopyBlockUnaligned((void*)box.DataPointer, pointer, (uint)sizeOfTextureData); + Unsafe.CopyBlockUnaligned((void*) box.DataPointer, ptrFromData, (uint) sizeOfTextureData); } else { // Otherwise, the long way by copying each scanline - var destPerDepthPtr = (byte*)box.DataPointer; - var sourcePtr = (byte*)pointer; + var destPerDepthPtr = (byte*) box.DataPointer; + var sourcePtr = (byte*) ptrFromData; // Iterate on all depths - for (int j = 0; j < depth; j++) + for (int z = 0; z < depth; z++) { var destPtr = destPerDepthPtr; + // Iterate on each line - for (int i = 0; i < height; i++) + for (int y = 0; y < height; y++) { - Unsafe.CopyBlockUnaligned(destPtr, sourcePtr, (uint)rowStride); + Unsafe.CopyBlockUnaligned(destPtr, sourcePtr, (uint) rowStride); destPtr += box.RowPitch; sourcePtr += rowStride; } destPerDepthPtr += box.SlicePitch; } } - commandList.UnmapSubresource(mappedResource); + commandList.UnmapSubResource(mappedResource); } } } + #endregion + /// - /// Makes a copy of this texture. + /// Creates a copy of the Texture. /// + /// A copy of the Texture. /// - /// This method doesn't copy the content of the texture. + /// This method creates a new Texture with the exact same characteristics, but does not copy the contents + /// of the Texture to the new one. /// - /// - /// A copy of this texture. - /// public Texture Clone() { - return new Texture(GraphicsDevice).InitializeFrom(textureDescription.ToCloneableDescription(), ViewDescription); + var cloneableDescription = textureDescription.ToCloneableDescription(); + + var texture = GraphicsDevice.IsDebugMode + ? new Texture(GraphicsDevice, Name) + : new Texture(GraphicsDevice); + + return texture.InitializeFrom(in cloneableDescription, in ViewDescription); } /// - /// Return an equivalent staging texture CPU read-writable from this instance. + /// Creates a new Texture with the needed changes to serve as a staging Texture that can be read / written by the CPU. /// - /// The equivalent staging texture. + /// The equivalent staging Texture. public Texture ToStaging() { - return new Texture(this.GraphicsDevice).InitializeFrom(textureDescription.ToStagingDescription(), ViewDescription.ToStagingDescription()); + var stagingDescription = textureDescription.ToStagingDescription(); + var stagingViewDescription = ViewDescription.ToStagingDescription(); + + var texture = GraphicsDevice.IsDebugMode + ? new Texture(GraphicsDevice, Name) + : new Texture(GraphicsDevice); + + return texture.InitializeFrom(in stagingDescription, in stagingViewDescription); } /// - /// Loads a texture from a stream. + /// Loads a Texture from a stream. /// /// The . - /// The stream to load the texture from. - /// True to load the texture with unordered access enabled. Default is false. - /// Usage of the resource. Default is - /// Indicate if the texture should be loaded as an sRGB texture. If false, the texture is load in its default format. - /// A texture - public static Texture Load(GraphicsDevice device, Stream stream, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable, bool loadAsSRGB = false) + /// The stream to load the Texture from. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// + /// if the Texture should be loaded as an sRGB Texture; + /// to load it in its default format. + /// + /// The loaded Texture. + public static Texture Load(GraphicsDevice device, Stream stream, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable, bool loadAsSrgb = false) { - using (var image = Image.Load(stream, loadAsSRGB)) - return New(device, image, textureFlags, usage); + using var image = Image.Load(stream, loadAsSrgb); + + return New(device, image, textureFlags, usage); } /// - /// Loads a texture from a stream. + /// Creates a new Texture from an . /// - /// The . - /// The image. - /// True to load the texture with unordered access enabled. Default is false. - /// Usage of the resource. Default is - /// A texture - /// Dimension not supported + /// The . + /// An to create the Texture from. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read access by the GPU. + /// + /// The loaded Texture. + /// + /// is , or + /// is . + /// public static Texture New(GraphicsDevice device, Image image, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) { - if (device == null) throw new ArgumentNullException("device"); - if (image == null) throw new ArgumentNullException("image"); + ArgumentNullException.ThrowIfNull(device); + ArgumentNullException.ThrowIfNull(image); return New(device, image.Description, image.ToDataBox()); } /// - /// Creates a new texture with the specified generic texture description. + /// Creates a new Texture with the specified description. /// - /// The graphics device. - /// The description. - /// The data boxes. - /// A Texture instance, either a RenderTarget or DepthStencilBuffer or Texture, depending on Binding flags. - /// graphicsDevice + /// The . + /// A for the new Texture. + /// + /// An optional array of structures describing the initial data for all the sub-Resources of the new Texture. + /// + /// The new Texture. + /// graphicsDevice public static Texture New(GraphicsDevice graphicsDevice, TextureDescription description, params DataBox[] boxes) { return New(graphicsDevice, description, new TextureViewDescription(), boxes); } /// - /// Creates a new texture with the specified generic texture description. + /// Creates a new Texture with the specified description. /// - /// The graphics device. - /// The description. - /// The view description. - /// The data boxes. - /// A Texture instance, either a RenderTarget or DepthStencilBuffer or Texture, depending on Binding flags. - /// graphicsDevice + /// The . + /// A for the new Texture. + /// A describing a Texture View that will be created the same time. + /// + /// An optional array of structures describing the initial data for all the sub-Resources of the new Texture. + /// + /// The new Texture. + /// is . + /// + /// + /// The Texture's and the are not compatible. The parent Texture must include all + /// the flags defined by the Texture View, or + /// + /// + /// The is not supported for the specified . Check the + /// for information about supported pixel formats and the compatible + /// multi-sample counts. + /// + /// public static Texture New(GraphicsDevice graphicsDevice, TextureDescription description, TextureViewDescription viewDescription, params DataBox[] boxes) { - if (graphicsDevice == null) - { - throw new ArgumentNullException("graphicsDevice"); - } + ArgumentNullException.ThrowIfNull(graphicsDevice); + + var texture = graphicsDevice.IsDebugMode + ? new Texture(graphicsDevice, GetViewDebugName(in description, in viewDescription)) + : new Texture(graphicsDevice); - return new Texture(graphicsDevice).InitializeFrom(description, viewDescription, boxes); + return texture.InitializeFrom(in description, in viewDescription, boxes); } #if STRIDE_PLATFORM_ANDROID //&& USE_GLES_EXT_OES_TEXTURE @@ -1166,41 +1676,51 @@ public static Texture NewExternalOES(GraphicsDevice graphicsDevice) #endif /// - /// Saves this texture to a stream with a specified format. + /// Saves the Texture to a stream with the specified image format. /// - /// The command list. - /// The stream. - /// Type of the image file. + /// The where to register the command. + /// The stream to write the Texture contents to. + /// The type of the image file to create. + /// is . public void Save(CommandList commandList, Stream stream, ImageFileType fileType) { - if (stream == null) throw new ArgumentNullException("stream"); - using (var staging = ToStaging()) - Save(commandList, stream, staging, fileType); + ArgumentNullException.ThrowIfNull(stream); + + using var staging = ToStaging(); + Save(commandList, stream, staging, fileType); } /// - /// Gets the GPU content of this texture as an on the CPU. + /// Copies the contents of the Texture on GPU memory to an on the CPU. /// + /// The where to register the command. + /// The Image on CPU memory. public Image GetDataAsImage(CommandList commandList) { + // Directly if this is a staging Resource if (Usage == GraphicsResourceUsage.Staging) - return GetDataAsImage(commandList, this); // Directly if this is a staging resource + return GetDataAsImage(commandList, stagingTexture: this); using var stagingTexture = ToStaging(); return GetDataAsImage(commandList, stagingTexture); } /// - /// Gets the GPU content of this texture to an on the CPU. + /// Gets the contents of the Texture on GPU memory to an on the CPU. /// - /// The command list. - /// The staging texture used to temporary transfer the image from the GPU to CPU. - /// If stagingTexture is not a staging texture. + /// The where to register the command. + /// + /// The staging Texture used to temporarily transfer the image from GPU memory to CPU memory. + /// + /// is not a staging Texture. + /// is . + /// The Image on CPU memory. public unsafe Image GetDataAsImage(CommandList commandList, Texture stagingTexture) { - if (stagingTexture == null) throw new ArgumentNullException("stagingTexture"); + ArgumentNullException.ThrowIfNull(stagingTexture); + if (stagingTexture.Usage != GraphicsResourceUsage.Staging) - throw new ArgumentException("Invalid texture used as staging. Must have Usage = GraphicsResourceUsage.Staging", "stagingTexture"); + throw new ArgumentException("Invalid Texture used as staging Resource. It must have GraphicsResourceUsage.Staging", nameof(stagingTexture)); var image = Image.New(stagingTexture.Description); try @@ -1210,13 +1730,13 @@ public unsafe Image GetDataAsImage(CommandList commandList, Texture stagingTextu for (int mipLevel = 0; mipLevel < image.Description.MipLevels; mipLevel++) { var pixelBuffer = image.PixelBuffer[arrayIndex, mipLevel]; - GetData(commandList, stagingTexture, new Span((byte*)pixelBuffer.DataPointer, pixelBuffer.BufferStride), arrayIndex, mipLevel); + GetData(commandList, stagingTexture, new Span((byte*) pixelBuffer.DataPointer, pixelBuffer.BufferStride), arrayIndex, mipLevel); } } } - catch (Exception) + catch { - // If there was an exception, free the allocated image to avoid any memory leak. + // If there was an exception, free the allocated image to avoid any memory leak image.Dispose(); throw; } @@ -1224,100 +1744,182 @@ public unsafe Image GetDataAsImage(CommandList commandList, Texture stagingTextu } /// - /// Saves this texture to a stream with a specified format. + /// Saves the Texture to a stream with a specified format. /// - /// The command list. - /// The stream. - /// The staging texture used to temporary transfer the image from the GPU to CPU. - /// Type of the image file. - /// If stagingTexture is not a staging texture. + /// The where to register the command. + /// The stream to write the Texture to. + /// + /// The staging Texture used to temporarily transfer the image from GPU memory to CPU memory. + /// + /// The type of the image file to create. + /// is not a staging Texture. public void Save(CommandList commandList, Stream stream, Texture stagingTexture, ImageFileType fileType) { - using (var image = GetDataAsImage(commandList, stagingTexture)) - image.Save(stream, fileType); + using var image = GetDataAsImage(commandList, stagingTexture); + image.Save(stream, fileType); } /// - /// Calculates the mip map count from a requested level. + /// Calculates the mipmap count for a Texture with the specified dimensions up to a requested mip-level. /// - /// The requested level. - /// The width. - /// The height. - /// The depth. - /// The resulting mipmap count (clamp to [1, maxMipMapCount] for this texture) + /// The requested mip-level. + /// The width of the Texture, in pixels. + /// The height of the Texture, in pixels. + /// The depth of the Texture, in pixels. + /// The computed mip-level count. internal static int CalculateMipMapCount(MipMapCount requestedLevel, int width, int height = 0, int depth = 0) { - int size = Math.Max(Math.Max(width, height), depth); - //int maxMipMap = 1 + (int)Math.Ceiling(Math.Log(size) / Math.Log(2.0)); - int maxMipMap = CountMips(size); + int largestSize = Math.Max(Math.Max(width, height), depth); + + int maxMipLevelCount = CountMipLevels(largestSize); - return requestedLevel == 0 ? maxMipMap : Math.Min(requestedLevel, maxMipMap); + // If all mip-levels requested (0), accept the full count, else limit to `requestedLevel` + return requestedLevel == 0 ? maxMipLevelCount : Math.Min(requestedLevel, maxMipLevelCount); } - private static DataBox GetDataBox(PixelFormat format, int width, int height, int depth, T[] textureData, IntPtr fixedPointer) where T : unmanaged + /// + /// Computes and validates the memory layout of the data of a Texture that corresponds to a specific + /// pixel format and dimensions. + /// + /// + /// The pixel format of the Texture to calculate the for. + /// The width in pixels of the Texture to calculate the for. + /// The height in pixels of the Texture to calculate the for. + /// The depth in pixels of the Texture to calculate the for. + /// The data buffer to upload to the Texture. + /// A pointer to the data buffer to upload to the Texture. + /// A structure describing the data and its memory layout. + /// + /// The length of and data type are incorrect for + /// the specified size and pixel . + /// + private static unsafe DataBox GetDataBox(PixelFormat format, int width, int height, int depth, TData[] textureData, IntPtr fixedPointer) where TData : unmanaged { - // Check that the textureData size is correct - if (textureData == null) throw new ArgumentNullException("textureData"); + ArgumentNullException.ThrowIfNull(textureData); + + // Check that the textureData has the correct size for the Texture's data Image.ComputePitch(format, width, height, out var rowPitch, out var slicePitch, out _, out _); - if (Unsafe.SizeOf() * textureData.Length != (slicePitch * depth)) throw new ArgumentException("Invalid size for Image"); + + if (sizeof(TData) * textureData.Length != (slicePitch * depth)) + throw new ArgumentException("Invalid Texture data length", nameof(textureData)); return new DataBox(fixedPointer, rowPitch, slicePitch); } /// - /// Swaps the texture internal data with the other texture. + /// Swaps the Texture's internal data with another Texture. /// - /// The other texture. + /// The other Texture. internal void Swap([NotNull] Texture other) { - Utilities.Swap(ref textureDescription, ref other.textureDescription); - Utilities.Swap(ref textureViewDescription, ref other.textureViewDescription); - Utilities.Swap(ref mipmapDescriptions, ref other.mipmapDescriptions); - Utilities.Swap(ref fullQualitySize, ref other.fullQualitySize); - - var temp = ViewWidth; - ViewWidth = other.ViewWidth; - other.ViewWidth = temp; + (textureDescription, other.textureDescription) = (other.textureDescription, textureDescription); + (textureViewDescription, other.textureViewDescription) = (other.textureViewDescription, textureViewDescription); + (mipmapDescriptions, other.mipmapDescriptions) = (other.mipmapDescriptions, mipmapDescriptions); + (fullQualitySize, other.fullQualitySize) = (other.fullQualitySize, fullQualitySize); - temp = ViewHeight; - ViewHeight = other.ViewHeight; - other.ViewHeight = temp; - - temp = ViewDepth; - ViewDepth = other.ViewDepth; - other.ViewDepth = temp; - - temp = SizeInBytes; - SizeInBytes = other.SizeInBytes; - other.SizeInBytes = temp; + (other.ViewWidth, ViewWidth) = (ViewWidth, other.ViewWidth); + (other.ViewHeight, ViewHeight) = (ViewHeight, other.ViewHeight); + (other.ViewDepth, ViewDepth) = (ViewDepth, other.ViewDepth); + (other.SizeInBytes, SizeInBytes) = (SizeInBytes, other.SizeInBytes); SwapInternal(other); } + /// + /// Swaps the Texture's internal data with another Texture. + /// + /// The other Texture. + internal partial void SwapInternal(Texture other); + + /// + /// Computes the bounds of a Texture View based on the View type, which determines what sub-Resources + /// the Texture View can access. + /// + /// The View type. + /// + /// The index of the selected array slice (if a Texture Array or Cube) or depth slice (if a three-dimensional Texture). + /// When this method returns, it will contain the adjusted index based on the . + /// + /// + /// The index of the selected mip-level. + /// When this method returns, it will contain the adjusted index based on the . + /// + /// + /// When this method returns, it will contain the number of the selected array slices (if a Texture Array or Cube) + /// or depth slices (if a three-dimensional Texture). + /// + /// + /// When this method returns, it will contain the number of the selected mip-levels. + /// + /// internal void GetViewSliceBounds(ViewType viewType, ref int arrayOrDepthIndex, ref int mipIndex, out int arrayOrDepthCount, out int mipCount) { - int arrayOrDepthSize = this.Depth > 1 ? this.Depth : this.ArraySize; + int arrayOrDepthSize = Depth > 1 ? Depth : ArraySize; switch (viewType) { + // Array slice + // 0 1 2 + // ┌───┬───┬───┐ + // 0 │ ▓ │ ▓ │ ▓ │ ■ = Selected + // M ├───┼───┼───┤ □ = Not selected + // i 1 │ ▓ │ ▓ │ ▓ │ + // p ├───┼───┼───┤ + // 2 │ ▓ │ ▓ │ ▓ │ + // └───┴───┴───┘ + // case ViewType.Full: arrayOrDepthIndex = 0; mipIndex = 0; arrayOrDepthCount = arrayOrDepthSize; - mipCount = this.MipLevels; + mipCount = MipLevelCount; break; + + // Array slice + // 0 1 2 + // ┌───┬───┬───┐ + // 0 │ │ │ │ ■ = Selected + // M ├───┼───┼───┤ □ = Not selected + // i 1 │ │ ▓ │ │ + // p ├───┼───┼───┤ + // 2 │ │ │ │ + // └───┴───┴───┘ + // case ViewType.Single: arrayOrDepthCount = ViewDimension == TextureDimension.Texture3D ? CalculateMipSize(Depth, mipIndex) : 1; mipCount = 1; break; + + // Array slice + // 0 1 2 + // ┌───┬───┬───┐ + // 0 │ │ │ │ ■ = Selected + // M ├───┼───┼───┤ □ = Not selected + // i 1 │ │ ▓ │ ▓ │ + // p ├───┼───┼───┤ + // 2 │ │ │ │ + // └───┴───┴───┘ + // case ViewType.MipBand: arrayOrDepthCount = arrayOrDepthSize - arrayOrDepthIndex; mipCount = 1; break; + + // Array slice + // 0 1 2 + // ┌───┬───┬───┐ + // 0 │ │ │ │ ■ = Selected + // M ├───┼───┼───┤ □ = Not selected + // i 1 │ │ ▓ │ │ + // p ├───┼───┼───┤ + // 2 │ │ ▓ │ │ + // └───┴───┴───┘ + // case ViewType.ArrayBand: arrayOrDepthCount = 1; - mipCount = MipLevels - mipIndex; + mipCount = MipLevelCount - mipIndex; break; + default: arrayOrDepthCount = 0; mipCount = 0; @@ -1325,29 +1927,58 @@ internal void GetViewSliceBounds(ViewType viewType, ref int arrayOrDepthIndex, r } } + /// + /// Returns the number of Views the Texture can have. + /// + /// The number of Views the Texture can have. internal int GetViewCount() { - int arrayOrDepthSize = this.Depth > 1 ? this.Depth : this.ArraySize; - return GetViewIndex((ViewType)4, arrayOrDepthSize, this.MipLevels); + // TODO: This is unused and internal. Should it be kept? + + int arrayOrDepthSize = Depth > 1 ? Depth : ArraySize; + int viewIndex = (4 * arrayOrDepthSize + arrayOrDepthSize) * MipLevelCount + MipLevelCount; + + return viewIndex; } + /// + /// Returns the sub-Resource index for a view of a specific type. + /// + /// The View type, indicating which sub-Resources the View can select. + /// The depth or array slice index. + /// The mip-level index. + /// The index of the View. internal int GetViewIndex(ViewType viewType, int arrayOrDepthIndex, int mipIndex) { - int arrayOrDepthSize = this.Depth > 1 ? this.Depth : this.ArraySize; - return (((int)viewType) * arrayOrDepthSize + arrayOrDepthIndex) * this.MipLevels + mipIndex; + // TODO: This is unused and internal. Should it be kept? + + int arrayOrDepthSize = Depth > 1 ? Depth : ArraySize; + + return (((int) viewType) * arrayOrDepthSize + arrayOrDepthIndex) * MipLevelCount + mipIndex; } + /// + /// Determines the correct for the provided Texture flags combination. + /// + /// The current Texture's intended usage. + /// The Texture's flags. + /// The adjusted Texture flags. internal static GraphicsResourceUsage GetUsageWithFlags(GraphicsResourceUsage usage, TextureFlags flags) { - // If we have a texture supporting render target or unordered access, force to UsageDefault - if ((flags & TextureFlags.RenderTarget) != 0 || (flags & TextureFlags.UnorderedAccess) != 0) - return GraphicsResourceUsage.Default; - return usage; + // If we have a Texture supporting Render Target View or Unordered Access View, force GraphicsResourceUsage.Default + return flags.HasFlag(TextureFlags.RenderTarget) || flags.HasFlag(TextureFlags.UnorderedAccess) + ? GraphicsResourceUsage.Default + : usage; } - internal int ComputeSubresourceSize(int subresource) + /// + /// Computes the size of a specific Texture's sub-Resource. + /// + /// The index of the sub-Resource. + /// The size of the sub-Resource, in bytes. + internal int ComputeSubResourceSize(int subResourceIndex) { - var mipLevel = subresource % MipLevels; + var mipLevel = subResourceIndex % MipLevelCount; var slicePitch = ComputeSlicePitch(mipLevel); var depth = CalculateMipSize(Description.Depth, mipLevel); @@ -1355,46 +1986,76 @@ internal int ComputeSubresourceSize(int subresource) return (slicePitch * depth + TextureSubresourceAlignment - 1) / TextureSubresourceAlignment * TextureSubresourceAlignment; } - internal int ComputeBufferOffset(int subresource, int depthSlice) + /// + /// Computes the offset of a specific sub-Resource in the Texture's data buffer. + /// + /// The index of the sub-Resource. + /// The depth slice. + /// The offset of the sub-Resource at , in bytes. + internal int ComputeBufferOffset(int subResourceIndex, int depthSlice) { int offset = 0; - for (var i = 0; i < subresource; ++i) + for (var i = 0; i < subResourceIndex; ++i) { - offset += ComputeSubresourceSize(i); + offset += ComputeSubResourceSize(i); } if (depthSlice != 0) - offset += ComputeSlicePitch(subresource % Description.MipLevels) * depthSlice; + offset += ComputeSlicePitch(subResourceIndex % Description.MipLevelCount) * depthSlice; return offset; } + /// + /// Computes the slice pitch for a specific mip-level, i.e. the number of bytes a depth slice of that mip-level occupies in memory, + /// including memory alignment considerations. + /// + /// The mip-level index. + /// The slice pitch of the , in bytes. internal int ComputeSlicePitch(int mipLevel) { return ComputeRowPitch(mipLevel) * CalculateMipSize(Height, mipLevel); } + /// + /// Computes the row pitch for a specific mip-level, i.e. the number of bytes a row of that mip-level occupies in memory, + /// including memory alignment considerations. + /// + /// The mip-level index. + /// The row pitch of the , in bytes. internal int ComputeRowPitch(int mipLevel) { // Round up to 256 + // TODO: Stale comment? return ((CalculateMipSize(Width, mipLevel) * TexturePixelSize) + TextureRowPitchAlignment - 1) / TextureRowPitchAlignment * TextureRowPitchAlignment; } + /// + /// Computes the total size of the Texture, taking into account all mip-levels and array slices. + /// + /// The total size of the Texture. internal int ComputeBufferTotalSize() { - int result = 0; + int totalSize = 0; - for (int i = 0; i < Description.MipLevels; ++i) + for (int i = 0; i < Description.MipLevelCount; ++i) { - result += ComputeSubresourceSize(i); + totalSize += ComputeSubResourceSize(i); } - return result * Description.ArraySize; + return totalSize * Description.ArraySize; } + /// + /// Counts the number of mip-levels a Texture with the specified size can have. + /// + /// The width of the Texture, in pixels. + /// The maximum number of mip-levels for the given size. public static int CountMips(int width) { + // TODO: Efficient calculation without loop. Lzcnt? + int mipLevels = 1; while (width > 1) @@ -1407,14 +2068,35 @@ public static int CountMips(int width) return mipLevels; } + /// + /// Counts the number of mip-levels a Texture with the specified size can have. + /// + /// The width of the Texture, in pixels. + /// The height of the Texture, in pixels. + /// The maximum number of mip-levels for the given size. public static int CountMips(int width, int height) { - return CountMips(Math.Max(width, height)); + var largestDimension = Math.Max(width, height); + return CountMips(largestDimension); } + /// + /// Counts the number of mip-levels a Texture with the specified size can have. + /// + /// The width of the Texture, in pixels. + /// The height of the Texture, in pixels. + /// The depth of the Texture, in pixels. + /// The maximum number of mip-levels for the given size. public static int CountMips(int width, int height, int depth) { - return CountMips(Math.Max(width, Math.Max(height, depth))); + var largestDimension = Math.Max(width, Math.Max(height, depth)); + return CountMips(largestDimension); } + + /// + /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. + /// + /// if the Texture is flipped; otherwise. + private partial bool IsFlipped(); } } diff --git a/sources/engine/Stride.Graphics/TextureDescription.Extensions1D.cs b/sources/engine/Stride.Graphics/TextureDescription.Extensions1D.cs index 44a707d21c..140a23e5bc 100644 --- a/sources/engine/Stride.Graphics/TextureDescription.Extensions1D.cs +++ b/sources/engine/Stride.Graphics/TextureDescription.Extensions1D.cs @@ -1,68 +1,107 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +public partial struct TextureDescription { - public partial struct TextureDescription + /// + /// Creates a new for a one-dimensional (1D) with a single mipmap. + /// + /// The width of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a one-dimensional Texture. + public static TextureDescription New1D(int width, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - /// - /// Creates a new 1D with a single mipmap. - /// - /// The width. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// A new instance of 1D class. - public static TextureDescription New1D(int width, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New1D(width, false, format, textureFlags, arraySize, usage); - } + return New1D(width, MipMapCount.One, format, textureFlags, arraySize, usage); + } - /// - /// Creates a new 1D . - /// - /// The width. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// A new instance of 1D class. - public static TextureDescription New1D(int width, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New1D(width, format, textureFlags, mipCount, arraySize, usage); - } + /// + /// Creates a new for a one-dimensional (1D) . + /// + /// The width of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a one-dimensional Texture. + public static TextureDescription New1D(int width, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + return New1D(width, format, textureFlags, mipCount, arraySize, usage); + } - /// - /// Creates a new 1D with a single level of mipmap. - /// - /// The width. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// The usage. - /// A new instance of 1D class. - /// The first dimension of mipMapTextures describes the number of array (Texture1D Array), second dimension is the mipmap, the third is the texture data for a particular mipmap. - public static TextureDescription New1D(int width, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) - { - return New1D(width, format, textureFlags, 1, 1, usage); - } + /// + /// Creates a new for a one-dimensional (1D) with a single mipmap. + /// + /// The width of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a one-dimensional Texture. + public static TextureDescription New1D(int width, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Immutable) + { + return New1D(width, format, textureFlags, mipCount: 1, arraySize: 1, usage); + } + + private static TextureDescription New1D(int width, PixelFormat format, TextureFlags textureFlags, int mipCount, int arraySize, GraphicsResourceUsage usage) + { + if (textureFlags.HasFlag(TextureFlags.UnorderedAccess)) + usage = GraphicsResourceUsage.Default; - private static TextureDescription New1D(int width, PixelFormat format, TextureFlags flags, int mipCount, int arraySize, GraphicsResourceUsage usage) + var desc = new TextureDescription { - usage = (flags & TextureFlags.UnorderedAccess) != 0 ? GraphicsResourceUsage.Default : usage; - var desc = new TextureDescription() - { - Dimension = TextureDimension.Texture1D, - Width = width, - Height = 1, - Depth = 1, - ArraySize = arraySize, - Flags = flags, - Format = format, - MipLevels = Texture.CalculateMipMapCount(mipCount, width), - Usage = Texture.GetUsageWithFlags(usage, flags), - }; - return desc; - } + Dimension = TextureDimension.Texture1D, + Width = width, + Height = 1, + Depth = 1, + ArraySize = arraySize, + Flags = textureFlags, + Format = format, + MipLevelCount = Texture.CalculateMipMapCount(mipCount, width), + Usage = Texture.GetUsageWithFlags(usage, textureFlags) + }; + return desc; } } diff --git a/sources/engine/Stride.Graphics/TextureDescription.Extensions2D.cs b/sources/engine/Stride.Graphics/TextureDescription.Extensions2D.cs index dd5ec4f1ca..d8aec597bd 100644 --- a/sources/engine/Stride.Graphics/TextureDescription.Extensions2D.cs +++ b/sources/engine/Stride.Graphics/TextureDescription.Extensions2D.cs @@ -1,61 +1,103 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +public partial struct TextureDescription { - public partial struct TextureDescription + /// + /// Creates a new for a two-dimensional (2D) with a single mipmap. + /// + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// + /// A combination of flags indicating options about the creation of the Texture, like creating it as a shared resource. + /// The default value is . + /// + /// A new description for a two-dimensional Texture. + public static TextureDescription New2D(int width, int height, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, TextureOptions textureOptions = TextureOptions.None) { - /// - /// Creates a new with a single mipmap. - /// - /// The width. - /// The height. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// A new instance of class. - public static TextureDescription New2D(int width, int height, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, TextureOptions textureOptions = TextureOptions.None) - { - return New2D(width, height, false, format, textureFlags, arraySize, usage, MultisampleCount.None, textureOptions); - } + return New2D(width, height, MipMapCount.One, format, textureFlags, arraySize, usage, MultisampleCount.None, textureOptions); + } - /// - /// Creates a new . - /// - /// The width. - /// The height. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// Size of the texture 2D array, default to 1. - /// The usage. - /// The multisample count. - /// A new instance of class. - public static TextureDescription New2D(int width, int height, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, MultisampleCount multisampleCount = MultisampleCount.None, TextureOptions textureOptions = TextureOptions.None) - { - return New2D(width, height, format, textureFlags, mipCount, arraySize, usage, multisampleCount, textureOptions); - } + /// + /// Creates a new for a two-dimensional (2D) . + /// + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// The number of array slices for the Texture. + /// The default value is 1, indicating the Texture is not a Texture Array. + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// + /// The number of samples per texel the Texture will have. + /// The default value is , indicating a non-multisampled Texture. + /// + /// + /// A combination of flags indicating options about the creation of the Texture, like creating it as a shared resource. + /// The default value is . + /// + /// A new description for a two-dimensional Texture. + public static TextureDescription New2D(int width, int height, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, int arraySize = 1, GraphicsResourceUsage usage = GraphicsResourceUsage.Default, MultisampleCount multisampleCount = MultisampleCount.None, TextureOptions textureOptions = TextureOptions.None) + { + return New2D(width, height, format, textureFlags, mipCount, arraySize, usage, multisampleCount, textureOptions); + } - private static TextureDescription New2D(int width, int height, PixelFormat format, TextureFlags textureFlags, int mipCount, int arraySize, GraphicsResourceUsage usage, MultisampleCount multisampleCount, TextureOptions textureOptions = TextureOptions.None) - { - if ((textureFlags & TextureFlags.UnorderedAccess) != 0) - usage = GraphicsResourceUsage.Default; + private static TextureDescription New2D(int width, int height, PixelFormat format, TextureFlags textureFlags, int mipCount, int arraySize, GraphicsResourceUsage usage, MultisampleCount multisampleCount, TextureOptions textureOptions = TextureOptions.None) + { + if (textureFlags.HasFlag(TextureFlags.UnorderedAccess)) + usage = GraphicsResourceUsage.Default; - var desc = new TextureDescription - { - Dimension = TextureDimension.Texture2D, - Width = width, - Height = height, - Depth = 1, - ArraySize = arraySize, - MultisampleCount = multisampleCount, - Flags = textureFlags, - Format = format, - MipLevels = Texture.CalculateMipMapCount(mipCount, width, height), - Usage = Texture.GetUsageWithFlags(usage, textureFlags), - Options = textureOptions - }; - return desc; - } + var desc = new TextureDescription + { + Dimension = TextureDimension.Texture2D, + Width = width, + Height = height, + Depth = 1, + ArraySize = arraySize, + MultisampleCount = multisampleCount, + Flags = textureFlags, + Format = format, + MipLevelCount = Texture.CalculateMipMapCount(mipCount, width, height), + Usage = Texture.GetUsageWithFlags(usage, textureFlags), + Options = textureOptions + }; + return desc; } } diff --git a/sources/engine/Stride.Graphics/TextureDescription.Extensions3D.cs b/sources/engine/Stride.Graphics/TextureDescription.Extensions3D.cs index a064217bc4..7364b585d9 100644 --- a/sources/engine/Stride.Graphics/TextureDescription.Extensions3D.cs +++ b/sources/engine/Stride.Graphics/TextureDescription.Extensions3D.cs @@ -1,57 +1,81 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +public partial struct TextureDescription { - public partial struct TextureDescription + /// + /// Creates a new for a three-dimensional (3D) with a single mipmap. + /// + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The depth of the Texture in texels. + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a three-dimensional Texture. + public static TextureDescription New3D(int width, int height, int depth, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - /// - /// Creates a new with a single mipmap. - /// - /// The width. - /// The height. - /// The depth. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// The usage. - /// A new instance of class. - public static TextureDescription New3D(int width, int height, int depth, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New3D(width, height, depth, false, format, textureFlags, usage); - } + return New3D(width, height, depth, MipMapCount.One, format, textureFlags, usage); + } - /// - /// Creates a new . - /// - /// The width. - /// The height. - /// The depth. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// true if the texture needs to support unordered read write. - /// The usage. - /// A new instance of class. - public static TextureDescription New3D(int width, int height, int depth, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return New3D(width, height, depth, format, textureFlags, mipCount, usage); - } + /// + /// Creates a new for a three-dimensional (3D) . + /// + /// The width of the Texture in texels. + /// The height of the Texture in texels. + /// The depth of the Texture in texels. + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a three-dimensional Texture. + public static TextureDescription New3D(int width, int height, int depth, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + return New3D(width, height, depth, format, textureFlags, mipCount, usage); + } - private static TextureDescription New3D(int width, int height, int depth, PixelFormat format, TextureFlags flags, int mipCount, GraphicsResourceUsage usage) + private static TextureDescription New3D(int width, int height, int depth, PixelFormat format, TextureFlags flags, int mipCount, GraphicsResourceUsage usage) + { + var desc = new TextureDescription { - var desc = new TextureDescription() - { - Width = width, - Height = height, - Depth = depth, - Flags = flags, - Format = format, - MipLevels = Texture.CalculateMipMapCount(mipCount, width, height, depth), - Usage = Texture.GetUsageWithFlags(usage, flags), - ArraySize = 1, - Dimension = TextureDimension.Texture3D, - MultisampleCount = MultisampleCount.None, - }; - - return desc; - } + Width = width, + Height = height, + Depth = depth, + Flags = flags, + Format = format, + MipLevelCount = Texture.CalculateMipMapCount(mipCount, width, height, depth), + Usage = Texture.GetUsageWithFlags(usage, flags), + ArraySize = 1, + Dimension = TextureDimension.Texture3D, + MultisampleCount = MultisampleCount.None + }; + return desc; } } diff --git a/sources/engine/Stride.Graphics/TextureDescription.ExtensionsCube.cs b/sources/engine/Stride.Graphics/TextureDescription.ExtensionsCube.cs index 48cf993359..6450153d4e 100644 --- a/sources/engine/Stride.Graphics/TextureDescription.ExtensionsCube.cs +++ b/sources/engine/Stride.Graphics/TextureDescription.ExtensionsCube.cs @@ -1,41 +1,73 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +public partial struct TextureDescription { - public partial struct TextureDescription + /// + /// Creates a new for a cube-map composed of six two-dimensional (2D) s + /// with a single mipmap. + /// + /// + /// The size in texels of the faces of the cube Texture. + /// As the Texture is a cube, this single value specifies both the width and the height. + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a cube-map Texture. + public static TextureDescription NewCube(int size, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) { - /// - /// Creates a new Cube . - /// - /// The size (in pixels) of the top-level faces of the cube texture. - /// Describes the format to use. - /// The texture flags. - /// The usage. - /// A new instance of class. - public static TextureDescription NewCube(int size, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return NewCube(size, false, format, textureFlags, usage); - } + return NewCube(size, MipMapCount.One, format, textureFlags, usage); + } - /// - /// Creates a new Cube . - /// - /// The size (in pixels) of the top-level faces of the cube texture. - /// Number of mipmaps, set to true to have all mipmaps, set to an int >=1 for a particular mipmap count. - /// Describes the format to use. - /// The texture flags. - /// The usage. - /// A new instance of class. - public static TextureDescription NewCube(int size, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) - { - return NewCube(size, format, textureFlags, mipCount, usage); - } + /// + /// Creates a new for a cube-map composed of six two-dimensional (2D) s. + /// + /// + /// The size in texels of the faces of the cube Texture. + /// As the Texture is a cube, this single value specifies both the width and the height. + /// + /// + /// + /// A structure describing the number of mipmaps for the Texture. + /// Specify to have all mipmaps, or + /// to indicate a single mipmap, or + /// any number greater than 1 for a particular mipmap count. + /// + /// + /// You can also specify a number (which will be converted implicitly) or a . + /// See for more information about accepted values. + /// + /// + /// The format to use. + /// + /// A combination of flags determining what kind of Texture and how the is should behave + /// (i.e. how it is bound, how can it be read / written, etc.). + /// By default, it is . + /// + /// + /// A combination of flags determining how the Texture will be used during rendering. + /// The default is , meaning it will need read/write access by the GPU. + /// + /// A new description for a cube-map Texture. + public static TextureDescription NewCube(int size, MipMapCount mipCount, PixelFormat format, TextureFlags textureFlags = TextureFlags.ShaderResource, GraphicsResourceUsage usage = GraphicsResourceUsage.Default) + { + return NewCube(size, format, textureFlags, mipCount, usage); + } - private static TextureDescription NewCube(int size, PixelFormat format, TextureFlags textureFlags, int mipCount, GraphicsResourceUsage usage) - { - var desc = New2D(size, size, format, textureFlags, mipCount, 6, usage, MultisampleCount.None); - desc.Dimension = TextureDimension.TextureCube; - return desc; - } + private static TextureDescription NewCube(int size, PixelFormat format, TextureFlags textureFlags, int mipCount, GraphicsResourceUsage usage) + { + var desc = New2D(size, size, format, textureFlags, mipCount, arraySize: 6, usage, MultisampleCount.None); + desc.Dimension = TextureDimension.TextureCube; + return desc; } } diff --git a/sources/engine/Stride.Graphics/TextureDescription.cs b/sources/engine/Stride.Graphics/TextureDescription.cs index b9608a073c..d134890c1e 100644 --- a/sources/engine/Stride.Graphics/TextureDescription.cs +++ b/sources/engine/Stride.Graphics/TextureDescription.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,277 +24,294 @@ using System; using System.Runtime.InteropServices; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A structure providing a common description for all kinds of s. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct TextureDescription : IEquatable { /// - /// A Common description for all textures. + /// The dimension (type) of the Texture. /// - [StructLayout(LayoutKind.Sequential)] - public partial struct TextureDescription : IEquatable - { - /// - /// The dimension of a texture. - /// - public TextureDimension Dimension; + public TextureDimension Dimension; - /// - ///

Texture width (in texels). The range is from 1 to (16384). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.

- ///
- /// - /// This field is valid for all textures: , , and . - /// - public int Width; + /// + /// The Texture width in texels. + /// + /// + /// + /// The range of this value is from 1 to the maximum supported size, which is constrained by the graphics profile of the device. + /// + /// + /// This field is valid for all textures: , , , + /// and . + /// + /// + /// + /// + /// + /// + public int Width; - /// - ///

Texture height (in texels). The range is from 1 to (2048). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.

- ///
- /// - /// This field is only valid for , and . - /// - public int Height; + /// + /// The Texture height in texels. + /// + /// + /// + /// The range of this value is from 1 to the maximum supported size, which is constrained by the graphics profile of the device. + /// + /// + /// This field is valid for , , and . + /// + /// + /// + /// + /// + public int Height; - /// - ///

Texture depth (in texels). The range is from 1 to (2048). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.

- ///
- /// - /// This field is only valid for . - /// - public int Depth; + /// + /// The Texture depth in texels. + /// + /// + /// + /// The range of this value is from 1 to the maximum supported size, which is constrained by the graphics profile of the device. + /// + /// + /// This field is valid for . + /// + /// + /// + public int Depth; - /// - ///

Number of textures in the array. The range is from 1 to (2048). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.

- ///
- /// - /// This field is only valid for , and - /// - /// - /// This field is only valid for textures: , and . - /// - public int ArraySize; + /// + /// The number of Textures in the Texture Array. + /// + /// + /// + /// The range of this value is from 1 to the maximum supported array size, which is constrained by the graphics profile of the device. + /// + /// + /// This field is valid for , , and . + /// + /// + /// + /// + public int ArraySize; - /// - ///

The maximum number of mipmap levels in the texture. See the remarks in . Use 1 for a multisampled texture; or 0 to generate a full set of subtextures.

- ///
- public int MipLevels; + /// + /// The number of mipmap levels in the Texture. + /// + /// + /// + /// The range of this value is from 1 to , which is constrained by the graphics profile of the device. + /// + /// + /// Use 1 for a multisampled Texture; or 0 to generate a full set of subtextures. + /// + /// + /// + public int MipLevelCount; - /// - ///

Texture format (see ).

- ///
- public PixelFormat Format; + /// + /// The format of the Texture. + /// + public PixelFormat Format; - /// - ///

Structure that specifies multisampling parameters for the texture. See .

- ///
- /// - /// This field is only valid for . - /// - public MultisampleCount MultisampleCount; + /// + /// The level of multisampling for the Texture. + /// + /// + /// This field is only valid for . + /// + public MultisampleCount MultisampleCount; - /// - ///

Value that identifies how the texture is to be read from and written to. The most common value is ; see for all possible values.

- ///
- public GraphicsResourceUsage Usage; + /// + /// A value that indicates how the Texture is to be read from and written to. + /// + /// + /// The most common value is . + /// + public GraphicsResourceUsage Usage; - /// - ///

Flags (see ) for binding to pipeline stages. The flags can be combined by a logical OR. For a 1D texture, the allowable values are: , and .

- ///
- public TextureFlags Flags; + /// + /// A combination of flags describing how the Texture is to be bound to the stages of the graphics pipeline. + /// + public TextureFlags Flags; - /// - /// Resource options for DirectX 11 textures. - /// - public TextureOptions Options; + /// + /// A combination of flags specifying options for Textures, like creating them as shared resources. + /// + /// + /// This field must be when creating Textures with CPU access flags. + /// + public TextureOptions Options; - /// - /// Gets a value indicating whether this instance is a render target. - /// - /// true if this instance is render target; otherwise, false. - public bool IsRenderTarget - { - get - { - return (Flags & TextureFlags.RenderTarget) != 0; - } - } + /// + /// Gets a value indicating whether the Texture is a Render Target. + /// + /// if the Texture is a Render Target; otherwise, . + public readonly bool IsRenderTarget => Flags.HasFlag(TextureFlags.RenderTarget); - /// - /// Gets a value indicating whether this instance is a depth stencil. - /// - /// true if this instance is a depth stencil; otherwise, false. - public bool IsDepthStencil - { - get - { - return (Flags & TextureFlags.DepthStencil) != 0; - } - } + /// + /// Gets a value indicating whether the Texture is a Depth-Stencil buffer. + /// + /// if the Texture is a Depth-Stencil buffer; otherwise, . + public readonly bool IsDepthStencil => Flags.HasFlag(TextureFlags.DepthStencil); - /// - /// Gets a value indicating whether this instance is a shader resource. - /// - /// true if this instance is a shader resource; otherwise, false. - public bool IsShaderResource - { - get - { - return (Flags & TextureFlags.ShaderResource) != 0; - } - } + /// + /// Gets a value indicating whether the Texture is a Shader Resource. + /// + /// if the Texture is a Shader Resource; otherwise, . + public readonly bool IsShaderResource => Flags.HasFlag(TextureFlags.ShaderResource); - /// - /// Gets a value indicating whether this instance is a shader resource. - /// - /// true if this instance is a shader resource; otherwise, false. - public bool IsUnorderedAccess - { - get - { - return (Flags & TextureFlags.UnorderedAccess) != 0; - } - } + /// + /// Gets a value indicating whether the Texture is a created to allow Unordered Access + /// when used as Shader Resource. + /// + /// if the Texture allows Unordered Access; otherwise, . + public readonly bool IsUnorderedAccess => Flags.HasFlag(TextureFlags.UnorderedAccess); - /// - /// Gets a value indicating whether this instance is a multi sample texture. - /// - /// true if this instance is multi sample texture; otherwise, false. - public bool IsMultisample - { - get - { - return this.MultisampleCount > MultisampleCount.None; - } - } + /// + /// Gets a value indicating whether the Texture is a multi-sampled Texture. + /// + /// if the Texture is multi-sampled; otherwise, . + public readonly bool IsMultiSampled => MultisampleCount > MultisampleCount.None; - /// - /// Gets the staging description for this instance.. - /// - /// A Staging description - public TextureDescription ToStagingDescription() + /// + /// Returns a copy of this Texture Description modified to describe a staging Texture. + /// + /// A staging Texture Description. + public readonly TextureDescription ToStagingDescription() + { + return this with { - var copy = this; - copy.Flags = TextureFlags.None; - copy.Usage = GraphicsResourceUsage.Staging; - return copy; - } + Flags = TextureFlags.None, + Usage = GraphicsResourceUsage.Staging + }; + } - /// - /// Gets a clone description of this instance (if texture is immutable, it is switched to default). - /// - /// A clone of this instance. - public TextureDescription ToCloneableDescription() + /// + /// Returns a copy of this Texture Description modified so if the Texture is , + /// it is switched to . + /// + /// A modified copy of this Texture Description. + public readonly TextureDescription ToCloneableDescription() + { + return this with { - var description = this; - if (description.Usage == GraphicsResourceUsage.Immutable) - description.Usage = GraphicsResourceUsage.Default; - return description; - } + Usage = Usage is GraphicsResourceUsage.Immutable ? GraphicsResourceUsage.Default : Usage + }; + } - /// - /// Creates a new description from another description but overrides and . - /// - /// The desc. - /// The texture flags. - /// The usage. - /// TextureDescription. - public static TextureDescription FromDescription(TextureDescription desc, TextureFlags textureFlags, GraphicsResourceUsage usage) - { - desc.Flags = textureFlags; - desc.Usage = usage; - if ((textureFlags & TextureFlags.UnorderedAccess) != 0) - desc.Usage = GraphicsResourceUsage.Default; - return desc; - } - /// - /// Performs an explicit conversion from to . - /// - /// The image description. - /// The result of the conversion. - public static implicit operator TextureDescription(ImageDescription description) + /// + /// Creates a new description from another description but overrides and . + /// + /// The Texture Description to copy. + /// The new Texture flags. + /// The new usage. + /// A modified copy of the specified . + public static TextureDescription FromDescription(TextureDescription description, TextureFlags textureFlags, GraphicsResourceUsage usage) + { + return description with { - return new TextureDescription() - { - Dimension = description.Dimension, - Width = description.Width, - Height = description.Height, - Depth = description.Depth, - ArraySize = description.ArraySize, - MipLevels = description.MipLevels, - Format = description.Format, - Flags = TextureFlags.ShaderResource, - MultisampleCount = MultisampleCount.None, - }; - } + Flags = textureFlags, + Usage = textureFlags.HasFlag(TextureFlags.UnorderedAccess) + ? GraphicsResourceUsage.Default + : usage + }; + } - /// - /// Performs an explicit conversion from to . - /// - /// The image description. - /// The result of the conversion. - public static implicit operator ImageDescription(TextureDescription description) + /// + /// Performs an explicit conversion from to . + /// + /// The Image Description to convert. + /// The result of the conversion. + public static implicit operator TextureDescription(ImageDescription description) + { + return new TextureDescription { - return new ImageDescription() - { - Dimension = description.Dimension, - Width = description.Width, - Height = description.Height, - Depth = description.Depth, - ArraySize = description.ArraySize, - MipLevels = description.MipLevels, - Format = description.Format, - }; - } + Dimension = description.Dimension, + Width = description.Width, + Height = description.Height, + Depth = description.Depth, + ArraySize = description.ArraySize, + MipLevelCount = description.MipLevels, + Format = description.Format, + Flags = TextureFlags.ShaderResource, + MultisampleCount = MultisampleCount.None + }; + } - public bool Equals(TextureDescription other) + /// + /// Performs an implicit conversion from to . + /// + /// The Texture Description to convert. + /// The result of the conversion. + public static implicit operator ImageDescription(TextureDescription description) + { + return new ImageDescription { - return Dimension == other.Dimension && Width == other.Width && Height == other.Height && Depth == other.Depth && ArraySize == other.ArraySize && MipLevels == other.MipLevels && Format == other.Format && MultisampleCount == other.MultisampleCount && Usage == other.Usage && Flags == other.Flags; - } + Dimension = description.Dimension, + Width = description.Width, + Height = description.Height, + Depth = description.Depth, + ArraySize = description.ArraySize, + MipLevels = description.MipLevelCount, + Format = description.Format + }; + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is TextureDescription && Equals((TextureDescription)obj); - } + /// + public readonly bool Equals(TextureDescription other) + { + return Dimension == other.Dimension + && Width == other.Width + && Height == other.Height + && Depth == other.Depth + && ArraySize == other.ArraySize + && MipLevelCount == other.MipLevelCount + && Format == other.Format + && MultisampleCount == other.MultisampleCount + && Usage == other.Usage + && Flags == other.Flags; + } - /// - public override int GetHashCode() - { - unchecked - { - int hashCode = (int)Dimension; - hashCode = (hashCode * 397) ^ Width; - hashCode = (hashCode * 397) ^ Height; - hashCode = (hashCode * 397) ^ Depth; - hashCode = (hashCode * 397) ^ ArraySize; - hashCode = (hashCode * 397) ^ MipLevels; - hashCode = (hashCode * 397) ^ (int)Format; - hashCode = (hashCode * 397) ^ (int)MultisampleCount; - hashCode = (hashCode * 397) ^ (int)Usage; - hashCode = (hashCode * 397) ^ (int)Flags; - return hashCode; - } - } + /// + public override readonly bool Equals(object obj) + { + if (obj is null) + return false; - /// - /// Implements the operator ==. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator ==(TextureDescription left, TextureDescription right) - { - return left.Equals(right); - } + return obj is TextureDescription description && Equals(description); + } - /// - /// Implements the operator !=. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator !=(TextureDescription left, TextureDescription right) - { - return !left.Equals(right); - } + /// + public override readonly int GetHashCode() + { + var hash = new HashCode(); + hash.Add(Dimension); + hash.Add(Width); + hash.Add(Height); + hash.Add(Depth); + hash.Add(ArraySize); + hash.Add(MipLevelCount); + hash.Add(Format); + hash.Add(MultisampleCount); + hash.Add(Usage); + hash.Add(Flags); + return hash.ToHashCode(); + } + + public static bool operator ==(TextureDescription left, TextureDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureDescription left, TextureDescription right) + { + return !left.Equals(right); } } diff --git a/sources/engine/Stride.Graphics/TextureFlags.cs b/sources/engine/Stride.Graphics/TextureFlags.cs index 67464030d4..89d10161d4 100644 --- a/sources/engine/Stride.Graphics/TextureFlags.cs +++ b/sources/engine/Stride.Graphics/TextureFlags.cs @@ -2,39 +2,39 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Flags describing how a can be bound to the graphics pipeline. +/// +[Flags] +public enum TextureFlags { - [Flags] - public enum TextureFlags - { - /// - /// No option. - /// - None = 0, + None = 0, - /// - /// A texture usable as a ShaderResourceView. - /// - ShaderResource = 1, + /// + /// The Texture can be used as a Shader Resource View, i.e. it can be bound to a shader stage. + /// + ShaderResource = 1, - /// - /// A texture usable as render target. - /// - RenderTarget = 2, // TODO: This naming is ambiguous. A render target can be a depth, stencil or color buffer. But we use "RenderTarget" synonymously with "ColorTarget". + /// + /// The Texture can be used as a Render Target View, i.e. it can be bound as the render target of the output-merger stage. + /// + RenderTarget = 2, // TODO: This naming is ambiguous. A render target can be a depth, stencil or color buffer. But we use "RenderTarget" synonymously with "ColorTarget". - /// - /// A texture usable as an unordered access buffer. - /// - UnorderedAccess = 4, + /// + /// The Texture can be used as an Unordered Access resource. + /// + UnorderedAccess = 4, - /// - /// A texture usable as a depth stencil buffer. - /// - DepthStencil = 8, + /// + /// The Texture can be used as a Depth-Stencil buffer, i.e. it can be bound as a render target where the output-merger stage + /// writes depth or stencil information. + /// + DepthStencil = 8, - /// - /// A texture usable as a readonly depth stencil buffer. - /// - DepthStencilReadOnly = 8 + Texture.DepthStencilReadOnlyFlags, - } + /// + /// The Texture can be used as a read-only Depth-Stencil buffer. + /// + DepthStencilReadOnly = DepthStencil | Texture.DepthStencilReadOnlyFlags } diff --git a/sources/engine/Stride.Graphics/TextureOptions.cs b/sources/engine/Stride.Graphics/TextureOptions.cs index c0c9182fe4..f92ddb42ca 100644 --- a/sources/engine/Stride.Graphics/TextureOptions.cs +++ b/sources/engine/Stride.Graphics/TextureOptions.cs @@ -1,85 +1,72 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Flags describing resource options for a . +/// +/// +/// This enumeration is used in . +/// +[Flags] +public enum TextureOptions { + None = 0, + /// - /// Resource options for textures. + /// Enables data sharing between two or more Direct3D devices. /// /// - /// This enumeration is used in TextureDescription.The TextureOptions - /// must be 'None' when creating textures with CPU access flags. + /// + /// The only resources that can be shared are 2D non-mipmapped Textures. + /// + /// + /// and are mutually exclusive. + /// + /// + /// Note that, starting with Windows 8, it is recommended to enable resource data sharing between two or more Direct3D devices + /// by using a combination of the and flags instead. + /// /// - [Flags] - public enum TextureOptions - { - /// - /// None. The default. - /// - None = 0, + Shared = 2, - /// - /// Enables resource data sharing between two or more Direct3D devices. - /// - /// - /// The only resources that can be shared are 2D non-mipmapped textures. SharpDX.Direct3D11.ResourceOptionFlags.Shared - /// and SharpDX.Direct3D11.ResourceOptionFlags.SharedKeyedmutex are mutually exclusive. - /// WARP and REF devices do not support shared resources. If you try to create a - /// resource with this flag on either a WARP or REF device, the create method will - /// return an E_OUTOFMEMORY error code. Note?? Starting with Windows?8, WARP devices - /// fully support shared resources. ? Note?? Starting with Windows?8, we recommend - /// that you enable resource data sharing between two or more Direct3D devices by - /// using a combination of the SharpDX.Direct3D11.ResourceOptionFlags.SharedNthandle - /// and SharpDX.Direct3D11.ResourceOptionFlags.SharedKeyedmutex flags instead. - /// - Shared = 2, #if STRIDE_GRAPHICS_API_DIRECT3D11 - /// - /// Enables the resource to be synchronized by using the SharpDX.DXGI.KeyedMutex.Acquire(System.Int64,System.Int32) - /// and SharpDX.DXGI.KeyedMutex.Release(System.Int64) APIs. - /// - /// - /// The following Direct3D 11 resource creation APIs, that take SharpDX.Direct3D11.ResourceOptionFlags parameters, - /// have been extended to support the new flag. SharpDX.Direct3D11.Device.CreateTexture1D(SharpDX.Direct3D11.Texture1DDescription@,SharpDX.DataBox[],SharpDX.Direct3D11.Texture1D) - /// SharpDX.Direct3D11.Device.CreateTexture2D(SharpDX.Direct3D11.Texture2DDescription@,SharpDX.DataBox[],SharpDX.Direct3D11.Texture2D) - /// SharpDX.Direct3D11.Device.CreateTexture3D(SharpDX.Direct3D11.Texture3DDescription@,SharpDX.DataBox[],SharpDX.Direct3D11.Texture3D) - /// SharpDX.Direct3D11.Device.CreateBuffer(SharpDX.Direct3D11.BufferDescription@,System.Nullable{SharpDX.DataBox},SharpDX.Direct3D11.Buffer) - /// If you call any of these methods with the SharpDX.Direct3D11.ResourceOptionFlags.SharedKeyedmutex - /// flag set, the interface returned will support the SharpDX.DXGI.KeyedMutex interface. - /// You can retrieve a reference to the SharpDX.DXGI.KeyedMutex interface from the - /// resource by using IUnknown::QueryInterface. The SharpDX.DXGI.KeyedMutex interface - /// implements the SharpDX.DXGI.KeyedMutex.Acquire(System.Int64,System.Int32) and - /// SharpDX.DXGI.KeyedMutex.Release(System.Int64) APIs to synchronize access to the - /// surface. The device that creates the surface, and any other device that opens - /// the surface by using OpenSharedResource, must call SharpDX.DXGI.KeyedMutex.Acquire(System.Int64,System.Int32) - /// before they issue any rendering commands to the surface. When those devices finish - /// rendering, they must call SharpDX.DXGI.KeyedMutex.Release(System.Int64). SharpDX.Direct3D11.ResourceOptionFlags.Shared - /// and SharpDX.Direct3D11.ResourceOptionFlags.SharedKeyedmutex are mutually exclusive. - /// WARP and REF devices do not support shared resources. If you try to create a - /// resource with this flag on either a WARP or REF device, the create method will - /// return an E_OUTOFMEMORY error code. Note?? Starting with Windows?8, WARP devices - /// fully support shared resources. - /// - SharedKeyedmutex = 256, + /// + /// Enables the Texture to be synchronized by using APIs. + /// + /// + /// + /// When using this flag when creating a Texture with a graphics device, any other device can open + /// the same Texture by using OpenSharedResource. Then they must obtain the KeyedMutex + /// from the Texture and acquire the mutex before they issue any rendering commands + /// to the Texture. When those devices finish rendering, they must release the mutex. + /// + /// + /// and are mutually exclusive. + /// + /// + SharedKeyedMutex = 256, // TODO: Support KeyedMutex from Texture - /// - /// Set this flag to enable the use of NT HANDLE values when you create a shared - /// resource. - /// - /// - /// By enabling this flag, you deprecate the use of existing HANDLE values. - /// When you use this flag, you must combine it with the SharpDX.Direct3D11.ResourceOptionFlags.SharedKeyedmutex - /// flag by using a bitwise OR operation. The resulting value specifies a new shared - /// resource type that directs the runtime to use NT HANDLE values for the shared - /// resource. The runtime then must confirm that the shared resource works on all - /// hardware at the specified feature level. Without this flag set, the runtime does - /// not strictly validate shared resource parameters (that is, formats, flags, usage, - /// and so on). When the runtime does not validate shared resource parameters, behavior - /// of much of the Direct3D API might be undefined and might vary from driver to - /// driver. Direct3D 11 and earlier: This value is not supported until Direct3D 11.1. - /// - SharedNthandle = 2048, + /// + /// Enable the use of NT HANDLE values when you create a shared Texture. + /// + /// + /// + /// When you use this flag, you must combine it with the + /// flag by using a bitwise OR operation. The resulting value specifies a new shared + /// resource type that directs the runtime to use NT HANDLE values for the shared + /// resource. The runtime then must confirm that the shared resource works on all + /// hardware at the specified graphics profile. Without this flag set, the runtime does + /// not strictly validate shared resource parameters (that is, formats, flags, usage, + /// and so on). + /// + /// + /// This flag is not supported until . + /// + /// + SharedNtHandle = 2048 #endif - } } diff --git a/sources/engine/Stride.Graphics/TextureViewDescription.cs b/sources/engine/Stride.Graphics/TextureViewDescription.cs index 00b59fe9a5..30e58d0c5e 100644 --- a/sources/engine/Stride.Graphics/TextureViewDescription.cs +++ b/sources/engine/Stride.Graphics/TextureViewDescription.cs @@ -1,46 +1,56 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Describes a View for a . +/// +public struct TextureViewDescription { /// - /// View description of a . + /// A combination of flags determining what kind of the View is attached to and + /// how it should behave (i.e. how it is bound, how can it be read / written, etc.). + /// + /// If this field is , the View is reusing the same flags as its parent Texture. + /// For any other value, this field overrides the flags of the parent Texture. + /// + /// + public TextureFlags Flags; + + /// + /// The pixel format of the View (used for the Shader Resource View or Unordered Access View). + /// + public PixelFormat Format; + + /// + /// A value of indicating which sub-resources the View can see (single mip, band, or full). + /// + public ViewType Type; + + /// + /// The index of the array slice. + /// + /// + /// If the Texture is not a Texture Array, only a single slice is assumed, so this should be zero (i.e. the first index). + /// + public int ArraySlice; + + /// + /// The index of the mip-level. + /// + /// + /// If the Texture has a single mipmap, this should be zero (i.e. the first index). + /// + public int MipLevel; + + + /// + /// Returns a copy of this description modified to describe a Texture View that will be used for staging. /// - public struct TextureViewDescription + /// A staging-compatible copy of the View description. + public readonly TextureViewDescription ToStagingDescription() { - /// - /// The flags used for the view. If then the view is using the flags from the texture. - /// - public TextureFlags Flags; - - /// - /// The format of the view (used for the ShaderResource or Unordered access). - /// - public PixelFormat Format; - - /// - /// The (single mip, band, or full) - /// - public ViewType Type; - - /// - /// The array slice index. - /// - public int ArraySlice; - - /// - /// The mip level index. - /// - public int MipLevel; - - /// - /// Gets a staging compatible description of this instance. - /// - /// TextureViewDescription. - public TextureViewDescription ToStagingDescription() - { - var viewDescription = this; - viewDescription.Flags = TextureFlags.None; - return viewDescription; - } + return this with { Flags = TextureFlags.None }; } } diff --git a/sources/engine/Stride.Graphics/VertexBufferBinding.cs b/sources/engine/Stride.Graphics/VertexBufferBinding.cs index c7484efeb0..a13711f1c8 100644 --- a/sources/engine/Stride.Graphics/VertexBufferBinding.cs +++ b/sources/engine/Stride.Graphics/VertexBufferBinding.cs @@ -1,111 +1,143 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; + using Stride.Core.Serialization; -using Stride.Core.Serialization.Serializers; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Binding structure that specifies a Vertex Buffer and other per-vertex parameters (such as offset and instancing) for a Graphics Device. +/// +[DataSerializer(typeof(VertexBufferBinding.Serializer))] +public readonly struct VertexBufferBinding : IEquatable { + private readonly int hashCode; + /// - /// Binding structure that specifies a vertex buffer and other per-vertex parameters (such as offset and instancing) for a graphics device. + /// Initializes a new instance of the structure. /// - [DataSerializer(typeof(VertexBufferBinding.Serializer))] - public struct VertexBufferBinding : IEquatable + /// The Vertex Buffer to bind. + /// + /// A description of the layout of the vertices in the , defining how the data is structured. + /// + /// The number of vertices in the Buffer to use. + /// + /// The size of a single vertex in bytes. This is the distance between two consecutive vertices in the buffer. + /// Specify -1 to auto-discover the stride from the . + /// + /// + /// The offset (in number of vertices) from the beginning of the Buffer to the first vertex to use. + /// Default is 0, meaning the first vertex in the Buffer will be used. + /// + /// + /// or is . + /// + public VertexBufferBinding(Buffer vertexBuffer, VertexDeclaration vertexDeclaration, int vertexCount, int vertexStride = -1, int vertexOffset = 0) : this() { - private readonly int hashCode; + ArgumentNullException.ThrowIfNull(vertexBuffer); + ArgumentNullException.ThrowIfNull(vertexDeclaration); - /// - /// Initializes a new instance of the struct. - /// - /// Jump size to the next element. if -1, it gets auto-discovered from the vertexDeclaration - /// Offset (in Vertex ElementCount) from the beginning of the buffer to the first vertex to use. - public VertexBufferBinding(Buffer vertexBuffer, VertexDeclaration vertexDeclaration, int vertexCount, int vertexStride = -1, int vertexOffset = 0) : this() - { - if (vertexBuffer == null) throw new ArgumentNullException("vertexBuffer"); - if (vertexDeclaration == null) throw new ArgumentNullException("vertexDeclaration"); + Buffer = vertexBuffer; + Stride = vertexStride != -1 ? vertexStride : vertexDeclaration.VertexStride; + Offset = vertexOffset; + Count = vertexCount; + Declaration = vertexDeclaration; - Buffer = vertexBuffer; - Stride = vertexStride != -1 ? vertexStride : vertexDeclaration.VertexStride; - Offset = vertexOffset; - Count = vertexCount; - Declaration = vertexDeclaration; + hashCode = HashCode.Combine(vertexBuffer, vertexOffset, vertexStride, vertexCount, vertexDeclaration); + } - unchecked - { - hashCode = Buffer.GetHashCode(); - hashCode = (hashCode * 397) ^ Offset; - hashCode = (hashCode * 397) ^ Stride; - hashCode = (hashCode * 397) ^ Count; - hashCode = (hashCode * 397) ^ Declaration.GetHashCode(); - } - } - /// - /// Gets a vertex buffer. - /// - public Buffer Buffer { get; private set; } + /// + /// Gets the Vertex Buffer to bind. + /// + public Buffer Buffer { get; } - /// - /// Gets the offset (vertex index) between the beginning of the buffer and the vertex data to use. - /// - public int Offset { get; private set; } + /// + /// Gets the offset (in number of vertices) from the beginning of the to the first vertex to use. + /// + public int Offset { get; } - /// - /// Gets the vertex stride. - /// - public int Stride { get; private set; } + /// + /// Gets the size of a single vertex in bytes. This is the distance between two consecutive vertices in the buffer. + /// + public int Stride { get; } - /// - /// Gets the number of vertex. - /// - /// The count. - public int Count { get; private set; } + /// + /// Gets the number of vertices in the Buffer to use. + /// + public int Count { get; } - /// - /// Gets the layout of the vertex buffer. - /// - /// The declaration. - public VertexDeclaration Declaration { get; private set; } + /// + /// Gets a description of the layout of the vertices in the , defining how the data is structured. + /// + public VertexDeclaration Declaration { get; } - public bool Equals(VertexBufferBinding other) - { - return Buffer.Equals(other.Buffer) && Offset == other.Offset && Stride == other.Stride && Count == other.Count && Declaration.Equals(other.Declaration); - } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is VertexBufferBinding && Equals((VertexBufferBinding)obj); - } + /// + public readonly bool Equals(VertexBufferBinding other) + { + return Buffer.Equals(other.Buffer) + && Offset == other.Offset + && Stride == other.Stride + && Count == other.Count + && Declaration.Equals(other.Declaration); + } - public override int GetHashCode() - { - return hashCode; - } + /// + public override readonly bool Equals(object obj) + { + return obj is VertexBufferBinding vbb && Equals(vbb); + } + + public static bool operator ==(VertexBufferBinding left, VertexBufferBinding right) + { + return left.Equals(right); + } - internal class Serializer : DataSerializer + public static bool operator !=(VertexBufferBinding left, VertexBufferBinding right) + { + return !(left == right); + } + + /// + public override readonly int GetHashCode() => hashCode; + + #region Serializer + + /// + /// Provides functionality to serialize and deserialize objects. + /// + internal class Serializer : DataSerializer + { + /// + /// Serializes or deserializes a object. + /// + /// The object to serialize or deserialize. + /// + public override void Serialize(ref VertexBufferBinding vertexBufferBinding, ArchiveMode mode, SerializationStream stream) { - public override void Serialize(ref VertexBufferBinding vertexBufferBinding, ArchiveMode mode, SerializationStream stream) + if (mode == ArchiveMode.Deserialize) { - if (mode == ArchiveMode.Deserialize) - { - var buffer = stream.Read(); - var declaration = stream.Read(); - var count = stream.ReadInt32(); - var stride = stream.ReadInt32(); - var offset = stream.ReadInt32(); - - vertexBufferBinding = new VertexBufferBinding(buffer, declaration, count, stride, offset); - } - else - { - stream.Write(vertexBufferBinding.Buffer); - stream.Write(vertexBufferBinding.Declaration); - stream.Write(vertexBufferBinding.Count); - stream.Write(vertexBufferBinding.Stride); - stream.Write(vertexBufferBinding.Offset); - } + var buffer = stream.Read(); + var declaration = stream.Read(); + var count = stream.ReadInt32(); + var stride = stream.ReadInt32(); + var offset = stream.ReadInt32(); + + vertexBufferBinding = new VertexBufferBinding(buffer, declaration, count, stride, offset); + } + else + { + stream.Write(vertexBufferBinding.Buffer); + stream.Write(vertexBufferBinding.Declaration); + stream.Write(vertexBufferBinding.Count); + stream.Write(vertexBufferBinding.Stride); + stream.Write(vertexBufferBinding.Offset); } } + + #endregion } } diff --git a/sources/engine/Stride.Graphics/VertexBufferBindingExtensions.cs b/sources/engine/Stride.Graphics/VertexBufferBindingExtensions.cs index 562f8e3283..e41bb1e637 100644 --- a/sources/engine/Stride.Graphics/VertexBufferBindingExtensions.cs +++ b/sources/engine/Stride.Graphics/VertexBufferBindingExtensions.cs @@ -1,56 +1,92 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Provides extension methods that facilitate the conversion of Vertex Declarations and +/// Vertex Buffer bindings into structures, which are used +/// to define the input layout for Shaders in the graphics pipeline. +/// +public static class VertexBufferBindingExtensions { - public static class VertexBufferBindingExtensions + /// + /// Creates descriptions for the Input Elements in a Vertex Buffer that will be fed to a Shader + /// based on the specified Vertex Declaration. + /// + /// + /// The Vertex Declaration containing the Vertex Elements to be converted into Input Element descriptions. + /// + /// + /// An array of structures representing the Input Elements derived from the + /// Vertex Declaration. + /// + /// + /// This method processes the vertex elements in the provided and + /// maps them to corresponding structures. + /// + public static InputElementDescription[] CreateInputElements(this VertexDeclaration vertexDeclaration) { - public static InputElementDescription[] CreateInputElements(this VertexDeclaration vertexDeclaration) + var inputElements = new InputElementDescription[vertexDeclaration.VertexElements.Length]; + var inputElementIndex = 0; + + foreach (var element in vertexDeclaration.EnumerateWithOffsets()) + { + inputElements[inputElementIndex++] = new InputElementDescription + { + SemanticName = element.VertexElement.SemanticName, + SemanticIndex = element.VertexElement.SemanticIndex, + Format = element.VertexElement.Format, + InputSlot = 0, + AlignedByteOffset = element.Offset + }; + } + + return inputElements; + } + + /// + /// Creates descriptions for the Input Elements in the Vertex Buffers that will be bound to the + /// Graphics Pipeline and fed to a Shader based on the specified Vertex Declaration. + /// + /// + /// The descriptions of the Vertex Buffers that will be bound to the pipeline. + /// + /// + /// An array of structures representing the Input Elements derived from the + /// Vertex Buffer bindings. + /// + /// + /// This method processes the vertex elements in the provided s and + /// maps them to corresponding structures. + /// + public static InputElementDescription[] CreateInputElements(this VertexBufferBinding[] vertexBuffers) + { + // Count the total number of Input Elements across all Vertex Buffers + var inputElementCount = 0; + foreach (var vertexBuffer in vertexBuffers) + { + inputElementCount += vertexBuffer.Declaration.VertexElements.Length; + } + + var inputElements = new InputElementDescription[inputElementCount]; + var inputElementIndex = 0; + for (int inputSlot = 0; inputSlot < vertexBuffers.Length; inputSlot++) { - var inputElements = new InputElementDescription[vertexDeclaration.VertexElements.Length]; - var inputElementIndex = 0; - foreach (var element in vertexDeclaration.EnumerateWithOffsets()) + var vertexBuffer = vertexBuffers[inputSlot]; + foreach (var element in vertexBuffer.Declaration.EnumerateWithOffsets()) { inputElements[inputElementIndex++] = new InputElementDescription { SemanticName = element.VertexElement.SemanticName, SemanticIndex = element.VertexElement.SemanticIndex, Format = element.VertexElement.Format, - InputSlot = 0, - AlignedByteOffset = element.Offset, + InputSlot = inputSlot, + AlignedByteOffset = element.Offset }; } - - return inputElements; } - public static InputElementDescription[] CreateInputElements(this VertexBufferBinding[] vertexBuffers) - { - var inputElementCount = 0; - foreach (var vertexBuffer in vertexBuffers) - { - inputElementCount += vertexBuffer.Declaration.VertexElements.Length; - } - - var inputElements = new InputElementDescription[inputElementCount]; - var inputElementIndex = 0; - for (int inputSlot = 0; inputSlot < vertexBuffers.Length; inputSlot++) - { - var vertexBuffer = vertexBuffers[inputSlot]; - foreach (var element in vertexBuffer.Declaration.EnumerateWithOffsets()) - { - inputElements[inputElementIndex++] = new InputElementDescription - { - SemanticName = element.VertexElement.SemanticName, - SemanticIndex = element.VertexElement.SemanticIndex, - Format = element.VertexElement.Format, - InputSlot = inputSlot, - AlignedByteOffset = element.Offset, - }; - } - } - - return inputElements; - } + return inputElements; } } diff --git a/sources/engine/Stride.Graphics/VertexDeclaration.cs b/sources/engine/Stride.Graphics/VertexDeclaration.cs index df6eb323c5..a4c77fb465 100644 --- a/sources/engine/Stride.Graphics/VertexDeclaration.cs +++ b/sources/engine/Stride.Graphics/VertexDeclaration.cs @@ -1,220 +1,222 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; + using Stride.Core; using Stride.Core.Serialization; -using Stride.Core.Serialization.Serializers; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines the layout of the vertices in a Vertex Buffer by specifying its component s. +/// +[DataContract] +[DataSerializer(typeof(Serializer))] +public sealed class VertexDeclaration : IEquatable { + private readonly VertexElement[] elements; + private readonly int instanceCount; // TODO: InstanceCount is not used in any place. Consider removing it or using it in a meaningful way + private readonly int vertexStride; + + // The precomputed hash code for this VertexDeclaration instance + private readonly int hashCode; + + /// - /// The layout of a vertex buffer with a set of . + /// Initializes a new instance of the class. /// - [DataContract] - [DataSerializer(typeof(Serializer))] - public class VertexDeclaration : IEquatable + internal VertexDeclaration() { } + + /// + /// Initializes a new instance of the class. + /// + /// The elements that compose a vertex. + public VertexDeclaration(params VertexElement[] elements) + : this(elements, instanceCount: 0, vertexStride: 0) + { } + + /// + /// Initializes a new instance of the class. + /// + /// The elements that compose a vertex. + /// The instance count. + /// + /// The size of a single vertex in bytes. This is the distance between two consecutive vertices in the Vertex Buffer. + /// Specify 0 to auto-discover the stride from the . + /// + /// is . + public VertexDeclaration(VertexElement[] elements, int instanceCount, int vertexStride) { - private readonly VertexElement[] elements; - private readonly int instanceCount; - private readonly int vertexStride; - private readonly int hashCode; + ArgumentNullException.ThrowIfNull(elements); - /// - /// Initializes a new instance of the class. - /// - internal VertexDeclaration() { } + this.elements = elements; + this.vertexStride = vertexStride == 0 ? VertexElementValidator.GetVertexStride(elements) : vertexStride; + this.instanceCount = instanceCount; - /// - /// Initializes a new instance of the class. - /// - /// The elements. - public VertexDeclaration(params VertexElement[] elements) - : this(elements, 0, 0) - { - } + // Validate Vertices + VertexElementValidator.Validate(this.vertexStride, elements); - /// - /// Initializes a new instance of the class. - /// - /// The elements. - /// The instance count. - /// The vertex stride. - /// elements - public VertexDeclaration(VertexElement[] elements, int instanceCount, int vertexStride) - { - if (elements == null) throw new ArgumentNullException("elements"); + // Precompute hash code + hashCode = HashCode.Combine(instanceCount, this.vertexStride, elements); + } - this.elements = elements; - this.vertexStride = vertexStride == 0 ? VertexElementValidator.GetVertexStride(elements) : vertexStride; - this.instanceCount = instanceCount; - // Validate Vertices - VertexElementValidator.Validate(VertexStride, elements); + /// + /// Gets the Vertex Elements that define the layout of the vertices in this declaration. + /// + [DataMember] + public VertexElement[] VertexElements => elements; - hashCode = instanceCount; - hashCode = (hashCode * 397) ^ vertexStride; - foreach (var vertexElement in elements) - { - hashCode = (hashCode * 397) ^ vertexElement.GetHashCode(); - } - } + // TODO: InstanceCount is not used in any place. Consider removing it or using it in a meaningful way + /// + /// Gets the instance count. + /// + public int InstanceCount => instanceCount; - /// - /// Gets the vertex elements. - /// - /// The vertex elements. - [DataMember] - public VertexElement[] VertexElements => elements; + /// + /// Gets the size, in bytes, of a single vertex in this declaration. + /// + public int VertexStride => vertexStride; - /// - /// Gets the instance count. - /// - /// The instance count. - public int InstanceCount - { - get - { - return instanceCount; - } - } - /// - /// Gets the vertex stride. - /// - /// The vertex stride. - public int VertexStride + /// + /// Enumerates the Vertex Elements along with their declared offsets. + /// + /// A sequence of s structures. + public IEnumerable EnumerateWithOffsets() + { + int offset = 0; + foreach (var element in VertexElements) { - get - { - return vertexStride; - } + // Get new offset (if specified) + var currentElementOffset = element.AlignedByteOffset; + if (currentElementOffset != VertexElement.AppendAligned) + offset = currentElementOffset; + + var elementSize = element.Format.SizeInBytes(); + yield return new VertexElementWithOffset(element, offset, elementSize); + + // Compute next offset (if automatic) + offset += elementSize; } + } - /// - /// Enumerates with declared offsets. - /// - /// A set of with offsets. - public IEnumerable EnumerateWithOffsets() + /// + /// Calculates the size in bytes of a vertex described by this Vertex Declaration. + /// + /// The size in bytes of the vertices using the layout described by this instance. + public int CalculateSize() + { + var size = 0; + var offset = 0; + foreach (var element in VertexElements) { - int offset = 0; - foreach (var element in VertexElements) - { - // Get new offset (if specified) - var currentElementOffset = element.AlignedByteOffset; - if (currentElementOffset != VertexElement.AppendAligned) - offset = currentElementOffset; + // Get new offset (if specified) + var currentElementOffset = element.AlignedByteOffset; + if (currentElementOffset != VertexElement.AppendAligned) + offset = currentElementOffset; - var elementSize = element.Format.SizeInBytes(); - yield return new VertexElementWithOffset(element, offset, elementSize); + var elementSize = element.Format.SizeInBytes(); - // Compute next offset (if automatic) - offset += elementSize; - } + // Compute next offset (if automatic) + offset += elementSize; + + // Elements are not necessarily ordered by increasing offsets + size = Math.Max(size, offset); } - /// - /// Calculate the size of the vertex declaration. - /// - /// The size in bytes of the vertex declaration - public int CalculateSize() - { - var size = 0; - var offset = 0; - foreach (var element in VertexElements) - { - // Get new offset (if specified) - var currentElementOffset = element.AlignedByteOffset; - if (currentElementOffset != VertexElement.AppendAligned) - offset = currentElementOffset; + return size; + } - var elementSize = element.Format.SizeInBytes(); - // Compute next offset (if automatic) - offset += elementSize; + /// + public bool Equals(VertexDeclaration other) + { + if (other is null) + return false; - size = Math.Max(size, offset); // element are not necessary ordered by increasing offsets - } + if (ReferenceEquals(this, other)) + return true; - return size; - } + return hashCode == other.hashCode + && vertexStride == other.vertexStride + && instanceCount == other.instanceCount + && elements.SequenceEqualAllowNull(other.elements); + } - public bool Equals(VertexDeclaration other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return - hashCode == other.hashCode && - vertexStride == other.vertexStride && - instanceCount == other.instanceCount && - elements.SequenceEqualAllowNull(other.elements); - } + /// + public override bool Equals(object obj) + { + return obj is VertexDeclaration vertexDeclaration && Equals(vertexDeclaration); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((VertexDeclaration)obj); - } + /// + public override int GetHashCode() => hashCode; - public override int GetHashCode() - { - return hashCode; - } - /// - /// Performs an implicit conversion from to . - /// - /// The element. - /// The result of the conversion. - public static implicit operator VertexDeclaration(VertexElement element) + /// + /// Performs an implicit conversion from to , + /// creating a Vertex Declaration with a single Vertex Element. + /// + /// The single Vertex Element. + /// The resulting Vertex Declaration. + public static implicit operator VertexDeclaration(VertexElement element) => new(element); + + /// + /// Performs an implicit conversion from an array of to , + /// creating a Vertex Declaration with the provided Vertex Elements. + /// + /// The Vertex Elements. + /// The resulting Vertex Declaration. + public static implicit operator VertexDeclaration(VertexElement[] elements) => new(elements); + + #region Serializer + + /// + /// Provides functionality to serialize and deserialize objects. + /// + internal class Serializer : DataSerializer, IDataSerializerGenericInstantiation + { + /// + public override void PreSerialize(ref object obj, ArchiveMode mode, SerializationStream stream) { - return new VertexDeclaration(element); + // We are creating object at deserialization time + if (mode == ArchiveMode.Serialize) + { + base.PreSerialize(ref obj, mode, stream); + } } /// - /// Performs an implicit conversion from to . + /// Serializes or deserializes a object. /// - /// The elements. - /// The result of the conversion. - public static implicit operator VertexDeclaration(VertexElement[] elements) - { - return new VertexDeclaration(elements); - } - - internal class Serializer : DataSerializer, IDataSerializerGenericInstantiation + /// The object to serialize or deserialize. + /// + public override void Serialize(ref VertexDeclaration vertexDeclaration, ArchiveMode mode, SerializationStream stream) { - public override void PreSerialize(ref object obj, ArchiveMode mode, SerializationStream stream) + if (mode == ArchiveMode.Deserialize) { - // We are creating object at deserialization time - if (mode == ArchiveMode.Serialize) - { - base.PreSerialize(ref obj, mode, stream); - } + var elements = stream.Read(); + var instanceCount = stream.ReadInt32(); + var vertexStride = stream.ReadInt32(); + vertexDeclaration = new VertexDeclaration(elements, instanceCount, vertexStride); } - - public override void Serialize(ref VertexDeclaration obj, ArchiveMode mode, SerializationStream stream) + else { - if (mode == ArchiveMode.Deserialize) - { - var elements = stream.Read(); - var instanceCount = stream.ReadInt32(); - var vertexStride = stream.ReadInt32(); - obj = new VertexDeclaration(elements, instanceCount, vertexStride); - } - else - { - stream.Write(obj.elements); - stream.Write(obj.instanceCount); - stream.Write(obj.vertexStride); - } + stream.Write(vertexDeclaration.elements); + stream.Write(vertexDeclaration.instanceCount); + stream.Write(vertexDeclaration.vertexStride); } + } - public void EnumerateGenericInstantiations(SerializerSelector serializerSelector, IList genericInstantiations) - { - genericInstantiations.Add(typeof(VertexElement[])); - } + /// + public void EnumerateGenericInstantiations(SerializerSelector serializerSelector, IList genericInstantiations) + { + genericInstantiations.Add(typeof(VertexElement[])); } } + + #endregion } diff --git a/sources/engine/Stride.Graphics/VertexElement.cs b/sources/engine/Stride.Graphics/VertexElement.cs index f2d1c12d25..d06681eb96 100644 --- a/sources/engine/Stride.Graphics/VertexElement.cs +++ b/sources/engine/Stride.Graphics/VertexElement.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,502 +22,609 @@ // THE SOFTWARE. using System; -using System.Globalization; using System.Text.RegularExpressions; + using Stride.Core; using Stride.Core.Mathematics; using Stride.Core.Serialization; -using Stride.Core.Serialization.Serializers; + using Half = Stride.Core.Mathematics.Half; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// A description of a single element for the input-assembler stage. This structure is related to . +/// +/// +/// Because requires to have the same , and , +/// the structure encapsulates a set of for a particular slot, classification and instance data step rate. +/// Unlike the default , this structure accepts a semantic name with a postfix number that will be automatically extracted to the semantic index. +/// +/// +[DataContract] +[DataSerializer(typeof(Serializer))] +public partial struct VertexElement : IEquatable { + private string semanticName; + private int semanticIndex; + private PixelFormat format; + private int alignedByteOffset; + private readonly int hashCode; + + + // Match the last digit of a semantic name. + private static readonly Regex MatchSemanticIndex = MatchSemanticIndexRegex(); + [GeneratedRegex(@"(.*)(\d+)$")] + private static partial Regex MatchSemanticIndexRegex(); + /// - /// A description of a single element for the input-assembler stage. This structure is related to . + /// Returns a value that can be used for the offset parameter of an Vertex Element to indicate that the element + /// should be aligned directly after the previous one, including any packing if neccessary. /// - /// - /// Because requires to have the same , and , - /// the structure encapsulates a set of for a particular slot, classification and instance data step rate. - /// Unlike the default , this structure accepts a semantic name with a postfix number that will be automatically extracted to the semantic index. - /// - /// - [DataContract] - [DataSerializer(typeof(Serializer))] - public struct VertexElement : IEquatable - { - private string semanticName; + public const int AppendAligned = -1; - private int semanticIndex; - private PixelFormat format; + /// + /// Initializes a new instance of the structure. + /// + /// + /// The semantic name associated with this element, such as "POSITION", "TEXCOORD", or "NORMAL". + ///
+ /// If the semantic name contains a postfix number, this number will be used as a semantic index, + /// such as "TEXCOORD1" or "COLOR0". + /// + /// + /// The data format of the element, such as (equivalent to ) + /// or (equivalent to a 32-bit, 8bpc RGBA color). + /// + /// + /// is or an empty . + /// + public VertexElement(string semanticName, PixelFormat format) + : this() + { + ArgumentException.ThrowIfNullOrWhiteSpace(semanticName); - private int alignedByteOffset; + // All semantics will be upper case + semanticName = semanticName.ToUpperInvariant(); - private int hashCode; + var match = MatchSemanticIndex.Match(semanticName); + if (match.Success) + { + // Convert to singleton string in order to speed up things + // TODO: Stale comment? Use string.Intern? + this.semanticName = match.Groups[1].Value; - // Match the last digit of a semantic name. - internal static readonly Regex MatchSemanticIndex = new Regex(@"(.*)(\d+)$"); + if (!uint.TryParse(match.Groups[2].Value, out var semanticIndex)) + throw new ArgumentException("Could not parse semantic index from the semantic name", nameof(semanticName)); - /// - /// Returns a value that can be used for the offset parameter of an InputElement to indicate that the element - /// should be aligned directly after the previous element, including any packing if neccessary. - /// - /// A value used to align input elements. - public const int AppendAligned = -1; + this.semanticIndex = (int) semanticIndex; + } + else this.semanticName = semanticName; - /// - /// Initializes a new instance of the struct. - /// - /// Name of the semantic. - /// The format. - /// - /// If the semantic name contains a postfix number, this number will be used as a semantic index. - /// - public VertexElement(string semanticName, PixelFormat format) - : this() - { - if (semanticName == null) - throw new ArgumentNullException("semanticName"); + this.format = format; + alignedByteOffset = AppendAligned; - // All semantics will be upper case. - semanticName = semanticName.ToUpperInvariant(); + // Precalculate hashcode + hashCode = ComputeHashCode(); + } - var match = MatchSemanticIndex.Match(semanticName); - if (match.Success) - { - // Convert to singleton string in order to speed up things. - this.semanticName = match.Groups[1].Value; - semanticIndex = int.Parse(match.Groups[2].Value); - } - else - { - this.semanticName = semanticName; - } + /// + /// Initializes a new instance of the structure. + /// + /// + /// The semantic name associated with this element, such as "POSITION", "TEXCOORD", or "NORMAL". + /// + /// + /// The semantic index for the element, used when multiple elements share the same semantic name, + /// such as the 1 in "TEXCOORD1" or the 0 in "COLOR0". + /// + /// + /// The data format of the element, such as (equivalent to ) + /// or (equivalent to a 32-bit, 8bpc RGBA color). + /// + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// + /// is or an empty . + /// + /// + /// Cannot specify a semantic index in the when using this constructor. + /// Use the constructor with explicit semantic index instead (). + /// + public VertexElement(string semanticName, int semanticIndex, PixelFormat format, int alignedByteOffset = AppendAligned) + : this() + { + ArgumentException.ThrowIfNullOrWhiteSpace(semanticName); - this.format = format; - alignedByteOffset = AppendAligned; + // All semantics will be upper case + semanticName = semanticName.ToUpperInvariant(); - // Precalculate hashcode - hashCode = ComputeHashCode(); - } + var match = MatchSemanticIndex.Match(semanticName); + if (match.Success) + throw new ArgumentException("Cannot specify a semantic index when using the constructor with explicit semantic index. Use the implicit semantic index constructor."); - /// - /// Initializes a new instance of the struct. - /// - /// Name of the semantic. - /// Index of the semantic. - /// The format. - /// The aligned byte offset. - public VertexElement(string semanticName, int semanticIndex, PixelFormat format, int alignedByteOffset = AppendAligned) - : this() - { - if (semanticName == null) - throw new ArgumentNullException("semanticName"); + // Convert to singleton string in order to speed up things + // TODO: Stale comment? Use string.Intern? + this.semanticName = semanticName; + this.semanticIndex = semanticIndex; + this.format = format; + this.alignedByteOffset = alignedByteOffset; - // All semantics will be upper case. - semanticName = semanticName.ToUpperInvariant(); + // Precalculate hashcode + hashCode = ComputeHashCode(); + } - var match = MatchSemanticIndex.Match(semanticName); - if (match.Success) - throw new ArgumentException("Semantic name cannot a semantic index when using constructor with explicit semantic index. Use implicit semantic index constructor."); - // Convert to singleton string in order to speed up things. - this.semanticName = semanticName; - this.semanticIndex = semanticIndex; - this.format = format; - this.alignedByteOffset = alignedByteOffset; + /// + /// Gets the HLSL semantic name associated with this element, such as "POSITION", "TEXCOORD", or "NORMAL". + /// + /// + /// This name must match the semantic used in the Vertex Shader input signature. + /// + public readonly string SemanticName => semanticName; - // Precalculate hashcode - hashCode = ComputeHashCode(); - } + /// + /// Gets the HLSL semantic name associated with this element, such as "POSITION", "TEXCOORD", or "NORMAL". + ///
+ /// It can contain a postfix number, the , such as "TEXCOORD1" or "COLOR0". + ///
+ /// + /// This name must match the semantic used in the Vertex Shader input signature. + /// + /// + /// + public readonly string SemanticAsText + => semanticIndex == 0 + ? semanticName + : FormattableString.Invariant($"{semanticName}{semanticIndex}"); - /// - ///

The HLSL semantic associated with this element in a shader input-signature.

- ///
- public string SemanticName - { - get - { - return semanticName; - } - } + /// + /// Gets the semantic index for the element, used when multiple elements share the same semantic name. + /// + /// + /// For example, a 4x4 matrix might be passed as four "TEXCOORD" elements with indices 0 through 3. + /// + public readonly int SemanticIndex => semanticIndex; - /// - ///

The HLSL semantic associated with this element in a shader input-signature.

- ///
- public string SemanticAsText - { - get - { - if (semanticIndex == 0) - return semanticName; - return string.Format("{0}{1}", semanticName, semanticIndex.ToString(CultureInfo.InvariantCulture)); - } - } + /// + /// The data format of the element, such as or . + /// + /// + /// This must match the format expected by the Shader and the layout of the Vertex Buffer. + /// + public readonly PixelFormat Format => format; - /// - ///

The semantic index for the element. A semantic index modifies a semantic, with an integer index number. A semantic index is only needed in a case where there is more than one element with the same semantic. For example, a 4x4 matrix would have four components each with the semantic name

matrix

, however each of the four component would have different semantic indices (0, 1, 2, and 3).

- ///
- public int SemanticIndex - { - get - { - return semanticIndex; - } - } + /// + /// The byte offset from the start of the vertex to this element. + /// + /// + /// Use -1 (or a constant like to automatically align the element after the previous one, + /// including any packing if necessary. + /// + public readonly int AlignedByteOffset => alignedByteOffset; - /// - ///

The data type of the element data. See .

- ///
- public PixelFormat Format - { - get - { - return format; - } - } - /// - ///

Optional. Offset (in bytes) between each element. Use D3D11_APPEND_ALIGNED_ELEMENT for convenience to define the current element directly after the previous one, including any packing if necessary.

- ///
- public int AlignedByteOffset - { - get - { - return alignedByteOffset; - } - } + /// + public readonly bool Equals(VertexElement other) + { + // First use hashCode to compute + return hashCode == other.hashCode + && semanticName.Equals(other.semanticName) + && semanticIndex == other.semanticIndex + && format == other.format + && alignedByteOffset == other.alignedByteOffset; + } - public bool Equals(VertexElement other) - { - // First use hashCode to compute - return hashCode == other.hashCode && semanticName.Equals(other.semanticName) && semanticIndex == other.semanticIndex && format == other.format && alignedByteOffset == other.alignedByteOffset; - } + /// + public override readonly bool Equals(object obj) + { + return obj is VertexElement ve && Equals(ve); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is VertexElement && Equals((VertexElement)obj); - } + public static bool operator ==(VertexElement left, VertexElement right) + { + return left.Equals(right); + } - public override int GetHashCode() - { - return hashCode; - } + public static bool operator !=(VertexElement left, VertexElement right) + { + return !left.Equals(right); + } - internal int ComputeHashCode() - { - unchecked - { - int localHashCode = semanticName.GetHashCode(); - localHashCode = (localHashCode * 397) ^ semanticIndex; - localHashCode = (localHashCode * 397) ^ format.GetHashCode(); - localHashCode = (localHashCode * 397) ^ alignedByteOffset; - return localHashCode; - } - } + /// + public override readonly int GetHashCode() => hashCode; + // + // Computes the hash code for this VertexElement so it can be cached. + // + private readonly int ComputeHashCode() + { + return HashCode.Combine(semanticName, semanticIndex, format, alignedByteOffset); + } - public static bool operator ==(VertexElement left, VertexElement right) - { - return left.Equals(right); - } + /// + public override readonly string ToString() + { + return FormattableString.Invariant($"{SemanticAsText},{Format},{AlignedByteOffset}"); + } - public static bool operator !=(VertexElement left, VertexElement right) - { - return !left.Equals(right); - } + #region Common VertexElements - public override string ToString() - { - return string.Format("{0}{1},{2},{3}", semanticName, semanticIndex == 0 ? string.Empty : string.Empty + semanticIndex, format, alignedByteOffset); - } + /// + /// Declares a with the semantic "COLOR". + /// + /// The type of the color element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "COLOR" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement Color(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return Color(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "COLOR". - /// - /// Type of the Color semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Color(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return Color(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "COLOR". + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "COLOR" semantic. + public static VertexElement Color(PixelFormat format, int offsetInBytes = AppendAligned) + { + return Color(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "COLOR". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Color(PixelFormat format, int offsetInBytes = AppendAligned) - { - return Color(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "COLOR". + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "COLOR" semantic. + public static VertexElement Color(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("COLOR", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "COLOR". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Color(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("COLOR", semanticIndex, format, offsetInBytes); - } + /// + /// Declares a with the semantic "NORMAL". + /// + /// The type of the normal element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "NORMAL" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement Normal(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return Normal(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "NORMAL". - /// - /// Type of the Normal semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Normal(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return Normal(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "NORMAL". + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "NORMAL" semantic. + public static VertexElement Normal(PixelFormat format, int offsetInBytes = AppendAligned) + { + return Normal(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "NORMAL". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Normal(PixelFormat format, int offsetInBytes = AppendAligned) - { - return Normal(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "NORMAL". + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "NORMAL" semantic. + public static VertexElement Normal(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("NORMAL", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "NORMAL". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Normal(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("NORMAL", semanticIndex, format, offsetInBytes); - } + /// + /// Declares a with the semantic "POSITION". + /// + /// The type of the position element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "POSITION" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement Position(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return Position(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "POSITION". - /// - /// Type of the Position semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Position(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return Position(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "POSITION". + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "POSITION" semantic. + public static VertexElement Position(PixelFormat format, int offsetInBytes = AppendAligned) + { + return Position(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "POSITION". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Position(PixelFormat format, int offsetInBytes = AppendAligned) - { - return Position(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "POSITION". + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "POSITION" semantic. + public static VertexElement Position(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("POSITION", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "POSITION". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Position(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("POSITION", semanticIndex, format, offsetInBytes); - } + /// + /// Declares a with the semantic "SV_POSITION" (transformed position). + /// + /// The type of the tranformed position element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "SV_POSITION" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement PositionTransformed(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return PositionTransformed(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "SV_POSITION". - /// - /// Type of the PositionTransformed semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement PositionTransformed(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return PositionTransformed(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "SV_POSITION" (transformed position). + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "SV_POSITION" semantic. + public static VertexElement PositionTransformed(PixelFormat format, int offsetInBytes = AppendAligned) + { + return PositionTransformed(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "SV_POSITION". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement PositionTransformed(PixelFormat format, int offsetInBytes = AppendAligned) - { - return PositionTransformed(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "SV_POSITION" (transformed position). + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "SV_POSITION" semantic. + public static VertexElement PositionTransformed(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("SV_POSITION", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "SV_POSITION". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement PositionTransformed(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("SV_POSITION", semanticIndex, format, offsetInBytes); - } + /// + /// Declares a with the semantic "TEXCOORD" (texture coordinates). + /// + /// The type of the texture coordinates element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "TEXCOORD" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement TextureCoordinate(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return TextureCoordinate(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "TEXCOORD". - /// - /// Type of the TextureCoordinate semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement TextureCoordinate(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return TextureCoordinate(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "TEXCOORD" (texture coordinates). + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "TEXCOORD" semantic. + public static VertexElement TextureCoordinate(PixelFormat format, int offsetInBytes = AppendAligned) + { + return TextureCoordinate(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "TEXCOORD". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement TextureCoordinate(PixelFormat format, int offsetInBytes = AppendAligned) - { - return TextureCoordinate(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "TEXCOORD" (texture coordinates). + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "TEXCOORD" semantic. + public static VertexElement TextureCoordinate(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("TEXCOORD", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "TEXCOORD". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement TextureCoordinate(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("TEXCOORD", semanticIndex, format, offsetInBytes); - } + /// + /// Declares a with the semantic "TANGENT". + /// + /// The type of the tangent element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "TANGENT" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement Tangent(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return Tangent(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "TANGENT". - /// - /// Type of the Tangent semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Tangent(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return Tangent(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "TANGENT". + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "TANGENT" semantic. + public static VertexElement Tangent(PixelFormat format, int offsetInBytes = AppendAligned) + { + return Tangent(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "TANGENT". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Tangent(PixelFormat format, int offsetInBytes = AppendAligned) - { - return Tangent(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "TANGENT". + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "TANGENT" semantic. + public static VertexElement Tangent(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("TANGENT", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "TANGENT". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement Tangent(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("TANGENT", semanticIndex, format, offsetInBytes); - } + /// + /// Declares a with the semantic "BITANGENT". + /// + /// The type of the bitangent element. + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "BITANGENT" semantic. + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static VertexElement BiTangent(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct + { + return BiTangent(semanticIndex, ConvertTypeToFormat(), offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "BITANGENT". - /// - /// Type of the BiTangent semantic. - /// The semantic index. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement BiTangent(int semanticIndex = 0, int offsetInBytes = AppendAligned) where T : struct - { - return BiTangent(semanticIndex, ConvertTypeToFormat(), offsetInBytes); - } + /// + /// Declares a with the semantic "BITANGENT". + /// + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "BITANGENT" semantic. + public static VertexElement BiTangent(PixelFormat format, int offsetInBytes = AppendAligned) + { + return BiTangent(0, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "BITANGENT". - /// - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement BiTangent(PixelFormat format, int offsetInBytes = AppendAligned) - { - return BiTangent(0, format, offsetInBytes); - } + /// + /// Declares a with the semantic "BITANGENT". + /// + /// The semantic index for the element, used when multiple elements share the same semantic name. + /// The data format of the element. + /// + /// The byte offset from the start of the vertex to this element. + /// Use -1 (or a constant like to automatically align the element after the previous one. + /// + /// A new Vertex Element that represents the "BITANGENT" semantic. + public static VertexElement BiTangent(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) + { + return new VertexElement("BITANGENT", semanticIndex, format, offsetInBytes); + } - /// - /// Declares a VertexElement with the semantic "BITANGENT". - /// - /// The semantic index. - /// Format of this element. - /// The offset in bytes of this element. Use to compute automatically the offset from previous elements. - /// A new instance of that represents this semantic. - public static VertexElement BiTangent(int semanticIndex, PixelFormat format, int offsetInBytes = AppendAligned) - { - return new VertexElement("BITANGENT", semanticIndex, format, offsetInBytes); - } + #endregion - public static PixelFormat ConvertTypeToFormat() where T : struct - { - return ConvertTypeToFormat(typeof(T)); - } + /// + /// Converts a type to its equivalent . + /// + /// The type of the Vertex Element to convert. + /// The equivalent to . + /// + /// The specified is not supported. It cannot be converted to a . + /// + public static PixelFormat ConvertTypeToFormat() where T : struct + { + return ConvertTypeToFormat(typeof(T)); - /// - /// Converts a type to a . - /// - /// The type T. - /// The equivalent Format. - /// If the convertion for this type is not supported. - private static PixelFormat ConvertTypeToFormat(Type typeT) + static PixelFormat ConvertTypeToFormat(Type type) { - if (typeof(Vector4) == typeT || typeof(Color4) == typeT) + if (typeof(Vector4) == type || typeof(Color4) == type) return PixelFormat.R32G32B32A32_Float; - if (typeof(Vector3) == typeT || typeof(Color3) == typeT) + if (typeof(Vector3) == type || typeof(Color3) == type) return PixelFormat.R32G32B32_Float; - if (typeof(Vector2) == typeT) + if (typeof(Vector2) == type) return PixelFormat.R32G32_Float; - if (typeof(float) == typeT) + if (typeof(float) == type) return PixelFormat.R32_Float; - if (typeof(Color) == typeT) + if (typeof(Color) == type) return PixelFormat.R8G8B8A8_UNorm; - if (typeof(ColorBGRA) == typeT) + if (typeof(ColorBGRA) == type) return PixelFormat.B8G8R8A8_UNorm; - if (typeof(Half4) == typeT) + if (typeof(Half4) == type) return PixelFormat.R16G16B16A16_Float; - if (typeof(Half2) == typeT) + if (typeof(Half2) == type) return PixelFormat.R16G16_Float; - if (typeof(Half) == typeT) + if (typeof(Half) == type) return PixelFormat.R16_Float; - if (typeof(Int4) == typeT) + if (typeof(Int4) == type) return PixelFormat.R32G32B32A32_UInt; - if (typeof(Int3) == typeT) + if (typeof(Int3) == type) return PixelFormat.R32G32B32_UInt; - if (typeof(int) == typeT) + if (typeof(int) == type) return PixelFormat.R32_UInt; - if (typeof(uint) == typeT) + if (typeof(uint) == type) return PixelFormat.R32_UInt; //if (typeof(Bool4) == typeT) @@ -526,29 +633,41 @@ private static PixelFormat ConvertTypeToFormat(Type typeT) //if (typeof(Bool) == typeT) // return PixelFormat.R32_UInt; - throw new NotSupportedException(string.Format("Type [{0}] is not supported. You must specify an explicit DXGI.Format", typeT.Name)); + throw new NotSupportedException($"Type [{type.Name}] is not supported. You must specify an explicit PixelFormat"); } + } + + #region Serializer - internal class Serializer : DataSerializer + /// + /// Provides functionality to serialize and deserialize objects. + /// + internal class Serializer : DataSerializer + { + /// + /// Serializes or deserializes a object. + /// + /// The object to serialize or deserialize. + /// + public override void Serialize(ref VertexElement vertexElement, ArchiveMode mode, SerializationStream stream) { - public override void Serialize(ref VertexElement obj, ArchiveMode mode, SerializationStream stream) + if (mode == ArchiveMode.Deserialize) { - if (mode == ArchiveMode.Deserialize) - { - obj.semanticName = stream.ReadString(); - obj.semanticIndex = stream.ReadInt32(); - obj.format = stream.Read(); - obj.alignedByteOffset = stream.ReadInt32(); - obj.ComputeHashCode(); - } - else - { - stream.Write(obj.semanticName); - stream.Write(obj.semanticIndex); - stream.Write(obj.format); - stream.Write(obj.alignedByteOffset); - } + vertexElement.semanticName = stream.ReadString(); + vertexElement.semanticIndex = stream.ReadInt32(); + vertexElement.format = stream.Read(); + vertexElement.alignedByteOffset = stream.ReadInt32(); + vertexElement.ComputeHashCode(); + } + else + { + stream.Write(vertexElement.semanticName); + stream.Write(vertexElement.semanticIndex); + stream.Write(vertexElement.format); + stream.Write(vertexElement.alignedByteOffset); } } } + + #endregion } diff --git a/sources/engine/Stride.Graphics/VertexElementValidator.cs b/sources/engine/Stride.Graphics/VertexElementValidator.cs index a1d982d9c9..ff430e489f 100644 --- a/sources/engine/Stride.Graphics/VertexElementValidator.cs +++ b/sources/engine/Stride.Graphics/VertexElementValidator.cs @@ -1,74 +1,116 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Provides methods for validating and calculating properties of Vertex Elements used to define +/// the layout of Vertex Declarations. +/// +/// +/// +internal static class VertexElementValidator { - internal class VertexElementValidator + /// + /// Calculates the total stride, in bytes, of a vertex based on its elements. + /// + /// + /// An array of s representing the components of the vertex. Each element + /// defines a format and size. + /// + /// The total stride, in bytes, of the vertex, calculated as the sum of the sizes of all elements. + internal static int GetVertexStride(VertexElement[] elements) { - internal static int GetVertexStride(VertexElement[] elements) - { - int num2 = 0; - for (int i = 0; i < elements.Length; i++) - num2 += elements[i].Format.SizeInBytes(); - return num2; - } + int stride = 0; + + for (int i = 0; i < elements.Length; i++) + stride += elements[i].Format.SizeInBytes(); + + return stride; + } + + /// + /// Validates the configuration of vertex elements that form a Vertex Declaration. + /// + /// The size, in bytes, of a single vertex. Must be a positive value and a multiple of 4. + /// + /// An array of objects representing the layout of vertex attributes. Cannot contain + /// overlapping elements or duplicate semantic names and indices. + /// + /// + /// This method ensures that the Vertex Declaration adheres to alignment and size constraints + /// required for proper rendering. It checks for overlapping elements, duplicate semantic names and indices, + /// and ensures that all offsets are aligned to 4-byte boundaries. + /// + /// + /// is not a positive number greater than zero. + /// + /// + /// Thrown if: + /// + /// is not a multiple of 4 bytes. + /// Any Vertex Element extends beyond the bounds of the vertex stride. + /// Any Vertex Element has an offset that is not a multiple of 4 bytes. + /// Duplicate Vertex Elements are found with the same semantic name and index. + /// Any Vertex Element overlaps with another element. + /// + /// + internal static void Validate(int vertexStride, VertexElement[] elements) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(vertexStride); + + if ((vertexStride & 3) != 0) + throw new ArgumentException($"The vertex stride is not a multiple of 4 bytes", nameof(vertexStride)); + + // For checking overlaps, we use an array to track occupied bytes + var overlapCheckArray = new int[vertexStride]; + for (int i = 0; i < overlapCheckArray.Length; i++) + overlapCheckArray[i] = -1; - internal static void Validate(int vertexStride, VertexElement[] elements) + int totalOffset = 0; + for (int elementIndex = 0; elementIndex < elements.Length; elementIndex++) { - if (vertexStride <= 0) + // Compute the offset for this element + int elementOffset = elements[elementIndex].AlignedByteOffset; + if (elementOffset == VertexElement.AppendAligned) { - throw new ArgumentOutOfRangeException("vertexStride"); + elementOffset = elementIndex == 0 ? 0 : totalOffset + elements[elementIndex - 1].Format.SizeInBytes(); } - if ((vertexStride & 3) != 0) + totalOffset = elementOffset; + + // Validate the element offset + int typeSize = elements[elementIndex].Format.SizeInBytes(); + if (elementOffset == VertexElement.AppendAligned || (elementOffset + typeSize) > vertexStride) { - throw new ArgumentException("VertexElementOffsetNotMultipleFour"); + throw new ArgumentException($"The {nameof(VertexElement)}'s offset and size makes it extend beyond the vertex stride", nameof(elements)); } - int[] numArray = new int[vertexStride]; - for (int i = 0; i < vertexStride; i++) + if ((elementOffset & 3) != 0) { - numArray[i] = -1; + throw new ArgumentException($"The offset of the {nameof(VertexElement)} is not a multiple of 4 bytes", nameof(elements)); } - int totalOffset = 0; - for (int j = 0; j < elements.Length; j++) + + // Check for duplicate semantic names and indices + for (int i = 0; i < elementIndex; i++) { - int offset = elements[j].AlignedByteOffset; - if (offset == VertexElement.AppendAligned) - { - if (j == 0) - { - offset = 0; - } - else - { - offset = totalOffset + elements[j - 1].Format.SizeInBytes(); - } - } - totalOffset = offset; - int typeSize = elements[j].Format.SizeInBytes(); - if ((offset < 0) || ((offset + typeSize) > vertexStride)) - { - throw new ArgumentException("VertexElementOutsideStride"); - } - if ((offset & 3) != 0) + if (elements[elementIndex].SemanticName.Equals(elements[i].SemanticName) && + elements[elementIndex].SemanticIndex == elements[i].SemanticIndex) { - throw new ArgumentException("VertexElementOffsetNotMultipleFour"); + throw new ArgumentException($"Duplicate {nameof(VertexElement)}s found with the same semantic name and index", nameof(elements)); } - for (int k = 0; k < j; k++) - { - if (elements[j].SemanticName == elements[k].SemanticName && elements[j].SemanticIndex == elements[k].SemanticIndex) - { - throw new ArgumentException("DuplicateVertexElement"); - } - } - for (int m = offset; m < (offset + typeSize); m++) + } + + // Check for overlap with existing elements + for (int i = elementOffset; i < (elementOffset + typeSize); i++) + { + // Check for overlap with existing elements + if (overlapCheckArray[i] >= 0) { - if (numArray[m] >= 0) - { - throw new ArgumentException("VertexElementsOverlap"); - } - numArray[m] = j; + throw new ArgumentException($"A {nameof(VertexElement)} overlaps with another element. Check the element's offset", nameof(elements)); } + // Mark this byte as occupied by this element + overlapCheckArray[i] = elementIndex; } } } diff --git a/sources/engine/Stride.Graphics/VertexElementWithOffset.cs b/sources/engine/Stride.Graphics/VertexElementWithOffset.cs index 13a59a84ef..9189554508 100644 --- a/sources/engine/Stride.Graphics/VertexElementWithOffset.cs +++ b/sources/engine/Stride.Graphics/VertexElementWithOffset.cs @@ -1,18 +1,28 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Represents a with additional metadata, including its offset and size. +/// +/// +/// +/// +public struct VertexElementWithOffset(VertexElement vertexElement, int offset, int size) { - public struct VertexElementWithOffset - { - public VertexElement VertexElement; - public int Offset; - public int Size; + /// + /// The Vertex Element structure that describes a vertex attribute. + /// + public VertexElement VertexElement = vertexElement; + + /// + /// The offset in bytes from the start of the vertex to this element. + /// + public int Offset = offset; - public VertexElementWithOffset(VertexElement vertexElement, int offset, int size) - { - VertexElement = vertexElement; - Offset = offset; - Size = size; - } - } + /// + /// The size in bytes of this element within the vertex structure. + /// + public int Size = size; } diff --git a/sources/engine/Stride.Graphics/ViewType.cs b/sources/engine/Stride.Graphics/ViewType.cs index 15066867dc..bd45599e45 100644 --- a/sources/engine/Stride.Graphics/ViewType.cs +++ b/sources/engine/Stride.Graphics/ViewType.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -20,81 +20,94 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -namespace Stride.Graphics + +namespace Stride.Graphics; + +/// +/// Defines what sub-resources (mip-levels, array elements) from a are +/// selected by a View. +/// +/// +/// This selection model is taken from Nuaj by Patapom (). +/// +public enum ViewType { /// - /// Defines how a view is selected from a resource. + /// Gets a Texture View for the whole Texture and for all mips/arrays dimensions. /// /// - /// This selection model is taken from Nuaj by Patapom (http://wiki.patapom.com/index.php/Nuaj) + /// Here is an example of what the resulting View will cover with a Texture Array of 3, each with 3 mip levels: + /// + /// Array slice + /// 0 1 2 + /// ┌───┬───┬───┐ + /// 0 │ ▓ │ ▓ │ ▓ │ ■ = Selected + /// M ├───┼───┼───┤ □ = Not selected + /// i 1 │ ▓ │ ▓ │ ▓ │ + /// p ├───┼───┼───┤ + /// 2 │ ▓ │ ▓ │ ▓ │ + /// └───┴───┴───┘ + /// /// - public enum ViewType - { - /// - /// Gets a texture view for the whole texture for all mips/arrays dimensions. - /// - /// Here is what the view covers with whatever mipLevelIndex/arrayIndex - /// - /// Array0 Array1 Array2 - /// ______________________ - /// Mip0 | X | X | X | - /// |------+------+------| - /// Mip1 | X | X | X | - /// |------+------+------| - /// Mip2 | X | X | X | - /// ---------------------- - /// - Full = 0, + Full = 0, - /// - /// Gets a single texture view at the specified index in the mip hierarchy and in the array of textures - /// The texture view contains a single texture element at the specified mip level and array index - /// - /// Here is what the view covers with mipLevelIndex=1 and mrrayIndex=1 - /// - /// Array0 Array1 Array2 - /// ______________________ - /// Mip0 | | | | - /// |------+------+------| - /// Mip1 | | X | | - /// |------+------+------| - /// Mip2 | | | | - /// ---------------------- - /// - Single = 1, + /// + /// The Texture View contains a single Texture element at the specified mip level and array index. + /// + /// + /// Here is an example of what the resulting View will cover with a Texture Array of 3, each with 3 mip levels + /// when specifying a mipmap level index of 1, and a array index of 1: + /// + /// Array slice + /// 0 1 2 + /// ┌───┬───┬───┐ + /// 0 │ │ │ │ ■ = Selected + /// M ├───┼───┼───┤ □ = Not selected + /// i 1 │ │ ▓ │ │ + /// p ├───┼───┼───┤ + /// 2 │ │ │ │ + /// └───┴───┴───┘ + /// + /// + Single = 1, - /// - /// Gets a band texture view at the specified index in the mip hierarchy and in the array of textures - /// The texture view contains all the mip level texture elements from the specified mip level and array index - /// - /// Here is what the view covers with mipLevelIndex=1 and mrrayIndex=1 - /// - /// Array0 Array1 Array2 - /// ______________________ - /// Mip0 | | | | - /// |------+------+------| - /// Mip1 | | X | | - /// |------+------+------| - /// Mip2 | | X | | - /// ---------------------- - /// - ArrayBand = 2, + /// + /// A band Texture View containing all the mip level Texture elements from the specified mip level and array index. + /// + /// + /// Here is an example of what the resulting View will cover with a Texture Array of 3, each with 3 mip levels + /// when specifying a mipmap level index of 1, and a array index of 1: + /// + /// Array slice + /// 0 1 2 + /// ┌───┬───┬───┐ + /// 0 │ │ │ │ ■ = Selected + /// M ├───┼───┼───┤ □ = Not selected + /// i 1 │ │ ▓ │ │ + /// p ├───┼───┼───┤ + /// 2 │ │ ▓ │ │ + /// └───┴───┴───┘ + /// + /// + ArrayBand = 2, - /// - /// Gets a band texture view at the specified index in the mip hierarchy and in the array of textures - /// The texture view contains all the array texture elements from the specified mip level and array index - /// - /// Here is what the view covers with mipLevelIndex=1 and mrrayIndex=1 - /// - /// Array0 Array1 Array2 - /// ______________________ - /// Mip0 | | | | - /// |------+------+------| - /// Mip1 | | X | X | - /// |------+------+------| - /// Mip2 | | | | - /// ---------------------- - /// - MipBand = 3, - } + /// + /// A band Texture View containing all the array Texture elements from the specified mip level and array index. + /// + /// + /// Here is an example of what the resulting View will cover with a Texture Array of 3, each with 3 mip levels + /// when specifying a mipmap level index of 1, and a array index of 1: + /// + /// Array slice + /// 0 1 2 + /// ┌───┬───┬───┐ + /// 0 │ │ │ │ ■ = Selected + /// M ├───┼───┼───┤ □ = Not selected + /// i 1 │ │ ▓ │ ▓ │ + /// p ├───┼───┼───┤ + /// 2 │ │ │ │ + /// └───┴───┴───┘ + /// + /// + MipBand = 3 } diff --git a/sources/engine/Stride.Graphics/Viewport.cs b/sources/engine/Stride.Graphics/Viewport.cs index 585de4ee07..0ebf88dd78 100644 --- a/sources/engine/Stride.Graphics/Viewport.cs +++ b/sources/engine/Stride.Graphics/Viewport.cs @@ -1,48 +1,59 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -using System.Globalization; using System.Runtime.InteropServices; using Stride.Core.Mathematics; namespace Stride.Graphics { /// - /// Defines the window dimensions of a render-target surface onto which a 3D volume projects. + /// Defines the viewport dimensions of a render-target surface onto which a 3D volume projects. /// [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct Viewport : IEquatable { /// - /// Empty value for an undefined viewport. + /// Empty value for an undefined viewport. /// public static readonly Viewport Empty; /// - /// Gets or sets the pixel coordinate of the upper-left corner of the viewport on the render-target surface. + /// X coordinate of the upper-left corner of the viewport on the render-target surface, in pixels. /// public float X; - /// Gets or sets the pixel coordinate of the upper-left corner of the viewport on the render-target surface. + /// + /// Y coordinate of the upper-left corner of the viewport on the render-target surface, in pixels. + /// public float Y; - /// Gets or sets the width dimension of the viewport on the render-target surface, in pixels. + /// + /// Width dimension of the viewport on the render-target surface, in pixels. + /// public float Width; - /// Gets or sets the height dimension of the viewport on the render-target surface, in pixels. + /// + /// Height dimension of the viewport on the render-target surface, in pixels. + /// public float Height; - /// Gets or sets the minimum depth of the clip volume. + /// + /// Minimum depth of the clip volume. + /// public float MinDepth; - /// Gets or sets the maximum depth of the clip volume. + /// + /// Maximum depth of the clip volume. + /// public float MaxDepth; + /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The x coordinate of the upper-left corner of the viewport in pixels. - /// The y coordinate of the upper-left corner of the viewport in pixels. + /// The X coordinate of the upper-left corner of the viewport in pixels. + /// The Y coordinate of the upper-left corner of the viewport in pixels. /// The width of the viewport in pixels. /// The height of the viewport in pixels. public Viewport(int x, int y, int width, int height) @@ -56,10 +67,10 @@ public Viewport(int x, int y, int width, int height) } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The x coordinate of the upper-left corner of the viewport in pixels. - /// The y coordinate of the upper-left corner of the viewport in pixels. + /// The X coordinate of the upper-left corner of the viewport in pixels. + /// The Y coordinate of the upper-left corner of the viewport in pixels. /// The width of the viewport in pixels. /// The height of the viewport in pixels. public Viewport(float x, float y, float width, float height) @@ -73,23 +84,26 @@ public Viewport(float x, float y, float width, float height) } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// A bounding box that defines the location and size of the viewport in a render target. + /// A rectangle that defines the location and size of the viewport in a render target. public Viewport(Rectangle bounds) { X = bounds.X; Y = bounds.Y; Width = bounds.Width; - Height = bounds.Height; + Height = bounds.Height; MinDepth = 0; - MaxDepth = 1; + MaxDepth = 1; } - /// Gets the size of this resource. + + /// + /// Gets a rectangle with the location and size of the viewport. + /// public Rectangle Bounds { - get { return new Rectangle((int)X, (int)Y, (int)Width, (int)Height); } + readonly get => new((int) X, (int) Y, (int) Width, (int) Height); set { X = value.X; @@ -99,30 +113,40 @@ public Rectangle Bounds } } - public bool Equals(Viewport other) + /// + /// Gets the aspect ratio of the viewport, i.e. Width / Height. + /// + public readonly float AspectRatio => Width != 0 && Height != 0 ? Width / Height : 0; + + /// + /// Gets the size of the viewport. + /// + public readonly Vector2 Size => new(Width, Height); + + /// + public readonly bool Equals(Viewport other) { - return other.X.Equals(X) && other.Y.Equals(Y) && other.Width.Equals(Width) && other.Height.Equals(Height) && other.MinDepth.Equals(MinDepth) && other.MaxDepth.Equals(MaxDepth); + return other.X.Equals(X) + && other.Y.Equals(Y) + && other.Width.Equals(Width) + && other.Height.Equals(Height) + && other.MinDepth.Equals(MinDepth) + && other.MaxDepth.Equals(MaxDepth); } - public override bool Equals(object obj) + /// + public override readonly bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; - if (obj.GetType() != typeof(Viewport)) return false; - return Equals((Viewport)obj); + if (obj is null) + return false; + + return obj is Viewport viewport && Equals(viewport); } - public override int GetHashCode() + /// + public override readonly int GetHashCode() { - unchecked - { - int result = X.GetHashCode(); - result = (result * 397) ^ Y.GetHashCode(); - result = (result * 397) ^ Width.GetHashCode(); - result = (result * 397) ^ Height.GetHashCode(); - result = (result * 397) ^ MinDepth.GetHashCode(); - result = (result * 397) ^ MaxDepth.GetHashCode(); - return result; - } + return HashCode.Combine(X, Y, Width, Height, MinDepth, MaxDepth); } public static bool operator ==(Viewport left, Viewport right) @@ -135,84 +159,76 @@ public override int GetHashCode() return !left.Equals(right); } - /// Retrieves a string representation of this object. - public override string ToString() + /// + /// Returns a string representation of this viewport. + /// + public override readonly string ToString() { - return string.Format(CultureInfo.CurrentCulture, "{{X:{0} Y:{1} Width:{2} Height:{3} MinDepth:{4} MaxDepth:{5}}}", new object[] { X, Y, Width, Height, MinDepth, MaxDepth }); + return FormattableString.CurrentCulture($"{{X:{X} Y:{Y} Width:{Width} Height:{Height} MinDepth:{MinDepth} MaxDepth:{MaxDepth}}}"); } private static bool WithinEpsilon(float a, float b) { - float num = a - b; - return ((num >= -1.401298E-45f) && (num <= float.Epsilon)); + float difference = a - b; + return (difference >= -1.401298E-45f) && (difference <= float.Epsilon); } - /// Projects a 3D vector from object space into screen space. + /// + /// Projects a 3D vector from object space into screen space. + /// /// The vector to project. /// The projection matrix. /// The view matrix. /// The world matrix. - public Vector3 Project(Vector3 source, Matrix projection, Matrix view, Matrix world) + public readonly Vector3 Project(Vector3 source, Matrix projection, Matrix view, Matrix world) { - Matrix matrix = Matrix.Multiply(Matrix.Multiply(world, view), projection); - Vector4 vector = Vector3.Transform(source, matrix); - float a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44; - if (!WithinEpsilon(a, 1f)) + Matrix worldViewProj = Matrix.Multiply(Matrix.Multiply(world, view), projection); + Vector4 vector = Vector3.Transform(source, worldViewProj); + + float w = (source.X * worldViewProj.M14) + (source.Y * worldViewProj.M24) + (source.Z * worldViewProj.M34) + worldViewProj.M44; + if (!WithinEpsilon(w, 1)) { - vector = (vector / a); + vector /= w; } - vector.X = (((vector.X + 1f) * 0.5f) * Width) + X; - vector.Y = (((-vector.Y + 1f) * 0.5f) * Height) + Y; + vector.X = ((vector.X + 1) * 0.5f * Width) + X; + vector.Y = ((-vector.Y + 1) * 0.5f * Height) + Y; vector.Z = (vector.Z * (MaxDepth - MinDepth)) + MinDepth; return new Vector3(vector.X, vector.Y, vector.Z); } - /// Converts a screen space point into a corresponding point in world space. - /// The vector to project. + /// + /// Converts a screen space point into a corresponding point in world space. + /// + /// The vector to unproject. /// The projection matrix. /// The view matrix. /// The world matrix. public Vector3 Unproject(Vector3 source, Matrix projection, Matrix view, Matrix world) { - Matrix matrix = Matrix.Multiply(Matrix.Multiply(world, view), projection); - return Unproject(source, ref matrix); + Matrix worldViewProj = Matrix.Multiply(Matrix.Multiply(world, view), projection); + return Unproject(source, in worldViewProj); } - /// Converts a screen space point into a corresponding point in world space. - /// The vector to project. + /// + /// Converts a screen space point into a corresponding point in world space. + /// + /// The vector to unproject. /// The World-View-Projection matrix. - public Vector3 Unproject(Vector3 source, ref Matrix worldViewProjection) + public readonly Vector3 Unproject(Vector3 source, ref readonly Matrix worldViewProjection) { - Matrix matrix = Matrix.Invert(worldViewProjection); + Matrix invWorldViewProj = Matrix.Invert(worldViewProjection); - source.X = (((source.X - X) / Width) * 2f) - 1f; - source.Y = -((((source.Y - Y) / Height) * 2f) - 1f); + source.X = ((source.X - X) / Width * 2) - 1; + source.Y = -(((source.Y - Y) / Height * 2) - 1); source.Z = (source.Z - MinDepth) / (MaxDepth - MinDepth); - Vector4 vector = Vector3.Transform(source, matrix); - float a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44; - if (!WithinEpsilon(a, 1f)) - { - vector = vector / a; - } - return new Vector3(vector.X, vector.Y, vector.Z); - } + Vector4 vector = Vector3.Transform(source, invWorldViewProj); - /// Gets the aspect ratio used by the viewport - public float AspectRatio - { - get + float w = (source.X * invWorldViewProj.M14) + (source.Y * invWorldViewProj.M24) + (source.Z * invWorldViewProj.M34) + invWorldViewProj.M44; + if (!WithinEpsilon(w, 1)) { - if (Width != 0 && Height != 0) - { - return Width / Height; - } - return 0f; + vector /= w; } + return new Vector3(vector.X, vector.Y, vector.Z); } - - /// - /// Gets the size of the viewport (Width, Height). - /// - public Vector2 Size => new Vector2(Width, Height); } } diff --git a/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs index a532eba672..b2709fbbc0 100644 --- a/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/Buffer.Vulkan.cs @@ -21,7 +21,7 @@ public partial class Buffer /// Type of the buffer. /// The view format. /// The data pointer. - protected Buffer InitializeFromImpl(BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) + protected partial Buffer InitializeFromImpl(ref readonly BufferDescription description, BufferFlags viewFlags, PixelFormat viewFormat, IntPtr dataPointer) { bufferDescription = description; //nativeDescription = ConvertToNativeDescription(Description); diff --git a/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs index 0830f13bd9..f86edfb847 100644 --- a/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/CommandList.Vulkan.cs @@ -3,13 +3,15 @@ #if STRIDE_GRAPHICS_API_VULKAN using System; using System.Collections.Generic; +using System.Data; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using Vortice.Vulkan; -using static Vortice.Vulkan.Vulkan; using Stride.Core; using Stride.Core.Collections; using Stride.Core.Mathematics; -using System.Runtime.CompilerServices; +using Stride.Core.UnsafeExtensions; +using Vortice.Vulkan; +using static Vortice.Vulkan.Vulkan; namespace Stride.Graphics { @@ -57,7 +59,10 @@ private void Recreate() Reset(); } - public unsafe void Reset() + /// + /// Resets a Command List back to its initial state as if a new Command List was just created. + /// + public unsafe partial void Reset() { if (currentCommandList.Builder != null) return; @@ -84,10 +89,13 @@ public unsafe void Reset() } /// - /// Closes the command list for recording and returns an executable token. + /// Indicates that recording to the Command List has finished. /// - /// The executable command list. - public CompiledCommandList Close() + /// + /// A representing the frozen list of recorded commands + /// that can be executed at a later time. + /// + public partial CompiledCommandList Close() { // End active render pass CleanupRenderPass(); @@ -109,9 +117,9 @@ public CompiledCommandList Close() } /// - /// Closes and executes the command list. + /// Closes and executes the Command List. /// - public void Flush() + public partial void Flush() { GraphicsDevice.ExecuteCommandList(Close()); } @@ -137,7 +145,10 @@ private unsafe void FlushInternal(bool wait) SetRenderTargetsImpl(depthStencilBuffer, renderTargetCount, renderTargets); } - private void ClearStateImpl() + /// + /// Vulkan-specific implementation that clears and restores the state of the Graphics Device. + /// + private partial void ClearStateImpl() { } @@ -233,6 +244,25 @@ public void UnsetRenderTargets() { } + /// + /// Vulkan implementation that sets a scissor rectangle to the rasterizer stage. + /// + /// The scissor rectangle to set. + private unsafe partial void SetScissorRectangleImpl(ref Rectangle scissorRectangle) + { + // Do nothing. Vulkan already sets the scissor rectangle as part of PrepareDraw() + } + + /// + /// Vulkan implementation that sets one or more scissor rectangles to the rasterizer stage. + /// + /// The number of scissor rectangles to bind. + /// The set of scissor rectangles to bind. + private unsafe partial void SetScissorRectanglesImpl(int scissorCount, Rectangle[] scissorRectangles) + { + // Do nothing. Vulkan already sets the scissor rectangles as part of PrepareDraw() + } + /// /// Prepares a draw call. This method is called before each Draw() method to setup the correct Primitive, InputLayout and VertexBuffers. /// @@ -823,7 +853,7 @@ public unsafe void Copy(GraphicsResource source, GraphicsResource destination) sourceTexture.Height != destinationTexture.Height || sourceTexture.Depth != destinationTexture.Depth || sourceTexture.ArraySize != destinationTexture.ArraySize || - sourceTexture.MipLevels != destinationTexture.MipLevels) + sourceTexture.MipLevelCount != destinationTexture.MipLevelCount) throw new InvalidOperationException($"{nameof(source)} and {nameof(destination)} textures don't match"); CleanupRenderPass(); @@ -858,14 +888,14 @@ public unsafe void Copy(GraphicsResource source, GraphicsResource destination) vkCmdPipelineBarrier(currentCommandList.NativeCommandBuffer, sourceTexture.NativePipelineStageMask | destinationParent.NativePipelineStageMask, VkPipelineStageFlags.Transfer, VkDependencyFlags.None, memoryBarrierCount: 0, memoryBarriers: null, bufferBarrierCount, bufferBarriers, imageBarrierCount, imageBarriers); - for (var subresource = 0; subresource < sourceTexture.MipLevels * sourceTexture.ArraySize; ++subresource) + for (var subresource = 0; subresource < sourceTexture.MipLevelCount * sourceTexture.ArraySize; ++subresource) { - var arraySlice = subresource / sourceTexture.MipLevels; - var mipLevel = subresource % sourceTexture.MipLevels; + var arraySlice = subresource / sourceTexture.MipLevelCount; + var mipLevel = subresource % sourceTexture.MipLevelCount; var sourceOffset = sourceTexture.ComputeBufferOffset(subresource, depthSlice: 0); var destinationOffset = destinationTexture.ComputeBufferOffset(subresource, depthSlice: 0); - var size = sourceTexture.ComputeSubresourceSize(subresource); + var size = sourceTexture.ComputeSubResourceSize(subresource); var width = Texture.CalculateMipSize(sourceTexture.Width, mipLevel); var height = Texture.CalculateMipSize(sourceTexture.Height, mipLevel); @@ -1001,7 +1031,7 @@ public unsafe void CopyRegion(GraphicsResource source, int sourceSubresource, Re { CleanupRenderPass(); - var mipmapDescription = sourceTexture.GetMipMapDescription(sourceSubresource % sourceTexture.MipLevels); + var mipmapDescription = sourceTexture.GetMipMapDescription(sourceSubresource % sourceTexture.MipLevelCount); var region = sourceRegion ?? new ResourceRegion(left: 0, top: 0, front: 0, mipmapDescription.Width, mipmapDescription.Height, mipmapDescription.Depth); @@ -1147,23 +1177,61 @@ public void CopyCount(Buffer sourceBuffer, Buffer destBuffer, int offsetInBytes) throw new NotImplementedException(); } - internal void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// is . + /// + /// + /// If is a Constant Buffer, it must be updated in full. + /// It is not possible to use this method to partially update a Constant Buffer. + /// + /// + /// A Graphics Resource cannot be used as a destination if: + /// + /// The resource was created with or . + /// The resource was created as a Depth-Stencil Buffer. + /// The resource is a Texture created with multi-sampling capability (see ). + /// + /// + /// + /// When returns, the application is free to change or even free the data pointed to by + /// because the method has already copied/snapped away the original contents. + /// + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData) + { + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + fixed (byte* sourceDataPtr = sourceData) + { + UpdateSubResource(resource, subResourceIndex, new DataBox((nint) sourceDataPtr, sourceData.Length, 0)); + } + } + + internal void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox databox) { if (resource is Texture texture) { - var mipLevel = subResourceIndex % texture.MipLevels; + var mipLevel = subResourceIndex % texture.MipLevelCount; var width = Texture.CalculateMipSize(texture.Width, mipLevel); var height = Texture.CalculateMipSize(texture.Height, mipLevel); var depth = Texture.CalculateMipSize(texture.Depth, mipLevel); - UpdateSubresource(resource, subResourceIndex, databox, new ResourceRegion(left: 0, top: 0, front: 0, width, height, depth)); + UpdateSubResource(resource, subResourceIndex, databox, new ResourceRegion(left: 0, top: 0, front: 0, width, height, depth)); } else { if (resource is Buffer buffer) { - UpdateSubresource(resource, subResourceIndex, databox, new ResourceRegion(left: 0, top: 0, front: 0, buffer.SizeInBytes, bottom: 1, back: 1)); + UpdateSubResource(resource, subResourceIndex, databox, new ResourceRegion(left: 0, top: 0, front: 0, buffer.SizeInBytes, bottom: 1, back: 1)); } else { @@ -1172,7 +1240,40 @@ internal void UpdateSubresource(GraphicsResource resource, int subResourceIndex, } } - internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) + /// + /// Copies data from memory to a sub-resource created in non-mappable memory. + /// + /// The destination Graphics Resource to copy data to. + /// The sub-resource index of to copy data to. + /// The source data in CPU memory to copy. + /// + /// + /// A that defines the portion of the destination sub-resource to copy the resource data into. + /// Coordinates are in bytes for Buffers and in texels for Textures. + /// The dimensions of the source must fit the destination. + /// + /// + /// An empty region makes this method to not perform a copy operation. + /// It is considered empty if the top value is greater than or equal to the bottom value, + /// or the left value is greater than or equal to the right value, or the front value is greater than or equal to the back value. + /// + /// + /// is . + /// + internal unsafe void UpdateSubResource(GraphicsResource resource, int subResourceIndex, ReadOnlySpan sourceData, ResourceRegion region) + { + ArgumentNullException.ThrowIfNull(resource); + + if (sourceData.IsEmpty) + return; + + fixed (byte* sourceDataPtr = sourceData) + { + UpdateSubResource(resource, subResourceIndex, new DataBox((nint)sourceDataPtr, sourceData.Length, 0), region); + } + } + + internal unsafe partial void UpdateSubResource(GraphicsResource resource, int subResourceIndex, DataBox databox, ResourceRegion region) { // Barriers need to be global to command buffer CleanupRenderPass(); @@ -1204,8 +1305,8 @@ internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourc if (texture != null) { - var mipSlice = subResourceIndex % texture.MipLevels; - var arraySlice = subResourceIndex / texture.MipLevels; + var mipSlice = subResourceIndex % texture.MipLevelCount; + var arraySlice = subResourceIndex / texture.MipLevelCount; var subresourceRange = new VkImageSubresourceRange(VkImageAspectFlags.Color, (uint) mipSlice, levelCount: 1, (uint) arraySlice, 1); var memoryBarrier = new VkImageMemoryBarrier(texture.NativeImage, subresourceRange, texture.NativeAccessMask, VkAccessFlags.TransferWrite, texture.NativeLayout, VkImageLayout.TransferDstOptimal); @@ -1268,7 +1369,7 @@ internal unsafe void UpdateSubresource(GraphicsResource resource, int subResourc /// The offset information in bytes. /// The length information in bytes. /// Pointer to the sub resource to map. - public unsafe MappedResource MapSubresource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) + public unsafe partial MappedResource MapSubResource(GraphicsResource resource, int subResourceIndex, MapMode mapMode, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) { if (resource == null) throw new ArgumentNullException("resource"); @@ -1279,8 +1380,8 @@ public unsafe MappedResource MapSubresource(GraphicsResource resource, int subRe { usage = texture.Usage; if (lengthInBytes == 0) - lengthInBytes = texture.ComputeSubresourceSize(subResourceIndex); - rowPitch = texture.ComputeRowPitch(subResourceIndex % texture.MipLevels); + lengthInBytes = texture.ComputeSubResourceSize(subResourceIndex); + rowPitch = texture.ComputeRowPitch(subResourceIndex % texture.MipLevelCount); offsetInBytes += texture.ComputeBufferOffset(subResourceIndex, depthSlice: 0); } @@ -1337,7 +1438,7 @@ public unsafe MappedResource MapSubresource(GraphicsResource resource, int subRe } // TODO GRAPHICS REFACTOR what should we do with this? - public void UnmapSubresource(MappedResource unmapped) + public unsafe partial void UnmapSubResource(MappedResource unmapped) { vkUnmapMemory(GraphicsDevice.NativeDevice, unmapped.Resource.NativeMemory); } diff --git a/sources/engine/Stride.Graphics/Vulkan/DisplayMode.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/DisplayMode.Vulkan.cs index 4aba0f45ba..438df6fd62 100644 --- a/sources/engine/Stride.Graphics/Vulkan/DisplayMode.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/DisplayMode.Vulkan.cs @@ -7,17 +7,27 @@ namespace Stride.Graphics { - public partial class DisplayMode + public partial record struct DisplayMode { + ///// + ///// Gets a Vulkan from the display mode. + ///// + ///// Returns a . //internal ModeDescription ToDescription() //{ - // return new ModeDescription(Width, Height, RefreshRate.ToSharpDX(), format: (SharpDX.DXGI.Format)Format); + // return new ModeDescription(Width, Height, RefreshRate.ToSharpDX(), (SharpDX.DXGI.Format) Format); //} + ///// + ///// Gets a from a Vulkan structure. + ///// + ///// The Vulkan structure. + ///// A corresponding . //internal static DisplayMode FromDescription(ModeDescription description) //{ - // return new DisplayMode((PixelFormat)description.Format, description.Width, description.Height, new Rational(description.RefreshRate.Numerator, description.RefreshRate.Denominator)); + // return new DisplayMode((PixelFormat) description.Format, description.Width, description.Height, new Rational(description.RefreshRate.Numerator, description.RefreshRate.Denominator)); //} } } + #endif diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapter.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapter.Vulkan.cs index d6fb3206b3..ad576f261c 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapter.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsAdapter.Vulkan.cs @@ -1,26 +1,23 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_VULKAN + using System; using System.Collections.Generic; -using System.Resources; using System.Runtime.InteropServices; -using System.Text; using Vortice.Vulkan; -using static Vortice.Vulkan.Vulkan; -using Stride.Core; -using ComponentBase = Stride.Core.ComponentBase; -using Utilities = Stride.Core.Utilities; +using static Vortice.Vulkan.Vulkan; namespace Stride.Graphics { /// /// Provides methods to retrieve and manipulate graphics adapters. This is the equivalent to . /// - /// ff471329 - /// IDXGIAdapter1 - /// IDXGIAdapter1 + /// ff471329 + /// IDXGIAdapter1 + /// IDXGIAdapter1 public partial class GraphicsAdapter { private VkPhysicalDevice defaultPhysicalDevice; @@ -56,7 +53,7 @@ internal GraphicsAdapter(VkPhysicalDevice defaultPhysicalDevice, int adapterOrdi //outputs = new GraphicsOutput[displayProperties.Length]; //for (var i = 0; i < outputs.Length; i++) // outputs[i] = new GraphicsOutput(this, displayProperties[i], i).DisposeBy(this); - outputs = new[] { new GraphicsOutput() }; + graphicsOutputs = [ new GraphicsOutput() ]; } /// @@ -132,5 +129,6 @@ public bool IsProfileSupported(GraphicsProfile graphicsProfile) //return SharpDX.Direct3D11.Device.IsSupportedFeatureLevel(this.NativeAdapter, (SharpDX.Direct3D.FeatureLevel)graphicsProfile); } } -} +} + #endif diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs index 97b3bd53f1..b862606b47 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsDevice.Vulkan.cs @@ -159,9 +159,9 @@ public void Begin() } /// - /// Enables profiling. + /// Enables or disables profiling. /// - /// if set to true [enabled flag]. + /// to enable profiling; to disable it. public void EnableProfile(bool enabledFlag) { } @@ -230,14 +230,14 @@ public unsafe void ExecuteCommandLists(int count, CompiledCommandList[] commandL graphicsResourceLinkCollector.Release(); } - private void InitializePostFeatures() + /// + /// Initializes the platform-specific features of the Graphics Device once it has been fully initialized. + /// + private unsafe partial void InitializePostFeatures() { } - private string GetRendererName() - { - return rendererName; - } + private partial string GetRendererName() => rendererName; public void SimulateReset() { @@ -245,12 +245,12 @@ public void SimulateReset() } /// - /// Initializes the specified device. + /// Initialize the platform-specific implementation of the Graphics Device. /// - /// The graphics profiles. + /// A non- list of the graphics profiles to try, in order of preference. /// The device creation flags. /// The window handle. - private unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) + private unsafe partial void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle) { if (nativeDevice != VkDevice.Null) { @@ -455,11 +455,18 @@ protected unsafe void AllocateMemory(VkMemoryPropertyFlags memoryProperties) vkBindBufferMemory(NativeDevice, nativeUploadBuffer, nativeUploadBufferMemory, 0); } - private void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) + /// + /// Makes Vulkan-specific adjustments to the Pipeline State objects created by the Graphics Device. + /// + /// A Pipeline State description that can be modified and adjusted. + private partial void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription) { } - protected void DestroyPlatformDevice() + /// + /// Releases the platform-specific Graphics Device and all its associated resources. + /// + protected partial void DestroyPlatformDevice() { ReleaseDevice(); } @@ -647,7 +654,14 @@ internal void Collect(NativeResource nativeResource) nativeResourceCollector.Add(NextFenceValue, nativeResource); } - internal void TagResource(GraphicsResourceLink resourceLink) + /// + /// Tags a Graphics Resource as no having alive references, meaning it should be safe to dispose it + /// or discard its contents during the next or SetData operation. + /// + /// + /// A object identifying the Graphics Resource along some related allocation information. + /// + internal partial void TagResourceAsNotAlive(GraphicsResourceLink resourceLink) { switch (resourceLink.Resource) { @@ -726,7 +740,7 @@ protected virtual void DestroyObject(T obj) protected override void Destroy() { lock (liveObjects) - { + { foreach (var item in liveObjects) { DestroyObject(item.Value); @@ -945,7 +959,7 @@ protected override void ReleaseObject(NativeResource item) item.Destroy(GraphicsDevice); } } - + internal abstract class TemporaryResourceCollector : IDisposable { protected readonly GraphicsDevice GraphicsDevice; diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsDeviceFeatures.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsDeviceFeatures.Vulkan.cs index 8be7dc0a5f..b8e0071368 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsDeviceFeatures.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsDeviceFeatures.Vulkan.cs @@ -1,10 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_GRAPHICS_API_VULKAN -using System; -using System.Collections.Generic; -using Vortice.Vulkan; -using static Vortice.Vulkan.Vulkan; namespace Stride.Graphics { @@ -39,13 +36,13 @@ internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) HasDepthAsSRV = true; HasDepthAsReadOnlyRT = true; - HasMultisampleDepthAsSRV = true; + HasMultiSampleDepthAsSRV = true; HasResourceRenaming = false; // TODO D3D12 for (int i = 0; i < mapFeaturesPerFormat.Length; i++) - mapFeaturesPerFormat[i] = new FeaturesPerFormat((PixelFormat)i, MultisampleCount.None, FormatSupport.None); + mapFeaturesPerFormat[i] = new FeaturesPerFormat((PixelFormat) i, MultisampleCount.None, ComputeShaderFormatSupport.None, FormatSupport.None); //// Check features for each DXGI.Format //foreach (var format in Enum.GetValues(typeof(SharpDX.DXGI.Format))) //{ @@ -69,4 +66,5 @@ internal GraphicsDeviceFeatures(GraphicsDevice deviceRoot) } } } + #endif diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsOutput.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsOutput.Vulkan.cs index f07d27f4d3..ca7ecd0749 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsOutput.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsOutput.Vulkan.cs @@ -15,14 +15,17 @@ namespace Stride.Graphics /// /// Provides methods to retrieve and manipulate an graphics output (a monitor), it is equivalent to . /// - /// bb174546 - /// IDXGIOutput - /// IDXGIOutput + /// bb174546 + /// IDXGIOutput + /// IDXGIOutput public partial class GraphicsOutput { private readonly VkDisplayPropertiesKHR displayProperties; private readonly int outputIndex; + // TODO VULKAN + internal GraphicsOutput() { } // Here for GraphicsAdapter.Vulkan to be able to create a GraphicsOutput without an adapter + /// /// Initializes a new instance of . /// @@ -37,7 +40,7 @@ internal GraphicsOutput(GraphicsAdapter adapter, VkDisplayPropertiesKHR displayP this.outputIndex = outputIndex; this.displayProperties = displayProperties; - desktopBounds = new Rectangle(0, 0, (int)displayProperties.physicalResolution.width, (int)displayProperties.physicalResolution.height); + DesktopBounds = new Rectangle(0, 0, (int)displayProperties.physicalResolution.width, (int)displayProperties.physicalResolution.height); } /// @@ -91,9 +94,9 @@ public DisplayMode FindClosestMatchingDisplayMode(GraphicsProfile[] targetProfil /// /// Retrieves the handle of the monitor associated with this . /// - /// bb173068 - /// HMONITOR Monitor - /// HMONITOR Monitor + /// bb173068 + /// HMONITOR Monitor + /// HMONITOR Monitor public IntPtr MonitorHandle { get { return IntPtr.Zero; } } /// @@ -180,7 +183,7 @@ private void InitializeCurrentDisplayMode() /// /// The format to match with. /// A matched or null if nothing is found. - private DisplayMode TryFindMatchingDisplayMode(VkFormat format) + private DisplayMode? TryFindMatchingDisplayMode(VkFormat format) { //var desktopBounds = outputDescription.DesktopBounds; diff --git a/sources/engine/Stride.Graphics/Vulkan/GraphicsResourceBase.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/GraphicsResourceBase.Vulkan.cs index e54a90dcbe..657b54c577 100644 --- a/sources/engine/Stride.Graphics/Vulkan/GraphicsResourceBase.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/GraphicsResourceBase.Vulkan.cs @@ -14,15 +14,15 @@ namespace Stride.Graphics /// public abstract partial class GraphicsResourceBase { - private void Initialize() - { - } + // No Vulkan-specific initialization + private partial void Initialize() { } /// - /// Called when graphics device has been detected to be internally destroyed. + /// Called when the has been detected to be internally destroyed, + /// or when the methad has been called. Raises the event. /// - protected internal virtual void OnDestroyed() + protected internal virtual partial void OnDestroyed() { Destroyed?.Invoke(this, EventArgs.Empty); } @@ -37,5 +37,5 @@ protected internal virtual bool OnRecreate() } } } - + #endif diff --git a/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs index 7a17961b10..077e0fcef2 100644 --- a/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/PipelineState.Vulkan.cs @@ -130,7 +130,7 @@ private unsafe void RecreateInner() var renderTargetCount = Description.Output.RenderTargetCount; var colorBlendAttachments = new VkPipelineColorBlendAttachmentState[renderTargetCount]; - var renderTargetBlendState = &description.RenderTarget0; + var renderTargetBlendState = &description.RenderTargets[0]; for (int i = 0; i < renderTargetCount; i++) { colorBlendAttachments[i] = new VkPipelineColorBlendAttachmentState @@ -236,7 +236,7 @@ private unsafe void CreateRenderPass(PipelineStateDescription pipelineStateDescr var colorAttachmentReferences = new VkAttachmentReference[renderTargetCount]; fixed (PixelFormat* renderTargetFormat = &pipelineStateDescription.Output.RenderTargetFormat0) - fixed (BlendStateRenderTargetDescription* blendDescription = &pipelineStateDescription.BlendState.RenderTarget0) + fixed (BlendStateRenderTargetDescription* blendDescription = &pipelineStateDescription.BlendState.RenderTargets[0]) { for (int i = 0; i < renderTargetCount; i++) { diff --git a/sources/engine/Stride.Graphics/Vulkan/QueryPool.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/QueryPool.Vulkan.cs index 6532961eea..32241f898e 100644 --- a/sources/engine/Stride.Graphics/Vulkan/QueryPool.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/QueryPool.Vulkan.cs @@ -26,7 +26,13 @@ public unsafe bool TryGetData(long[] dataArray) return true; } - private unsafe void Recreate() + /// + /// Implementation in Vulkan that recreates the queries in the pool. + /// + /// + /// Only GPU queries of type are supported. + /// + private unsafe partial void Recreate() { var createInfo = new VkQueryPoolCreateInfo { diff --git a/sources/engine/Stride.Graphics/Vulkan/SamplerState.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/SamplerState.Vulkan.cs index 48c106d9d8..97f33314b9 100644 --- a/sources/engine/Stride.Graphics/Vulkan/SamplerState.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/SamplerState.Vulkan.cs @@ -22,7 +22,9 @@ public partial class SamplerState /// The device. /// The name. /// The sampler state description. - private SamplerState(GraphicsDevice device, SamplerStateDescription samplerStateDescription) : base(device) + /// An optional name that can be used to identify the Sampler State. + private SamplerState(GraphicsDevice device, ref readonly SamplerStateDescription samplerStateDescription, string? name = null) + : base(device, name) { Description = samplerStateDescription; @@ -198,5 +200,5 @@ private void ConvertMinFilter(TextureFilter filter, out VkFilter minFilter, out } } } -} +} #endif diff --git a/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs index 0309b8e839..f24f7ed517 100644 --- a/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/SwapChainGraphicsPresenter.Vulkan.cs @@ -122,7 +122,7 @@ public override bool IsFullScreen // Resize(backBuffer.ViewWidth, backBuffer.ViewHeight, backBuffer.ViewFormat); // } -// // If going to window mode: +// // If going to window mode: // if (!switchToFullScreen) // { // // call 1) SwapChain.IsFullScreen 2) SwapChain.Resize @@ -399,7 +399,7 @@ private unsafe void CreateBackBuffers() Depth = 1, Flags = TextureFlags.RenderTarget, Format = Description.BackBufferFormat, - MipLevels = 1, + MipLevelCount = 1, MultisampleCount = MultisampleCount.None, Usage = GraphicsResourceUsage.Default }; @@ -474,7 +474,7 @@ private unsafe void CreateBackBuffers() // Get next image vkAcquireNextImageKHR(GraphicsDevice.NativeDevice, swapChain, ulong.MaxValue, GraphicsDevice.GetNextPresentSemaphore(), VkFence.Null, out currentBufferIndex); - + // Apply the first swap chain image to the texture backbuffer.SetNativeHandles(swapchainImages[currentBufferIndex].NativeImage, swapchainImages[currentBufferIndex].NativeColorAttachmentView); } diff --git a/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs b/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs index c138ce2872..4077550736 100644 --- a/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs +++ b/sources/engine/Stride.Graphics/Vulkan/Texture.Vulkan.cs @@ -44,7 +44,11 @@ public static bool IsDepthStencilReadOnlySupported(GraphicsDevice device) return true; } - internal void SwapInternal(Texture other) + /// + /// Swaps the Texture's internal data with another Texture. + /// + /// The other Texture. + internal partial void SwapInternal(Texture other) { (NativeImage, other.NativeImage) = (other.NativeImage, NativeImage); (NativeBuffer, other.NativeBuffer) = (other.NativeBuffer, NativeBuffer); @@ -85,7 +89,7 @@ internal void SetNativeHandles(VkImage image, VkImageView attachmentView) NativeColorAttachmentView = attachmentView; } - private void InitializeFromImpl(DataBox[] dataBoxes = null) + private partial void InitializeFromImpl(DataBox[] dataBoxes = null) { NativeFormat = VulkanConvertExtensions.ConvertPixelFormat(ViewFormat); HasStencil = IsStencilFormat(ViewFormat); @@ -222,7 +226,7 @@ private unsafe void CreateImage() sType = VkStructureType.ImageCreateInfo, arrayLayers = (uint) ArraySize, extent = new VkExtent3D(Width, Height, Depth), - mipLevels = (uint) MipLevels, + mipLevels = (uint) MipLevelCount, samples = VkSampleCountFlags.Count1, format = NativeFormat, flags = VkImageCreateFlags.None, @@ -331,8 +335,8 @@ private unsafe void InitializeData(DataBox[] dataBoxes) { var slicePitch = dataBoxes[i].SlicePitch; - int arraySlice = i / MipLevels; - int mipSlice = i % MipLevels; + int arraySlice = i / MipLevelCount; + int mipSlice = i % MipLevelCount; var mipMapDescription = GetMipMapDescription(mipSlice); var alignment = ((uploadOffset + alignmentMask) & ~alignmentMask) - uploadOffset; @@ -347,7 +351,7 @@ private unsafe void InitializeData(DataBox[] dataBoxes) { srcOffset = (ulong) uploadOffset, dstOffset = (ulong) ComputeBufferOffset(i, depthSlice: 0), - size = (uint) ComputeSubresourceSize(i) + size = (uint) ComputeSubResourceSize(i) }; vkCmdCopyBuffer(commandBuffer, uploadResource, NativeBuffer, regionCount: 1, ©); @@ -462,7 +466,10 @@ protected internal override void OnDestroyed() base.OnDestroyed(); } - private void OnRecreateImpl() + /// + /// Perform Vulkan-specific recreation of the Texture. + /// + private partial void OnRecreateImpl() { // Dependency: wait for underlying texture to be recreated if (ParentTexture != null && ParentTexture.LifetimeState != GraphicsResourceLifetimeState.Active) @@ -503,12 +510,12 @@ private unsafe VkImageView GetImageView(ViewType viewType, int arrayOrDepthSlice subresourceRange = new VkImageSubresourceRange(IsDepthStencil ? VkImageAspectFlags.Depth : VkImageAspectFlags.Color, (uint) mipIndex, (uint) mipCount, (uint) arrayOrDepthSlice, (uint) layerCount) // TODO VULKAN: Select between depth and stencil? }; - if (IsMultisample) + if (IsMultiSampled) throw new NotImplementedException(); if (this.ArraySize > 1) { - if (IsMultisample && Dimension != TextureDimension.Texture2D) + if (IsMultiSampled && Dimension != TextureDimension.Texture2D) throw new NotSupportedException("Multisample is only supported for 2D Textures"); if (Dimension == TextureDimension.Texture3D) @@ -531,7 +538,7 @@ private unsafe VkImageView GetImageView(ViewType viewType, int arrayOrDepthSlice } else { - if (IsMultisample && Dimension != TextureDimension.Texture2D) + if (IsMultiSampled && Dimension != TextureDimension.Texture2D) throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); if (Dimension == TextureDimension.TextureCube) @@ -574,12 +581,12 @@ private unsafe VkImageView GetColorAttachmentView(ViewType viewType, int arrayOr subresourceRange = new VkImageSubresourceRange(VkImageAspectFlags.Color, (uint) mipIndex, (uint) mipCount, (uint) arrayOrDepthSlice, 1) }; - if (IsMultisample) + if (IsMultiSampled) throw new NotImplementedException(); if (this.ArraySize > 1) { - if (IsMultisample && Dimension != TextureDimension.Texture2D) + if (IsMultiSampled && Dimension != TextureDimension.Texture2D) throw new NotSupportedException("Multisample is only supported for 2D Textures"); if (Dimension == TextureDimension.Texture3D) @@ -587,7 +594,7 @@ private unsafe VkImageView GetColorAttachmentView(ViewType viewType, int arrayOr } else { - if (IsMultisample && Dimension != TextureDimension.Texture2D) + if (IsMultiSampled && Dimension != TextureDimension.Texture2D) throw new NotSupportedException("Multisample is only supported for 2D RenderTarget Textures"); if (Dimension == TextureDimension.TextureCube) @@ -633,7 +640,11 @@ private unsafe VkImageView GetDepthStencilView() return imageView; } - private bool IsFlipped() + /// + /// Indicates if the Texture is flipped vertically, i.e. if the rows are ordered bottom-to-top instead of top-to-bottom. + /// + /// if the Texture is flipped; otherwise. + private partial bool IsFlipped() { return false; } @@ -653,7 +664,7 @@ private static TextureDescription CheckMipLevels(GraphicsDevice device, ref Text { if (device.Features.CurrentProfile < GraphicsProfile.Level_10_0 && (description.Flags & TextureFlags.DepthStencil) == 0 && description.Format.IsCompressed()) { - description.MipLevels = Math.Min(CalculateMipCount(description.Width, description.Height), description.MipLevels); + description.MipLevelCount = Math.Min(CalculateMipCount(description.Width, description.Height), description.MipLevelCount); } return description; } diff --git a/sources/engine/Stride.Graphics/WindowHandle.cs b/sources/engine/Stride.Graphics/WindowHandle.cs index d5b96134ec..f94f01d39c 100644 --- a/sources/engine/Stride.Graphics/WindowHandle.cs +++ b/sources/engine/Stride.Graphics/WindowHandle.cs @@ -4,39 +4,30 @@ using System; using Stride.Games; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Encapsulates a platform-specific window handle. +/// +/// The context type, indicating the platform and UI framework. +/// +/// The native window instance (e.g. a Windows Forms' Form, a SDL's SDLWindow, etc.). +/// +/// The associated handle of . +public class WindowHandle(AppContextType context, object nativeWindow, IntPtr handle) { /// - /// A platform specific window handle. + /// The context type, indicating the platform and graphics API. /// - public class WindowHandle - { - /// - /// Initializes a new instance of the class. - /// - /// The context. - /// The native window instance (Winforms, SDLWindow, ...). - /// The associated handle of . - public WindowHandle(AppContextType context, object nativeWindow, IntPtr handle) - { - Context = context; - NativeWindow = nativeWindow; - Handle = handle; - } - - /// - /// The context. - /// - public readonly AppContextType Context; + public readonly AppContextType Context = context; - /// - /// The native windows as an opaque . - /// - public object NativeWindow { get; } + /// + /// Gets the native window as an opaque . + /// + public object NativeWindow { get; } = nativeWindow; - /// - /// The associated platform specific handle of . - /// - public IntPtr Handle { get; } - } + /// + /// Gets the associated platform-specific handle of . + /// + public IntPtr Handle { get; } = handle; } diff --git a/sources/engine/Stride.Particles/Rendering/ParticleEmitterRenderFeature.cs b/sources/engine/Stride.Particles/Rendering/ParticleEmitterRenderFeature.cs index 3e8d3bdc77..5c87936f83 100644 --- a/sources/engine/Stride.Particles/Rendering/ParticleEmitterRenderFeature.cs +++ b/sources/engine/Stride.Particles/Rendering/ParticleEmitterRenderFeature.cs @@ -250,7 +250,7 @@ private void BuildParticleBuffers(RenderDrawContext renderDrawContext) var renderParticleNodeData = RenderData.GetData(renderParticleNodeKey); - var mappedVertices = commandList.MapSubresource(particleBufferContext.VertexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, particleBufferContext.VertexBufferSize); + var mappedVertices = commandList.MapSubResource(particleBufferContext.VertexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, particleBufferContext.VertexBufferSize); var sharedBufferPtr = mappedVertices.DataBox.DataPointer; //for (int renderNodeIndex = 0; renderNodeIndex < RenderNodes.Count; renderNodeIndex++) @@ -274,7 +274,7 @@ private void BuildParticleBuffers(RenderDrawContext renderDrawContext) renderParticleEmitter.ParticleEmitter.BuildVertexBuffer(sharedBufferPtr + nodeData.VertexBufferOffset, ref viewInverse, ref renderNode.RenderView.ViewProjection); }); - commandList.UnmapSubresource(mappedVertices); + commandList.UnmapSubResource(mappedVertices); } /// @@ -448,7 +448,7 @@ public unsafe void AllocateBuffers(RenderDrawContext renderDrawContext, int vert { var commandList = renderDrawContext.CommandList; - var mappedIndices = commandList.MapSubresource(IndexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, IndexBufferSize); + var mappedIndices = commandList.MapSubResource(IndexBuffer, 0, MapMode.WriteNoOverwrite, false, 0, IndexBufferSize); var indexPointer = mappedIndices.DataBox.DataPointer; int indexStructSize = sizeof(short); @@ -465,7 +465,7 @@ public unsafe void AllocateBuffers(RenderDrawContext renderDrawContext, int vert *(short*)(indexPointer + indexStructSize * i++) = (short)(k + 3); } - commandList.UnmapSubresource(mappedIndices); + commandList.UnmapSubResource(mappedIndices); } } } diff --git a/sources/engine/Stride.Rendering/Rendering/Compositing/MSAAResolver.cs b/sources/engine/Stride.Rendering/Rendering/Compositing/MSAAResolver.cs index 7ecc13e9d5..38f18724f3 100644 --- a/sources/engine/Stride.Rendering/Rendering/Compositing/MSAAResolver.cs +++ b/sources/engine/Stride.Rendering/Rendering/Compositing/MSAAResolver.cs @@ -149,9 +149,9 @@ protected override void DrawCore(RenderDrawContext drawContext) throw new ArgumentNullException(nameof(input)); if (output == null) throw new ArgumentNullException(nameof(output)); - if (!input.IsMultisample) + if (!input.IsMultiSampled) throw new ArgumentOutOfRangeException(nameof(input), "Source texture is not a MSAA texture."); - if (output.IsMultisample) + if (output.IsMultiSampled) throw new ArgumentOutOfRangeException(nameof(input), "Destination texture is a MSAA texture."); // Prepare @@ -167,7 +167,7 @@ protected override void DrawCore(RenderDrawContext drawContext) FilterType == FilterTypes.Default) { // We currently only support the default hardware MSAA resolve on OpenGL and OpenGL ES. - drawContext.CommandList.CopyMultisample(input, 0, output, 0); + drawContext.CommandList.CopyMultiSampled(input, 0, output, 0); } else if (input.IsDepthStencil) { diff --git a/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGX.cs b/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGX.cs index 1ec87a7862..79fb7ac42b 100644 --- a/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGX.cs +++ b/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGX.cs @@ -85,7 +85,7 @@ protected override void DrawCore(RenderDrawContext context) var roughness = 0f; var faceCount = output.ArraySize; var levelSize = new Int2(output.Width, output.Height); - var mipCount = MipmapGenerationCount == 0 ? output.MipLevels : MipmapGenerationCount; + var mipCount = MipmapGenerationCount == 0 ? output.MipLevelCount : MipmapGenerationCount; for (int l = 0; l < mipCount; l++) { @@ -94,8 +94,8 @@ protected override void DrawCore(RenderDrawContext context) var inputLevel = MathUtil.Log2(input.Width / output.Width); for (int f = 0; f < 6; f++) { - var inputSubresource = inputLevel + f * input.MipLevels; - var outputSubresource = 0 + f * output.MipLevels; + var inputSubresource = inputLevel + f * input.MipLevelCount; + var outputSubresource = 0 + f * output.MipLevelCount; context.CommandList.CopyRegion(input, inputSubresource, null, output, outputSubresource); } } @@ -106,7 +106,7 @@ protected override void DrawCore(RenderDrawContext context) computeShader.ThreadGroupCounts = new Int3(levelSize.X, levelSize.Y, faceCount); computeShader.ThreadNumbers = new Int3(SamplingsCount, 1, 1); computeShader.Parameters.Set(RadiancePrefilteringGGXShaderKeys.Roughness, roughness); - computeShader.Parameters.Set(RadiancePrefilteringGGXShaderKeys.MipmapCount, input.MipLevels - 1); + computeShader.Parameters.Set(RadiancePrefilteringGGXShaderKeys.MipmapCount, input.MipLevelCount - 1); computeShader.Parameters.Set(RadiancePrefilteringGGXShaderKeys.RadianceMap, input); computeShader.Parameters.Set(RadiancePrefilteringGGXShaderKeys.RadianceMapSize, input.Width); computeShader.Parameters.Set(RadiancePrefilteringGGXShaderKeys.FilteredRadiance, outputView); diff --git a/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGXNoCompute.cs b/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGXNoCompute.cs index b9aceba33f..f92bb94a4c 100644 --- a/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGXNoCompute.cs +++ b/sources/engine/Stride.Rendering/Rendering/ComputeEffect/GGXPrefiltering/RadiancePrefilteringGGXNoCompute.cs @@ -90,7 +90,7 @@ protected override void DrawCore(RenderDrawContext context) var roughness = 0f; var faceCount = output.ArraySize; var levelSize = new Int2(output.Width, output.Height); - var mipCount = MipmapGenerationCount == 0 ? output.MipLevels : MipmapGenerationCount; + var mipCount = MipmapGenerationCount == 0 ? output.MipLevelCount : MipmapGenerationCount; for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) { @@ -100,16 +100,16 @@ protected override void DrawCore(RenderDrawContext context) var inputLevel = MathUtil.Log2(input.Width / output.Width); if (mipLevel == 0 && DoNotFilterHighestLevel) { - if (input.Width >= output.Width && inputLevel < input.MipLevels && input.Format == output.Format) + if (input.Width >= output.Width && inputLevel < input.MipLevelCount && input.Format == output.Format) { // Optimization: make a simple copy of the texture when possible - var inputSubresource = inputLevel + faceIndex * input.MipLevels; - var outputSubresource = 0 + faceIndex * output.MipLevels; + var inputSubresource = inputLevel + faceIndex * input.MipLevelCount; + var outputSubresource = 0 + faceIndex * output.MipLevelCount; context.CommandList.CopyRegion(input, inputSubresource, null, output, outputSubresource); } else // otherwise rescale the closest mipmap { - var inputMipmapLevel = Math.Min(inputLevel, input.MipLevels - 1); + var inputMipmapLevel = Math.Min(inputLevel, input.MipLevelCount - 1); using var inputView = input.ToTextureView(ViewType.Single, faceIndex, inputMipmapLevel); scaler.SetInput(inputView); scaler.SetOutput(outputView); @@ -120,7 +120,7 @@ protected override void DrawCore(RenderDrawContext context) { shader.Parameters.Set(RadiancePrefilteringGGXNoComputeShaderKeys.Face, faceIndex); shader.Parameters.Set(RadiancePrefilteringGGXNoComputeShaderKeys.Roughness, roughness); - shader.Parameters.Set(RadiancePrefilteringGGXNoComputeShaderKeys.MipmapCount, input.MipLevels - 1); + shader.Parameters.Set(RadiancePrefilteringGGXNoComputeShaderKeys.MipmapCount, input.MipLevelCount - 1); shader.Parameters.Set(RadiancePrefilteringGGXNoComputeShaderKeys.RadianceMap, input); shader.Parameters.Set(RadiancePrefilteringGGXNoComputeShaderKeys.RadianceMapSize, input.Width); shader.Parameters.Set(RadiancePrefilteringGGXNoComputeParams.NbOfSamplings, SamplingsCount); diff --git a/sources/engine/Stride.Rendering/Rendering/EffectSystem.cs b/sources/engine/Stride.Rendering/Rendering/EffectSystem.cs index 8fdd770f31..3fa7f9a594 100644 --- a/sources/engine/Stride.Rendering/Rendering/EffectSystem.cs +++ b/sources/engine/Stride.Rendering/Rendering/EffectSystem.cs @@ -63,9 +63,8 @@ public override void Initialize() { base.Initialize(); - // Get graphics device service - base.InitGraphicsDeviceService(); + InitializeGraphicsDeviceService(); #if STRIDE_PLATFORM_DESKTOP Enabled = true; @@ -127,7 +126,7 @@ public bool IsValid(Effect effect) return cachedEffects.ContainsKey(effect.Bytecode); } } - + /// /// Loads the effect. /// @@ -226,7 +225,7 @@ private Effect CreateEffect(string effectName, EffectBytecodeCompilerResult effe { filePath = reader.ReadToEnd(); } - } + } } if (filePath != null) directoryWatcher.Track(filePath); @@ -365,7 +364,7 @@ protected CompilerResults GetShaderFromParameters(string effectName, CompilerPar return null; // Compiler Parameters are supposed to be created in the same order every time, so we just check if they were created in the same order (ParameterKeyInfos) with same values (ObjectValues) - + // TODO GRAPHICS REFACTOR we could probably compute a hash for faster lookup foreach (var compiledResults in compilerResultsList) { diff --git a/sources/engine/Stride.Rendering/Rendering/Images/ImageEffectShader.cs b/sources/engine/Stride.Rendering/Rendering/Images/ImageEffectShader.cs index bf4d7e3ba4..89ed647d0e 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/ImageEffectShader.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/ImageEffectShader.cs @@ -18,9 +18,9 @@ public class ImageEffectShader : ImageEffect { private MutablePipelineState pipelineState; private bool pipelineStateDirty = true; - private BlendStateDescription blendState = BlendStateDescription.Default; - private DepthStencilStateDescription depthStencilState = DepthStencilStateDescription.Default; - private RasterizerStateDescription rasterizerState = RasterizerStateDescription.Default; + private BlendStateDescription blendState = BlendStates.Default; + private DepthStencilStateDescription depthStencilState = DepthStencilStates.Default; + private RasterizerStateDescription rasterizerState = RasterizerStates.Default; private EffectBytecode previousBytecode; private bool delaySetRenderTargets; diff --git a/sources/engine/Stride.Rendering/Rendering/Images/LightShafts/LightShafts.cs b/sources/engine/Stride.Rendering/Rendering/Images/LightShafts/LightShafts.cs index ffa9019295..c753ede15c 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/LightShafts/LightShafts.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/LightShafts/LightShafts.cs @@ -42,7 +42,7 @@ public class LightShafts : ImageEffect /// [DataMemberRange(1, 64, 1, 1, 0)] public int BoundingVolumeBufferDownsampleLevel { get; set; } = 8; - + /// /// Size of the orthographic projection used to find minimum bounding volume distance behind the camera /// @@ -90,7 +90,7 @@ protected override void InitializeCore() var meshRenderFeature = Context.RenderSystem.RenderFeatures.OfType().FirstOrDefault(); if (meshRenderFeature == null) throw new InvalidOperationException("Missing mesh render feature"); - + var forwardLightingFeature = meshRenderFeature.RenderFeatures.OfType().FirstOrDefault(); if (forwardLightingFeature == null) throw new InvalidOperationException("Missing forward lighting render feature"); @@ -102,11 +102,11 @@ protected override void InitializeCore() var minmaxPipelineState = new MutablePipelineState(Context.GraphicsDevice); minmaxPipelineState.State.SetDefaults(); - minmaxPipelineState.State.BlendState.RenderTarget0.BlendEnable = true; - minmaxPipelineState.State.BlendState.RenderTarget0.ColorSourceBlend = Blend.One; - minmaxPipelineState.State.BlendState.RenderTarget0.ColorDestinationBlend = Blend.One; - minmaxPipelineState.State.BlendState.RenderTarget0.ColorBlendFunction = i == 0 ? BlendFunction.Min : BlendFunction.Max; - minmaxPipelineState.State.BlendState.RenderTarget0.ColorWriteChannels = i == 0 ? ColorWriteChannels.Red : ColorWriteChannels.Green; + minmaxPipelineState.State.BlendState.RenderTargets[0].BlendEnable = true; + minmaxPipelineState.State.BlendState.RenderTargets[0].ColorSourceBlend = Blend.One; + minmaxPipelineState.State.BlendState.RenderTargets[0].ColorDestinationBlend = Blend.One; + minmaxPipelineState.State.BlendState.RenderTargets[0].ColorBlendFunction = i == 0 ? BlendFunction.Min : BlendFunction.Max; + minmaxPipelineState.State.BlendState.RenderTargets[0].ColorWriteChannels = i == 0 ? ColorWriteChannels.Red : ColorWriteChannels.Green; minmaxPipelineState.State.DepthStencilState.DepthBufferEnable = false; minmaxPipelineState.State.DepthStencilState.DepthBufferWriteEnable = false; @@ -193,13 +193,13 @@ protected override void DrawCore(RenderDrawContext context) var currentBoundingVolumes = (lightShaft.SeparateBoundingVolumes) ? singleBoundingVolume : lightShaft.BoundingVolumes; if (lightShaft.SeparateBoundingVolumes) singleBoundingVolume[0] = lightShaft.BoundingVolumes[i]; - + using (context.PushRenderTargetsAndRestore()) { // Clear bounding box buffer context.CommandList.Clear(boundingBoxBuffer, new Color4(1.0f, 0.0f, 0.0f, 0.0f)); context.CommandList.SetRenderTargetAndViewport(null, boundingBoxBuffer); - + // If nothing visible, skip second part if (!DrawBoundingVolumeMinMax(context, currentBoundingVolumes)) continue; @@ -221,7 +221,7 @@ protected override void DrawCore(RenderDrawContext context) { // Then: add var desc = BlendStates.Additive; - desc.RenderTarget0.ColorSourceBlend = Blend.One; // But without multiplying alpha + desc.RenderTargets[0].ColorSourceBlend = Blend.One; // But without multiplying alpha lightShaftsEffectShader.BlendState = desc; } diff --git a/sources/engine/Stride.Rendering/Rendering/Images/LocalReflections/LocalReflections.cs b/sources/engine/Stride.Rendering/Rendering/Images/LocalReflections/LocalReflections.cs index b09a41f4a4..057a03123f 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/LocalReflections/LocalReflections.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/LocalReflections/LocalReflections.cs @@ -503,7 +503,7 @@ protected override void DrawCore(RenderDrawContext context) int colorBuffer1MipOffset = 1; // For colorBuffer1 we could use one mip less (optimized) // Cache per color buffer mip views - int colorBuffer0Mips = colorBuffer0.MipLevels; + int colorBuffer0Mips = colorBuffer0.MipLevelCount; if (cachedColorBuffer0Mips == null || cachedColorBuffer0Mips.Length != colorBuffer0Mips || cachedColorBuffer0Mips[0].ParentTexture != colorBuffer0) { cachedColorBuffer0Mips?.ForEach(view => view?.Dispose()); @@ -513,7 +513,7 @@ protected override void DrawCore(RenderDrawContext context) cachedColorBuffer0Mips[mipIndex] = colorBuffer0.ToTextureView(ViewType.Single, 0, mipIndex); } } - int colorBuffer1Mips = colorBuffer1.MipLevels; + int colorBuffer1Mips = colorBuffer1.MipLevelCount; if (cachedColorBuffer1Mips == null || cachedColorBuffer1Mips.Length != colorBuffer1Mips || cachedColorBuffer1Mips[0].ParentTexture != colorBuffer1) { cachedColorBuffer1Mips?.ForEach(view => view?.Dispose()); diff --git a/sources/engine/Stride.Rendering/Rendering/Images/SubsurfaceScattering/SubsurfaceScatteringBlur.cs b/sources/engine/Stride.Rendering/Rendering/Images/SubsurfaceScattering/SubsurfaceScatteringBlur.cs index 4478bb97e2..8b434b80aa 100644 --- a/sources/engine/Stride.Rendering/Rendering/Images/SubsurfaceScattering/SubsurfaceScatteringBlur.cs +++ b/sources/engine/Stride.Rendering/Rendering/Images/SubsurfaceScattering/SubsurfaceScatteringBlur.cs @@ -183,7 +183,7 @@ private unsafe void UpdateKernelBuffer(RenderDrawContext context) { DataBox dataBox = new DataBox((IntPtr)dataPtr, 0, 0); ResourceRegion resourceRegion = new ResourceRegion(0, 0, 0, bufferSize, 1, 1); // TODO: PERFORMANCE: Only upload the actual number of elements active in the buffer? - context.CommandList.UpdateSubresource(materialScatteringKernelBuffer, 0, dataBox, resourceRegion); + context.CommandList.UpdateSubResource(materialScatteringKernelBuffer, 0, dataBox, resourceRegion); } SetValueParameterForBothShaders(SubsurfaceScatteringBlurShaderKeys.KernelBuffer, materialScatteringKernelBuffer); diff --git a/sources/engine/Stride.Rendering/Rendering/Lights/LightClusteredPointSpotGroupRenderer.cs b/sources/engine/Stride.Rendering/Rendering/Lights/LightClusteredPointSpotGroupRenderer.cs index 3d515fdd1a..7ea0099848 100644 --- a/sources/engine/Stride.Rendering/Rendering/Lights/LightClusteredPointSpotGroupRenderer.cs +++ b/sources/engine/Stride.Rendering/Rendering/Lights/LightClusteredPointSpotGroupRenderer.cs @@ -522,7 +522,7 @@ public override unsafe void UpdateViewResources(RenderDrawContext context, int v if (renderViewInfo.LightClusters != null && renderViewInfo.LightClusters.Length > 0) { fixed (Int2* dataPtr = renderViewInfo.LightClusters) - context.CommandList.UpdateSubresource(clusteredGroupRenderer.lightClusters, 0, new DataBox((IntPtr)dataPtr, sizeof(Int2) * renderViewInfo.ClusterCount.X, sizeof(Int2) * renderViewInfo.ClusterCount.X * renderViewInfo.ClusterCount.Y), + context.CommandList.UpdateSubResource(clusteredGroupRenderer.lightClusters, 0, new DataBox((IntPtr)dataPtr, sizeof(Int2) * renderViewInfo.ClusterCount.X, sizeof(Int2) * renderViewInfo.ClusterCount.X * renderViewInfo.ClusterCount.Y), new ResourceRegion(0, 0, 0, renderViewInfo.ClusterCount.X, renderViewInfo.ClusterCount.Y, ClusterSlices)); } @@ -530,7 +530,7 @@ public override unsafe void UpdateViewResources(RenderDrawContext context, int v if (renderViewInfo.PointLights.Count > 0) { fixed (PointLightData* pointLightsPtr = renderViewInfo.PointLights.Items) - context.CommandList.UpdateSubresource(clusteredGroupRenderer.pointLightsBuffer, 0, new DataBox((IntPtr)pointLightsPtr, 0, 0), new ResourceRegion(0, 0, 0, renderViewInfo.PointLights.Count * sizeof(PointLightData), 1, 1)); + context.CommandList.UpdateSubResource(clusteredGroupRenderer.pointLightsBuffer, 0, new DataBox((IntPtr)pointLightsPtr, 0, 0), new ResourceRegion(0, 0, 0, renderViewInfo.PointLights.Count * sizeof(PointLightData), 1, 1)); } // macOS doesn't like when we provide a null Buffer or if it is not sufficiently allocated. // It would cause an inifite loop. So for now we just create one with one element but not initializing it. @@ -545,7 +545,7 @@ public override unsafe void UpdateViewResources(RenderDrawContext context, int v if (renderViewInfo.SpotLights.Count > 0) { fixed (SpotLightData* spotLightsPtr = renderViewInfo.SpotLights.Items) - context.CommandList.UpdateSubresource(clusteredGroupRenderer.spotLightsBuffer, 0, new DataBox((IntPtr)spotLightsPtr, 0, 0), new ResourceRegion(0, 0, 0, renderViewInfo.SpotLights.Count * sizeof(SpotLightData), 1, 1)); + context.CommandList.UpdateSubResource(clusteredGroupRenderer.spotLightsBuffer, 0, new DataBox((IntPtr)spotLightsPtr, 0, 0), new ResourceRegion(0, 0, 0, renderViewInfo.SpotLights.Count * sizeof(SpotLightData), 1, 1)); } // See previous macOS comment. else if (Platform.Type == PlatformType.macOS @@ -559,7 +559,7 @@ public override unsafe void UpdateViewResources(RenderDrawContext context, int v if (renderViewInfo.LightIndices.Count > 0) { fixed (int* lightIndicesPtr = renderViewInfo.LightIndices.Items) - context.CommandList.UpdateSubresource(clusteredGroupRenderer.lightIndicesBuffer, 0, new DataBox((IntPtr)lightIndicesPtr, 0, 0), new ResourceRegion(0, 0, 0, renderViewInfo.LightIndices.Count * sizeof(int), 1, 1)); + context.CommandList.UpdateSubResource(clusteredGroupRenderer.lightIndicesBuffer, 0, new DataBox((IntPtr)lightIndicesPtr, 0, 0), new ResourceRegion(0, 0, 0, renderViewInfo.LightIndices.Count * sizeof(int), 1, 1)); } // See previous macOS comment. else if (Platform.Type == PlatformType.macOS diff --git a/sources/engine/Stride.Rendering/Rendering/Lights/LightSkyboxRenderer.cs b/sources/engine/Stride.Rendering/Rendering/Lights/LightSkyboxRenderer.cs index d78e318de7..5fed092b25 100644 --- a/sources/engine/Stride.Rendering/Rendering/Lights/LightSkyboxRenderer.cs +++ b/sources/engine/Stride.Rendering/Rendering/Lights/LightSkyboxRenderer.cs @@ -144,7 +144,7 @@ public override void ApplyViewParameters(RenderDrawContext context, int viewInde int specularCubemapLevels = 0; if (specularCubemap != null) { - specularCubemapLevels = specularCubemap.MipLevels; + specularCubemapLevels = specularCubemap.MipLevelCount; } // global parameters diff --git a/sources/engine/Stride.Rendering/Rendering/Lights/LightSpotTextureProjectionRenderer.cs b/sources/engine/Stride.Rendering/Rendering/Lights/LightSpotTextureProjectionRenderer.cs index 4ea4eb7e68..1aec612e62 100644 --- a/sources/engine/Stride.Rendering/Rendering/Lights/LightSpotTextureProjectionRenderer.cs +++ b/sources/engine/Stride.Rendering/Rendering/Lights/LightSpotTextureProjectionRenderer.cs @@ -171,7 +171,7 @@ public void Collect(RenderContext context, RenderView sourceView, LightShadowMap var shaderData = shaderDataPool.Add(); lightShadowMap.ShaderData = shaderData; - shaderData.ProjectiveTextureMipMapLevel = (float)(lightParameters.ProjectionTexture.MipLevels - 1) * spotLight.MipMapScale; // "- 1" because the lowest mip level is 0, not 1. + shaderData.ProjectiveTextureMipMapLevel = (float)(lightParameters.ProjectionTexture.MipLevelCount - 1) * spotLight.MipMapScale; // "- 1" because the lowest mip level is 0, not 1. shaderData.WorldToTextureUV = ComputeWorldToTextureUVMatrix(lightComponent); // View-projection matrix without offset to cascade. } @@ -275,7 +275,7 @@ public void ApplyDrawParameters(RenderDrawContext context, ParameterCollection p // We use the maximum number of mips instead of the actual number, // so things like video textures behave more consistently when changing the number of mip maps to generate. - int maxMipMapCount = Texture.CountMips(lightParameters.ProjectionTexture.Width, lightParameters.ProjectionTexture.Height); + int maxMipMapCount = Texture.CountMipLevels(lightParameters.ProjectionTexture.Width, lightParameters.ProjectionTexture.Height); float projectiveTextureMipMapLevel = (float)(maxMipMapCount - 1) * spotLight.MipMapScale; // "- 1" because the lowest mip level is 0, not 1. projectionTextureMipMapLevels[lightIndex] = projectiveTextureMipMapLevel; transitionAreas[lightIndex] = Math.Max(spotLight.TransitionArea, 0.001f); // Keep the value just above zero. This is to prevent some issues with the "smoothstep()" function on OpenGL and OpenGL ES. diff --git a/sources/engine/Stride.Rendering/Rendering/Materials/MaterialSpecularThinGlassModelFeature.cs b/sources/engine/Stride.Rendering/Rendering/Materials/MaterialSpecularThinGlassModelFeature.cs index ae84a1cd0d..cc0b124c82 100644 --- a/sources/engine/Stride.Rendering/Rendering/Materials/MaterialSpecularThinGlassModelFeature.cs +++ b/sources/engine/Stride.Rendering/Rendering/Materials/MaterialSpecularThinGlassModelFeature.cs @@ -1,5 +1,7 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -using System.Collections.Generic; using Stride.Core; using Stride.Core.Annotations; using Stride.Graphics; @@ -52,7 +54,7 @@ public override void GenerateShader(MaterialGeneratorContext context) context.MaterialPass.CullMode = context.PassIndex < 2 ? CullMode.Back : CullMode.Front; else context.MaterialPass.CullMode = attributes.CullMode; - + // Compute transmittance context.GetShading(this).LightDependentExtraModels.Add(new ShaderClassSource("MaterialTransmittanceReflectanceStream")); @@ -61,7 +63,10 @@ public override void GenerateShader(MaterialGeneratorContext context) if (glassPassIndex == 0) { // Transmittance pass - context.MaterialPass.BlendState = new BlendStateDescription(Blend.Zero, Blend.SourceColor) { RenderTarget0 = { AlphaSourceBlend = Blend.One, AlphaDestinationBlend = Blend.Zero } }; + var blendState = new BlendStateDescription(sourceBlend: Blend.Zero, destinationBlend: Blend.SourceColor); + blendState.RenderTargets[0] = new BlendStateRenderTargetDescription { AlphaSourceBlend = Blend.One, AlphaDestinationBlend = Blend.Zero }; + + context.MaterialPass.BlendState = blendState; // Shader output is matTransmittance // Note: we make sure to run after MaterialTransparencyBlendFeature so that shadingColorAlpha is fully updated @@ -81,4 +86,4 @@ private void AddMaterialSurfaceTransmittanceShading(MaterialShaderStage stage, M public bool Equals(MaterialSpecularThinGlassModelFeature other) => base.Equals(other); } -} \ No newline at end of file +} diff --git a/sources/engine/Stride.Rendering/Rendering/ProceduralModels/PrimitiveProceduralModelBase.cs b/sources/engine/Stride.Rendering/Rendering/ProceduralModels/PrimitiveProceduralModelBase.cs index 6fcc88cb6c..e3c7e24f72 100644 --- a/sources/engine/Stride.Rendering/Rendering/ProceduralModels/PrimitiveProceduralModelBase.cs +++ b/sources/engine/Stride.Rendering/Rendering/ProceduralModels/PrimitiveProceduralModelBase.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Stride.Core; using Stride.Core.Annotations; using Stride.Core.Mathematics; @@ -87,7 +86,7 @@ public Model Generate(IServiceRegistry services) public void Generate(IServiceRegistry services, Model model) { - if (model == null) throw new ArgumentNullException(nameof(model)); + ArgumentNullException.ThrowIfNull(model); var needsTempDevice = false; var graphicsDevice = services?.GetSafeServiceAs().GraphicsDevice; @@ -147,7 +146,7 @@ public void Generate(IServiceRegistry services, Model model) var meshDraw = new MeshDraw(); var layout = result.Layout; - var vertexBuffer = result.VertexBuffer; + var vertexBufferData = result.VertexBuffer; var indices = data.Indices; if (indices.Length < 0xFFFF) @@ -179,11 +178,12 @@ public void Generate(IServiceRegistry services, Model model) } } - meshDraw.VertexBuffers = new[] { new VertexBufferBinding(Buffer.New(graphicsDevice, vertexBuffer, BufferFlags.VertexBuffer).RecreateWith(vertexBuffer), layout, data.Vertices.Length) }; + var vertexBuffer = Buffer.New(graphicsDevice, vertexBufferData, BufferFlags.VertexBuffer, GraphicsResourceUsage.Default); + meshDraw.VertexBuffers = [ new VertexBufferBinding(vertexBuffer.RecreateWith(vertexBufferData), layout, data.Vertices.Length) ]; if (needsTempDevice) { - var vertexData = BufferData.New(BufferFlags.VertexBuffer, vertexBuffer); - meshDraw.VertexBuffers = new[] { new VertexBufferBinding(vertexData.ToSerializableVersion(), layout, data.Vertices.Length) }; + var vertexData = BufferData.New(BufferFlags.VertexBuffer, vertexBufferData); + meshDraw.VertexBuffers = [ new VertexBufferBinding(vertexData.ToSerializableVersion(), layout, data.Vertices.Length) ]; } meshDraw.DrawCount = indices.Length; diff --git a/sources/engine/Stride.Rendering/Rendering/QueryManager.cs b/sources/engine/Stride.Rendering/Rendering/QueryManager.cs index 91e8bdc425..3f2238b170 100644 --- a/sources/engine/Stride.Rendering/Rendering/QueryManager.cs +++ b/sources/engine/Stride.Rendering/Rendering/QueryManager.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using Stride.Core.Collections; using Stride.Core.Diagnostics; using Stride.Core.Mathematics; using Stride.Graphics; @@ -25,7 +23,7 @@ private struct QueryEvent private const int TimestampQueryPoolCapacity = 64; private readonly CommandList commandList; - private readonly GraphicsResourceAllocator allocator; + private readonly GraphicsResourceAllocator allocator; private readonly long[] queryResults = new long[TimestampQueryPoolCapacity]; private readonly Queue queryEvents = new Queue(); private readonly Stack queries = new Stack(); @@ -54,7 +52,7 @@ public Scope BeginProfile(Color4 profileColor, ProfilingKey profilingKey) EnsureQueryPoolSize(); - // Push the current query range onto the stack + // Push the current query range onto the stack var query = new QueryEvent { ProfilingKey = profilingKey, @@ -143,10 +141,10 @@ public void Flush() // Profile // An event with a key is a begin event - if (query.ProfilingKey != null) + if (query.ProfilingKey is not null) { var profilingState = Profiler.New(query.ProfilingKey); - profilingState.TickFrequency = commandList.GraphicsDevice.TimestampFrequency; + profilingState.TickFrequency = (long) commandList.GraphicsDevice.TimestampFrequency; profilingState.BeginGpu(queryResults[query.Index]); profilingStates.Push(profilingState); } diff --git a/sources/engine/Stride.Rendering/Rendering/RootEffectRenderFeature.cs b/sources/engine/Stride.Rendering/Rendering/RootEffectRenderFeature.cs index 7d5b843b4a..733f19be90 100644 --- a/sources/engine/Stride.Rendering/Rendering/RootEffectRenderFeature.cs +++ b/sources/engine/Stride.Rendering/Rendering/RootEffectRenderFeature.cs @@ -616,7 +616,7 @@ public override void PrepareEffectPermutations(RenderDrawContext context) renderEffectReflection = new RenderEffectReflection(); // Build root signature automatically from reflection - renderEffectReflection.DescriptorReflection = EffectDescriptorSetReflection.New(RenderSystem.GraphicsDevice, effect.Bytecode, effectDescriptorSetSlots, "PerFrame"); + renderEffectReflection.DescriptorReflection = EffectDescriptorSetReflection.New(RenderSystem.GraphicsDevice, effect.Bytecode, effectDescriptorSetSlots, defaultSetSlot: "PerFrame"); renderEffectReflection.ResourceGroupDescriptions = new ResourceGroupDescription[renderEffectReflection.DescriptorReflection.Layouts.Count]; // Compute ResourceGroup hashes @@ -797,7 +797,7 @@ public override void Prepare(RenderDrawContext context) renderNode.EffectObjectNode = new EffectObjectNodeReference(effectObjectNodeIndex); renderNode.RenderEffect = renderEffect; - + // Bind well-known descriptor sets var descriptorSetPoolOffset = ComputeResourceGroupOffset(renderNodeReference); ResourceGroupPool[descriptorSetPoolOffset + perFrameDescriptorSetSlot.Index] = frameLayout?.Entry.Resources; diff --git a/sources/engine/Stride.Rendering/Streaming/StreamingTexture.cs b/sources/engine/Stride.Rendering/Streaming/StreamingTexture.cs index 3fc9ca0bee..5493095c7b 100644 --- a/sources/engine/Stride.Rendering/Streaming/StreamingTexture.cs +++ b/sources/engine/Stride.Rendering/Streaming/StreamingTexture.cs @@ -114,7 +114,7 @@ internal StreamingTexture(StreamingManager manager, [NotNull] Texture texture) public PixelFormat Format => description.Format; /// - /// Gets index of the highest resident mip map (may be equal to MipLevels if no mip has been uploaded). Note: mip=0 is the highest (top quality) + /// Gets index of the highest resident mip map (may be equal to MipLevelCount if no mip has been uploaded). Note: mip=0 is the highest (top quality) /// /// Mip index public int HighestResidentMipIndex => TotalMipLevels - residentMips; @@ -126,7 +126,7 @@ internal StreamingTexture(StreamingManager manager, [NotNull] Texture texture) public override int CurrentResidency => residentMips; /// - public override int AllocatedResidency => Texture.MipLevels; + public override int AllocatedResidency => Texture.MipLevelCount; /// public override int MaxResidency => description.MipLevels; @@ -300,8 +300,8 @@ private void StreamingTask(int residency) // Setup texture description TextureDescription newDesc = description; var newHighestResidentMipIndex = TotalMipLevels - mipsCount; - newDesc.MipLevels = mipsCount; - var topMip = mipInfos[description.MipLevels - newDesc.MipLevels]; + newDesc.MipLevelCount = mipsCount; + var topMip = mipInfos[description.MipLevels - newDesc.MipLevelCount]; newDesc.Width = topMip.Width; newDesc.Height = topMip.Height; @@ -329,7 +329,7 @@ private void StreamingTask(int residency) // Get data boxes var dataBoxIndex = 0; - var dataBoxes = new DataBox[newDesc.MipLevels * newDesc.ArraySize]; + var dataBoxes = new DataBox[newDesc.MipLevelCount * newDesc.ArraySize]; for (var arrayIndex = 0; arrayIndex < newDesc.ArraySize; arrayIndex++) { for (var mipIndex = 0; mipIndex < mipsCount; mipIndex++) @@ -351,7 +351,7 @@ private void StreamingTask(int residency) textureToSync = Texture.New(texture.GraphicsDevice, newDesc, new TextureViewDescription(), dataBoxes); textureToSync.FullQualitySize = texture.FullQualitySize; - residentMips = newDesc.MipLevels; + residentMips = newDesc.MipLevelCount; } finally { diff --git a/sources/engine/Stride.Shaders.Compiler/Direct3D/ShaderCompiler.cs b/sources/engine/Stride.Shaders.Compiler/Direct3D/ShaderCompiler.cs index 4afb3e78e9..1d63870efa 100644 --- a/sources/engine/Stride.Shaders.Compiler/Direct3D/ShaderCompiler.cs +++ b/sources/engine/Stride.Shaders.Compiler/Direct3D/ShaderCompiler.cs @@ -1,536 +1,711 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #if STRIDE_PLATFORM_DESKTOP + using System; -using System.Collections.Generic; using System.Linq; -using SharpDX; -using SharpDX.D3DCompiler; +using System.Text; +using System.Runtime.CompilerServices; + +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; +using Silk.NET.Direct3D.Compilers; + using Stride.Core.Diagnostics; using Stride.Core.Storage; -using Stride.Rendering; +using Stride.Core.UnsafeExtensions; using Stride.Graphics; -using ConstantBufferType = Stride.Shaders.ConstantBufferType; -using ShaderBytecode = Stride.Shaders.ShaderBytecode; -using ShaderVariableType = SharpDX.D3DCompiler.ShaderVariableType; + +using static System.Runtime.CompilerServices.Unsafe; +using static Stride.Core.UnsafeExtensions.StringMarshal; namespace Stride.Shaders.Compiler.Direct3D { - internal class ShaderCompiler : IShaderCompiler + /// + /// Provides functionality to compile Shader source code into bytecode for various Shader stages + /// using the Direct3D compiler APIs (fxc). + /// It also handles Shader reflection to provide metadata about the compiled Shaders. + /// + internal unsafe class ShaderCompiler : IShaderCompiler { - public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null) + // D3DCOMPILE constants from d3dcompiler.h in the Windows SDK for Windows 10.0.22621.0 + + public const uint D3DCOMPILE_DEBUG = (1 << 0); + public const uint D3DCOMPILE_SKIP_OPTIMIZATION = (1 << 2); + public const uint D3DCOMPILE_OPTIMIZATION_LEVEL0 = (1 << 14); + public const uint D3DCOMPILE_OPTIMIZATION_LEVEL1 = 0; + public const uint D3DCOMPILE_OPTIMIZATION_LEVEL2 = ((1 << 14) | (1 << 15)); + public const uint D3DCOMPILE_OPTIMIZATION_LEVEL3 = (1 << 15); + + + /// + /// Compiles the specified Shader source code into byte-code for a given shader stage + /// using the Direct3D Compiler APIs (fxc). + /// + /// The source code of the Shader to compile. + /// The entry point function name within the Shader source. + /// + /// The Shader stage for which the byte-code is being compiled + /// (e.g., , ). + /// + /// + /// A set of parameters that influence the compilation process, such as debug and optimization settings. + /// + /// An object to be updated with reflection data from the compiled Shader. + /// The optional filename of the Shader source, used for error reporting. + /// + /// A containing the compiled Shader byte-code and any warnings or errors + /// encountered during compilation. + /// + /// The specified Shader is not supported. + /// + /// The specified in is not supported. + /// + /// + /// During reflection, if an unsupported or + /// is encountered. + /// + public ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, + EffectCompilerParameters effectParameters, EffectReflection reflection, + string? sourceFilename = null) { + var d3dCompiler = D3DCompiler.GetApi(); + var isDebug = effectParameters.Debug; var optimLevel = effectParameters.OptimizationLevel; var profile = effectParameters.Profile; - + var shaderModel = ShaderStageToString(stage) + "_" + ShaderProfileFromGraphicsProfile(profile); - var shaderFlags = ShaderFlags.None; + uint effectFlags = 0; + uint shaderFlags = 0; + if (isDebug) { - shaderFlags = ShaderFlags.Debug; + shaderFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; } switch (optimLevel) { - case 0: - shaderFlags |= ShaderFlags.OptimizationLevel0; - break; - case 1: - shaderFlags |= ShaderFlags.OptimizationLevel1; - break; - case 2: - shaderFlags |= ShaderFlags.OptimizationLevel2; - break; - case 3: - shaderFlags |= ShaderFlags.OptimizationLevel3; - break; + case 0: shaderFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL0; break; + case 1: shaderFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL1; break; + case 2: shaderFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL2; break; + case 3: shaderFlags |= D3DCOMPILE_OPTIMIZATION_LEVEL3; break; } - SharpDX.Configuration.ThrowOnShaderCompileError = false; - // Compile using D3DCompiler - var compilationResult = SharpDX.D3DCompiler.ShaderBytecode.Compile(shaderSource, entryPoint, shaderModel, shaderFlags, EffectFlags.None, null, null, sourceFilename); + var shaderBytes = shaderSource.GetAsciiSpan(); + ComPtr byteCode = default; + ComPtr compileErrors = default; var byteCodeResult = new ShaderBytecodeResult(); - if (compilationResult.HasErrors || compilationResult.Bytecode == null) - { - // Log compilation errors - byteCodeResult.Error(compilationResult.Message); - } - else + HResult result = Compile(); + + if (result.IsSuccess && byteCode.Handle is not null) { // TODO: Make this optional - try - { - byteCodeResult.DisassembleText = compilationResult.Bytecode.Disassemble(); - } - catch (SharpDXException) - { - } + byteCodeResult.DisassembleText = Disassemble(byteCode); + + // As effect bytecode binary can change when having debug info (with d3dcompiler_47), we are calculating a bytecodeId on the stripped version + using ComPtr strippedByteCode = Strip(byteCode); + var byteCodeId = ObjectId.FromBytes(strippedByteCode.Handle->Buffer); - // As effect bytecode binary can changed when having debug infos (with d3dcompiler_47), we are calculating a bytecodeId on the stripped version - var rawData = compilationResult.Bytecode.Strip(StripFlags.CompilerStripDebugInformation | StripFlags.CompilerStripReflectionData); - var bytecodeId = ObjectId.FromBytes(rawData); - byteCodeResult.Bytecode = new ShaderBytecode(bytecodeId, compilationResult.Bytecode.Data) { Stage = stage }; + var byteCodeBuffer = byteCode.Handle->Buffer; + byteCodeResult.Bytecode = new ShaderBytecode(byteCodeId, byteCodeBuffer.ToArray()) { Stage = stage }; - // If compilation succeed, then we can update reflection. + // If compilation succeeded, then we can update reflection UpdateReflection(byteCodeResult.Bytecode, reflection, byteCodeResult); - if (!string.IsNullOrEmpty(compilationResult.Message)) + if (compileErrors.Handle is not null) { - byteCodeResult.Warning(compilationResult.Message); + byteCodeResult.Warning(GetTextFromBlob(compileErrors)); } } + byteCode.Dispose(); + compileErrors.Dispose(); + return byteCodeResult; - } - private void UpdateReflection(ShaderBytecode shaderBytecode, EffectReflection effectReflection, LoggerResult log) - { - var shaderReflectionRaw = new SharpDX.D3DCompiler.ShaderReflection(shaderBytecode); - var shaderReflectionRawDesc = shaderReflectionRaw.Description; - foreach (var constantBuffer in effectReflection.ConstantBuffers) + // + // Returns a string representation of the Shader stage. + // + static string ShaderStageToString(ShaderStage stage) { - UpdateConstantBufferReflection(constantBuffer); + return stage switch + { + ShaderStage.Compute => "cs", + ShaderStage.Vertex => "vs", + ShaderStage.Hull => "hs", + ShaderStage.Domain => "ds", + ShaderStage.Geometry => "gs", + ShaderStage.Pixel => "ps", + + _ => throw new ArgumentException("Shader Stage not supported.", nameof(stage)) + }; } - // Constant Buffers - for (int i = 0; i < shaderReflectionRawDesc.ConstantBuffers; ++i) + // + // Returns the Shader profile string based on the specified Graphics Profile. + // + static string ShaderProfileFromGraphicsProfile(GraphicsProfile graphicsProfile) { - var constantBufferRaw = shaderReflectionRaw.GetConstantBuffer(i); - var constantBufferRawDesc = constantBufferRaw.Description; - if (constantBufferRawDesc.Type == SharpDX.D3DCompiler.ConstantBufferType.ResourceBindInformation) - continue; - - var linkBuffer = effectReflection.ConstantBuffers.First(buffer => buffer.Name == constantBufferRawDesc.Name); - - ValidateConstantBufferReflection(constantBufferRaw, ref constantBufferRawDesc, linkBuffer, log); + return graphicsProfile switch + { + GraphicsProfile.Level_9_1 => "4_0_level_9_1", + GraphicsProfile.Level_9_2 => "4_0_level_9_2", + GraphicsProfile.Level_9_3 => "4_0_level_9_3", + GraphicsProfile.Level_10_0 => "4_0", + GraphicsProfile.Level_10_1 => "4_1", + GraphicsProfile.Level_11_0 or GraphicsProfile.Level_11_1 => "5_0", + + _ => throw new ArgumentException("Graphics Profile not supported.", nameof(graphicsProfile)) + }; } - // BoundResources - for (int i = 0; i < shaderReflectionRawDesc.BoundResources; ++i) - { - var boundResourceDesc = shaderReflectionRaw.GetResourceBindingDescription(i); - string linkKeyName = null; - string resourceGroup = null; - string logicalGroup = null; - var elementType = default(EffectTypeDescription); - foreach (var linkResource in effectReflection.ResourceBindings) - { - if (linkResource.RawName == boundResourceDesc.Name && linkResource.Stage == ShaderStage.None) - { - linkKeyName = linkResource.KeyInfo.KeyName; - resourceGroup = linkResource.ResourceGroup; - logicalGroup = linkResource.LogicalGroup; - elementType = linkResource.ElementType; - break; - } + // + // Compiles the Shader source code to byte-code for the specified Shader Model. + // + HResult Compile() + { + var shaderSourceSpan = shaderSource.GetAsciiSpan(); + var shaderSourceLength = (nuint) shaderSourceSpan.Length; - } + ref var noSourceName = ref Unsafe.NullRef(); - if (linkKeyName == null) - { - log.Error($"Resource [{boundResourceDesc.Name}] has no link"); - } - else - { + ref var noDefines = ref Unsafe.NullRef(); + var noIncludes = default(ComPtr); - var binding = GetResourceBinding(boundResourceDesc, linkKeyName, log); - binding.Stage = shaderBytecode.Stage; - binding.ResourceGroup = resourceGroup; - binding.LogicalGroup = logicalGroup; - binding.ElementType = elementType; + var entryPointSpan = entryPoint.GetUtf8Span(); - effectReflection.ResourceBindings.Add(binding); - } - } - } + var shaderModelSpan = shaderModel.GetUtf8Span(); - private EffectResourceBindingDescription GetResourceBinding(SharpDX.D3DCompiler.InputBindingDescription bindingDescriptionRaw, string name, LoggerResult log) - { - var paramClass = EffectParameterClass.Object; - var paramType = EffectParameterType.Void; + HResult result = d3dCompiler.Compile(in shaderSourceSpan[0], shaderSourceLength, in noSourceName, + in noDefines, noIncludes, in entryPointSpan[0], in shaderModelSpan[0], + shaderFlags, effectFlags, + ref byteCode, ref compileErrors); - switch (bindingDescriptionRaw.Type) - { - case SharpDX.D3DCompiler.ShaderInputType.TextureBuffer: - paramType = EffectParameterType.TextureBuffer; - paramClass = EffectParameterClass.TextureBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.ConstantBuffer: - paramType = EffectParameterType.ConstantBuffer; - paramClass = EffectParameterClass.ConstantBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.Texture: - paramClass = EffectParameterClass.ShaderResourceView; - switch (bindingDescriptionRaw.Dimension) - { - case SharpDX.Direct3D.ShaderResourceViewDimension.Buffer: - paramType = EffectParameterType.Buffer; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture1D: - paramType = EffectParameterType.Texture1D; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture1DArray: - paramType = EffectParameterType.Texture1DArray; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture2D: - paramType = EffectParameterType.Texture2D; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture2DArray: - paramType = EffectParameterType.Texture2DArray; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture2DMultisampled: - paramType = EffectParameterType.Texture2DMultisampled; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture2DMultisampledArray: - paramType = EffectParameterType.Texture2DMultisampledArray; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture3D: - paramType = EffectParameterType.Texture3D; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.TextureCube: - paramType = EffectParameterType.TextureCube; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.TextureCubeArray: - paramType = EffectParameterType.TextureCubeArray; - break; - } - break; - case SharpDX.D3DCompiler.ShaderInputType.Structured: - paramClass = EffectParameterClass.ShaderResourceView; - paramType = EffectParameterType.StructuredBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.ByteAddress: - paramClass = EffectParameterClass.ShaderResourceView; - paramType = EffectParameterType.ByteAddressBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.UnorderedAccessViewRWTyped: - paramClass = EffectParameterClass.UnorderedAccessView; - switch (bindingDescriptionRaw.Dimension) + if (result.IsFailure || byteCode.Handle is null) + { + // Log compilation errors + if (compileErrors.Handle is not null) { - case SharpDX.Direct3D.ShaderResourceViewDimension.Buffer: - paramType = EffectParameterType.RWBuffer; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture1D: - paramType = EffectParameterType.RWTexture1D; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture1DArray: - paramType = EffectParameterType.RWTexture1DArray; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture2D: - paramType = EffectParameterType.RWTexture2D; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture2DArray: - paramType = EffectParameterType.RWTexture2DArray; - break; - case SharpDX.Direct3D.ShaderResourceViewDimension.Texture3D: - paramType = EffectParameterType.RWTexture3D; - break; + byteCodeResult.Error(GetTextFromBlob(compileErrors)); } - break; - case SharpDX.D3DCompiler.ShaderInputType.UnorderedAccessViewRWStructured: - paramClass = EffectParameterClass.UnorderedAccessView; - paramType = EffectParameterType.RWStructuredBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.UnorderedAccessViewRWByteAddress: - paramClass = EffectParameterClass.UnorderedAccessView; - paramType = EffectParameterType.RWByteAddressBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.UnorderedAccessViewAppendStructured: - paramClass = EffectParameterClass.UnorderedAccessView; - paramType = EffectParameterType.AppendStructuredBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.UnorderedAccessViewConsumeStructured: - paramClass = EffectParameterClass.UnorderedAccessView; - paramType = EffectParameterType.ConsumeStructuredBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.UnorderedAccessViewRWStructuredWithCounter: - paramClass = EffectParameterClass.UnorderedAccessView; - paramType = EffectParameterType.RWStructuredBuffer; - break; - case SharpDX.D3DCompiler.ShaderInputType.Sampler: - paramClass = EffectParameterClass.Sampler; - paramType = EffectParameterType.Sampler; - break; + } + + return result; } - var binding = new EffectResourceBindingDescription() - { - KeyInfo = - { - KeyName = name, - }, - RawName = bindingDescriptionRaw.Name, - Class = paramClass, - Type = paramType, - SlotStart = bindingDescriptionRaw.BindPoint, - SlotCount = bindingDescriptionRaw.BindCount, - }; + // + // Disassembles a blob of Shader byte-code to its textual equivalent in HLSL code. + // + string Disassemble(ComPtr byteCode) + { + ref var noComments = ref Unsafe.NullRef(); - return binding; - } + ComPtr disassembly = default; - private void UpdateConstantBufferReflection(EffectConstantBufferDescription reflectionConstantBuffer) - { - // Used to compute constant buffer size and member offsets (std140 rule) - int constantBufferOffset = 0; + d3dCompiler.Disassemble(byteCode.GetBufferPointer(), byteCode.GetBufferSize(), Flags: 0, + in noComments, ref disassembly); - // Fill members - for (int index = 0; index < reflectionConstantBuffer.Members.Length; index++) - { - var member = reflectionConstantBuffer.Members[index]; + string shaderDisassembly = GetTextFromBlob(disassembly); + disassembly.Dispose(); - // Properly compute size and offset according to DX rules - var memberSize = ComputeMemberSize(ref member.Type, ref constantBufferOffset); + return shaderDisassembly; + } - // Store size/offset info - member.Offset = constantBufferOffset; - member.Size = memberSize; + // + // Gets a blob of Shader byte-code with debug and reflection data stripped out. + // + ComPtr Strip(ComPtr byteCode) + { + const uint StripDebugAndReflection = (uint) (CompilerStripFlags.ReflectionData | CompilerStripFlags.DebugInfo); - // Adjust offset for next item - constantBufferOffset += memberSize; + var byteCodePointer = byteCode.Handle->GetBufferPointer(); + var byteCodeSize = byteCode.Handle->GetBufferSize(); - reflectionConstantBuffer.Members[index] = member; - } + ComPtr strippedByteCode = default; - // Round buffer size to next multiple of 16 - reflectionConstantBuffer.Size = (constantBufferOffset + 15) / 16 * 16; - } + HResult result = d3dCompiler.StripShader(byteCodePointer, byteCodeSize, + StripDebugAndReflection, ref strippedByteCode); + if (result.IsFailure) + return null; - private void ValidateConstantBufferReflection(ConstantBuffer constantBufferRaw, ref ConstantBufferDescription constantBufferRawDesc, EffectConstantBufferDescription constantBuffer, LoggerResult log) - { - switch (constantBufferRawDesc.Type) - { - case SharpDX.D3DCompiler.ConstantBufferType.ConstantBuffer: - if (constantBuffer.Type != ConstantBufferType.ConstantBuffer) - log.Error($"Invalid buffer type for {constantBuffer.Name}: {constantBuffer.Type} instead of {ConstantBufferType.ConstantBuffer}"); - break; - case SharpDX.D3DCompiler.ConstantBufferType.TextureBuffer: - if (constantBuffer.Type != ConstantBufferType.TextureBuffer) - log.Error($"Invalid buffer type for {constantBuffer.Name}: {constantBuffer.Type} instead of {ConstantBufferType.TextureBuffer}"); - break; - default: - if (constantBuffer.Type != ConstantBufferType.Unknown) - log.Error($"Invalid buffer type for {constantBuffer.Name}: {constantBuffer.Type} instead of {ConstantBufferType.Unknown}"); - break; + return strippedByteCode; } - // ConstantBuffers variables - for (int i = 0; i < constantBufferRawDesc.VariableCount; i++) + // + // Updates the reflection information for a Shader byte-code. + // + void UpdateReflection(ShaderBytecode shaderBytecode, EffectReflection effectReflection, LoggerResult log) { - var variable = constantBufferRaw.GetVariable(i); - var variableType = variable.GetVariableType(); - var variableDescription = variable.Description; - var variableTypeDescription = variableType.Description; + var byteCode = shaderBytecode.Data; + + ComPtr shaderReflection = Reflect(byteCode); + + SkipInit(out ShaderDesc shaderReflectionDesc); + shaderReflection.GetDesc(ref shaderReflectionDesc); - if (variableTypeDescription.Offset != 0) + // Adjust the Constant Buffer size, and compute the offsets and sizes of its members + foreach (var constantBuffer in effectReflection.ConstantBuffers) { - log.Error($"Unexpected offset [{variableTypeDescription.Offset}] for variable [{variableDescription.Name}] in constant buffer [{constantBuffer.Name}]"); + UpdateConstantBufferReflection(constantBuffer); } - var binding = constantBuffer.Members[i]; - // Retrieve Link Member - if (binding.RawName != variableDescription.Name) + // Constant Buffers + for (uint i = 0; i < shaderReflectionDesc.ConstantBuffers; ++i) { - log.Error($"Variable [{variableDescription.Name}] in constant buffer [{constantBuffer.Name}] has no link"); + var constantBuffer = ToComPtr(shaderReflection.GetConstantBufferByIndex(i)); + + SkipInit(out ShaderBufferDesc constantBufferDesc); + constantBuffer.GetDesc(ref constantBufferDesc); + + if (constantBufferDesc.Type == D3DCBufferType.D3DCTResourceBindInfo) + continue; + + string constantBufferName = GetUtf8Span(constantBufferDesc.Name).GetString(); + var linkBuffer = effectReflection.ConstantBuffers.First(buffer => buffer.Name == constantBufferName); + + ValidateConstantBufferReflection(constantBuffer, ref constantBufferDesc, linkBuffer, log); } - else + + // Bound Resources + for (uint i = 0; i < shaderReflectionDesc.BoundResources; ++i) { - var parameter = new EffectValueDescription() + SkipInit(out ShaderInputBindDesc boundResourceDesc); + shaderReflection.GetResourceBindingDesc(i, ref boundResourceDesc); + + string linkKeyName = null; + string resourceGroup = null; + string logicalGroup = null; + var elementType = default(EffectTypeDescription); + + var resourceName = GetUtf8Span(boundResourceDesc.Name).GetString(); + + foreach (var linkResource in effectReflection.ResourceBindings) { - Type = + if (linkResource.RawName == resourceName && linkResource.Stage == ShaderStage.None) { - Class = (EffectParameterClass)variableTypeDescription.Class, - Type = ConvertVariableValueType(variableTypeDescription.Type, log), - Elements = variableTypeDescription.ElementCount, - RowCount = (byte)variableTypeDescription.RowCount, - ColumnCount = (byte)variableTypeDescription.ColumnCount, - }, - RawName = variableDescription.Name, - Offset = variableDescription.StartOffset, - Size = variableDescription.Size, - }; + linkKeyName = linkResource.KeyInfo.KeyName; + resourceGroup = linkResource.ResourceGroup; + logicalGroup = linkResource.LogicalGroup; + elementType = linkResource.ElementType; + break; + } + } - if (parameter.Offset != binding.Offset - || parameter.Size != binding.Size - || parameter.Type.Elements != binding.Type.Elements - || ((parameter.Type.Class != EffectParameterClass.Struct) && // Ignore columns/rows if it's a struct (sometimes it contains weird data) - (parameter.Type.RowCount != binding.Type.RowCount || parameter.Type.ColumnCount != binding.Type.ColumnCount))) + if (linkKeyName is null) { - log.Error($"Variable [{variableDescription.Name}] in constant buffer [{constantBuffer.Name}] binding doesn't match what was expected"); + log.Error($"Resource [{resourceName}] has no link"); + } + else + { + var binding = GetResourceBinding(in boundResourceDesc, linkKeyName); + binding.Stage = shaderBytecode.Stage; + binding.ResourceGroup = resourceGroup; + binding.LogicalGroup = logicalGroup; + binding.ElementType = elementType; + + effectReflection.ResourceBindings.Add(binding); } } - } - if (constantBuffer.Size != constantBufferRawDesc.Size) - { - log.Error($"Error precomputing buffer size for {constantBuffer.Name}: {constantBuffer.Size} instead of {constantBufferRawDesc.Size}"); - } - } - private static int ComputeMemberSize(ref EffectTypeDescription memberType, ref int constantBufferOffset) - { - var elementSize = ComputeTypeSize(memberType.Type); - int size; - int alignment = 4; + shaderReflection.Dispose(); - switch (memberType.Class) - { - case EffectParameterClass.Struct: + // + // Gets reflection information about a Shader from its byte-code. + // + ComPtr Reflect(byte[] byteCode) + { + HResult result = d3dCompiler.Reflect(in byteCode[0], (nuint) byteCode.Length, + out ComPtr shaderReflection); + if (result.IsFailure) + result.Throw(); + + return shaderReflection; + } + + // + // Given a Constant Buffer description, updates the sizes and offsets of its members, + // as well as its total size (aligned to 16 bytes). + // + void UpdateConstantBufferReflection(EffectConstantBufferDescription reflectionConstantBuffer) + { + // Used to compute Constant Buffer size and member offsets (std140 rule) + int constantBufferOffset = 0; + + // Fill members + for (int index = 0; index < reflectionConstantBuffer.Members.Length; index++) { - // Fill members - size = 0; - for (int index = 0; index < memberType.Members.Length; index++) - { - // Properly compute size and offset according to DX rules - var memberSize = ComputeMemberSize(ref memberType.Members[index].Type, ref size); + var member = reflectionConstantBuffer.Members[index]; - // Align offset and store it as member offset - memberType.Members[index].Offset = size; + // Properly compute size and offset according to DirectX rules + var memberSize = ComputeMemberSize(ref member.Type, ref constantBufferOffset); - // Adjust offset for next item - size += memberSize; - } + member.Offset = constantBufferOffset; + member.Size = memberSize; + + // Adjust offset for next item + constantBufferOffset += memberSize; + + reflectionConstantBuffer.Members[index] = member; + } - alignment = size; - break; + // Round buffer size to next multiple of 16 bytes + reflectionConstantBuffer.Size = (constantBufferOffset + 15) / 16 * 16; + } + + // + // Validates the reflection of a Constant Buffer against the expected description. + // + void ValidateConstantBufferReflection(ComPtr constantBufferRaw, + ref ShaderBufferDesc constantBufferRawDesc, + EffectConstantBufferDescription constantBuffer, + LoggerResult log) + { + switch (constantBufferRawDesc.Type) + { + case D3DCBufferType.D3DCTCbuffer: + if (constantBuffer.Type != ConstantBufferType.ConstantBuffer) + log.Error($"Invalid Buffer type for \"{constantBuffer.Name}\": {constantBuffer.Type} instead of {ConstantBufferType.ConstantBuffer}"); + break; + + case D3DCBufferType.D3DCTTbuffer: + if (constantBuffer.Type != ConstantBufferType.TextureBuffer) + log.Error($"Invalid Buffer type for \"{constantBuffer.Name}\": {constantBuffer.Type} instead of {ConstantBufferType.TextureBuffer}"); + break; + + default: + if (constantBuffer.Type != ConstantBufferType.Unknown) + log.Error($"Invalid Buffer type for \"{constantBuffer.Name}\": {constantBuffer.Type} instead of {ConstantBufferType.Unknown}"); + break; } - case EffectParameterClass.Scalar: + + // Constant Buffer variables + for (uint i = 0; i < constantBufferRawDesc.Variables; i++) { - size = elementSize; - break; + var variable = ToComPtr(constantBufferRaw.GetVariableByIndex(i)); + + SkipInit(out ShaderVariableDesc variableDescription); + variable.GetDesc(ref variableDescription); + + // NOTE: To force to call the GetType() of ID3D11ShaderReflectionVariable and not the GetType() of System.Object + var variableType = ToComPtr(variable.Handle->GetType()); + + SkipInit(out ShaderTypeDesc variableTypeDescription); + variableType.GetDesc(ref variableTypeDescription); + + var variableName = GetUtf8Span(variableDescription.Name).GetString(); + if (variableTypeDescription.Offset != 0) + { + log.Error($"Unexpected offset [{variableTypeDescription.Offset}] for variable [{variableName}] in Constant Buffer [{constantBuffer.Name}]"); + } + + var binding = constantBuffer.Members[i]; + + // Retrieve Link Member + if (binding.RawName != variableName) + { + log.Error($"Variable [{variableName}] in Constant Buffer [{constantBuffer.Name}] has no link"); + } + else + { + var parameter = new EffectValueDescription() + { + Type = + { + Class = (EffectParameterClass) variableTypeDescription.Class, + Type = ConvertVariableValueType(variableTypeDescription.Type, log), + Elements = (int) variableTypeDescription.Elements, + RowCount = (byte) variableTypeDescription.Rows, + ColumnCount = (byte) variableTypeDescription.Columns + }, + RawName = variableName, + Offset = (int) variableDescription.StartOffset, + Size = (int) variableDescription.Size + }; + + if (parameter.Offset != binding.Offset || + parameter.Size != binding.Size || + parameter.Type.Elements != binding.Type.Elements || + // Ignore columns/rows if it's a struct (sometimes it contains weird data) + ((parameter.Type.Class != EffectParameterClass.Struct) && + (parameter.Type.RowCount != binding.Type.RowCount || parameter.Type.ColumnCount != binding.Type.ColumnCount))) + { + log.Error($"Variable [{variableName}] in Constant Buffer [{constantBuffer.Name}] binding doesn't match what was expected"); + } + } } - case EffectParameterClass.Color: - case EffectParameterClass.Vector: + if (constantBuffer.Size != constantBufferRawDesc.Size) { - size = elementSize * memberType.ColumnCount; - break; + log.Error($"Error precomputing Constant Buffer size for \"{constantBuffer.Name}\": {constantBuffer.Size} instead of {constantBufferRawDesc.Size}"); } - case EffectParameterClass.MatrixColumns: + } + + // + // Computes the size of a member type, including its alignment and array size. + // It does so recursively for structs, and handles different parameter classes. + // + static int ComputeMemberSize(ref EffectTypeDescription memberType, ref int constantBufferOffset) + { + var elementSize = ComputeTypeSize(memberType.Type); + int size; + int alignment = 4; + + switch (memberType.Class) { - size = elementSize * (4 * (memberType.ColumnCount - 1) + memberType.RowCount); - break; + case EffectParameterClass.Struct: + { + // Fill members + size = 0; + for (int index = 0; index < memberType.Members.Length; index++) + { + // Properly compute size and offset according to DX rules + var memberSize = ComputeMemberSize(ref memberType.Members[index].Type, ref size); + + // Align offset and store it as member offset + memberType.Members[index].Offset = size; + + // Adjust offset for next item + size += memberSize; + } + alignment = size; + break; + } + case EffectParameterClass.Scalar: + { + size = elementSize; + break; + } + case EffectParameterClass.Color: + case EffectParameterClass.Vector: + { + size = elementSize * memberType.ColumnCount; + break; + } + case EffectParameterClass.MatrixColumns: + { + size = elementSize * (4 * (memberType.ColumnCount - 1) + memberType.RowCount); + break; + } + case EffectParameterClass.MatrixRows: + { + size = elementSize * (4 * (memberType.RowCount - 1) + memberType.ColumnCount); + break; + } + default: + throw new NotImplementedException("An unknown EffectParameterClass was found."); } - case EffectParameterClass.MatrixRows: + + // Update element size + memberType.ElementSize = size; + + // Array + if (memberType.Elements > 0) { - size = elementSize * (4 * (memberType.RowCount - 1) + memberType.ColumnCount); - break; + var roundedSize = (size + 15) / 16 * 16; // Round up to 16 bytes (size of float4) + size += roundedSize * (memberType.Elements - 1); + alignment = 16; } - default: - throw new NotImplementedException(); - } - // Update element size - memberType.ElementSize = size; + // Align to float4 if it is bigger than leftover space in current float4 + if (constantBufferOffset / 16 != (constantBufferOffset + size - 1) / 16) + alignment = 16; - // Array - if (memberType.Elements > 0) - { - var roundedSize = (size + 15) / 16 * 16; // Round up to vec4 - size += roundedSize * (memberType.Elements - 1); - alignment = 16; - } + // Align offset and store it as member offset + constantBufferOffset = (constantBufferOffset + alignment - 1) / alignment * alignment; - // Align to float4 if it is bigger than leftover space in current float4 - if (constantBufferOffset / 16 != (constantBufferOffset + size - 1) / 16) - alignment = 16; + return size; + } - // Align offset and store it as member offset - constantBufferOffset = (constantBufferOffset + alignment - 1) / alignment * alignment; + // + // Computes the size of a type based on its EffectParameterType. + // + static int ComputeTypeSize(EffectParameterType type) + { + return type switch + { + EffectParameterType.Bool or + EffectParameterType.Float or + EffectParameterType.Int or + EffectParameterType.UInt => 4, - return size; - } + EffectParameterType.Double => 8, - private static int ComputeTypeSize(EffectParameterType type) - { - switch (type) - { - case EffectParameterType.Bool: - case EffectParameterType.Float: - case EffectParameterType.Int: - case EffectParameterType.UInt: - return 4; - case EffectParameterType.Double: - return 8; - case EffectParameterType.Void: - return 0; - default: - throw new NotImplementedException(); - } - } + EffectParameterType.Void => 0, - private static string ShaderStageToString(ShaderStage stage) - { - string shaderStageText; - switch (stage) - { - case ShaderStage.Compute: - shaderStageText = "cs"; - break; - case ShaderStage.Vertex: - shaderStageText = "vs"; - break; - case ShaderStage.Hull: - shaderStageText = "hs"; - break; - case ShaderStage.Domain: - shaderStageText = "ds"; - break; - case ShaderStage.Geometry: - shaderStageText = "gs"; - break; - case ShaderStage.Pixel: - shaderStageText = "ps"; - break; - default: - throw new ArgumentException("Stage not supported", "stage"); + _ => throw new NotImplementedException("An unknown EffectParameterType was found.") + }; + } + + // + // Creates a resource binding description from a Shader input binding description. + // + EffectResourceBindingDescription GetResourceBinding(ref readonly ShaderInputBindDesc bindingDescriptionRaw, string name) + { + var paramClass = EffectParameterClass.Object; + var paramType = EffectParameterType.Void; + + switch (bindingDescriptionRaw.Type) + { + case D3DShaderInputType.D3DSitTbuffer: + paramType = EffectParameterType.TextureBuffer; + paramClass = EffectParameterClass.TextureBuffer; + break; + + case D3DShaderInputType.D3DSitCbuffer: + paramType = EffectParameterType.ConstantBuffer; + paramClass = EffectParameterClass.ConstantBuffer; + break; + + case D3DShaderInputType.D3DSitTexture: + paramClass = EffectParameterClass.ShaderResourceView; + paramType = bindingDescriptionRaw.Dimension switch + { + D3DSrvDimension.D3DSrvDimensionBuffer => EffectParameterType.Buffer, + D3DSrvDimension.D3DSrvDimensionTexture1D => EffectParameterType.Texture1D, + D3DSrvDimension.D3DSrvDimensionTexture1Darray => EffectParameterType.Texture1DArray, + D3DSrvDimension.D3DSrvDimensionTexture2D => EffectParameterType.Texture2D, + D3DSrvDimension.D3DSrvDimensionTexture2Darray => EffectParameterType.Texture2DArray, + D3DSrvDimension.D3DSrvDimensionTexture2Dms => EffectParameterType.Texture2DMultisampled, + D3DSrvDimension.D3DSrvDimensionTexture2Dmsarray => EffectParameterType.Texture2DMultisampledArray, + D3DSrvDimension.D3DSrvDimensionTexture3D => EffectParameterType.Texture3D, + D3DSrvDimension.D3DSrvDimensionTexturecube => EffectParameterType.TextureCube, + D3DSrvDimension.D3DSrvDimensionTexturecubearray => EffectParameterType.TextureCubeArray, + _ => EffectParameterType.Void + }; + break; + + case D3DShaderInputType.D3DSitStructured: + paramClass = EffectParameterClass.ShaderResourceView; + paramType = EffectParameterType.StructuredBuffer; + break; + + case D3DShaderInputType.D3DSitByteaddress: + paramClass = EffectParameterClass.ShaderResourceView; + paramType = EffectParameterType.ByteAddressBuffer; + break; + + case D3DShaderInputType.D3DSitUavRwtyped: + paramClass = EffectParameterClass.UnorderedAccessView; + paramType = bindingDescriptionRaw.Dimension switch + { + D3DSrvDimension.D3DSrvDimensionBuffer => EffectParameterType.RWBuffer, + D3DSrvDimension.D3DSrvDimensionTexture1D => EffectParameterType.RWTexture1D, + D3DSrvDimension.D3DSrvDimensionTexture1Darray => EffectParameterType.RWTexture1DArray, + D3DSrvDimension.D3DSrvDimensionTexture2D => EffectParameterType.RWTexture2D, + D3DSrvDimension.D3DSrvDimensionTexture2Darray => EffectParameterType.RWTexture2DArray, + D3DSrvDimension.D3DSrvDimensionTexture3D => EffectParameterType.RWTexture3D, + _ => EffectParameterType.Void + }; + break; + + case D3DShaderInputType.D3DSitUavRwstructured: + paramClass = EffectParameterClass.UnorderedAccessView; + paramType = EffectParameterType.RWStructuredBuffer; + break; + + case D3DShaderInputType.D3DSitUavRwbyteaddress: + paramClass = EffectParameterClass.UnorderedAccessView; + paramType = EffectParameterType.RWByteAddressBuffer; + break; + + case D3DShaderInputType.D3DSitUavAppendStructured: + paramClass = EffectParameterClass.UnorderedAccessView; + paramType = EffectParameterType.AppendStructuredBuffer; + break; + + case D3DShaderInputType.D3DSitUavConsumeStructured: + paramClass = EffectParameterClass.UnorderedAccessView; + paramType = EffectParameterType.ConsumeStructuredBuffer; + break; + + case D3DShaderInputType.D3DSitUavRwstructuredWithCounter: + paramClass = EffectParameterClass.UnorderedAccessView; + paramType = EffectParameterType.RWStructuredBuffer; + break; + + case D3DShaderInputType.D3DSitSampler: + paramClass = EffectParameterClass.Sampler; + paramType = EffectParameterType.Sampler; + break; + } + + var binding = new EffectResourceBindingDescription() + { + KeyInfo = { KeyName = name }, + RawName = GetUtf8Span(bindingDescriptionRaw.Name).GetString(), + Class = paramClass, + Type = paramType, + SlotStart = (int) bindingDescriptionRaw.BindPoint, + SlotCount = (int) bindingDescriptionRaw.BindCount + }; + + return binding; + } + + // + // Converts a D3DShaderVariableType to an EffectParameterType. + // + static EffectParameterType ConvertVariableValueType(D3DShaderVariableType type, LoggerResult log) + { + if (MapType(type) is EffectParameterType effectParameterType) + return effectParameterType; + + log.Error($"Type [{type}] from D3DCompiler not supported"); + return default; + + // + // Maps a D3DShaderVariableType to an EffectParameterType. + // Returns null if the type is not supported. + // + static EffectParameterType? MapType(D3DShaderVariableType type) + { + return type switch + { + D3DShaderVariableType.D3DSvtVoid => EffectParameterType.Void, + D3DShaderVariableType.D3DSvtBool => EffectParameterType.Bool, + D3DShaderVariableType.D3DSvtInt => EffectParameterType.Int, + D3DShaderVariableType.D3DSvtFloat => EffectParameterType.Float, + D3DShaderVariableType.D3DSvtUint => EffectParameterType.UInt, + D3DShaderVariableType.D3DSvtUint8 => EffectParameterType.UInt8, + D3DShaderVariableType.D3DSvtDouble => EffectParameterType.Double, + + _ => null + }; + } + } } - return shaderStageText; - } - private static string ShaderProfileFromGraphicsProfile(GraphicsProfile graphicsProfile) - { - switch (graphicsProfile) + // + // Gets text data from a ID3D10Blob as a string. + // + static string GetTextFromBlob(ComPtr blob) { - case GraphicsProfile.Level_9_1: - return "4_0_level_9_1"; - case GraphicsProfile.Level_9_2: - return "4_0_level_9_2"; - case GraphicsProfile.Level_9_3: - return "4_0_level_9_3"; - case GraphicsProfile.Level_10_0: - return "4_0"; - case GraphicsProfile.Level_10_1: - return "4_1"; - case GraphicsProfile.Level_11_0: - case GraphicsProfile.Level_11_1: - return "5_0"; + if (blob.Handle is null) + return null; + + var blobBuffer = blob.Handle->Buffer; + return blobBuffer.GetString(); } - throw new ArgumentException("graphicsProfile"); - } - private static readonly Dictionary MapTypes = new Dictionary() - { - {ShaderVariableType.Void , EffectParameterType.Void }, - {ShaderVariableType.Bool , EffectParameterType.Bool }, - {ShaderVariableType.Int , EffectParameterType.Int }, - {ShaderVariableType.Float , EffectParameterType.Float }, - {ShaderVariableType.UInt , EffectParameterType.UInt }, - {ShaderVariableType.UInt8 , EffectParameterType.UInt8 }, - {ShaderVariableType.Double , EffectParameterType.Double }, - }; - - private EffectParameterType ConvertVariableValueType(ShaderVariableType type, LoggerResult log) - { - EffectParameterType effectParameterType; - if (!MapTypes.TryGetValue(type, out effectParameterType)) + + // + // Creates a wrapping ComPtr from the unsafe pointer to a COM object, but without calling AddRef() + // in the process. + // This is a convenience method to avoid unnecessary reference counting, as implicit conversions + // in the ComPtr class will call AddRef() automatically. + // + // NOTE: This is a mirror from the ComPtrHelpers type. + // + static ComPtr ToComPtr(T* comPtr) where T : unmanaged, IComVtbl { - log.Error($"Type [{type}] from D3DCompiler not supported"); + return new ComPtr { Handle = comPtr }; } - return effectParameterType; } } } + #endif diff --git a/sources/engine/Stride.Shaders.Compiler/IShaderCompiler.cs b/sources/engine/Stride.Shaders.Compiler/IShaderCompiler.cs index 29964c25e0..2082c2e4cc 100644 --- a/sources/engine/Stride.Shaders.Compiler/IShaderCompiler.cs +++ b/sources/engine/Stride.Shaders.Compiler/IShaderCompiler.cs @@ -1,20 +1,41 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Diagnostics; -using Stride.Rendering; -using Stride.Graphics; -namespace Stride.Shaders.Compiler -{ - internal class ShaderBytecodeResult : LoggerResult - { - public ShaderBytecode Bytecode { get; set; } - - public string DisassembleText { get; set; } - } +namespace Stride.Shaders.Compiler; - internal interface IShaderCompiler - { - ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, EffectCompilerParameters effectParameters, EffectReflection reflection, string sourceFilename = null); - } +/// +/// Provides functionality to compile Shader source code into bytecode for various Shader stages, +/// and handles Shader reflection to provide metadata about the compiled Shaders. +/// +internal interface IShaderCompiler +{ + /// + /// Compiles the specified Shader source code into byte-code for a given shader stage. + /// + /// The source code of the Shader to compile. + /// The entry point function name within the Shader source. + /// + /// The Shader stage for which the byte-code is being compiled + /// (e.g., , ). + /// + /// + /// A set of parameters that influence the compilation process, such as debug and optimization settings. + /// + /// An object to be updated with reflection data from the compiled Shader. + /// The optional filename of the Shader source. + /// + /// A containing the compiled Shader byte-code and any warnings or errors + /// encountered during compilation. + /// + /// The specified Shader is not supported. + /// + /// The specified in is not supported. + /// + /// + /// During reflection, if an unsupported or + /// is encountered. + /// + ShaderBytecodeResult Compile(string shaderSource, string entryPoint, ShaderStage stage, + EffectCompilerParameters effectParameters, EffectReflection reflection, + string? sourceFilename = null); } diff --git a/sources/engine/Stride.Shaders.Compiler/ShaderBytecodeResult.cs b/sources/engine/Stride.Shaders.Compiler/ShaderBytecodeResult.cs new file mode 100644 index 0000000000..55b2c44803 --- /dev/null +++ b/sources/engine/Stride.Shaders.Compiler/ShaderBytecodeResult.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Stride.Core.Diagnostics; + +namespace Stride.Shaders.Compiler; + +/// +/// A result class for storing the output of a Shader compilation process, serving also +/// as a logger for any messages generated during the compilation. +/// +internal class ShaderBytecodeResult : LoggerResult +{ + /// + /// Gets or sets the compiled Shader byte-code if the compilation is succesful. + /// + /// The compiled Shader byte-code, or if the compilation failed. + public ShaderBytecode? Bytecode { get; set; } + + /// + /// Gets or sets the decompiled Shader HLSL code from the compiled Shader byte-code if the compilation is succesful, + /// useful for debugging or analysis purposes. + /// + /// The decompiled Shader HLSL code, or if the compilation failed. + public string? DisassembleText { get; set; } +} diff --git a/sources/engine/Stride.Shaders.Compiler/Stride.Shaders.Compiler.csproj b/sources/engine/Stride.Shaders.Compiler/Stride.Shaders.Compiler.csproj index 42d4cb9361..dd8ba84516 100644 --- a/sources/engine/Stride.Shaders.Compiler/Stride.Shaders.Compiler.csproj +++ b/sources/engine/Stride.Shaders.Compiler/Stride.Shaders.Compiler.csproj @@ -19,7 +19,8 @@ - + + diff --git a/sources/engine/Stride.Shaders/EffectBytecode.cs b/sources/engine/Stride.Shaders/EffectBytecode.cs index 7e769c5c46..2935f5aabd 100644 --- a/sources/engine/Stride.Shaders/EffectBytecode.cs +++ b/sources/engine/Stride.Shaders/EffectBytecode.cs @@ -9,131 +9,130 @@ using Stride.Core.Serialization.Contents; using Stride.Core.Storage; -namespace Stride.Shaders +namespace Stride.Shaders; + +/// +/// Represents a compiled Effect with bytecode for each Shader stage (vertex, pixel, geometry, etc.) +/// +[DataContract] +[ContentSerializer(typeof(DataContentSerializer))] +public sealed class EffectBytecode { /// - /// Contains a compiled shader with bytecode for each stage. + /// A constant value representing the magic header stored in front of an Effect bytecode + /// to avoid reading old versions. /// - [DataContract] - [ContentSerializer(typeof(DataContentSerializer))] - public sealed class EffectBytecode - { - /// - /// Magic header stored in front of an effect bytecode to avoid reading old versions. - /// - /// - /// If EffectBytecode is changed, this number must be changed manually. - /// - public const uint MagicHeader = 0xEFFEC007; - - /// - /// The reflection from the bytecode. - /// - public EffectReflection Reflection; - - /// - /// The used sources - /// - public HashSourceCollection HashSources; - - /// - /// The bytecode for each stage. - /// - public ShaderBytecode[] Stages; - - /// - /// Computes a unique identifier for this bytecode instance. - /// - /// ObjectId. - public ObjectId ComputeId() - { - var effectBytecode = this; + public const uint MagicHeader = 0xEFFEC007; // NOTE: If EffectBytecode is changed, this number must be changed manually - // We should most of the time have stages, unless someone is calling this method on a new EffectBytecode - if (effectBytecode.Stages != null) - { - effectBytecode = (EffectBytecode)MemberwiseClone(); - effectBytecode.Stages = (ShaderBytecode[])effectBytecode.Stages.Clone(); + /// + /// The reflection data extracted from the Effect bytecode. + /// + public EffectReflection Reflection; - // Because ShaderBytecode.Data can vary, we are calculating the bytecodeId only with the ShaderBytecode.Id. - for (int i = 0; i < effectBytecode.Stages.Length; i++) - { - var newStage = effectBytecode.Stages[i].Clone(); - effectBytecode.Stages[i] = newStage; - newStage.Data = null; - } - } + /// + /// A collection of each of the Effect Shader source URIs and their associated s. + /// + public HashSourceCollection HashSources; - // Not optimized: Pre-calculate bytecodeId in order to avoid writing to same storage - ObjectId newBytecodeId; - var memStream = new MemoryStream(); - using (var stream = new DigestStream(memStream)) - { - effectBytecode.WriteTo(stream); - newBytecodeId = stream.CurrentHash; - } - return newBytecodeId; - } + /// + /// The Effect bytecode for each of the Shader stages. + /// + public ShaderBytecode[] Stages; - /// - /// Loads an from a buffer. - /// - /// The buffer. - /// EffectBytecode. - /// buffer - public static EffectBytecode FromBytes(byte[] buffer) - { - if (buffer == null) throw new ArgumentNullException("buffer"); - return FromStream(new MemoryStream(buffer)); - } - /// - /// Loads an from a buffer. - /// - /// The buffer. - /// EffectBytecode. - /// buffer - public static EffectBytecode FromBytesSafe(byte[] buffer) - { - var result = FromBytes(buffer); - if (result == null) - { - throw new ArgumentException("Invalid effect buffer bytecode. Magic number is not matching."); - } - return result; - } + /// + /// Computes a unique identifier for the Effect bytecode. + /// + /// An unique for the Effect bytecode. + public ObjectId ComputeId() + { + var effectBytecode = this; - /// - /// Loads an from a stream. - /// - /// The stream. - /// EffectBytecode or null if the magic header is not matching - /// stream - public static EffectBytecode FromStream(Stream stream) + // We should most of the time have stages, unless someone is calling this method on a new EffectBytecode + if (effectBytecode.Stages is not null) { - if (stream == null) throw new ArgumentNullException("stream"); - var reader = new BinarySerializationReader(stream); - var version = reader.Read(); - // Version is not matching, return null - if (version != MagicHeader) + effectBytecode = (EffectBytecode) MemberwiseClone(); + + effectBytecode.Stages = (ShaderBytecode[]) effectBytecode.Stages.Clone(); + + // Because ShaderBytecode.Data can vary, we are calculating the bytecodeId only with the ShaderBytecode.Id + for (int i = 0; i < effectBytecode.Stages.Length; i++) { - return null; + var newStage = effectBytecode.Stages[i].Clone(); + effectBytecode.Stages[i] = newStage; + newStage.Data = null; } - return reader.Read(); } - /// - /// Writes this to a stream with its magic number. - /// - /// The stream. - /// stream - public void WriteTo(Stream stream) + // TODO: Optimize: Pre-calculate bytecodeId in order to avoid writing to same storage + ObjectId newBytecodeId; + var memStream = new MemoryStream(); + using (var stream = new DigestStream(memStream)) { - if (stream == null) throw new ArgumentNullException("stream"); - var writer = new BinarySerializationWriter(stream); - writer.Write(MagicHeader); - writer.Write(this); + effectBytecode.WriteTo(stream); + newBytecodeId = stream.CurrentHash; } + return newBytecodeId; + } + + /// + /// Loads an from a buffer. + /// + /// The buffer to read from. + /// The loaded Effect bytecode. + /// is . + public static EffectBytecode FromBytes(byte[] buffer) + { + ArgumentNullException.ThrowIfNull(buffer); + + return FromStream(new MemoryStream(buffer)); + } + + /// + /// Loads an from a buffer. + /// + /// The buffer to read from. + /// The loaded Effect bytecode. + /// is . + /// contains invalid Effect bytecode. + public static EffectBytecode FromBytesSafe(byte[] buffer) + { + var result = FromBytes(buffer); + return result ?? throw new ArgumentException($"{nameof(buffer)} contains invalid Effect bytecode. Could not find magic header 0x{MagicHeader:X}."); + } + + /// + /// Loads an from a stream. + /// + /// The stream to read from. + /// The loaded Effect bytecode, or if the magic header is not matching. + /// is . + public static EffectBytecode FromStream(Stream stream) + { + ArgumentNullException.ThrowIfNull(stream); + + var reader = new BinarySerializationReader(stream); + var version = reader.Read(); + + // Version is not matching, return null + if (version != MagicHeader) + return null; + + return reader.Read(); + } + + /// + /// Writes the to a stream with its magic number. + /// + /// The stream to write to. + /// is . + public void WriteTo(Stream stream) + { + ArgumentNullException.ThrowIfNull(stream); + + var writer = new BinarySerializationWriter(stream); + writer.Write(MagicHeader); + writer.Write(this); } } diff --git a/sources/engine/Stride.Shaders/EffectParameterClass.cs b/sources/engine/Stride.Shaders/EffectParameterClass.cs index 4d8cbc7672..63ebb4a31a 100644 --- a/sources/engine/Stride.Shaders/EffectParameterClass.cs +++ b/sources/engine/Stride.Shaders/EffectParameterClass.cs @@ -1,87 +1,87 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Shaders +namespace Stride.Shaders; + +/// +/// Defines the class of a Effect / Shader parameter. +/// +/// +/// The class of a Effect / Shader parameter is not a C# ; it identifies the kind of variable +/// such as scalar, vector, object, and so on. +/// +[DataContract] +public enum EffectParameterClass : byte { /// - /// Values that identify the class of a shader variable. + /// The Shader parameter is a scalar value. + /// + Scalar = 0, + + /// + /// The Shader parameter is a vector value. + /// + Vector = 1, + + /// + /// The Shader parameter is a row-major matrix. + /// + MatrixRows = 2, + + /// + /// The Shader parameter is a column-major matrix. + /// + MatrixColumns = 3, + + /// + /// The Shader parameter is an object. + /// + Object = 4, + + /// + /// The Shader parameter is a structure. + /// + Struct = 5, + + /// + /// The Shader parameter is a class. + /// + InterfaceClass = 6, + + /// + /// The Shader parameter is an interface. + /// + InterfacePointer = 7, + + /// + /// The Shader parameter is a Sampler State object. + /// + Sampler = 8, + + /// + /// The Shader parameter is a Shader Resource View. + /// + ShaderResourceView = 9, + + /// + /// The Shader parameter is a Constant Buffer. + /// + ConstantBuffer = 10, + + /// + /// The Shader parameter is a Texture. + /// + TextureBuffer = 11, + + /// + /// The Shader parameter is an Unordered Access View. + /// + UnorderedAccessView = 12, + + /// + /// The Shader parameter is a vector value that represents a color. /// - /// - /// The class of a shader variable is not a programming class; the class identifies the variable class such as scalar, vector, object, and so on. - /// - [DataContract] - public enum EffectParameterClass : byte - { - /// - ///

The shader variable is a scalar.

- ///
- Scalar = unchecked((int)0), - - /// - ///

The shader variable is a vector.

- ///
- Vector = unchecked((int)1), - - /// - ///

The shader variable is a row-major matrix.

- ///
- MatrixRows = unchecked((int)2), - - /// - ///

The shader variable is a column-major matrix.

- ///
- MatrixColumns = unchecked((int)3), - - /// - ///

The shader variable is an object.

- ///
- Object = unchecked((int)4), - - /// - ///

The shader variable is a structure.

- ///
- Struct = unchecked((int)5), - - /// - ///

The shader variable is a class.

- ///
- InterfaceClass = unchecked((int)6), - - /// - ///

The shader variable is an interface.

- ///
- InterfacePointer = unchecked((int)7), - - /// - /// A sampler state object. - /// - Sampler = unchecked((int)8), - - /// - /// A shader resource view. - /// - ShaderResourceView = unchecked((int)9), - - /// - /// A constant buffer - /// - ConstantBuffer = unchecked((int)10), - - /// - /// A constant buffer - /// - TextureBuffer = unchecked((int)11), - - /// - /// An unordered access view - /// - UnorderedAccessView = unchecked((int)12), - - /// - ///

The shader variable is a vector.

- ///
- Color = unchecked((int)13), - - } + Color = 13 } diff --git a/sources/engine/Stride.Shaders/EffectParameterType.cs b/sources/engine/Stride.Shaders/EffectParameterType.cs index b335776b86..98daa0793b 100644 --- a/sources/engine/Stride.Shaders/EffectParameterType.cs +++ b/sources/engine/Stride.Shaders/EffectParameterType.cs @@ -1,203 +1,203 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Shaders +namespace Stride.Shaders; + +/// +/// Values that identify various types of data, Textures, and Buffers that can be assigned to a Shader parameter. +/// +[DataContract] +public enum EffectParameterType : byte { /// - ///

Values that identify various data, texture, and buffer types that can be assigned to a shader variable.

- ///
- [DataContract] - public enum EffectParameterType : byte - { - /// - ///

The variable is a void reference.

- ///
- Void = unchecked((int)0), - - /// - ///

The variable is a boolean.

- ///
- Bool = unchecked((int)1), - - /// - ///

The variable is an integer.

- ///
- Int = unchecked((int)2), - - /// - ///

The variable is a floating-point number.

- ///
- Float = unchecked((int)3), - - /// - ///

The variable is a string.

- ///
- String = unchecked((int)4), - - /// - ///

The variable is a texture.

- ///
- Texture = unchecked((int)5), - - /// - ///

The variable is a 1D texture.

- ///
- Texture1D = unchecked((int)6), - - /// - ///

The variable is a 2D texture.

- ///
- Texture2D = unchecked((int)7), - - /// - ///

The variable is a 3D texture.

- ///
- Texture3D = unchecked((int)8), - - /// - ///

The variable is a texture cube.

- ///
- TextureCube = unchecked((int)9), - - /// - ///

The variable is a sampler.

- ///
- Sampler = unchecked((int)10), - - /// - ///

The variable is a sampler.

- ///
- Sampler1D = unchecked((int)11), - - /// - ///

The variable is a sampler.

- ///
- Sampler2D = unchecked((int)12), - - /// - ///

The variable is a sampler.

- ///
- Sampler3D = unchecked((int)13), - - /// - ///

The variable is a sampler.

- ///
- SamplerCube = unchecked((int)14), - - /// - ///

The variable is an unsigned integer.

- ///
- UInt = unchecked((int)19), - - /// - ///

The variable is an 8-bit unsigned integer.

- ///
- UInt8 = unchecked((int)20), - - /// - ///

The variable is a buffer.

- ///
- Buffer = unchecked((int)25), - - /// - ///

The variable is a constant buffer.

- ///
- ConstantBuffer = unchecked((int)26), - - /// - ///

The variable is a texture buffer.

- ///
- TextureBuffer = unchecked((int)27), - - /// - ///

The variable is a 1D-texture array.

- ///
- Texture1DArray = unchecked((int)28), - - /// - ///

The variable is a 2D-texture array.

- ///
- Texture2DArray = unchecked((int)29), - - /// - ///

The variable is a 2D-multisampled texture.

- ///
- Texture2DMultisampled = unchecked((int)32), - - /// - ///

The variable is a 2D-multisampled-texture array.

- ///
- Texture2DMultisampledArray = unchecked((int)33), - - /// - ///

The variable is a texture-cube array.

- ///
- TextureCubeArray = unchecked((int)34), - - /// - ///

The variable is a double precision (64-bit) floating-point number.

- ///
- Double = unchecked((int)39), - - /// - ///

The variable is a 1D read-and-write texture.

- ///
- RWTexture1D = unchecked((int)40), - - /// - ///

The variable is an array of 1D read-and-write textures.

- ///
- RWTexture1DArray = unchecked((int)41), - - /// - ///

The variable is a 2D read-and-write texture.

- ///
- RWTexture2D = unchecked((int)42), - - /// - ///

The variable is an array of 2D read-and-write textures.

- ///
- RWTexture2DArray = unchecked((int)43), - - /// - ///

The variable is a 3D read-and-write texture.

- ///
- RWTexture3D = unchecked((int)44), - - /// - ///

The variable is a read-and-write buffer.

- ///
- RWBuffer = unchecked((int)45), - - /// - ///

The variable is a byte-address buffer.

- ///
- ByteAddressBuffer = unchecked((int)46), - - /// - ///

The variable is a read-and-write byte-address buffer.

- ///
- RWByteAddressBuffer = unchecked((int)47), - - /// - ///

The variable is a structured buffer.

For more information about structured buffer, see the Remarks section.

- ///
- StructuredBuffer = unchecked((int)48), - - /// - ///

The variable is a read-and-write structured buffer.

- ///
- RWStructuredBuffer = unchecked((int)49), - - /// - ///

The variable is an append structured buffer.

- ///
- AppendStructuredBuffer = unchecked((int)50), - - /// - ///

The variable is a consume structured buffer.

- ///
- ConsumeStructuredBuffer = unchecked((int)51), - } + /// The parameter is a reference. + ///
+ Void = 0, + + /// + /// The parameter is a boolean (i.e. ). + /// + Bool = 1, + + /// + /// The parameter is an integer (i.e. ). + /// + Int = 2, + + /// + /// The parameter is a single precision (32-bit) floating-point number (i.e. ). + /// + Float = 3, + + /// + /// The parameter is a . + /// + String = 4, + + /// + /// The parameter is a Texture. + /// + Texture = 5, + + /// + /// The parameter is a 1D Texture. + /// + Texture1D = 6, + + /// + /// The parameter is a 2D Texture. + /// + Texture2D = 7, + + /// + /// The parameter is a 3D Texture. + /// + Texture3D = 8, + + /// + /// The parameter is a Texture Cube. + /// + TextureCube = 9, + + /// + /// The parameter is a Sampler. + /// + Sampler = 10, + + /// + /// The parameter is a 1D Sampler. + /// + Sampler1D = 11, + + /// + /// The parameter is a 2D Sampler. + /// + Sampler2D = 12, + + /// + /// The parameter is a 3D Sampler. + /// + Sampler3D = 13, + + /// + /// The parameter is a Cube Sampler. + /// + SamplerCube = 14, + + /// + /// The parameter is an unsigned integer (i.e. ). + /// + UInt = 19, + + /// + /// The parameter is an 8-bit unsigned integer (i.e. ). + /// + UInt8 = 20, + + /// + /// The parameter is a Buffer. + /// + Buffer = 25, + + /// + /// The parameter is a Constant Buffer. + /// + ConstantBuffer = 26, + + /// + /// The parameter is a Texture. + /// + TextureBuffer = 27, + + /// + /// The parameter is a 1D Texture Array. + /// + Texture1DArray = 28, + + /// + /// The parameter is a 2D Texture Array. + /// + Texture2DArray = 29, + + /// + /// The parameter is a Multi-sampled 2D Texture. + /// + Texture2DMultisampled = 32, + + /// + /// The parameter is a Multi-sampled 2D Texture Array. + /// + Texture2DMultisampledArray = 33, + + /// + /// The parameter is a Cube Texture Array. + /// + TextureCubeArray = 34, + + /// + /// The parameter is a double precision (64-bit) floating-point number. + /// + Double = 39, + + /// + /// The parameter is a 1D Read-and-Write Texture. + /// + RWTexture1D = 40, + + /// + /// The parameter is an Array of 1D Read-and-Write Textures. + /// + RWTexture1DArray = 41, + + /// + /// The parameter is a 2D Read-and-Write Texture. + /// + RWTexture2D = 42, + + /// + /// The parameter is an Array of 2D Read-and-Write Textures. + /// + RWTexture2DArray = 43, + + /// + /// The parameter is a 3D Read-and-Write Texture. + /// + RWTexture3D = 44, + + /// + /// The parameter is a Read-and-Write Buffer. + /// + RWBuffer = 45, + + /// + /// The parameter is a Byte-Address Buffer. + /// + ByteAddressBuffer = 46, + + /// + /// The parameter is a Read-and-Write Byte-Address Buffer. + /// + RWByteAddressBuffer = 47, + + /// + /// The parameter is a Structured Buffer. + /// + StructuredBuffer = 48, + + /// + /// The parameter is a Read-and-Write Structured Buffer. + /// + RWStructuredBuffer = 49, + + /// + /// The parameter is an Append Structured Buffer. + /// + AppendStructuredBuffer = 50, + + /// + /// The parameter is a Consume Structured Buffer. + /// + ConsumeStructuredBuffer = 51 } diff --git a/sources/engine/Stride.Shaders/HashSourceCollection.cs b/sources/engine/Stride.Shaders/HashSourceCollection.cs index 4fa48ebb5e..92a61ee993 100644 --- a/sources/engine/Stride.Shaders/HashSourceCollection.cs +++ b/sources/engine/Stride.Shaders/HashSourceCollection.cs @@ -1,50 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; + using Stride.Core; -using Stride.Core.Serialization; using Stride.Core.Storage; -namespace Stride.Shaders +namespace Stride.Shaders; + +/// +/// A collection associating the Shader source URLs and their corresponding s. +/// +[DataContract] +public sealed class HashSourceCollection : Dictionary, IEquatable { /// - /// A dictionary of associations betweens asset shader urls and + /// Initializes a new instance of the class. /// - [DataContract] - public class HashSourceCollection : Dictionary, IEquatable + public HashSourceCollection() { } + + + /// + public bool Equals(HashSourceCollection other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + + return Utilities.Compare(this, other); + } + + /// + public override bool Equals(object obj) + { + return obj is HashSourceCollection other && Equals(other); + } + + /// + public override int GetHashCode() { - /// - /// Initializes a new instance of the class. - /// - public HashSourceCollection() - { - } - - /// - /// Indicates whether the current object is equal to another object of the same type. - /// - /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. - public bool Equals(HashSourceCollection other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - - return Utilities.Compare(this, other); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((HashSourceCollection)obj); - } - - public override int GetHashCode() - { - return Utilities.GetHashCode(this); - } + return Utilities.GetHashCode(this); } } diff --git a/sources/engine/Stride.Shaders/ShaderBytecode.cs b/sources/engine/Stride.Shaders/ShaderBytecode.cs index 2196282804..bbfe303780 100644 --- a/sources/engine/Stride.Shaders/ShaderBytecode.cs +++ b/sources/engine/Stride.Shaders/ShaderBytecode.cs @@ -1,78 +1,82 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Text; + using Stride.Core; using Stride.Core.Storage; -namespace Stride.Shaders +namespace Stride.Shaders; + +/// +/// Represents a compiled Shader bytecode. +/// +[DataContract] +public partial class ShaderBytecode { /// - /// The bytecode of an effect. + /// The stage of this Shader bytecode. /// - [DataContract] - public partial class ShaderBytecode - { - /// - /// The stage of this Bytecode. - /// - public ShaderStage Stage; + public ShaderStage Stage; + + /// + /// Gets or sets an unique identifier for the Shader bytecode. + /// + public ObjectId Id { get; set; } // TODO: Public set? - /// - /// Hash of the Data. - /// - public ObjectId Id { get; set; } + /// + /// Gets or sets the compiled Shader bytecode data that should be used to create the Shader. + /// + public byte[] Data { get; set; } - /// - /// Gets the shader data that should be used to create the . - /// - /// - /// The shader data. - /// - public byte[] Data { get; set; } - /// - /// Initializes a new instance of the class. - /// - public ShaderBytecode() - { - } + /// + /// Initializes a new instance of the class. + /// + public ShaderBytecode() { } + + /// + /// Initializes a new instance of the class. + /// + /// An unique identifier for the compiled Shader bytecode data. + /// The compiled Shader bytecode data. + public ShaderBytecode(ObjectId id, byte[] data) + { + Id = id; + Data = data; + } + - /// - /// Initializes a new instance of the class. - /// - /// The data. - public ShaderBytecode(ObjectId id, byte[] data) - { - Id = id; - Data = data; - } + /// + /// Creates a shallow copy of the current . + /// + /// A shallow copy of the current instance. + public ShaderBytecode Clone() + { + return (ShaderBytecode) MemberwiseClone(); + } - /// - /// Shallow clones this instance. - /// - /// ShaderBytecode. - public ShaderBytecode Clone() - { - return (ShaderBytecode)MemberwiseClone(); - } + /// + /// Performs an implicit conversion from to , returning + /// the compiled Shader data. + /// + /// The compiled Shader bytecode. + /// The compiled Shader bytecode data converted to a byte array. + public static implicit operator byte[](ShaderBytecode shaderBytecode) + { + return shaderBytecode.Data; + } - /// - /// Performs an implicit conversion from to . - /// - /// The shader bytecode. - /// The result of the conversion. - public static implicit operator byte[](ShaderBytecode shaderBytecode) - { - return shaderBytecode.Data; - } + /// + /// Gets the Shader data as a . In some platforms (e.g. OpenGL), the data + /// represents the GLSL source code, instead of compiled bytecode. + /// + /// The Shader data as GLSL source code. + public string GetDataAsString() + { + // TODO: This is a workaround for OpenGL, where the shader bytecode is actually GLSL source code. + // But the class is still called ShaderBytecode! - /// - /// Gets the data as a string. - /// - /// System.String. - public string GetDataAsString() - { - return Encoding.UTF8.GetString(Data, 0, Data.Length); - } + return Encoding.UTF8.GetString(Data); } } diff --git a/sources/engine/Stride.Shaders/ShaderStage.cs b/sources/engine/Stride.Shaders/ShaderStage.cs index c399ccf49a..0be41b5258 100644 --- a/sources/engine/Stride.Shaders/ShaderStage.cs +++ b/sources/engine/Stride.Shaders/ShaderStage.cs @@ -1,49 +1,48 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -using Stride.Core.Serialization; -namespace Stride.Shaders +namespace Stride.Shaders; + +/// +/// Specifies a particular shader stage. +/// +[DataContract] +public enum ShaderStage { /// - /// Enum to specify shader stage. + /// No shader stage defined. + /// + None = 0, + + /// + /// The Vertex Shader stage. + /// + Vertex = 1, + + /// + /// The Hull Shader stage. + /// + Hull = 2, + + /// + /// The Domain Shader stage. + /// + Domain = 3, + + /// + /// The Geometry Shader stage. + /// + Geometry = 4, + + /// + /// The Pixel Shader stage. + /// + Pixel = 5, + + /// + /// The Compute Shader stage. /// - [DataContract] - public enum ShaderStage - { - /// - /// No shader stage defined. - /// - None = 0, - - /// - /// The vertex shader stage. - /// - Vertex = 1, - - /// - /// The Hull shader stage. - /// - Hull = 2, - - /// - /// The domain shader stage. - /// - Domain = 3, - - /// - /// The geometry shader stage. - /// - Geometry = 4, - - /// - /// The pixel shader stage. - /// - Pixel = 5, - - /// - /// The compute shader stage. - /// - Compute = 6, - } + Compute = 6 } diff --git a/sources/engine/Stride.SpriteStudio.Runtime/SpriteStudioRenderFeature.cs b/sources/engine/Stride.SpriteStudio.Runtime/SpriteStudioRenderFeature.cs index 2faa202181..d658beaec7 100644 --- a/sources/engine/Stride.SpriteStudio.Runtime/SpriteStudioRenderFeature.cs +++ b/sources/engine/Stride.SpriteStudio.Runtime/SpriteStudioRenderFeature.cs @@ -1,9 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using Stride.Core; using Stride.Core.Mathematics; -using Stride.Engine; using Stride.Graphics; using Stride.Rendering; @@ -26,26 +26,22 @@ protected override void InitializeCore() sprite3DBatch = new Sprite3DBatch(Context.GraphicsDevice); - var blendDesc = new BlendStateDescription(Blend.SourceAlpha, Blend.One) + var blendDesc = new BlendStateDescription(Blend.SourceAlpha, Blend.One); + blendDesc.RenderTargets[0] = new() { - RenderTarget0 = - { - BlendEnable = true, - ColorBlendFunction = BlendFunction.ReverseSubtract, - AlphaBlendFunction = BlendFunction.ReverseSubtract - } + BlendEnable = true, + ColorBlendFunction = BlendFunction.ReverseSubtract, + AlphaBlendFunction = BlendFunction.ReverseSubtract }; SubBlendState = blendDesc; - blendDesc = new BlendStateDescription(Blend.DestinationColor, Blend.InverseSourceAlpha) + blendDesc = new BlendStateDescription(Blend.DestinationColor, Blend.InverseSourceAlpha); + blendDesc.RenderTargets[0] = new() { - RenderTarget0 = - { - BlendEnable = true, - ColorBlendFunction = BlendFunction.Add, - AlphaSourceBlend = Blend.Zero, - AlphaBlendFunction = BlendFunction.Add - } + BlendEnable = true, + ColorBlendFunction = BlendFunction.Add, + AlphaSourceBlend = Blend.Zero, + AlphaBlendFunction = BlendFunction.Add }; MultBlendState = blendDesc; } diff --git a/sources/engine/Stride.Video/FFmpeg/FFmpegCodec.cs b/sources/engine/Stride.Video/FFmpeg/FFmpegCodec.cs index 2ce0af5218..988962e735 100644 --- a/sources/engine/Stride.Video/FFmpeg/FFmpegCodec.cs +++ b/sources/engine/Stride.Video/FFmpeg/FFmpegCodec.cs @@ -7,20 +7,28 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using FFmpeg.AutoGen; +using Silk.NET.Core; +using Silk.NET.Core.Native; +using Silk.NET.DXGI; using Stride.Core.Diagnostics; using Stride.Graphics; #if STRIDE_GRAPHICS_API_DIRECT3D11 -using SharpDX.Direct3D11; +using Silk.NET.Direct3D11; #endif +using FFmpegID3D11VideoContext = FFmpeg.AutoGen.ID3D11VideoContext; +using FFmpegID3D11VideoDecoder = FFmpeg.AutoGen.ID3D11VideoDecoder; +using FFmpegD3D11_VIDEO_DECODER_CONFIG = FFmpeg.AutoGen.D3D11_VIDEO_DECODER_CONFIG; +using FFmpegID3D11VideoDecoderOutputView = FFmpeg.AutoGen.ID3D11VideoDecoderOutputView; + namespace Stride.Video.FFmpeg { /// /// Represents a codec. /// - /// - /// + /// + /// public sealed unsafe class FFmpegCodec : IDisposable { public static Logger Logger = GlobalLogger.GetLogger(nameof(FFmpegCodec)); @@ -32,53 +40,19 @@ public sealed unsafe class FFmpegCodec : IDisposable private bool isDisposed = false; #if STRIDE_GRAPHICS_API_DIRECT3D11 - private VideoDecoder videoHardwareDecoder; - private VideoDecoderOutputView videoHardwareDecoderView; + private Silk.NET.Direct3D11.ID3D11VideoDecoder* videoHardwareDecoder; + private Silk.NET.Direct3D11.ID3D11VideoDecoderOutputView* videoHardwareDecoderView; #endif - private PinnedObject videoContextHandle; - private PinnedObject videoDecoderHandle; - private PinnedObject decoderConfigHandle; - private PinnedObject decoderOuputViewsHandle; - - private class PinnedObject - { - private GCHandle handle; - private GCHandle arrayHandle; - - public IntPtr Pointer - { - get - { - if (arrayHandle.IsAllocated) - return arrayHandle.AddrOfPinnedObject(); - - return handle.AddrOfPinnedObject(); - } - } - - public PinnedObject(T referenceObject, bool asArray = false) - { - handle = GCHandle.Alloc(referenceObject, GCHandleType.Pinned); - - if (asArray) - arrayHandle = GCHandle.Alloc(handle.AddrOfPinnedObject(), GCHandleType.Pinned); - } - - public void Dispose() - { - if (arrayHandle.IsAllocated) - handle.Free(); - - if (handle.IsAllocated) - handle.Free(); - } - } + private PinnedObject videoContextHandle; + private PinnedObject videoDecoderHandle; + private PinnedObject decoderConfigHandle; + private PinnedObject decoderOuputViewsHandle; private AVHWDeviceType HardwareDeviceType => AVHWDeviceType.AV_HWDEVICE_TYPE_D3D11VA; #if STRIDE_GRAPHICS_API_DIRECT3D11 - private static SharpDX.DXGI.Format DecoderOuputFormat => SharpDX.DXGI.Format.NV12; + private static Format DecoderOuputFormat => Format.FormatNV12; #endif public bool IsHardwareAccelerated { get; } @@ -89,6 +63,7 @@ public void Dispose() private AVCodecContext_get_format getFormat; + /// /// Initializes a new instance of the class. /// @@ -99,7 +74,7 @@ public FFmpegCodec(GraphicsDevice graphcsDevice, AVCodecContext* originalContext if (pCodec == null) // TODO: log? throw new ApplicationException("Unsupported codec."); - + int ret; var pCodecContext = ffmpeg.avcodec_alloc_context3(pCodec); var pCodecParam = ffmpeg.avcodec_parameters_alloc(); @@ -159,81 +134,110 @@ public FFmpegCodec(GraphicsDevice graphcsDevice, AVCodecContext* originalContext #if STRIDE_GRAPHICS_API_DIRECT3D11 private void CreateHarwareAccelerationContext(GraphicsDevice graphicsDevice, AVCodecContext* pAVCodecContext, AVCodec* pCodec) { - var videoDevice = graphicsDevice?.NativeDevice?.QueryInterface(); - var videoContext = graphicsDevice?.NativeDeviceContext?.QueryInterface(); - if (videoDevice == null || videoContext == null) + if (graphicsDevice is null || graphicsDevice.NativeDevice.IsNull() || graphicsDevice.NativeDeviceContext.IsNull()) return; - - foreach (var profile in FindVideoFormatCompatibleProfiles(videoDevice, *pCodec)) + + graphicsDevice.NativeDevice.QueryInterface(out ComPtr videoDevice); + graphicsDevice.NativeDeviceContext.QueryInterface(out ComPtr videoContext); + + if (videoDevice.IsNull() || videoContext.IsNull()) + return; + + foreach (var profile in FindVideoFormatCompatibleProfiles(videoDevice)) { // Create and configure the video decoder - var videoDecoderDescription = new VideoDecoderDescription + var videoDecoderDescription = new VideoDecoderDesc { Guid = profile, - SampleWidth = pAVCodecContext->width, - SampleHeight = pAVCodecContext->height, - OutputFormat = DecoderOuputFormat, + SampleWidth = (uint) pAVCodecContext->width, + SampleHeight = (uint) pAVCodecContext->height, + OutputFormat = DecoderOuputFormat }; - videoDevice.GetVideoDecoderConfigCount(ref videoDecoderDescription, out var configCount); - for (var i = 0; i < configCount; ++i) + uint configCount; + videoDevice.GetVideoDecoderConfigCount(videoDecoderDescription, &configCount); + for (uint i = 0; i < configCount; ++i) { - // get and check the decoder configuration for the profile - videoDevice.GetVideoDecoderConfig(ref videoDecoderDescription, i, out var decoderConfig); - //if (check to performe on the config) + // Get and check the decoder configuration for the profile + VideoDecoderConfig decoderConfig; + videoDevice.GetVideoDecoderConfig(videoDecoderDescription, i, &decoderConfig); + //if (check to perform on the config) // continue; - // create the decoder from the configuration - videoDevice.CreateVideoDecoder(ref videoDecoderDescription, ref decoderConfig, out videoHardwareDecoder); + // Create the decoder from the configuration + Silk.NET.Direct3D11.ID3D11VideoDecoder* videoHardwareDecoder; + HResult result = videoDevice.CreateVideoDecoder(videoDecoderDescription, decoderConfig, &videoHardwareDecoder); + + if (result.IsFailure) + result.Throw(); - // create the decoder output view - var videoDecoderOutputViewDescription = new VideoDecoderOutputViewDescription + // Create the decoder output view + var videoDecoderOutputViewDescription = new VideoDecoderOutputViewDesc { DecodeProfile = profile, - Dimension = VdovDimension.Texture2D, - Texture2D = new Texture2DVdov { ArraySlice = 0, }, + ViewDimension = VdovDimension.Texture2D, + Texture2D = new Tex2DVdov { ArraySlice = 0 } }; DecoderOutputTexture = Texture.New2D(graphicsDevice, pAVCodecContext->width, pAVCodecContext->height, (PixelFormat)DecoderOuputFormat, TextureFlags.RenderTarget | TextureFlags.ShaderResource); - videoDevice.CreateVideoDecoderOutputView(DecoderOutputTexture.NativeResource, ref videoDecoderOutputViewDescription, out videoHardwareDecoderView); + + Silk.NET.Direct3D11.ID3D11VideoDecoderOutputView* videoDecoderOutputView; + result = videoDevice.CreateVideoDecoderOutputView(DecoderOutputTexture.NativeResource, videoDecoderOutputViewDescription, &videoDecoderOutputView); + + if (result.IsFailure) + result.Throw(); + + videoHardwareDecoderView = videoDecoderOutputView; // Create and fill the hardware context var contextd3d11 = ffmpeg.av_d3d11va_alloc_context(); - - var iVideoContext = new ID3D11VideoContext { lpVtbl = (ID3D11VideoContextVtbl*)videoContext.NativePointer }; - videoContextHandle = new PinnedObject(iVideoContext); - contextd3d11->video_context = (ID3D11VideoContext*)videoContextHandle.Pointer; - - var iVideoDecoder = new ID3D11VideoDecoder { lpVtbl = (ID3D11VideoDecoderVtbl*)videoHardwareDecoder.NativePointer }; - videoDecoderHandle = new PinnedObject(iVideoDecoder); - contextd3d11->decoder = (ID3D11VideoDecoder*)videoDecoderHandle.Pointer; - - decoderConfigHandle = new PinnedObject(decoderConfig.ToFFmpegDecoderConfig()); - contextd3d11->cfg = (D3D11_VIDEO_DECODER_CONFIG*)decoderConfigHandle.Pointer; - - var iVideoOutputView = new ID3D11VideoDecoderOutputView { lpVtbl = (ID3D11VideoDecoderOutputViewVtbl*)videoHardwareDecoderView.NativePointer }; - decoderOuputViewsHandle = new PinnedObject(iVideoOutputView, true); - contextd3d11->surface = (ID3D11VideoDecoderOutputView**)decoderOuputViewsHandle.Pointer; + + var iVideoContext = new FFmpegID3D11VideoContext { lpVtbl = (ID3D11VideoContextVtbl*) videoContext.AsVtblPtr() }; + videoContextHandle = new PinnedObject(iVideoContext); + contextd3d11->video_context = (FFmpegID3D11VideoContext*) videoContextHandle.Pointer; + + var iVideoDecoder = new FFmpegID3D11VideoDecoder { lpVtbl = (ID3D11VideoDecoderVtbl*) videoHardwareDecoder->LpVtbl }; + videoDecoderHandle = new PinnedObject(iVideoDecoder); + contextd3d11->decoder = (FFmpegID3D11VideoDecoder*) videoDecoderHandle.Pointer; + + decoderConfigHandle = new PinnedObject(decoderConfig.ToFFmpegDecoderConfig()); + contextd3d11->cfg = (FFmpegD3D11_VIDEO_DECODER_CONFIG*) decoderConfigHandle.Pointer; + + var iVideoOutputView = new FFmpegID3D11VideoDecoderOutputView { lpVtbl = (ID3D11VideoDecoderOutputViewVtbl*) videoHardwareDecoderView->LpVtbl }; + decoderOuputViewsHandle = new PinnedObject(iVideoOutputView, asArray: true); + contextd3d11->surface = (FFmpegID3D11VideoDecoderOutputView**) decoderOuputViewsHandle.Pointer; contextd3d11->surface_count = 1; pAVCodecContext->hwaccel_context = contextd3d11; } } - } - private static IEnumerable FindVideoFormatCompatibleProfiles(VideoDevice videoDevice, AVCodec codec) - { - for (var i = 0; i < videoDevice.VideoDecoderProfileCount; ++i) + /// + /// Enumerates the hardware video decoding profiles compatible with the desired output format. + /// + static IEnumerable FindVideoFormatCompatibleProfiles(ComPtr videoDevice) { - videoDevice.GetVideoDecoderProfile(i, out var profile); + var profileCount = videoDevice.GetVideoDecoderProfileCount(); - // TODO Check profile id + var foundDecoderProfiles = new List(); - videoDevice.CheckVideoDecoderFormat(profile, DecoderOuputFormat, out var suppported); - if (suppported) - yield return profile; - } + for (uint i = 0; i < profileCount; ++i) + { + Guid decoderProfile; + HResult result = videoDevice.GetVideoDecoderProfile(i, &decoderProfile); + + if (result.IsFailure) + continue; + + // TODO Check profile id - yield break; + Bool32 supported; + videoDevice.CheckVideoDecoderFormat(decoderProfile, DecoderOuputFormat, (int*) &supported); + if (supported) + foundDecoderProfiles.Add(decoderProfile); + } + + return foundDecoderProfiles; + } } #endif @@ -265,7 +269,7 @@ public void Flush(AVFrame* pFrame) pAVCodecContext->get_format = getFormat; // for some reason this is needed after the flush (native crash otherwise) } } - + public void Dispose() { if (isDisposed) @@ -279,8 +283,11 @@ public void Dispose() decoderOuputViewsHandle?.Dispose(); #if STRIDE_GRAPHICS_API_DIRECT3D11 - videoHardwareDecoder?.Dispose(); - videoHardwareDecoderView?.Dispose(); + if (videoHardwareDecoder != null) + videoHardwareDecoder->Release(); + + if (videoHardwareDecoderView != null) + videoHardwareDecoderView->Release(); #endif DecoderOutputTexture?.Dispose(); @@ -293,6 +300,45 @@ public void Dispose() ffmpeg.avcodec_close(pAVCodecContextLocal); ffmpeg.avcodec_free_context(&pAVCodecContextLocal); } + + #region PinnedObject wrapper class + + private class PinnedObject + { + private GCHandle handle; + private GCHandle arrayHandle; + + public IntPtr Pointer + { + get + { + if (arrayHandle.IsAllocated) + return arrayHandle.AddrOfPinnedObject(); + + return handle.AddrOfPinnedObject(); + } + } + + public PinnedObject(T referenceObject, bool asArray = false) + { + handle = GCHandle.Alloc(referenceObject, GCHandleType.Pinned); + + if (asArray) + arrayHandle = GCHandle.Alloc(handle.AddrOfPinnedObject(), GCHandleType.Pinned); + } + + public void Dispose() + { + if (arrayHandle.IsAllocated) + handle.Free(); + + if (handle.IsAllocated) + handle.Free(); + } + } + + #endregion } } + #endif diff --git a/sources/engine/Stride.Video/FFmpeg/FFmpegExtensions.cs b/sources/engine/Stride.Video/FFmpeg/FFmpegExtensions.cs index 5140206e38..5d17ce4d14 100644 --- a/sources/engine/Stride.Video/FFmpeg/FFmpegExtensions.cs +++ b/sources/engine/Stride.Video/FFmpeg/FFmpegExtensions.cs @@ -1,7 +1,9 @@ #if STRIDE_GRAPHICS_API_DIRECT3D11 && STRIDE_VIDEO_FFMPEG + using System; +using System.Runtime.CompilerServices; using FFmpeg.AutoGen; -using SharpDX.Direct3D11; +using Silk.NET.Direct3D11; namespace Stride.Video.FFmpeg { @@ -9,19 +11,7 @@ public static class FFmpegExtensions { public static _GUID ToGUID(this Guid guid) { - var bytes = guid.ToByteArray(); - - var data4 = new byte_array8(); - for (int i = 0; i < 8; ++i) - data4[(uint)i] = bytes[8 + i]; - - return new _GUID - { - Data1 = (((ulong)bytes[0]) << 24) + (((ulong)bytes[1]) << 16) + (((ulong)bytes[2]) << 8) + bytes[3], - Data2 = (ushort)((bytes[4] << 8) + bytes[5]), - Data3 = (ushort)((bytes[6] << 8) + bytes[7]), - Data4 = data4, - }; + return Unsafe.As(ref guid); } public static D3D11_VIDEO_DECODER_CONFIG ToFFmpegDecoderConfig(this VideoDecoderConfig configuration) @@ -29,24 +19,25 @@ public static D3D11_VIDEO_DECODER_CONFIG ToFFmpegDecoderConfig(this VideoDecoder return new D3D11_VIDEO_DECODER_CONFIG { guidConfigBitstreamEncryption = configuration.GuidConfigBitstreamEncryption.ToGUID(), - Config4GroupedCoefs = (uint)configuration.Config4GroupedCoefs, - ConfigSpecificIDCT = (uint)configuration.ConfigSpecificIDCT, - ConfigHostInverseScan = (uint)configuration.ConfigHostInverseScan, - ConfigResidDiffAccelerator = (uint)configuration.ConfigResidDiffAccelerator, - ConfigIntraResidUnsigned = (uint)configuration.ConfigIntraResidUnsigned, - ConfigSpatialResidInterleaved = (uint)configuration.ConfigSpatialResidInterleaved, + Config4GroupedCoefs = configuration.Config4GroupedCoefs, + ConfigSpecificIDCT = configuration.ConfigSpecificIDCT, + ConfigHostInverseScan = configuration.ConfigHostInverseScan, + ConfigResidDiffAccelerator = configuration.ConfigResidDiffAccelerator, + ConfigIntraResidUnsigned = configuration.ConfigIntraResidUnsigned, + ConfigSpatialResidInterleaved = configuration.ConfigSpatialResidInterleaved, ConfigMinRenderTargetBuffCount = (ushort)configuration.ConfigMinRenderTargetBuffCount, - ConfigSpatialHost8or9Clipping = (uint)configuration.ConfigSpatialHost8or9Clipping, - ConfigSpatialResid8 = (uint)configuration.ConfigSpatialResid8, - ConfigResidDiffHost = (uint)configuration.ConfigResidDiffHost, - ConfigMBcontrolRasterOrder = (uint)configuration.ConfigMBcontrolRasterOrder, - ConfigBitstreamRaw = (uint)configuration.ConfigBitstreamRaw, + ConfigSpatialHost8or9Clipping = configuration.ConfigSpatialHost8or9Clipping, + ConfigSpatialResid8 = configuration.ConfigSpatialResid8, + ConfigResidDiffHost = configuration.ConfigResidDiffHost, + ConfigMBcontrolRasterOrder = configuration.ConfigMBcontrolRasterOrder, + ConfigBitstreamRaw = configuration.ConfigBitstreamRaw, guidConfigResidDiffEncryption = configuration.GuidConfigResidDiffEncryption.ToGUID(), guidConfigMBcontrolEncryption = configuration.GuidConfigMBcontrolEncryption.ToGUID(), - ConfigResid8Subtraction = (uint)configuration.ConfigResid8Subtraction, - ConfigDecoderSpecific = (ushort)configuration.ConfigDecoderSpecific + ConfigResid8Subtraction = configuration.ConfigResid8Subtraction, + ConfigDecoderSpecific = configuration.ConfigDecoderSpecific }; } } } + #endif diff --git a/sources/engine/Stride.Video/Stride.Video.csproj b/sources/engine/Stride.Video/Stride.Video.csproj index 104ba52b90..af0b1840c7 100644 --- a/sources/engine/Stride.Video/Stride.Video.csproj +++ b/sources/engine/Stride.Video/Stride.Video.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/sources/engine/Stride.Video/VideoInstance.Direct3D.cs b/sources/engine/Stride.Video/VideoInstance.Direct3D.cs index a576783e34..1949f71bef 100644 --- a/sources/engine/Stride.Video/VideoInstance.Direct3D.cs +++ b/sources/engine/Stride.Video/VideoInstance.Direct3D.cs @@ -2,24 +2,30 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #if STRIDE_GRAPHICS_API_DIRECT3D11 + using System; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using SharpDX.MediaFoundation; +using Silk.NET.Core.Native; +using Silk.NET.DXGI; +using Silk.NET.Direct3D11; using Stride.Core; -using Stride.Core.IO; using Stride.Core.Serialization; using Stride.Graphics; using Stride.Media; namespace Stride.Video { - partial class VideoInstance + unsafe partial class VideoInstance { private MediaEngine mediaEngine; + private Texture videoOutputTexture; - private SharpDX.DXGI.Surface videoOutputSurface; + private IDXGISurface* videoOutputSurface; + private SharpDX.ComObject videoOutputSurfaceSharpDX; // TODO: Remove when Silk includes Media Foundation + private Stream videoFileStream; private ByteStream videoDataStream; @@ -28,7 +34,7 @@ partial class VideoInstance private bool reachedEOF; - private static MediaEngineClassFactory mediaEngineFactory = new MediaEngineClassFactory(); + private static MediaEngineClassFactory mediaEngineFactory = new(); partial void ReleaseMediaImpl() { @@ -37,16 +43,21 @@ partial void ReleaseMediaImpl() mediaEngine = null; // Randomly crashes in sharpDX and the native code when disabling this - // The stream is problably accessed after disposal due to communication latency + // The stream is problably accessed after disposal due to communication latency // Unfortunately we don't receive any events after the call to Shutdown where we could dispose those //videoDataStream?.Dispose(); //videoDataStream = null; //videoFileStream?.Dispose(); //videoFileStream = null; - - videoOutputSurface?.Dispose(); - videoOutputSurface = null; + + if (videoOutputSurface != null) + videoOutputSurface->Release(); + + videoOutputSurface = null; + videoOutputSurfaceSharpDX = null; // TODO: Remove when Silk includes Media Foundation + // NOTE: Do not Dispose() it. It just wraps around the IDXGISurface + videoOutputTexture?.Dispose(); videoOutputTexture = null; } @@ -97,7 +108,7 @@ partial void UpdateImpl(ref TimeSpan elapsed) if (videoOutputSurface == null || PlayState == PlayState.Stopped) return; - //Transfer frame if a new one is available + // Transfer frame if a new one is available if (mediaEngine.OnVideoStreamTick(out var presentationTimeTicks)) { CurrentTime = TimeSpan.FromTicks(presentationTimeTicks); @@ -106,7 +117,7 @@ partial void UpdateImpl(ref TimeSpan elapsed) var endOfMedia = reachedEOF; if (!endOfMedia) { - //check the video loop and play range + // Check the video loop and play range if (PlayRange.IsValid() && CurrentTime > PlayRange.End) { endOfMedia = true; @@ -121,12 +132,12 @@ partial void UpdateImpl(ref TimeSpan elapsed) { if (IsLooping) { - //Restart the video at LoopRangeStart + // Restart the video at LoopRangeStart Seek(LoopRange.Start); } else { - //stop the video + // Stop the video Stop(); return; } @@ -136,10 +147,10 @@ partial void UpdateImpl(ref TimeSpan elapsed) { videoTexture.SetTargetContentToVideoStream(videoComponent.Target); - // Now update the video texture with data of the new video frame: + // Now update the video texture with data of the new video frame var graphicsContext = services.GetSafeServiceAs(); - mediaEngine.TransferVideoFrame(videoOutputSurface, null, new SharpDX.Mathematics.Interop.RawRectangle(0, 0, videoWidth, videoHeight), null); + mediaEngine.TransferVideoFrame(videoOutputSurfaceSharpDX, srcRef: null, new SharpDX.Mathematics.Interop.RawRectangle(0, 0, videoWidth, videoHeight), borderClrRef: null); videoTexture.CopyDecoderOutputToTopLevelMipmap(graphicsContext, videoOutputTexture); videoTexture.GenerateMipMaps(graphicsContext); @@ -166,8 +177,8 @@ partial void InitializeMediaImpl(string url, long startPosition, long length, re //Assign our dxgi manager, and set format to bgra var attr = new MediaEngineAttributes { - VideoOutputFormat = (int)SharpDX.DXGI.Format.B8G8R8A8_UNorm, - DxgiManager = videoSystem.DxgiDeviceManager, + VideoOutputFormat = (int) Format.FormatB8G8R8A8Unorm, + DxgiManager = videoSystem.DxgiDeviceManager }; mediaEngine = new MediaEngine(mediaEngineFactory, attr); @@ -175,9 +186,9 @@ partial void InitializeMediaImpl(string url, long startPosition, long length, re // Register our PlayBackEvent mediaEngine.PlaybackEvent += OnPlaybackCallback; - // set the video source - var mediaEngineEx = mediaEngine.QueryInterface(); - + // set the video source + using var mediaEngineEx = mediaEngine.QueryInterface(); + videoFileStream = new VirtualFileStream(File.OpenRead(url), startPosition, startPosition + length); videoDataStream = new ByteStream(videoFileStream); @@ -194,7 +205,7 @@ partial void InitializeMediaImpl(string url, long startPosition, long length, re } } - private void CompleteMediaInitialization() + private unsafe void CompleteMediaInitialization() { //Get our video size mediaEngine.GetNativeVideoSize(out videoWidth, out videoHeight); @@ -202,7 +213,14 @@ private void CompleteMediaInitialization() //Get DXGI surface to be used by our media engine videoOutputTexture = Texture.New2D(GraphicsDevice, videoWidth, videoHeight, 1, PixelFormat.B8G8R8A8_UNorm, TextureFlags.ShaderResource | TextureFlags.RenderTarget); - videoOutputSurface = videoOutputTexture.NativeResource.QueryInterface(); + + HResult result = videoOutputTexture.NativeResource.QueryInterface(out ComPtr outputSurface); + + if (result.IsFailure) + result.Throw(); + + videoOutputSurface = outputSurface; + videoOutputSurfaceSharpDX = new SharpDX.ComObject((IntPtr) videoOutputSurface); AllocateVideoTexture(videoWidth, videoHeight); @@ -276,4 +294,5 @@ private void OnPlaybackCallback(MediaEngineEvent playEvent, long param1, int par } } } + #endif diff --git a/sources/engine/Stride.Video/VideoSystem.Direct3D.cs b/sources/engine/Stride.Video/VideoSystem.Direct3D.cs index 600fe0c479..6e1d156b4e 100644 --- a/sources/engine/Stride.Video/VideoSystem.Direct3D.cs +++ b/sources/engine/Stride.Video/VideoSystem.Direct3D.cs @@ -3,28 +3,38 @@ #if STRIDE_GRAPHICS_API_DIRECT3D11 -using SharpDX.Direct3D; +using System; using SharpDX.MediaFoundation; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; using Stride.Games; namespace Stride.Video { public partial class VideoSystem { - public DXGIDeviceManager DxgiDeviceManager; + public DXGIDeviceManager DxgiDeviceManager; // TODO: Remove when Silk includes Media Foundation - public override void Initialize() + public override unsafe void Initialize() { base.Initialize(); var graphicsDevice = Services.GetService().GraphicsDevice; + var d3d11Device = graphicsDevice.NativeDevice; + var d3d11DeviceSharpDX = new SharpDX.ComObject((IntPtr) d3d11Device.Handle); + DxgiDeviceManager = new DXGIDeviceManager(); - DxgiDeviceManager.ResetDevice(graphicsDevice.NativeDevice); + DxgiDeviceManager.ResetDevice(d3d11DeviceSharpDX); + + // Add multi-thread protection on the device + HResult result = graphicsDevice.NativeDevice.QueryInterface(out ComPtr multiThread); + + if (result.IsFailure) + result.Throw(); - //Add multi thread protection on device - var mt = graphicsDevice.NativeDevice.QueryInterface(); - mt.SetMultithreadProtected(true); + multiThread.SetMultithreadProtected(true); + multiThread.Dispose(); MediaManager.Startup(); } diff --git a/sources/engine/Stride.Video/VideoTexture.cs b/sources/engine/Stride.Video/VideoTexture.cs index a1d4d014b6..c46c42e89c 100644 --- a/sources/engine/Stride.Video/VideoTexture.cs +++ b/sources/engine/Stride.Video/VideoTexture.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; using Stride.Core; @@ -56,7 +59,7 @@ public VideoTexture(GraphicsDevice graphicsDevice, IServiceRegistry serviceRegis // Create a mip mapped texture with the same size as our video // Only generate up to "MaxMipMapCount" number of mip maps - var mipMapCount = Math.Min(Texture.CountMips(Math.Max(width, height)), maxMipMapCount + 1); + var mipMapCount = Math.Min(Texture.CountMipLevels(Math.Max(width, height)), maxMipMapCount + 1); var textureDescription = TextureDescription.New2D(width, height, mipMapCount, PixelFormat.R8G8B8A8_UNorm_SRgb, TextureFlags.ShaderResource | TextureFlags.RenderTarget, 1, GraphicsResourceUsage.Dynamic); renderTargetTexture = Texture.New(graphicsDevice, textureDescription, null); // Supply no data. Create an empty texture. } @@ -81,7 +84,7 @@ public void SetTargetContentToVideoStream(Texture newTargetTexture) if (originalTargetTexture == newTargetTexture) // the target content is already set to the video stream return; // -> nothing to do - if (originalTargetTexture != null) // the target Texture changed, we need to revert the previous one + if (originalTargetTexture != null) // the target Texture changed, we need to revert the previous one SetTargetContentToOriginalPlaceholder(); if (newTargetTexture == null) @@ -108,8 +111,8 @@ public unsafe void UpdateTopLevelMipmapFromData(GraphicsContext context, VideoIm { // "videoComponent.Target" contains the mip mapped video texture at this point. // We now copy the new video frame directly into the video texture's first mip level: - var dataPointer = new Span((void*)image.Buffer, image.BufferSize); - renderTargetMipMaps[0].SetData(context.CommandList, dataPointer, 0, 0); + var dataPointer = new ReadOnlySpan((void*)image.Buffer, image.BufferSize); + renderTargetMipMaps[0].SetData(context.CommandList, dataPointer, arrayIndex: 0, mipLevel: 0); } public void CopyDecoderOutputToTopLevelMipmap(GraphicsContext context, Texture decoderOutputTexture) @@ -199,7 +202,7 @@ private void AllocateTextureViewsForMipMaps(Texture parentTexture) // Create a texture view for every mip map of the texture that we use for displaying the video in the scene: DeallocateTextureViewsForMipMaps(); - for (int i = 0; i < parentTexture.MipLevels; ++i) + for (int i = 0; i < parentTexture.MipLevelCount; ++i) { var renderTargetMipMapTextureViewDescription = new TextureViewDescription { diff --git a/sources/engine/Stride.VirtualReality/OculusOVR/OculusOverlay.cs b/sources/engine/Stride.VirtualReality/OculusOVR/OculusOverlay.cs index 493b1a6a89..39b01e285a 100644 --- a/sources/engine/Stride.VirtualReality/OculusOVR/OculusOverlay.cs +++ b/sources/engine/Stride.VirtualReality/OculusOVR/OculusOverlay.cs @@ -3,7 +3,7 @@ #if STRIDE_GRAPHICS_API_DIRECT3D11 using System; -using SharpDX.Direct3D11; +using Silk.NET.Direct3D11; using Stride.Graphics; using CommandList = Stride.Graphics.CommandList; @@ -15,12 +15,11 @@ internal class OculusOverlay : VROverlay, IDisposable internal IntPtr OverlayPtr; private readonly Texture[] textures; - public OculusOverlay(IntPtr ovrSession, GraphicsDevice device, int width, int height, int mipLevels, int sampleCount) + public unsafe OculusOverlay(IntPtr ovrSession, GraphicsDevice device, int width, int height, int mipLevels, int sampleCount) { - int textureCount; this.ovrSession = ovrSession; - OverlayPtr = OculusOvr.CreateQuadLayerTexturesDx(ovrSession, device.NativeDevice.NativePointer, out textureCount, width, height, mipLevels, sampleCount); + OverlayPtr = OculusOvr.CreateQuadLayerTexturesDx(ovrSession, (nint) device.NativeDevice.Handle, out var textureCount, width, height, mipLevels, sampleCount); if (OverlayPtr == IntPtr.Zero) { throw new Exception(OculusOvr.GetError()); @@ -36,7 +35,7 @@ public OculusOverlay(IntPtr ovrSession, GraphicsDevice device, int width, int he } textures[i] = new Texture(device); - textures[i].InitializeFromImpl(new Texture2D(ptr), false); + textures[i].InitializeFromImpl((ID3D11Texture2D*) ptr, treatAsSrgb: false); } } diff --git a/sources/engine/Stride.VirtualReality/OculusOVR/OculusOvrHmd.cs b/sources/engine/Stride.VirtualReality/OculusOVR/OculusOvrHmd.cs index a9f12c4518..7c4853f8f6 100644 --- a/sources/engine/Stride.VirtualReality/OculusOVR/OculusOvrHmd.cs +++ b/sources/engine/Stride.VirtualReality/OculusOVR/OculusOvrHmd.cs @@ -5,7 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; -using SharpDX.Direct3D11; +using Silk.NET.Core.Native; +using Silk.NET.Direct3D11; using Stride.Core.Mathematics; using Stride.Games; using Stride.Graphics; @@ -17,8 +18,8 @@ internal class OculusOvrHmd : VRDevice { private static bool initDone; - //private static readonly Guid dx12ResourceGuid = new Guid("696442be-a72e-4059-bc79-5b5c98040fad"); - internal static readonly Guid Dx11Texture2DGuid = new Guid("6f15aaf2-d208-4e89-9ab4-489535d34f9c"); + //private static readonly Guid dx12ResourceGuid = SilkMarshal.GuidOf(); + internal static readonly Guid Dx11Texture2DGuid = SilkMarshal.GuidOf(); private IntPtr ovrSession; private Texture[] textures; @@ -48,15 +49,14 @@ public override void Dispose() } } - public override void Enable(GraphicsDevice device, GraphicsDeviceManager graphicsDeviceManager, bool requireMirror, int mirrorWidth, int mirrorHeight) + public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager graphicsDeviceManager, bool requireMirror, int mirrorWidth, int mirrorHeight) { graphicsDevice = device; long adapterId; ovrSession = OculusOvr.CreateSessionDx(out adapterId); //Game.GraphicsDeviceManager.RequiredAdapterUid = adapterId.ToString(); //should not be needed - int texturesCount; - if (!OculusOvr.CreateTexturesDx(ovrSession, device.NativeDevice.NativePointer, out texturesCount, RenderFrameScaling, requireMirror ? mirrorWidth : 0, requireMirror ? mirrorHeight : 0)) + if (!OculusOvr.CreateTexturesDx(ovrSession, (nint) device.NativeDevice.Handle, out var texturesCount, RenderFrameScaling, requireMirror ? mirrorWidth : 0, requireMirror ? mirrorHeight : 0)) { throw new Exception(OculusOvr.GetError()); } @@ -65,7 +65,7 @@ public override void Enable(GraphicsDevice device, GraphicsDeviceManager graphic { var mirrorTex = OculusOvr.GetMirrorTexture(ovrSession, Dx11Texture2DGuid); MirrorTexture = new Texture(device); - MirrorTexture.InitializeFromImpl(new Texture2D(mirrorTex), false); + MirrorTexture.InitializeFromImpl((ID3D11Texture2D*) mirrorTex, false); } textures = new Texture[texturesCount]; @@ -78,7 +78,7 @@ public override void Enable(GraphicsDevice device, GraphicsDeviceManager graphic } textures[i] = new Texture(device); - textures[i].InitializeFromImpl(new Texture2D(ptr), false); + textures[i].InitializeFromImpl((ID3D11Texture2D*) ptr, treatAsSrgb: false); } ActualRenderFrameSize = new Size2(textures[0].Width, textures[0].Height); diff --git a/sources/engine/Stride.VirtualReality/OpenVR/OpenVR.cs b/sources/engine/Stride.VirtualReality/OpenVR/OpenVR.cs index 9da9de784e..58fb5e2582 100644 --- a/sources/engine/Stride.VirtualReality/OpenVR/OpenVR.cs +++ b/sources/engine/Stride.VirtualReality/OpenVR/OpenVR.cs @@ -3,18 +3,18 @@ #if STRIDE_GRAPHICS_API_DIRECT3D11 using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Text; -using SharpDX.Direct3D11; +using Silk.NET.Direct3D11; using Valve.VR; using Stride.Core; using Stride.Core.Mathematics; using Stride.Graphics; -using System.Runtime.CompilerServices; -using System.Diagnostics; namespace Stride.VirtualReality { - internal static class OpenVR + internal static unsafe class OpenVR { public class Controller { @@ -233,7 +233,7 @@ public static bool Submit(int eyeIndex, Texture texture, ref RectangleF viewport { eType = ETextureType.DirectX, eColorSpace = EColorSpace.Auto, - handle = texture.NativeResource.NativePointer, + handle = (nint) texture.NativeResource.Handle }; var bounds = new VRTextureBounds_t { @@ -411,12 +411,12 @@ public static void HideMirror() public static Texture GetMirrorTexture(GraphicsDevice device, int eyeIndex) { - var nativeDevice = device.NativeDevice.NativePointer; + var nativeDevice = device.NativeDevice; var eyeTexSrv = IntPtr.Zero; - Valve.VR.OpenVR.Compositor.GetMirrorTextureD3D11(eyeIndex == 0 ? EVREye.Eye_Left : EVREye.Eye_Right, nativeDevice, ref eyeTexSrv); + Valve.VR.OpenVR.Compositor.GetMirrorTextureD3D11(eyeIndex == 0 ? EVREye.Eye_Left : EVREye.Eye_Right, (nint) nativeDevice.Handle, ref eyeTexSrv); var tex = new Texture(device); - var srv = new ShaderResourceView(eyeTexSrv); + var srv = (ID3D11ShaderResourceView*) (void*) eyeTexSrv; tex.InitializeFromImpl(srv); @@ -448,7 +448,7 @@ public static bool SubmitOverlay(ulong overlayId, Texture texture) { eType = ETextureType.DirectX, eColorSpace = EColorSpace.Auto, - handle = texture.NativeResource.NativePointer, + handle = (nint) texture.NativeResource.Handle }; return Valve.VR.OpenVR.Overlay.SetOverlayTexture(overlayId, ref tex) == EVROverlayError.None; diff --git a/sources/engine/Stride.VirtualReality/OpenXR/OpenXRHmd.cs b/sources/engine/Stride.VirtualReality/OpenXR/OpenXRHmd.cs index 078cd06148..396a895bde 100644 --- a/sources/engine/Stride.VirtualReality/OpenXR/OpenXRHmd.cs +++ b/sources/engine/Stride.VirtualReality/OpenXR/OpenXRHmd.cs @@ -2,23 +2,25 @@ using System; using System.Collections.Generic; -using Stride.Core.Mathematics; -using Stride.Games; -using Stride.Graphics; -using Silk.NET.OpenXR; +using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Silk.NET.Core; -using System.Diagnostics; using Silk.NET.Core.Native; -using System.Runtime.CompilerServices; +using Silk.NET.Direct3D11; +using Silk.NET.OpenXR; using Stride.Core; +using Stride.Core.Mathematics; +using Stride.Core.Diagnostics; +using Stride.Games; +using Stride.Graphics; namespace Stride.VirtualReality { - public class OpenXRHmd : VRDevice + public unsafe class OpenXRHmd : VRDevice { // Public static variable to add extensions to the initialization of the openXR session - public static List extensions = new List(); + public static List extensions = []; /// /// Creates a VR device using OpenXR. @@ -52,14 +54,14 @@ public class OpenXRHmd : VRDevice public ReferenceSpaceType play_space_type = ReferenceSpaceType.Local; //XR_REFERENCE_SPACE_TYPE_LOCAL; #if STRIDE_GRAPHICS_API_DIRECT3D11 public SwapchainImageD3D11KHR[]? images; - public SharpDX.Direct3D11.RenderTargetView[]? render_targets; + public ID3D11RenderTargetView*[]? render_targets; #endif public ActionSet globalActionSet; public InteractionProfileState handProfileState; private ulong leftHandPath; // Misc - private static Stride.Core.Diagnostics.Logger Logger = Stride.Core.Diagnostics.GlobalLogger.GetLogger("OpenXRHmd"); + private static Logger Logger = GlobalLogger.GetLogger("OpenXRHmd"); private bool runFramecycle = false; private bool sessionRunning = false; private SessionState state = SessionState.Unknown; @@ -77,14 +79,13 @@ public class OpenXRHmd : VRDevice private unsafe delegate Result pfnGetD3D11GraphicsRequirementsKHR(Instance instance, ulong sys_id, GraphicsRequirementsD3D11KHR* req); #endif - private bool begunFrame, swapImageCollected; private uint swapchainPointer; // array of view_count containers for submitting swapchains with rendered VR frames private CompositionLayerProjectionView[]? projection_views; private View[]? views; - private readonly unsafe List compositionLayers = new(); + private readonly List compositionLayers = []; public override Size2 ActualRenderFrameSize { @@ -130,7 +131,11 @@ public override bool CanInitialize // TODO (not implemented) private Texture? mirrorTexture; - public override Texture? MirrorTexture { get => mirrorTexture; protected set => mirrorTexture = value; } + public override Texture? MirrorTexture + { + get => mirrorTexture; + protected set => mirrorTexture = value; + } public override TrackedItem[]? TrackedItems => null; @@ -159,13 +164,15 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager SystemProperties system_props = new SystemProperties() { Type = StructureType.SystemProperties, - Next = null, + Next = null }; // Changing the form_factor may require changing the view_type too. - ViewConfigurationType view_type = ViewConfigurationType.PrimaryStereo; + var view_type = ViewConfigurationType.PrimaryStereo; uint view_config_count = 0; - Xr.EnumerateViewConfiguration(Instance, SystemId, 0, ref view_config_count, null).CheckResult(); + + Xr.EnumerateViewConfiguration(Instance, SystemId, 0, ref view_config_count, viewConfigurationTypes: null).CheckResult(); + var viewconfigs = new ViewConfigurationType[view_config_count]; fixed (ViewConfigurationType* viewconfigspnt = &viewconfigs[0]) Xr.EnumerateViewConfiguration(Instance, SystemId, view_config_count, ref view_config_count, viewconfigspnt).CheckResult(); @@ -197,20 +204,16 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager renderSize.Height = (int)Math.Round(viewconfig_views[0].RecommendedImageRectHeight * RenderFrameScaling); SessionCreateInfo session_create_info; + #if STRIDE_GRAPHICS_API_DIRECT3D11 - Logger.Debug( - "Initializing DX11 graphics device: " - ); - GraphicsRequirementsD3D11KHR dx11 = new GraphicsRequirementsD3D11KHR() - { - Type = StructureType.GraphicsRequirementsD3D11Khr - }; + Logger.Debug("Initializing DX11 graphics device: "); + var dx11 = new GraphicsRequirementsD3D11KHR { Type = StructureType.GraphicsRequirementsD3D11Khr }; - Silk.NET.Core.PfnVoidFunction xrGetD3D11GraphicsRequirementsKHR = new Silk.NET.Core.PfnVoidFunction(); + var xrGetD3D11GraphicsRequirementsKHR = new PfnVoidFunction(); Xr.GetInstanceProcAddr(Instance, "xrGetD3D11GraphicsRequirementsKHR", ref xrGetD3D11GraphicsRequirementsKHR).CheckResult(); // this function pointer was loaded with xrGetInstanceProcAddr Delegate dx11_req = Marshal.GetDelegateForFunctionPointer((IntPtr)xrGetD3D11GraphicsRequirementsKHR.Handle, typeof(pfnGetD3D11GraphicsRequirementsKHR)); - dx11_req.DynamicInvoke(Instance, SystemId, new System.IntPtr(&dx11)); + dx11_req.DynamicInvoke(Instance, SystemId, new IntPtr(&dx11)); Logger.Debug("Initializing dx11 graphics device"); Logger.Debug( "DX11 device luid: " + dx11.AdapterLuid @@ -220,8 +223,8 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager var graphics_binding_dx11 = new GraphicsBindingD3D11KHR() { Type = StructureType.GraphicsBindingD3D11Khr, - Device = baseDevice.NativeDevice.NativePointer.ToPointer(), - Next = null, + Device = baseDevice.NativeDevice, + Next = null }; session_create_info = new SessionCreateInfo() { @@ -239,13 +242,13 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager // --- Create swapchain for main VR rendering Swapchain swapchain = new Swapchain(); - SwapchainCreateInfo swapchain_create_info = new SwapchainCreateInfo() + SwapchainCreateInfo swapchain_create_info = new() { Type = StructureType.SwapchainCreateInfo, Next = null, UsageFlags = SwapchainUsageFlags.TransferDstBit | - SwapchainUsageFlags.SampledBit | - SwapchainUsageFlags.ColorAttachmentBit, + SwapchainUsageFlags.SampledBit | + SwapchainUsageFlags.ColorAttachmentBit, CreateFlags = 0, #if STRIDE_GRAPHICS_API_DIRECT3D11 Format = (long)PixelFormat.R8G8B8A8_UNorm_SRgb, @@ -255,7 +258,7 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager Height = (uint)renderSize.Height, FaceCount = 1, ArraySize = 1, - MipCount = 1, + MipCount = 1 }; Xr.CreateSwapchain(session, &swapchain_create_info, &swapchain).CheckResult(); @@ -276,20 +279,29 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager Xr.EnumerateSwapchainImages(swapchain, img_count, ref img_count, (SwapchainImageBaseHeader*)sibhp).CheckResult(); } - render_targets = new SharpDX.Direct3D11.RenderTargetView[img_count]; + render_targets = new ID3D11RenderTargetView*[img_count]; for (var i = 0; i < img_count; ++i) { - var texture = new SharpDX.Direct3D11.Texture2D((IntPtr)images[i].Texture); - var color_desc = texture.Description; - Logger.Debug("Color texture description: " + color_desc.Width.ToString() + "x" + color_desc.Height.ToString() + " format: " + color_desc.Format.ToString()); + var texture = (ID3D11Texture2D*) images[i].Texture; + + Texture2DDesc color_desc; + texture->GetDesc(&color_desc); + + Logger.Debug($"Color texture description: {color_desc.Width}x{color_desc.Height} format: {color_desc.Format}"); - var target_desc = new SharpDX.Direct3D11.RenderTargetViewDescription() + var target_desc = new RenderTargetViewDesc { - Dimension = SharpDX.Direct3D11.RenderTargetViewDimension.Texture2D, - Format = SharpDX.DXGI.Format.R8G8B8A8_UNorm_SRgb, + ViewDimension = RtvDimension.Texture2D, + Format = Silk.NET.DXGI.Format.FormatR8G8B8A8UnormSrgb }; - var render_target = new SharpDX.Direct3D11.RenderTargetView(baseDevice.NativeDevice, texture, target_desc); - render_targets[i] = render_target; + + ID3D11RenderTargetView* rtv; + HResult result = baseDevice.NativeDevice.CreateRenderTargetView((ID3D11Resource*) texture, target_desc, &rtv); + + if (result.IsFailure) + result.Throw(); + + render_targets[i] = rtv; } #endif @@ -320,7 +332,7 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager Type = StructureType.ReferenceSpaceCreateInfo, Next = null, ReferenceSpaceType = play_space_type, - PoseInReferenceSpace = new Posef(new Quaternionf(0f, 0f, 0f, 1f), new Vector3f(0f, 0f, 0f)), + PoseInReferenceSpace = new Posef(new Quaternionf(0f, 0f, 0f, 1f), new Vector3f(0f, 0f, 0f)) }; var play_space = new Space(); Xr.CreateReferenceSpace(session, &play_space_create_info, &play_space).CheckResult(); @@ -329,7 +341,7 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager ActionSetCreateInfo gameplay_actionset_info = new ActionSetCreateInfo() { Type = StructureType.ActionSetCreateInfo, - Next = null, + Next = null }; Span asname = new Span(gameplay_actionset_info.ActionSetName, 16); @@ -360,7 +372,7 @@ public override unsafe void Enable(GraphicsDevice device, GraphicsDeviceManager handProfileState = new InteractionProfileState() { Type = StructureType.InteractionProfileState, - Next = null, + Next = null }; Xr.StringToPath(Instance, "/user/hand/left", ref leftHandPath); } @@ -393,7 +405,7 @@ private void EndNullFrame() EnvironmentBlendMode = EnvironmentBlendMode.Opaque, LayerCount = 0, Layers = null, - Next = null, + Next = null }; Xr.EndFrame(globalSession, frame_end_info).CheckResult(); } @@ -410,21 +422,21 @@ public override unsafe void Draw(GameTime gameTime) // --- Wait for our turn to do head-pose dependent computation and render a frame FrameWaitInfo frame_wait_info = new FrameWaitInfo() { - Type = StructureType.FrameWaitInfo, + Type = StructureType.FrameWaitInfo }; //Logger.Warning("WaitFrame"); globalFrameState = new FrameState() { Type = StructureType.FrameState, - Next = null, + Next = null }; Xr.WaitFrame(globalSession, in frame_wait_info, ref globalFrameState).CheckResult(); FrameBeginInfo frame_begin_info = new FrameBeginInfo() { Type = StructureType.FrameBeginInfo, - Next = null, + Next = null }; //Logger.Warning("BeginFrame"); @@ -455,13 +467,13 @@ public void UpdateViews() ViewConfigurationType = ViewConfigurationType.PrimaryStereo, DisplayTime = globalFrameState.PredictedDisplayTime, Space = globalPlaySpace, - Next = null, + Next = null }; ViewState view_state = new ViewState() { Type = StructureType.ViewState, - Next = null, + Next = null }; unsafe @@ -489,22 +501,25 @@ public unsafe uint GetSwapchainImage() // Logger.Warning("AcquireSwapchainImage"); // Get the swapchain image var swapchainIndex = 0u; - var acquireInfo = new SwapchainImageAcquireInfo() { + var acquireInfo = new SwapchainImageAcquireInfo() + { Type = StructureType.SwapchainImageAcquireInfo, - Next = null, + Next = null }; Xr.AcquireSwapchainImage(globalSwapchain, in acquireInfo, ref swapchainIndex).CheckResult(); // Logger.Warning("WaitSwapchainImage"); - var waitInfo = new SwapchainImageWaitInfo(timeout: long.MaxValue) { + var waitInfo = new SwapchainImageWaitInfo(timeout: long.MaxValue) + { Type = StructureType.SwapchainImageWaitInfo, - Next = null, + Next = null }; swapImageCollected = (Xr.WaitSwapchainImage(globalSwapchain, in waitInfo) == Result.Success); if(!swapImageCollected) { // Logger.Warning("WaitSwapchainImage failed"); - var releaseInfo = new SwapchainImageReleaseInfo() { + var releaseInfo = new SwapchainImageReleaseInfo() + { Type = StructureType.SwapchainImageReleaseInfo, Next = null, }; @@ -539,9 +554,16 @@ public override void Commit(CommandList commandList, Texture renderFrame) if (swapImageCollected) { #if STRIDE_GRAPHICS_API_DIRECT3D11 - Debug.Assert(commandList.NativeDeviceContext == baseDevice.NativeDeviceContext); + Debug.Assert(commandList.NativeDeviceContext.EqualsComPtr(baseDevice.NativeDeviceContext)); + // Logger.Warning("Blit render target"); - baseDevice.NativeDeviceContext.CopyResource(renderFrame.NativeRenderTargetView.Resource, render_targets[swapchainPointer].Resource); + ID3D11Resource* renderFrameResource; + renderFrame.NativeRenderTargetView.GetResource(&renderFrameResource); + + ID3D11Resource* swapChainRenderTargetResource; + render_targets[swapchainPointer]->GetResource(&swapChainRenderTargetResource); + + baseDevice.NativeDeviceContext.CopyResource(renderFrameResource, swapChainRenderTargetResource); #endif // Release the swapchain image @@ -632,7 +654,7 @@ public override void SetTrackingSpace(TrackingSpace space) Type = StructureType.ReferenceSpaceCreateInfo, Next = null, ReferenceSpaceType = play_space_type, - PoseInReferenceSpace = new Posef(new Quaternionf(0f, 0f, 0f, 1f), new Vector3f(0f, 0f, 0f)), + PoseInReferenceSpace = new Posef(new Quaternionf(0f, 0f, 0f, 1f), new Vector3f(0f, 0f, 0f)) }; unsafe { @@ -643,9 +665,9 @@ public override void SetTrackingSpace(TrackingSpace space) public override void Recenter() { - // TODO: OpenXR doens´t have a renceter api. Recenter in this case needs to be done from the + // TODO: OpenXR doens´t have a renceter api. Recenter in this case needs to be done from the // engine by moving the world or adding an offset? - // + // // The VR api could have a new property CanRencenter or DoesRecenter that returns true or false // if the specific API can do a renceter or the engine needs to take care of that } @@ -862,7 +884,7 @@ public override unsafe void Update(GameTime gameTime) Type = StructureType.ActionsSyncInfo, Next = null, CountActiveActionSets = 1, - ActiveActionSets = &active_actionsets, + ActiveActionSets = &active_actionsets }; Xr.SyncAction(globalSession, &actions_sync_info); @@ -878,7 +900,7 @@ public override void Dispose() { foreach (var render_target in render_targets) { - render_target.Dispose(); + render_target->Release(); } } #endif diff --git a/sources/engine/Stride.Voxels/Voxels/GraphicsCompositor/VoxelPipelineProcessor.cs b/sources/engine/Stride.Voxels/Voxels/GraphicsCompositor/VoxelPipelineProcessor.cs index 284d9c6f13..2823196b48 100644 --- a/sources/engine/Stride.Voxels/Voxels/GraphicsCompositor/VoxelPipelineProcessor.cs +++ b/sources/engine/Stride.Voxels/Voxels/GraphicsCompositor/VoxelPipelineProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Sean Boettger +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Sean Boettger // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System.Collections.Generic; using System.ComponentModel; @@ -27,7 +27,7 @@ public override void Process(RenderNodeReference renderNodeReference, ref Render pipelineState.DepthStencilState.StencilEnable = false; pipelineState.DepthStencilState.StencilWriteMask = 0; pipelineState.DepthStencilState.StencilMask = 0; - pipelineState.BlendState.RenderTarget0.BlendEnable = false; + pipelineState.BlendState.RenderTargets[0].BlendEnable = false; pipelineState.BlendState.IndependentBlendEnable = false; } } diff --git a/sources/engine/Stride/Data/ConfigPlatforms.cs b/sources/engine/Stride/Data/ConfigPlatforms.cs index 744bfef5d6..107650422d 100644 --- a/sources/engine/Stride/Data/ConfigPlatforms.cs +++ b/sources/engine/Stride/Data/ConfigPlatforms.cs @@ -1,20 +1,55 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + #pragma warning disable SA1300 // Element should begin with upper-case letter + using System; + using Stride.Core; -namespace Stride.Data +namespace Stride.Data; + +/// +/// Represents a set of platform configurations. +/// +/// +/// This enumeration allows specifying one or more platforms by combining values using bitwise +/// operations. For example, you can use | +/// to indicate both Windows and Android platforms. +/// +/// +[Flags] +public enum ConfigPlatforms { - [Flags] - public enum ConfigPlatforms - { - None = 0, - Windows = 1 << PlatformType.Windows, - UWP = 1 << PlatformType.UWP, - iOS = 1 << PlatformType.iOS, - Android = 1 << PlatformType.Android, - Linux = 1 << PlatformType.Linux, - macOS = 1 << PlatformType.macOS, - } + None = 0, + + /// + /// The Windows operating system for desktop applications. + /// + Windows = 1 << PlatformType.Windows, + + /// + /// The Universal Windows Platform (UWP) for applications that run on Windows 10 and later devices, and XBox gaming consoles. + /// + UWP = 1 << PlatformType.UWP, + + /// + /// The iOS operating system for Apple mobile devices such as iPhone and iPad. + /// + iOS = 1 << PlatformType.iOS, + + /// + /// The Android operating system for mobile devices and tablets. + /// + Android = 1 << PlatformType.Android, + + /// + /// The Linux operating system, typically used for servers and desktops. + /// + Linux = 1 << PlatformType.Linux, + + /// + /// The macOS operating system for Apple desktop and laptop computers. + /// + macOS = 1 << PlatformType.macOS } diff --git a/sources/engine/Stride/Data/PlatformConfigurations.cs b/sources/engine/Stride/Data/PlatformConfigurations.cs index f609270950..0bfa551ce8 100644 --- a/sources/engine/Stride/Data/PlatformConfigurations.cs +++ b/sources/engine/Stride/Data/PlatformConfigurations.cs @@ -1,5 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Collections.Generic; using System.Linq; @@ -16,10 +17,10 @@ public class PlatformConfigurations public static string DeviceModel = string.Empty; [DataMember] - public List Configurations = new List(); + public List Configurations = []; [DataMember] - public List PlatformFilters = new List(); + public List PlatformFilters = []; public T Get() where T : Configuration, new() { diff --git a/sources/engine/Stride/Graphics/ColorSpace.cs b/sources/engine/Stride/Graphics/ColorSpace.cs index 444f035006..dfd7914ef0 100644 --- a/sources/engine/Stride/Graphics/ColorSpace.cs +++ b/sources/engine/Stride/Graphics/ColorSpace.cs @@ -3,195 +3,36 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines the color space used for Textures, Materials, lighting calculations, etc. +/// +[DataContract("ColorSpace")] +public enum ColorSpace { /// - /// The colorspace used for textures, materials, lighting... + /// Use a linear color space, i.e. treat color values as linear values, without + /// applying any gamma correction. /// - [DataContract("ColorSpace")] - public enum ColorSpace - { - /// - /// Use a linear colorspace. - /// - Linear, - - /// - /// Use a gamma colorspace. - /// - Gamma, - } + /// + /// The linear color space is useful when the output of the rendering (or the input + /// Textures) represent values that can be transformed in a post-processing step + /// (such as tone-mapping, color-correction, etc.) or if they represent non-final + /// color values (like intermediate buffers) or non-color values (like heights, + /// roughness, etc.) + /// + Linear, /// - /// Specifies color space types. + /// Use a gamma color space. /// /// - /// This enum is used within DXGI in the CheckColorSpaceSupport, SetColorSpace1 and - /// CheckOverlayColorSpaceSupport methods. It is also referenced in D3D11 video methods - /// such as ID3D11VideoContext1::VideoProcessorSetOutputColorSpace1, and D2D methods - /// such as ID2D1DeviceContext2::CreateImageSourceFromDxgi. The following color parameters - /// are defined: + /// A gamma color space is a color space in which colors are applied a gamma curve + /// (like sRGB) so they are perceptually linear. This is useful when the output of + /// the rendering (or the input Textures) represent final color values that will + /// be presented to a non-HDR screen, or if they represent color values that won't + /// be transformed in a post-processing step. /// - public enum ColorSpaceType - { - /// - /// ColorspaceRGB Range0-255 Gamma2.2 SitingImage PrimariesBT.709. Use with backbuffer of 8 bit colors, such as PixelFormat.B8G8R8A8_UNorm. - /// This is the standard definition for sRGB. Note that this is often implemented - /// with a linear segment, but in that case, the exponent is corrected to stay aligned - /// with a gamma 2.2 curve. This is usually used with 8-bit and 10-bit color channels. - /// - RgbFullG22NoneP709 = 0, - - /// - /// ColorspaceRGB Range0-255 Gamma1.0 SitingImage PrimariesBT.709. Use with backbuffer of 16 bit colors, PixelFormat.R16G16B16A16_Float. - /// This is the standard definition for scRGB, and is usually used with 16-bit integer, - /// 16-bit floating point, and 32-bit floating point channels. - /// - RgbFullG10NoneP709 = 1, - - /// - /// ColorspaceRGB Range16-235 Gamma2.2 SitingImage PrimariesBT.709. - /// This is the standard definition for ITU-R Recommendation BT.709. Note that - /// due to the inclusion of a linear segment, the transfer curve looks similar to - /// a pure exponential gamma of 1.9. This is usually used with 8-bit and 10-bit color - /// channels. - /// - RgbStudioG22NoneP709 = 2, - - /// - /// ColorspaceRGB Range16-235 Gamma2.2 SitingImage PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - RgbStudioG22NoneP2020 = 3, - - /// - /// Reserved. - /// - Reserved = 4, - - /// - /// ColorspaceYCbCr Range0-255 Gamma2.2 SitingImage PrimariesBT.709 TransferBT.601. - /// This definition is commonly used for JPG, and is usually used with 8, 10, 12, - /// or 16-bit color channels. - /// - YcbcrFullG22NoneP709X601 = 5, - - /// - /// ColorspaceYCbCr Range16-235 Gamma2.2 SitingVideo PrimariesBT.601. - /// This definition is commonly used for MPEG2, and is usually used with 8, 10, 12, - /// or 16-bit color channels. - /// - YcbcrStudioG22LeftP601 = 6, - - /// - /// ColorspaceYCbCr Range0-255 Gamma2.2 SitingVideo PrimariesBT.601. - /// This is sometimes used for H.264 camera capture, and is usually used with 8, 10, - /// 12, or 16-bit color channels. - /// - YcbcrFullG22LeftP601 = 7, - - /// - /// ColorspaceYCbCr Range16-235 Gamma2.2 SitingVideo PrimariesBT.709. - /// This definition is commonly used for H.264 and HEVC, and is usually used with - /// 8, 10, 12, or 16-bit color channels. - /// - YcbcrStudioG22LeftP709 = 8, - - /// - /// ColorspaceYCbCr Range0-255 Gamma2.2 SitingVideo PrimariesBT.709. - /// This is sometimes used for H.264 camera capture, and is usually used with 8, 10, - /// 12, or 16-bit color channels. - /// - YcbcrFullG22LeftP709 = 9, - - /// - /// ColorspaceYCbCr Range16-235 Gamma2.2 SitingVideo PrimariesBT.2020. - /// This definition may be used by HEVC, and is usually used with 10, 12, or 16-bit - /// color channels. - /// - YcbcrStudioG22LeftP2020 = 10, - - /// - /// ColorspaceYCbCr Range0-255 Gamma2.2 SitingVideo PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - YcbcrFullG22LeftP2020 = 11, - - /// - /// ColorspaceRGB Range0-255 Gamma2084 SitingImage PrimariesBT.2020. Use with backbuffer of 10 bit colors, PixelFormat.R10G10B10A2_UNorm. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - RgbFullG2084NoneP2020 = 12, - - /// - /// ColorspaceYCbCr Range16-235 Gamma2084 SitingVideo PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - YcbcrStudioG2084LeftP2020 = 13, - - /// - /// ColorspaceRGB Range16-235 Gamma2084 SitingImage PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - RgbStudioG2084NoneP2020 = 14, - - /// - /// ColorspaceYCbCr Range16-235 Gamma2.2 SitingVideo PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - YcbcrStudioG22TopleftP2020 = 15, - - /// - /// ColorspaceYCbCr Range16-235 Gamma2084 SitingVideo PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - YcbcrStudioG2084TopleftP2020 = 16, - - /// - /// ColorspaceRGB Range0-255 Gamma2.2 SitingImage PrimariesBT.2020. - /// This is usually used with 10, 12, or 16-bit color channels. - /// - RgbFullG22NoneP2020 = 17, - - /// - /// A custom color definition is used. - /// - YcbcrStudioGhlgTopleftP2020 = 18, - - /// - /// No documentation. - /// - YcbcrFullGhlgTopleftP2020 = 19, - - /// - /// No documentation. - /// - RgbStudioG24NoneP709 = 20, - - /// - /// No documentation. - /// - RgbStudioG24NoneP2020 = 21, - - /// - /// No documentation. - /// - YcbcrStudioG24LeftP709 = 22, - - /// - /// No documentation. - /// - YcbcrStudioG24LeftP2020 = 23, - - /// - /// No documentation. - /// - YcbcrStudioG24TopleftP2020 = 24, - - /// - /// A custom color definition is used. - /// - Custom = -1 - } - + Gamma } diff --git a/sources/engine/Stride/Graphics/ColorSpaceType.cs b/sources/engine/Stride/Graphics/ColorSpaceType.cs new file mode 100644 index 0000000000..80d421de8e --- /dev/null +++ b/sources/engine/Stride/Graphics/ColorSpaceType.cs @@ -0,0 +1,721 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Graphics; + +/// +/// Specifies the type of color space a Graphics Presenter has to output. +/// +/// +/// +/// This enum is used within a Graphics Presenter when configuring a Swap Chain to check +/// the color space support of the Graphics Adapter and the Graphics Output, set a +/// specific color space as output of the Swap Chain. It is also referenced in D3D11 video methods +/// +/// +/// It is also used for defining the output color space of video decoding. +/// +/// +/// The constants in this enum are divided into parts that describe the following: +/// +///

Color Space

+/// Defines the color space of the color channel data. +/// +/// +/// Rgb +/// The Red / Green / Blue color space color channels. +/// +/// +/// YCbCr +/// +/// Three channel color model which splits luma (brightness) from chroma (color). +/// YUV technically refers to analog signals and YCbCr to digital, but they are used interchangeably. +/// +/// +/// +/// +///

Range

+/// +/// Indicates which integer range corresponds to the floating point [0..1] range of the data. +/// For video, integer YCbCr data with ranges of [16..235] or [8..247] are usually mapped to normalized YCbCr with ranges of [0..1] or [-0.5..0.5]. +/// +/// +/// +/// Full +/// +/// For PC desktop content and images. +/// Defines the ranges: For 8-bit: 0-255, for 10-bit: 0-1023, for 12-bit: 0-4095. +/// +/// +/// +/// Studio +/// +/// Often used in video. Enables the calibration of white and black between displays. +/// Defines the ranges: For 8-bit: 16-235, for 10-bit: 64-940, for 12-bit: 256 - 3760. +/// +/// +/// +/// +///

Gamma

+/// +/// +/// G10 +/// Gamma 1.0. Linear light levels. +/// +/// +/// G22 +/// +/// Gamma 2.2. Commonly used for sRGB and BT.709 (linear segment + 2.4). +/// +/// +/// +/// G24 +/// +/// Gamma 2.4. Commonly used in cinema and professional video workflows. +/// +/// +/// +/// G2084 +/// SMPTE ST.2084 (Perceptual Quantization). +/// +/// +/// +///

Siting

+/// +/// "Siting" indicates a horizontal or vertical shift of the chrominance channels relative to the luminance channel. +/// "Cositing" indicates values are sited between pixels in the vertical or horizontal direction (also known as being "sited interstitially"). +/// +/// +/// +/// None +/// For images. The U and V planes are aligned vertically. +/// +/// +/// Left +/// +/// For video. Chroma samples are aligned horizontally with the luma samples, or with multiples of the luma samples. +/// The U and V planes are aligned vertically. +/// +/// +/// +/// TopLeft +/// +/// For video. The sampling point is the top left pixel (usually of a 2x2 pixel block). Chroma samples are +/// aligned horizontally with the luma samples, or with multiples of the luma samples. Chroma samples are also aligned vertically +/// with the luma samples, or with multiples of the luma samples. +/// +/// +/// +/// +///

Primaries

+/// +/// +/// P601 +/// BT.601. Standard defining digital encoding of SDTV video. +/// +/// +/// P709 +/// +/// BT.709. Standard defining digital encoding of HDTV video. +/// +/// +/// +/// P2020 +/// BT.2020. Standard defining ultra-high definition television (UHDTV). +/// +/// +/// +///

Transfer Matrix

+/// +/// In most cases, the transfer matrix can be determined from the primaries. For some cases it must be explicitly specified as described below: +/// +/// +/// +/// X601 +/// BT.601. Standard defining digital encoding of SDTV video. +/// +/// +/// X709 +/// +/// BT.709. Standard defining digital encoding of HDTV video. +/// +/// +/// +/// X2020 +/// BT.2020. Standard defining ultra-high definition television (UHDTV). +/// +/// +///
+///
+public enum ColorSpaceType : uint +{ + // From DXGI_COLOR_SPACE_TYPE in dxgicommon.h + + /// + /// A custom color definition is used. + /// + Custom = 0xFFFFFFFF, // TODO: Expose a way to define custom color spaces (red, green, blue, white point, etc.) + + #region Color Space: RGB + + // Range: Full ----------------------------------------------------------------------- + + /// + /// + /// This is the standard definition for sRGB. Note that this is often implemented with a linear segment, + /// but in that case, the exponent is corrected to stay aligned with a gamma 2.2 curve. + /// + /// + /// This is usually used with 8-bit and 10-bit color channels. Use with 8-bit-per-channel Back-Buffers formats, + /// such as . + /// + /// + /// Color Space: RGB + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 2.2 + /// Siting: Image + /// Primaries: BT.709 + /// + /// + Rgb_Full_G22_None_P709 = 0, + + /// + /// + /// This is the linear RGB color space with BT.709 primaries. Linear gamma means no gamma correction is applied, + /// making it suitable for HDR content and mathematical operations on color values. + /// + /// + /// It is the standard definition for scRGB, and is usually used with 16-bit integer, + /// 16-bit floating point, and 32-bit floating point channels. + /// + /// + /// Commonly used in HDR workflows and when precise color calculations are required. + /// Use with floating-point formats like . + /// + /// + /// Color Space: RGB + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 1.0 (Linear) + /// Siting: Image + /// Primaries: BT.709 + /// + /// + Rgb_Full_G10_None_P709 = 1, + + /// + /// + /// This is RGB with Perceptual Quantizer (PQ) transfer function and BT.2020 primaries. PQ (ST-2084) + /// is designed for HDR content and can represent brightness levels up to 10,000 nits. + /// + /// + /// Used for HDR10 content and high dynamic range applications. Requires HDR-capable displays + /// and processing. Common with formats like . + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Color Space: RGB + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: ST-2084 (PQ) + /// Siting: Image + /// Primaries: BT.2020 + /// + /// + Rgb_Full_G2084_None_P2020 = 12, + + /// + /// + /// This is RGB with wide color gamut BT.2020 primaries and traditional gamma 2.2. + /// Provides wider color gamut than BT.709 while maintaining compatibility with SDR workflows. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used for wide color gamut content that doesn't require HDR transfer functions. + /// Suitable for displays capable of BT.2020 color reproduction. + /// + /// + /// Color Space: RGB + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 2.2 + /// Siting: Image + /// Primaries: BT.2020 + /// + /// + Rgb_Full_G22_None_P2020 = 17, + + + // Range: Studio ----------------------------------------------------------------------- + + /// + /// + /// This is RGB with studio/limited range, commonly used in broadcast and professional video production. + /// The limited range reserves headroom and footroom for video processing. + /// + /// + /// It is the standard definition for ITU-R Recommendation BT.709. Note that due to the inclusion of a linear segment, + /// the transfer curve looks similar to a pure exponential gamma of 1.9. + /// This is usually used with 8-bit and 10-bit color channels. + /// + /// + /// Typically used in broadcast workflows and professional video applications where studio range is required. + /// + /// + /// Color Space: RGB + /// Range: Studio (16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit) + /// Gamma: 2.2 + /// Siting: Image + /// Primaries: BT.709 + /// + /// + Rgb_Studio_G22_None_P709 = 2, + + /// + /// + /// This is RGB with studio range and wide color gamut BT.2020 primaries. BT.2020 provides a much wider + /// color gamut than BT.709, supporting more vivid and saturated colors. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used in Ultra HD (4K) and HDR content production. Requires displays capable of wide color gamut reproduction. + /// + /// + /// Color Space: RGB + /// Range: Studio (16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit) + /// Gamma: 2.2 + /// Siting: Image + /// Primaries: BT.2020 + /// + /// + Rgb_Studio_G22_None_P2020 = 3, + + /// + /// + /// This is RGB with Perceptual Quantizer (PQ) transfer function, studio range, and BT.2020 primaries. + /// Studio range provides headroom for HDR processing while maintaining compatibility with broadcast standards. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used in professional HDR workflows where studio range is required for broadcast compatibility. + /// + /// + /// Color Space: RGB + /// Range: Studio (16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit) + /// Gamma: ST-2084 (PQ) + /// Siting: Image + /// Primaries: BT.2020 + /// + /// + Rgb_Studio_G2084_None_P2020 = 14, + + /// + /// + /// This is RGB with gamma 2.4 transfer function, studio range, and BT.709 primaries. Gamma 2.4 + /// is used in some professional and cinema workflows for more precise color reproduction. + /// + /// + /// It is usually used with 8, 10, or 12-bit color channels. + /// + /// + /// Used in professional video production and cinema workflows where gamma 2.4 is specified. + /// + /// + /// Color Space: RGB + /// Range: Studio (16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit) + /// Gamma: 2.4 + /// Siting: Image + /// Primaries: BT.709 + /// + /// + Rgb_Studio_G24_None_P709 = 20, + + /// + /// + /// This is RGB with gamma 2.4 transfer function, studio range, and wide color gamut BT.2020 primaries. + /// Combines precise gamma 2.4 with wide color gamut for professional workflows. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used in professional wide color gamut workflows where gamma 2.4 is specified. + /// + /// + /// Color Space: RGB + /// Range: Studio (16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit) + /// Gamma: 2.4 + /// Siting: Image + /// Primaries: BT.2020 + /// + /// + Rgb_Studio_G24_None_P2020 = 21, + + #endregion + + #region Color Space: YCbCr, YUV + + // Range: Full ----------------------------------------------------------------------- + + /// + /// + /// This is YCbCr with full range using BT.709 primaries but BT.601 matrix coefficients. + /// This combination is sometimes used for compatibility reasons in certain legacy workflows. + /// + /// + /// It is usually used with 8, 10, 12, or 16-bit color channels. + /// + /// + /// Less common configuration, typically found in specific legacy or compatibility scenarios, + /// for example, it is commonly used in JPEG images and some video codecs. + /// + /// + /// Color Space: YCbCr + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 2.2 + /// Siting: Image + /// Primaries: BT.709 + /// Transfer Matrix: BT.601 + /// + /// + YCbCr_Full_G22_None_P709_X601 = 5, + + /// + /// + /// This is YCbCr with full range and BT.601 primaries. Full range utilizes the complete bit depth + /// available, providing slightly better precision than studio range. + /// + /// + /// It is usually used with 8, 10, 12, or 16-bit color channels. + /// + /// + /// Sometimes used in JPEG images and certain video codecs that support full range YCbCr, such + /// as H.264 camera capture. + /// + /// + /// Color Space: YCbCr + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 2.2 + /// Siting: Video (Left) + /// Primaries: BT.601 + /// + /// + YCbCr_Full_G22_Left_P601 = 7, + + /// + /// + /// This is YCbCr with full range and BT.709 primaries. Full range provides better utilization + /// of the available bit depth compared to studio range. + /// + /// + /// It is usually used with 8, 10, 12, or 16-bit color channels. + /// + /// + /// Used in some video codecs and applications that prefer full range for better precision. + /// Sometimes used for H.264 camera capture. + /// + /// + /// Color Space: YCbCr + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 2.2 + /// Siting: Video (Left) + /// Primaries: BT.709 + /// + /// + YCbCr_Full_G22_Left_P709 = 9, + + /// + /// + /// This is YCbCr with full range and wide color gamut BT.2020 primaries. Full range provides + /// better bit depth utilization while BT.2020 enables wide color gamut reproduction. + /// + /// + /// It is usually used with 10, 12, or 16-bit color channels. + /// + /// + /// Used in wide color gamut applications where full range precision is desired. + /// + /// + /// Color Space: YCbCr + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: 2.2 + /// Siting: Video (Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Full_G22_Left_P2020 = 11, + + /// + /// + /// This is YCbCr with Hybrid Log-Gamma (HLG) transfer function, full range, and BT.2020 primaries. + /// Full range provides better bit depth utilization while maintaining HLG's backward compatibility. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used in HDR applications where full range precision is desired with HLG transfer function. + /// + /// + /// Color Space: YCbCr + /// Range: Full (0-255 for 8-bit, 0-1023 for 10-bit, 0-4095 for 12-bit) + /// Gamma: HLG (Hybrid Log-Gamma) + /// Siting: Video (Top-Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Full_Ghlg_Topleft_P2020 = 19, + + + // Range: Studio ----------------------------------------------------------------------- + + /// + /// + /// This is the standard definition television (SDTV) color space with BT.601 primaries and matrix. + /// Left siting means chroma samples are aligned with the left edge of luma samples. + /// + /// + /// It is usually used with 8, 10, 12, or 16-bit color channels. + /// + /// + /// Used for standard definition video content, DVDs, and legacy broadcast systems. + /// Commonly used for MPEG2. It is ususally used with formats like NV12. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.2 + /// Siting: Video (Left) + /// Primaries: BT.601 + /// + /// + YCbCr_Studio_G22_Left_P601 = 6, + + /// + /// + /// This is the standard high definition television (HDTV) color space with BT.709 primaries. + /// This is the most common color space for HD video content and Blu-ray discs. + /// + /// + /// It is usually used with 8, 10, 12, or 16-bit color channels. + /// + /// + /// Widely used for HD video content, streaming, and broadcast television. + /// Compatible with most HD video formats and codecs, like H.264 and HEVC. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.2 + /// Siting: Video (Left) + /// Primaries: BT.709 + /// + /// + YCbCr_Studio_G22_Left_P709 = 8, + + /// + /// + /// This is YCbCr with wide color gamut BT.2020 primaries and studio range. BT.2020 supports + /// a much wider color gamut than BT.709, enabling more vivid and saturated colors. + /// + /// + /// It is usually used with 10, 12, or 16-bit color channels. + /// + /// + /// Used for Ultra HD (4K) content and wide color gamut video production. + /// Requires compatible displays and processing pipelines, like those used in HEVC (H.265) video encoding. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.2 + /// Siting: Video (Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_G22_Left_P2020 = 10, + + /// + /// + /// This is YCbCr with Perceptual Quantizer (PQ) transfer function, studio range, and BT.2020 primaries. + /// This is the standard color space for HDR10 video content. + /// + /// + /// It is usually used with 10, 12, or 16-bit color channels. + /// + /// + /// The primary color space for HDR10 video streams, Ultra HD Blu-ray, and HDR streaming content. + /// Widely supported by HDR displays and media players. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: ST-2084 (PQ) + /// Siting: Video (Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_G2084_Left_P2020 = 13, + + /// + /// + /// This is YCbCr with BT.2020 primaries and top-left chroma siting. Top-left siting means + /// chroma samples are aligned with the top-left corner of the corresponding luma samples. + /// + /// + /// It is usually used with 10, 12, or 16-bit color channels. + /// + /// + /// Used in specific video processing pipelines where top-left chroma siting is required. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.2 + /// Siting: Video (Top-Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_G22_Topleft_P2020 = 15, + + /// + /// + /// This is YCbCr with Perceptual Quantizer (PQ) transfer function, BT.2020 primaries, and top-left chroma siting. + /// Combines HDR capabilities with specific chroma siting requirements. + /// + /// + /// It is usually used with 10, 12, or 16-bit color channels. + /// + /// + /// Used in HDR video processing where top-left chroma siting is specifically required. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: ST-2084 (PQ) + /// Siting: Video (Top-Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_G2084_Topleft_P2020 = 16, + + /// + /// + /// This is YCbCr with Hybrid Log-Gamma (HLG) transfer function and BT.2020 primaries. HLG is designed + /// for HDR broadcasting and provides backward compatibility with SDR displays. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used for HDR broadcasting and live TV where backward compatibility with SDR is important. + /// Common in broadcast television and streaming services. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: HLG (Hybrid Log-Gamma) + /// Siting: Video (Top-Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_Ghlg_Topleft_P2020 = 18, + + /// + /// + /// This is YCbCr with gamma 2.4 transfer function, studio range, and BT.709 primaries. + /// Gamma 2.4 provides more precise color reproduction in professional video workflows. + /// + /// + /// It is usually used with 8, 10, or 12-bit color channels. + /// + /// + /// Used in professional video production where gamma 2.4 is specified for HD content. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.4 + /// Siting: Video (Left) + /// Primaries: BT.709 + /// + /// + YCbCr_Studio_G24_Left_P709 = 22, + + /// + /// + /// This is YCbCr with gamma 2.4 transfer function, studio range, and wide color gamut BT.2020 primaries. + /// Combines precise gamma 2.4 with wide color gamut for professional workflows. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used in professional wide color gamut video production where gamma 2.4 is specified. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.4 + /// Siting: Video (Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_G24_Left_P2020 = 23, + + /// + /// + /// This is YCbCr with gamma 2.4 transfer function, studio range, BT.2020 primaries, and top-left chroma siting. + /// Combines precise gamma 2.4 with wide color gamut and specific chroma siting requirements. + /// + /// + /// It is usually used with 10 or 12-bit color channels. + /// + /// + /// Used in professional wide color gamut video production where gamma 2.4 and top-left chroma siting are specified. + /// + /// + /// Color Space: YCbCr + /// + /// Range: Studio (luma: 16-235 for 8-bit, 64-940 for 10-bit, 256-3760 for 12-bit; + /// chroma (Cb/Cr): 16-240 for 8-bit, 64-960 for 10-bit, 256-3840 for 12-bit). + /// + /// Gamma: 2.4 + /// Siting: Video (Top-Left) + /// Primaries: BT.2020 + /// + /// + YCbCr_Studio_G24_Topleft_P2020 = 24 + + #endregion +} diff --git a/sources/engine/Stride/Graphics/CompareFunction.cs b/sources/engine/Stride/Graphics/CompareFunction.cs index 99f77214fb..03a93a895d 100644 --- a/sources/engine/Stride/Graphics/CompareFunction.cs +++ b/sources/engine/Stride/Graphics/CompareFunction.cs @@ -1,58 +1,59 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Identifies comparison functions that can be used to determine how the runtime compares +/// source (new) data against destination (existing) data before storing the new data. +/// +/// +/// The comparison functions can be used for a Depth-Stencil Buffer (see ) +/// for depth comparisons or rejections, or for stencil operations, or for Texture sampling +/// (see ). +/// +[DataContract] +public enum CompareFunction { /// - /// Comparison options. - /// - /// - /// A comparison option determines whether how the runtime compares source (new) data against destination (existing) data before storing the new data. - /// The comparison option is declared in a description before an object is created. - /// The API allows you to set a comparison option for a depth-stencil buffer (see ), depth-stencil operations, or sampler state (see ). - /// - [DataContract] - public enum CompareFunction - { - /// - /// Never pass the comparison. - /// - Never = 1, - - /// - /// If the source data is less than the destination data, the comparison passes. - /// - Less = 2, - - /// - /// If the source data is equal to the destination data, the comparison passes. - /// - Equal = 3, - - /// - /// If the source data is less than or equal to the destination data, the comparison passes. - /// - LessEqual = 4, - - /// - /// If the source data is greater than the destination data, the comparison passes. - /// - Greater = 5, - - /// - /// If the source data is not equal to the destination data, the comparison passes. - /// - NotEqual = 6, - - /// - /// If the source data is greater than or equal to the destination data, the comparison passes. - /// - GreaterEqual = 7, - - /// - /// Always pass the comparison. - /// - Always = 8, - } + /// Never pass the comparison. + ///
+ Never = 1, + + /// + /// If the source data is less than the destination data, the comparison passes. + /// + Less = 2, + + /// + /// If the source data is equal to the destination data, the comparison passes. + /// + Equal = 3, + + /// + /// If the source data is less than or equal to the destination data, the comparison passes. + /// + LessEqual = 4, + + /// + /// If the source data is greater than the destination data, the comparison passes. + /// + Greater = 5, + + /// + /// If the source data is not equal to the destination data, the comparison passes. + /// + NotEqual = 6, + + /// + /// If the source data is greater than or equal to the destination data, the comparison passes. + /// + GreaterEqual = 7, + + /// + /// Always pass the comparison. + /// + Always = 8 } diff --git a/sources/engine/Stride/Graphics/DataBox.cs b/sources/engine/Stride/Graphics/DataBox.cs index 32cd7307d0..4ce74c26fa 100644 --- a/sources/engine/Stride/Graphics/DataBox.cs +++ b/sources/engine/Stride/Graphics/DataBox.cs @@ -1,108 +1,75 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; using System.Runtime.InteropServices; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines a region of data organized in 3D. +/// +/// A pointer to the data. +/// The number of bytes per row of the data. +/// The number of bytes per slice of the data (for a 3D Texture, a slice is a 2D image). +[StructLayout(LayoutKind.Sequential)] +public struct DataBox(IntPtr dataPointer, int rowPitch, int slicePitch) : IEquatable { /// - /// Provides access to data organized in 3D. + /// An empty . + /// + public static readonly DataBox Empty = default; + + + /// + /// A pointer to the data. /// - [StructLayout(LayoutKind.Sequential)] - public struct DataBox : IEquatable + public IntPtr DataPointer = dataPointer; + + /// + /// The number of bytes per row of the data. + /// + public int RowPitch = rowPitch; + + /// + /// The number of bytes per slice of the data (for a 3D Texture, a slice is a 2D image). + /// + public int SlicePitch = slicePitch; + + + /// + /// Gets a value indicating whether this data box is empty. + /// + /// if this instance is empty; otherwise, . + public readonly bool IsEmpty => EqualsByRef(in Empty); + + + /// + public readonly bool Equals(DataBox other) + { + return EqualsByRef(in other); + } + + private readonly bool EqualsByRef(scoped ref readonly DataBox other) + { + return DataPointer == other.DataPointer + && RowPitch == other.RowPitch + && SlicePitch == other.SlicePitch; + } + + /// + public override readonly bool Equals(object obj) { - /// - /// An empty DataBox. - /// - private static DataBox empty; - - /// - /// Initializes a new instance of the struct. - /// - /// The datapointer. - /// The row pitch. - /// The slice pitch. - public DataBox(IntPtr datapointer, int rowPitch, int slicePitch) - { - DataPointer = datapointer; - RowPitch = rowPitch; - SlicePitch = slicePitch; - } - - /// - /// Pointer to the data. - /// - public IntPtr DataPointer; - - /// - /// Gets the number of bytes per row. - /// - public int RowPitch; - - /// - /// Gets the number of bytes per slice (for a 3D texture, a slice is a 2D image) - /// - public int SlicePitch; - - /// - /// Gets a value indicating whether this instance is empty. - /// - /// true if this instance is empty; otherwise, false. - public bool IsEmpty - { - get - { - return EqualsByRef(ref empty); - } - } - - public bool Equals(DataBox other) - { - return EqualsByRef(ref other); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is DataBox && Equals((DataBox)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = DataPointer.GetHashCode(); - hashCode = (hashCode * 397) ^ RowPitch; - hashCode = (hashCode * 397) ^ SlicePitch; - return hashCode; - } - } - - /// - /// Implements the operator ==. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator ==(DataBox left, DataBox right) - { - return left.Equals(right); - } - - /// - /// Implements the operator !=. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator !=(DataBox left, DataBox right) - { - return !left.Equals(right); - } - - private bool EqualsByRef(ref DataBox other) - { - return DataPointer == other.DataPointer && RowPitch == other.RowPitch && SlicePitch == other.SlicePitch; - } + return obj is DataBox dataBox && Equals(dataBox); } + + /// + public override readonly int GetHashCode() + { + return HashCode.Combine(DataPointer, RowPitch, SlicePitch); + } + + public static bool operator ==(DataBox left, DataBox right) => left.Equals(right); + + public static bool operator !=(DataBox left, DataBox right) => !left.Equals(right); } diff --git a/sources/engine/Stride/Graphics/DisplayOrientation.cs b/sources/engine/Stride/Graphics/DisplayOrientation.cs index 781cbf5028..6e563f4682 100644 --- a/sources/engine/Stride/Graphics/DisplayOrientation.cs +++ b/sources/engine/Stride/Graphics/DisplayOrientation.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,33 +24,32 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes the orientation of the display. +/// +[Flags] +[DataContract("DisplayOrientation")] +public enum DisplayOrientation { /// - /// Describes the orientation of the display. + /// The default value for the orientation. /// - [Flags] - [DataContract("DisplayOrientation")] - public enum DisplayOrientation - { - /// - /// The default value for the orientation. - /// - Default = 0, + Default = 0, - /// - /// Displays in landscape mode to the left. - /// - LandscapeLeft = 1, + /// + /// Displays in landscape mode to the left. + /// + LandscapeLeft = 1, - /// - /// Displays in landscape mode to the right. - /// - LandscapeRight = 2, + /// + /// Displays in landscape mode to the right. + /// + LandscapeRight = 2, - /// - /// Displays in portrait mode. - /// - Portrait = 4, - } + /// + /// Displays in portrait mode. + /// + Portrait = 4 } diff --git a/sources/engine/Stride/Graphics/GraphicsProfile.cs b/sources/engine/Stride/Graphics/GraphicsProfile.cs index 1250b7a584..87415c1d68 100644 --- a/sources/engine/Stride/Graphics/GraphicsProfile.cs +++ b/sources/engine/Stride/Graphics/GraphicsProfile.cs @@ -1,61 +1,86 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Identifies the base set of capabilities a Graphics Device supports. +/// +/// +/// +/// The graphics profile only indicates which capabilities are available on the device, not which +/// graphics API is used. For example, a Graphics Device with a +/// supports Direct3D 10.0, Direct3D 10.1, and Direct3D 11.0 APIs, as well as OpenGL ES 3.0. +/// +/// +/// Some platforms may not support all graphics profiles, or may have additional restrictions. +/// Also, some graphics APIs may not support all features of a given profile, or may need to +/// enable specific extensions to access certain features (OpenGL, Vulkan). +/// +/// +[DataContract("GraphicsProfile")] +public enum GraphicsProfile { /// - /// Identifies the set of supported devices for the demo based on device capabilities. - /// - [DataContract("GraphicsProfile")] - public enum GraphicsProfile - { - /// - /// DirectX9 support (HLSL 3.0) - /// - [Display("Direct3D 9.1 / OpenGL ES 2.0")] - Level_9_1 = 0x9100, - - /// - /// DirectX9 support (HLSL 3.0) - /// - [Display("Direct3D 9.2")] - Level_9_2 = 0x9200, - - /// - /// DirectX9 support (HLSL 3.0) - /// - [Display("Direct3D 9.3")] - Level_9_3 = 0x9300, - - /// - /// DirectX10 support (HLSL 4.0, Geometry Shader) - /// - [Display("Direct3D 10.0 / OpenGL ES 3.0")] - Level_10_0 = 0xA000, - - /// - /// DirectX10.1 support (HLSL 4.1, Geometry Shader) - /// - [Display("Direct3D 10.1")] - Level_10_1 = 0xA100, - - /// - /// DirectX11 support (HLSL 5.0, Compute Shaders, Domain/Hull Shaders) - /// - [Display("Direct3D 11.0 / OpenGL ES 3.1")] - Level_11_0 = 0xB000, - - /// - /// DirectX11.1 support (HLSL 5.0, Compute Shaders, Domain/Hull Shaders) - /// - [Display("Direct3D 11.1")] - Level_11_1 = 0xB100, - - /// - /// DirectX11.2 support (HLSL 5.0, Compute Shaders, Domain/Hull Shaders) - /// - [Display("Direct3D 11.2")] - Level_11_2 = 0xB200, - } + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 9.0a (HLSL 3.0), + /// OpenGL ES 2.0, or Vulkan 1.0. + ///
+ [Display("Level 9.1 ~ like Direct3D 9.0 / OpenGL ES 2.0 / Vulkan 1.0")] + Level_9_1 = 0x9100, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 9.0b (HLSL 3.0), + /// OpenGL ES 2.0, or Vulkan 1.0. + /// + [Display("Level 9.2 ~ like Direct3D 9.0b / OpenGL ES 2.0 / Vulkan 1.0")] + Level_9_2 = 0x9200, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 9.0c (HLSL 3.0), + /// OpenGL ES 2.0, or Vulkan 1.0. + /// + [Display("Level 9.3 ~ like Direct3D 9.0c / OpenGL ES 2.0 / Vulkan 1.0")] + Level_9_3 = 0x9300, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 10 + /// (HLSL 4.0, Geometry Shaders), OpenGL ES 3.0, or Vulkan 1.0. + /// + [Display("Level 10.0 ~ like Direct3D 10.0 / OpenGL ES 3.0 / Vulkan 1.0")] + Level_10_0 = 0xA000, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 10.1 + /// (HLSL 4.1, Geometry Shaders), OpenGL ES 3.0, or Vulkan 1.0. + /// + [Display("Level 10.1 ~ like Direct3D 10.1 / OpenGL ES 3.0 / Vulkan 1.0")] + Level_10_1 = 0xA100, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 11 + /// (HLSL 5.0, Compute Shaders, Domain Shaders, Hull Shaders), OpenGL ES 3.1, or Vulkan 1.1. + /// + [Display("Level 11.0 ~ like Direct3D 11.0 / OpenGL ES 3.1 / Vulkan 1.1")] + Level_11_0 = 0xB000, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 11.1 + /// (HLSL 5.0, Compute Shaders, Domain Shaders, Hull Shaders), OpenGL ES 3.1, or Vulkan 1.1. + /// + [Display("Level 11.1 ~ like Direct3D 11.1 / OpenGL ES 3.1 / Vulkan 1.1")] + Level_11_1 = 0xB100, + + /// + /// Identifies Graphics Devices with capabilities roughly at the level of DirectX 11.2 + /// (HLSL 5.0, Compute Shaders, Domain Shaders, Hull Shaders), OpenGL ES 3.1, or Vulkan 1.1. + /// + [Display("Level 11.2 ~ like Direct3D 11.2 / OpenGL ES 3.1 / Vulkan 1.1")] + Level_11_2 = 0xB200 + + // Future? + // DirectX 12 (HLSL 6.0, Ray-tracing, Mesh Shaders, Variable-rate Shading), or Vulkan 1.2+. + //[Display("Level 12 ~ like Direct3D 12 / Vulkan 1.2")] + //Level_12 = 0xC000 } diff --git a/sources/engine/Stride/Graphics/Image.cs b/sources/engine/Stride/Graphics/Image.cs index 4f9c568672..4acd5a1304 100644 --- a/sources/engine/Stride/Graphics/Image.cs +++ b/sources/engine/Stride/Graphics/Image.cs @@ -72,6 +72,7 @@ // cannot change. To the extent permitted under your local laws, the // contributors exclude the implied warranties of merchantability, fitness for a // particular purpose and non-infringement. + using System; using System.Collections.Generic; using System.IO; @@ -101,7 +102,7 @@ public sealed class Image : IDisposable private List mipMapToZIndex; private int zBufferCountPerArraySlice; private MipMapDescription[] mipmapDescriptions; - private static List loadSaveDelegates = new List(); + private static readonly List loadSaveDelegates = []; /// /// Provides access to all pixel buffers. @@ -171,7 +172,7 @@ internal Image() /// The offset from the beginning of the data buffer. /// The handle (optionnal). /// if set to true [buffer is disposable]. - /// If the format is invalid, or width/height/depth/arraysize is invalid with respect to the dimension. + /// If the format is invalid, or width/height/depth/arraysize is invalid with respect to the dimension. internal unsafe Image(ImageDescription description, IntPtr dataPointer, int offset, GCHandle? handle, bool bufferIsDisposable, PitchFlags pitchFlags = PitchFlags.None, int rowStride = 0) { Initialize(description, dataPointer, offset, handle, bufferIsDisposable, pitchFlags, rowStride); @@ -213,18 +214,18 @@ public MipMapDescription GetMipMapDescription(int mipmap) /// /// For 3D image, the parameter is the Z slice, otherwise it is an index into the texture array. /// The mipmap. - /// A . - /// If arrayOrZSliceIndex or mipmap are out of range. + /// A . + /// If arrayOrZSliceIndex or mipmap are out of range. public PixelBuffer GetPixelBuffer(int arrayOrZSliceIndex, int mipmap) { // Check for parameters, as it is easy to mess up things... if (mipmap > Description.MipLevels) - throw new ArgumentException("Invalid mipmap level", "mipmap"); + throw new ArgumentException("Invalid mipmap level", nameof(mipmap)); if (Description.Dimension == TextureDimension.Texture3D) { if (arrayOrZSliceIndex > Description.Depth) - throw new ArgumentException("Invalid z slice index", "arrayOrZSliceIndex"); + throw new ArgumentException("Invalid z slice index", nameof(arrayOrZSliceIndex)); // For 3D textures return GetPixelBufferUnsafe(0, arrayOrZSliceIndex, mipmap); @@ -232,7 +233,7 @@ public PixelBuffer GetPixelBuffer(int arrayOrZSliceIndex, int mipmap) if (arrayOrZSliceIndex > Description.ArraySize) { - throw new ArgumentException("Invalid array slice index", "arrayOrZSliceIndex"); + throw new ArgumentException("Invalid array slice index", nameof(arrayOrZSliceIndex)); } // For 1D, 2D textures @@ -245,35 +246,50 @@ public PixelBuffer GetPixelBuffer(int arrayOrZSliceIndex, int mipmap) /// Index into the texture array. Must be set to 0 for 3D images. /// Z index for 3D image. Must be set to 0 for all 1D/2D images. /// The mipmap. - /// A . - /// If arrayIndex, zIndex or mipmap are out of range. + /// A . + /// If arrayIndex, zIndex or mipmap are out of range. public PixelBuffer GetPixelBuffer(int arrayIndex, int zIndex, int mipmap) { // Check for parameters, as it is easy to mess up things... if (mipmap > Description.MipLevels) - throw new ArgumentException("Invalid mipmap level", "mipmap"); + throw new ArgumentException("Invalid mipmap level", nameof(mipmap)); if (arrayIndex > Description.ArraySize) - throw new ArgumentException("Invalid array slice index", "arrayIndex"); + throw new ArgumentException("Invalid array slice index", nameof(arrayIndex)); if (zIndex > Description.Depth) - throw new ArgumentException("Invalid z slice index", "zIndex"); + throw new ArgumentException("Invalid Z slice index", nameof(zIndex)); - return this.GetPixelBufferUnsafe(arrayIndex, zIndex, mipmap); + return GetPixelBufferUnsafe(arrayIndex, zIndex, mipmap); } /// - /// Registers a loader/saver for a specified image file type. + /// Registers a loader / saver for a specified Image file type. /// - /// The file type (use integer and explicit casting to to register other fileformat. - /// The loader delegate (can be null). - /// The saver delegate (can be null). - /// - public static void Register(ImageFileType type, ImageLoadDelegate loader, ImageSaveDelegate saver) + /// + /// The file type. Use an integer and explicit casting to to register other file formats. + /// + /// + /// A delegate that will be invoked to load an Image of the specified . + /// Specify to register no loading delegate for this type. + /// + /// + /// A delegate that will be invoked to save an Image of the specified . + /// Specify to register no saving delegate for this type. + /// + /// + /// The application can register a (and set the to ), + /// or a (and set the to ), or both, + /// but cannot set both of them to . + /// + /// + /// Not both and have to be specified. But at least one of them + /// must be (i.e. not both of them can be ). + /// + public static void Register(ImageFileType type, ImageLoadDelegate? loader, ImageSaveDelegate? saver) { - // If reference equals, then it is null - if (ReferenceEquals(loader, saver)) - throw new ArgumentNullException("Can set both loader and saver to null", "loader/saver"); + if (loader is null && ReferenceEquals(loader, saver)) + throw new ArgumentNullException($"{nameof(loader)}/{nameof(saver)}", $"Cannot set both '{nameof(loader)}' and '{nameof(saver)}' to null"); var newDelegate = new LoadSaveDelegate(type, loader, saver); for (int i = 0; i < loadSaveDelegates.Count; i++) @@ -294,7 +310,7 @@ public static void Register(ImageFileType type, ImageLoadDelegate loader, ImageS /// A pointer to the image buffer in memory. public IntPtr DataPointer { - get { return this.buffer; } + get { return buffer; } } /// @@ -339,7 +355,7 @@ private DataBox[] ComputeDataBox() for (int mipIndex = 0; mipIndex < Description.MipLevels; mipIndex++) { // Get the first z-slize (A DataBox for a Texture3D is pointing to the whole texture). - var pixelBuffer = this.GetPixelBufferUnsafe(arrayIndex, 0, mipIndex); + var pixelBuffer = GetPixelBufferUnsafe(arrayIndex, 0, mipIndex); dataBoxArray[i].DataPointer = pixelBuffer.DataPointer; dataBoxArray[i].RowPitch = pixelBuffer.RowStride; @@ -432,7 +448,7 @@ public static Image New(ImageDescription description, IntPtr dataPointer) /// The offset from the beginning of the data buffer. /// The handle (optionnal). /// if set to true [buffer is disposable]. - /// If the format is invalid, or width/height/depth/arraysize is invalid with respect to the dimension. + /// If the format is invalid, or width/height/depth/arraysize is invalid with respect to the dimension. public static Image New(ImageDescription description, IntPtr dataPointer, int offset, GCHandle? handle, bool bufferIsDisposable) { return new Image(description, dataPointer, offset, handle, bufferIsDisposable); @@ -549,8 +565,7 @@ public static Image Load(IntPtr dataPointer, int dataSize, bool makeACopy = fals /// This method support the following format: dds, bmp, jpg, png, gif, tiff, wmp, tga. public static unsafe Image Load(byte[] buffer, bool loadAsSRGB = false) { - if (buffer == null) - throw new ArgumentNullException("buffer"); + ArgumentNullException.ThrowIfNull(buffer); // If buffer is allocated on Larget Object Heap, then we are going to pin it instead of making a copy. if (buffer.Length > (85 * 1024)) @@ -574,8 +589,9 @@ public static unsafe Image Load(byte[] buffer, bool loadAsSRGB = false) /// This method support the following format: dds, bmp, jpg, png, gif, tiff, wmp, tga. public static Image Load(Stream imageStream, bool loadAsSRGB = false) { - if (imageStream == null) throw new ArgumentNullException("imageStream"); - // Read the whole stream into memory. + ArgumentNullException.ThrowIfNull(imageStream); + + // Read the whole stream into memory return Load(Utilities.ReadStream(imageStream), loadAsSRGB); } @@ -587,8 +603,9 @@ public static Image Load(Stream imageStream, bool loadAsSRGB = false) /// This method support the following format: dds, bmp, jpg, png, gif, tiff, wmp, tga. public void Save(Stream imageStream, ImageFileType fileType) { - if (imageStream == null) throw new ArgumentNullException("imageStream"); - Save(PixelBuffers, this.PixelBuffers.Length, Description, imageStream, fileType); + ArgumentNullException.ThrowIfNull(imageStream); + + Save(PixelBuffers, PixelBuffers.Length, Description, imageStream, fileType); } /// @@ -678,7 +695,7 @@ public static int CalculateMipLevels(int width, int height, int depth, MipMapCou public static int CalculateMipSize(int width, int mipLevel) { mipLevel = Math.Min(mipLevel, CountMips(width)); - width = width >> mipLevel; + width >>= mipLevel; return width > 0 ? width : 1; } @@ -691,7 +708,7 @@ public static int CalculateMipSize(int width, int mipLevel) /// The handle. /// Indicate if the image should be loaded as an sRGB texture /// - /// + /// private static Image Load(IntPtr dataPointer, int dataSize, bool makeACopy, GCHandle? handle, bool loadAsSRGB = true) { foreach (var loadSaveDelegate in loadSaveDelegates) @@ -790,10 +807,9 @@ internal unsafe void Initialize(ImageDescription description, IntPtr dataPointer } // Calculate mipmaps - int pixelBufferCount; - this.mipMapToZIndex = CalculateImageArray(description, pitchFlags, rowStride, out pixelBufferCount, out totalSizeInBytes); - this.mipmapDescriptions = CalculateMipMapDescription(description, pitchFlags); - zBufferCountPerArraySlice = this.mipMapToZIndex[this.mipMapToZIndex.Count - 1]; + mipMapToZIndex = CalculateImageArray(description, pitchFlags, rowStride, out var pixelBufferCount, out totalSizeInBytes); + mipmapDescriptions = CalculateMipMapDescription(description); + zBufferCountPerArraySlice = mipMapToZIndex[^1]; // Allocate all pixel buffers PixelBuffers = new PixelBuffer[pixelBufferCount]; @@ -802,7 +818,7 @@ internal unsafe void Initialize(ImageDescription description, IntPtr dataPointer // Setup all pointers // only release buffer that is not pinned and is asked to be disposed. this.bufferIsDisposable = !handle.HasValue && bufferIsDisposable; - this.buffer = dataPointer; + buffer = dataPointer; if (dataPointer == IntPtr.Zero) { @@ -811,7 +827,7 @@ internal unsafe void Initialize(ImageDescription description, IntPtr dataPointer this.bufferIsDisposable = true; } - SetupImageArray((IntPtr)((byte*)buffer + offset), totalSizeInBytes, rowStride, description, pitchFlags, PixelBuffers); + SetupImageArray((IntPtr)((byte*)buffer + offset), rowStride, description, pitchFlags, PixelBuffers); Description = description; @@ -837,71 +853,67 @@ internal void InitializeFrom(Image image) private PixelBuffer GetPixelBufferUnsafe(int arrayIndex, int zIndex, int mipmap) { - var depthIndex = this.mipMapToZIndex[mipmap]; - var pixelBufferIndex = arrayIndex * this.zBufferCountPerArraySlice + depthIndex + zIndex; + var depthIndex = mipMapToZIndex[mipmap]; + var pixelBufferIndex = arrayIndex * zBufferCountPerArraySlice + depthIndex + zIndex; return PixelBuffers[pixelBufferIndex]; } private static ImageDescription CreateDescription(TextureDimension dimension, int width, int height, int depth, MipMapCount mipMapCount, PixelFormat format, int arraySize) { return new ImageDescription() - { - Width = width, - Height = height, - Depth = depth, - ArraySize = arraySize, - Dimension = dimension, - Format = format, - MipLevels = mipMapCount, - }; + { + Width = width, + Height = height, + Depth = depth, + ArraySize = arraySize, + Dimension = dimension, + Format = format, + MipLevels = mipMapCount + }; } [Flags] internal enum PitchFlags { - None = 0x0, // Normal operation - LegacyDword = 0x1, // Assume pitch is DWORD aligned instead of BYTE aligned - Bpp24 = 0x10000, // Override with a legacy 24 bits-per-pixel format size - Bpp16 = 0x20000, // Override with a legacy 16 bits-per-pixel format size - Bpp8 = 0x40000, // Override with a legacy 8 bits-per-pixel format size + None = 0, // Normal operation + LegacyDword = 0x1, // Assume pitch is DWORD aligned instead of BYTE aligned + Bpp24 = 0x10000, // Override with a legacy 24 bits-per-pixel format size + Bpp16 = 0x20000, // Override with a legacy 16 bits-per-pixel format size + Bpp8 = 0x40000 // Override with a legacy 8 bits-per-pixel format size } - internal static void ComputePitch(PixelFormat fmt, int width, int height, out int rowPitch, out int slicePitch, out int widthCount, out int heightCount, PitchFlags flags = PitchFlags.None) + internal static void ComputePitch(PixelFormat format, int width, int height, out int rowPitch, out int slicePitch, out int widthPacked, out int heightPacked, PitchFlags flags = PitchFlags.None) { - widthCount = width; - heightCount = height; + widthPacked = width; + heightPacked = height; - if (fmt.IsCompressed()) + if (format.IsCompressed()) { int minWidth = 1; int minHeight = 1; - int bpb = 8; - switch (fmt) + var bytesPerBlock = format switch { - case PixelFormat.BC1_Typeless: - case PixelFormat.BC1_UNorm: - case PixelFormat.BC1_UNorm_SRgb: - case PixelFormat.BC4_Typeless: - case PixelFormat.BC4_UNorm: - case PixelFormat.BC4_SNorm: - case PixelFormat.ETC1: - case PixelFormat.ETC2_RGB: - case PixelFormat.ETC2_RGB_SRgb: - bpb = 8; - break; - default: - bpb = 16; - break; - } - - widthCount = Math.Max(1, (Math.Max(minWidth, width) + 3)) / 4; - heightCount = Math.Max(1, (Math.Max(minHeight, height) + 3)) / 4; - rowPitch = widthCount * bpb; - - slicePitch = rowPitch * heightCount; + PixelFormat.BC1_Typeless + or PixelFormat.BC1_UNorm + or PixelFormat.BC1_UNorm_SRgb + or PixelFormat.BC4_Typeless + or PixelFormat.BC4_UNorm + or PixelFormat.BC4_SNorm + or PixelFormat.ETC1 + or PixelFormat.ETC2_RGB + or PixelFormat.ETC2_RGB_SRgb => 8, + + _ => 16 + }; + + widthPacked = Math.Max(1, (Math.Max(minWidth, width) + 3)) / 4; + heightPacked = Math.Max(1, (Math.Max(minHeight, height) + 3)) / 4; + rowPitch = widthPacked * bytesPerBlock; + + slicePitch = rowPitch * heightPacked; } - else if (fmt.IsPacked()) + else if (format.IsPacked()) { rowPitch = ((width + 1) >> 1) * 4; @@ -909,77 +921,72 @@ internal static void ComputePitch(PixelFormat fmt, int width, int height, out in } else { - int bpp; - - if ((flags & PitchFlags.Bpp24) != 0) - bpp = 24; - else if ((flags & PitchFlags.Bpp16) != 0) - bpp = 16; - else if ((flags & PitchFlags.Bpp8) != 0) - bpp = 8; + int bitsPerPixel; + + if (flags.HasFlag(PitchFlags.Bpp24)) + bitsPerPixel = 24; + else if (flags.HasFlag(PitchFlags.Bpp16)) + bitsPerPixel = 16; + else if (flags.HasFlag(PitchFlags.Bpp8)) + bitsPerPixel = 8; else - bpp = fmt.SizeInBits(); + bitsPerPixel = format.SizeInBits(); - if ((flags & PitchFlags.LegacyDword) != 0) + if (flags.HasFlag(PitchFlags.LegacyDword)) { // Special computation for some incorrectly created DDS files based on // legacy DirectDraw assumptions about pitch alignment - rowPitch = ((width * bpp + 31) / 32) * sizeof(int); + rowPitch = ((width * bitsPerPixel + 31) / 32) * sizeof(int); slicePitch = rowPitch * height; } else { - rowPitch = (width * bpp + 7) / 8; + rowPitch = (width * bitsPerPixel + 7) / 8; slicePitch = rowPitch * height; } } } - internal static MipMapDescription[] CalculateMipMapDescription(ImageDescription metadata, PitchFlags cpFlags = PitchFlags.None) + internal static MipMapDescription[] CalculateMipMapDescription(ImageDescription metadata) { - int nImages; - int pixelSize; - return CalculateMipMapDescription(metadata, cpFlags, out nImages, out pixelSize); + return CalculateMipMapDescription(metadata, out _, out _); } - internal static MipMapDescription[] CalculateMipMapDescription(ImageDescription metadata, PitchFlags cpFlags, out int nImages, out int pixelSize) + internal static MipMapDescription[] CalculateMipMapDescription(ImageDescription metadata, out int nImages, out int pixelSize) { pixelSize = 0; nImages = 0; - int w = metadata.Width; - int h = metadata.Height; - int d = metadata.Depth; + int width = metadata.Width; + int height = metadata.Height; + int depth = metadata.Depth; var mipmaps = new MipMapDescription[metadata.MipLevels]; for (int level = 0; level < metadata.MipLevels; ++level) { - int rowPitch, slicePitch; - int widthPacked; - int heightPacked; - ComputePitch(metadata.Format, w, h, out rowPitch, out slicePitch, out widthPacked, out heightPacked, PitchFlags.None); + ComputePitch(metadata.Format, width, height, out var rowPitch, out var slicePitch, out var widthPacked, out var heightPacked, PitchFlags.None); mipmaps[level] = new MipMapDescription( - w, - h, - d, + width, + height, + depth, rowPitch, slicePitch, widthPacked, heightPacked); - pixelSize += d * slicePitch; - nImages += d; + pixelSize += depth * slicePitch; + nImages += depth; - if (h > 1) - h >>= 1; + if (height > 1) + height >>= 1; - if (w > 1) - w >>= 1; + if (width > 1) + width >>= 1; - if (d > 1) - d >>= 1; + if (depth > 1) + depth >>= 1; } return mipmaps; } @@ -1006,10 +1013,7 @@ private static List CalculateImageArray(ImageDescription imageDesc, PitchFl for (int i = 0; i < imageDesc.MipLevels; i++) { - int rowPitch, slicePitch; - int widthPacked; - int heightPacked; - ComputePitch(imageDesc.Format, w, h, out rowPitch, out slicePitch, out widthPacked, out heightPacked, pitchFlags); + ComputePitch(imageDesc.Format, w, h, out var rowPitch, out var slicePitch, out var widthPacked, out var heightPacked, pitchFlags); if (rowStride > 0) { @@ -1060,7 +1064,7 @@ private static List CalculateImageArray(ImageDescription imageDesc, PitchFl /// /// /// - private static unsafe void SetupImageArray(IntPtr buffer, int pixelSize, int rowStride, ImageDescription imageDesc, PitchFlags pitchFlags, PixelBuffer[] output) + private static unsafe void SetupImageArray(IntPtr buffer, int rowStride, ImageDescription imageDesc, PitchFlags pitchFlags, PixelBuffer[] output) { int index = 0; var pixels = (byte*)buffer; @@ -1072,16 +1076,13 @@ private static unsafe void SetupImageArray(IntPtr buffer, int pixelSize, int row for (uint level = 0; level < imageDesc.MipLevels; ++level) { - int rowPitch, slicePitch; - int widthPacked; - int heightPacked; - ComputePitch(imageDesc.Format, w, h, out rowPitch, out slicePitch, out widthPacked, out heightPacked, pitchFlags); + ComputePitch(imageDesc.Format, w, h, out var rowPitch, out var slicePitch, out var widthPacked, out var heightPacked, pitchFlags); if (rowStride > 0) { // Check that stride is ok if (rowStride < rowPitch) - throw new InvalidOperationException(string.Format("Invalid stride [{0}]. Value can't be lower than actual stride [{1}]", rowStride, rowPitch)); + throw new InvalidOperationException($"Invalid stride [{rowStride}]. Value can't be lower than actual stride [{rowPitch}]"); if (widthPacked != w || heightPacked != h) throw new InvalidOperationException("Custom strides is not supported with packed PixelFormats"); @@ -1173,20 +1174,12 @@ public static int CountMips(int width, int height, int depth) return mipLevels; } - private class LoadSaveDelegate + private class LoadSaveDelegate(ImageFileType fileType, ImageLoadDelegate load, ImageSaveDelegate save) { - public LoadSaveDelegate(ImageFileType fileType, ImageLoadDelegate load, ImageSaveDelegate save) - { - FileType = fileType; - Load = load; - Save = save; - } - - public ImageFileType FileType; - - public ImageLoadDelegate Load; + public ImageFileType FileType = fileType; - public ImageSaveDelegate Save; + public ImageLoadDelegate Load = load; + public ImageSaveDelegate Save = save; } } } diff --git a/sources/engine/Stride/Graphics/MipMapCount.cs b/sources/engine/Stride/Graphics/MipMapCount.cs index 488620d2a8..a3a71a026d 100644 --- a/sources/engine/Stride/Graphics/MipMapCount.cs +++ b/sources/engine/Stride/Graphics/MipMapCount.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -24,122 +24,152 @@ using System; using System.Runtime.InteropServices; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes the number of mipmap levels of a Texture. +/// +/// +/// allows implicit conversion from several types of values: +/// +/// +/// Set to to specify all mipmaps (i.e. the whole mipchain). +/// This is equivalent to . +/// +/// +/// Set to to specify a single mipmap. +/// This is equivalent to . +/// +/// Set to any positive non-zero integer to indicate a specific number of mipmaps. +/// +/// +[StructLayout(LayoutKind.Sequential, Size = 4)] +public readonly struct MipMapCount : IEquatable { /// - /// A simple wrapper to specify number of mipmaps. - /// Set to true to specify all mipmaps or sets an integer value >= 1 - /// to specify the exact number of mipmaps. + /// Automatic mipmap count based on the size of the Texture (i.e. the whole mipchain). + /// + public static readonly MipMapCount Auto = new(allMipMaps: true); + + /// + /// Just a single mipmap. + /// + public static readonly MipMapCount One = new(allMipMaps: false); + + + /// + /// The number of mipmaps. /// /// - /// This structure use implicit conversion: - ///
    - ///
  • Set to true to specify all mipmaps.
  • - ///
  • Set to false to specify a single mipmap.
  • - ///
  • Set to an integer value >=1 to specify an exact count of mipmaps.
  • - ///
+ /// A value of zero (0) means that all mipmaps (the whole mipchain) will be generated. + /// A value of one (1) means only a single mipmap is generated. + /// Any other number indicates the number of mipmaps to generate. ///
- [StructLayout(LayoutKind.Sequential, Size = 4)] - public struct MipMapCount : IEquatable + public readonly int Count; + + + /// + /// Initializes a new instance of the struct. + /// + /// + /// to indicate that all mipmap levels should be generated; + /// to indicate only a single level. + /// + public MipMapCount(bool allMipMaps) + { + Count = allMipMaps ? 0 : 1; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The mipmap count. + public MipMapCount(int count) + { + ArgumentOutOfRangeException.ThrowIfLessThan(count, 0); + + Count = count; + } + + + /// + public readonly bool Equals(MipMapCount other) + { + return Count == other.Count; + } + + /// + public override readonly bool Equals(object obj) + { + if (obj is null) + return false; + + return obj is MipMapCount count && Equals(count); + } + + /// + public override readonly int GetHashCode() + { + return Count; + } + + public static bool operator ==(MipMapCount left, MipMapCount right) + { + return left.Equals(right); + } + + public static bool operator !=(MipMapCount left, MipMapCount right) + { + return !left.Equals(right); + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// + /// if all mipmap levels should be generated; + /// a single level or more should be generated. + /// + public static implicit operator bool(MipMapCount mipMap) + { + return mipMap.Count == 0; + } + + /// + /// Performs an implicit conversion from to . + /// + /// + /// to indicate that all mipmap levels should be generated; + /// to indicate only a single level. + /// + /// The result of the conversion. + public static implicit operator MipMapCount(bool allMipMaps) + { + return new MipMapCount(allMipMaps); + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value to convert. + /// + /// The number of mipmaps. A value of zero (0) means all mipmaps. + /// + public static implicit operator int(MipMapCount mipMap) + { + return mipMap.Count; + } + + /// + /// Performs an implicit conversion from to . + /// + /// + /// The number of mipmaps. A value of zero (0) means all mipmaps. + /// + /// The result of the conversion. + public static implicit operator MipMapCount(int mipMapCount) { - /// - /// Automatic mipmap level based on texture size. - /// - public static readonly MipMapCount Auto = new MipMapCount(true); - - /// - /// Initializes a new instance of the struct. - /// - /// if set to true generates all mip maps. - public MipMapCount(bool allMipMaps) - { - this.Count = allMipMaps ? 0 : 1; - } - - /// - /// Initializes a new instance of the struct. - /// - /// The count. - public MipMapCount(int count) - { - if (count < 0) - throw new ArgumentException("mipCount must be >= 0"); - this.Count = count; - } - - /// - /// Number of mipmaps. - /// - /// - /// Zero(0) means generate all mipmaps. One(1) generates a single mipmap... etc. - /// - public readonly int Count; - - public bool Equals(MipMapCount other) - { - return this.Count == other.Count; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - return obj is MipMapCount && Equals((MipMapCount)obj); - } - - public override int GetHashCode() - { - return this.Count; - } - - public static bool operator ==(MipMapCount left, MipMapCount right) - { - return left.Equals(right); - } - - public static bool operator !=(MipMapCount left, MipMapCount right) - { - return !left.Equals(right); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// The result of the conversion. - public static implicit operator bool(MipMapCount mipMap) - { - return mipMap.Count == 0; - } - - /// - /// Performs an explicit conversion from to . - /// - /// True to generate all mipmaps, false to use a single mipmap. - /// The result of the conversion. - public static implicit operator MipMapCount(bool mipMapAll) - { - return new MipMapCount(mipMapAll); - } - - /// - /// Performs an explicit conversion from to . - /// - /// The value. - /// The count of mipmap (0 means all mipmaps). - public static implicit operator int(MipMapCount mipMap) - { - return mipMap.Count; - } - - /// - /// Performs an explicit conversion from to . - /// - /// True to generate all mipmaps, false to use a single mipmap. - /// The result of the conversion. - public static implicit operator MipMapCount(int mipMapCount) - { - return new MipMapCount(mipMapCount); - } + return new MipMapCount(mipMapCount); } } diff --git a/sources/engine/Stride/Graphics/MipMapDescription.cs b/sources/engine/Stride/Graphics/MipMapDescription.cs index 2b8a0501ad..18fbb6df0d 100644 --- a/sources/engine/Stride/Graphics/MipMapDescription.cs +++ b/sources/engine/Stride/Graphics/MipMapDescription.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,129 +23,145 @@ using System; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes a mipmap image. +/// +/// The width of the mipmap, in texels. +/// The height of the mipmap, in texels. +/// The depth of the mipmap, in texels. +/// The row stride of the mipmap, in bytes. +/// The depth stride of the mipmap, in bytes. +/// +/// The width of the mipmap in "blocks". +/// +/// If the pixel format is not a block-compressed format (like BC1, BC2, etc.), this will be +/// the same as . +/// +/// +/// Otherwise, if the format is block-compressed, this parameter represents the number of blocks +/// across the width (usually ~1/4 the width). +/// +/// +/// +/// The height of the mipmap in "blocks". +/// +/// If the pixel format is not a block-compressed format (like BC1, BC2, etc.), this will be +/// the same as . +/// +/// +/// Otherwise, if the format is block-compressed, this parameter represents the number of blocks +/// across the height (usually ~1/4 the height). +/// +/// +/// +/// Mipmaps are a sequence of precomputed textures, each of which is a progressively smaller version of the original texture. +/// These are used in 3D graphics to improve rendering performance and reduce aliasing artifacts. +/// Each of the images in the sequence is called a mipmap level. This structure describes one of those. +/// +public readonly struct MipMapDescription(int width, int height, int depth, int rowStride, int depthStride, int widthPacked, int heightPacked) + : IEquatable { /// - /// Describes a mipmap. + /// Width of the mipmap, in texels. + /// + public readonly int Width = width; + + /// + /// Height of the mipmap, in texels. + /// + public readonly int Height = height; + + /// + /// Width of the mipmap, in blocks. + /// + /// + /// + /// If the pixel format is not a block-compressed format (like BC1, BC2, etc.), this will be + /// the same as . + /// + /// + /// Otherwise, if the format is block-compressed, this parameter represents the number of blocks + /// across the width (usually ~1/4 the width). + /// + /// + public readonly int WidthPacked = widthPacked; + + /// + /// Height of the mipmap, in blocks. + /// + /// + /// + /// If the pixel format is not a block-compressed format (like BC1, BC2, etc.), this will be + /// the same as . + /// + /// + /// Otherwise, if the format is block-compressed, this parameter represents the number of blocks + /// across the height (usually ~1/4 the height). + /// + /// + public readonly int HeightPacked = heightPacked; + + /// + /// Depth of the mipmap, in texels. + /// + public readonly int Depth = depth; + + /// + /// Row stride of the mipmap (i.e. number of bytes per row). + /// + public readonly int RowStride = rowStride; + + /// + /// Depth stride of the mipmap (i.e. number of bytes per depth slice). /// - public class MipMapDescription : IEquatable + public readonly int DepthStride = depthStride; + + /// + /// Size of the whole mipmap, in bytes. + /// + public readonly int MipmapSize = depthStride * depth; + + + public bool Equals(MipMapDescription other) + { + return Width == other.Width + && Height == other.Height + && WidthPacked == other.WidthPacked + && HeightPacked == other.HeightPacked + && Depth == other.Depth + && RowStride == other.RowStride + && MipmapSize == other.MipmapSize + && DepthStride == other.DepthStride; + } + + public override bool Equals(object obj) + { + if (obj is null) + return false; + + return obj is MipMapDescription mipMapDescription && Equals(mipMapDescription); + } + + public override int GetHashCode() + { + return HashCode.Combine(Width, Height, WidthPacked, HeightPacked, Depth, RowStride, MipmapSize, DepthStride); + } + + public static bool operator ==(MipMapDescription left, MipMapDescription right) + { + return Equals(left, right); + } + + public static bool operator !=(MipMapDescription left, MipMapDescription right) + { + return !Equals(left, right); + } + + /// + public override string ToString() { - /// - /// Initializes a new instance of the class. - /// - /// The width. - /// The height. - /// The depth. - /// The row stride. - /// The depth stride. - public MipMapDescription(int width, int height, int depth, int rowStride, int depthStride, int widthPacked, int heightPacked) - { - Width = width; - Height = height; - Depth = depth; - RowStride = rowStride; - DepthStride = depthStride; - MipmapSize = depthStride * depth; - WidthPacked = widthPacked; - HeightPacked = heightPacked; - } - - /// - /// Width of this mipmap. - /// - public readonly int Width; - - /// - /// Height of this mipmap. - /// - public readonly int Height; - - /// - /// Width of this mipmap. - /// - public readonly int WidthPacked; - - /// - /// Height of this mipmap. - /// - public readonly int HeightPacked; - - /// - /// Depth of this mipmap. - /// - public readonly int Depth; - - /// - /// RowStride of this mipmap (number of bytes per row). - /// - public readonly int RowStride; - - /// - /// DepthStride of this mipmap (number of bytes per depth slice). - /// - public readonly int DepthStride; - - /// - /// Size in bytes of this whole mipmap. - /// - public readonly int MipmapSize; - - public bool Equals(MipMapDescription other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - return this.Width == other.Width && this.Height == other.Height && this.WidthPacked == other.WidthPacked && this.HeightPacked == other.HeightPacked && this.Depth == other.Depth && this.RowStride == other.RowStride && this.MipmapSize == other.MipmapSize && this.DepthStride == other.DepthStride; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != this.GetType()) - return false; - return Equals((MipMapDescription)obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = this.Width; - hashCode = (hashCode * 397) ^ this.Height; - hashCode = (hashCode * 397) ^ this.WidthPacked; - hashCode = (hashCode * 397) ^ this.HeightPacked; - hashCode = (hashCode * 397) ^ this.Depth; - hashCode = (hashCode * 397) ^ this.RowStride; - hashCode = (hashCode * 397) ^ this.MipmapSize; - hashCode = (hashCode * 397) ^ this.DepthStride; - return hashCode; - } - } - - /// - /// Implements the ==. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator ==(MipMapDescription left, MipMapDescription right) - { - return Equals(left, right); - } - - /// - /// Implements the !=. - /// - /// The left. - /// The right. - /// The result of the operator. - public static bool operator !=(MipMapDescription left, MipMapDescription right) - { - return !Equals(left, right); - } + return FormattableString.Invariant($"{nameof(MipMapDescription)}: {Width}x{Height}x{Depth}, Size: {MipmapSize} bytes"); } } diff --git a/sources/engine/Stride/Graphics/PixelFormat.cs b/sources/engine/Stride/Graphics/PixelFormat.cs index 20ebde9271..d121b991c8 100644 --- a/sources/engine/Stride/Graphics/PixelFormat.cs +++ b/sources/engine/Stride/Graphics/PixelFormat.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,523 +23,946 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Defines various types of pixel formats. +/// +/// +/// +/// Pixel formats describe the layout and type of data stored in a pixel, including the number of channels, bits per channel, +/// and whether the data is normalized or typeless. +/// +/// The suffixes in the names indicate the type of data: +/// +/// +/// UNorm (Unsigned Normalized) +/// +/// Values are stored as unsigned integers, but interpreted as floating-point values between 0.0 and 1.0. +/// For example, stores 8-bit values per channel, where 0 maps to 0.0 and 255 maps to 1.0. +///
+/// Common for color Textures and Render Targets. +///
+///
+/// +/// SNorm (Signed Normalized) +/// +/// Values are stored as signed integers, but interpreted as floating-point values between -1.0 and 1.0. +/// For example, stores 8-bit values per channel, where -128 to -1.0 and 127 to ~1.0. +///
+/// Useful for storing normals or vectors. +///
+///
+/// +/// Typeless +/// +/// The format is not fully defined —it’s just a memory layout. +///
+/// Multiple Views (e.g., Shader Resource Views, Render Target Views) can be created over a Graphics Resource with a typeless format +/// with different interpretations. For example, can be viewed as UNorm, UNorm_SRgb, +/// or UInt, depending on how the resource is bound. +///
+/// Great for flexibility, like rendering in linear space and sampling in sRGB. +///
+///
+/// +/// UInt / SInt +/// +/// Unsigned (UInt) or signed (SInt) integer values. No normalization —values are used as-is. +///
+/// Often used for IDs, masks, or counters. +///
+///
+/// +/// Float +/// +/// Stores values as IEEE floating-point numbers. +///
+/// Used when precision and range are important, like Depth Buffers or HDR color. +///
+///
+/// +/// SRgb +/// +/// Stores color data in the sRGB color space. +/// When sampling, the GPU automatically converts it to linear space, and when writing the data, it converts it back to sRGB +/// (applies gamma correction). +///
+/// It is typically used for textures that represent colors. +///
+///
+///
+///
+[DataContract] +public enum PixelFormat { /// - /// Defines various types of pixel formats. - /// - [DataContract] - public enum PixelFormat - { - /// - ///

The format is not known.

- ///
- None = unchecked((int)0), - - /// - ///

A four-component, 128-bit typeless format that supports 32 bits per channel including alpha. 1

- ///
- R32G32B32A32_Typeless = unchecked((int)1), - - /// - ///

A four-component, 128-bit floating-point format that supports 32 bits per channel including alpha. 1

- ///
- R32G32B32A32_Float = unchecked((int)2), - - /// - ///

A four-component, 128-bit unsigned-integer format that supports 32 bits per channel including alpha. 1

- ///
- R32G32B32A32_UInt = unchecked((int)3), - - /// - ///

A four-component, 128-bit signed-integer format that supports 32 bits per channel including alpha. 1

- ///
- R32G32B32A32_SInt = unchecked((int)4), - - /// - ///

A three-component, 96-bit typeless format that supports 32 bits per color channel.

- ///
- R32G32B32_Typeless = unchecked((int)5), - - /// - ///

A three-component, 96-bit floating-point format that supports 32 bits per color channel.

- ///
- R32G32B32_Float = unchecked((int)6), - - /// - ///

A three-component, 96-bit unsigned-integer format that supports 32 bits per color channel.

- ///
- R32G32B32_UInt = unchecked((int)7), - - /// - ///

A three-component, 96-bit signed-integer format that supports 32 bits per color channel.

- ///
- R32G32B32_SInt = unchecked((int)8), - - /// - ///

A four-component, 64-bit typeless format that supports 16 bits per channel including alpha.

- ///
- R16G16B16A16_Typeless = unchecked((int)9), - - /// - ///

A four-component, 64-bit floating-point format that supports 16 bits per channel including alpha.

- ///
- R16G16B16A16_Float = unchecked((int)10), - - /// - ///

A four-component, 64-bit unsigned-normalized-integer format that supports 16 bits per channel including alpha.

- ///
- R16G16B16A16_UNorm = unchecked((int)11), - - /// - ///

A four-component, 64-bit unsigned-integer format that supports 16 bits per channel including alpha.

- ///
- R16G16B16A16_UInt = unchecked((int)12), - - /// - ///

A four-component, 64-bit signed-normalized-integer format that supports 16 bits per channel including alpha.

- ///
- R16G16B16A16_SNorm = unchecked((int)13), - - /// - ///

A four-component, 64-bit signed-integer format that supports 16 bits per channel including alpha.

- ///
- R16G16B16A16_SInt = unchecked((int)14), - - /// - ///

A two-component, 64-bit typeless format that supports 32 bits for the red channel and 32 bits for the green channel.

- ///
- R32G32_Typeless = unchecked((int)15), - - /// - ///

A two-component, 64-bit floating-point format that supports 32 bits for the red channel and 32 bits for the green channel.

- ///
- R32G32_Float = unchecked((int)16), - - /// - ///

A two-component, 64-bit unsigned-integer format that supports 32 bits for the red channel and 32 bits for the green channel.

- ///
- R32G32_UInt = unchecked((int)17), - - /// - ///

A two-component, 64-bit signed-integer format that supports 32 bits for the red channel and 32 bits for the green channel.

- ///
- R32G32_SInt = unchecked((int)18), - - /// - ///

A two-component, 64-bit typeless format that supports 32 bits for the red channel, 8 bits for the green channel, and 24 bits are unused.

- ///
- R32G8X24_Typeless = unchecked((int)19), - - /// - ///

A 32-bit floating-point component, and two unsigned-integer components (with an additional 32 bits). This format supports 32-bit depth, 8-bit stencil, and 24 bits are unused.

- ///
- D32_Float_S8X24_UInt = unchecked((int)20), - - /// - ///

A 32-bit floating-point component, and two typeless components (with an additional 32 bits). This format supports 32-bit red channel, 8 bits are unused, and 24 bits are unused.

- ///
- R32_Float_X8X24_Typeless = unchecked((int)21), - - /// - ///

A 32-bit typeless component, and two unsigned-integer components (with an additional 32 bits). This format has 32 bits unused, 8 bits for green channel, and 24 bits are unused.

- ///
- X32_Typeless_G8X24_UInt = unchecked((int)22), - - /// - ///

A four-component, 32-bit typeless format that supports 10 bits for each color and 2 bits for alpha.

- ///
- R10G10B10A2_Typeless = unchecked((int)23), - - /// - ///

A four-component, 32-bit unsigned-normalized-integer format that supports 10 bits for each color and 2 bits for alpha.

- ///
- R10G10B10A2_UNorm = unchecked((int)24), - - /// - ///

A four-component, 32-bit unsigned-integer format that supports 10 bits for each color and 2 bits for alpha.

- ///
- R10G10B10A2_UInt = unchecked((int)25), - - /// - ///

Three partial-precision floating-point numbers encoded into a single 32-bit value (a variant of s10e5, which is sign bit, 10-bit mantissa, and 5-bit biased (15) exponent). There are no sign bits, and there is a 5-bit biased (15) exponent for each channel, 6-bit mantissa for R and G, and a 5-bit mantissa for B, as shown in the following illustration.

- ///
- R11G11B10_Float = unchecked((int)26), - - /// - ///

A four-component, 32-bit typeless format that supports 8 bits per channel including alpha.

- ///
- R8G8B8A8_Typeless = unchecked((int)27), - - /// - ///

A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits per channel including alpha.

- ///
- R8G8B8A8_UNorm = unchecked((int)28), - - /// - ///

A four-component, 32-bit unsigned-normalized integer sRGB format that supports 8 bits per channel including alpha.

- ///
- R8G8B8A8_UNorm_SRgb = unchecked((int)29), - - /// - ///

A four-component, 32-bit unsigned-integer format that supports 8 bits per channel including alpha.

- ///
- R8G8B8A8_UInt = unchecked((int)30), - - /// - ///

A four-component, 32-bit signed-normalized-integer format that supports 8 bits per channel including alpha.

- ///
- R8G8B8A8_SNorm = unchecked((int)31), - - /// - ///

A four-component, 32-bit signed-integer format that supports 8 bits per channel including alpha.

- ///
- R8G8B8A8_SInt = unchecked((int)32), - - /// - ///

A two-component, 32-bit typeless format that supports 16 bits for the red channel and 16 bits for the green channel.

- ///
- R16G16_Typeless = unchecked((int)33), - - /// - ///

A two-component, 32-bit floating-point format that supports 16 bits for the red channel and 16 bits for the green channel.

- ///
- R16G16_Float = unchecked((int)34), - - /// - ///

A two-component, 32-bit unsigned-normalized-integer format that supports 16 bits each for the green and red channels.

- ///
- R16G16_UNorm = unchecked((int)35), - - /// - ///

A two-component, 32-bit unsigned-integer format that supports 16 bits for the red channel and 16 bits for the green channel.

- ///
- R16G16_UInt = unchecked((int)36), - - /// - ///

A two-component, 32-bit signed-normalized-integer format that supports 16 bits for the red channel and 16 bits for the green channel.

- ///
- R16G16_SNorm = unchecked((int)37), - - /// - ///

A two-component, 32-bit signed-integer format that supports 16 bits for the red channel and 16 bits for the green channel.

- ///
- R16G16_SInt = unchecked((int)38), - - /// - ///

A single-component, 32-bit typeless format that supports 32 bits for the red channel.

- ///
- R32_Typeless = unchecked((int)39), - - /// - ///

A single-component, 32-bit floating-point format that supports 32 bits for depth.

- ///
- D32_Float = unchecked((int)40), - - /// - ///

A single-component, 32-bit floating-point format that supports 32 bits for the red channel.

- ///
- R32_Float = unchecked((int)41), - - /// - ///

A single-component, 32-bit unsigned-integer format that supports 32 bits for the red channel.

- ///
- R32_UInt = unchecked((int)42), - - /// - ///

A single-component, 32-bit signed-integer format that supports 32 bits for the red channel.

- ///
- R32_SInt = unchecked((int)43), - - /// - ///

A two-component, 32-bit typeless format that supports 24 bits for the red channel and 8 bits for the green channel.

- ///
- R24G8_Typeless = unchecked((int)44), - - /// - ///

A 32-bit z-buffer format that supports 24 bits for depth and 8 bits for stencil.

- ///
- D24_UNorm_S8_UInt = unchecked((int)45), - - /// - ///

A 32-bit format, that contains a 24 bit, single-component, unsigned-normalized integer, with an additional typeless 8 bits. This format has 24 bits red channel and 8 bits unused.

- ///
- R24_UNorm_X8_Typeless = unchecked((int)46), - - /// - ///

A 32-bit format, that contains a 24 bit, single-component, typeless format, with an additional 8 bit unsigned integer component. This format has 24 bits unused and 8 bits green channel.

- ///
- X24_Typeless_G8_UInt = unchecked((int)47), - - /// - ///

A two-component, 16-bit typeless format that supports 8 bits for the red channel and 8 bits for the green channel.

- ///
- R8G8_Typeless = unchecked((int)48), - - /// - ///

A two-component, 16-bit unsigned-normalized-integer format that supports 8 bits for the red channel and 8 bits for the green channel.

- ///
- R8G8_UNorm = unchecked((int)49), - - /// - ///

A two-component, 16-bit unsigned-integer format that supports 8 bits for the red channel and 8 bits for the green channel.

- ///
- R8G8_UInt = unchecked((int)50), - - /// - ///

A two-component, 16-bit signed-normalized-integer format that supports 8 bits for the red channel and 8 bits for the green channel.

- ///
- R8G8_SNorm = unchecked((int)51), - - /// - ///

A two-component, 16-bit signed-integer format that supports 8 bits for the red channel and 8 bits for the green channel.

- ///
- R8G8_SInt = unchecked((int)52), - - /// - ///

A single-component, 16-bit typeless format that supports 16 bits for the red channel.

- ///
- R16_Typeless = unchecked((int)53), - - /// - ///

A single-component, 16-bit floating-point format that supports 16 bits for the red channel.

- ///
- R16_Float = unchecked((int)54), - - /// - ///

A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for depth.

- ///
- D16_UNorm = unchecked((int)55), - - /// - ///

A single-component, 16-bit unsigned-normalized-integer format that supports 16 bits for the red channel.

- ///
- R16_UNorm = unchecked((int)56), - - /// - ///

A single-component, 16-bit unsigned-integer format that supports 16 bits for the red channel.

- ///
- R16_UInt = unchecked((int)57), - - /// - ///

A single-component, 16-bit signed-normalized-integer format that supports 16 bits for the red channel.

- ///
- R16_SNorm = unchecked((int)58), - - /// - ///

A single-component, 16-bit signed-integer format that supports 16 bits for the red channel.

- ///
- R16_SInt = unchecked((int)59), - - /// - ///

A single-component, 8-bit typeless format that supports 8 bits for the red channel.

- ///
- R8_Typeless = unchecked((int)60), - - /// - ///

A single-component, 8-bit unsigned-normalized-integer format that supports 8 bits for the red channel.

- ///
- R8_UNorm = unchecked((int)61), - - /// - ///

A single-component, 8-bit unsigned-integer format that supports 8 bits for the red channel.

- ///
- R8_UInt = unchecked((int)62), - - /// - ///

A single-component, 8-bit signed-normalized-integer format that supports 8 bits for the red channel.

- ///
- R8_SNorm = unchecked((int)63), - - /// - ///

A single-component, 8-bit signed-integer format that supports 8 bits for the red channel.

- ///
- R8_SInt = unchecked((int)64), - - /// - ///

A single-component, 8-bit unsigned-normalized-integer format for alpha only.

- ///
- A8_UNorm = unchecked((int)65), - - /// - ///

A single-component, 1-bit unsigned-normalized integer format that supports 1 bit for the red channel. 2.

- ///
- R1_UNorm = unchecked((int)66), - - /// - ///

Three partial-precision floating-point numbers encoded into a single 32-bit value all sharing the same 5-bit exponent (variant of s10e5, which is sign bit, 10-bit mantissa, and 5-bit biased (15) exponent). There is no sign bit, and there is a shared 5-bit biased (15) exponent and a 9-bit mantissa for each channel, as shown in the following illustration. 2.

- ///
- R9G9B9E5_Sharedexp = unchecked((int)67), - - /// - ///

A four-component, 32-bit unsigned-normalized-integer format. This packed RGB format is analogous to the UYVY format. Each 32-bit block describes a pair of pixels: (R8, G8, B8) and (R8, G8, B8) where the R8/B8 values are repeated, and the G8 values are unique to each pixel. 3

- ///
- R8G8_B8G8_UNorm = unchecked((int)68), - - /// - ///

A four-component, 32-bit unsigned-normalized-integer format. This packed RGB format is analogous to the YUY2 format. Each 32-bit block describes a pair of pixels: (R8, G8, B8) and (R8, G8, B8) where the R8/B8 values are repeated, and the G8 values are unique to each pixel. 3

- ///
- G8R8_G8B8_UNorm = unchecked((int)69), - - /// - ///

Four-component typeless block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC1_Typeless = unchecked((int)70), - - /// - ///

Four-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC1_UNorm = unchecked((int)71), - - /// - ///

Four-component block-compression format for sRGB data. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC1_UNorm_SRgb = unchecked((int)72), - - /// - ///

Four-component typeless block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC2_Typeless = unchecked((int)73), - - /// - ///

Four-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC2_UNorm = unchecked((int)74), - - /// - ///

Four-component block-compression format for sRGB data. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC2_UNorm_SRgb = unchecked((int)75), - - /// - ///

Four-component typeless block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC3_Typeless = unchecked((int)76), - - /// - ///

Four-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC3_UNorm = unchecked((int)77), - - /// - ///

Four-component block-compression format for sRGB data. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC3_UNorm_SRgb = unchecked((int)78), - - /// - ///

One-component typeless block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC4_Typeless = unchecked((int)79), - - /// - ///

One-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC4_UNorm = unchecked((int)80), - - /// - ///

One-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC4_SNorm = unchecked((int)81), - - /// - ///

Two-component typeless block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC5_Typeless = unchecked((int)82), - - /// - ///

Two-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC5_UNorm = unchecked((int)83), - - /// - ///

Two-component block-compression format. For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC5_SNorm = unchecked((int)84), - - /// - ///

A three-component, 16-bit unsigned-normalized-integer format that supports 5 bits for blue, 6 bits for green, and 5 bits for red.

- ///
- B5G6R5_UNorm = unchecked((int)85), - - /// - ///

A four-component, 16-bit unsigned-normalized-integer format that supports 5 bits for each color channel and 1-bit alpha.

- ///
- B5G5R5A1_UNorm = unchecked((int)86), - - /// - ///

A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits for each color channel and 8-bit alpha.

- ///
- B8G8R8A8_UNorm = unchecked((int)87), - - /// - ///

A four-component, 32-bit unsigned-normalized-integer format that supports 8 bits for each color channel and 8 bits unused.

- ///
- B8G8R8X8_UNorm = unchecked((int)88), - - /// - ///

A four-component, 32-bit 2.8-biased fixed-point format that supports 10 bits for each color channel and 2-bit alpha.

- ///
- R10G10B10_Xr_Bias_A2_UNorm = unchecked((int)89), - - /// - ///

A four-component, 32-bit typeless format that supports 8 bits for each channel including alpha. 4

- ///
- B8G8R8A8_Typeless = unchecked((int)90), - - /// - ///

A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each channel including alpha. 4

- ///
- B8G8R8A8_UNorm_SRgb = unchecked((int)91), - - /// - ///

A four-component, 32-bit typeless format that supports 8 bits for each color channel, and 8 bits are unused. 4

- ///
- B8G8R8X8_Typeless = unchecked((int)92), - - /// - ///

A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each color channel, and 8 bits are unused. 4

- ///
- B8G8R8X8_UNorm_SRgb = unchecked((int)93), - - /// - ///

A typeless block-compression format. 4 For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC6H_Typeless = unchecked((int)94), - - /// - ///

A block-compression format. 4 For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC6H_Uf16 = unchecked((int)95), - - /// - ///

A block-compression format. 4 For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC6H_Sf16 = unchecked((int)96), - - /// - ///

A typeless block-compression format. 4 For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC7_Typeless = unchecked((int)97), - - /// - ///

A block-compression format. 4 For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC7_UNorm = unchecked((int)98), - - /// - ///

A block-compression format. 4 For information about block-compression formats, see Texture Block Compression in Direct3D 11.

- ///
- BC7_UNorm_SRgb = unchecked((int)99), - - ETC1 = unchecked((int)1088), - ETC2_RGB = unchecked((int)1089), - ETC2_RGBA = unchecked((int)1090), - ETC2_RGB_A1 = unchecked((int)1091), - EAC_R11_Unsigned = unchecked((int)1092), - EAC_R11_Signed = unchecked((int)1093), - EAC_RG11_Unsigned = unchecked((int)1094), - EAC_RG11_Signed = unchecked((int)1095), - ETC2_RGBA_SRgb = unchecked((int)1096), - ETC2_RGB_SRgb = unchecked((int)1097), - } + /// The format is not known. + ///
+ None = 0, + + + /// + /// A four-component, 128-bit format that supports 32 bits per channel including alpha. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R32G32B32A32_Typeless = 1, + + /// + /// A four-component, 128-bit floating-point format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_Float = 2, + + /// + /// A four-component, 128-bit unsigned integer format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_UInt = 3, + + /// + /// A four-component, 128-bit signed integer format that supports 32 bits per channel including alpha. + /// + R32G32B32A32_SInt = 4, + + + /// + /// A three-component, 96-bit format that supports 32 bits per channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R32G32B32_Typeless = 5, + + /// + /// A three-component, 96-bit floating-point format that supports 32 bits per channel. + /// + R32G32B32_Float = 6, + + /// + /// A three-component, 96-bit unsigned integer format that supports 32 bits per channel. + /// + R32G32B32_UInt = 7, + + /// + /// A three-component, 96-bit signed integer format that supports 32 bits per channel. + /// + R32G32B32_SInt = 8, + + + /// + /// A four-component, 64-bit format that supports 16 bits per channel including alpha. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R16G16B16A16_Typeless = 9, + + /// + /// A four-component, 64-bit floating-point format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_Float = 10, + + /// + /// A four-component, 64-bit unsigned normalized integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_UNorm = 11, + + /// + /// A four-component, 64-bit unsigned integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_UInt = 12, + + /// + /// A four-component, 64-bit signed normalized integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_SNorm = 13, + + /// + /// A four-component, 64-bit signed integer format that supports 16 bits per channel including alpha. + /// + R16G16B16A16_SInt = 14, + + + /// + /// A two-component, 64-bit format that supports 32 bits for the red channel and 32 bits for the green channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R32G32_Typeless = 15, + + /// + /// A two-component, 64-bit floating-point format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_Float = 16, + + /// + /// A two-component, 64-bit unsigned integer format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_UInt = 17, + + /// + /// A two-component, 64-bit signed integer format that supports 32 bits for the red channel and 32 bits for the green channel. + /// + R32G32_SInt = 18, + + + /// + /// A two-component, 64-bit format that supports 32 bits for the red channel, 8 bits for the green channel, and 24 bits are unused. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R32G8X24_Typeless = 19, + + /// + /// A composite format consisting of a 32-bit floating-point component intended for depth, plus a 8-bit unsigned-integer component + /// intended for stencil values in an additional 32-bit part where the last 24 bits are unused (for padding). + /// + D32_Float_S8X24_UInt = 20, + + /// + /// A composite format consisting of a 32-bit floating-point component for the red channel, and two typeless components (8-bit and 24-bit respectively) + /// in an additional 32-bit part. + ///
+ /// This is commonly used to create Views for Depth-Stencil Buffers where a shader needs to access the depth. + ///
+ /// This format has typeless components, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R32_Float_X8X24_Typeless = 21, + + /// + /// A composite format consisting of an unused 32-bit typeless component, plus an 8-bit unsigned-integer component + /// in an additional 32-bit part where the last 24 bits are unused (for padding). + ///
+ /// This is commonly used to create Views for Depth-Stencil Buffers where a shader needs to access the stencil values. + ///
+ /// This format has typeless components, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ X32_Typeless_G8X24_UInt = 22, + + + /// + /// A four-component, 32-bit format that supports 10 bits for each color and 2 bits for alpha. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R10G10B10A2_Typeless = 23, + + /// + /// A four-component, 32-bit unsigned normalized integer format that supports 10 bits for each color and 2 bits for alpha. + /// + R10G10B10A2_UNorm = 24, + + /// + /// A four-component, 32-bit unsigned integer format that supports 10 bits for each color and 2 bits for alpha. + /// + R10G10B10A2_UInt = 25, + + /// + /// A three-component, 32-bit partial-precision floating-point format that supports + /// 11 bits for the red and green channels and 10 bits for the blue channel. + ///
+ /// It uses a variant of s10e5 (sign bit, 10-bit mantissa, and 5-bit biased (15) exponent), but there are no sign bits, and there is a 5-bit biased (15) exponent + /// for each channel, 6-bit mantissa for R and G, and a 5-bit mantissa for B. + ///
+ R11G11B10_Float = 26, + + + /// + /// A four-component, 32-bit format that supports 8 bits per channel including alpha. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R8G8B8A8_Typeless = 27, + + /// + /// A four-component, 32-bit unsigned normalized integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_UNorm = 28, + + /// + /// A four-component, 32-bit unsigned normalized integer format that supports 8 bits per channel including alpha, + /// where the data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + /// + R8G8B8A8_UNorm_SRgb = 29, + + /// + /// A four-component, 32-bit unsigned integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_UInt = 30, + + /// + /// A four-component, 32-bit signed normalized integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_SNorm = 31, + + /// + /// A four-component, 32-bit signed integer format that supports 8 bits per channel including alpha. + /// + R8G8B8A8_SInt = 32, + + + /// + /// A two-component, 32-bit format that supports 16 bits for the red channel and 16 bits for the green channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R16G16_Typeless = 33, + + /// + /// A two-component, 32-bit floating-point format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_Float = 34, + + /// + /// A two-component, 32-bit unsigned normalized integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_UNorm = 35, + + /// + /// A two-component, 32-bit unsigned integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_UInt = 36, + + /// + /// A two-component, 32-bit signed normalized integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_SNorm = 37, + + /// + /// A two-component, 32-bit signed integer format that supports 16 bits for the red channel and 16 bits for the green channel. + /// + R16G16_SInt = 38, + + + /// + /// A single-component, 32-bit format that supports 32 bits for the red channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R32_Typeless = 39, + + /// + /// A single-component, 32-bit floating-point format that supports 32 bits for depth. + /// + D32_Float = 40, + + /// + /// A single-component, 32-bit floating-point format that supports 32 bits for the red channel. + /// + R32_Float = 41, + + /// + /// A single-component, 32-bit unsigned integer format that supports 32 bits for the red channel. + /// + R32_UInt = 42, + + /// + /// A single-component, 32-bit signed integer format that supports 32 bits for the red channel. + /// + R32_SInt = 43, + + + /// + /// A two-component, 32-bit format that supports 24 bits for the red channel and 8 bits for the green channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R24G8_Typeless = 44, + + /// + /// A two-component, 32-bit format consisting of a 24-bit unsigned normalized integer component for depth + /// and a 8-bit unsigned integer component for stencil. + /// + D24_UNorm_S8_UInt = 45, + + /// + /// A two-component, 32-bit format consisting of a 24-bit unsigned normalized integer red component + /// and a 8-bit typeless component. + ///
+ /// This format has a typeless component, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R24_UNorm_X8_Typeless = 46, + + /// + /// A two-component, 32-bit format consisting of a 24-bit typeless component + /// and a 8-bit unsigned integer green component. + ///
+ /// This format has a typeless component, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ X24_Typeless_G8_UInt = 47, + + + /// + /// A two-component, 16-bit format that supports 8 bits for the red channel and 8 bits for the green channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R8G8_Typeless = 48, + + /// + /// A two-component, 16-bit unsigned normalized integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_UNorm = 49, + + /// + /// A two-component, 16-bit unsigned integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_UInt = 50, + + /// + /// A two-component, 16-bit signed normalized integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_SNorm = 51, + + /// + /// A two-component, 16-bit signed integer format that supports 8 bits for the red channel and 8 bits for the green channel. + /// + R8G8_SInt = 52, + + + /// + /// A single-component, 16-bit format that supports 16 bits for the red channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R16_Typeless = 53, + + /// + /// A single-component, 16-bit floating-point format that supports 16 bits for the red channel. + /// + R16_Float = 54, + + /// + /// A single-component, 16-bit unsigned normalized integer format that supports 16 bits for depth. + /// + D16_UNorm = 55, + + /// + /// A single-component, 16-bit unsigned normalized integer format that supports 16 bits for the red channel. + /// + R16_UNorm = 56, + + /// + /// A single-component, 16-bit unsigned integer format that supports 16 bits for the red channel. + /// + R16_UInt = 57, + + /// + /// A single-component, 16-bit signed normalized integer format that supports 16 bits for the red channel. + /// + R16_SNorm = 58, + + /// + /// A single-component, 16-bit signed integer format that supports 16 bits for the red channel. + /// + R16_SInt = 59, + + + /// + /// A single-component, 8-bit format that supports 8 bits for the red channel. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ R8_Typeless = 60, + + /// + /// A single-component, 8-bit unsigned normalized integer format that supports 8 bits for the red channel. + /// + R8_UNorm = 61, + + /// + /// A single-component, 8-bit unsigned integer format that supports 8 bits for the red channel. + /// + R8_UInt = 62, + + /// + /// A single-component, 8-bit signed normalized integer format that supports 8 bits for the red channel. + /// + R8_SNorm = 63, + + /// + /// A single-component, 8-bit signed integer format that supports 8 bits for the red channel. + /// + R8_SInt = 64, + + /// + /// A single-component, 8-bit unsigned normalized integer format for alpha only. + /// + A8_UNorm = 65, + + + /// + /// A single-component, 1-bit unsigned normalized integer format that supports 1 bit for the red channel. + /// + R1_UNorm = 66, + + + /// + /// A three-component, 32-bit partial-precision floating-point format that supports + /// 9 bits per channel with the same 5-bit shared exponent. + ///
+ /// It uses a variant of s10e5 (sign bit, 10-bit mantissa, and 5-bit biased (15) exponent), but there are no sign bits, and the exponent is shared. + ///
+ R9G9B9E5_Sharedexp = 67, + + /// + /// A four-component, 32-bit unsigned normalized integer format packed in a form analogous to the UYVY format. + /// Each 32-bit block describes a pair of pixels (RGB, RGB) with 8-bit RGB components where the R/B values are repeated, + /// and the G values are unique to each pixel. + /// + R8G8_B8G8_UNorm = 68, + + /// + /// A four-component, 32-bit unsigned normalized integer format packed in a form analogous to the YUY2 format. + /// Each 32-bit block describes a pair of pixels (RGB, RGB) with 8-bit RGB components where the R/B values are repeated, + /// and the G values are unique to each pixel. + /// + G8R8_G8B8_UNorm = 69, + + + /// + /// A four-component, 64-bit block-compression format using the BC1 encoding, where + /// the alpha channel is optionally encoded in 1 bit (either fully opaque or fully transparent), and the RGB color is encoded + /// in 2 bits per pixel to select from a 4-color (or 3-color + transparent) color palette. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC1 format is tipically used for diffuse maps that do not require alpha transparency or where the alpha channel is not important. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC1_Typeless = 70, + + /// + /// A four-component, 64-bit unsigned normalized integer block-compression format + /// using the BC1 encoding, where the alpha channel is optionally encoded in 1 bit (either fully opaque or fully transparent), + /// and the RGB color is encoded in 2 bits per pixel to select from a 4-color (or 3-color + transparent) color palette. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC1 format is tipically used for diffuse maps that do not require alpha transparency or where the alpha channel is not important. + ///
+ BC1_UNorm = 71, + + /// + /// A four-component, 64-bit unsigned normalized integer block-compression format for sRGB data + /// using the BC1 encoding, where the alpha channel is optionally encoded in 1 bit (either fully opaque or fully transparent), + /// and the RGB color is encoded in 2 bits per pixel to select from a 4-color (or 3-color + transparent) color palette. + ///
+ /// The data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC1 format is tipically used for diffuse maps that do not require alpha transparency or where the alpha channel is not important. + ///
+ BC1_UNorm_SRgb = 72, + + + /// + /// A four-component, 128-bit block-compression format using the BC2 encoding, + /// where the alpha channel is encoded in 4 bits (for 16 levels of transparency, quantized, not interpolated), + /// and the RGB color is encoded in 2 bits per pixel to select from a 4-color color palette. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC2 format is tipically used for UI, decals, or sharp masks, although it is now considered somewhat outdated. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC2_Typeless = 73, + + /// + /// A four-component, 128-bit unsigned normalized integer block-compression format + /// using the BC2 encoding, where the alpha channel is encoded in 4 bits (for 16 levels of transparency, quantized, not interpolated), + /// and the RGB color is encoded in 2 bits per pixel to select from a 4-color color palette. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC2 format is tipically used for UI, decals, or sharp masks, although it is now considered somewhat outdated. + ///
+ BC2_UNorm = 74, + + /// + /// A four-component, 128-bit unsigned normalized integer block-compression format for sRGB data + /// using the BC2 encoding, where the alpha channel is encoded in 4 bits (for 16 levels of transparency, quantized, not interpolated), + /// and the RGB color is encoded in 2 bits per pixel to select from a 4-color color palette. + ///
+ /// The data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC2 format is tipically used for UI, decals, or sharp masks, although it is now considered somewhat outdated. + ///
+ BC2_UNorm_SRgb = 75, + + + /// + /// A four-component, 128-bit block-compression format using the BC3 encoding, + /// where the alpha channel is encoded as two 8-bit alpha endpoints and 3 bits per pixel to select + /// from 6 interpolated alpha values, and the RGB color is encoded in 2 bits per pixel to select from a 4-color color palette. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC3 format is tipically used for smooth and soft transparency, like foliage, shadows, hair, or smoke. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC3_Typeless = 76, + + /// + /// A four-component, 128-bit unsigned normalized integer block-compression format + /// using the BC3 encoding, where the alpha channel is encoded as two 8-bit alpha endpoints and 3 bits per pixel to select + /// from 6 interpolated alpha values, and the RGB color is encoded in 2 bits per pixel to select from a 4-color color palette. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC3 format is tipically used for smooth and soft transparency, like foliage, shadows, hair, or smoke. + ///
+ BC3_UNorm = 77, + + /// + /// A four-component, 128-bit unsigned normalized integer block-compression format for sRGB data + /// using the BC3 encoding, where the alpha channel is encoded as two 8-bit alpha endpoints and 3 bits per pixel to select + /// from 6 interpolated alpha values, and the RGB color is encoded in 2 bits per pixel to select from a 4-color color palette. + ///
+ /// The data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC3 format is tipically used for smooth and soft transparency, like foliage, shadows, hair, or smoke. + ///
+ BC3_UNorm_SRgb = 78, + + + /// + /// A single-component, 64-bit block-compression format using the BC4 encoding, + /// where the data is encoded as two 8-bit endpoints and 3 bits per pixel to select from 6 interpolated values. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC4 format is tipically used for grayscale Textures, smooth masks, or heightmaps. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC4_Typeless = 79, + + /// + /// A single-component, 64-bit unsigned normalized integer block-compression format + /// using the BC4 encoding, where the data is encoded as two 8-bit endpoints and 3 bits per pixel to select + /// from 6 interpolated values. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC4 format is tipically used for grayscale Textures, smooth masks, or heightmaps. + ///
+ BC4_UNorm = 80, + + /// + /// A single-component, 64-bit signed normalized integer block-compression format + /// using the BC4 encoding, where the data is encoded as two 8-bit endpoints and 3 bits per pixel to select + /// from 6 interpolated values. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC4 format is tipically used for grayscale Textures, smooth masks, or heightmaps. + ///
+ BC4_SNorm = 81, + + + /// + /// A two-component, 128-bit block-compression format using the BC5 encoding, + /// where for each channel the data is encoded as two 8-bit endpoints and 3 bits per pixel to select from 6 interpolated values. + /// This is essentially a BC4 format for each channel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC5 format is tipically used for dual-channel data like normal maps (X and Y) or vector fields. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC5_Typeless = 82, + + /// + /// A two-component, 128-bit unsigned normalized integer block-compression format + /// using the BC5 encoding, where for each channel the data is encoded as two 8-bit endpoints and 3 bits per pixel + /// to select from 6 interpolated values. This is essentially a BC4 format for each channel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC5 format is tipically used for dual-channel data like normal maps (X and Y) or vector fields. + ///
+ BC5_UNorm = 83, + + /// + /// A two-component, 128-bit signed normalized integer block-compression format + /// using the BC5 encoding, where for each channel the data is encoded as two 8-bit endpoints and 3 bits per pixel + /// to select from 6 interpolated values. This is essentially a BC4 format for each channel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC5 format is tipically used for dual-channel data like normal maps (X and Y) or vector fields. + ///
+ BC5_SNorm = 84, + + + /// + /// A three-component, 16-bit unsigned normalized integer format that supports 5 bits for blue, 6 bits for green, and 5 bits for red. + /// + B5G6R5_UNorm = 85, + + /// + /// A four-component, 16-bit unsigned normalized integer format that supports 5 bits for each color channel and 1-bit alpha. + /// + B5G5R5A1_UNorm = 86, + + + /// + /// A four-component, 32-bit unsigned normalized integer format that supports 8 bits for each color channel and 8-bit alpha. + /// + B8G8R8A8_UNorm = 87, + + /// + /// A four-component, 32-bit unsigned normalized integer format that supports 8 bits for each color channel and a remaining unused 8-bit part. + /// + B8G8R8X8_UNorm = 88, + + + /// + /// A four-component, 32-bit format consisting of 10 bits for each of the red, green, and blue channels encoded as + /// 2.8-biased fixed-point numbers and 2-bit alpha channel. + /// The XR bias applies a fixed scale and offset to the red channel to better represent HDR luminance. This is useful when + /// tone mapping has already been applied and the image is ready for display. + ///
+ /// This format is intended for use as a presentation format, particularly for High Dynamic Range (HDR) content. It + /// is read-only for shaders and not renderable directly. Instead, it is typically converted from a compatible format (like ). + ///
+ R10G10B10_Xr_Bias_A2_UNorm = 89, + + + /// + /// A four-component, 32-bit format that supports 8 bits for each color channel and 8-bit alpha. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ B8G8R8A8_Typeless = 90, + + /// + /// A four-component, 32-bit unsigned normalized integer format that supports 8 bits for each color channel and 8-bit alpha, + /// where the data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + /// + B8G8R8A8_UNorm_SRgb = 91, + + /// + /// A four-component, 32-bit format that supports 8 bits for each color channel, and 8 bits are unused. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ B8G8R8X8_Typeless = 92, + + /// + /// A four-component, 32-bit unsigned-normalized standard RGB format that supports 8 bits for each color channel, and 8 bits are unused. + /// , + /// where the data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + /// + B8G8R8X8_UNorm_SRgb = 93, + + + /// + /// A three-component, 128-bit block-compression format for HDR data + /// using the BC6H encoding, where the color channels are encoded as 16-bit values and + /// selected and interpolated according to the BC6H algorithm. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC6H format is best used for HDR environment maps, lightprobes, or any Texture with values beyond the [0,1] range. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC6H_Typeless = 94, + + /// + /// A three-component, 128-bit unsigned floating-point block-compression format for positive-only HDR data + /// using the BC6H encoding, where the color channels are encoded as 16-bit values and + /// selected and interpolated according to the BC6H algorithm. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC6H format is best used for HDR environment maps, lightprobes, or any Texture with values beyond the [0,1] range. + ///
+ BC6H_Uf16 = 95, + + /// + /// A three-component, 128-bit signed floating-point block-compression format for full-range HDR data + /// using the BC6H encoding, where the color channels are encoded as 16-bit values and + /// selected and interpolated according to the BC6H algorithm. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC6H format is best used for HDR environment maps, lightprobes, or any Texture with values beyond the [0,1] range. + ///
+ BC6H_Sf16 = 96, + + + /// + /// A three- or four-component, 128-bit block-compression format using the BC7 encoding, + /// where it can encode the RGB channels and an optional 1-8-bit alpha channel according to the BC7 algorithm. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC7 format is best for high-quality color Textures, like albedo maps, UI, or detailed color surfaces. + ///
+ /// This format is typeless, i.e. it just specifies the memory layout, but can be used for different types of data, + /// such as floating-point, unsigned-integer, or signed-integer. + ///
+ BC7_Typeless = 97, + + /// + /// A three- or four-component, 128-bit unsigned normalized integer block-compression format + /// using the BC7 encoding, where it can encode the RGB channels and an optional 1-8-bit alpha channel according to the BC7 algorithm. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC7 format is best for high-quality color Textures, like albedo maps, UI, or detailed color surfaces. + ///
+ BC7_UNorm = 98, + + /// + /// A three- or four-component, 128-bit unsigned normalized integer block-compression format for sRGB data + /// using the BC7 encoding, where it can encode the RGB channels and an optional 1-8-bit alpha channel according to the BC7 algorithm. + ///
+ /// The data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// The BC7 format is best for high-quality color Textures, like albedo maps, UI, or detailed color surfaces. + ///
+ BC7_UNorm_SRgb = 99, + + + /// + /// A three-component, 64-bit block-compression format using the ETC1 encoding, + /// where the RGB color is encoded in 4 bits per pixel. Does not support alpha. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Commonly used for opaque textures on mobile devices. + ///
+ ETC1 = 1088, + + /// + /// A three-component, 64-bit block-compression format using the ETC2 RGB encoding, + /// where the RGB color is encoded in 4 bits per pixel. Does not support alpha. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Widely supported on modern mobile and embedded GPUs for general color data. + ///
+ ETC2_RGB = 1089, + + /// + /// A four-component, 128-bit block-compression format using the ETC2 RGBA encoding, + /// where the RGB color is encoded in 4 bits per pixel and the alpha channel is encoded in 4 bits per pixel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Used for Textures requiring full alpha support on mobile and embedded devices. + ///
+ ETC2_RGBA = 1090, + + /// + /// A four-component, 64-bit block-compression format using the ETC2 RGB+A1 encoding, + /// where the RGB color is encoded in 4 bits per pixel and the alpha channel is encoded as a single bit (1-bit alpha mask). + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Suitable for Textures with simple transparency (fully opaque or fully transparent pixels). + ///
+ ETC2_RGB_A1 = 1091, + + /// + /// A single-component, 64-bit block-compression format using the EAC R11 unsigned encoding, + /// where the red channel is encoded as an unsigned 11-bit value per pixel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Typically used for single-channel data such as heightmaps or masks. + ///
+ EAC_R11_Unsigned = 1092, + + /// + /// A single-component, 64-bit block-compression format using the EAC R11 signed encoding, + /// where the red channel is encoded as a signed 11-bit value per pixel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Typically used for single-channel signed data such as normal maps or vector fields (reconstructing the second component). + ///
+ EAC_R11_Signed = 1093, + + /// + /// A two-component, 128-bit block-compression format using the EAC RG11 unsigned encoding, + /// where the red and green channels are encoded as unsigned 11-bit values per pixel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Used for dual-channel data such as normal maps or vector fields. + ///
+ EAC_RG11_Unsigned = 1094, + + /// + /// A two-component, 128-bit block-compression format using the EAC RG11 signed encoding, + /// where the red and green channels are encoded as signed 11-bit values per pixel. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Used for dual-channel signed data such as normal maps or vector fields. + ///
+ EAC_RG11_Signed = 1095, + + /// + /// A four-component, 128-bit block-compression format using the ETC2 RGBA encoding for sRGB data, + /// where the RGB color is encoded in 4 bits per pixel and the alpha channel is encoded in 4 bits per pixel. + ///
+ /// The data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Tipically used for color textures with transparency on mobile and embedded devices. + ///
+ ETC2_RGBA_SRgb = 1096, + + /// + /// A three-component, 64-bit block-compression format using the ETC2 RGB encoding for sRGB data, + /// where the RGB color is encoded in 4 bits per pixel. + ///
+ /// The data is stored in sRGB color space, and the GPU will automatically convert it to linear space when sampling in a shader. + ///
+ /// The block-compression formats operate only on 4x4 blocks, so Textures using this format must have dimensions that are multiples of 4. + ///
+ /// Tipically used for color textures without alpha on mobile and embedded devices. + ///
+ ETC2_RGB_SRgb = 1097 } diff --git a/sources/engine/Stride/Graphics/PixelFormatExtensions.cs b/sources/engine/Stride/Graphics/PixelFormatExtensions.cs index cf531a4294..cafb4a154f 100644 --- a/sources/engine/Stride/Graphics/PixelFormatExtensions.cs +++ b/sources/engine/Stride/Graphics/PixelFormatExtensions.cs @@ -24,38 +24,33 @@ using System; using System.Collections.Generic; -using Stride.Core; +using static Stride.Graphics.PixelFormat; namespace Stride.Graphics { /// - /// Extensions to . + /// Provides extensions for to help in conversions between formats, querying format + /// information, calculating sizes, pitches, etc. /// public static class PixelFormatExtensions { - private struct PixelFormatSizeInfo - { - public byte IsCompressed; - public byte BlockWidth; - public byte BlockHeight; - public byte BlockSize; - } + private readonly record struct PixelFormatSizeInfo(byte IsCompressed, byte BlockWidth, byte BlockHeight, byte BlockSize); private static readonly PixelFormatSizeInfo[] sizeInfos = new PixelFormatSizeInfo[256]; private static readonly bool[] srgbFormats = new bool[256]; private static readonly bool[] hdrFormats = new bool[256]; private static readonly bool[] alpha32Formats = new bool[256]; private static readonly bool[] typelessFormats = new bool[256]; - private static readonly Dictionary sRgbConvertion; + private static readonly Dictionary sRgbConversion; private static int GetIndex(PixelFormat format) { // DirectX official pixel formats (0..115 use 0..127 in the arrays) // Custom pixel formats (1024..1151? use 128..255 in the array) - if ((int)format >= 1024) - return (int)format - 1024 + 128; + if ((int) format >= 1024) + return (int) format - 1024 + 128; - return (int)format; + return (int) format; } public static int BlockSize(this PixelFormat format) @@ -81,7 +76,7 @@ public static int BlockHeight(this PixelFormat format) public static int SizeInBytes(this PixelFormat format) { var sizeInfo = sizeInfos[GetIndex(format)]; - return (int)sizeInfo.BlockSize / ((int)sizeInfo.BlockWidth * (int)sizeInfo.BlockHeight); + return sizeInfo.BlockSize / (sizeInfo.BlockWidth * sizeInfo.BlockHeight); } /// @@ -92,7 +87,7 @@ public static int SizeInBytes(this PixelFormat format) public static int SizeInBits(this PixelFormat format) { var sizeInfo = sizeInfos[GetIndex(format)]; - return (int)sizeInfo.BlockSize * 8 / ((int)sizeInfo.BlockWidth * (int)sizeInfo.BlockHeight); ; + return sizeInfo.BlockSize * 8 / (sizeInfo.BlockWidth * sizeInfo.BlockHeight); } /// @@ -104,69 +99,69 @@ public static int AlphaSizeInBits(this PixelFormat format) { switch (format) { - case PixelFormat.R32G32B32A32_Typeless: - case PixelFormat.R32G32B32A32_Float: - case PixelFormat.R32G32B32A32_UInt: - case PixelFormat.R32G32B32A32_SInt: + case R32G32B32A32_Typeless: + case R32G32B32A32_Float: + case R32G32B32A32_UInt: + case R32G32B32A32_SInt: return 32; - case PixelFormat.R16G16B16A16_Typeless: - case PixelFormat.R16G16B16A16_Float: - case PixelFormat.R16G16B16A16_UNorm: - case PixelFormat.R16G16B16A16_UInt: - case PixelFormat.R16G16B16A16_SNorm: - case PixelFormat.R16G16B16A16_SInt: + case R16G16B16A16_Typeless: + case R16G16B16A16_Float: + case R16G16B16A16_UNorm: + case R16G16B16A16_UInt: + case R16G16B16A16_SNorm: + case R16G16B16A16_SInt: return 16; - case PixelFormat.R10G10B10A2_Typeless: - case PixelFormat.R10G10B10A2_UNorm: - case PixelFormat.R10G10B10A2_UInt: - case PixelFormat.R10G10B10_Xr_Bias_A2_UNorm: + case R10G10B10A2_Typeless: + case R10G10B10A2_UNorm: + case R10G10B10A2_UInt: + case R10G10B10_Xr_Bias_A2_UNorm: return 2; - case PixelFormat.R8G8B8A8_Typeless: - case PixelFormat.R8G8B8A8_UNorm: - case PixelFormat.R8G8B8A8_UNorm_SRgb: - case PixelFormat.R8G8B8A8_UInt: - case PixelFormat.R8G8B8A8_SNorm: - case PixelFormat.R8G8B8A8_SInt: - case PixelFormat.B8G8R8A8_UNorm: - case PixelFormat.B8G8R8A8_Typeless: - case PixelFormat.B8G8R8A8_UNorm_SRgb: - case PixelFormat.A8_UNorm: + case R8G8B8A8_Typeless: + case R8G8B8A8_UNorm: + case R8G8B8A8_UNorm_SRgb: + case R8G8B8A8_UInt: + case R8G8B8A8_SNorm: + case R8G8B8A8_SInt: + case B8G8R8A8_UNorm: + case B8G8R8A8_Typeless: + case B8G8R8A8_UNorm_SRgb: + case A8_UNorm: return 8; - case (PixelFormat)115: // DXGI_FORMAT_B4G4R4A4_UNORM + case (PixelFormat) 115: // DXGI_FORMAT_B4G4R4A4_UNORM return 4; - case PixelFormat.B5G5R5A1_UNorm: + case B5G5R5A1_UNorm: return 1; - case PixelFormat.BC1_Typeless: - case PixelFormat.BC1_UNorm: - case PixelFormat.BC1_UNorm_SRgb: + case BC1_Typeless: + case BC1_UNorm: + case BC1_UNorm_SRgb: return 1; // or 0 - case PixelFormat.BC2_Typeless: - case PixelFormat.BC2_UNorm: - case PixelFormat.BC2_UNorm_SRgb: + case BC2_Typeless: + case BC2_UNorm: + case BC2_UNorm_SRgb: return 4; - case PixelFormat.BC3_Typeless: - case PixelFormat.BC3_UNorm: - case PixelFormat.BC3_UNorm_SRgb: + case BC3_Typeless: + case BC3_UNorm: + case BC3_UNorm_SRgb: return 8; - case PixelFormat.BC7_Typeless: - case PixelFormat.BC7_UNorm: - case PixelFormat.BC7_UNorm_SRgb: + case BC7_Typeless: + case BC7_UNorm: + case BC7_UNorm_SRgb: return 8; // or 0 - case PixelFormat.ETC2_RGBA: - case PixelFormat.ETC2_RGBA_SRgb: + case ETC2_RGBA: + case ETC2_RGBA_SRgb: return 8; - case PixelFormat.ETC2_RGB_A1: + case ETC2_RGB_A1: return 1; } return 0; @@ -179,78 +174,78 @@ public static int AlphaSizeInBits(this PixelFormat format) /// True if the is valid. public static bool IsValid(this PixelFormat format) { - return ((int)format >= 1 && (int)format <= 115) // DirectX formats - || ((int)format >= 1088 && (int)format <= 1097); // ETC formats + return ((int) format >= 1 && (int) format <= 115) // DirectX formats + || ((int) format >= 1088 && (int) format <= 1097); // ETC formats } /// /// Returns true if the is a compressed format. /// - /// The format to check for compressed. + /// The format to check for compressed. /// True if the is a compressed format - public static bool IsCompressed(this PixelFormat fmt) + public static bool IsCompressed(this PixelFormat format) { - return sizeInfos[GetIndex(fmt)].IsCompressed == 1; + return sizeInfos[GetIndex(format)].IsCompressed == 1; } /// - /// Returns true if the is an uncompressed 32 bit color with an Alpha channel. + /// Returns true if the is an uncompressed 32-bit color with an Alpha channel. /// - /// The format to check for an uncompressed 32 bit color with an Alpha channel. - /// True if the is an uncompressed 32 bit color with an Alpha channel - public static bool HasAlpha32Bits(this PixelFormat fmt) + /// The format to check for an uncompressed 32-bit color with an Alpha channel. + /// True if the is an uncompressed 32-bit color with an Alpha channel + public static bool HasAlpha32Bits(this PixelFormat format) { - return alpha32Formats[GetIndex(fmt)]; + return alpha32Formats[GetIndex(format)]; } /// /// Returns true if the has an Alpha channel. /// - /// The format to check for an Alpha channel. + /// The format to check for an Alpha channel. /// True if the has an Alpha channel - public static bool HasAlpha(this PixelFormat fmt) + public static bool HasAlpha(this PixelFormat format) { - return AlphaSizeInBits(fmt) != 0; + return AlphaSizeInBits(format) != 0; } /// /// Determines whether the specified is packed. /// - /// The DXGI Format. + /// The DXGI Format. /// true if the specified is packed; otherwise, false. - public static bool IsPacked(this PixelFormat fmt) + public static bool IsPacked(this PixelFormat format) { - return ((fmt == PixelFormat.R8G8_B8G8_UNorm) || (fmt == PixelFormat.G8R8_G8B8_UNorm)); + return format is R8G8_B8G8_UNorm or G8R8_G8B8_UNorm; } /// /// Determines whether the specified is video. /// - /// The . + /// The . /// true if the specified is video; otherwise, false. - public static bool IsVideo(this PixelFormat fmt) + public static bool IsVideo(this PixelFormat format) { #if DIRECTX11_1 - switch ( fmt ) + switch (format) { - case Format.AYUV: - case Format.Y410: - case Format.Y416: - case Format.NV12: - case Format.P010: - case Format.P016: - case Format.YUY2: - case Format.Y210: - case Format.Y216: - case Format.NV11: + case PixelFormat.AYUV: + case PixelFormat.Y410: + case PixelFormat.Y416: + case PixelFormat.NV12: + case PixelFormat.P010: + case PixelFormat.P016: + case PixelFormat.YUY2: + case PixelFormat.Y210: + case PixelFormat.Y216: + case PixelFormat.NV11: // These video formats can be used with the 3D pipeline through special view mappings return true; - case Format.Opaque420: - case Format.AI44: - case Format.IA44: - case Format.P8: - case Format.A8P8: + case PixelFormat.Opaque420: + case PixelFormat.AI44: + case PixelFormat.IA44: + case PixelFormat.P8: + case PixelFormat.A8P8: // These are limited use video formats not usable in any way by the 3D pipeline return true; @@ -266,36 +261,36 @@ public static bool IsVideo(this PixelFormat fmt) /// /// Determines whether the specified is a SRGB format. /// - /// The . + /// The . /// true if the specified is a SRGB format; otherwise, false. - public static bool IsSRgb(this PixelFormat fmt) + public static bool IsSRgb(this PixelFormat format) { - return srgbFormats[GetIndex(fmt)]; + return srgbFormats[GetIndex(format)]; } /// /// Determines whether the specified is HDR (either 16 or 32bits float) /// - /// The FMT. + /// The FMT. /// true if the specified pixel format is HDR (floating point); otherwise, false. - public static bool IsHDR(this PixelFormat fmt) + public static bool IsHDR(this PixelFormat format) { - return hdrFormats[GetIndex(fmt)]; + return hdrFormats[GetIndex(format)]; } /// /// Determines whether the specified is typeless. /// - /// The . + /// The . /// true if the specified is typeless; otherwise, false. - public static bool IsTypeless(this PixelFormat fmt) + public static bool IsTypeless(this PixelFormat format) { - return typelessFormats[GetIndex(fmt)]; + return typelessFormats[GetIndex(format)]; } - public static void ComputePitch(this PixelFormat fmt, int width, int height, out int rowPitch, out int slicePitch) + public static void ComputePitch(this PixelFormat format, int width, int height, out int rowPitch, out int slicePitch) { - var sizeInfo = sizeInfos[GetIndex(fmt)]; + var sizeInfo = sizeInfos[GetIndex(format)]; rowPitch = ((width + sizeInfo.BlockWidth - 1) / sizeInfo.BlockWidth) * sizeInfo.BlockSize; slicePitch = rowPitch * ((height + sizeInfo.BlockHeight - 1) / sizeInfo.BlockHeight); @@ -309,9 +304,9 @@ public static void ComputePitch(this PixelFormat fmt, int width, int height, out public static bool HasSRgbEquivalent(this PixelFormat format) { if (format.IsSRgb()) - throw new ArgumentException("The '{0}' format is already an sRGB format".ToFormat(format)); + throw new ArgumentException($"'{format}' is already an sRGB pixel format", nameof(format)); - return sRgbConvertion.ContainsKey(format); + return sRgbConversion.ContainsKey(format); } /// @@ -322,9 +317,9 @@ public static bool HasSRgbEquivalent(this PixelFormat format) public static bool HasNonSRgbEquivalent(this PixelFormat format) { if (!format.IsSRgb()) - throw new ArgumentException("The provided format is not a sRGB format"); + throw new ArgumentException($"'{format}' is not a sRGB format", nameof(format)); - return sRgbConvertion.ContainsKey(format); + return sRgbConversion.ContainsKey(format); } /// @@ -336,10 +331,10 @@ public static bool HasNonSRgbEquivalent(this PixelFormat format) /// public static PixelFormat ToSRgb(this PixelFormat format) { - if (format.IsSRgb() || !sRgbConvertion.ContainsKey(format)) + if (format.IsSRgb() || !sRgbConversion.TryGetValue(format, out var srgbFormat)) return format; - return sRgbConvertion[format]; + return srgbFormat; } /// @@ -351,76 +346,78 @@ public static PixelFormat ToSRgb(this PixelFormat format) /// public static PixelFormat ToNonSRgb(this PixelFormat format) { - if (!format.IsSRgb() || !sRgbConvertion.ContainsKey(format)) + if (!format.IsSRgb() || !sRgbConversion.TryGetValue(format, out var nonSrgbFormat)) return format; - return sRgbConvertion[format]; + return nonSrgbFormat; } /// - /// Determines whether the specified format is in RGBA order. + /// Determines whether the specified format has components in the RGBA order. /// /// The format. /// - /// true if the specified format is in RGBA order; otherwise, false. + /// true if the specified format has components in the RGBA order; otherwise, false. /// - public static bool IsRGBAOrder(this PixelFormat format) + public static bool IsRgbaOrder(this PixelFormat format) { switch (format) { - case PixelFormat.R32G32B32A32_Typeless: - case PixelFormat.R32G32B32A32_Float: - case PixelFormat.R32G32B32A32_UInt: - case PixelFormat.R32G32B32A32_SInt: - case PixelFormat.R32G32B32_Typeless: - case PixelFormat.R32G32B32_Float: - case PixelFormat.R32G32B32_UInt: - case PixelFormat.R32G32B32_SInt: - case PixelFormat.R16G16B16A16_Typeless: - case PixelFormat.R16G16B16A16_Float: - case PixelFormat.R16G16B16A16_UNorm: - case PixelFormat.R16G16B16A16_UInt: - case PixelFormat.R16G16B16A16_SNorm: - case PixelFormat.R16G16B16A16_SInt: - case PixelFormat.R32G32_Typeless: - case PixelFormat.R32G32_Float: - case PixelFormat.R32G32_UInt: - case PixelFormat.R32G32_SInt: - case PixelFormat.R32G8X24_Typeless: - case PixelFormat.R10G10B10A2_Typeless: - case PixelFormat.R10G10B10A2_UNorm: - case PixelFormat.R10G10B10A2_UInt: - case PixelFormat.R11G11B10_Float: - case PixelFormat.R8G8B8A8_Typeless: - case PixelFormat.R8G8B8A8_UNorm: - case PixelFormat.R8G8B8A8_UNorm_SRgb: - case PixelFormat.R8G8B8A8_UInt: - case PixelFormat.R8G8B8A8_SNorm: - case PixelFormat.R8G8B8A8_SInt: + case R32G32B32A32_Typeless: + case R32G32B32A32_Float: + case R32G32B32A32_UInt: + case R32G32B32A32_SInt: + case R32G32B32_Typeless: + case R32G32B32_Float: + case R32G32B32_UInt: + case R32G32B32_SInt: + case R16G16B16A16_Typeless: + case R16G16B16A16_Float: + case R16G16B16A16_UNorm: + case R16G16B16A16_UInt: + case R16G16B16A16_SNorm: + case R16G16B16A16_SInt: + case R32G32_Typeless: + case R32G32_Float: + case R32G32_UInt: + case R32G32_SInt: + case R32G8X24_Typeless: + case R10G10B10A2_Typeless: + case R10G10B10A2_UNorm: + case R10G10B10A2_UInt: + case R11G11B10_Float: + case R8G8B8A8_Typeless: + case R8G8B8A8_UNorm: + case R8G8B8A8_UNorm_SRgb: + case R8G8B8A8_UInt: + case R8G8B8A8_SNorm: + case R8G8B8A8_SInt: return true; + default: return false; } } /// - /// Determines whether the specified format is in BGRA order. + /// Determines whether the specified format has components in the BGRA order. /// /// The format. /// - /// true if the specified format is in BGRA order; otherwise, false. + /// true if the specified format has components in the BGRA order; otherwise, false. /// - public static bool IsBGRAOrder(this PixelFormat format) + public static bool IsBgraOrder(this PixelFormat format) { switch (format) { - case PixelFormat.B8G8R8A8_UNorm: - case PixelFormat.B8G8R8X8_UNorm: - case PixelFormat.B8G8R8A8_Typeless: - case PixelFormat.B8G8R8A8_UNorm_SRgb: - case PixelFormat.B8G8R8X8_Typeless: - case PixelFormat.B8G8R8X8_UNorm_SRgb: + case B8G8R8A8_UNorm: + case B8G8R8X8_UNorm: + case B8G8R8A8_Typeless: + case B8G8R8A8_UNorm_SRgb: + case B8G8R8X8_Typeless: + case B8G8R8X8_UNorm_SRgb: return true; + default: return false; } @@ -431,275 +428,279 @@ public static bool IsBGRAOrder(this PixelFormat format) /// static PixelFormatExtensions() { - InitFormat(new[] - { - PixelFormat.A8_UNorm, - PixelFormat.R8_SInt, - PixelFormat.R8_SNorm, - PixelFormat.R8_Typeless, - PixelFormat.R8_UInt, - PixelFormat.R8_UNorm - }, 1); - - InitFormat(new[] - { - PixelFormat.B5G5R5A1_UNorm, - PixelFormat.B5G6R5_UNorm, - PixelFormat.D16_UNorm, - PixelFormat.R16_Float, - PixelFormat.R16_SInt, - PixelFormat.R16_SNorm, - PixelFormat.R16_Typeless, - PixelFormat.R16_UInt, - PixelFormat.R16_UNorm, - PixelFormat.R8G8_SInt, - PixelFormat.R8G8_SNorm, - PixelFormat.R8G8_Typeless, - PixelFormat.R8G8_UInt, - PixelFormat.R8G8_UNorm, + InitFormat( + [ + A8_UNorm, + R8_SInt, + R8_SNorm, + R8_Typeless, + R8_UInt, + R8_UNorm + ], + pixelSize: 1); + + InitFormat( + [ + B5G5R5A1_UNorm, + B5G6R5_UNorm, + D16_UNorm, + R16_Float, + R16_SInt, + R16_SNorm, + R16_Typeless, + R16_UInt, + R16_UNorm, + R8G8_SInt, + R8G8_SNorm, + R8G8_Typeless, + R8G8_UInt, + R8G8_UNorm, #if DIRECTX11_1 - PixelFormat.B4G4R4A4_UNorm, + B4G4R4A4_UNorm #endif - }, 2); - - InitFormat(new[] - { - PixelFormat.B8G8R8X8_Typeless, - PixelFormat.B8G8R8X8_UNorm, - PixelFormat.B8G8R8X8_UNorm_SRgb, - PixelFormat.D24_UNorm_S8_UInt, - PixelFormat.D32_Float, - PixelFormat.D32_Float_S8X24_UInt, - PixelFormat.R10G10B10_Xr_Bias_A2_UNorm, - PixelFormat.R10G10B10A2_Typeless, - PixelFormat.R10G10B10A2_UInt, - PixelFormat.R10G10B10A2_UNorm, - PixelFormat.R11G11B10_Float, - PixelFormat.R16G16_Float, - PixelFormat.R16G16_SInt, - PixelFormat.R16G16_SNorm, - PixelFormat.R16G16_Typeless, - PixelFormat.R16G16_UInt, - PixelFormat.R16G16_UNorm, - PixelFormat.R24_UNorm_X8_Typeless, - PixelFormat.R24G8_Typeless, - PixelFormat.R32_Float, - PixelFormat.R32_Float_X8X24_Typeless, - PixelFormat.R32_SInt, - PixelFormat.R32_Typeless, - PixelFormat.R32_UInt, - PixelFormat.R8G8B8A8_SInt, - PixelFormat.R8G8B8A8_SNorm, - PixelFormat.R8G8B8A8_Typeless, - PixelFormat.R8G8B8A8_UInt, - PixelFormat.R8G8B8A8_UNorm, - PixelFormat.R8G8B8A8_UNorm_SRgb, - PixelFormat.B8G8R8A8_Typeless, - PixelFormat.B8G8R8A8_UNorm, - PixelFormat.B8G8R8A8_UNorm_SRgb, - PixelFormat.R9G9B9E5_Sharedexp, - PixelFormat.X24_Typeless_G8_UInt, - PixelFormat.X32_Typeless_G8X24_UInt, - }, 4); - - InitFormat(new[] - { - PixelFormat.R16G16B16A16_Float, - PixelFormat.R16G16B16A16_SInt, - PixelFormat.R16G16B16A16_SNorm, - PixelFormat.R16G16B16A16_Typeless, - PixelFormat.R16G16B16A16_UInt, - PixelFormat.R16G16B16A16_UNorm, - PixelFormat.R32G32_Float, - PixelFormat.R32G32_SInt, - PixelFormat.R32G32_Typeless, - PixelFormat.R32G32_UInt, - PixelFormat.R32G8X24_Typeless, - }, 8); - - InitFormat(new[] - { - PixelFormat.R32G32B32_Float, - PixelFormat.R32G32B32_SInt, - PixelFormat.R32G32B32_Typeless, - PixelFormat.R32G32B32_UInt, - }, 12); - - InitFormat(new[] - { - PixelFormat.R32G32B32A32_Float, - PixelFormat.R32G32B32A32_SInt, - PixelFormat.R32G32B32A32_Typeless, - PixelFormat.R32G32B32A32_UInt, - }, 16); - - // Init compressed formats - InitBlockFormat(new[] - { - PixelFormat.BC1_Typeless, - PixelFormat.BC1_UNorm, - PixelFormat.BC1_UNorm_SRgb, - PixelFormat.BC4_SNorm, - PixelFormat.BC4_Typeless, - PixelFormat.BC4_UNorm, - PixelFormat.ETC1, - PixelFormat.ETC2_RGB, - PixelFormat.ETC2_RGB_SRgb, - PixelFormat.ETC2_RGB_A1, - PixelFormat.EAC_R11_Unsigned, - PixelFormat.EAC_R11_Signed, - }, 8, 4, 4); - - InitBlockFormat(new[] - { - PixelFormat.BC2_Typeless, - PixelFormat.BC2_UNorm, - PixelFormat.BC2_UNorm_SRgb, - PixelFormat.BC3_Typeless, - PixelFormat.BC3_UNorm, - PixelFormat.BC3_UNorm_SRgb, - PixelFormat.BC5_SNorm, - PixelFormat.BC5_Typeless, - PixelFormat.BC5_UNorm, - PixelFormat.BC6H_Sf16, - PixelFormat.BC6H_Typeless, - PixelFormat.BC6H_Uf16, - PixelFormat.BC7_Typeless, - PixelFormat.BC7_UNorm, - PixelFormat.BC7_UNorm_SRgb, - PixelFormat.ETC2_RGBA, - PixelFormat.EAC_RG11_Unsigned, - PixelFormat.EAC_RG11_Signed, - PixelFormat.ETC2_RGBA_SRgb, - }, 16, 4, 4); - - InitBlockFormat(new[] + ], + pixelSize: 2); + + InitFormat( + [ + B8G8R8X8_Typeless, + B8G8R8X8_UNorm, + B8G8R8X8_UNorm_SRgb, + D24_UNorm_S8_UInt, + D32_Float, + D32_Float_S8X24_UInt, + R10G10B10_Xr_Bias_A2_UNorm, + R10G10B10A2_Typeless, + R10G10B10A2_UInt, + R10G10B10A2_UNorm, + R11G11B10_Float, + R16G16_Float, + R16G16_SInt, + R16G16_SNorm, + R16G16_Typeless, + R16G16_UInt, + R16G16_UNorm, + R24_UNorm_X8_Typeless, + R24G8_Typeless, + R32_Float, + R32_Float_X8X24_Typeless, + R32_SInt, + R32_Typeless, + R32_UInt, + R8G8B8A8_SInt, + R8G8B8A8_SNorm, + R8G8B8A8_Typeless, + R8G8B8A8_UInt, + R8G8B8A8_UNorm, + R8G8B8A8_UNorm_SRgb, + B8G8R8A8_Typeless, + B8G8R8A8_UNorm, + B8G8R8A8_UNorm_SRgb, + R9G9B9E5_Sharedexp, + X24_Typeless_G8_UInt, + X32_Typeless_G8X24_UInt + ], + pixelSize: 4); + + InitFormat( + [ + R16G16B16A16_Float, + R16G16B16A16_SInt, + R16G16B16A16_SNorm, + R16G16B16A16_Typeless, + R16G16B16A16_UInt, + R16G16B16A16_UNorm, + R32G32_Float, + R32G32_SInt, + R32G32_Typeless, + R32G32_UInt, + R32G8X24_Typeless + ], + pixelSize: 8); + + InitFormat( + [ + R32G32B32_Float, + R32G32B32_SInt, + R32G32B32_Typeless, + R32G32B32_UInt + ], + pixelSize: 12); + + InitFormat( + [ + R32G32B32A32_Float, + R32G32B32A32_SInt, + R32G32B32A32_Typeless, + R32G32B32A32_UInt + ], + pixelSize: 16); + + // Compressed formats + InitBlockFormat( + [ + BC1_Typeless, + BC1_UNorm, + BC1_UNorm_SRgb, + BC4_SNorm, + BC4_Typeless, + BC4_UNorm, + ETC1, + ETC2_RGB, + ETC2_RGB_SRgb, + ETC2_RGB_A1, + EAC_R11_Unsigned, + EAC_R11_Signed + ], + blockSize: 8, blockWidth: 4, blockHeight: 4); + + InitBlockFormat( + [ + BC2_Typeless, + BC2_UNorm, + BC2_UNorm_SRgb, + BC3_Typeless, + BC3_UNorm, + BC3_UNorm_SRgb, + BC5_SNorm, + BC5_Typeless, + BC5_UNorm, + BC6H_Sf16, + BC6H_Typeless, + BC6H_Uf16, + BC7_Typeless, + BC7_UNorm, + BC7_UNorm_SRgb, + ETC2_RGBA, + EAC_RG11_Unsigned, + EAC_RG11_Signed, + ETC2_RGBA_SRgb + ], + blockSize: 16, blockWidth: 4, blockHeight: 4); + + InitBlockFormat( + [ + R8G8_B8G8_UNorm, + G8R8_G8B8_UNorm + ], + blockSize: 4, blockWidth: 2, blockHeight: 1); + + InitBlockFormat( + [ + R1_UNorm + ], + blockSize: 1, blockWidth: 8, blockHeight: 1); + + // sRGB formats + InitDefaults( + [ + R8G8B8A8_UNorm_SRgb, + BC1_UNorm_SRgb, + BC2_UNorm_SRgb, + BC3_UNorm_SRgb, + B8G8R8A8_UNorm_SRgb, + B8G8R8X8_UNorm_SRgb, + BC7_UNorm_SRgb, + ETC2_RGBA_SRgb, + ETC2_RGB_SRgb + ], + outputArray: srgbFormats); + + // Alpha formats + InitDefaults( + [ + R8G8B8A8_UNorm, + R8G8B8A8_UNorm_SRgb, + B8G8R8A8_UNorm, + B8G8R8A8_UNorm_SRgb + ], + outputArray: alpha32Formats); + + // HDR formats + InitDefaults( + [ + R16G16B16A16_Float, + R32G32B32A32_Float, + R16G16B16A16_Float, + R16G16_Float, + R16_Float, + BC6H_Sf16, + BC6H_Uf16 + ], + outputArray: hdrFormats); + + // Typeless formats + InitDefaults( + [ + R32G32B32A32_Typeless, + R32G32B32_Typeless, + R16G16B16A16_Typeless, + R32G32_Typeless, + R32G8X24_Typeless, + R32_Float_X8X24_Typeless, + X32_Typeless_G8X24_UInt, + R10G10B10A2_Typeless, + R8G8B8A8_Typeless, + R16G16_Typeless, + R32_Typeless, + R24G8_Typeless, + R24_UNorm_X8_Typeless, + X24_Typeless_G8_UInt, + R8G8_Typeless, + R16_Typeless, + R8_Typeless, + BC1_Typeless, + BC2_Typeless, + BC3_Typeless, + BC4_Typeless, + BC5_Typeless, + B8G8R8A8_Typeless, + B8G8R8X8_Typeless, + BC6H_Typeless, + BC7_Typeless + ], + outputArray: typelessFormats); + + sRgbConversion = new Dictionary { - PixelFormat.R8G8_B8G8_UNorm, - PixelFormat.G8R8_G8B8_UNorm, - }, 4, 2, 1); + { R8G8B8A8_UNorm_SRgb, R8G8B8A8_UNorm }, + { R8G8B8A8_UNorm, R8G8B8A8_UNorm_SRgb }, + { BC1_UNorm_SRgb, BC1_UNorm }, + { BC1_UNorm, BC1_UNorm_SRgb }, + { BC2_UNorm_SRgb, BC2_UNorm }, + { BC2_UNorm, BC2_UNorm_SRgb }, + { BC3_UNorm_SRgb, BC3_UNorm }, + { BC3_UNorm, BC3_UNorm_SRgb }, + { B8G8R8A8_UNorm_SRgb, B8G8R8A8_UNorm }, + { B8G8R8A8_UNorm, B8G8R8A8_UNorm_SRgb }, + { B8G8R8X8_UNorm_SRgb, B8G8R8X8_UNorm }, + { B8G8R8X8_UNorm, B8G8R8X8_UNorm_SRgb }, + { BC7_UNorm_SRgb, BC7_UNorm }, + { BC7_UNorm, BC7_UNorm_SRgb }, + { ETC2_RGBA_SRgb, ETC2_RGBA }, + { ETC2_RGBA, ETC2_RGBA_SRgb }, + { ETC2_RGB_SRgb, ETC2_RGB }, + { ETC2_RGB, ETC2_RGB_SRgb } + }; + return; - InitBlockFormat(new[] + static void InitFormat(ReadOnlySpan formats, byte pixelSize) { - PixelFormat.R1_UNorm, - }, 1, 8, 1); - - // Init srgb formats - InitDefaults(new[] - { - PixelFormat.R8G8B8A8_UNorm_SRgb, - PixelFormat.BC1_UNorm_SRgb, - PixelFormat.BC2_UNorm_SRgb, - PixelFormat.BC3_UNorm_SRgb, - PixelFormat.B8G8R8A8_UNorm_SRgb, - PixelFormat.B8G8R8X8_UNorm_SRgb, - PixelFormat.BC7_UNorm_SRgb, - PixelFormat.ETC2_RGBA_SRgb, - PixelFormat.ETC2_RGB_SRgb, - }, srgbFormats); - - // Init srgb formats - InitDefaults(new[] - { - PixelFormat.R8G8B8A8_UNorm, - PixelFormat.R8G8B8A8_UNorm_SRgb, - PixelFormat.B8G8R8A8_UNorm, - PixelFormat.B8G8R8A8_UNorm_SRgb, - }, alpha32Formats); - - InitDefaults(new[] + foreach (var format in formats) + sizeInfos[GetIndex(format)] = new PixelFormatSizeInfo(BlockSize: pixelSize, BlockWidth: 1, BlockHeight: 1, IsCompressed: 0); + } + + static void InitBlockFormat(ReadOnlySpan formats, byte blockSize, byte blockWidth, byte blockHeight) { - PixelFormat.R16G16B16A16_Float, - PixelFormat.R32G32B32A32_Float, - PixelFormat.R16G16B16A16_Float, - PixelFormat.R16G16_Float, - PixelFormat.R16_Float, - PixelFormat.BC6H_Sf16, - PixelFormat.BC6H_Uf16, - }, hdrFormats); - - // Init typeless formats - InitDefaults(new[] - { - PixelFormat.R32G32B32A32_Typeless, - PixelFormat.R32G32B32_Typeless, - PixelFormat.R16G16B16A16_Typeless, - PixelFormat.R32G32_Typeless, - PixelFormat.R32G8X24_Typeless, - PixelFormat.R32_Float_X8X24_Typeless, - PixelFormat.X32_Typeless_G8X24_UInt, - PixelFormat.R10G10B10A2_Typeless, - PixelFormat.R8G8B8A8_Typeless, - PixelFormat.R16G16_Typeless, - PixelFormat.R32_Typeless, - PixelFormat.R24G8_Typeless, - PixelFormat.R24_UNorm_X8_Typeless, - PixelFormat.X24_Typeless_G8_UInt, - PixelFormat.R8G8_Typeless, - PixelFormat.R16_Typeless, - PixelFormat.R8_Typeless, - PixelFormat.BC1_Typeless, - PixelFormat.BC2_Typeless, - PixelFormat.BC3_Typeless, - PixelFormat.BC4_Typeless, - PixelFormat.BC5_Typeless, - PixelFormat.B8G8R8A8_Typeless, - PixelFormat.B8G8R8X8_Typeless, - PixelFormat.BC6H_Typeless, - PixelFormat.BC7_Typeless, - }, typelessFormats); - - sRgbConvertion = new Dictionary + foreach (var format in formats) + sizeInfos[GetIndex(format)] = new PixelFormatSizeInfo(BlockSize: blockSize, BlockWidth: blockWidth, BlockHeight: blockHeight, IsCompressed: 1); + } + + static void InitDefaults(ReadOnlySpan formats, bool[] outputArray) { - { PixelFormat.R8G8B8A8_UNorm_SRgb, PixelFormat.R8G8B8A8_UNorm }, - { PixelFormat.R8G8B8A8_UNorm, PixelFormat.R8G8B8A8_UNorm_SRgb }, - { PixelFormat.BC1_UNorm_SRgb, PixelFormat.BC1_UNorm }, - { PixelFormat.BC1_UNorm, PixelFormat.BC1_UNorm_SRgb }, - { PixelFormat.BC2_UNorm_SRgb, PixelFormat.BC2_UNorm }, - { PixelFormat.BC2_UNorm, PixelFormat.BC2_UNorm_SRgb }, - { PixelFormat.BC3_UNorm_SRgb, PixelFormat.BC3_UNorm }, - { PixelFormat.BC3_UNorm, PixelFormat.BC3_UNorm_SRgb }, - { PixelFormat.B8G8R8A8_UNorm_SRgb, PixelFormat.B8G8R8A8_UNorm }, - { PixelFormat.B8G8R8A8_UNorm, PixelFormat.B8G8R8A8_UNorm_SRgb }, - { PixelFormat.B8G8R8X8_UNorm_SRgb, PixelFormat.B8G8R8X8_UNorm }, - { PixelFormat.B8G8R8X8_UNorm, PixelFormat.B8G8R8X8_UNorm_SRgb }, - { PixelFormat.BC7_UNorm_SRgb, PixelFormat.BC7_UNorm }, - { PixelFormat.BC7_UNorm, PixelFormat.BC7_UNorm_SRgb }, - { PixelFormat.ETC2_RGBA_SRgb, PixelFormat.ETC2_RGBA }, - { PixelFormat.ETC2_RGBA, PixelFormat.ETC2_RGBA_SRgb }, - { PixelFormat.ETC2_RGB_SRgb, PixelFormat.ETC2_RGB }, - { PixelFormat.ETC2_RGB, PixelFormat.ETC2_RGB_SRgb }, - }; - } - - private static void InitBlockFormat(IEnumerable formats, byte blockSize, byte blockWidth, byte blockHeight) - { - foreach (var format in formats) - sizeInfos[GetIndex(format)] = new PixelFormatSizeInfo - { - BlockSize = blockSize, - BlockWidth = blockWidth, - BlockHeight = blockHeight, - IsCompressed = 1, - }; - } - - private static void InitFormat(IEnumerable formats, byte pixelSize) - { - foreach (var format in formats) - sizeInfos[GetIndex(format)] = new PixelFormatSizeInfo - { - BlockSize = pixelSize, - BlockWidth = 1, - BlockHeight = 1, - IsCompressed = 0, - }; - } - - private static void InitDefaults(IEnumerable formats, bool[] outputArray) - { - foreach (var format in formats) - outputArray[GetIndex(format)] = true; + foreach (var format in formats) + outputArray[GetIndex(format)] = true; + } } } } diff --git a/sources/engine/Stride/Graphics/SamplerStateDescription.cs b/sources/engine/Stride/Graphics/SamplerStateDescription.cs index 52cdbcfa7c..d77aca309b 100644 --- a/sources/engine/Stride/Graphics/SamplerStateDescription.cs +++ b/sources/engine/Stride/Graphics/SamplerStateDescription.cs @@ -1,137 +1,179 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Stride.Core; using Stride.Core.Mathematics; -using Stride.Core.Serialization; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Describes a Sampler State object, which determines how to sample Texture data. +/// +/// +[DataContract] +[StructLayout(LayoutKind.Sequential)] +public struct SamplerStateDescription : IEquatable { /// - /// Describes a sampler state. + /// Initializes a new instance of the structure. /// - [DataContract] - [StructLayout(LayoutKind.Sequential)] - public struct SamplerStateDescription : IEquatable + /// The Texture filtering mode. + /// The Texture addressing mode for U, V, and W coordinates. + public SamplerStateDescription(TextureFilter filter, TextureAddressMode addressMode) : this() { - /// - /// Initializes a new instance of the class. - /// - /// The filter. - /// The address mode. - public SamplerStateDescription(TextureFilter filter, TextureAddressMode addressMode) : this() - { - SetDefaults(); - Filter = filter; - AddressU = AddressV = AddressW = addressMode; - } + SetDefaults(); - /// - /// Gets or sets filtering method to use when sampling a texture (see ). - /// - public TextureFilter Filter; - - /// - /// Gets or sets method to use for resolving a u texture coordinate that is outside the 0 to 1 range (see ). - /// - public TextureAddressMode AddressU; - - /// - /// Gets or sets method to use for resolving a v texture coordinate that is outside the 0 to 1 range. - /// - public TextureAddressMode AddressV; - - /// - /// Gets or sets method to use for resolving a w texture coordinate that is outside the 0 to 1 range. - /// - public TextureAddressMode AddressW; - - /// - /// Gets or sets offset from the calculated mipmap level. - /// For example, if Direct3D calculates that a texture should be sampled at mipmap level 3 and MipLODBias is 2, then the texture will be sampled at mipmap level 5. - /// - public float MipMapLevelOfDetailBias; - - /// - /// Gets or sets clamping value used if Anisotropy or ComparisonAnisotropy is specified in Filter. Valid values are between 1 and 16. - /// - public int MaxAnisotropy; - - /// - /// Gets or sets a function that compares sampled data against existing sampled data. The function options are listed in . - /// - public CompareFunction CompareFunction; - - /// - /// Gets or sets border color to use if is specified for AddressU, AddressV, or AddressW. Range must be between 0.0 and 1.0 inclusive. - /// - public Color4 BorderColor; - - /// - /// Gets or sets lower end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed. - /// - public float MinMipLevel; - - /// - /// Gets or sets upper end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed. This value must be greater than or equal to MinLOD. To have no upper limit on LOD set this to a large value such as D3D11_FLOAT32_MAX. - /// - public float MaxMipLevel; - - /// - /// Gets default values for this instance. - /// - public static SamplerStateDescription Default - { - get - { - var desc = new SamplerStateDescription(); - desc.SetDefaults(); - return desc; - } - } + Filter = filter; + AddressU = AddressV = AddressW = addressMode; + } - public bool Equals(SamplerStateDescription other) - { - return Filter == other.Filter && AddressU == other.AddressU && AddressV == other.AddressV && AddressW == other.AddressW && MipMapLevelOfDetailBias.Equals(other.MipMapLevelOfDetailBias) && MaxAnisotropy == other.MaxAnisotropy && CompareFunction == other.CompareFunction && BorderColor.Equals(other.BorderColor) && MinMipLevel.Equals(other.MinMipLevel) && MaxMipLevel.Equals(other.MaxMipLevel); - } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is SamplerStateDescription && Equals((SamplerStateDescription)obj); - } + /// + /// The filtering method to use when sampling a Texture. + /// + public TextureFilter Filter; + + /// + /// The method to use for resolving a U texture coordinate that is outside the [0, 1] range. + /// + public TextureAddressMode AddressU; + + /// + /// The method to use for resolving a V texture coordinate that is outside the [0, 1] range. + /// + public TextureAddressMode AddressV; + + /// + /// The method to use for resolving a W texture coordinate that is outside the [0, 1] range. + /// + public TextureAddressMode AddressW; + + /// + /// The offset to apply from the calculated mipmap level. + /// + /// + /// For example, if a Texture should be sampled at mipmap level 3 and + /// is 2, then the Texture will be sampled at mipmap level 5. + /// + public float MipMapLevelOfDetailBias; + + /// + /// The clamping value used if or + /// is specified in . Valid values are between 1 and 16. + /// + public int MaxAnisotropy; + + /// + /// A function that compares sampled data against existing sampled data. + /// + /// + /// This function will be used when specifying one of the comparison filtering modes in + /// . + /// + public CompareFunction CompareFunction; + + /// + /// The border color to use if is specified for + /// , , or . + /// + public Color4 BorderColor; + + /// + /// The lower end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap + /// level and any level higher than that is less detailed. + /// + public float MinMipLevel; + + /// + /// The upper end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap + /// level and any level higher than that is less detailed. + /// + /// + /// This value must be greater than or equal to . + /// To have no upper limit set this to a large value such as . + /// + public float MaxMipLevel; - public override int GetHashCode() - { - unchecked - { - int hashCode = (int)Filter; - hashCode = (hashCode * 397) ^ (int)AddressU; - hashCode = (hashCode * 397) ^ (int)AddressV; - hashCode = (hashCode * 397) ^ (int)AddressW; - hashCode = (hashCode * 397) ^ MipMapLevelOfDetailBias.GetHashCode(); - hashCode = (hashCode * 397) ^ MaxAnisotropy; - hashCode = (hashCode * 397) ^ (int)CompareFunction; - hashCode = (hashCode * 397) ^ BorderColor.GetHashCode(); - hashCode = (hashCode * 397) ^ MinMipLevel.GetHashCode(); - hashCode = (hashCode * 397) ^ MaxMipLevel.GetHashCode(); - return hashCode; - } - } - private void SetDefaults() + /// + /// Returns a with default values. + /// + public static SamplerStateDescription Default + { + get { - Filter = TextureFilter.Linear; - AddressU = TextureAddressMode.Clamp; - AddressV = TextureAddressMode.Clamp; - AddressW = TextureAddressMode.Clamp; - BorderColor = new Color4(); - MaxAnisotropy = 16; - MinMipLevel = -float.MaxValue; - MaxMipLevel = float.MaxValue; - MipMapLevelOfDetailBias = 0.0f; - CompareFunction = CompareFunction.Never; + Unsafe.SkipInit(out SamplerStateDescription desc); + desc.SetDefaults(); + return desc; } } + + + public static bool operator ==(SamplerStateDescription left, SamplerStateDescription right) + { + return left.Equals(right); + } + + public static bool operator !=(SamplerStateDescription left, SamplerStateDescription right) + { + return !(left == right); + } + + /// + public bool Equals(SamplerStateDescription other) + { + return Filter == other.Filter + && AddressU == other.AddressU + && AddressV == other.AddressV + && AddressW == other.AddressW + && MipMapLevelOfDetailBias.Equals(other.MipMapLevelOfDetailBias) + && MaxAnisotropy == other.MaxAnisotropy + && CompareFunction == other.CompareFunction + && BorderColor.Equals(other.BorderColor) + && MinMipLevel.Equals(other.MinMipLevel) + && MaxMipLevel.Equals(other.MaxMipLevel); + } + + /// + public override bool Equals(object obj) + { + return obj is SamplerStateDescription description && Equals(description); + } + + /// + public override readonly int GetHashCode() + { + var hash = new HashCode(); + hash.Add(Filter); + hash.Add(AddressU); + hash.Add(AddressV); + hash.Add(AddressW); + hash.Add(MipMapLevelOfDetailBias); + hash.Add(MaxAnisotropy); + hash.Add(CompareFunction); + hash.Add(BorderColor); + hash.Add(MinMipLevel); + hash.Add(MaxMipLevel); + return hash.ToHashCode(); + } + + /// + /// Sets the default values for this instance. + /// + private void SetDefaults() + { + Filter = TextureFilter.Linear; + AddressU = TextureAddressMode.Clamp; + AddressV = TextureAddressMode.Clamp; + AddressW = TextureAddressMode.Clamp; + BorderColor = new Color4(); + MaxAnisotropy = 16; + MinMipLevel = -float.MaxValue; + MaxMipLevel = float.MaxValue; + MipMapLevelOfDetailBias = 0.0f; + CompareFunction = CompareFunction.Never; + } } diff --git a/sources/engine/Stride/Graphics/TextureAddressMode.cs b/sources/engine/Stride/Graphics/TextureAddressMode.cs index 0a4d9ec895..24338655a3 100644 --- a/sources/engine/Stride/Graphics/TextureAddressMode.cs +++ b/sources/engine/Stride/Graphics/TextureAddressMode.cs @@ -1,38 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Identifies a technique for resolving Texture coordinates that are outside +/// of the boundaries of a Texture (outside the [0, 1] range). +/// +[DataContract("TextureAddressMode")] +public enum TextureAddressMode { /// - /// Identify a technique for resolving texture coordinates that are outside of the boundaries of a texture. + /// Tile the Texture at every (u,v) integer junction. + /// For example, for u values between 0 and 3, the Texture is repeated three times. /// - [DataContract("TextureAddressMode")] - public enum TextureAddressMode - { - /// - /// Tile the texture at every (u,v) integer junction. For example, for u values between 0 and 3, the texture is repeated three times. - /// - Wrap = 1, + Wrap = 1, - /// - /// Flip the texture at every (u,v) integer junction. For u values between 0 and 1, for example, the texture is addressed normally; between 1 and 2, the texture is flipped (mirrored); between 2 and 3, the texture is normal again; and so on. - /// - Mirror = 2, + /// + /// Flip the Texture at every (u,v) integer junction. + /// For u values between 0 and 1, for example, the Texture is addressed normally; + /// between 1 and 2, the Texture is flipped (mirrored); + /// between 2 and 3, the Texture is normal again; and so on. + /// + Mirror = 2, - /// - /// Texture coordinates outside the range [0.0, 1.0] are set to the texture color at 0.0 or 1.0, respectively. - /// - Clamp = 3, + /// + /// Texture coordinates outside the range [0, 1] are set to the Texture color at 0 or 1, respectively. + /// + Clamp = 3, - /// - /// Texture coordinates outside the range [0.0, 1.0] are set to the border color specified in or HLSL code. - /// - Border = 4, + /// + /// Texture coordinates outside the range [0, 1] are set to the border color specified in + /// or HLSL code. + /// + Border = 4, - /// - /// Similar to D3D11_TEXTURE_ADDRESS_MIRROR and D3D11_TEXTURE_ADDRESS_CLAMP. Takes the absolute value of the texture coordinate (thus, mirroring around 0), and then clamps to the maximum value. - /// - MirrorOnce = 5, - } + /// + /// Similar to and . + /// Takes the absolute value of the Texture coordinate (thus, mirroring around 0), and then + /// clamps to the maximum value. + /// + MirrorOnce = 5 } diff --git a/sources/engine/Stride/Graphics/TextureDimension.cs b/sources/engine/Stride/Graphics/TextureDimension.cs index 6ea40a580a..b2f64ff296 100644 --- a/sources/engine/Stride/Graphics/TextureDimension.cs +++ b/sources/engine/Stride/Graphics/TextureDimension.cs @@ -2,17 +2,17 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. // // Copyright (c) 2010-2012 SharpDX - Alexandre Mutel -// +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: -// +// // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,32 +23,33 @@ using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +// TODO: Should not this be in Stride.Graphics? + +/// +/// Identifies the type of Texture resource being used. +/// +[DataContract] +public enum TextureDimension { /// - /// Defines the dimension of a texture. + /// The Texture is a one-dimensional (1D) texture. /// - [DataContract] - public enum TextureDimension - { - /// - /// The texture dimension is 1D. - /// - Texture1D, + Texture1D, - /// - /// The texture dimension is 2D. - /// - Texture2D, + /// + /// The Texture is a two-dimensional (2D) texture. + /// + Texture2D, - /// - /// The texture dimension is 3D. - /// - Texture3D, + /// + /// The Texture is a three-dimensional (3D) texture (also known as a Volume Texture). + /// + Texture3D, - /// - /// The texture dimension is a CubeMap. - /// - TextureCube, - } + /// + /// The Texture is a Cube Map, six two-dimensional images forming a cube, each with their own mip-chain. + /// + TextureCube } diff --git a/sources/engine/Stride/Graphics/TextureFilter.cs b/sources/engine/Stride/Graphics/TextureFilter.cs index 058b0c4757..7469d2e5aa 100644 --- a/sources/engine/Stride/Graphics/TextureFilter.cs +++ b/sources/engine/Stride/Graphics/TextureFilter.cs @@ -1,106 +1,172 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core; -namespace Stride.Graphics +namespace Stride.Graphics; + +/// +/// Identifies the filtering mode to use during Texture sampling. +/// +/// +/// During texture sampling, one or more texels are read and combined (this is called filtering) +/// to produce a single value. +/// +[DataContract("TextureFilter")] +public enum TextureFilter { /// - /// Filtering options during texture sampling. + /// Use point sampling for minification, magnification, and mip-level sampling. + /// + /// + /// Point sampling is the fastest texture filtering method. It is also the lowest quality, + /// because it just reads a single value and does not blend between texels. + /// + Point = 0, + + /// + /// Use point sampling for minification and magnification, and linear interpolation for mip-level sampling. + /// + /// + /// + MinMagPointMipLinear = 1, + + /// + /// Use point sampling for minification, linear interpolation for magnification, and point sampling for mip-level sampling. + /// + /// + /// + MinPointMagLinearMipPoint = 4, + + /// + /// Use point sampling for minification, linear interpolation for magnification and mip-level sampling. + /// + /// + /// + MinPointMagMipLinear = 5, + + /// + /// Use linear interpolation for minification, point sampling for magnification and mip-level sampling. + /// + /// + /// + MinLinearMagMipPoint = 16, + + /// + /// Use linear interpolation for minification, point sampling for magnification, and linear interpolation for mip-level sampling. + /// + /// + /// + MinLinearMagPointMipLinear = 17, + + /// + /// Use linear interpolation for minification and magnification, and point sampling for mip-level sampling. + /// + MinMagLinearMipPoint = 20, + + /// + /// Use linear interpolation for minification, magnification, and mip-level sampling. + /// + /// + /// Linear interpolation is slower than point sampling, but produces higher quality results. + /// Two samples are taken across the sampling direction, and a linearly interpolated value + /// is generated between those by blending them. + /// + Linear = 21, + + /// + /// Use anisotropic interpolation for minification, magnification, and mip-level sampling. /// /// - /// During texture sampling, one or more texels are read and combined (this is calling filtering) to produce a single value. Point sampling reads a single texel while linear sampling reads two texels (endpoints) and linearly interpolates a third value between the endpoints. HLSL texture-sampling functions also support comparison filtering during texture sampling. Comparison filtering compares each sampled texel against a comparison value. The boolean result is blended the same way that normal texture filtering is blended. You can use HLSL intrinsic texture-sampling functions that implement texture filtering only or companion functions that use texture filtering with comparison filtering. Texture Sampling FunctionTexture Sampling Function with Comparison Filtering samplesamplecmp or samplecmplevelzero ? Comparison filters only work with textures that have the following DXGI formats: R32_FLOAT_X8X24_TYPELESS, R32_FLOAT, R24_UNORM_X8_TYPELESS, R16_UNORM. + /// + /// When viewing a surface at a shallow angle, the Texture is stretched according to the perspective. + /// Point or linear interpolation sample in a circular area independent of the viewing angle, producing a + /// blurry or smeared appearance. + /// + /// + /// Anisotropic filtering addresses this by sampling Textures differently depending on the angle of the + /// surface relative to the viewer. + /// Instead of assuming a circular sampling footprint (as in isotropic methods like bilinear filtering), + /// it stretches the sampling region into an ellipse or more complex shapes that better fit the distorted + /// projection of the Texture, and takes more samples along that direction, depending on the anisotropy level. + /// + /// + /// It also interacts with mipmapping, selecting and interpolating from the appropriate mipmap levels, + /// to ensure that the correct Texture resolution is used, even when viewed at extreme angles. + /// /// - [DataContract("TextureFilter")] - public enum TextureFilter - { - /// - /// Use point sampling for minification, magnification, and mip-level sampling. - /// - Point = 0, - - /// - /// Use point sampling for minification and magnification; use linear interpolation for mip-level sampling. - /// - MinMagPointMipLinear = 1, - - /// - /// Use point sampling for minification; use linear interpolation for magnification; use point sampling for mip-level sampling. - /// - MinPointMagLinearMipPoint = 4, - - /// - /// Use point sampling for minification; use linear interpolation for magnification and mip-level sampling. - /// - MinPointMagMipLinear = 5, - - /// - /// Use linear interpolation for minification; use point sampling for magnification and mip-level sampling. - /// - MinLinearMagMipPoint = 16, - - /// - /// Use linear interpolation for minification; use point sampling for magnification; use linear interpolation for mip-level sampling. - /// - MinLinearMagPointMipLinear = 17, - - /// - /// Use linear interpolation for minification and magnification; use point sampling for mip-level sampling. - /// - MinMagLinearMipPoint = 20, - - /// - /// Use linear interpolation for minification, magnification, and mip-level sampling. - /// - Linear = 21, - - /// - /// Use anisotropic interpolation for minification, magnification, and mip-level sampling. - /// - Anisotropic = 85, - - /// - /// Use point sampling for minification, magnification, and mip-level sampling. Compare the result to the comparison value. - /// - ComparisonPoint = 128, - - /// - /// Use point sampling for minification and magnification; use linear interpolation for mip-level sampling. Compare the result to the comparison value. - /// - ComparisonMinMagPointMipLinear = 129, - - /// - /// Use point sampling for minification; use linear interpolation for magnification; use point sampling for mip-level sampling. Compare the result to the comparison value. - /// - ComparisonMinPointMagLinearMipPoint = 132, - - /// - /// Use point sampling for minification; use linear interpolation for magnification and mip-level sampling. Compare the result to the comparison value. - /// - ComparisonMinPointMagMipLinear = 133, - - /// - /// Use linear interpolation for minification; use point sampling for magnification and mip-level sampling. Compare the result to the comparison value. - /// - ComparisonMinLinearMagMipPoint = 144, - - /// - /// Use linear interpolation for minification; use point sampling for magnification; use linear interpolation for mip-level sampling. Compare the result to the comparison value. - /// - ComparisonMinLinearMagPointMipLinear = 145, - - /// - /// Use linear interpolation for minification and magnification; use point sampling for mip-level sampling. Compare the result to the comparison value. - /// - ComparisonMinMagLinearMipPoint = 148, - - /// - /// Use linear interpolation for minification, magnification, and mip-level sampling. Compare the result to the comparison value. - /// - ComparisonLinear = 149, - - /// - /// Use anisotropic interpolation for minification, magnification, and mip-level sampling. Compare the result to the comparison value. - /// - ComparisonAnisotropic = 213, - } + Anisotropic = 85, + + /// + /// Use point sampling for minification, magnification, and mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + /// + /// Comparison filtering compares each sampled texel against a comparison value. + /// The boolean result is blended the same way that normal texture filtering is blended. + /// + /// + /// Comparison filters only work with textures that have the following formats: + /// , , + /// , and . + /// + /// + ComparisonPoint = 128, + + /// + /// Use point sampling for minification and magnification, and linear interpolation for mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonMinMagPointMipLinear = 129, + + /// + /// Use point sampling for minification, linear interpolation for magnification, and point sampling for mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonMinPointMagLinearMipPoint = 132, + + /// + /// Use point sampling for minification, and linear interpolation for magnification and mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonMinPointMagMipLinear = 133, + + /// + /// Use linear interpolation for minification, and point sampling for magnification and mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonMinLinearMagMipPoint = 144, + + /// + /// Use linear interpolation for minification, point sampling for magnification, and linear interpolation for mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonMinLinearMagPointMipLinear = 145, + + /// + /// Use linear interpolation for minification and magnification, and point sampling for mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonMinMagLinearMipPoint = 148, + + /// + /// Use linear interpolation for minification, magnification, and mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonLinear = 149, + + /// + /// Use anisotropic interpolation for minification, magnification, and mip-level sampling. + /// Compare the result to the comparison value. + /// + /// + ComparisonAnisotropic = 213 } diff --git a/sources/tests/tools/Stride.TextureConverter.Tests/TexLibraryTest.cs b/sources/tests/tools/Stride.TextureConverter.Tests/TexLibraryTest.cs index b250ce52ad..dfe9294838 100644 --- a/sources/tests/tools/Stride.TextureConverter.Tests/TexLibraryTest.cs +++ b/sources/tests/tools/Stride.TextureConverter.Tests/TexLibraryTest.cs @@ -66,9 +66,9 @@ public static void FixedRescaleTest(TexImage image, ITexLibrary library, Filter. public static void SwitchChannelsTest(TexImage image, ITexLibrary library) { - var isInRgbaOrder = image.Format.IsRGBAOrder(); + var isInRgbaOrder = image.Format.IsRgbaOrder(); library.Execute(image, new SwitchingBRChannelsRequest()); - Assert.True(image.Format.IsRGBAOrder() != isInRgbaOrder); + Assert.True(image.Format.IsRgbaOrder() != isInRgbaOrder); //Console.WriteLine("SwitchChannelsTest_" + image.Name + "." + TestTools.ComputeSHA1(image.Data, image.DataSize)); Assert.Equal(TestTools.GetInstance().Checksum["SwitchChannelsTest_" + image.Name], TestTools.ComputeSHA1(image.Data, image.DataSize)); diff --git a/sources/tests/tools/Stride.TextureConverter.Tests/TextureToolTest.cs b/sources/tests/tools/Stride.TextureConverter.Tests/TextureToolTest.cs index fefa278ead..96c73f1e18 100644 --- a/sources/tests/tools/Stride.TextureConverter.Tests/TextureToolTest.cs +++ b/sources/tests/tools/Stride.TextureConverter.Tests/TextureToolTest.cs @@ -245,12 +245,12 @@ public void ResizeTest(string file) public void SwitchChannelTest(string file) { var image = texTool.Load(Module.PathToInputImages + file); - var isInBgraOrder = image.Format.IsBGRAOrder(); + var isInBgraOrder = image.Format.IsBgraOrder(); texTool.SwitchChannel(image); image.Update(); - Assert.True(isInBgraOrder != image.Format.IsBGRAOrder()); + Assert.True(isInBgraOrder != image.Format.IsBgraOrder()); Assert.Equal(TestTools.GetInstance().Checksum["TextureTool_SwitchChannel_" + image.Name], TestTools.ComputeSHA1(image.Data, image.DataSize)); //Console.WriteLine("TextureTool_SwitchChannel_" + image.Name + "." + TestTools.ComputeSHA1(image.Data, image.DataSize)); diff --git a/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs b/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs index 93087568f6..fa655829ea 100644 --- a/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs +++ b/sources/tools/Stride.Graphics.RenderDocPlugin/RenderDocManager.cs @@ -100,12 +100,13 @@ public void DiscardFrameCapture(GraphicsDevice graphicsDevice, IntPtr hwndPtr) isCaptureStarted = false; } - private static IntPtr GetDevicePointer(GraphicsDevice graphicsDevice) + private static unsafe nint GetDevicePointer(GraphicsDevice graphicsDevice) { - var devicePointer = IntPtr.Zero; + nint devicePointer = 0; + #if STRIDE_GRAPHICS_API_DIRECT3D11 || STRIDE_GRAPHICS_API_DIRECT3D12 - if (graphicsDevice != null) - devicePointer = ((SharpDX.CppObject)SharpDXInterop.GetNativeDevice(graphicsDevice)).NativePointer; + if (graphicsDevice is not null) + devicePointer = (nint) GraphicsMarshal.GetNativeDevice(graphicsDevice).Handle; #endif return devicePointer; } diff --git a/sources/tools/Stride.Graphics.RenderDocPlugin/Stride.Graphics.RenderDocPlugin.csproj b/sources/tools/Stride.Graphics.RenderDocPlugin/Stride.Graphics.RenderDocPlugin.csproj index d9c971d4e4..a31e11a7b3 100644 --- a/sources/tools/Stride.Graphics.RenderDocPlugin/Stride.Graphics.RenderDocPlugin.csproj +++ b/sources/tools/Stride.Graphics.RenderDocPlugin/Stride.Graphics.RenderDocPlugin.csproj @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/sources/tools/Stride.TextureConverter/Backend/TexLibraries/FITexLib.cs b/sources/tools/Stride.TextureConverter/Backend/TexLibraries/FITexLib.cs index 26fb63cbfb..b6435e9331 100644 --- a/sources/tools/Stride.TextureConverter/Backend/TexLibraries/FITexLib.cs +++ b/sources/tools/Stride.TextureConverter/Backend/TexLibraries/FITexLib.cs @@ -1,17 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using FreeImageAPI; using Stride.Core; using Stride.Core.Diagnostics; using Stride.Graphics; using Stride.TextureConverter.Requests; -using FreeImageAPI; -using FreeImageAPI.Plugins; -using System.Runtime.CompilerServices; namespace Stride.TextureConverter.TexLibraries { @@ -256,12 +256,12 @@ private void Load(TexImage image, LoadingRequest loader) image.Dimension = image.Height == 1 ? TexImage.TextureDimension.Texture1D : TexImage.TextureDimension.Texture2D; image.Format = loader.LoadAsSRgb? Graphics.PixelFormat.B8G8R8A8_UNorm_SRgb : Graphics.PixelFormat.B8G8R8A8_UNorm; image.OriginalAlphaDepth = alphaSize; - + int rowPitch, slicePitch; Tools.ComputePitch(image.Format, image.Width, image.Height, out rowPitch, out slicePitch); image.RowPitch = rowPitch; image.SlicePitch = slicePitch; - + //Only one image in the SubImageArray, FreeImage is only used to load images, not textures. image.SubImageArray[0].Data = image.Data; image.SubImageArray[0].DataSize = image.DataSize; @@ -349,7 +349,7 @@ private void Rescale(TexImage image, FreeImageTextureLibraryData libraryData, Re int rowPitch, slicePitch; Tools.ComputePitch(image.Format, width, height, out rowPitch, out slicePitch); - + image.RowPitch = rowPitch; image.SlicePitch = slicePitch; image.MipmapCount = 1; @@ -381,7 +381,7 @@ private void SwitchChannels(TexImage image, FreeImageTextureLibraryData libraryD FreeImage.Unload(redChannel); } - if (image.Format.IsBGRAOrder()) + if (image.Format.IsBgraOrder()) image.Format = Graphics.PixelFormat.R8G8B8A8_UNorm; else image.Format = Graphics.PixelFormat.B8G8R8A8_UNorm; @@ -507,7 +507,7 @@ private void Export(TexImage image, FreeImageTextureLibraryData libraryData, Exp throw new TextureToolsException("Not implemented."); } - if (!image.Format.IsBGRAOrder()) + if (!image.Format.IsBgraOrder()) { SwitchChannels(image, libraryData, new SwitchingBRChannelsRequest()); } diff --git a/sources/tools/Stride.TextureConverter/Backend/Tools.cs b/sources/tools/Stride.TextureConverter/Backend/Tools.cs index 0f5c211d7e..b9103790c8 100644 --- a/sources/tools/Stride.TextureConverter/Backend/Tools.cs +++ b/sources/tools/Stride.TextureConverter/Backend/Tools.cs @@ -61,7 +61,7 @@ public static void ComputePitch(PixelFormat fmt, int width, int height, out int /// public static bool IsInSameChannelOrder(PixelFormat format1, PixelFormat format2) { - return format1.IsBGRAOrder() && format2.IsBGRAOrder() || format1.IsRGBAOrder() && format2.IsRGBAOrder(); + return format1.IsBgraOrder() && format2.IsBgraOrder() || format1.IsRgbaOrder() && format2.IsRgbaOrder(); } } } diff --git a/sources/tools/Stride.TextureConverter/Frontend/TextureTool.cs b/sources/tools/Stride.TextureConverter/Frontend/TextureTool.cs index d8dfa06a58..21e8c8d6e2 100644 --- a/sources/tools/Stride.TextureConverter/Frontend/TextureTool.cs +++ b/sources/tools/Stride.TextureConverter/Frontend/TextureTool.cs @@ -763,7 +763,7 @@ public unsafe AlphaLevels GetAlphaLevels(TexImage texture, Rectangle region, Col // check that we support the format var format = texture.Format; var pixelSize = format.SizeInBytes(); - if (texture.Dimension != TexImage.TextureDimension.Texture2D || !(format.IsRGBAOrder() || format.IsBGRAOrder() || pixelSize != 4)) + if (texture.Dimension != TexImage.TextureDimension.Texture2D || !(format.IsRgbaOrder() || format.IsBgraOrder() || pixelSize != 4)) { var guessedAlphaLevel = alphaDepth > 0 ? AlphaLevels.InterpolatedAlpha : AlphaLevels.NoAlpha; logger?.Debug($"Unable to find alpha levels for texture type {format}. Returning default alpha level '{guessedAlphaLevel}'."); @@ -781,7 +781,7 @@ public unsafe AlphaLevels GetAlphaLevels(TexImage texture, Rectangle region, Col if (tranparencyColor.HasValue) // specific case when using a transparency color { - var transparencyValue = format.IsRGBAOrder() ? tranparencyColor.Value.ToRgba() : tranparencyColor.Value.ToBgra(); + var transparencyValue = format.IsRgbaOrder() ? tranparencyColor.Value.ToRgba() : tranparencyColor.Value.ToBgra(); for (int y = 0; y < region.Height; ++y) { @@ -838,7 +838,7 @@ public unsafe Color PickColor(TexImage texture, Int2 pixel) if (texture == null) throw new ArgumentNullException(nameof(texture)); var format = texture.Format; - if (texture.Dimension != TexImage.TextureDimension.Texture2D || !(format.IsRGBAOrder() || format.IsBGRAOrder() || format.SizeInBytes() != 4)) + if (texture.Dimension != TexImage.TextureDimension.Texture2D || !(format.IsRgbaOrder() || format.IsBgraOrder() || format.SizeInBytes() != 4)) throw new NotImplementedException(); // check that the pixel is inside the texture @@ -850,7 +850,7 @@ public unsafe Color PickColor(TexImage texture, Int2 pixel) var stride = texture.RowPitch / 4; var pixelColorInt = ptr[stride*pixel.Y + pixel.X]; - var pixelColor = format.IsRGBAOrder() ? Color.FromRgba(pixelColorInt) : Color.FromBgra(pixelColorInt); + var pixelColor = format.IsRgbaOrder() ? Color.FromRgba(pixelColorInt) : Color.FromBgra(pixelColorInt); return pixelColor; } @@ -869,12 +869,12 @@ public unsafe Rectangle FindSpriteRegion(TexImage texture, Int2 pixel, Color? se if (texture == null) throw new ArgumentNullException(nameof(texture)); var format = texture.Format; - if (texture.Dimension != TexImage.TextureDimension.Texture2D || !(format.IsRGBAOrder() || format.IsBGRAOrder() || format.SizeInBytes() != 4)) + if (texture.Dimension != TexImage.TextureDimension.Texture2D || !(format.IsRgbaOrder() || format.IsBgraOrder() || format.SizeInBytes() != 4)) throw new NotImplementedException(); // adjust the separator color the mask depending on the color format. var separator = (uint)(separatorColor ?? Color.Transparent).ToRgba(); - if (texture.Format.IsBGRAOrder()) + if (texture.Format.IsBgraOrder()) { separator = RgbaToBgra(separator); separatorMask = RgbaToBgra(separatorMask); @@ -1443,7 +1443,7 @@ private void ExecuteRequest(TexImage image, IRequest request) ITexLibrary library; if ((library = FindLibrary(image, request)) != null) { - if (image.Format.IsBGRAOrder() && !library.SupportBGRAOrder()) + if (image.Format.IsBgraOrder() && !library.SupportBGRAOrder()) { SwitchChannel(image); } @@ -1488,7 +1488,7 @@ private void ExecuteRequest(TexImage image, IRequest request) } // Both libraries for intermediate processing were found, preceeding with the request - if (image.Format.IsBGRAOrder() && !library.SupportBGRAOrder()) + if (image.Format.IsBgraOrder() && !library.SupportBGRAOrder()) { SwitchChannel(image); }