From f93e651adac7201f2cc0c2213cd79546a9ec9d3d Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:22:04 +0800 Subject: [PATCH 01/30] start implementing node graph From 0762ed8ac623646cd24aede69d77909b5ebdc2a0 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:22:25 +0800 Subject: [PATCH 02/30] update Sekai --- source/Vignette.Desktop/Vignette.Desktop.csproj | 4 +++- source/Vignette/Vignette.csproj | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/Vignette.Desktop/Vignette.Desktop.csproj b/source/Vignette.Desktop/Vignette.Desktop.csproj index c3ef1cc..fc1497c 100644 --- a/source/Vignette.Desktop/Vignette.Desktop.csproj +++ b/source/Vignette.Desktop/Vignette.Desktop.csproj @@ -10,7 +10,9 @@ - + + + diff --git a/source/Vignette/Vignette.csproj b/source/Vignette/Vignette.csproj index d58d98f..9054d8f 100644 --- a/source/Vignette/Vignette.csproj +++ b/source/Vignette/Vignette.csproj @@ -6,7 +6,7 @@ - + From a544bdcd4383fb7fdf779ea166f79dc46110d8b5 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:23:10 +0800 Subject: [PATCH 03/30] remove previous rendering impl --- source/Vignette/Rendering/BlendMode.cs | 35 -- source/Vignette/Rendering/Effect.cs | 223 -------- source/Vignette/Rendering/Material.cs | 233 -------- source/Vignette/Rendering/Parameter.cs | 66 --- .../Rendering/Primitives/IPrimitive.cs | 47 -- source/Vignette/Rendering/Primitives/Line.cs | 77 --- source/Vignette/Rendering/Primitives/Mesh.cs | 163 ------ .../Rendering/Primitives/MeshAttribteType.cs | 55 -- .../Rendering/Primitives/MeshDrawMode.cs | 35 -- .../Rendering/Primitives/MeshFormat.cs | 62 -- source/Vignette/Rendering/Primitives/Quad.cs | 91 --- source/Vignette/Rendering/RenderContext.cs | 531 ------------------ source/Vignette/Rendering/RenderContext2D.cs | 187 ------ .../Vignette/Rendering/RenderContextState.cs | 79 --- source/Vignette/Rendering/RenderTarget.cs | 123 ---- source/Vignette/Rendering/Renderer.cs | 284 ---------- source/Vignette/Rendering/Texture.cs | 195 ------- source/Vignette/Rendering/Vertices/IVertex.cs | 14 - .../Rendering/Vertices/TexturedVertex.cs | 75 --- 19 files changed, 2575 deletions(-) delete mode 100644 source/Vignette/Rendering/BlendMode.cs delete mode 100644 source/Vignette/Rendering/Effect.cs delete mode 100644 source/Vignette/Rendering/Material.cs delete mode 100644 source/Vignette/Rendering/Parameter.cs delete mode 100644 source/Vignette/Rendering/Primitives/IPrimitive.cs delete mode 100644 source/Vignette/Rendering/Primitives/Line.cs delete mode 100644 source/Vignette/Rendering/Primitives/Mesh.cs delete mode 100644 source/Vignette/Rendering/Primitives/MeshAttribteType.cs delete mode 100644 source/Vignette/Rendering/Primitives/MeshDrawMode.cs delete mode 100644 source/Vignette/Rendering/Primitives/MeshFormat.cs delete mode 100644 source/Vignette/Rendering/Primitives/Quad.cs delete mode 100644 source/Vignette/Rendering/RenderContext.cs delete mode 100644 source/Vignette/Rendering/RenderContext2D.cs delete mode 100644 source/Vignette/Rendering/RenderContextState.cs delete mode 100644 source/Vignette/Rendering/RenderTarget.cs delete mode 100644 source/Vignette/Rendering/Renderer.cs delete mode 100644 source/Vignette/Rendering/Texture.cs delete mode 100644 source/Vignette/Rendering/Vertices/IVertex.cs delete mode 100644 source/Vignette/Rendering/Vertices/TexturedVertex.cs diff --git a/source/Vignette/Rendering/BlendMode.cs b/source/Vignette/Rendering/BlendMode.cs deleted file mode 100644 index 1915bde..0000000 --- a/source/Vignette/Rendering/BlendMode.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -namespace Vignette.Rendering; - -/// -/// An enumeration of default blending modes. -/// -public enum BlendMode -{ - /// - /// Non-premultiplied alpha blending the source and destination colors. - /// - NonPremultiplied, - - /// - /// Disabled blending. - /// - Disabled, - - /// - /// Additive blending where the destination color is appended to the source color. - /// - Additive, - - /// - /// Opaque blending where the source is overwritten by the destination. - /// - Opaque, - - /// - /// Alpha blending where the source and destination are blended using alpha. - /// - AlphaBlend, -} diff --git a/source/Vignette/Rendering/Effect.cs b/source/Vignette/Rendering/Effect.cs deleted file mode 100644 index 719c26e..0000000 --- a/source/Vignette/Rendering/Effect.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Text; -using Sekai.Graphics; - -namespace Vignette.Rendering; - -/// -/// Defines how primitives should be drawn to the screen. -/// -public readonly struct Effect : IEquatable -{ - private readonly ShaderCode[] shaderCode; - private readonly IDictionary parameters; - - public Effect() - { - shaderCode = Array.Empty(); - parameters = ImmutableDictionary.Empty; - } - - private Effect(ShaderCode shVert, ShaderCode shFrag, IDictionary parameters) - { - this.parameters = parameters; - this.shaderCode = new[] { shVert, shFrag }; - } - - /// - /// Checks whether a named parameter of a given type is present on this . - /// - /// The parameter type. - /// The parameter name. - /// if the parameter exists. Otherwise, returns . - public bool HasParameter(string name) - where T : class - { - if (!parameters.TryGetValue(name, out var param)) - { - return false; - } - - return param is T; - } - - /// - /// Gets the named parameter. - /// - /// The parameter type. - /// The parameter name. - /// The parameter. - /// Thrown when the parameter is not found in this . - /// Thrown when the parameter exists but the type argument does not match the parameter type. - public Parameter GetParameter(string name) - where T : class - { - if (!parameters.TryGetValue(name, out var param)) - { - throw new KeyNotFoundException($"The effect has no parameter named \"{name}\"."); - } - - if (param is not Parameter typedParam) - { - throw new InvalidCastException($"The effect parameter \"{name}\" is not a {nameof(Parameter)}."); - } - - return typedParam; - } - - /// - /// Gets all parameters this effect has. - /// - /// An enumeration of parameters. - public IEnumerable GetParameters() => parameters.Values; - - /// - /// Gets the named parameter. - /// - /// The parameter type. - /// The parameter name. - /// The retrieved parameter. - /// if the parameter exists. Otherwise, returns . - public bool TryGetParameter(string name, out Parameter typedParam) - where T : class - { - try - { - typedParam = GetParameter(name); - return true; - } - catch - { - typedParam = default; - return false; - } - } - - /// - /// Creates an from a . - /// - /// The stream to be read. - /// The text encoding. - /// An effect. - /// Thrown when the stream failed to be read. - public static Effect From(Stream stream, Encoding? encoding = null) - { - Span buffer = stackalloc byte[(int)stream.Length]; - - if (stream.Read(buffer) != stream.Length) - { - throw new InvalidOperationException("Failed to read stream."); - } - - return From(buffer, encoding); - } - - /// - /// Creates an from a . - /// - /// The bytes to be read. - /// The text encoding. - /// An effect. - public static Effect From(ReadOnlySpan bytes, Encoding? encoding = null) - { - return From((encoding ?? Encoding.UTF8).GetString(bytes)); - } - - /// - /// Creates an from a . - /// - /// The text to be compiled. - /// An effect. - public static Effect From(string text) - { - string code = sh_common + text; - - var shVert = ShaderCode.From(code, ShaderStage.Vertex, sh_vert, ShaderLanguage.HLSL); - var shFrag = ShaderCode.From(code, ShaderStage.Fragment, sh_frag, ShaderLanguage.HLSL); - - var shVertReflect = shVert.Reflect(); - var shFragReflect = shFrag.Reflect(); - - var parameters = new Dictionary(); - - if (shVertReflect.Uniforms is not null) - { - foreach (var buffer in shVertReflect.Uniforms) - parameters[buffer.Name] = new Parameter(buffer.Name, buffer.Binding); - } - - if (shVertReflect.Textures is not null) - { - foreach (var texture in shVertReflect.Textures) - parameters[texture.Name] = new Parameter(texture.Name, texture.Binding); - } - - if (shFragReflect.Uniforms is not null) - { - foreach (var buffer in shFragReflect.Uniforms) - parameters[buffer.Name] = new Parameter(buffer.Name, buffer.Binding); - } - - if (shFragReflect.Textures is not null) - { - foreach (var texture in shFragReflect.Textures) - parameters[texture.Name] = new Parameter(texture.Name, texture.Binding); - } - - return new(shVert, shFrag, parameters); - } - - public bool Equals(Effect other) - { - return ((IStructuralEquatable)shaderCode).Equals(other.shaderCode, EqualityComparer.Default); - } - - public override bool Equals([NotNullWhen(true)] object? obj) - { - return obj is Effect effect && Equals(effect); - } - - public override int GetHashCode() - { - return ((IStructuralEquatable)shaderCode).GetHashCode(EqualityComparer.Default); - } - - public static bool operator ==(Effect left, Effect right) - { - return left.Equals(right); - } - - public static bool operator !=(Effect left, Effect right) - { - return !(left == right); - } - - public static implicit operator ShaderCode[](Effect effect) => effect.shaderCode; - - private const string sh_frag = "Pixel"; - private const string sh_vert = "Vertex"; - - private const string sh_common = -@" -#define P_MATRIX g_internal_ProjMatrix -#define V_MATRIX g_internal_ViewMatrix -#define M_MATRIX g_internal_ModelMatrix -#define OBJECT_TO_CLIP(a) mul(mul(V_MATRIX, M_MATRIX), a) -#define OBJECT_TO_VIEW(a) mul(P_MATRIX, OBJECT_TO_CLIP(a)) - -cbuffer g_internal_Transform : register(b89) -{ - float4x4 g_internal_ProjMatrix; - float4x4 g_internal_ViewMatrix; - float4x4 g_internal_ModelMatrix; -}; -"; -} diff --git a/source/Vignette/Rendering/Material.cs b/source/Vignette/Rendering/Material.cs deleted file mode 100644 index 93496e9..0000000 --- a/source/Vignette/Rendering/Material.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Sekai.Graphics; - -namespace Vignette.Rendering; - -/// -/// Represents a collection of named shader resources. -/// -public sealed class Material -{ - /// - /// The effect used by this material. - /// - public Effect Effect { get; } - - private readonly Dictionary properties = new(); - - private Material(Effect effect) - { - Effect = effect; - } - - /// - /// Sets a named texture on this . - /// - /// The property name. - /// The texture to set. - public void SetProperty(string name, Texture texture) - { - if ((texture.Usage & TextureUsage.Resource) == 0) - { - throw new ArgumentException($"The texture must have the {nameof(TextureUsage.Resource)} flag to be used on materials."); - } - - set(name, texture); - } - - /// - /// Sets a named buffer on this . - /// - /// The property name. - /// The buffer to set. - /// Thrown when the buffer is not a . - public void SetProperty(string name, GraphicsBuffer buffer) - { - if (buffer.Type is not BufferType.Uniform) - { - throw new ArgumentException($"The buffer must be a {nameof(BufferType.Uniform)}.", nameof(buffer)); - } - - set(name, buffer); - } - - /// - /// Gets the value of a named property stored on this . - /// - /// The property type. - /// The property name. - /// The property value. - /// Thrown when the property does not exist on this . - /// Thrown when the property type does not match the type argument. - public T GetProperty(string name) - where T : class - { - if (!properties.TryGetValue(name, out var prop)) - { - throw new KeyNotFoundException($"Material has no property named \"{name}\"."); - } - - if (prop is not Property typedProp) - { - throw new InvalidCastException($"Property value cannot be casted to {typeof(T)}."); - } - - return typedProp.Value; - } - - /// - /// Checks whether a named property is present on this . - /// - /// The property type. - /// The property name. - /// if the property exists. Otherwise, returns . - public bool HasProperty(string name) - where T : class - { - if (!properties.TryGetValue(name, out var prop)) - { - return false; - } - - return prop is Property; - } - - /// - /// Gets the value of a named property. - /// - /// The property type. - /// The property name. - /// The returned value. - /// if the value has returned. Otherwise, returns . - public bool TryGetProperty(string name, [NotNullWhen(true)] out T? value) - where T : class - { - if (!properties.TryGetValue(name, out var prop)) - { - value = null; - return false; - } - - if (prop is not Property typedProp) - { - value = null; - return false; - } - - value = typedProp.Value; - return true; - } - - /// - /// Removes a named property from this . - /// - /// The property name. - /// if the property has been removed. Otherwise, returns . - public bool RemoveProperty(string name) - { - return properties.Remove(name); - } - - /// - /// Removes a named property of a given type from this . - /// - /// The property type. - /// The property name. - /// The removed property's value. - /// if the property has been removed. Otherwise, returns . - public bool RemoveProperty(string name, [NotNullWhen(true)] out T? value) - where T : class - { - if (!properties.TryGetValue(name, out var prop)) - { - value = null; - return false; - } - - if (prop is not Property typedProp) - { - value = null; - return false; - } - - value = typedProp.Value; - properties.Remove(name); - return true; - } - - /// - /// Creates a new . - /// - /// - /// - public static Material Create(Effect effect) - { - return new Material(effect); - } - - /// - /// Creates a deep clone of an existing . - /// - /// A copy of a . - public static Material Create(Material other) - { - var m = new Material(other.Effect); - - foreach (var prop in other.properties.Values) - { - switch (prop) - { - case Property textureProp: - m.SetProperty(prop.Name, textureProp.Value); - break; - - case Property bufferProp: - m.SetProperty(prop.Name, bufferProp.Value); - break; - } - } - - return m; - } - - private void set(string name, T value) - where T : class - { - if (properties.TryGetValue(name, out var prop)) - { - if (prop is not Property typedProp) - { - throw new InvalidCastException($"Property value cannot be casted to {typeof(T)}."); - } - - typedProp.Value = value; - } - else - { - properties[name] = new Property(name, value); - } - } - - private interface IProperty - { - string Name { get; } - } - - private struct Property : IProperty - where T : class - { - public string Name { get; } - public T Value { get; set; } - - public Property(string name, T value) - { - Name = name; - Value = value; - } - } -} diff --git a/source/Vignette/Rendering/Parameter.cs b/source/Vignette/Rendering/Parameter.cs deleted file mode 100644 index 994694d..0000000 --- a/source/Vignette/Rendering/Parameter.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Diagnostics.CodeAnalysis; - -namespace Vignette.Rendering; - -/// -/// Represents a typed parameter in an . -/// -/// The parameter type. -public readonly struct Parameter : IParameter, IEquatable> - where T : class -{ - public string Name { get; } - - public int Binding { get; } - - internal Parameter(string name, int binding) - { - Name = name; - Binding = binding; - } - - public bool Equals(Parameter other) - { - return Name.Equals(other.Name) && Binding.Equals(other.Binding); - } - - public override bool Equals([NotNullWhen(true)] object? obj) - { - return obj is Parameter parameter && Equals(parameter); - } - - public override int GetHashCode() - { - return HashCode.Combine(Name, Binding); - } - - public static bool operator ==(Parameter left, Parameter right) - { - return left.Equals(right); - } - - public static bool operator !=(Parameter left, Parameter right) - { - return !(left == right); - } -} - -/// -/// Represents a parameter in an . -/// -public interface IParameter -{ - /// - /// The parameter's name. - /// - string Name { get; } - - /// - /// The parameter's binding slot. - /// - int Binding { get; } -} diff --git a/source/Vignette/Rendering/Primitives/IPrimitive.cs b/source/Vignette/Rendering/Primitives/IPrimitive.cs deleted file mode 100644 index fde8b3e..0000000 --- a/source/Vignette/Rendering/Primitives/IPrimitive.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using Sekai.Graphics; -using Vignette.Rendering.Vertices; - -namespace Vignette.Rendering.Primitives; - -/// -/// Represents a geometric primitive. -/// -public interface IPrimitive -{ - /// - /// Gets the number of vertices of this primitive. - /// - int VertexCount { get; } - - /// - /// Gets the primitive drawing mode. - /// - PrimitiveType Mode { get; } - - /// - /// Gets the indices of this primitive. - /// - ReadOnlySpan GetIndices(); - - /// - /// Gets the vertex data of this primitive. - /// - ReadOnlySpan GetVertices(); -} - -/// -/// Represents a geometric primitive. -/// -/// The type of vertices this primitive contains. -public interface IPrimitive : IPrimitive - where T : unmanaged, IVertex -{ - /// - /// Gets the vertices of this primitive. - /// - new ReadOnlySpan GetVertices(); -} diff --git a/source/Vignette/Rendering/Primitives/Line.cs b/source/Vignette/Rendering/Primitives/Line.cs deleted file mode 100644 index 95320d2..0000000 --- a/source/Vignette/Rendering/Primitives/Line.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using Sekai.Graphics; -using Vignette.Rendering.Vertices; - -namespace Vignette.Rendering.Primitives; - -/// -/// A geometric primitive with 2 points. -/// -/// The vertex type. -[StructLayout(LayoutKind.Sequential)] -public struct Line : IPrimitive, IEquatable> - where T : unmanaged, IVertex, IEquatable -{ - /// - /// The start vertex. - /// - public T Start; - - /// - /// The end vertex. - /// - public T End; - - /// - /// Creates a line. - /// - /// The start vertex. - /// The end vertex. - public Line(T start, T end) - { - Start = start; - End = end; - } - - readonly PrimitiveType IPrimitive.Mode => PrimitiveType.LineList; - - readonly int IPrimitive.VertexCount => 2; - - readonly ReadOnlySpan IPrimitive.GetIndices() => indices; - - ReadOnlySpan IPrimitive.GetVertices() => MemoryMarshal.CreateReadOnlySpan(ref Start, 2); - - readonly ReadOnlySpan IPrimitive.GetVertices() => MemoryMarshal.AsBytes(((IPrimitive)this).GetVertices()); - - public readonly bool Equals(Line other) - { - return Start.Equals(other.Start) && End.Equals(other.End); - } - - public override readonly bool Equals([NotNullWhen(true)] object? obj) - { - return obj is Line line && Equals(line); - } - - public override readonly int GetHashCode() - { - return HashCode.Combine(Start, End); - } - - public static bool operator ==(Line left, Line right) - { - return left.Equals(right); - } - - public static bool operator !=(Line left, Line right) - { - return !(left == right); - } - - private static readonly short[] indices = new short[] { 0, 1 }; -} diff --git a/source/Vignette/Rendering/Primitives/Mesh.cs b/source/Vignette/Rendering/Primitives/Mesh.cs deleted file mode 100644 index 0d8ac89..0000000 --- a/source/Vignette/Rendering/Primitives/Mesh.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using Sekai.Graphics; - -namespace Vignette.Rendering.Primitives; - -/// -/// A primitive with an arbitrary shape. -/// -public struct Mesh : IPrimitive, IEquatable -{ - /// - /// Determines how the vertices are used when drawn. - /// - public MeshDrawMode Mode { get; set; } - - /// - /// Determines the vertex format of this mesh. - /// - public MeshFormat Format { get; } - - public ReadOnlySpan Vertices - { - readonly get => vertices is null ? Span.Empty : vertices; - set => vertices = value.ToArray(); - } - - public ReadOnlySpan Indices - { - readonly get => indices is null ? Span.Empty : indices; - set => indices = value.ToArray(); - } - - private byte[]? vertices; - private short[]? indices; - - /// - /// Creates a new . - /// - /// The format defining the mesh vertices. - public Mesh(MeshFormat format) - { - Format = format; - } - - readonly int IPrimitive.VertexCount => Vertices.Length / Format.SizeOfFormat(); - readonly PrimitiveType IPrimitive.Mode => Mode.AsPrimitiveType(); - readonly ReadOnlySpan IPrimitive.GetIndices() => indices is null ? Span.Empty : indices; - readonly ReadOnlySpan IPrimitive.GetVertices() => vertices is null ? Span.Empty : vertices; - - public readonly bool Equals(Mesh other) - { - return Mode == other.Mode && - Format == other.Format && - indices is not null && ((IStructuralEquatable)indices).Equals(other.indices, EqualityComparer.Default) && - vertices is not null && ((IStructuralEquatable)vertices).Equals(other.vertices, EqualityComparer.Default); - } - - public override readonly bool Equals([NotNullWhen(true)] object? obj) - { - return obj is Mesh mesh && Equals(mesh); - } - - public override readonly int GetHashCode() - { - int hash = HashCode.Combine(Mode, Format); - - if (indices is not null) - { - hash += ((IStructuralEquatable)indices).GetHashCode(EqualityComparer.Default); - } - - if (vertices is not null) - { - hash += ((IStructuralEquatable)vertices).GetHashCode(EqualityComparer.Default); - } - - return hash; - } - - public static bool operator ==(Mesh left, Mesh right) - { - return left.Equals(right); - } - - public static bool operator !=(Mesh left, Mesh right) - { - return !(left == right); - } -} - -internal static class MeshFormatExtensions -{ - public static InputLayoutFormat AsLayoutFormat(this MeshAttribteType type) => type switch - { - MeshAttribteType.Byte => InputLayoutFormat.Byte, - MeshAttribteType.UnsignedByte => InputLayoutFormat.UnsignedByte, - MeshAttribteType.Short => InputLayoutFormat.Short, - MeshAttribteType.UnsignedShort => InputLayoutFormat.UnsignedShort, - MeshAttribteType.Int => InputLayoutFormat.Int, - MeshAttribteType.UnsignedInt => InputLayoutFormat.UnsignedInt, - MeshAttribteType.Half => InputLayoutFormat.Half, - MeshAttribteType.Float => InputLayoutFormat.Float, - MeshAttribteType.Double => InputLayoutFormat.Double, - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; - - public static PrimitiveType AsPrimitiveType(this MeshDrawMode mode) => mode switch - { - MeshDrawMode.Points => PrimitiveType.PointList, - MeshDrawMode.Triangles => PrimitiveType.TriangleList, - MeshDrawMode.TriangleStrip => PrimitiveType.TriangleStrip, - MeshDrawMode.Lines => PrimitiveType.LineList, - MeshDrawMode.LineStrip => PrimitiveType.LineStrip, - _ => throw new ArgumentOutOfRangeException(nameof(mode)), - }; - - public static InputLayoutDescription AsDescription(this MeshFormat format) - { - if (!formats.TryGetValue(format, out var layout)) - { - var members = new InputLayoutMember[format.Attributes.Length]; - - for (int i = 0; i < members.Length; i++) - { - members[i] = new InputLayoutMember - ( - format.Attributes[i].Count, - format.Attributes[i].Normalized, - format.Attributes[i].Type.AsLayoutFormat() - ); - } - - layout = new InputLayoutDescription(members); - formats.Add(format, layout); - } - - return layout; - } - - public static int SizeOfFormat(this MeshAttribteType type) => type.AsLayoutFormat().SizeOfFormat(); - - public static int SizeOfFormat(this MeshAttribute attrib) => attrib.SizeOfFormat() * attrib.Count; - - public static int SizeOfFormat(this MeshFormat format) - { - int total = 0; - - for (int i = 0; i < format.Attributes.Length; i++) - { - total += format.Attributes[i].SizeOfFormat(); - } - - return total; - } - - private static readonly Dictionary formats = new(); -} diff --git a/source/Vignette/Rendering/Primitives/MeshAttribteType.cs b/source/Vignette/Rendering/Primitives/MeshAttribteType.cs deleted file mode 100644 index d4d1081..0000000 --- a/source/Vignette/Rendering/Primitives/MeshAttribteType.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -namespace Vignette.Rendering.Primitives; - -/// -/// An enumeration of mesh attribute types. -/// -public enum MeshAttribteType -{ - /// - /// An 8 bit signed integer. - /// - Byte, - - /// - /// An 8 bit unsigned integer. - /// - UnsignedByte, - - /// - /// A 16 bit signed integer. - /// - Short, - - /// - /// A 16 bit unsigned integer. - /// - UnsignedShort, - - /// - /// A 32 bit signed integer. - /// - Int, - - /// - /// A 32 bit unsigned integer. - /// - UnsignedInt, - - /// - /// A 16 bit floating point number. - /// - Half, - - /// - /// A 32 bit floating point number. - /// - Float, - - /// - /// A 64 bit floating point number. - /// - Double, -} diff --git a/source/Vignette/Rendering/Primitives/MeshDrawMode.cs b/source/Vignette/Rendering/Primitives/MeshDrawMode.cs deleted file mode 100644 index 4765b89..0000000 --- a/source/Vignette/Rendering/Primitives/MeshDrawMode.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -namespace Vignette.Rendering.Primitives; - -/// -/// An enumeration of mesh drawing modes. -/// -public enum MeshDrawMode -{ - /// - /// The vertices are ordered as a list of triangles. - /// - Triangles, - - /// - /// The vertices are ordered as a strip of triangles. - /// - TriangleStrip, - - /// - /// The vertices are ordered as a list of lines. - /// - Lines, - - /// - /// The vertices are ordered as a strip of lines. - /// - LineStrip, - - /// - /// The vertices are ordered as a list of points. - /// - Points, -} diff --git a/source/Vignette/Rendering/Primitives/MeshFormat.cs b/source/Vignette/Rendering/Primitives/MeshFormat.cs deleted file mode 100644 index 2bf76fe..0000000 --- a/source/Vignette/Rendering/Primitives/MeshFormat.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Vignette.Rendering.Primitives; - -/// -/// Defines a mesh's vertex format. -/// -public readonly struct MeshFormat : IEquatable -{ - /// - /// The attributes that make up this format. - /// - public MeshAttribute[] Attributes { get; } - - /// - /// Creates a . - /// - /// The attributes that make up this format. - public MeshFormat(params MeshAttribute[] attributes) - { - Attributes = attributes; - } - - public bool Equals(MeshFormat other) - { - return Attributes is not null && other.Attributes is not null && ((IStructuralEquatable)Attributes).Equals(other.Attributes, EqualityComparer.Default); - } - - public override readonly bool Equals([NotNullWhen(true)] object? obj) - { - return obj is MeshFormat format && Equals(format); - } - - public override readonly int GetHashCode() - { - return Attributes is not null ? ((IStructuralEquatable)Attributes).GetHashCode(EqualityComparer.Default) : base.GetHashCode(); - } - - public static bool operator ==(MeshFormat left, MeshFormat right) - { - return left.Equals(right); - } - - public static bool operator !=(MeshFormat left, MeshFormat right) - { - return !(left == right); - } -} - -/// -/// Defines a single mesh attribute. -/// -/// The attribute type. -/// The component count. -/// Whether values should be normalized. -public readonly record struct MeshAttribute(MeshAttribteType Type, int Count, bool Normalized = false); diff --git a/source/Vignette/Rendering/Primitives/Quad.cs b/source/Vignette/Rendering/Primitives/Quad.cs deleted file mode 100644 index c31f9b6..0000000 --- a/source/Vignette/Rendering/Primitives/Quad.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using Sekai.Graphics; -using Vignette.Rendering.Vertices; - -namespace Vignette.Rendering.Primitives; - -/// -/// A planar figure with 4 points. -/// -/// The vertex type. -[StructLayout(LayoutKind.Sequential)] -public struct Quad : IPrimitive, IEquatable> - where T : unmanaged, IVertex, IEquatable -{ - /// - /// The top left vertex. - /// - public T TopLeft; - - /// - /// The bottom left vertex. - /// - public T BottomLeft; - - /// - /// The bottom right vertex. - /// - public T BottomRight; - - /// - /// The top right vertex. - /// - public T TopRight; - - /// - /// Creates a quad - /// - /// The top left vertex. - /// The bottom left vertex. - /// The bottom right vertex. - /// The top right vertex. - public Quad(T topLeft, T bottomLeft, T bottomRight, T topRight) - { - TopLeft = topLeft; - BottomLeft = bottomLeft; - BottomRight = bottomRight; - TopRight = topRight; - } - - public readonly bool Equals(Quad other) - { - return TopLeft.Equals(other.TopLeft) && BottomLeft.Equals(other.BottomLeft) && BottomRight.Equals(other.BottomRight) && TopRight.Equals(other.TopRight); - } - - public override readonly bool Equals([NotNullWhen(true)] object? obj) - { - return obj is Quad quad && Equals(quad); - } - - public override readonly int GetHashCode() - { - return HashCode.Combine(TopLeft, BottomLeft, BottomRight, TopRight); - } - - readonly PrimitiveType IPrimitive.Mode => PrimitiveType.TriangleList; - - readonly int IPrimitive.VertexCount => 4; - - readonly ReadOnlySpan IPrimitive.GetIndices() => indices; - - ReadOnlySpan IPrimitive.GetVertices() => MemoryMarshal.CreateReadOnlySpan(ref TopLeft, 4); - - readonly ReadOnlySpan IPrimitive.GetVertices() => MemoryMarshal.AsBytes(((IPrimitive)this).GetVertices()); - - private static readonly short[] indices = new short[] { 0, 1, 3, 2, 3, 1 }; - - public static bool operator ==(Quad left, Quad right) - { - return left.Equals(right); - } - - public static bool operator !=(Quad left, Quad right) - { - return !(left == right); - } -} diff --git a/source/Vignette/Rendering/RenderContext.cs b/source/Vignette/Rendering/RenderContext.cs deleted file mode 100644 index 2f15863..0000000 --- a/source/Vignette/Rendering/RenderContext.cs +++ /dev/null @@ -1,531 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Collections.Generic; -using System.Numerics; -using System.Runtime.CompilerServices; -using Sekai.Graphics; -using Sekai.Mathematics; -using Vignette.Rendering.Primitives; -using Vignette.Rendering.Vertices; - -namespace Vignette.Rendering; - -/// -/// A render context. -/// -public abstract class RenderContext : IDisposable -{ - private RenderContextState state; - private bool isDisposed; - private int currentIndexLength; - private int currentVertexCount; - private int currentBufferLength; - private readonly byte[] vbo; - private readonly short[] ibo; - private readonly Material material; - private readonly Renderer renderer; - private readonly Stack states = new(); - - internal RenderContext(Renderer renderer, Material material, Matrix4x4 projection) - { - vbo = new byte[Renderer.BUFFER_SIZE]; - ibo = new short[Renderer.BUFFER_SIZE / Unsafe.SizeOf()]; - - state = new RenderContextState - { - View = Matrix4x4.Identity, - Scale = Matrix4x4.Identity, - Rotation = Matrix4x4.Identity, - Translation = Matrix4x4.Identity, - Projection = projection, - Scissor = Rectangle.Empty, - Material = material, - Blend = BlendStateDescription.NonPremultiplied, - Rasterizer = new RasterizerStateDescription - ( - FaceCulling.None, - FaceWinding.CounterClockwise, - FillMode.Solid, - false - ), - Layout = new InputLayoutDescription(Array.Empty()), - DepthStencil = new DepthStencilStateDescription(false, false, ComparisonKind.Always), - }; - - this.renderer = renderer; - this.material = material; - } - - /// - /// Saves the current state. - /// - public void Save() - { - states.Push(state); - } - - /// - /// Restores the previous state. - /// - public void Restore() - { - states.TryPop(out state); - } - - /// - /// Sets the render target. - /// - /// The render target to make active. - /// - /// Setting to will use the default render target. - /// - public void SetRenderTarget(RenderTarget? target = null) - { - if (ReferenceEquals(state.Target, target)) - { - return; - } - - Flush(); - - state.Target = target; - } - - /// - /// Sets the material. - /// - /// The material to set. - /// - /// Setting to will use the default material. - /// - public void SetMaterial(Material? material = null) - { - material ??= this.material; - - if (ReferenceEquals(state.Material, material)) - { - return; - } - - Flush(); - - state.Material = material; - } - - /// - /// Sets the scissor rectangle. - /// - /// The scissor rectangle to set. - /// - /// Setting will disable the scissor. - /// - public void SetScissor(Rectangle scissor) - { - if (state.Scissor.Equals(scissor) && state.Rasterizer.Scissor == !scissor.IsEmpty) - { - return; - } - - Flush(); - - state.Scissor = scissor; - state.Rasterizer.Scissor = !scissor.IsEmpty; - } - - /// - /// Sets the face culling. - /// - /// The faces culled. - public void SetFaceCulling(FaceCulling culling) - { - if (state.Rasterizer.Culling == culling) - { - return; - } - - Flush(); - - state.Rasterizer.Culling = culling; - } - - /// - /// Sets the winding order of the vertices. - /// - /// The winding order. - public void SetFaceWinding(FaceWinding winding) - { - if (state.Rasterizer.Winding == winding) - { - return; - } - - Flush(); - - state.Rasterizer.Winding = winding; - } - - /// - /// Sets the fill mode of primitives. - /// - /// The fill mode. - public void SetFillMode(FillMode mode) - { - if (state.Rasterizer.Mode == mode) - { - return; - } - - Flush(); - - state.Rasterizer.Mode = mode; - } - - /// - /// Draws a . - /// - /// The mesh to draw. - public void DrawMesh(Mesh mesh) - { - Draw(mesh, mesh.Format.AsDescription()); - } - - /// - /// Sets depth testing parameters. - /// - /// Whether depth testing is enabled or not. - /// Whether the depth buffer should be written to if the test passes. - /// The depth test comparison function. - public void SetDepthTest(bool enabled, bool write, ComparisonKind comparison) - { - if (state.DepthStencil.DepthTest == enabled && state.DepthStencil.DepthMask == write && state.DepthStencil.DepthComparison == comparison) - { - return; - } - - Flush(); - - state.DepthStencil.DepthTest = enabled; - state.DepthStencil.DepthMask = write; - state.DepthStencil.DepthComparison = comparison; - } - - /// - /// Sets stencil testing parameters. - /// - /// Whether stencil testing is enabled or not. - /// The stencil read mask. - /// The stencil write mask. - public void SetStencilTest(bool enabled, byte read, byte write) - { - if (state.DepthStencil.StencilTest == enabled && state.DepthStencil.StencilReadMask == read && state.DepthStencil.StencilWriteMask == write) - { - return; - } - - Flush(); - - state.DepthStencil.StencilTest = enabled; - state.DepthStencil.StencilReadMask = read; - state.DepthStencil.StencilWriteMask = write; - } - - /// - /// Sets custom stencil parameters. - /// - /// The stencil reference value. - /// The operation performed for passing the front face stencil test. - /// The operation performed for failing the front face stencil test. - /// The operation performed for failing the front face depth test. - /// The comparison performed for the front face. - /// The operation performed for passing the back face stencil test. - /// The operation performed for failing the back face stencil test. - /// The operation performed for failing the back face depth test. - /// The comparison performed for the back face. - public void SetStencil(int reference, StencilOperation frontStencilPass, StencilOperation frontStencilFail, StencilOperation frontDepthFail, ComparisonKind frontComparison, StencilOperation backStencilPass, StencilOperation backStencilFail, StencilOperation backDepthFail, ComparisonKind backComparison) - { - if (state.Stencil == reference && - state.DepthStencil.Front.StencilPass == frontStencilPass && state.DepthStencil.Front.StencilFail == frontStencilFail && - state.DepthStencil.Front.DepthFail == frontDepthFail && state.DepthStencil.Front.Comparison == frontComparison && - state.DepthStencil.Back.StencilPass == backStencilPass && state.DepthStencil.Back.StencilFail == backStencilFail && - state.DepthStencil.Back.DepthFail == backDepthFail && state.DepthStencil.Back.Comparison == backComparison) - { - return; - } - - Flush(); - - state.Stencil = reference; - state.DepthStencil.Front.StencilPass = frontStencilPass; - state.DepthStencil.Front.StencilFail = frontStencilFail; - state.DepthStencil.Front.DepthFail = frontDepthFail; - state.DepthStencil.Front.Comparison = frontComparison; - state.DepthStencil.Back.StencilPass = backStencilPass; - state.DepthStencil.Back.StencilFail = backStencilFail; - state.DepthStencil.Back.DepthFail = backDepthFail; - state.DepthStencil.Back.Comparison = backComparison; - } - - /// - /// Sets stencil parameters for both the front and back faces. - /// - /// - /// The operation performed for passing the stencil test. - /// The operation performed for failing the stencil test. - /// The operation performed for failing the depth test. - /// The comparison performed. - public void SetStencil(int reference, StencilOperation pass, StencilOperation fail, StencilOperation depthFail, ComparisonKind comparison) - { - SetStencil(reference, pass, fail, depthFail, comparison, pass, fail, depthFail, comparison); - } - - /// - /// Sets custom blending parameters. - /// - /// Whether blending should be enabled or not. - /// The source color blending. - /// The source alpha blending. - /// The destination color blending. - /// The destination alpha blending. - /// The operation to perform between and . - /// The operation to perform between and . - public void SetBlend(bool enabled, BlendType srcColor, BlendType srcAlpha, BlendType dstColor, BlendType dstAlpha, BlendOperation colorOperation, BlendOperation alphaOperation) - { - if (state.Blend.Enabled == enabled && - state.Blend.SourceColor == srcColor && state.Blend.SourceAlpha == srcAlpha && - state.Blend.DestinationColor == dstColor && state.Blend.DestinationAlpha == dstAlpha && - state.Blend.ColorOperation == colorOperation && state.Blend.AlphaOperation == alphaOperation) - { - return; - } - - Flush(); - - state.Blend.Enabled = enabled; - state.Blend.SourceColor = srcColor; - state.Blend.SourceAlpha = srcAlpha; - state.Blend.DestinationColor = dstColor; - state.Blend.DestinationAlpha = dstAlpha; - state.Blend.ColorOperation = colorOperation; - state.Blend.AlphaOperation = alphaOperation; - } - - /// - /// Sets individual blending parameters. - /// - /// The source color blending. - /// The source alpha blending. - /// The destination color blending. - /// The destination alpha blending. - public void SetBlend(BlendType srcColor, BlendType srcAlpha, BlendType dstColor, BlendType dstAlpha) - { - SetBlend(true, srcColor, srcAlpha, dstColor, dstAlpha, BlendOperation.Add, BlendOperation.Add); - } - - /// - /// Sets the source and destination blending parameters. - /// - /// The source blending. - /// The destination blending. - public void SetBlend(BlendType source, BlendType destination) - { - SetBlend(source, source, destination, destination); - } - - /// - /// Sets a predefined blending operation. - /// - /// The predefined blending mode. - public void SetBlend(BlendMode mode) - { - switch (mode) - { - case BlendMode.Disabled: - SetBlend(false, BlendType.One, BlendType.Zero, BlendType.One, BlendType.Zero, BlendOperation.Add, BlendOperation.Add); - break; - - case BlendMode.AlphaBlend: - SetBlend(BlendType.One, BlendType.OneMinusSourceAlpha); - break; - - case BlendMode.Opaque: - SetBlend(BlendType.One, BlendType.Zero); - break; - - case BlendMode.Additive: - SetBlend(BlendType.SourceAlpha, BlendType.One); - break; - - case BlendMode.NonPremultiplied: - SetBlend(BlendType.SourceAlpha, BlendType.OneMinusSourceAlpha); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(mode)); - } - } - - /// - /// Sets the color write mask. - /// - /// The color write mask to set. - public void SetColorMask(ColorWriteMask mask) - { - if (state.Blend.WriteMask == mask) - { - return; - } - - Flush(); - - state.Blend.WriteMask = mask; - } - - /// - /// Sets the scale matrix. - /// - /// The scale matrix to set. - protected void Scale(Matrix4x4 scale) - { - if (state.Scale.Equals(scale)) - { - return; - } - - Flush(); - - state.Scale = scale; - } - - /// - /// Sets the rotation matrix. - /// - /// The rotation matrix to set. - protected void Rotate(Matrix4x4 rotation) - { - if (state.Rotation.Equals(rotation)) - { - return; - } - - Flush(); - - state.Rotation = rotation; - } - - /// - /// Sets the translation matrix. - /// - /// The translation matrix to set. - protected void Translate(Matrix4x4 translation) - { - if (state.Translation.Equals(translation)) - { - return; - } - - Flush(); - - state.Translation = translation; - } - - /// - /// Sets the view matrix. - /// - /// The view matrixt to set. - protected void LookAt(Matrix4x4 view) - { - if (state.View.Equals(view)) - { - return; - } - - Flush(); - - state.View = view; - } - - /// - /// Draws a . - /// - /// The primitive type. - /// The primitive vertex type. - /// The primitive to be drawn. - protected void Draw(in T primitive) - where T : unmanaged, IPrimitive - where U : unmanaged, IVertex, IEquatable - { - Draw(primitive, U.Layout); - } - - /// - /// Draws a primitive. - /// - /// The primitive to be drawn. - protected void Draw(in IPrimitive primitive, InputLayoutDescription layout) - { - if (state.Layout != layout || state.Primitive != primitive.Mode) - { - Flush(); - - state.Layout = layout; - state.Primitive = primitive.Mode; - } - - var indexs = primitive.GetIndices(); - var buffer = primitive.GetVertices(); - - if (currentBufferLength + buffer.Length >= vbo.Length || currentIndexLength + indexs.Length >= ibo.Length) - { - Flush(); - } - - for (int i = 0; i < indexs.Length; i++) - { - ibo[i + currentIndexLength] = (short)(currentVertexCount + indexs[i]); - } - - currentIndexLength += indexs.Length; - currentVertexCount += primitive.VertexCount; - - for (int i = 0; i < buffer.Length; i++) - { - vbo[i + currentBufferLength] = buffer[i]; - } - - currentBufferLength += buffer.Length; - } - - /// - /// Flushes and submits the buffered vertices and indices. - /// - protected void Flush() - { - if (currentBufferLength == 0 || currentVertexCount == 0 || currentIndexLength == 0) - { - return; - } - - renderer.Submit(state, vbo.AsSpan()[..currentBufferLength], ibo.AsSpan()[..currentIndexLength]); - - currentIndexLength = 0; - currentVertexCount = 0; - currentBufferLength = 0; - } - - public void Dispose() - { - if (isDisposed) - { - return; - } - - states.Clear(); - Flush(); - - isDisposed = true; - - GC.SuppressFinalize(this); - } -} diff --git a/source/Vignette/Rendering/RenderContext2D.cs b/source/Vignette/Rendering/RenderContext2D.cs deleted file mode 100644 index e54f6e4..0000000 --- a/source/Vignette/Rendering/RenderContext2D.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System.Numerics; -using Sekai.Mathematics; -using Vignette.Rendering.Primitives; -using Vignette.Rendering.Vertices; - -namespace Vignette.Rendering; - -/// -/// A 2D render context. -/// -public sealed class RenderContext2D : RenderContext -{ - private Color4 color = Color4.White; - - internal RenderContext2D(Renderer renderer, Material material, Matrix4x4 projection) - : base(renderer, material, projection) - { - } - - /// - /// Sets the color of the drawn psrimitives. - /// - /// The color to use. - /// - /// This is applicable to any primitive except the . - /// - public void SetColor(Color color) - { - this.color = color; - } - - /// - /// Translates the coordinate system. - /// - /// The amount to translate in each axis. - public void Translate(Vector2 translation) - { - Translate(translation.X, translation.Y); - } - - /// - /// Translates the coordinate system. - /// - /// The amount to translate in the X axis. - /// The amount to translate in the Y axis. - public void Translate(float x, float y) - { - Translate(Matrix4x4.CreateTranslation(new Vector3(x, y, 0))); - } - - /// - /// Rotates the coordinate system. - /// - /// The amount to rotate in radians. - public void Rotate(float radians) - { - Rotate(Matrix4x4.CreateRotationZ(radians)); - } - - /// - /// Scales the coordinate system. - /// - /// The amount to scale in each axis. - public void Scale(Vector2 scale) - { - Scale(scale.X, scale.Y); - } - - /// - /// Scales the coodinate system. - /// - /// The amount to scale in all axes. - public void Scale(float scale) - { - Scale(scale, scale); - } - - /// - /// Scales the coordinate system. - /// - /// The amount to scale in the X axis. - /// The amount to scale in the Y axis. - public void Scale(float x, float y) - { - Scale(Matrix4x4.CreateScale(x, y, 1)); - } - - /// - /// Draws a quadrilateral. - /// - /// The rectangle to draw. - public void DrawQuad(RectangleF rectangle) - { - var quad = new Quad - ( - new TexturedVertex - { - Position = new Vector3(rectangle.TopLeft, 0), - TexCoord = Vector2.Zero, - Color = color, - }, - new TexturedVertex - { - Position = new Vector3(rectangle.BottomLeft, 0), - TexCoord = Vector2.UnitX, - Color = color, - }, - new TexturedVertex - { - Position = new Vector3(rectangle.BottomRight, 0), - TexCoord = Vector2.One, - Color = color, - }, - new TexturedVertex - { - Position = new Vector3(rectangle.TopRight, 0), - TexCoord = Vector2.UnitY, - Color = color, - } - ); - - Draw, TexturedVertex>(quad); - } - - /// - /// Draws a rectangle. - /// - /// The rectangle position. - /// The rectangle size. - public void DrawQuad(Vector2 position, SizeF size) - { - DrawQuad(new RectangleF(position, size)); - } - - /// - /// Draws a rectangle. - /// - /// The rectangle's horizontal positon. - /// The rectangle's vertical position. - /// The rectangle's width. - /// The rectangle's height. - public void DrawQuad(float x, float y, float width, float height) - { - DrawQuad(new RectangleF(x, y, width, height)); - } - - /// - /// Draws a line. - /// - /// The start position. - /// The end position. - public void DrawLine(Vector2 start, Vector2 end) - { - var line = new Line - ( - new TexturedVertex - { - Position = new Vector3(start, 0), - TexCoord = Vector2.Zero, - Color = color, - }, - new TexturedVertex - { - Position = new Vector3(end, 0), - TexCoord = Vector2.Zero, - Color = color, - } - ); - - Draw, TexturedVertex>(line); - } - - /// - /// Draws a line. - /// - /// The starting x position. - /// The starting y position. - /// The ending x position. - /// The ending y position. - public void DrawLine(float x1, float y1, float x2, float y2) - { - DrawLine(new Vector2(x1, y1), new Vector2(x2, y2)); - } -} diff --git a/source/Vignette/Rendering/RenderContextState.cs b/source/Vignette/Rendering/RenderContextState.cs deleted file mode 100644 index d1953a3..0000000 --- a/source/Vignette/Rendering/RenderContextState.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Numerics; -using Sekai.Graphics; -using Sekai.Mathematics; - -namespace Vignette.Rendering; - -internal struct RenderContextState : IEquatable -{ - public int Stencil; - public Material Material; - public Matrix4x4 View; - public Matrix4x4 Scale; - public Matrix4x4 Rotation; - public Matrix4x4 Translation; - public Matrix4x4 Projection; - public Rectangle Scissor; - public RenderTarget? Target; - public PrimitiveType Primitive; - public BlendStateDescription Blend; - public InputLayoutDescription Layout; - public RasterizerStateDescription Rasterizer; - public DepthStencilStateDescription DepthStencil; - - public readonly bool Equals(RenderContextState other) - { - return Stencil == other.Stencil && - ReferenceEquals(Material, other.Material) && - ReferenceEquals(Target, other.Target) && - View.Equals(other.View) && - Scale.Equals(other.Scale) && - Rotation.Equals(other.Rotation) && - Translation.Equals(other.Translation) && - Projection.Equals(other.Projection) && - Scissor.Equals(other.Scissor) && - Primitive == other.Primitive && - Blend.Equals(other.Blend) && - Layout.Equals(other.Layout) && - Rasterizer.Equals(other.Rasterizer) && - DepthStencil.Equals(other.DepthStencil); - } - - public override readonly bool Equals(object? obj) - { - return obj is RenderContextState state && Equals(state); - } - - public override readonly int GetHashCode() - { - var hash = new HashCode(); - hash.Add(Stencil); - hash.Add(Material); - hash.Add(View); - hash.Add(Scale); - hash.Add(Rotation); - hash.Add(Translation); - hash.Add(Projection); - hash.Add(Scissor); - hash.Add(Primitive); - hash.Add(Blend); - hash.Add(Layout); - hash.Add(Rasterizer); - hash.Add(DepthStencil); - return hash.ToHashCode(); - } - - public static bool operator ==(RenderContextState left, RenderContextState right) - { - return left.Equals(right); - } - - public static bool operator !=(RenderContextState left, RenderContextState right) - { - return !(left == right); - } -} diff --git a/source/Vignette/Rendering/RenderTarget.cs b/source/Vignette/Rendering/RenderTarget.cs deleted file mode 100644 index dfd05eb..0000000 --- a/source/Vignette/Rendering/RenderTarget.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using Sekai.Graphics; -using NativeTexture = Sekai.Graphics.Texture; - -namespace Vignette.Rendering; - -/// -/// An output target. -/// -public sealed class RenderTarget : IDisposable -{ - /// - /// The render target's width. - /// - public int Width { get; } - - /// - /// The render target's height. - /// - public int Height { get; } - - /// - /// The color texture. - /// - public Texture Color { get; } - - /// - /// The depth texture. - /// - public Texture? Depth { get; } - - private bool isDisposed; - private readonly Framebuffer framebuffer; - - private RenderTarget(int width, int height, Framebuffer framebuffer, NativeTexture color, NativeTexture? depth) - { - Depth = depth is not null ? new Texture(depth) : null; - Color = new Texture(color); - Width = width; - Height = height; - this.framebuffer = framebuffer; - } - - public static RenderTarget Create(GraphicsDevice device, int width, int height, int layers = 1, PixelFormat colorFormat = PixelFormat.R8G8B8A8_UNorm, PixelFormat? depthFormat = null) - { - if (layers <= 0) - { - throw new ArgumentOutOfRangeException(nameof(layers), "Layer count must be greater than zero."); - } - - if (colorFormat.IsDepthStencil()) - { - throw new ArgumentException("Invalid color format.", nameof(colorFormat)); - } - - if (depthFormat.HasValue && !depthFormat.Value.IsDepthStencil()) - { - throw new ArgumentException("Invalid depth format.", nameof(depthFormat)); - } - - var colorDescriptor = new TextureDescription - ( - TextureType.Texture2D, - width, - height, - 1, - colorFormat, - 1, - layers, - TextureUsage.RenderTarget | TextureUsage.Resource - ); - - var color = device.CreateTexture(colorDescriptor); - - var colorAttachments = new FramebufferAttachment[layers]; - - for (int i = 0; i < layers; i++) - { - colorAttachments[i] = new FramebufferAttachment(color, i, 0); - } - - if (!depthFormat.HasValue) - { - return new RenderTarget(width, height, device.CreateFramebuffer(null, colorAttachments), color, null); - } - - var depthDescriptor = new TextureDescription - ( - TextureType.Texture2D, - width, - height, - 1, - depthFormat.Value, - 1, - 1, - TextureUsage.RenderTarget | TextureUsage.Resource - ); - - var depth = device.CreateTexture(depthDescriptor); - var depthAttachment = new FramebufferAttachment(depth, 0, 0); - - return new RenderTarget(width, height, device.CreateFramebuffer(depthAttachment, colorAttachments), color, depth); - } - - public void Dispose() - { - if (isDisposed) - { - return; - } - - Color.Dispose(); - Depth?.Dispose(); - framebuffer.Dispose(); - - isDisposed = true; - } - - public static explicit operator Framebuffer(RenderTarget target) => target.framebuffer; -} diff --git a/source/Vignette/Rendering/Renderer.cs b/source/Vignette/Rendering/Renderer.cs deleted file mode 100644 index 241625d..0000000 --- a/source/Vignette/Rendering/Renderer.cs +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Numerics; -using Sekai.Graphics; -using Sekai.Mathematics; - -namespace Vignette.Rendering; - -/// -/// Performs rendering operations and screen presentation. -/// -public sealed class Renderer -{ - /// - /// A 1x1 white pixel texture. - /// - public Texture WhitePixel { get; } - - private readonly GraphicsBuffer ibo; - private readonly GraphicsBuffer vbo; - private readonly GraphicsBuffer trs; - private readonly GraphicsDevice device; - private readonly Material defaultMaterial; - private readonly Dictionary caches = new(); - - internal Renderer(GraphicsDevice device) - { - ibo = device.CreateBuffer(BufferType.Index, BUFFER_SIZE, true); - vbo = device.CreateBuffer(BufferType.Vertex, BUFFER_SIZE, true); - trs = device.CreateBuffer(BufferType.Uniform, 3, true); - - Span whitePixelData = stackalloc byte[] { 255, 255, 255, 255 }; - WhitePixel = Texture.Create(device, 1, 1, 1, 1, PixelFormat.R8G8B8A8_UNorm, TextureUsage.Resource); - WhitePixel.SetData((ReadOnlySpan)whitePixelData, 0, 0, 0, 0, 0, 1, 1, 0); - - defaultMaterial = Material.Create(Effect.From(sh_default)); - defaultMaterial.SetProperty("AlbedoTexture", WhitePixel); - - this.device = device; - } - - /// - /// Begins a 2D rendering context. - /// - /// The rectangle to define the screen ortho. - /// The near clipping plane. - /// The far clipping plane. - /// A 2D rendering context. - public RenderContext2D Begin2D(RectangleF rectangle, float nearPlane = 0.0f, float farPlane = 1.0f) - { - return new RenderContext2D(this, defaultMaterial, Matrix4x4.CreateOrthographicOffCenter(rectangle.Left, rectangle.Right, rectangle.Bottom, rectangle.Top, nearPlane, farPlane)); - } - - /// - /// Begins a 2D rendering context. - /// - /// The top left offset of the rectangle. - /// The size of the rectangle. - /// The near clipping plane. - /// The far clipping plane. - /// A 2D rendering context. - public RenderContext2D Begin2D(Vector2 position, SizeF size, float nearPlane = 0.0f, float farPlane = 1.0f) - { - return Begin2D(new RectangleF(position, size), nearPlane, farPlane); - } - - /// - /// Begins a 2D rendering context. - /// - /// The size of the rectangle. - /// The near clipping plane. - /// The far clipping plane. - /// A 2D rendering context. - public RenderContext2D Begin2D(SizeF size, float nearPlane = 0.0f, float farPlane = 1.0f) - { - return Begin2D(new RectangleF(Vector2.Zero, size), nearPlane, farPlane); - } - - /// - /// Begins a 2D rendering context. - /// - /// The x offset of the rectangle. - /// The y offset of the rectangle. - /// The width of the rectangle. - /// The height of the rectangle. - /// The near clipping plane. - /// The far clipping plane. - /// A 2D rendering context. - public RenderContext2D Begin2D(float x, float y, float width, float height, float nearPlane = 0.0f, float farPlane = 1.0f) - { - return Begin2D(new Vector2(x, y), new SizeF(width, height), nearPlane, farPlane); - } - - /// - /// Begins a 2D rendering context. - /// - /// The width of the rectangle. - /// The height of the rectangle. - /// The near clipping plane. - /// The far clipping plane. - /// A 2D rendering context. - public RenderContext2D Begin2D(float width, float height, float nearPlane = 0.0f, float farPlane = 1.0f) - { - return Begin2D(new SizeF(width, height), nearPlane, farPlane); - } - - /// - /// Submits the and draws the and . - /// - /// The context state. - /// The vertex data. - /// The index data. - internal void Submit(in RenderContextState state, ReadOnlySpan vertices, ReadOnlySpan indices) - { - device.SetScissor(state.Scissor); - - var rasterizerCache = getCache(); - - if (!rasterizerCache.TryGetValue(state.Rasterizer, out var rasterizer)) - { - rasterizer = device.CreateRasterizerState(state.Rasterizer); - rasterizerCache[state.Rasterizer] = rasterizer; - } - - device.SetRasterizerState(rasterizer); - - var blendCache = getCache(); - - if (!blendCache.TryGetValue(state.Blend, out var blend)) - { - blend = device.CreateBlendState(state.Blend); - blendCache[state.Blend] = blend; - } - - device.SetBlendState(blend); - - var depthStencilCache = getCache(); - - if (!depthStencilCache.TryGetValue(state.DepthStencil, out var depthStencil)) - { - depthStencil = device.CreateDepthStencilState(state.DepthStencil); - depthStencilCache[state.DepthStencil] = depthStencil; - } - - device.SetDepthStencilState(depthStencil, state.Stencil); - - var samplerCache = getCache(); - - foreach (var parameter in state.Material.Effect.GetParameters()) - { - switch (parameter) - { - case Parameter textureParam when state.Material.TryGetProperty(textureParam.Name, out var texture): - { - var samplerState = new SamplerDescription - ( - texture.Filter, - texture.AddressU, - texture.AddressV, - texture.AddressW, - texture.MaxAnisotropy, - texture.BorderColor, - texture.MinimumLOD, - texture.MaximumLOD, - texture.LODBias - ); - - if (!samplerCache.TryGetValue(samplerState, out var sampler)) - { - sampler = device.CreateSampler(samplerState); - samplerCache[samplerState] = sampler; - } - - device.SetTexture((Sekai.Graphics.Texture)texture, sampler, (uint)textureParam.Binding); - break; - } - - case Parameter bufferParam when state.Material.TryGetProperty(bufferParam.Name, out var buffer): - { - device.SetUniformBuffer(buffer, (uint)bufferParam.Binding); - break; - } - } - } - - var shaderCache = getCache(); - - if (!shaderCache.TryGetValue(state.Material.Effect, out var shader)) - { - shader = device.CreateShader(state.Material.Effect); - shaderCache[state.Material.Effect] = shader; - } - - device.SetShader(shader); - - var layoutCache = getCache(); - - if (!layoutCache.TryGetValue(state.Layout, out var layout)) - { - layout = device.CreateInputLayout(state.Layout); - layoutCache[state.Layout] = layout; - } - - if (state.Target is not null) - { - device.SetFramebuffer((Framebuffer)state.Target); - } - else - { - device.SetFramebuffer(null); - } - - Span transform = stackalloc Matrix4x4[] - { - state.Projection, - state.View, - state.Scale * state.Rotation * state.Translation - }; - - ibo.SetData(indices); - vbo.SetData(vertices); - trs.SetData((ReadOnlySpan)transform); - - device.SetUniformBuffer(trs, 89); - device.SetVertexBuffer(vbo, layout); - device.SetIndexBuffer(ibo, IndexType.UnsignedShort); - device.DrawIndexed(state.Primitive, (uint)indices.Length); - } - - private Dictionary getCache() - where T : struct, IEquatable - where U : class - { - if (!caches.TryGetValue(typeof(T), out var cache)) - { - cache = new Dictionary(); - caches.Add(typeof(T), cache); - } - - return (Dictionary)cache; - } - - internal const int BUFFER_SIZE = 8000; - - private const string sh_default = -@" -struct VSInput -{ - float3 Position : POSITION; - float2 TexCoord : TEXCOORD; - float4 Color : COLOR; -}; - -struct PSInput -{ - float4 Position : SV_POSITION; - float2 TexCoord : TEXCOORD; - float4 Color : COLOR; -}; - -Texture2D AlbedoTexture : register(t0); -SamplerState AlbedoSampler : register(s0); - -PSInput Vertex(in VSInput input) -{ - PSInput output; - - output.Color = input.Color; - output.Position = OBJECT_TO_VIEW(float4(input.Position, 1.0)); - output.TexCoord = input.TexCoord; - - return output; -} - -float4 Pixel(in PSInput input) : SV_TARGET -{ - return AlbedoTexture.Sample(AlbedoSampler, input.TexCoord) * input.Color; -} -"; -} diff --git a/source/Vignette/Rendering/Texture.cs b/source/Vignette/Rendering/Texture.cs deleted file mode 100644 index 31d9444..0000000 --- a/source/Vignette/Rendering/Texture.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using Sekai.Graphics; -using Sekai.Mathematics; -using NativeTexture = Sekai.Graphics.Texture; - -namespace Vignette.Rendering; - -/// -/// A graphics resource representing image or arbitrary data. -/// -public sealed class Texture : IDisposable -{ - /// - /// The texture width. - /// - public int Width => native.Width; - - /// - /// The texture height. - /// - public int Height => native.Height; - - /// - /// The texture depth. - /// - public int Depth => native.Depth; - - /// - /// The texture mipmap levels. - /// - public int Levels => native.Levels; - - /// - /// The texture layer count. - /// - public int Layers => native.Layers; - - /// - /// The texture type. - /// - public TextureType Type => native.Type; - - /// - /// The texture pixel format. - /// - public PixelFormat Format => native.Format; - - /// - /// The texture usage. - /// - public TextureUsage Usage => native.Usage; - - /// - /// The texture sample count. - /// - public TextureSampleCount Count => native.Count; - - /// - /// The texture filtering mode. - /// - public TextureFilter Filter = TextureFilter.MinMagMipPoint; - - /// - /// The texture address mode for the U component. - /// - public TextureAddress AddressU = TextureAddress.ClampToBorder; - - /// - /// The texture address mode for the V component. - /// - public TextureAddress AddressV = TextureAddress.ClampToBorder; - - /// - /// The texture address mode for the W component. - /// - public TextureAddress AddressW = TextureAddress.ClampToBorder; - - /// - /// The maximum anisotropy. - /// - public int MaxAnisotropy; - - /// - /// The texture border color when is used. - /// - public Color BorderColor = Color.White; - - /// - /// The minimum level of detail. - /// - public float MinimumLOD; - - /// - /// The maximum level of detail. - /// - public float MaximumLOD; - - /// - /// The bias to level of detail. - /// - public float LODBias; - - private bool isDisposed; - private readonly NativeTexture native; - - internal Texture(NativeTexture native) - { - this.native = native; - } - - public void SetData(ReadOnlySpan data, int level, int layer, int x, int y, int z, int width, int height, int depth) - where T : unmanaged - { - native.SetData(data, level, layer, x, y, z, width, height, depth); - } - - public void GetData(Span data, int level, int layer, int x, int y, int z, int width, int height, int depth) - where T : unmanaged - { - native.GetData(data, level, layer, x, y, z, width, height, depth); - } - - /// - /// Create a new 1D texture. - /// - /// The graphics device. - /// The width of the texture. - /// The mipmap levels of the texture. - /// The layer count of the texture. - /// The pixel format of the texture. - /// The texture's usage. - /// The texture's sample count. - /// A new 1D texture. - public static Texture Create(GraphicsDevice device, int width, int levels = 1, int layers = 1, PixelFormat format = PixelFormat.R8G8B8A8_UNorm, TextureUsage usage = TextureUsage.Resource, TextureSampleCount count = TextureSampleCount.Count1) - { - return create(device, width, 0, 0, levels, layers, TextureType.Texture1D, format, usage, count); - } - - /// - /// Create a new 2D texture. - /// - /// The graphics device. - /// The width of the texture. - /// The height of the texture. - /// The mipmap levels of the texture. - /// The layer count of the texture. - /// The pixel format of the texture. - /// The texture's usage. - /// The texture's sample count. - /// A new 2D texture. - public static Texture Create(GraphicsDevice device, int width, int height, int levels = 1, int layers = 1, PixelFormat format = PixelFormat.R8G8B8A8_UNorm, TextureUsage usage = TextureUsage.Resource, TextureSampleCount count = TextureSampleCount.Count1) - { - return create(device, width, height, 0, levels, layers, TextureType.Texture2D, format, usage, count); - } - - /// - /// Create a new 3D texture. - /// - /// The graphics device. - /// The width of the texture. - /// The height of the texture. - /// The depth of the texture. - /// The mipmap levels of the texture. - /// The layer count of the texture. - /// The pixel format of the texture. - /// The texture's usage. - /// The texture's sample count. - /// A new 3D texture. - public static Texture Create(GraphicsDevice device, int width, int height, int depth, int levels = 1, int layers = 1, PixelFormat format = PixelFormat.R8G8B8A8_UNorm, TextureUsage usage = TextureUsage.Resource, TextureSampleCount count = TextureSampleCount.Count1) - { - return create(device, width, height, depth, levels, layers, TextureType.Texture3D, format, usage, count); - } - - private static Texture create(GraphicsDevice device, int width, int height, int depth, int levels, int layers, TextureType type, PixelFormat format, TextureUsage usage, TextureSampleCount count) - { - return new(device.CreateTexture(new TextureDescription(type, width, height, depth, format, levels, layers, usage, count))); - } - - public void Dispose() - { - if (isDisposed) - { - return; - } - - native.Dispose(); - - isDisposed = true; - } - - public static explicit operator NativeTexture(Texture texture) => texture.native; -} diff --git a/source/Vignette/Rendering/Vertices/IVertex.cs b/source/Vignette/Rendering/Vertices/IVertex.cs deleted file mode 100644 index 5d129ce..0000000 --- a/source/Vignette/Rendering/Vertices/IVertex.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using Sekai.Graphics; - -namespace Vignette.Rendering.Vertices; - -/// -/// Represents a single vertex in a geometric primitive. -/// -public interface IVertex -{ - static abstract InputLayoutDescription Layout { get; } -} diff --git a/source/Vignette/Rendering/Vertices/TexturedVertex.cs b/source/Vignette/Rendering/Vertices/TexturedVertex.cs deleted file mode 100644 index 6ea1d03..0000000 --- a/source/Vignette/Rendering/Vertices/TexturedVertex.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using Sekai.Graphics; - -namespace Vignette.Rendering.Vertices; - -/// -/// A textured vertex. -/// -public struct TexturedVertex : IVertex, IEquatable -{ - /// - /// The vertex position. - /// - public Vector3 Position; - - /// - /// The vertex texture coordinate. - /// - public Vector2 TexCoord; - - /// - /// The vertex color. - /// - public Vector4 Color; - - /// - /// Creates a new . - /// - /// The vertex position. - /// The vertex texture coordinate. - /// The vertex color. - public TexturedVertex(Vector3 position, Vector2 texCoord, Vector4 color) - { - Color = color; - Position = position; - TexCoord = texCoord; - } - - public readonly bool Equals(TexturedVertex other) - { - return Position.Equals(other.Position) && TexCoord.Equals(other.TexCoord) && Color.Equals(other.Color); - } - - public override readonly bool Equals([NotNullWhen(true)] object? obj) - { - return obj is TexturedVertex vertex && Equals(vertex); - } - - public override readonly int GetHashCode() - { - return HashCode.Combine(Position, TexCoord, Color); - } - - public static bool operator ==(TexturedVertex left, TexturedVertex right) - { - return left.Equals(right); - } - - public static bool operator !=(TexturedVertex left, TexturedVertex right) - { - return !(left == right); - } - - static InputLayoutDescription IVertex.Layout { get; } = new - ( - new InputLayoutMember(3, false, InputLayoutFormat.Float), - new InputLayoutMember(2, false, InputLayoutFormat.Float), - new InputLayoutMember(4, false, InputLayoutFormat.Float) - ); -} From cddf17807d505a3a506d1cfa55f2d55aaed0f91c Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:23:41 +0800 Subject: [PATCH 04/30] add coordinate providing interfaces --- source/Vignette/Graphics/IProjector.cs | 43 ++++++++++++++++++++++++++ source/Vignette/Graphics/IWorld.cs | 42 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 source/Vignette/Graphics/IProjector.cs create mode 100644 source/Vignette/Graphics/IWorld.cs diff --git a/source/Vignette/Graphics/IProjector.cs b/source/Vignette/Graphics/IProjector.cs new file mode 100644 index 0000000..17601b1 --- /dev/null +++ b/source/Vignette/Graphics/IProjector.cs @@ -0,0 +1,43 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System.Numerics; +using Sekai.Mathematics; + +namespace Vignette.Graphics; + +/// +/// An interface for objects providing clip space info. +/// +public interface IProjector +{ + /// + /// The projector's position. + /// + Vector3 Position { get; } + + /// + /// The projector's rotation. + /// + Vector3 Rotation { get; } + + /// + /// The projector's view matrix. + /// + Matrix4x4 ViewMatrix { get; } + + /// + /// The projector's projection matrix. + /// + Matrix4x4 ProjMatrix { get; } + + /// + /// The projector's render groups. + /// + RenderGroup Groups { get; } + + /// + /// The projector's bounding frustum. + /// + BoundingFrustum Frustum { get; } +} diff --git a/source/Vignette/Graphics/IWorld.cs b/source/Vignette/Graphics/IWorld.cs new file mode 100644 index 0000000..b06f28e --- /dev/null +++ b/source/Vignette/Graphics/IWorld.cs @@ -0,0 +1,42 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System.Numerics; + +namespace Vignette.Graphics; + +/// +/// An interface for objects providing world space info. +/// +public interface IWorld +{ + /// + /// The world's position. + /// + Vector3 Position { get; } + + /// + /// The world's rotation. + /// + Vector3 Rotation { get; } + + /// + /// The world's scaling. + /// + Vector3 Scale { get; } + + /// + /// The world's shearing. + /// + Vector3 Shear { get; } + + /// + /// The world's local matrix. + /// + Matrix4x4 LocalMatrix { get; } + + /// + /// The world's world matrix. + /// + Matrix4x4 WorldMatrix { get; } +} From 36e3e0041bf048d202b41d2acbe5157c3b30cb76 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:23:59 +0800 Subject: [PATCH 05/30] add `Node` --- source/Vignette/Node.cs | 385 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 source/Vignette/Node.cs diff --git a/source/Vignette/Node.cs b/source/Vignette/Node.cs new file mode 100644 index 0000000..83e82b4 --- /dev/null +++ b/source/Vignette/Node.cs @@ -0,0 +1,385 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; + +namespace Vignette; + +public abstract class Node : INotifyCollectionChanged, ICollection, IEquatable +{ + /// + /// The 's unique identifier. + /// + public Guid Id { get; } + + /// + /// The 's name. + /// + public string Name { get; } + + /// + /// The depth of this relative to the root. + /// + public int Depth { get; private set; } + + /// + /// The number of children this contains. + /// + public int Count => nodes.Count; + + /// + /// The parent . + /// + public Node? Parent { get; private set; } + + /// + /// Called when the 's children has been changed. + /// + public event NotifyCollectionChangedEventHandler? CollectionChanged; + + private readonly Dictionary nodes = new(); + + /// + /// Creates a new . + /// + /// The optional name for this . + protected Node(string? name = null) + : this(Guid.NewGuid(), name) + { + } + + private Node(Guid id, string? name = null) + { + Id = id; + Name = name ?? id.ToString(); + } + + /// + /// Called when the has entered the node graph. + /// + protected virtual void Enter() + { + } + + /// + /// Called when the is leaving the node graph. + /// + protected virtual void Leave() + { + } + + /// + /// Adds a child . + /// + /// The to add. + public void Add(Node node) + { + add(node); + raiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, node)); + } + + /// + /// Adds a range of children. + /// + /// The children to add. + public void AddRange(IEnumerable nodes) + { + foreach (var node in nodes) + { + add(node); + } + + raiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, nodes.ToArray())); + } + + /// + /// Removes a child . + /// + /// The to remove. + /// if the has been removed. Otherwise, returns . + public bool Remove(Node node) + { + if (!remove(node)) + { + return false; + } + + raiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, node)); + + return true; + } + + /// + /// Removes a range of children based on a given . + /// + /// The predicate used to select the children. + /// The number of removed children. + public int RemoveRange(Predicate predicate) + { + var selected = nodes.Values.Where(n => predicate(n)).ToArray(); + + foreach (var node in selected) + { + remove(node); + } + + raiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, selected)); + + return selected.Length; + } + + /// + /// Removes a range of children. + /// + /// The children to remove. + /// The number of removed children. + public int RemoveRange(IEnumerable nodes) + { + var removed = new List(); + + foreach (var node in nodes) + { + if (remove(node)) + { + removed.Add(node); + } + } + + raiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + + return removed.Count; + } + + /// + /// Removes all children from this . + /// + public void Clear() + { + var copy = nodes.Values.ToArray(); + + foreach (var node in copy) + { + remove(node); + } + + raiseCollectionChanged(reset_args); + } + + /// + /// Determines whether a given is a child of this node. + /// + /// The node to test. + /// if the is a child of this node or if not. + public bool Contains(Node node) + { + return nodes.ContainsValue(node); + } + + /// + /// Gets the root node. + /// + /// The root node. + public Node GetRoot() + { + var current = this; + + while (current.Parent is not null) + { + current = current.Parent; + } + + return current; + } + + /// + /// Gets the nearest node. + /// + /// The node type to search for. + /// The nearest node. + public T? GetNearest() + where T : Node + { + var current = this; + + while (current.Parent is not null) + { + current = current.Parent; + + if (current is T) + { + break; + } + } + + return current as T; + } + + /// + /// Gets the node from the given path. + /// + /// The relative or absolute path. + /// The node on the given path. + /// Thrown when is invalid. + /// Thrown when a part of the path is not found. + public Node GetNode(string path) + { + if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uri)) + { + throw new ArgumentException("Provided path is not a URI.", nameof(path)); + } + + if (uri.IsAbsoluteUri && uri.Scheme != node_scheme) + { + throw new ArgumentException("Absolute paths must start with the node scheme.", nameof(path)); + } + + var current = uri.IsAbsoluteUri ? GetRoot() : this; + string[] cm = uri.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped).Split(Path.AltDirectorySeparatorChar, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + foreach (string part in cm) + { + if (!current.nodes.ContainsKey(part)) + { + throw new KeyNotFoundException($"The node \"{part}\" was not found."); + } + + current = current.nodes[part]; + } + + return current; + } + + /// + /// Gets the node from the given path. + /// + /// The type to cast the node as. + /// The relative or absolute path. + /// The node on the given path. + /// Thrown when the returned node cannot be casted to . + public T GetNode(string path) + where T : Node + { + var node = GetNode(path); + + if (node is not T typed) + { + throw new InvalidCastException($"Cannot cast {typeof(T)} to the found node."); + } + + return (T)node; + } + + /// + /// Gets an enumeration of the nodes of type . + /// + /// The type to filter the enumeration. + /// An enumerable of nodes of type . + public IEnumerable GetNodes() + where T : Node + { + return this.OfType(); + } + + public IEnumerator GetEnumerator() + { + return nodes.Values.GetEnumerator(); + } + + public bool Equals(Node? node) + { + if (node is null) + { + return false; + } + + if (node.Id.Equals(Id)) + { + return true; + } + + if (ReferenceEquals(this, node)) + { + return true; + } + + return false; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Node node && Equals(node); + } + + public override int GetHashCode() + { + return HashCode.Combine(Id); + } + + private void raiseCollectionChanged(NotifyCollectionChangedEventArgs args) + { + CollectionChanged?.Invoke(this, args); + } + + private void add(Node node) + { + if (Equals(node)) + { + throw new ArgumentException("Cannot add self as a child.", nameof(node)); + } + + if (node.Parent is not null) + { + throw new ArgumentException("Cannot add a node that already has a parent.", nameof(node)); + } + + if (nodes.ContainsKey(node.Name)) + { + throw new ArgumentException($"There is already a child with the name \"{node.Name}\".", nameof(node)); + } + + node.Depth = Depth + 1; + node.Parent = this; + + nodes.Add(node.Name, node); + + node.Enter(); + } + + private bool remove(Node node) + { + if (!nodes.ContainsKey(node.Name)) + { + return false; + } + + node.Leave(); + + node.Depth = 0; + node.Parent = null; + + nodes.Remove(node.Name); + + return true; + } + + void ICollection.CopyTo(Node[] array, int arrayIndex) + { + nodes.Values.CopyTo(array, arrayIndex); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + bool ICollection.IsReadOnly => false; + + private const string node_scheme = "node"; + private static readonly NotifyCollectionChangedEventArgs reset_args = new(NotifyCollectionChangedAction.Reset); +} From 738f46746b282ac64f512cd2ce324d25b502e35c Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:25:08 +0800 Subject: [PATCH 06/30] add `Effect` --- source/Vignette/Graphics/Effect.cs | 192 +++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 source/Vignette/Graphics/Effect.cs diff --git a/source/Vignette/Graphics/Effect.cs b/source/Vignette/Graphics/Effect.cs new file mode 100644 index 0000000..3662974 --- /dev/null +++ b/source/Vignette/Graphics/Effect.cs @@ -0,0 +1,192 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Sekai.Graphics; + +namespace Vignette.Graphics; + +public readonly struct Effect : IEquatable +{ + private readonly ShaderCode[] shaderCodes; + + private Effect(params ShaderCode[] shaderCodes) + { + this.shaderCodes = shaderCodes; + } + + public readonly bool Equals(Effect other) + { + return ((IStructuralEquatable)shaderCodes).Equals(other.shaderCodes, EqualityComparer.Default); + } + + public override readonly bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Effect effect && Equals(effect); + } + + public override readonly int GetHashCode() + { + HashCode hash = default; + + for (int i = 0; i < shaderCodes.Length; i++) + { + hash.Add(shaderCodes[i]); + } + + return hash.ToHashCode(); + } + + /// + /// Creates a new from HLSL shader code. + /// + /// The HLSL shader code. + /// The reflected input layout. + /// The reflected shader properties. + /// A new . + internal static Effect From(string code, out InputLayoutDescription layout, out IProperty[] properties) + { + var shVert = ShaderCode.From(code, ShaderStage.Vertex, sh_vert, ShaderLanguage.HLSL); + var shFrag = ShaderCode.From(code, ShaderStage.Fragment, sh_frag, ShaderLanguage.HLSL); + + var shVertReflect = shVert.Reflect(); + var shFragReflect = shFrag.Reflect(); + + if (shVertReflect.Inputs is not null) + { + var format = new InputLayoutMember[shVertReflect.Inputs.Length]; + + for (int i = 0; i < shVertReflect.Inputs.Length; i++) + { + format[i] = format_members[shVertReflect.Inputs[i].Type]; + } + + layout = new(format); + } + else + { + layout = new(); + } + + var props = new List(); + + if (shVertReflect.Uniforms is not null) + { + foreach (var uniform in shVertReflect.Uniforms) + props.Add(new UniformProperty(uniform.Name, uniform.Binding)); + } + + if (shVertReflect.Textures is not null) + { + foreach (var texture in shVertReflect.Textures) + props.Add(new TextureProperty(texture.Name, texture.Binding)); + } + + if (shFragReflect.Uniforms is not null) + { + foreach (var uniform in shFragReflect.Uniforms) + props.Add(new UniformProperty(uniform.Name, uniform.Binding)); + } + + if (shFragReflect.Textures is not null) + { + foreach (var texture in shFragReflect.Textures) + props.Add(new TextureProperty(texture.Name, texture.Binding)); + } + + properties = props.ToArray(); + + return new Effect(shVert, shFrag); + } + + public static bool operator ==(Effect left, Effect right) + { + return left.Equals(right); + } + + public static bool operator !=(Effect left, Effect right) + { + return !(left == right); + } + + public static explicit operator ShaderCode[](Effect effect) => effect.shaderCodes; + + private const string sh_vert = "Vertex"; + private const string sh_frag = "Pixel"; + + private static readonly IReadOnlyDictionary format_members = ImmutableDictionary.CreateRange + ( + new KeyValuePair[] + { + KeyValuePair.Create("int", new(1, false, InputLayoutFormat.Int)), + KeyValuePair.Create("ivec2", new(2, false, InputLayoutFormat.Int)), + KeyValuePair.Create("ivec3", new(3, false, InputLayoutFormat.Int)), + KeyValuePair.Create("ivec4", new(4, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat2", new(4, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat2x3", new(6, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat2x4", new(8, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat3", new(9, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat3x2", new(6, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat3x4", new(12, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat4", new(16, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat4x2", new(8, false, InputLayoutFormat.Int)), + KeyValuePair.Create("imat4x3", new(12, false, InputLayoutFormat.Int)), + KeyValuePair.Create("uint", new(1, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("uvec2", new(2, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("uvec3", new(3, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("uvec4", new(4, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat2", new(4, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat2x3", new(6, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat2x4", new(8, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat3", new(9, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat3x2", new(6, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat3x4", new(12, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat4", new(16, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat4x2", new(8, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("umat4x3", new(12, false, InputLayoutFormat.UnsignedInt)), + KeyValuePair.Create("bool", new(1, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bvec2", new(2, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bvec3", new(3, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bvec4", new(4, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat2", new(4, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat2x3", new(6, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat2x4", new(8, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat3", new(9, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat3x2", new(6, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat3x4", new(12, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat4", new(16, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat4x2", new(8, false, InputLayoutFormat.Int)), + KeyValuePair.Create("bmat4x3", new(12, false, InputLayoutFormat.Int)), + KeyValuePair.Create("float", new(1, false, InputLayoutFormat.Float)), + KeyValuePair.Create("vec2", new(2, false, InputLayoutFormat.Float)), + KeyValuePair.Create("vec3", new(3, false, InputLayoutFormat.Float)), + KeyValuePair.Create("vec4", new(4, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat2", new(4, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat2x3", new(6, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat2x4", new(8, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat3", new(9, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat3x2", new(6, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat3x4", new(12, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat4", new(16, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat4x2", new(8, false, InputLayoutFormat.Float)), + KeyValuePair.Create("mat4x3", new(12, false, InputLayoutFormat.Float)), + KeyValuePair.Create("double", new(1, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dvec2", new(2, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dvec3", new(3, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dvec4", new(4, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat2", new(4, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat2x3", new(6, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat2x4", new(8, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat3", new(9, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat3x2", new(6, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat3x4", new(12, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat4", new(16, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat4x2", new(8, false, InputLayoutFormat.Double)), + KeyValuePair.Create("dmat4x3", new(12, false, InputLayoutFormat.Double)), + } + ); +} From 16c4b281e7040aa0c224f50b1985da82b23c3a8a Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:25:17 +0800 Subject: [PATCH 07/30] add material-related interfaces --- source/Vignette/Graphics/IMaterial.cs | 62 +++++++++++++++++++++++++++ source/Vignette/Graphics/IProperty.cs | 39 +++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 source/Vignette/Graphics/IMaterial.cs create mode 100644 source/Vignette/Graphics/IProperty.cs diff --git a/source/Vignette/Graphics/IMaterial.cs b/source/Vignette/Graphics/IMaterial.cs new file mode 100644 index 0000000..df9f2c0 --- /dev/null +++ b/source/Vignette/Graphics/IMaterial.cs @@ -0,0 +1,62 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections.Generic; +using Sekai.Graphics; + +namespace Vignette.Graphics; + +/// +/// Defines properties of a surface and how it should be drawn. +/// +public interface IMaterial +{ + /// + /// The stencil reference. + /// + int Stencil { get; } + + /// + /// The material effect. + /// + Effect Effect { get; } + + /// + /// The primitive type. + /// + PrimitiveType Primitives { get; } + + /// + /// The layout descriptor. + /// + InputLayoutDescription Layout { get; } + + /// + /// The blend descriptor. + /// + BlendStateDescription Blend { get; } + + /// + /// The rasterizer descriptor. + /// + RasterizerStateDescription Rasterizer { get; } + + /// + /// The depth stencil descriptor. + /// + DepthStencilStateDescription DepthStencil { get; } + + /// + /// The material properties. + /// + IEnumerable Properties { get; } + + /// + /// Gets a unique ID for this . + /// + int GetMaterialID() + { + return HashCode.Combine(Stencil, Blend, Rasterizer, DepthStencil, Effect, Layout); + } +} diff --git a/source/Vignette/Graphics/IProperty.cs b/source/Vignette/Graphics/IProperty.cs new file mode 100644 index 0000000..cc6e875 --- /dev/null +++ b/source/Vignette/Graphics/IProperty.cs @@ -0,0 +1,39 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using Sekai.Graphics; + +namespace Vignette.Graphics; + +/// +/// Defines a property. +/// +public interface IProperty +{ + /// + /// The property name. + /// + string Name { get; } + + /// + /// The property slot. + /// + int Slot { get; } +} + +/// +/// Defines a property that contains a as its value. +/// +/// The property name. +/// The property slot. +/// The buffer. +public record struct UniformProperty(string Name, int Slot, GraphicsBuffer? Uniform = null) : IProperty; + +/// +/// Defines a property that contains a and pair as its value. +/// +/// The property name. +/// The property slot. +/// The texture. +/// The sampler. +public record struct TextureProperty(string Name, int Slot, Texture? Texture = null, Sampler? Sampler = null) : IProperty; From b7ac6d66654f6d6f2f5d11032fd7bb69e23bcd05 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:25:25 +0800 Subject: [PATCH 08/30] add `ShaderMaterial` --- source/Vignette/Graphics/ShaderMaterial.cs | 298 +++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 source/Vignette/Graphics/ShaderMaterial.cs diff --git a/source/Vignette/Graphics/ShaderMaterial.cs b/source/Vignette/Graphics/ShaderMaterial.cs new file mode 100644 index 0000000..1ac18b6 --- /dev/null +++ b/source/Vignette/Graphics/ShaderMaterial.cs @@ -0,0 +1,298 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections.Generic; +using System.Linq; +using Sekai.Graphics; + +namespace Vignette.Graphics; + +/// +/// A composable material that created from shader code. +/// +public sealed class ShaderMaterial : IMaterial, ICloneable +{ + private int stencil; + private PrimitiveType primitives; + private BlendStateDescription blend; + private RasterizerStateDescription rasterizer; + private DepthStencilStateDescription depthStencil; + private readonly Effect effect; + private readonly InputLayoutDescription layout; + private readonly IProperty[] properties; + + private ShaderMaterial(InputLayoutDescription layout, Effect effect, IProperty[] properties) + { + this.layout = layout; + this.effect = effect; + this.properties = properties; + } + + private ShaderMaterial(PrimitiveType primitives, BlendStateDescription blend, RasterizerStateDescription rasterizer, DepthStencilStateDescription depthStencil, InputLayoutDescription layout, Effect effect, IProperty[] properties) + { + this.blend = blend; + this.layout = layout; + this.effect = effect; + this.properties = properties; + this.primitives = primitives; + this.rasterizer = rasterizer; + this.depthStencil = depthStencil; + } + + /// + /// Sets the primitive type for this . + /// + /// The primitive type. + public ShaderMaterial SetPrimitives(PrimitiveType primitives) + { + this.primitives = primitives; + return this; + } + + /// + /// Sets the face culling mode for this . + /// + /// The face culling mode. + public ShaderMaterial SetFaceCulling(FaceCulling culling) + { + rasterizer.Culling = culling; + return this; + } + + /// + /// Sets the face winding mode for this . + /// + /// The face winding mode. + public ShaderMaterial SetFaceWinding(FaceWinding winding) + { + rasterizer.Winding = winding; + return this; + } + + /// + /// Sets the polygon fill mode for this . + /// + /// The polygon fill mode. + public ShaderMaterial SetFillMode(FillMode mode) + { + rasterizer.Mode = mode; + return this; + } + + /// + /// Sets custom stencil parameters for this . + /// + /// The stencil reference. + /// The operation performed for the passing front face stencil test. + /// The operation performed for the failing front face stencil test. + /// The operation performed for the failing front face depth test. + /// The comparison performed for the front face. + /// The operation performed for the passing back face stencil test. + /// The operation performed for the failing back face stencil test. + /// The operation performed for the failing back face depth test. + /// The comparison performed for the back face. + public ShaderMaterial SetStencil(int reference, StencilOperation frontStencilPass, StencilOperation frontStencilFail, StencilOperation frontDepthFail, ComparisonKind frontComparison, StencilOperation backStencilPass, StencilOperation backStencilFail, StencilOperation backDepthFail, ComparisonKind backComparison) + { + stencil = reference; + depthStencil.Front.StencilPass = frontStencilPass; + depthStencil.Front.StencilFail = frontStencilFail; + depthStencil.Front.DepthFail = frontDepthFail; + depthStencil.Front.Comparison = frontComparison; + depthStencil.Back.StencilPass = backStencilPass; + depthStencil.Back.StencilFail = backStencilFail; + depthStencil.Back.DepthFail = backDepthFail; + depthStencil.Back.Comparison = backComparison; + return this; + } + + /// + /// Sets custom stencil parameters for both the front and back faces for this . + /// + /// The stencil reference. + /// The operation performed for passing the stencil test. + /// The operation performed for failing the stencil test. + /// The operation performed for failing the depth test. + /// The comparison performed. + public ShaderMaterial SetStencil(int reference, StencilOperation pass, StencilOperation fail, StencilOperation depthFail, ComparisonKind comparison) + { + return SetStencil(reference, pass, fail, depthFail, comparison, pass, fail, depthFail, comparison); + } + + /// + /// Sets custom blending parameters for this . + /// + /// Whether to enable or disable blending. + /// The source color blending. + /// The source alpha blending. + /// The destination color blending. + /// The destination alpha blending. + /// The operation performed between and . + /// The operation performed between and . + public ShaderMaterial SetBlend(bool enabled, BlendType srcColor, BlendType srcAlpha, BlendType dstColor, BlendType dstAlpha, BlendOperation colorOperation, BlendOperation alphaOperation) + { + blend.Enabled = enabled; + blend.SourceColor = srcColor; + blend.SourceAlpha = srcAlpha; + blend.DestinationColor = dstColor; + blend.DestinationAlpha = dstAlpha; + blend.ColorOperation = colorOperation; + blend.AlphaOperation = alphaOperation; + return this; + } + + /// + /// Sets individual blending parameters for this . + /// + /// The source color blending. + /// The source alpha blending. + /// The destination color blending. + /// The destination alpha blending. + public ShaderMaterial SetBlend(BlendType srcColor, BlendType srcAlpha, BlendType dstColor, BlendType dstAlpha) + { + return SetBlend(true, srcColor, srcAlpha, dstColor, dstAlpha, BlendOperation.Add, BlendOperation.Add); + } + + /// + /// Sets blending parameters for the source and destination colors for this . + /// + /// The source blending. + /// The destination blending. + public ShaderMaterial SetBlend(BlendType source, BlendType destination) + { + return SetBlend(source, source, destination, destination); + } + + /// + /// Sets the color write mask. + /// + /// The color write mask. + public ShaderMaterial SetColorMask(ColorWriteMask mask) + { + blend.WriteMask = mask; + return this; + } + + /// + /// Sets a for this . + /// + /// The property name. + /// The to set. Setting will use the default texture. + /// The to set. Setting will use the default sampler. + /// Thrown when the is not usable as a resource. + public ShaderMaterial SetProperty(string name, Texture? texture = null, Sampler? sampler = null) + { + var prop = getProperty(name); + + if (texture is not null && (texture.Usage & TextureUsage.Resource) == 0) + { + throw new ArgumentException($"The texture must have the {nameof(TextureUsage.Resource)} flag to be used on materials.", nameof(texture)); + } + + prop.Texture = texture; + prop.Sampler = sampler; + + return this; + } + + /// + /// Sets a for this . + /// + /// The property name. + /// The to set. Setting will not bind this property during rendering. + /// Thrown when the is not usable as a uniform. + public ShaderMaterial SetProperty(string name, GraphicsBuffer? buffer = null) + { + var prop = getProperty(name); + + if (buffer is not null && buffer.Type is not BufferType.Uniform) + { + throw new ArgumentException($"The buffer must be a {nameof(BufferType.Uniform)} to be used on materials.", nameof(buffer)); + } + + prop.Uniform = buffer; + + return this; + } + + /// + /// Gets whether a property exists. + /// + /// The property name. + /// if the property exists or if it does not. + public bool HasProperty(string name) + { + foreach (var prop in properties) + { + if (prop.Name == name) + { + return true; + } + } + + return false; + } + + /// + /// Creates a shallow copy of this . + /// + /// A new . + public ShaderMaterial Clone() => new + ( + primitives, + blend, + rasterizer, + depthStencil, + layout, + effect, + properties.ToArray() + ); + + private T getProperty(string name) + where T : IProperty + { + var prop = default(IProperty); + + foreach (var p in properties) + { + if (p.Name == name) + { + prop = p; + break; + } + } + + if (prop is null) + { + throw new KeyNotFoundException($"There is no property named \"{name}\" on this material."); + } + + if (prop is not T typedProp) + { + throw new InvalidCastException($"Property \"{name}\" is not compatible with the type {typeof(T)}."); + } + + return typedProp; + } + + int IMaterial.Stencil => stencil; + Effect IMaterial.Effect => effect; + PrimitiveType IMaterial.Primitives => primitives; + InputLayoutDescription IMaterial.Layout => layout; + BlendStateDescription IMaterial.Blend => blend; + RasterizerStateDescription IMaterial.Rasterizer => rasterizer; + DepthStencilStateDescription IMaterial.DepthStencil => depthStencil; + IEnumerable IMaterial.Properties => properties; + object ICloneable.Clone() => Clone(); + + /// + /// Creates a new from HLSL shader code. + /// + /// The shader code to use. + /// A new . + public static ShaderMaterial Create(string code) + { + var effect = Effect.From(code, out var layout, out var properties); + return new(layout, effect, properties); + } +} From 51a4f8a97e58defda6a6bb416c95992071e84f43 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:25:32 +0800 Subject: [PATCH 09/30] add `UnlitMaterial` --- source/Vignette/Graphics/UnlitMaterial.cs | 138 ++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 source/Vignette/Graphics/UnlitMaterial.cs diff --git a/source/Vignette/Graphics/UnlitMaterial.cs b/source/Vignette/Graphics/UnlitMaterial.cs new file mode 100644 index 0000000..297b52b --- /dev/null +++ b/source/Vignette/Graphics/UnlitMaterial.cs @@ -0,0 +1,138 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections.Generic; +using Sekai.Graphics; + +namespace Vignette.Graphics; + +/// +/// A material that is unlit. +/// +public sealed class UnlitMaterial : IMaterial +{ + /// + /// The default unlit material. + /// + public static readonly IMaterial Default = new UnlitMaterial(true); + + /// + /// The material's texture. + /// + public Texture? Texture + { + get => ((TextureProperty)properties[0]).Texture; + set + { + if (isDefault) + { + throw new InvalidOperationException("Cannot modify the default instance."); + } + + var texture = (TextureProperty)properties[0]; + texture.Texture = value; + } + } + + /// + /// The material's sampler. + /// + public Sampler? Sampler + { + get => ((TextureProperty)properties[0]).Sampler; + set + { + if (isDefault) + { + throw new InvalidOperationException("Cannot modify the default instance."); + } + + var texture = (TextureProperty)properties[0]; + texture.Sampler = value; + } + } + + /// + /// The material's primitive type. + /// + public PrimitiveType Primitives { get; set; } = PrimitiveType.TriangleList; + + private readonly bool isDefault; + private readonly Effect effect; + private readonly IProperty[] properties; + private readonly InputLayoutDescription layout; + private readonly RasterizerStateDescription rasterizer; + private readonly DepthStencilStateDescription depthStencil; + + public UnlitMaterial() + : this(false) + { + } + + private UnlitMaterial(bool isDefault) + { + effect = Effect.From(shader, out layout, out properties); + + rasterizer = new RasterizerStateDescription + ( + FaceCulling.None, + FaceWinding.CounterClockwise, + FillMode.Solid, + false + ); + + depthStencil = new DepthStencilStateDescription + ( + false, + false, + ComparisonKind.Always + ); + + this.isDefault = isDefault; + } + + int IMaterial.Stencil => 0; + Effect IMaterial.Effect => effect; + InputLayoutDescription IMaterial.Layout => layout; + BlendStateDescription IMaterial.Blend => BlendStateDescription.NonPremultiplied; + RasterizerStateDescription IMaterial.Rasterizer => rasterizer; + DepthStencilStateDescription IMaterial.DepthStencil => depthStencil; + IEnumerable IMaterial.Properties => properties; + + private const string shader = +@" +struct VSInput +{ + float3 Position : POSITION; + float2 TexCoord : TEXCOORD; + float4 Color : COLOR; +}; + +struct PSInput +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD; + float4 Color : COLOR; +}; + +Texture2D AlbedoTexture : register(t0); +SamplerState AlbedoSampler : register(s0); + +PSInput Vertex(in VSInput input) +{ + PSInput output; + + output.Color = input.Color; + output.Position = OBJECT_TO_VIEW(float4(input.Position, 1.0)); + output.TexCoord = input.TexCoord; + + return output; +} + +float4 Pixel(in PSInput input) : SV_TARGET +{ + return AlbedoTexture.Sample(AlbedoSampler, input.TexCoord) * input.Color; +} +"; +} From e617ca582b9e9d51f7c566ab359fa33974a6d206 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:26:00 +0800 Subject: [PATCH 10/30] add `Behavior` --- source/Vignette/Behavior.cs | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 source/Vignette/Behavior.cs diff --git a/source/Vignette/Behavior.cs b/source/Vignette/Behavior.cs new file mode 100644 index 0000000..c13d7a2 --- /dev/null +++ b/source/Vignette/Behavior.cs @@ -0,0 +1,100 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; + +namespace Vignette; + +/// +/// A that processes itself per-frame. +/// +public abstract class Behavior : Node, IComparable +{ + /// + /// The processing order for this . + /// + public int Order + { + get => order; + set + { + if (order.Equals(value)) + { + return; + } + + order = value; + OrderChanged?.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Whether this should be enabled or not affecting calls. + /// + public bool Enabled + { + get => enabled; + set + { + if (enabled.Equals(value)) + { + return; + } + + enabled = value; + EnabledChanged?.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Called when has been changed. + /// + public event EventHandler? OrderChanged; + + /// + /// Called when has been changed. + /// + public event EventHandler? EnabledChanged; + + private int order; + private bool enabled = true; + + /// + /// Called once in the update loop after the has entered the node graph. + /// + public virtual void Load() + { + } + + /// + /// Called every frame to perform updates on this . + /// + /// The time elapsed between frames. + public virtual void Update(TimeSpan elapsed) + { + } + + /// + /// Called once in the update loop before the exits the node graph. + /// + public virtual void Unload() + { + } + + public int CompareTo(Behavior? other) + { + if (other is null) + { + return -1; + } + + int value = Depth.CompareTo(other.Depth); + + if (value != 0) + { + return value; + } + + return Order.CompareTo(other.Order); + } +} From 3287a4eecca43a180cd5e8979f1c5209e90fa406 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:26:09 +0800 Subject: [PATCH 11/30] add `Renderable` --- source/Vignette/Renderable.cs | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 source/Vignette/Renderable.cs diff --git a/source/Vignette/Renderable.cs b/source/Vignette/Renderable.cs new file mode 100644 index 0000000..a56c721 --- /dev/null +++ b/source/Vignette/Renderable.cs @@ -0,0 +1,63 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Numerics; +using Vignette.Graphics; + +namespace Vignette; + +/// +/// A that provides a world space. +/// +public abstract class Renderable : Behavior, IWorld +{ + /// + /// The renderable's position. + /// + public Vector3 Position + { + get => position.Translation; + set => position = Matrix4x4.CreateTranslation(value); + } + + /// + /// The renderable's rotation as pitch, yaw, and roll. + /// + public Vector3 Rotation + { + get => new(MathF.Atan2(rotation[0, 2], rotation[2, 2]), MathF.Asin(-rotation[1, 2]), MathF.Atan2(rotation[1, 0], rotation[1, 1])); + set => rotation = Matrix4x4.CreateFromYawPitchRoll(value.Y, value.X, value.Z); + } + + /// + /// The renderable's scaling. + /// + public Vector3 Scale + { + get => new(scale[0, 0], scale[1, 1], scale[2, 2]); + set => scale = Matrix4x4.CreateScale(value); + } + + /// + /// The renderable's shearing. + /// + public Vector3 Shear + { + get => new(shear[0, 1], shear[0, 2], shear[1, 2]); + set + { + shear[0, 1] = value.X; + shear[0, 2] = value.Y; + shear[1, 2] = value.Z; + } + } + + private Matrix4x4 scale = Matrix4x4.Identity; + private Matrix4x4 shear = Matrix4x4.Identity; + private Matrix4x4 rotation = Matrix4x4.Identity; + private Matrix4x4 position = Matrix4x4.Identity; + + Matrix4x4 IWorld.LocalMatrix => shear * scale * rotation * position; + Matrix4x4 IWorld.WorldMatrix => Parent is not IWorld provider ? ((IWorld)this).LocalMatrix : provider.LocalMatrix * ((IWorld)this).LocalMatrix; +} From 0d6d8c4bc7038a6e05b071ab6bab8c19bfae6c3c Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:28:43 +0800 Subject: [PATCH 12/30] add `RenderGroup`s --- source/Vignette/Graphics/RenderGroup.cs | 98 +++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 source/Vignette/Graphics/RenderGroup.cs diff --git a/source/Vignette/Graphics/RenderGroup.cs b/source/Vignette/Graphics/RenderGroup.cs new file mode 100644 index 0000000..82aab81 --- /dev/null +++ b/source/Vignette/Graphics/RenderGroup.cs @@ -0,0 +1,98 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; + +namespace Vignette.Graphics; + +/// +/// Render Group Flags. +/// +[Flags] +public enum RenderGroup : uint +{ + /// + /// Render Group Default + /// + Default = 0, + + /// + /// Render Group 1 + /// + Group1 = 1 << 0, + + /// + /// Render Group 2 + /// + Group2 = 1 << 1, + + /// + /// Render Group 3 + /// + Group3 = 1 << 2, + + /// + /// Render Group 4 + /// + Group4 = 1 << 3, + + /// + /// Render Group 5 + /// + Group5 = 1 << 4, + + /// + /// Render Group 6 + /// + Group6 = 1 << 5, + + /// + /// Render Group 7 + /// + Group7 = 1 << 6, + + /// + /// Render Group 8 + /// + Group8 = 1 << 7, + + /// + /// Render Group 9 + /// + Group9 = 1 << 8, + + /// + /// Render Group 10 + /// + Group10 = 1 << 9, + + /// + /// Render Group 11 + /// + Group11 = 1 << 10, + + /// + /// Render Group 12 + /// + Group12 = 1 << 11, + + /// + /// Render Group 13 + /// + Group13 = 1 << 12, + + /// + /// Render Group 14 + /// + Group14 = 1 << 13, + + /// + /// Render Group 15 + /// + Group15 = 1 << 14, + + /// + /// Render Group 16 + /// + Group16 = 1 << 15, +} From 07b1a50ac213a32896a797ec52636d38bdf7d38b Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:28:58 +0800 Subject: [PATCH 13/30] add `RenderObject`s --- source/Vignette/Graphics/RenderObject.cs | 48 ++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 source/Vignette/Graphics/RenderObject.cs diff --git a/source/Vignette/Graphics/RenderObject.cs b/source/Vignette/Graphics/RenderObject.cs new file mode 100644 index 0000000..aaccb71 --- /dev/null +++ b/source/Vignette/Graphics/RenderObject.cs @@ -0,0 +1,48 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using Sekai.Graphics; +using Sekai.Mathematics; + +namespace Vignette.Graphics; + +/// +/// An object that can be drawn. +/// +public abstract class RenderObject +{ + /// + /// The bounding box of this . + /// + public BoundingBox Bounds { get; set; } = BoundingBox.Empty; + + /// + /// The rendering groups this is visible to. + /// + public RenderGroup Groups { get; set; } = RenderGroup.Default; + + /// + /// The material this uses. + /// + public IMaterial Material { get; set; } = UnlitMaterial.Default; + + /// + /// The number of indices to be drawn. + /// + public int IndexCount { get; set; } + + /// + /// The type of indices being to be interpreted in the . + /// + public IndexType IndexType { get; set; } = IndexType.UnsignedShort; + + /// + /// The index buffer for this render object. + /// + public GraphicsBuffer? IndexBuffer { get; set; } + + /// + /// The vertex buffer for this render object. + /// + public GraphicsBuffer? VertexBuffer { get; set; } +} From 702cccebc9ce2fa14a5caab80369aaa163049cbd Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:29:14 +0800 Subject: [PATCH 14/30] add `RenderData` --- source/Vignette/Graphics/RenderData.cs | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 source/Vignette/Graphics/RenderData.cs diff --git a/source/Vignette/Graphics/RenderData.cs b/source/Vignette/Graphics/RenderData.cs new file mode 100644 index 0000000..910f7fa --- /dev/null +++ b/source/Vignette/Graphics/RenderData.cs @@ -0,0 +1,32 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +namespace Vignette.Graphics; + +/// +/// A fully realized renderable that can be drawn. +/// +public readonly struct RenderData +{ + /// + /// The world. + /// + public IWorld World { get; } + + /// + /// The projector. + /// + public IProjector Projector { get; } + + /// + /// The renderable. + /// + public RenderObject Renderable { get; } + + public RenderData(IProjector projector, IWorld world, RenderObject renderable) + { + World = world; + Projector = projector; + Renderable = renderable; + } +} From 27f904b155a72d68de4f847d661df05ed5e21380 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:30:00 +0800 Subject: [PATCH 15/30] add `RenderQueue` and `RenderContext` --- source/Vignette/Graphics/RenderContext.cs | 30 +++ source/Vignette/Graphics/RenderQueue.cs | 215 ++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 source/Vignette/Graphics/RenderContext.cs create mode 100644 source/Vignette/Graphics/RenderQueue.cs diff --git a/source/Vignette/Graphics/RenderContext.cs b/source/Vignette/Graphics/RenderContext.cs new file mode 100644 index 0000000..eff0d59 --- /dev/null +++ b/source/Vignette/Graphics/RenderContext.cs @@ -0,0 +1,30 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +namespace Vignette.Graphics; + +/// +/// A rendering context for a given . +/// +public readonly struct RenderContext +{ + private readonly IWorld world; + private readonly RenderQueue queue; + private readonly IProjector projector; + + internal RenderContext(RenderQueue queue, IProjector projector, IWorld world) + { + this.queue = queue; + this.world = world; + this.projector = projector; + } + + /// + /// Draws a render object. + /// + /// The to draw. + public void Draw(RenderObject renderObject) + { + queue.Enqueue(projector, world, renderObject); + } +} diff --git a/source/Vignette/Graphics/RenderQueue.cs b/source/Vignette/Graphics/RenderQueue.cs new file mode 100644 index 0000000..76a80be --- /dev/null +++ b/source/Vignette/Graphics/RenderQueue.cs @@ -0,0 +1,215 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using Sekai.Mathematics; + +namespace Vignette.Graphics; + +/// +/// A priority queue that is sorted by the distance between a projector and a model. +/// +public sealed class RenderQueue : IReadOnlyCollection +{ + /// + /// Gets the number of s queued. + /// + public int Count => renderables.Count; + + private readonly List renderables = new(); + private readonly List renderOrders = new(); + + /// + /// Creates a new render queue. + /// + public RenderQueue() + { + } + + /// + /// Enqueues a to this queue. + /// + /// The projector used. + /// The model used. + /// The render object to be enqueued. + public void Enqueue(IProjector projector, IWorld world, RenderObject renderObject) + { + if ((projector.Groups & renderObject.Groups) == 0) + { + return; + } + + if (!renderObject.Bounds.Equals(BoundingBox.Empty)) + { + if (BoundingFrustum.Contains(projector.Frustum, renderObject.Bounds) == Containment.Disjoint) + { + return; + } + } + + int renderable = renderables.Count; + int materialID = renderObject.Material.GetMaterialID(); + float distance = Vector3.Distance((renderObject.Bounds.Center * world.Scale) + world.Position, projector.Position); + + renderables.Add(new RenderData(projector, world, renderObject)); + renderOrders.Add(new(renderable, materialID, distance)); + } + + /// + /// Clears the queue. + /// + public void Clear() + { + renderables.Clear(); + renderOrders.Clear(); + } + + /// + /// Returns an enumerator that iterates through this queue in order. + /// + /// An that iterates through this queue in order. + public IEnumerator GetEnumerator() + { + renderOrders.Sort(); + return new Enumerator(renderOrders, renderables); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private struct Enumerator : IEnumerator + { + public readonly RenderData Current => renderables[renderOrders[index].Renderable]; + + private int index; + private readonly IReadOnlyList renderables; + private readonly IReadOnlyList renderOrders; + + public Enumerator(IReadOnlyList renderOrders, IReadOnlyList renderables) + { + this.renderables = renderables; + this.renderOrders = renderOrders; + } + + public bool MoveNext() + { + if (index >= renderOrders.Count) + { + return false; + } + else + { + index += 1; + return true; + } + } + + public void Reset() + { + index = 0; + } + + public readonly void Dispose() + { + } + + readonly object IEnumerator.Current => Current; + } + + private readonly struct RenderOrder : IEquatable, IComparable + { + public int Renderable { get; } + public int MaterialID { get; } + public float Distance { get; } + + public RenderOrder(int renderable, int materialID, float distance) + { + Distance = distance; + Renderable = renderable; + MaterialID = materialID; + } + + public readonly int CompareTo(RenderOrder other) + { + if (Equals(other)) + { + return 0; + } + + int value = Distance.CompareTo(other.Distance); + + if (value != 0) + { + return value; + } + + return MaterialID.CompareTo(other.MaterialID); + } + + public readonly bool Equals(RenderOrder other) + { + return Renderable.Equals(other.Renderable) && MaterialID.Equals(other.MaterialID) && Distance.Equals(other.Distance); + } + + public override readonly bool Equals([NotNullWhen(true)] object? obj) + { + return obj is RenderOrder order && Equals(order); + } + + public override readonly int GetHashCode() + { + return HashCode.Combine(Renderable, MaterialID, Distance); + } + + public static bool operator ==(RenderOrder left, RenderOrder right) + { + return left.Equals(right); + } + + public static bool operator !=(RenderOrder left, RenderOrder right) + { + return !(left == right); + } + + public static bool operator <(RenderOrder left, RenderOrder right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(RenderOrder left, RenderOrder right) + { + return left.CompareTo(right) <= 0; + } + + public static bool operator >(RenderOrder left, RenderOrder right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(RenderOrder left, RenderOrder right) + { + return left.CompareTo(right) >= 0; + } + } +} + +internal static class RenderQueueExtensions +{ + /// + /// Begins a rendering context. + /// + /// The render queue. + /// The projector used. + /// The model used. + /// A new render context. + internal static RenderContext Begin(this RenderQueue queue, IProjector projector, IWorld world) + { + return new RenderContext(queue, projector, world); + } +} From f99c0b7eb2f1a4b11aee67908bf1b53d3cbc2684 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:30:32 +0800 Subject: [PATCH 16/30] add `RenderTarget` --- source/Vignette/Graphics/RenderTarget.cs | 140 +++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 source/Vignette/Graphics/RenderTarget.cs diff --git a/source/Vignette/Graphics/RenderTarget.cs b/source/Vignette/Graphics/RenderTarget.cs new file mode 100644 index 0000000..a7ee11e --- /dev/null +++ b/source/Vignette/Graphics/RenderTarget.cs @@ -0,0 +1,140 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using Sekai.Graphics; + +namespace Vignette.Graphics; + +/// +/// An output target for rendering. +/// +public sealed class RenderTarget : IDisposable +{ + /// + /// The render target's width. + /// + public int Width { get; } + + /// + /// The render target's height. + /// + public int Height { get; } + + private bool isDisposed; + private readonly Texture? color; + private readonly Texture? depth; + private readonly Framebuffer framebuffer; + + private RenderTarget(int width, int height, Texture? color, Texture? depth, Framebuffer framebuffer) + { + this.color = color; + this.depth = depth; + this.Width = width; + this.Height = height; + this.framebuffer = framebuffer; + } + + /// + /// Creates a new . + /// + /// The graphics device used in creating the render target. + /// The width of the render target. + /// The height of the render target. + /// The layer count of the render target. + /// The color texture pixel format. + /// The depth texture pixel format. + /// A new . + /// Thrown when is less than or equal to zero. + /// Thrown when both and are . + /// Thrown when either or are passed with an invalid format. + public static RenderTarget Create(GraphicsDevice device, int width, int height, int layers = 1, PixelFormat? colorFormat = null, PixelFormat? depthFormat = null) + { + if (layers <= 0) + { + throw new ArgumentOutOfRangeException(nameof(layers), "Layer count must be greater than zero."); + } + + if (!depthFormat.HasValue && !colorFormat.HasValue) + { + throw new InvalidOperationException("There must be a defined format for either or both the color and depth textures."); + } + + if (colorFormat.HasValue && colorFormat.Value.IsDepthStencil()) + { + throw new ArgumentException("Invalid color format.", nameof(colorFormat)); + } + + if (depthFormat.HasValue && !depthFormat.Value.IsDepthStencil()) + { + throw new ArgumentException("Invalid depth format.", nameof(depthFormat)); + } + + var color = default(Texture); + var depth = default(Texture); + + FramebufferAttachment? depthAttachment = null; + FramebufferAttachment[] colorAttachments; + + if (colorFormat.HasValue) + { + color = device.CreateTexture(new TextureDescription + ( + TextureType.Texture2D, + width, + height, + 1, + colorFormat.Value, + 1, + layers, + TextureUsage.RenderTarget | TextureUsage.Resource + )); + + colorAttachments = new FramebufferAttachment[layers]; + + for (int i = 0; i < layers; i++) + { + colorAttachments[i] = new FramebufferAttachment(color, i, 0); + } + } + else + { + colorAttachments = Array.Empty(); + } + + if (depthFormat.HasValue) + { + depth = device.CreateTexture(new TextureDescription + ( + TextureType.Texture2D, + width, + height, + 1, + depthFormat.Value, + 1, + 1, + TextureUsage.RenderTarget | TextureUsage.Resource + )); + + depthAttachment = new FramebufferAttachment(depth, 0, 0); + } + + return new RenderTarget(width, height, color, depth, device.CreateFramebuffer(depthAttachment, colorAttachments)); + } + + public void Dispose() + { + if (isDisposed) + { + return; + } + + color?.Dispose(); + depth?.Dispose(); + framebuffer.Dispose(); + + isDisposed = true; + } + + public static explicit operator Framebuffer(RenderTarget target) => target.framebuffer; +} From f58ec7ef1bfa991df5f6b3979106104e028fc354 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:30:41 +0800 Subject: [PATCH 17/30] add `Renderer` --- source/Vignette/Graphics/Renderer.cs | 197 +++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 source/Vignette/Graphics/Renderer.cs diff --git a/source/Vignette/Graphics/Renderer.cs b/source/Vignette/Graphics/Renderer.cs new file mode 100644 index 0000000..723e0ce --- /dev/null +++ b/source/Vignette/Graphics/Renderer.cs @@ -0,0 +1,197 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using Sekai.Graphics; +using Sekai.Mathematics; + +namespace Vignette.Graphics; + +public sealed class Renderer +{ + /// + /// A white pixel texture. + /// + public Texture WhitePixel { get; } + + /// + /// A point sampler. + /// + public Sampler SamplerPoint { get; } + + /// + /// A linear sampler. + /// + public Sampler SamplerLinear { get; } + + /// + /// A 4x anisotropic sampler. + /// + public Sampler SamplerAniso4x { get; } + + private readonly GraphicsBuffer ubo; + private readonly GraphicsDevice device; + private readonly Dictionary caches = new(); + + internal Renderer(GraphicsDevice device) + { + ubo = device.CreateBuffer(BufferType.Uniform, 3, true); + + WhitePixel = device.CreateTexture(new(1, 1, PixelFormat.R8G8B8A8_UNorm, 1, 1, TextureUsage.Resource)); + + SamplerPoint = device.CreateSampler(new(TextureFilter.MinMagMipPoint, TextureAddress.Repeat, TextureAddress.Repeat, TextureAddress.Repeat, 0, Color.White, 0, 0, 0)); + SamplerLinear = device.CreateSampler(new(TextureFilter.MinMagMipLinear, TextureAddress.Repeat, TextureAddress.Repeat, TextureAddress.Repeat, 0, Color.White, 0, 0, 0)); + SamplerAniso4x = device.CreateSampler(new(TextureFilter.Anisotropic, TextureAddress.Repeat, TextureAddress.Repeat, TextureAddress.Repeat, 4, Color.White, 0, 0, 0)); + + this.device = device; + } + + /// + /// Draws a single to . + /// + /// The renderable to draw. + /// The target to draw to. A value of to draw to the backbuffer. + public void Draw(RenderData renderable, RenderTarget? target = null) + { + Draw(new[] { renderable }, target); + } + + /// + /// Draws to . + /// + /// The renderables to draw. + /// The target to draw to. A value of to draw to the backbuffer. + public void Draw(IEnumerable renderables, RenderTarget? target = null) + { + var currentLayout = default(InputLayout); + int currentMaterialID = -1; + + foreach (var data in renderables) + { + using (var mvp = ubo.Map(MapMode.Write)) + { + mvp[0] = data.Projector.ProjMatrix; + mvp[1] = data.Projector.ViewMatrix; + mvp[2] = data.World.WorldMatrix; + } + + device.SetUniformBuffer(ubo, 89); + + if (target is not null) + { + device.SetFramebuffer((Framebuffer)target); + } + else + { + device.SetFramebuffer(null); + } + + draw(data.Renderable, ref currentMaterialID, ref currentLayout!); + } + } + + private void draw(RenderObject renderObject, ref int currentMaterialID, ref InputLayout currentLayout) + { + if (renderObject.IndexCount <= 0 || renderObject.VertexBuffer is null || renderObject.IndexBuffer is null) + { + return; + } + + int materialID = renderObject.Material.GetMaterialID(); + + if (currentMaterialID != materialID) + { + apply(renderObject.Material, ref currentLayout); + currentMaterialID = materialID; + } + + device.SetVertexBuffer(renderObject.VertexBuffer, currentLayout); + device.SetIndexBuffer(renderObject.IndexBuffer, renderObject.IndexType); + device.DrawIndexed(renderObject.Material.Primitives, (uint)renderObject.IndexCount); + } + + private void apply(IMaterial material, ref InputLayout layout) + { + var blendCache = getCache(); + + if (!blendCache.TryGetValue(material.Blend, out var blendState)) + { + blendState = device.CreateBlendState(material.Blend); + blendCache.Add(material.Blend, blendState); + } + + device.SetBlendState(blendState); + + var shaderCache = getCache(); + + if (!shaderCache.TryGetValue(material.Effect, out var shader)) + { + shader = device.CreateShader((ShaderCode[])material.Effect); + shaderCache.Add(material.Effect, shader); + } + + device.SetShader(shader); + + var layoutCache = getCache(); + + if (!layoutCache.TryGetValue(material.Layout, out var layoutState)) + { + layoutState = device.CreateInputLayout(material.Layout); + layoutCache.Add(material.Layout, layoutState); + } + + layout = layoutState; + + var rasterizerCache = getCache(); + + if (!rasterizerCache.TryGetValue(material.Rasterizer, out var rasterizerState)) + { + rasterizerState = device.CreateRasterizerState(material.Rasterizer); + rasterizerCache.Add(material.Rasterizer, rasterizerState); + } + + device.SetRasterizerState(rasterizerState); + + var depthStencilCache = getCache(); + + if (!depthStencilCache.TryGetValue(material.DepthStencil, out var depthStencilState)) + { + depthStencilState = device.CreateDepthStencilState(material.DepthStencil); + depthStencilCache.Add(material.DepthStencil, depthStencilState); + } + + device.SetDepthStencilState(depthStencilState, material.Stencil); + + foreach (var property in material.Properties) + { + if (property is UniformProperty uniform) + { + if (uniform.Uniform is not null) + { + device.SetUniformBuffer(uniform.Uniform, (uint)uniform.Slot); + } + } + + if (property is TextureProperty texture) + { + device.SetTexture(texture.Texture ?? WhitePixel, texture.Sampler ?? SamplerPoint, (uint)texture.Slot); + } + } + } + + private IDictionary getCache() + where T : struct, IEquatable + where U : notnull + { + if (!caches.TryGetValue(typeof(T), out var cache)) + { + cache = new Dictionary(); + caches.Add(typeof(T), cache); + } + + return (IDictionary)cache; + } +} From 3f07cd20765d6988634ca5c90126dc9e24a37f23 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:30:54 +0800 Subject: [PATCH 18/30] add `SortedFilteredCollection` --- .../Collections/SortedFilteredCollection.cs | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 source/Vignette/Collections/SortedFilteredCollection.cs diff --git a/source/Vignette/Collections/SortedFilteredCollection.cs b/source/Vignette/Collections/SortedFilteredCollection.cs new file mode 100644 index 0000000..d422217 --- /dev/null +++ b/source/Vignette/Collections/SortedFilteredCollection.cs @@ -0,0 +1,144 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Vignette.Collections; + +/// +/// A collection whose items are sorted and filtered. +/// +/// The type this collection will contain. +public class SortedFilteredCollection : ICollection +{ + public int Count => items.Count; + + private bool shouldRebuildCache; + private readonly List items = new(); + private readonly List cache = new(); + private readonly IComparer sorter; + private readonly Predicate filter; + private readonly Action sorterChangedSubscriber; + private readonly Action sorterChangedUnsubscriber; + private readonly Action filterChangedSubscriber; + private readonly Action filterChangedUnsubscriber; + + public SortedFilteredCollection( + IComparer sorter, + Predicate filter, + Action sorterChangedSubscriber, + Action sorterChangedUnsubscriber, + Action filterChangedSubscriber, + Action filterChangedUnsubscriber + ) + { + this.sorter = sorter; + this.filter = filter; + this.sorterChangedSubscriber = sorterChangedSubscriber; + this.sorterChangedUnsubscriber = sorterChangedUnsubscriber; + this.filterChangedSubscriber = filterChangedSubscriber; + this.filterChangedUnsubscriber = filterChangedUnsubscriber; + } + + public void Add(T item) + { + items.Add(item); + invalidate(); + } + + public bool Remove(T item) + { + if (!items.Remove(item)) + { + return false; + } + + invalidate(); + unsubscribeFromEvents(item); + + return true; + } + + public void Clear() + { + for (int i = 0; i < items.Count; i++) + { + unsubscribeFromEvents(items[i]); + } + + items.Clear(); + invalidate(); + } + + public bool Contains(T item) + { + return items.Contains(item); + } + + public IEnumerator GetEnumerator() + { + if (shouldRebuildCache) + { + cache.Clear(); + + for (int i = 0; i < items.Count; i++) + { + var item = items[i]; + + if (filter(item)) + { + cache.Add(item); + subscribeToEvents(item); + } + } + + if (cache.Count > 0) + { + cache.Sort(sorter); + } + + shouldRebuildCache = false; + } + + return cache.GetEnumerator(); + } + + private void invalidate() + { + shouldRebuildCache = true; + } + + private void subscribeToEvents(T item) + { + sorterChangedSubscriber(item, handleSorterChanged); + filterChangedSubscriber(item, handleFilterChanged); + } + + private void unsubscribeFromEvents(T item) + { + sorterChangedUnsubscriber(item, handleSorterChanged); + filterChangedUnsubscriber(item, handleFilterChanged); + } + + private void handleSorterChanged(object? sender, EventArgs args) + { + unsubscribeFromEvents((T)sender!); + invalidate(); + } + + private void handleFilterChanged(object? sender, EventArgs args) + { + invalidate(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + bool ICollection.IsReadOnly => false; + + void ICollection.CopyTo(T[] array, int arrayIndex) => items.CopyTo(array, arrayIndex); +} From 536e586c5e54f20b85cccb6040f1c64ddfb1034a Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:31:17 +0800 Subject: [PATCH 19/30] add `Drawable` --- source/Vignette/Drawable.cs | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 source/Vignette/Drawable.cs diff --git a/source/Vignette/Drawable.cs b/source/Vignette/Drawable.cs new file mode 100644 index 0000000..86d526b --- /dev/null +++ b/source/Vignette/Drawable.cs @@ -0,0 +1,46 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using Vignette.Graphics; + +namespace Vignette; + +/// +/// A that is capable of drawing. +/// +public abstract class Drawable : Renderable +{ + /// + /// Whether this should be drawn or not. + /// + public bool Visible + { + get => visible; + set + { + if (visible.Equals(value)) + { + return; + } + + visible = value; + VisibleChanged?.Invoke(this, EventArgs.Empty); + } + } + + /// + /// Called when has been changed. + /// + public event EventHandler? VisibleChanged; + + private bool visible = true; + + /// + /// Called every frame to perform drawing operations on this . + /// + /// The drawable rendering context. + public virtual void Draw(RenderContext context) + { + } +} From 48b8a7b1af727bac98e55e6e41f8316f40079386 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:31:29 +0800 Subject: [PATCH 20/30] add projectors --- source/Vignette/Camera.cs | 116 ++++++++++++++++++++++++++++++++++++++ source/Vignette/Light.cs | 41 ++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 source/Vignette/Camera.cs create mode 100644 source/Vignette/Light.cs diff --git a/source/Vignette/Camera.cs b/source/Vignette/Camera.cs new file mode 100644 index 0000000..c710bbd --- /dev/null +++ b/source/Vignette/Camera.cs @@ -0,0 +1,116 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Numerics; +using Sekai.Mathematics; +using Vignette.Graphics; + +namespace Vignette; + +public class Camera : Node, IProjector +{ + /// + /// The near plane distance. + /// + public float NearPlane = 0.1f; + + /// + /// The far plane distance. + /// + public float FarPlane = 1000f; + + /// + /// The camera's aspect ratio. + /// + /// Used when is . + public float AspectRatio = 16.0f / 9.0f; + + /// + /// The camera's field of view. + /// + public float FieldOfView = 60.0f; + + /// + /// The camera's view size. + /// + public SizeF ViewSize = SizeF.Zero; + + /// + /// The camera's view scale. + /// + public Vector2 ViewScale = Vector2.One; + + /// + /// The camera's top left position. + /// + public Vector2 ViewTopLeft = Vector2.Zero; + + /// + /// The camera projection mode. + /// + public CameraProjectionMode ProjectionMode = CameraProjectionMode.OrthographicOffCenter; + + /// + /// The camera's position. + /// + public Vector3 Position { get; set; } + + /// + /// The camera's rotation. + /// + public Vector3 Rotation { get; set; } + + /// + /// The camera's rendering groups. + /// + public RenderGroup Groups { get; set; } = RenderGroup.Default; + + /// + /// The camera's view frustum. + /// + public BoundingFrustum Frustum => BoundingFrustum.FromMatrix(((IProjector)this).ProjMatrix); + + Matrix4x4 IProjector.ViewMatrix => Matrix4x4.CreateLookAt(Position, Position + Vector3.Transform(-Vector3.UnitZ, Matrix4x4.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z)), Vector3.UnitY); + + Matrix4x4 IProjector.ProjMatrix => ProjectionMode switch + { + CameraProjectionMode.Perspective => Matrix4x4.CreatePerspective(ViewSize.Width * ViewScale.X, ViewSize.Height * ViewScale.Y, NearPlane, FarPlane), + CameraProjectionMode.PerspectiveOffCenter => Matrix4x4.CreatePerspectiveOffCenter(ViewTopLeft.X, ViewSize.Width * ViewScale.X, ViewSize.Height * ViewScale.Y, ViewTopLeft.Y, NearPlane, FarPlane), + CameraProjectionMode.PerspectiveFieldOfView => Matrix4x4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane), + CameraProjectionMode.Orthographic => Matrix4x4.CreateOrthographic(ViewSize.Width * ViewScale.X, ViewSize.Height * ViewScale.Y, NearPlane, FarPlane), + CameraProjectionMode.OrthographicOffCenter => Matrix4x4.CreateOrthographicOffCenter(ViewTopLeft.X, ViewSize.Width * ViewScale.X, ViewSize.Height * ViewScale.Y, ViewTopLeft.Y, NearPlane, FarPlane), + _ => throw new InvalidOperationException($"Unknown {nameof(ProjectionMode)} {ProjectionMode}."), + }; +} + +/// +/// An enumeration of camera projection modes. +/// +public enum CameraProjectionMode +{ + /// + /// Orthographic projection. + /// + Orthographic, + + /// + /// Custom orthographic projection. + /// + OrthographicOffCenter, + + /// + /// Perspective projection. + /// + Perspective, + + /// + /// Custom perspective projection. + /// + PerspectiveOffCenter, + + /// + /// Perspective field of view projection. + /// + PerspectiveFieldOfView, +} diff --git a/source/Vignette/Light.cs b/source/Vignette/Light.cs new file mode 100644 index 0000000..93412d1 --- /dev/null +++ b/source/Vignette/Light.cs @@ -0,0 +1,41 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System.Numerics; +using Sekai.Mathematics; +using Vignette.Graphics; + +namespace Vignette; + +/// +/// Represents a light source. +/// +public abstract class Light : Node, IProjector +{ + /// + /// The light source's position. + /// + public Vector3 Position { get; set; } + + /// + /// The light source's rotation. + /// + public Vector3 Rotation { get; set; } + + /// + /// The light source's view matrix. + /// + public abstract Matrix4x4 ViewMatrix { get; } + + /// + /// The light source's projection matrix. + /// + public abstract Matrix4x4 ProjMatrix { get; } + + /// + /// The light source's bounding frustum. + /// + public BoundingFrustum Frustum => BoundingFrustum.FromMatrix(ProjMatrix); + + RenderGroup IProjector.Groups => RenderGroup.Default; +} From 194e1e1e44da659f01332b8a8b204a95b7c1b874 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:31:39 +0800 Subject: [PATCH 21/30] add `World` --- source/Vignette/World.cs | 205 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 source/Vignette/World.cs diff --git a/source/Vignette/World.cs b/source/Vignette/World.cs new file mode 100644 index 0000000..d64e1d3 --- /dev/null +++ b/source/Vignette/World.cs @@ -0,0 +1,205 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using Sekai.Mathematics; +using Vignette.Collections; +using Vignette.Graphics; + +namespace Vignette; + +/// +/// A node that represents the world. +/// +public class World : Renderable +{ + private readonly SortedFilteredCollection behaviors = new + ( + Comparer.Default, + (node) => node.Enabled, + (node, handler) => node.OrderChanged += handler, + (node, handler) => node.OrderChanged -= handler, + (node, handler) => node.EnabledChanged += handler, + (node, handler) => node.EnabledChanged -= handler + ); + + private readonly SortedFilteredCollection drawables = new + ( + Comparer.Default, + (node) => node.Visible, + (node, handler) => node.OrderChanged += handler, + (node, handler) => node.OrderChanged -= handler, + (node, handler) => node.VisibleChanged += handler, + (node, handler) => node.VisibleChanged -= handler + ); + + private readonly SortedFilteredCollection worlds = new + ( + Comparer.Default, + (node) => node.Enabled, + (node, handler) => node.OrderChanged += handler, + (node, handler) => node.OrderChanged -= handler, + (node, handler) => node.EnabledChanged += handler, + (node, handler) => node.EnabledChanged -= handler + ); + + private readonly List lights = new(); + private readonly List cameras = new(); + + private readonly RenderQueue renderQueue = new(); + private readonly Queue behaviorLoadQueue = new(); + private readonly Queue behaviorUnloadQueue = new(); + + public World() + { + CollectionChanged += handleCollectionChanged; + } + + public override void Update(TimeSpan elapsed) + { + while (behaviorLoadQueue.TryDequeue(out var node)) + { + node.Load(); + add(node, worlds); + add(node, cameras); + add(node, behaviors); + add(node, drawables); + } + + while (behaviorUnloadQueue.TryDequeue(out var node)) + { + node.Load(); + rem(node, worlds); + rem(node, cameras); + rem(node, behaviors); + rem(node, drawables); + } + + foreach (var behavior in behaviors) + { + behavior.Update(elapsed); + } + + static void add(Node node, ICollection target) + where T : Node + { + if (node is not T typed) + { + return; + } + + target.Add(typed); + } + + static void rem(Node node, ICollection target) + where T : Node + { + if (node is not T typed) + { + return; + } + + target.Remove(typed); + } + } + + public void Draw(Renderer renderer) + { + foreach (var world in worlds) + { + world.Draw(renderer); + } + + foreach (var camera in cameras) + { + foreach (var light in lights) + { + renderQueue.Clear(); + + if (BoundingFrustum.Contains(camera.Frustum, light.Frustum) == Containment.Disjoint) + { + continue; + } + + foreach (var drawable in drawables) + { + drawable.Draw(renderQueue.Begin(light, drawable)); + } + + renderer.Draw(renderQueue); + } + } + + foreach (var camera in cameras) + { + renderQueue.Clear(); + + foreach (var drawable in drawables) + { + drawable.Draw(renderQueue.Begin(camera, drawable)); + } + + renderer.Draw(renderQueue); + } + } + + private void handleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) + { + if (args.Action == NotifyCollectionChangedAction.Add) + { + foreach (var node in args.NewItems!.Cast()) + { + load(node); + } + } + + if (args.Action == NotifyCollectionChangedAction.Remove) + { + foreach (var node in args.OldItems!.Cast()) + { + unload(node); + } + } + + if (args.Action == NotifyCollectionChangedAction.Reset) + { + foreach (var node in GetNodes()) + { + unload(node); + } + } + } + + private void load(Behavior node) + { + foreach (var child in node.GetNodes()) + { + load(child); + } + + if (node is not World) + { + node.CollectionChanged += handleCollectionChanged; + } + + behaviorLoadQueue.Enqueue(node); + } + + private void unload(Behavior node) + { + foreach (var child in node.GetNodes()) + { + unload(child); + } + + if (node is not World) + { + node.CollectionChanged -= handleCollectionChanged; + } + + behaviorUnloadQueue.Enqueue(node); + } +} From 072cc4ee5de77c7f8b7d7642cdfc7bbf75280b85 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:31:55 +0800 Subject: [PATCH 22/30] add `Window` node --- source/Vignette/Window.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 source/Vignette/Window.cs diff --git a/source/Vignette/Window.cs b/source/Vignette/Window.cs new file mode 100644 index 0000000..233cc9e --- /dev/null +++ b/source/Vignette/Window.cs @@ -0,0 +1,14 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +namespace Vignette; + +/// +/// The root of . +/// +public sealed class Window : World +{ + internal Window() + { + } +} From f3a3e540456ec1a53179455934e082bb39f41ad0 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 21:32:10 +0800 Subject: [PATCH 23/30] add executable --- .vscode/launch.json | 26 +++++++++++++++++++ .vscode/tasks.json | 41 ++++++++++++++++++++++++++++++ source/Vignette.Desktop/Program.cs | 7 +++++ source/Vignette/VignetteGame.cs | 34 +++++++++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 source/Vignette.Desktop/Program.cs create mode 100644 source/Vignette/VignetteGame.cs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b3ee6a5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/source/Vignette.Desktop/bin/Debug/net7.0/Vignette.Desktop.dll", + "args": [], + "cwd": "${workspaceFolder}/source/Vignette.Desktop", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..63adaee --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/source/Vignette.Desktop/Vignette.Desktop.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/source/Vignette.Desktop/Vignette.Desktop.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/source/Vignette.Desktop/Vignette.Desktop.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/source/Vignette.Desktop/Program.cs b/source/Vignette.Desktop/Program.cs new file mode 100644 index 0000000..df58c82 --- /dev/null +++ b/source/Vignette.Desktop/Program.cs @@ -0,0 +1,7 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using Sekai; +using Vignette; + +Host.Run(new HostOptions { Name = "Vignette" }); diff --git a/source/Vignette/VignetteGame.cs b/source/Vignette/VignetteGame.cs new file mode 100644 index 0000000..884fc58 --- /dev/null +++ b/source/Vignette/VignetteGame.cs @@ -0,0 +1,34 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using Sekai; +using Vignette.Graphics; + +namespace Vignette; + +public sealed class VignetteGame : Game +{ + private Renderer renderer = null!; + private readonly Window root = new(); + + public override void Load() + { + renderer = new(Graphics); + } + + public override void Draw() + { + root.Draw(renderer); + } + + public override void Update(TimeSpan elapsed) + { + root.Update(elapsed); + } + + public override void Unload() + { + root.Clear(); + } +} From 54940e956446cf4025654d364bd884706c541c5b Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 22:17:45 +0800 Subject: [PATCH 24/30] ensure material resources are applied --- source/Vignette/Graphics/Renderer.cs | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/source/Vignette/Graphics/Renderer.cs b/source/Vignette/Graphics/Renderer.cs index 723e0ce..6454323 100644 --- a/source/Vignette/Graphics/Renderer.cs +++ b/source/Vignette/Graphics/Renderer.cs @@ -108,6 +108,22 @@ private void draw(RenderObject renderObject, ref int currentMaterialID, ref Inpu currentMaterialID = materialID; } + foreach (var property in renderObject.Material.Properties) + { + if (property is UniformProperty uniform) + { + if (uniform.Uniform is not null) + { + device.SetUniformBuffer(uniform.Uniform, (uint)uniform.Slot); + } + } + + if (property is TextureProperty texture) + { + device.SetTexture(texture.Texture ?? WhitePixel, texture.Sampler ?? SamplerPoint, (uint)texture.Slot); + } + } + device.SetVertexBuffer(renderObject.VertexBuffer, currentLayout); device.SetIndexBuffer(renderObject.IndexBuffer, renderObject.IndexType); device.DrawIndexed(renderObject.Material.Primitives, (uint)renderObject.IndexCount); @@ -164,22 +180,6 @@ private void apply(IMaterial material, ref InputLayout layout) } device.SetDepthStencilState(depthStencilState, material.Stencil); - - foreach (var property in material.Properties) - { - if (property is UniformProperty uniform) - { - if (uniform.Uniform is not null) - { - device.SetUniformBuffer(uniform.Uniform, (uint)uniform.Slot); - } - } - - if (property is TextureProperty texture) - { - device.SetTexture(texture.Texture ?? WhitePixel, texture.Sampler ?? SamplerPoint, (uint)texture.Slot); - } - } } private IDictionary getCache() From 64e87b49168c156cb21e2a2360ea18c19bc15257 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 22:18:18 +0800 Subject: [PATCH 25/30] remove `Renderable` --- source/Vignette/Camera.cs | 10 ------ source/Vignette/Drawable.cs | 31 ++++++++++++++++- source/Vignette/Light.cs | 16 +++------ source/Vignette/Node.cs | 11 ++++++ source/Vignette/Renderable.cs | 63 ----------------------------------- source/Vignette/World.cs | 2 +- 6 files changed, 46 insertions(+), 87 deletions(-) delete mode 100644 source/Vignette/Renderable.cs diff --git a/source/Vignette/Camera.cs b/source/Vignette/Camera.cs index c710bbd..34c0551 100644 --- a/source/Vignette/Camera.cs +++ b/source/Vignette/Camera.cs @@ -51,16 +51,6 @@ public class Camera : Node, IProjector /// public CameraProjectionMode ProjectionMode = CameraProjectionMode.OrthographicOffCenter; - /// - /// The camera's position. - /// - public Vector3 Position { get; set; } - - /// - /// The camera's rotation. - /// - public Vector3 Rotation { get; set; } - /// /// The camera's rendering groups. /// diff --git a/source/Vignette/Drawable.cs b/source/Vignette/Drawable.cs index 86d526b..4ce2c91 100644 --- a/source/Vignette/Drawable.cs +++ b/source/Vignette/Drawable.cs @@ -2,6 +2,7 @@ // Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. using System; +using System.Numerics; using Vignette.Graphics; namespace Vignette; @@ -9,7 +10,7 @@ namespace Vignette; /// /// A that is capable of drawing. /// -public abstract class Drawable : Renderable +public abstract class Drawable : Behavior, IWorld { /// /// Whether this should be drawn or not. @@ -29,12 +30,37 @@ public bool Visible } } + /// + /// The renderable's scaling. + /// + public Vector3 Scale + { + get => new(scale[0, 0], scale[1, 1], scale[2, 2]); + set => scale = Matrix4x4.CreateScale(value); + } + + /// + /// The renderable's shearing. + /// + public Vector3 Shear + { + get => new(shear[0, 1], shear[0, 2], shear[1, 2]); + set + { + shear[0, 1] = value.X; + shear[0, 2] = value.Y; + shear[1, 2] = value.Z; + } + } + /// /// Called when has been changed. /// public event EventHandler? VisibleChanged; private bool visible = true; + private Matrix4x4 scale = Matrix4x4.Identity; + private Matrix4x4 shear = Matrix4x4.Identity; /// /// Called every frame to perform drawing operations on this . @@ -43,4 +69,7 @@ public bool Visible public virtual void Draw(RenderContext context) { } + + Matrix4x4 IWorld.LocalMatrix => shear * scale * Matrix4x4.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z) * Matrix4x4.CreateTranslation(Position); + Matrix4x4 IWorld.WorldMatrix => Parent is not IWorld provider ? ((IWorld)this).LocalMatrix : provider.LocalMatrix * ((IWorld)this).LocalMatrix; } diff --git a/source/Vignette/Light.cs b/source/Vignette/Light.cs index 93412d1..b71feeb 100644 --- a/source/Vignette/Light.cs +++ b/source/Vignette/Light.cs @@ -12,30 +12,22 @@ namespace Vignette; /// public abstract class Light : Node, IProjector { - /// - /// The light source's position. - /// - public Vector3 Position { get; set; } - - /// - /// The light source's rotation. - /// - public Vector3 Rotation { get; set; } - /// /// The light source's view matrix. /// - public abstract Matrix4x4 ViewMatrix { get; } + protected abstract Matrix4x4 ViewMatrix { get; } /// /// The light source's projection matrix. /// - public abstract Matrix4x4 ProjMatrix { get; } + protected abstract Matrix4x4 ProjMatrix { get; } /// /// The light source's bounding frustum. /// public BoundingFrustum Frustum => BoundingFrustum.FromMatrix(ProjMatrix); + Matrix4x4 IProjector.ViewMatrix => ViewMatrix; + Matrix4x4 IProjector.ProjMatrix => ProjMatrix; RenderGroup IProjector.Groups => RenderGroup.Default; } diff --git a/source/Vignette/Node.cs b/source/Vignette/Node.cs index 83e82b4..f8f86b8 100644 --- a/source/Vignette/Node.cs +++ b/source/Vignette/Node.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Numerics; namespace Vignette; @@ -38,6 +39,16 @@ public abstract class Node : INotifyCollectionChanged, ICollection, IEquat /// public Node? Parent { get; private set; } + /// + /// The node's position. + /// + public Vector3 Position { get; set; } + + /// + /// The node's rotation. + /// + public Vector3 Rotation { get; set; } + /// /// Called when the 's children has been changed. /// diff --git a/source/Vignette/Renderable.cs b/source/Vignette/Renderable.cs deleted file mode 100644 index a56c721..0000000 --- a/source/Vignette/Renderable.cs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Cosyne -// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. - -using System; -using System.Numerics; -using Vignette.Graphics; - -namespace Vignette; - -/// -/// A that provides a world space. -/// -public abstract class Renderable : Behavior, IWorld -{ - /// - /// The renderable's position. - /// - public Vector3 Position - { - get => position.Translation; - set => position = Matrix4x4.CreateTranslation(value); - } - - /// - /// The renderable's rotation as pitch, yaw, and roll. - /// - public Vector3 Rotation - { - get => new(MathF.Atan2(rotation[0, 2], rotation[2, 2]), MathF.Asin(-rotation[1, 2]), MathF.Atan2(rotation[1, 0], rotation[1, 1])); - set => rotation = Matrix4x4.CreateFromYawPitchRoll(value.Y, value.X, value.Z); - } - - /// - /// The renderable's scaling. - /// - public Vector3 Scale - { - get => new(scale[0, 0], scale[1, 1], scale[2, 2]); - set => scale = Matrix4x4.CreateScale(value); - } - - /// - /// The renderable's shearing. - /// - public Vector3 Shear - { - get => new(shear[0, 1], shear[0, 2], shear[1, 2]); - set - { - shear[0, 1] = value.X; - shear[0, 2] = value.Y; - shear[1, 2] = value.Z; - } - } - - private Matrix4x4 scale = Matrix4x4.Identity; - private Matrix4x4 shear = Matrix4x4.Identity; - private Matrix4x4 rotation = Matrix4x4.Identity; - private Matrix4x4 position = Matrix4x4.Identity; - - Matrix4x4 IWorld.LocalMatrix => shear * scale * rotation * position; - Matrix4x4 IWorld.WorldMatrix => Parent is not IWorld provider ? ((IWorld)this).LocalMatrix : provider.LocalMatrix * ((IWorld)this).LocalMatrix; -} diff --git a/source/Vignette/World.cs b/source/Vignette/World.cs index d64e1d3..38b7ecc 100644 --- a/source/Vignette/World.cs +++ b/source/Vignette/World.cs @@ -14,7 +14,7 @@ namespace Vignette; /// /// A node that represents the world. /// -public class World : Renderable +public class World : Behavior { private readonly SortedFilteredCollection behaviors = new ( From 347daea448ec8f8c1239d796f1ecc6aa176256d7 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 14 Jul 2023 16:06:48 +0000 Subject: [PATCH 26/30] cleanup pass --- source/Vignette/Drawable.cs | 31 +---------- source/Vignette/Graphics/Effect.cs | 58 +++++++++++++++++---- source/Vignette/Graphics/RenderObject.cs | 2 +- source/Vignette/Graphics/RenderQueue.cs | 7 ++- source/Vignette/Graphics/Renderer.cs | 5 +- source/Vignette/Node.cs | 39 +++++++++++++- source/Vignette/World.cs | 66 ++++++++++++++++++++---- 7 files changed, 153 insertions(+), 55 deletions(-) diff --git a/source/Vignette/Drawable.cs b/source/Vignette/Drawable.cs index 4ce2c91..e24023b 100644 --- a/source/Vignette/Drawable.cs +++ b/source/Vignette/Drawable.cs @@ -2,7 +2,6 @@ // Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. using System; -using System.Numerics; using Vignette.Graphics; namespace Vignette; @@ -10,7 +9,7 @@ namespace Vignette; /// /// A that is capable of drawing. /// -public abstract class Drawable : Behavior, IWorld +public abstract class Drawable : Behavior { /// /// Whether this should be drawn or not. @@ -30,37 +29,12 @@ public bool Visible } } - /// - /// The renderable's scaling. - /// - public Vector3 Scale - { - get => new(scale[0, 0], scale[1, 1], scale[2, 2]); - set => scale = Matrix4x4.CreateScale(value); - } - - /// - /// The renderable's shearing. - /// - public Vector3 Shear - { - get => new(shear[0, 1], shear[0, 2], shear[1, 2]); - set - { - shear[0, 1] = value.X; - shear[0, 2] = value.Y; - shear[1, 2] = value.Z; - } - } - /// /// Called when has been changed. /// public event EventHandler? VisibleChanged; private bool visible = true; - private Matrix4x4 scale = Matrix4x4.Identity; - private Matrix4x4 shear = Matrix4x4.Identity; /// /// Called every frame to perform drawing operations on this . @@ -69,7 +43,4 @@ public Vector3 Shear public virtual void Draw(RenderContext context) { } - - Matrix4x4 IWorld.LocalMatrix => shear * scale * Matrix4x4.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z) * Matrix4x4.CreateTranslation(Position); - Matrix4x4 IWorld.WorldMatrix => Parent is not IWorld provider ? ((IWorld)this).LocalMatrix : provider.LocalMatrix * ((IWorld)this).LocalMatrix; } diff --git a/source/Vignette/Graphics/Effect.cs b/source/Vignette/Graphics/Effect.cs index 3662974..553751c 100644 --- a/source/Vignette/Graphics/Effect.cs +++ b/source/Vignette/Graphics/Effect.cs @@ -50,6 +50,8 @@ public override readonly int GetHashCode() /// A new . internal static Effect From(string code, out InputLayoutDescription layout, out IProperty[] properties) { + code = sh_common + code; + var shVert = ShaderCode.From(code, ShaderStage.Vertex, sh_vert, ShaderLanguage.HLSL); var shFrag = ShaderCode.From(code, ShaderStage.Fragment, sh_frag, ShaderLanguage.HLSL); @@ -72,30 +74,49 @@ internal static Effect From(string code, out InputLayoutDescription layout, out layout = new(); } - var props = new List(); + var textures = new List(); + var uniforms = new List(); if (shVertReflect.Uniforms is not null) { - foreach (var uniform in shVertReflect.Uniforms) - props.Add(new UniformProperty(uniform.Name, uniform.Binding)); + uniforms.AddRange(shVertReflect.Uniforms); } if (shVertReflect.Textures is not null) { - foreach (var texture in shVertReflect.Textures) - props.Add(new TextureProperty(texture.Name, texture.Binding)); + textures.AddRange(shVertReflect.Textures); } if (shFragReflect.Uniforms is not null) { - foreach (var uniform in shFragReflect.Uniforms) - props.Add(new UniformProperty(uniform.Name, uniform.Binding)); + uniforms.AddRange(shFragReflect.Uniforms); } if (shFragReflect.Textures is not null) { - foreach (var texture in shFragReflect.Textures) - props.Add(new TextureProperty(texture.Name, texture.Binding)); + textures.AddRange(shFragReflect.Textures); + } + + var props = new List(); + + foreach (var uniform in uniforms) + { + if (uniform.Name.StartsWith(sh_global)) + { + continue; + } + + props.Add(new UniformProperty(uniform.Name, uniform.Binding)); + } + + foreach (var texture in textures) + { + if (texture.Name.StartsWith(sh_global)) + { + continue; + } + + props.Add(new TextureProperty(texture.Name, texture.Binding)); } properties = props.ToArray(); @@ -115,8 +136,27 @@ internal static Effect From(string code, out InputLayoutDescription layout, out public static explicit operator ShaderCode[](Effect effect) => effect.shaderCodes; + public const int GLOBAL_TRANSFORM_ID = 89; + private const string sh_vert = "Vertex"; private const string sh_frag = "Pixel"; + private const string sh_global = "g_internal_"; + + private static readonly string sh_common = +@$" +#define P_MATRIX g_internal_ProjMatrix +#define V_MATRIX g_internal_ViewMatrix +#define M_MATRIX g_internal_ModelMatrix +#define OBJECT_TO_CLIP(a) mul(mul(V_MATRIX, M_MATRIX), a) +#define OBJECT_TO_VIEW(a) mul(P_MATRIX, OBJECT_TO_CLIP(a)) + +cbuffer g_internal_Transform : register(b{GLOBAL_TRANSFORM_ID}) +{{ + float4x4 g_internal_ProjMatrix; + float4x4 g_internal_ViewMatrix; + float4x4 g_internal_ModelMatrix; +}}; +"; private static readonly IReadOnlyDictionary format_members = ImmutableDictionary.CreateRange ( diff --git a/source/Vignette/Graphics/RenderObject.cs b/source/Vignette/Graphics/RenderObject.cs index aaccb71..5006657 100644 --- a/source/Vignette/Graphics/RenderObject.cs +++ b/source/Vignette/Graphics/RenderObject.cs @@ -9,7 +9,7 @@ namespace Vignette.Graphics; /// /// An object that can be drawn. /// -public abstract class RenderObject +public class RenderObject { /// /// The bounding box of this . diff --git a/source/Vignette/Graphics/RenderQueue.cs b/source/Vignette/Graphics/RenderQueue.cs index 76a80be..5466303 100644 --- a/source/Vignette/Graphics/RenderQueue.cs +++ b/source/Vignette/Graphics/RenderQueue.cs @@ -38,7 +38,7 @@ public RenderQueue() /// The render object to be enqueued. public void Enqueue(IProjector projector, IWorld world, RenderObject renderObject) { - if ((projector.Groups & renderObject.Groups) == 0) + if ((projector.Groups & renderObject.Groups) != 0) { return; } @@ -85,7 +85,7 @@ IEnumerator IEnumerable.GetEnumerator() private struct Enumerator : IEnumerator { - public readonly RenderData Current => renderables[renderOrders[index].Renderable]; + public RenderData Current { get; private set; } private int index; private readonly IReadOnlyList renderables; @@ -101,10 +101,12 @@ public bool MoveNext() { if (index >= renderOrders.Count) { + Current = default; return false; } else { + Current = renderables[renderOrders[index].Renderable]; index += 1; return true; } @@ -113,6 +115,7 @@ public bool MoveNext() public void Reset() { index = 0; + Current = default; } public readonly void Dispose() diff --git a/source/Vignette/Graphics/Renderer.cs b/source/Vignette/Graphics/Renderer.cs index 6454323..79ae4eb 100644 --- a/source/Vignette/Graphics/Renderer.cs +++ b/source/Vignette/Graphics/Renderer.cs @@ -40,7 +40,10 @@ internal Renderer(GraphicsDevice device) { ubo = device.CreateBuffer(BufferType.Uniform, 3, true); + Span whitePixel = stackalloc byte[] { 255, 255, 255, 255 }; + WhitePixel = device.CreateTexture(new(1, 1, PixelFormat.R8G8B8A8_UNorm, 1, 1, TextureUsage.Resource)); + WhitePixel.SetData((ReadOnlySpan)whitePixel, 0, 0, 0, 0, 0, 1, 1, 0); SamplerPoint = device.CreateSampler(new(TextureFilter.MinMagMipPoint, TextureAddress.Repeat, TextureAddress.Repeat, TextureAddress.Repeat, 0, Color.White, 0, 0, 0)); SamplerLinear = device.CreateSampler(new(TextureFilter.MinMagMipLinear, TextureAddress.Repeat, TextureAddress.Repeat, TextureAddress.Repeat, 0, Color.White, 0, 0, 0)); @@ -78,7 +81,7 @@ public void Draw(IEnumerable renderables, RenderTarget? target = nul mvp[2] = data.World.WorldMatrix; } - device.SetUniformBuffer(ubo, 89); + device.SetUniformBuffer(ubo, Effect.GLOBAL_TRANSFORM_ID); if (target is not null) { diff --git a/source/Vignette/Node.cs b/source/Vignette/Node.cs index f8f86b8..37dffad 100644 --- a/source/Vignette/Node.cs +++ b/source/Vignette/Node.cs @@ -9,10 +9,15 @@ using System.IO; using System.Linq; using System.Numerics; +using Vignette.Graphics; namespace Vignette; -public abstract class Node : INotifyCollectionChanged, ICollection, IEquatable +/// +/// The base class of everything that resides inside the node graph. It can be a child of +/// another and can contain its own children s. +/// +public class Node : IWorld, INotifyCollectionChanged, ICollection, IEquatable { /// /// The 's unique identifier. @@ -49,11 +54,41 @@ public abstract class Node : INotifyCollectionChanged, ICollection, IEquat /// public Vector3 Rotation { get; set; } + /// + /// The node's scaling. + /// + public Vector3 Scale { get; set; } = Vector3.One; + + /// + /// The node's shearing. + /// + public Vector3 Shear + { + get => new(shear[0, 1], shear[0, 2], shear[1, 2]); + set + { + shear[0, 1] = value.X; + shear[0, 2] = value.Y; + shear[1, 2] = value.Z; + } + } + + /// + /// The node's local matrix. + /// + protected virtual Matrix4x4 LocalMatrix => shear * Matrix4x4.CreateScale(Scale) * Matrix4x4.CreateFromYawPitchRoll(Rotation.Y, Rotation.X, Rotation.Z) * Matrix4x4.CreateTranslation(Position); + + /// + /// The node's world matrix. + /// + protected virtual Matrix4x4 WorldMatrix => Parent is not IWorld provider ? LocalMatrix : provider.LocalMatrix * LocalMatrix; + /// /// Called when the 's children has been changed. /// public event NotifyCollectionChangedEventHandler? CollectionChanged; + private Matrix4x4 shear = Matrix4x4.Identity; private readonly Dictionary nodes = new(); /// @@ -390,6 +425,8 @@ IEnumerator IEnumerable.GetEnumerator() } bool ICollection.IsReadOnly => false; + Matrix4x4 IWorld.LocalMatrix => LocalMatrix; + Matrix4x4 IWorld.WorldMatrix => WorldMatrix; private const string node_scheme = "node"; private static readonly NotifyCollectionChangedEventArgs reset_args = new(NotifyCollectionChangedAction.Reset); diff --git a/source/Vignette/World.cs b/source/Vignette/World.cs index 38b7ecc..da5582f 100644 --- a/source/Vignette/World.cs +++ b/source/Vignette/World.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Numerics; using Sekai.Mathematics; using Vignette.Collections; using Vignette.Graphics; @@ -12,10 +13,12 @@ namespace Vignette; /// -/// A node that represents the world. +/// A that presents and processes its children. /// public class World : Behavior { + protected override Matrix4x4 WorldMatrix => LocalMatrix; + private readonly SortedFilteredCollection behaviors = new ( Comparer.Default, @@ -64,7 +67,6 @@ public override void Update(TimeSpan elapsed) { node.Load(); add(node, worlds); - add(node, cameras); add(node, behaviors); add(node, drawables); } @@ -73,7 +75,6 @@ public override void Update(TimeSpan elapsed) { node.Load(); rem(node, worlds); - rem(node, cameras); rem(node, behaviors); rem(node, drawables); } @@ -113,17 +114,19 @@ public void Draw(Renderer renderer) world.Draw(renderer); } + // Shadow Map Pass + foreach (var camera in cameras) { foreach (var light in lights) { - renderQueue.Clear(); - if (BoundingFrustum.Contains(camera.Frustum, light.Frustum) == Containment.Disjoint) { continue; } + renderQueue.Clear(); + foreach (var drawable in drawables) { drawable.Draw(renderQueue.Begin(light, drawable)); @@ -133,6 +136,8 @@ public void Draw(Renderer renderer) } } + // Lighting Pass + foreach (var camera in cameras) { renderQueue.Clear(); @@ -150,25 +155,64 @@ private void handleCollectionChanged(object? sender, NotifyCollectionChangedEven { if (args.Action == NotifyCollectionChangedAction.Add) { - foreach (var node in args.NewItems!.Cast()) + foreach (var node in args.NewItems!.OfType()) { - load(node); + if (node is Behavior behavior) + { + load(behavior); + } + + if (node is Light light) + { + lights.Add(light); + } + + if (node is Camera camera) + { + cameras.Add(camera); + } } } if (args.Action == NotifyCollectionChangedAction.Remove) { - foreach (var node in args.OldItems!.Cast()) + foreach (var node in args.OldItems!.OfType()) { - unload(node); + if (node is Behavior behavior) + { + unload(behavior); + } + + if (node is Light light) + { + lights.Remove(light); + } + + if (node is Camera camera) + { + cameras.Remove(camera); + } } } if (args.Action == NotifyCollectionChangedAction.Reset) { - foreach (var node in GetNodes()) + foreach (var node in this) { - unload(node); + if (node is Behavior behavior) + { + unload(behavior); + } + + if (node is Light light) + { + lights.Remove(light); + } + + if (node is Camera camera) + { + cameras.Remove(camera); + } } } } From 37fa552944f35c8e88858ffd2324022fbec5b3ef Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Sat, 15 Jul 2023 05:00:04 +0000 Subject: [PATCH 27/30] add `ServiceLocator` --- source/Vignette/ServiceLocator.cs | 126 ++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 source/Vignette/ServiceLocator.cs diff --git a/source/Vignette/ServiceLocator.cs b/source/Vignette/ServiceLocator.cs new file mode 100644 index 0000000..5da32b5 --- /dev/null +++ b/source/Vignette/ServiceLocator.cs @@ -0,0 +1,126 @@ +// Copyright (c) Cosyne +// Licensed under GPL 3.0 with SDK Exception. See LICENSE for details. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Vignette; + +/// +/// A collection of services. +/// +public sealed class ServiceLocator : IServiceLocator, IServiceProvider +{ + private readonly Dictionary services = new(); + + /// + /// Adds a service to this locator. + /// + /// The type of service. + /// The service instance. + /// Thrown when is already added to this locator. + /// Thrown when cannot be assigned to . + public void Add(Type type, object instance) + { + if (services.ContainsKey(type)) + { + throw new ArgumentException($"{type} already exists in this locator.", nameof(type)); + } + + if (!instance.GetType().IsAssignableTo(type)) + { + throw new InvalidCastException($"The {nameof(instance)} cannot be casted to {type}."); + } + + services.Add(type, instance); + } + + /// + /// Adds a service to this locator. + /// + /// The service instance. + /// The type of service. + /// Thrown when is already added to this locator. + /// Thrown when cannot be assigned to . + public void Add(T instance) + where T : class + { + Add(typeof(T), instance); + } + + /// + /// Removes a service from this locator. + /// + /// The service type to remove. + /// true when the service is removed. false otherwise. + public bool Remove(Type type) + { + return services.Remove(type); + } + + /// + /// Removes a service from this locator. + /// + /// The service type to remove. + /// true when the service is removed. false otherwise. + public bool Remove() + where T : class + { + return Remove(typeof(T)); + } + + public object? Get(Type type, [DoesNotReturnIf(true)] bool required = true) + { + if (!services.TryGetValue(type, out object? instance) && required) + { + throw new ServiceNotFoundException(type); + } + + return instance; + } + + public T? Get([DoesNotReturnIf(true)] bool required = true) + where T : class + { + return Unsafe.As(Get(typeof(T), required)); + } + + object? IServiceProvider.GetService(Type type) => Get(type, false); +} + +/// +/// An interface for objects capable of locating services. +/// +public interface IServiceLocator +{ + /// + /// Gets the service of a given type. + /// + /// The object type to resolve. + /// Whether the service is required or not. + /// The service object of the given type or when is false and the service is not found. + /// Thrown when is true and the service is not found. + object? Get(Type type, [DoesNotReturnIf(true)] bool required = true); + + /// + /// Gets the service of a given type. + /// + /// The object type to resolve. + /// Whether the service is required or not. + /// The service object of type or when is false and the service is not found. + /// Thrown when is true and the service is not found. + T? Get([DoesNotReturnIf(true)] bool required = true) where T : class; +} + +/// +/// Exception thrown when fails to locate a required service of a given type. +/// +public sealed class ServiceNotFoundException : Exception +{ + internal ServiceNotFoundException(Type type) + : base($"Failed to locate service of type {type}.") + { + } +} From 36919c224930d27d376b9f9c3021a70114b8815c Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Sat, 15 Jul 2023 05:00:18 +0000 Subject: [PATCH 28/30] add `Node.Services` --- source/Vignette/Node.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/Vignette/Node.cs b/source/Vignette/Node.cs index 37dffad..908ded8 100644 --- a/source/Vignette/Node.cs +++ b/source/Vignette/Node.cs @@ -39,6 +39,11 @@ public class Node : IWorld, INotifyCollectionChanged, ICollection, IEquata /// public int Count => nodes.Count; + /// + /// The 's services. + /// + public virtual IServiceLocator Services => Parent is not null ? Parent.Services : throw new InvalidOperationException("Services are unavailable"); + /// /// The parent . /// @@ -95,7 +100,7 @@ public Vector3 Shear /// Creates a new . /// /// The optional name for this . - protected Node(string? name = null) + public Node(string? name = null) : this(Guid.NewGuid(), name) { } From 2398f2062c542ee7cd4d822a770b3368a79b6c60 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Sat, 15 Jul 2023 05:00:36 +0000 Subject: [PATCH 29/30] make `Window` override `Services` --- source/Vignette/Window.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/Vignette/Window.cs b/source/Vignette/Window.cs index 233cc9e..50832ef 100644 --- a/source/Vignette/Window.cs +++ b/source/Vignette/Window.cs @@ -8,7 +8,10 @@ namespace Vignette; /// public sealed class Window : World { - internal Window() + public override IServiceLocator Services { get; } + + internal Window(IServiceLocator services) { + Services = services; } } From b8b57fdc4a68c9585636da012d3f8e0820fd2ea9 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Sat, 15 Jul 2023 05:00:48 +0000 Subject: [PATCH 30/30] refactor `World` --- source/Vignette/World.cs | 137 +++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 79 deletions(-) diff --git a/source/Vignette/World.cs b/source/Vignette/World.cs index da5582f..cda0a6e 100644 --- a/source/Vignette/World.cs +++ b/source/Vignette/World.cs @@ -66,45 +66,17 @@ public override void Update(TimeSpan elapsed) while (behaviorLoadQueue.TryDequeue(out var node)) { node.Load(); - add(node, worlds); - add(node, behaviors); - add(node, drawables); } while (behaviorUnloadQueue.TryDequeue(out var node)) { - node.Load(); - rem(node, worlds); - rem(node, behaviors); - rem(node, drawables); + node.Unload(); } foreach (var behavior in behaviors) { behavior.Update(elapsed); } - - static void add(Node node, ICollection target) - where T : Node - { - if (node is not T typed) - { - return; - } - - target.Add(typed); - } - - static void rem(Node node, ICollection target) - where T : Node - { - if (node is not T typed) - { - return; - } - - target.Remove(typed); - } } public void Draw(Renderer renderer) @@ -157,20 +129,7 @@ private void handleCollectionChanged(object? sender, NotifyCollectionChangedEven { foreach (var node in args.NewItems!.OfType()) { - if (node is Behavior behavior) - { - load(behavior); - } - - if (node is Light light) - { - lights.Add(light); - } - - if (node is Camera camera) - { - cameras.Add(camera); - } + load(node); } } @@ -178,20 +137,7 @@ private void handleCollectionChanged(object? sender, NotifyCollectionChangedEven { foreach (var node in args.OldItems!.OfType()) { - if (node is Behavior behavior) - { - unload(behavior); - } - - if (node is Light light) - { - lights.Remove(light); - } - - if (node is Camera camera) - { - cameras.Remove(camera); - } + unload(node); } } @@ -199,51 +145,84 @@ private void handleCollectionChanged(object? sender, NotifyCollectionChangedEven { foreach (var node in this) { - if (node is Behavior behavior) - { - unload(behavior); - } - - if (node is Light light) - { - lights.Remove(light); - } - - if (node is Camera camera) - { - cameras.Remove(camera); - } + unload(node); } } } - private void load(Behavior node) + private void load(Node node) { foreach (var child in node.GetNodes()) { load(child); } - if (node is not World) + if (node is Behavior behavior) { - node.CollectionChanged += handleCollectionChanged; + behaviors.Add(behavior); + behaviorLoadQueue.Enqueue(behavior); } - behaviorLoadQueue.Enqueue(node); + if (node is Drawable drawable) + { + drawables.Add(drawable); + } + + if (node is Light light) + { + lights.Add(light); + } + + if (node is Camera camera) + { + cameras.Add(camera); + } + + if (node is World world) + { + worlds.Add(world); + } + else + { + node.CollectionChanged += handleCollectionChanged; + } } - private void unload(Behavior node) + private void unload(Node node) { foreach (var child in node.GetNodes()) { unload(child); } - if (node is not World) + if (node is Behavior behavior) { - node.CollectionChanged -= handleCollectionChanged; + behaviors.Remove(behavior); + behaviorUnloadQueue.Enqueue(behavior); + } + + if (node is Drawable drawable) + { + drawables.Remove(drawable); + } + + if (node is Light light) + { + lights.Remove(light); } - behaviorUnloadQueue.Enqueue(node); + if (node is Camera camera) + { + cameras.Remove(camera); + } + + if (node is World world) + { + worlds.Add(world); + } + else + { + node.CollectionChanged -= handleCollectionChanged; + } } }