Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Godot 4 Tests/Run.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private static IEnumerable<Func<ITest>> Tests
yield return ITest.GetTest<ScriptForSceneWithDifferentName>;
yield return ITest.GetTest<ScriptForSceneWithDifferentPath>;
yield return ITest.GetTest<ShaderAttributeTests>;
yield return ITest.GetTest<ShaderGlobalsAttributeTests>;
yield return ITest.GetTest<SingletonAttributeTests>;
yield return ITest.GetTest<SubNodeSceneTreeTest>;
yield return ITest.GetTest<TranslationAttributeTests>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using FluentAssertions;
using Godot;
using GodotSharp.BuildingBlocks.TestRunner;

namespace GodotTests.TestScenes;

[ShaderGlobals]
public static partial class ShaderGlobals;

[SceneTree]
public partial class ShaderGlobalsAttributeTests : Node, ITest
{
void ITest.InitTests()
{
ShaderGlobals.A.Should().BeTrue();
ShaderGlobals.B.Should().Be(2);
ShaderGlobals.C.Should().Be(0);
ShaderGlobals.D.Should().Be(9);
ShaderGlobals.E.Should().Be(875);
ShaderGlobals.F.Should().Be(new Vector2I(565, 0));
ShaderGlobals.G.Should().Be(new Vector3I(0, 410, 0));
ShaderGlobals.H.Should().Be(new Vector4I(0, 475, 0, 180));
ShaderGlobals.I.Should().Be(new Rect2I(50, 0, 145, 0));
ShaderGlobals.J.Should().Be(345);
ShaderGlobals.K.Should().Be(new Vector2I(295, 355));
ShaderGlobals.L.Should().Be(new Vector3I(0, 195, 0));
ShaderGlobals.M.Should().Be(new Vector4I(0, 0, 275, 0));
ShaderGlobals.N.Should().Be(0.205f);
ShaderGlobals.O.Should().Be(new Vector2(0.23f, 0.385f));
ShaderGlobals.P.Should().Be(new Vector3(0.0f, 0.435f, 0.0f));
ShaderGlobals.Q.Should().Be(new Vector4(0.22f, 0.0f, 0.275f, 0.0f));
ShaderGlobals.R.Should().Be(new Color(0.517647f, 0.921569f, 0.52549f, 0.788235f));
ShaderGlobals.S.Should().Be(new Rect2(0.19f, 0.0f, 0.065f, 0.1f));
ShaderGlobals.T.Should().Be(new Vector4(1.36f, 0.44f, 0.22f, 1.0f));
ShaderGlobals.U.Should().Be(new Basis(1.0f, 0.205f, 1.37f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f));
ShaderGlobals.V.Should().Be(new Projection(1.0f, 0.46f, 0.92f, 0.0f, 0.0f, 1.315f, 0.92f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f));
ShaderGlobals.W.Should().Be(new Transform2D(1.0f, 0.0f, 1.885f, 1.0f, 0.755f, 0.0f));
ShaderGlobals.X.Should().Be(new Transform3D(1.0f, 0.0f, 0.0f, 0.0f, 1.35f, 0.59f, 0.0f, 0.0f, 1.0f, 0.0f, 0.54f, 0.0f));
ShaderGlobals.Y.Should().BeNull();
ShaderGlobals.Y.GetDeclaredType().Should().Be(typeof(Texture2D));
ShaderGlobals.Z.Should().BeNull();
ShaderGlobals.Z.GetDeclaredType().Should().Be(typeof(Texture2DArray));
ShaderGlobals.Ä.Should().Be(GD.Load<Texture3D>("res://TestScenes/Feature.ShaderGlobals/Noise.tres"));
ShaderGlobals.Ö.Should().BeNull();
ShaderGlobals.Ö.GetDeclaredType().Should().Be(typeof(Cubemap));
ShaderGlobals.Ü.Should().BeNull();
ShaderGlobals.Ü.GetDeclaredType().Should().Be(typeof(ExternalTexture));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://dm8vgm0v4e6hi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://qlum4js31ian"]

[ext_resource type="Script" uid="uid://dm8vgm0v4e6hi" path="res://TestScenes/Feature.ShaderGlobals/ShaderGlobalsAttributeTests.cs" id="1_y0unj"]

[node name="ShaderGlobalsAttributeTests" type="Node"]
script = ExtResource("1_y0unj")
2 changes: 2 additions & 0 deletions Godot 4 Tests/Utils/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@ public static void ShouldContain(this Type t,
if (Properties is not null) t.Properties().Should().Contain(Properties);
if (NestedTypes is not null) t.NestedTypes().Should().Contain(NestedTypes);
}

public static Type GetDeclaredType<T>(this T t) => typeof(T);
}
119 changes: 119 additions & 0 deletions Godot 4 Tests/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,122 @@ avoidance/layer_16="- With Leading - 16"
avoidance/layer_17="7 With Leading Numeric 17"
avoidance/layer_18=". With Leading . 18"
avoidance/layer_19="中文 With Leading Unicode 19"

[shader_globals]

a={
"type": "bool",
"value": true
}
b={
"type": "bvec2",
"value": 2
}
c={
"type": "bvec3",
"value": 0
}
d={
"type": "bvec4",
"value": 9
}
e={
"type": "int",
"value": 875
}
f={
"type": "ivec2",
"value": Vector2i(565, 0)
}
g={
"type": "ivec3",
"value": Vector3i(0, 410, 0)
}
h={
"type": "ivec4",
"value": Vector4i(0, 475, 0, 180)
}
i={
"type": "rect2i",
"value": Rect2i(50, 0, 145, 0)
}
j={
"type": "uint",
"value": 345
}
k={
"type": "uvec2",
"value": Vector2i(295, 355)
}
l={
"type": "uvec3",
"value": Vector3i(0, 195, 0)
}
m={
"type": "uvec4",
"value": Vector4i(0, 0, 275, 0)
}
n={
"type": "float",
"value": 0.205
}
o={
"type": "vec2",
"value": Vector2(0.23, 0.385)
}
p={
"type": "vec3",
"value": Vector3(0, 0.435, 0)
}
q={
"type": "vec4",
"value": Vector4(0.22, 0, 0.275, 0)
}
r={
"type": "color",
"value": Color(0.517647, 0.921569, 0.52549, 0.788235)
}
s={
"type": "rect2",
"value": Rect2(0.19, 0, 0.065, 0.1)
}
t={
"type": "mat2",
"value": PackedFloat32Array(1.36, 0.44, 0.22, 1)
}
u={
"type": "mat3",
"value": Basis(1, 0.205, 1.37, 0, 1, 0, 0, 0, 1)
}
v={
"type": "mat4",
"value": Projection(1, 0.46, 0.92, 0, 0, 1.315, 0.92, 0, 0, 0, 1, 0, 0, 0, 0, 1)
}
w={
"type": "transform_2d",
"value": Transform2D(1, 0, 1.885, 1, 0.755, 0)
}
x={
"type": "transform",
"value": Transform3D(1, 0, 0, 0, 1.35, 0.59, 0, 0, 1, 0, 0.54, 0)
}
y={
"type": "sampler2D",
"value": ""
}
z={
"type": "sampler2DArray",
"value": ""
}
"ä"={
"type": "sampler3D",
"value": "res://TestScenes/Feature.ShaderGlobals/Noise.tres"
}
"ö"={
"type": "samplerCube",
"value": ""
}
"ü"={
"type": "samplerExternalOES",
"value": ""
}
9 changes: 9 additions & 0 deletions SourceGenerators/ShaderGlobalsExtensions/Resources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Reflection;

namespace GodotSharp.SourceGenerators.ShaderGlobalsExtensions;

internal static class Resources
{
private const string shaderGlobalsTemplate = "GodotSharp.SourceGenerators.ShaderGlobalsExtensions.ShaderGlobalsTemplate.scriban";
public static readonly string ShaderGlobalsTemplate = Assembly.GetExecutingAssembly().GetEmbeddedResource(shaderGlobalsTemplate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Runtime.CompilerServices;

namespace Godot;

[AttributeUsage(AttributeTargets.Class)]
public sealed class ShaderGlobalsAttribute([CallerFilePath] string classPath = null) : Attribute
{
public string ClassPath { get; } = classPath;
}
137 changes: 137 additions & 0 deletions SourceGenerators/ShaderGlobalsExtensions/ShaderGlobalsDataModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;

namespace GodotSharp.SourceGenerators.ShaderGlobalsExtensions;

internal class ShaderGlobalsDataModel : ClassDataModel
{
public abstract record ShaderGlobalDefault
{
public abstract string GetDefault(string type);
}

public record ShaderGlobalResourceDefault(string ResourcePath) : ShaderGlobalDefault
{
private static readonly Regex ResourcePathRegex = new(@"^""res://.+""$");
public static bool TryParse(string @default, out ShaderGlobalDefault outDefault)
{
if (ResourcePathRegex.IsMatch(@default))
{
outDefault = new ShaderGlobalResourceDefault(@default);
return true;
}

outDefault = null;
return false;
}

public override string GetDefault(string type) => $"GD.Load<{type}>({ResourcePath})";
}

public record ShaderGlobalConstructorDefault(string Parameters) : ShaderGlobalDefault
{
private static readonly Regex ConstructorRegex = new(@"^.+\((?<Parameters>.+)\)$");
public static bool TryParse(string @default, out ShaderGlobalDefault outDefault)
{
var match = ConstructorRegex.Match(@default);
if (match.Success)
{
outDefault = new ShaderGlobalConstructorDefault(match.Groups["Parameters"].Value);
return true;
}

outDefault = null;
return false;
}

public override string GetDefault(string type) => $"new {type}({string.Join(",", Parameters
.Split(',')
.Select(ConvertFloatingLiteral))})";
}

public record ShaderGlobalLiteralDefault(string Literal) : ShaderGlobalDefault
{
public override string GetDefault(string type) => ConvertFloatingLiteral(Literal);
}

// We need to append 'f' to float literals.
private static string ConvertFloatingLiteral(string literal) => literal.Contains('.') ? literal + "f" : literal;

public record ShaderGlobal(string Type, string Name, string Default, string RawName);

public IList<ShaderGlobal> ShaderGlobals { get; }

public ShaderGlobalsDataModel(INamedTypeSymbol symbol, string csPath, string gdRoot) : base(symbol)
{
ShaderGlobals = ShaderGlobalsScraper
.GetShaderGlobals(csPath, gdRoot)
.Select(Convert)
.ToArray();

static ShaderGlobal Convert(ShaderGlobalsScraper.ShaderGlobal global)
{
var csType = ConvertType(global.Type);
return new(csType, global.Name.ToPascalCase(), ConvertDefault(global.Default)?.GetDefault(csType) ?? "default", global.Name);
}

static ShaderGlobalDefault ConvertDefault(string @default)
=> @default is null or "" or "\"\""
? null
: ShaderGlobalResourceDefault.TryParse(@default, out var outDefault)
? outDefault
: ShaderGlobalConstructorDefault.TryParse(@default, out outDefault)
? outDefault
: new ShaderGlobalLiteralDefault(@default);

static string ConvertType(string type)
=> type switch
{
"bvec2" => "int",
"bvec3" => "int",
"bvec4" => "int",

"ivec2" => "Vector2I",
"ivec3" => "Vector3I",
"ivec4" => "Vector4I",

"uvec2" => "Vector2I",
"uvec3" => "Vector3I",
"uvec4" => "Vector4I",

"vec2" => "Vector2",
"vec3" => "Vector3",
"vec4" => "Vector4",

"color" => "Color",

"rect2" => "Rect2",
"rect2i" => "Rect2I",

"mat2" => "Vector4",
"mat3" => "Basis",
"mat4" => "Projection",

"transform_2d" => "Transform2D",
"transform" => "Transform3D",

"sampler2D" => "Texture2D",
"sampler2DArray" => "Texture2DArray",
"sampler3D" => "Texture3D",
"samplerCube" => "Cubemap",
"samplerExternalOES" => "ExternalTexture",

_ => type,
};
}

protected override string Str()
{
return string.Join("\n", ShaderGlobals());

IEnumerable<string> ShaderGlobals()
{
foreach (var (type, name, @default, rawType) in this.ShaderGlobals)
yield return $"Type: {type}, Name: {name}, Default: {@default}, RawType: {rawType}";
}
}
}
Loading