diff --git a/.idea/.idea.Nerfed/.idea/vcs.xml b/.idea/.idea.Nerfed/.idea/vcs.xml
index 35eb1dd..3886124 100644
--- a/.idea/.idea.Nerfed/.idea/vcs.xml
+++ b/.idea/.idea.Nerfed/.idea/vcs.xml
@@ -2,5 +2,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Nerfed.Builder/ArgsParser.cs b/Nerfed.Builder/ArgsParser.cs
new file mode 100644
index 0000000..ff846ff
--- /dev/null
+++ b/Nerfed.Builder/ArgsParser.cs
@@ -0,0 +1,129 @@
+using System.Collections;
+using System.ComponentModel;
+using System.Reflection;
+
+namespace Nerfed.Builder;
+
+
+public class ArgsParser where TArgs : new()
+{
+ private enum ArgType
+ {
+ None,
+ Key,
+ Value
+ }
+
+ public TArgs Arguments { get; }
+
+ private readonly string[] args;
+ private readonly Dictionary argKeyPropertyMap = new Dictionary();
+
+ public ArgsParser(string[] args)
+ {
+ this.args = args;
+ Arguments = new TArgs();
+ PropertyInfo[] properties = Arguments.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
+ foreach (PropertyInfo property in properties)
+ {
+ ArgumentAttribute argAttribute = property.GetCustomAttribute();
+ if (argAttribute == null || string.IsNullOrEmpty(argAttribute.ArgKey))
+ {
+ continue;
+ }
+
+ argKeyPropertyMap.Add(argAttribute.ArgKey, property);
+ }
+ }
+
+ public bool Parse()
+ {
+ PropertyInfo property = null;
+ ArgType lastArgType = ArgType.None;
+
+ for (int i = 0; i < args.Length; i++)
+ {
+ string arg = args[i];
+ if (arg[0] == '-')
+ {
+ if (!argKeyPropertyMap.TryGetValue(arg, out property))
+ {
+ Console.Error.WriteLine($"Invalid argument: {arg}, no such argument key exists");
+ return false;
+ }
+
+ // Boolean arguments require no value, set to true immidiately.
+ if (property.PropertyType == typeof(bool))
+ {
+ property.SetValue(Arguments, true);
+ lastArgType = ArgType.Value;
+ }
+ else
+ {
+ lastArgType = ArgType.Key;
+ }
+ }
+ else
+ {
+ if (lastArgType == ArgType.None)
+ {
+ Console.Error.WriteLine($"Invalid argument: {arg}, no argument key was provided");
+ return false;
+ }
+
+ Type propertyType = property.PropertyType;
+ if (propertyType.IsArray)
+ {
+ throw new InvalidOperationException("Arrays are not supported, use List instead");
+ }
+
+ bool propertyTypeIsList = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>);
+ if (propertyTypeIsList)
+ {
+ propertyType = propertyType.GenericTypeArguments[0];
+ }
+
+ TypeConverter typeConverter = TypeDescriptor.GetConverter(propertyType);
+ object value = typeConverter.ConvertFromString(arg);
+
+ if (value is string stringValue)
+ {
+ if (!string.IsNullOrEmpty(stringValue))
+ {
+ if (stringValue[0] == '"')
+ {
+ stringValue = stringValue.Substring(1, stringValue.Length - 1);
+ }
+
+ if (stringValue[^1] == '"')
+ {
+ stringValue = stringValue.Substring(0, stringValue.Length - 1);
+ }
+
+ value = stringValue;
+ }
+ }
+
+ if (propertyTypeIsList)
+ {
+ IList list = (IList)property.GetValue(Arguments);
+ if (list == null)
+ {
+ list = (IList)Activator.CreateInstance(property.PropertyType);
+ property.SetValue(Arguments, list);
+ }
+
+ list.Add(value);
+ }
+ else
+ {
+ property.SetValue(Arguments, value);
+ }
+
+ lastArgType = ArgType.Value;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/Nerfed.Builder/ArgumentAttribute.cs b/Nerfed.Builder/ArgumentAttribute.cs
new file mode 100644
index 0000000..ef57cae
--- /dev/null
+++ b/Nerfed.Builder/ArgumentAttribute.cs
@@ -0,0 +1,11 @@
+namespace Nerfed.Builder;
+
+public class ArgumentAttribute : Attribute
+{
+ public string ArgKey { get; }
+
+ public ArgumentAttribute(string argKey)
+ {
+ ArgKey = argKey;
+ }
+}
diff --git a/Nerfed.Builder/Builder/BuildArgs.cs b/Nerfed.Builder/Builder/BuildArgs.cs
new file mode 100644
index 0000000..f9a8580
--- /dev/null
+++ b/Nerfed.Builder/Builder/BuildArgs.cs
@@ -0,0 +1,16 @@
+namespace Nerfed.Builder;
+
+public class BuildArgs
+{
+ [Argument("-build")]
+ public bool Build { get; set; }
+
+ [Argument("-projectPath")]
+ public string ProjectPath { get; set; }
+
+ [Argument("-platform")]
+ public string Platform { get; set; }
+
+ [Argument("-content")]
+ public List ContentFiles { get; set; }
+}
diff --git a/Nerfed.Builder/Builder/Builder.cs b/Nerfed.Builder/Builder/Builder.cs
new file mode 100644
index 0000000..bd536c2
--- /dev/null
+++ b/Nerfed.Builder/Builder/Builder.cs
@@ -0,0 +1,119 @@
+using System.Diagnostics;
+
+namespace Nerfed.Builder;
+
+public class Builder : IDisposable
+{
+ private readonly Dictionary importers = new Dictionary();
+ private readonly RawFileImporter rawFileImporter;
+
+ public Builder()
+ {
+ rawFileImporter = new RawFileImporter();
+
+ ShaderImporter shaderImporter = new ShaderImporter();
+ importers.Add(".vert", shaderImporter); // Vertex shader
+ importers.Add(".frag", shaderImporter); // Fragment shader
+ importers.Add(".tesc", shaderImporter); // Tessellation control shader
+ importers.Add(".tese", shaderImporter); // Tessellation evaluation shader
+ importers.Add(".geom", shaderImporter); // Geometry shader
+ importers.Add(".comp", shaderImporter); // Compute shader
+ }
+
+ public void Run(BuildArgs args)
+ {
+ Stopwatch stopwatch = new Stopwatch();
+ stopwatch.Start();
+
+ List contentFiles = args.ContentFiles;
+ string absContentPath = $"{args.ProjectPath}/{PathUtil.ContentFolderName}";
+
+ // If no files are provided, build all content.
+ if (args.ContentFiles == null)
+ {
+ contentFiles = [];
+ CollectAssetFiles(absContentPath, absContentPath, ref contentFiles);
+ }
+
+ string importPath = $"{args.ProjectPath}/{PathUtil.ImportFolderName}";
+
+ ParallelOptions parallelOptions = new ParallelOptions
+ {
+ MaxDegreeOfParallelism = contentFiles.Count
+ };
+ Parallel.ForEach(contentFiles, parallelOptions, relativeFile =>
+ {
+ try
+ {
+ string inFile = $"{args.ProjectPath}/{PathUtil.ContentFolderName}/{relativeFile}";
+
+ if (!File.Exists(inFile))
+ {
+ Console.Error.WriteLine($"Asset file '{relativeFile}' not found");
+ return;
+ }
+
+ string outFile = $"{importPath}/{relativeFile}{PathUtil.ImportedFileExtension}";
+
+ FileInfo inFileInfo = new FileInfo(inFile);
+ FileInfo outFileInfo = new FileInfo(outFile);
+
+ if (!FileUtil.IsNewer(inFileInfo, outFileInfo))
+ {
+ // File has not changed since last build, no need to build this one.
+ return;
+ }
+
+ string outDir = Path.GetDirectoryName(outFile);
+ if (!Directory.Exists(outDir))
+ {
+ Directory.CreateDirectory(outDir);
+ }
+
+ string ext = Path.GetExtension(inFile).ToLower();
+ if (importers.TryGetValue(ext, out IImporter importer))
+ {
+ importer.Import(inFile, outFile);
+ }
+ else
+ {
+ rawFileImporter.Import(inFile, outFile);
+ }
+
+ Console.WriteLine(relativeFile);
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine($"Import error on asset '{relativeFile}': {e.Message}");
+ }
+ });
+
+ Console.WriteLine($"Build content completed in {stopwatch.Elapsed.TotalSeconds:F2} seconds");
+ }
+
+ private void CollectAssetFiles(string assetDir, string dir, ref List files)
+ {
+ foreach (string file in Directory.EnumerateFiles(dir))
+ {
+ if (Path.GetExtension(file).Equals(PathUtil.ImportFileExtension, StringComparison.CurrentCultureIgnoreCase))
+ {
+ continue;
+ }
+
+ string relativeFile = file.Substring(assetDir.Length, file.Length - assetDir.Length);
+ if (relativeFile[0] == Path.DirectorySeparatorChar || relativeFile[0] == Path.AltDirectorySeparatorChar)
+ {
+ relativeFile = relativeFile.Substring(1, relativeFile.Length - 1);
+ }
+
+ files.Add(relativeFile);
+ }
+
+ foreach (string subDir in Directory.EnumerateDirectories(dir))
+ {
+ CollectAssetFiles(assetDir, subDir, ref files);
+ }
+ }
+
+ public void Dispose() { }
+}
diff --git a/Nerfed.Builder/Builder/FileUtil.cs b/Nerfed.Builder/Builder/FileUtil.cs
new file mode 100644
index 0000000..ebec6eb
--- /dev/null
+++ b/Nerfed.Builder/Builder/FileUtil.cs
@@ -0,0 +1,39 @@
+namespace Nerfed.Builder;
+
+public static class FileUtil
+{
+ public static void Copy(string srcFile, string dstFile) {
+ string dstDir = Path.GetDirectoryName(dstFile);
+ if(!Directory.Exists(dstDir)) {
+ Directory.CreateDirectory(dstDir);
+ }
+
+ File.Copy(srcFile, dstFile, true);
+ UpdateFileTimeAttributes(dstFile);
+ }
+
+ public static void WriteBytes(string dstFile, byte[] bytes) {
+ File.WriteAllBytes(dstFile, bytes);
+ UpdateFileTimeAttributes(dstFile);
+ }
+
+ public static void UpdateFileTimeAttributes(string file) {
+ // Copy over date time attributes so we can check if the file changed.
+ FileInfo dstFileInfo = new FileInfo(file);
+ DateTime now = DateTime.Now;
+ DateTime utcNow = DateTime.UtcNow;
+ dstFileInfo.CreationTime = now;
+ dstFileInfo.CreationTimeUtc = utcNow;
+ dstFileInfo.LastWriteTime = now;
+ dstFileInfo.LastWriteTimeUtc = utcNow;
+ dstFileInfo.LastAccessTime = now;
+ dstFileInfo.LastAccessTimeUtc = utcNow;
+ }
+
+ ///
+ /// True if the inFileInfo is newer than the outFileInfo.
+ ///
+ public static bool IsNewer(FileInfo inFileInfo, FileInfo outFileInfo) {
+ return !outFileInfo.Exists || outFileInfo.LastWriteTime <= inFileInfo.LastWriteTime;
+ }
+}
diff --git a/Nerfed.Builder/Builder/IImporter.cs b/Nerfed.Builder/Builder/IImporter.cs
new file mode 100644
index 0000000..8d6f7f9
--- /dev/null
+++ b/Nerfed.Builder/Builder/IImporter.cs
@@ -0,0 +1,6 @@
+namespace Nerfed.Builder;
+
+public interface IImporter
+{
+ void Import(string inFile, string outFile);
+}
diff --git a/Nerfed.Builder/Builder/Importers/RawFileImporter.cs b/Nerfed.Builder/Builder/Importers/RawFileImporter.cs
new file mode 100644
index 0000000..a750bf0
--- /dev/null
+++ b/Nerfed.Builder/Builder/Importers/RawFileImporter.cs
@@ -0,0 +1,9 @@
+namespace Nerfed.Builder;
+
+public class RawFileImporter : IImporter
+{
+ public void Import(string inFile, string outFile)
+ {
+ FileUtil.Copy(inFile, outFile);
+ }
+}
diff --git a/Nerfed.Builder/Builder/Importers/ShaderImporter.cs b/Nerfed.Builder/Builder/Importers/ShaderImporter.cs
new file mode 100644
index 0000000..fbaa887
--- /dev/null
+++ b/Nerfed.Builder/Builder/Importers/ShaderImporter.cs
@@ -0,0 +1,33 @@
+using System.Diagnostics;
+
+namespace Nerfed.Builder;
+
+public class ShaderImporter : IImporter
+{
+ public void Import(string inFile, string outFile)
+ {
+ using (Process proc = new Process())
+ {
+ string glslc;
+ if (OperatingSystem.IsWindows())
+ {
+ glslc = "Win64/glslc.exe";
+ }
+ else if (OperatingSystem.IsLinux())
+ {
+ glslc = "Linux/glslc";
+ }
+ else
+ {
+ throw new PlatformNotSupportedException("No shader compiler found for current platform");
+ }
+
+ proc.StartInfo.FileName = glslc;
+ proc.StartInfo.Arguments = @$"""{inFile}"" -o ""{outFile}"" -c";
+ proc.StartInfo.CreateNoWindow = true;
+ proc.StartInfo.UseShellExecute = false;
+ proc.Start();
+ proc.WaitForExit();
+ }
+ }
+}
diff --git a/Nerfed.Builder/Nerfed.Builder.csproj b/Nerfed.Builder/Nerfed.Builder.csproj
new file mode 100644
index 0000000..c7ea4d0
--- /dev/null
+++ b/Nerfed.Builder/Nerfed.Builder.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ disable
+ false
+
+
+
+ ../Tools
+
+
+
+ ../Tools
+
+
+
diff --git a/Nerfed.Builder/Nerfed.Builder.csproj.DotSettings b/Nerfed.Builder/Nerfed.Builder.csproj.DotSettings
new file mode 100644
index 0000000..799e870
--- /dev/null
+++ b/Nerfed.Builder/Nerfed.Builder.csproj.DotSettings
@@ -0,0 +1,4 @@
+
+ True
+ True
+ True
\ No newline at end of file
diff --git a/Nerfed.Builder/Packager/PackageArgs.cs b/Nerfed.Builder/Packager/PackageArgs.cs
new file mode 100644
index 0000000..9af4b54
--- /dev/null
+++ b/Nerfed.Builder/Packager/PackageArgs.cs
@@ -0,0 +1,6 @@
+namespace Nerfed.Builder;
+
+public class PackageArgs
+{
+
+}
diff --git a/Nerfed.Builder/Packager/Packager.cs b/Nerfed.Builder/Packager/Packager.cs
new file mode 100644
index 0000000..1bfdcae
--- /dev/null
+++ b/Nerfed.Builder/Packager/Packager.cs
@@ -0,0 +1,12 @@
+namespace Nerfed.Builder;
+
+public class Packager : IDisposable
+{
+ public void Run(PackageArgs args)
+ {
+ }
+
+ public void Dispose()
+ {
+ }
+}
diff --git a/Nerfed.Builder/PathUtil.cs b/Nerfed.Builder/PathUtil.cs
new file mode 100644
index 0000000..10937b7
--- /dev/null
+++ b/Nerfed.Builder/PathUtil.cs
@@ -0,0 +1,9 @@
+namespace Nerfed.Builder;
+
+public static class PathUtil
+{
+ public const string ImportedFileExtension = ".bin";
+ public const string ImportFileExtension = ".import";
+ public const string ImportFolderName = "Import";
+ public const string ContentFolderName = "Content";
+}
diff --git a/Nerfed.Builder/Program.cs b/Nerfed.Builder/Program.cs
new file mode 100644
index 0000000..bb45c88
--- /dev/null
+++ b/Nerfed.Builder/Program.cs
@@ -0,0 +1,70 @@
+using System.Diagnostics;
+
+namespace Nerfed.Builder;
+
+internal class Program
+{
+ private static int Main(string[] args)
+ {
+ if (Debugger.IsAttached)
+ {
+ return Run(args);
+ }
+ else
+ {
+ try
+ {
+ return Run(args);
+ }
+ catch (Exception e)
+ {
+ Console.Error.WriteLine(e.Message);
+ return -1;
+ }
+ }
+ }
+
+ private static int Run(string[] rawArgs)
+ {
+ if (rawArgs.Length == 0)
+ {
+ Console.Error.WriteLine($"Invalid build type '{rawArgs[0]}' expected '-build' or '-package'");
+ return -1;
+ }
+
+ string buildType = rawArgs[0].ToLower();
+ if (buildType == "-build")
+ {
+ ArgsParser parser = new ArgsParser(rawArgs);
+ if (!parser.Parse())
+ {
+ Console.Error.Write("Failed to parse build arguments");
+ return -1;
+ }
+
+ using (Builder builder = new Builder())
+ {
+ builder.Run(parser.Arguments);
+ }
+ }
+ else if (buildType == "-package")
+ {
+ ArgsParser parser = new ArgsParser(rawArgs);
+ if (!parser.Parse())
+ {
+ Console.Error.Write("Failed to parse package arguments");
+ return -1;
+ }
+
+ using (Packager packager = new Packager())
+ {
+ packager.Run(parser.Arguments);
+ }
+
+ Console.Error.WriteLine("Packaging not yet implemented");
+ return -1;
+ }
+
+ return 0;
+ }
+}
diff --git a/Nerfed.Runtime/Engine.cs b/Nerfed.Runtime/Engine.cs
index b714d2c..876396d 100644
--- a/Nerfed.Runtime/Engine.cs
+++ b/Nerfed.Runtime/Engine.cs
@@ -54,7 +54,7 @@ internal static void Run(string[] args)
{
throw new Exception("Failed to init SDL");
}
-
+
GraphicsDevice = new GraphicsDevice(BackendFlags.All);
MainWindow = new Window(GraphicsDevice, new WindowCreateInfo(WindowTitle, WindowWidth, WindowHeight, ScreenMode.Windowed));
diff --git a/Nerfed.sln b/Nerfed.sln
index be1920e..4565072 100644
--- a/Nerfed.sln
+++ b/Nerfed.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.10.35013.160
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nerfed.Runtime", "Nerfed.Runtime\Nerfed.Runtime.csproj", "{98E09BAF-587F-4238-89BD-7693C036C233}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nerfed.Builder", "Nerfed.Builder\Nerfed.Builder.csproj", "{1B88DE56-2AD8-441E-9B10-073AA43840BF}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
{98E09BAF-587F-4238-89BD-7693C036C233}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98E09BAF-587F-4238-89BD-7693C036C233}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98E09BAF-587F-4238-89BD-7693C036C233}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B88DE56-2AD8-441E-9B10-073AA43840BF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Tools/Linux/glslc b/Tools/Linux/glslc
new file mode 100755
index 0000000..7b3737d
Binary files /dev/null and b/Tools/Linux/glslc differ
diff --git a/Tools/Nerfed.Builder b/Tools/Nerfed.Builder
new file mode 100755
index 0000000..9758e62
Binary files /dev/null and b/Tools/Nerfed.Builder differ
diff --git a/Tools/Nerfed.Builder.deps.json b/Tools/Nerfed.Builder.deps.json
new file mode 100644
index 0000000..83b89f6
--- /dev/null
+++ b/Tools/Nerfed.Builder.deps.json
@@ -0,0 +1,23 @@
+{
+ "runtimeTarget": {
+ "name": ".NETCoreApp,Version=v8.0",
+ "signature": ""
+ },
+ "compilationOptions": {},
+ "targets": {
+ ".NETCoreApp,Version=v8.0": {
+ "Nerfed.Builder/1.0.0": {
+ "runtime": {
+ "Nerfed.Builder.dll": {}
+ }
+ }
+ }
+ },
+ "libraries": {
+ "Nerfed.Builder/1.0.0": {
+ "type": "project",
+ "serviceable": false,
+ "sha512": ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tools/Nerfed.Builder.dll b/Tools/Nerfed.Builder.dll
new file mode 100644
index 0000000..4ffefcd
--- /dev/null
+++ b/Tools/Nerfed.Builder.dll
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a6a3e03a68426fe28672e53f0a6df35e0e6282e391baf044f728d703fddfde9f
+size 13824
diff --git a/Tools/Nerfed.Builder.runtimeconfig.json b/Tools/Nerfed.Builder.runtimeconfig.json
new file mode 100644
index 0000000..becfaea
--- /dev/null
+++ b/Tools/Nerfed.Builder.runtimeconfig.json
@@ -0,0 +1,12 @@
+{
+ "runtimeOptions": {
+ "tfm": "net8.0",
+ "framework": {
+ "name": "Microsoft.NETCore.App",
+ "version": "8.0.0"
+ },
+ "configProperties": {
+ "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tools/Win64/glslc.exe b/Tools/Win64/glslc.exe
new file mode 100755
index 0000000..2d8207c
--- /dev/null
+++ b/Tools/Win64/glslc.exe
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1010f06ca8bfd332867d8e599309a50f9a947cd36dea000143a4f9816be70b98
+size 12196864