Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 059638e6e0 | |||
| fec2cd8d24 | |||
| 5eaf3547dc | |||
| d80e1177b9 | |||
| cf6cd080c6 | |||
| 87ee6df46f | |||
| 57b42d8daa | |||
| 2a351f7b9d | |||
| 7225d13880 | |||
| 567714a52d | |||
| 2c84e650d6 | |||
| 82fe47f627 | |||
| 6be63195f0 | |||
| 9387bfa59c | |||
| 86b54e1521 | |||
| ba88432e77 | |||
| 5cc876fce9 | |||
| 91b4f5fafb | |||
| 0d14a32726 | |||
| b3adef3a40 | |||
| 30deeca452 | |||
| 6f505f34a9 | |||
| 92cf24fe9f | |||
| cce6e00960 | |||
| 1096597161 | |||
| d45f7c3b8c | |||
| 7a81026ca5 | |||
| 60b85960ff | |||
| b003ffbaec | |||
| 777059489c | |||
| 16b04ea22a | |||
| 4b824f3205 | |||
| 9890026656 | |||
| d8b41b0827 | |||
| dd3bbf1d5b | |||
| 38080703ec | |||
| fe582c4fba | |||
| 42b978e8c9 | |||
| 2c839d8fad | |||
| 97c2b308f1 | |||
| 1eb899b240 | |||
| 7cbb745721 | |||
| 1e1ed303ad |
@@ -0,0 +1,4 @@
|
|||||||
|
# Copilot Instructions
|
||||||
|
|
||||||
|
## Project Guidelines
|
||||||
|
- In MoonTools.ECS, do not store plain references to `Entity` objects in long-lived collections outside the ECS world, because their underlying IDs can be reused or destroyed. Instead, query the ECS world to track or process entities based on their assigned components.
|
||||||
+7
-2
@@ -451,8 +451,13 @@ FodyWeavers.xsd
|
|||||||
|
|
||||||
#------------------------- Nerfed -------------------------
|
#------------------------- Nerfed -------------------------
|
||||||
|
|
||||||
|
Bin/
|
||||||
|
Intermediate/
|
||||||
|
|
||||||
imgui.ini
|
imgui.ini
|
||||||
|
|
||||||
# include libs
|
# include libs
|
||||||
!/libs/lib64
|
!/Native/lib64
|
||||||
!/libs/x64
|
!/Native/x64
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,3 +13,9 @@
|
|||||||
[submodule "Nerfed.Runtime/Libraries/dav1dfile"]
|
[submodule "Nerfed.Runtime/Libraries/dav1dfile"]
|
||||||
path = Nerfed.Runtime/Libraries/dav1dfile
|
path = Nerfed.Runtime/Libraries/dav1dfile
|
||||||
url = https://github.com/MoonsideGames/dav1dfile.git
|
url = https://github.com/MoonsideGames/dav1dfile.git
|
||||||
|
[submodule "Nerfed.Runtime/Libraries/ImGui.NET"]
|
||||||
|
path = Nerfed.Runtime/Libraries/ImGui.NET
|
||||||
|
url = https://github.com/ImGuiNET/ImGui.NET.git
|
||||||
|
[submodule "Nerfed.Runtime/Libraries/MoonTools.ECS"]
|
||||||
|
path = Nerfed.Runtime/Libraries/MoonTools.ECS
|
||||||
|
url = https://github.com/MoonsideGames/MoonTools.ECS.git
|
||||||
|
|||||||
Generated
+7
@@ -2,5 +2,12 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/FAudio" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/ImGui.NET" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/MoonTools.ECS" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/RefreshCS" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/SDL2CS" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/WellspringCS" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/dav1dfile" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutDir>../Bin/$(MSBuildProjectName)</OutDir>
|
||||||
|
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">../Intermediate/$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Nerfed Engine
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,129 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
|
||||||
|
public class ArgsParser<TArgs> where TArgs : new()
|
||||||
|
{
|
||||||
|
private enum ArgType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Key,
|
||||||
|
Value
|
||||||
|
}
|
||||||
|
|
||||||
|
public TArgs Arguments { get; }
|
||||||
|
|
||||||
|
private readonly string[] args;
|
||||||
|
private readonly Dictionary<string, PropertyInfo> argKeyPropertyMap = new Dictionary<string, PropertyInfo>();
|
||||||
|
|
||||||
|
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<ArgumentAttribute>();
|
||||||
|
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<T> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public class ArgumentAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string ArgKey { get; }
|
||||||
|
|
||||||
|
public ArgumentAttribute(string argKey)
|
||||||
|
{
|
||||||
|
ArgKey = argKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public class BuildArgs
|
||||||
|
{
|
||||||
|
[Argument("-build")]
|
||||||
|
public bool Build { get; set; }
|
||||||
|
|
||||||
|
[Argument("-resourcePath")]
|
||||||
|
public string ResourcePath { get; set; }
|
||||||
|
|
||||||
|
[Argument("-resourceOutPath")]
|
||||||
|
public string ResourceOutPath { get; set; }
|
||||||
|
|
||||||
|
[Argument("-platform")]
|
||||||
|
public string Platform { get; set; }
|
||||||
|
|
||||||
|
[Argument("-resourceFiles")]
|
||||||
|
public List<string> ResourceFiles { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Nerfed.Builder.Meta;
|
||||||
|
|
||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public class Builder : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, IImporter> importers = new Dictionary<string, IImporter>();
|
||||||
|
private readonly RawFileImporter rawFileImporter;
|
||||||
|
|
||||||
|
public Builder()
|
||||||
|
{
|
||||||
|
rawFileImporter = new RawFileImporter();
|
||||||
|
|
||||||
|
importers.Add(".vert", new ShaderImporter(ShaderStage.Vertex)); // Vertex shader
|
||||||
|
importers.Add(".frag", new ShaderImporter(ShaderStage.Fragment)); // 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();
|
||||||
|
|
||||||
|
//CopyLibs(args.ResourcePath);
|
||||||
|
|
||||||
|
List<string> contentFiles = args.ResourceFiles;
|
||||||
|
|
||||||
|
// If no files are provided, build all content.
|
||||||
|
if (args.ResourceFiles == null)
|
||||||
|
{
|
||||||
|
contentFiles = [];
|
||||||
|
CollectAssetFiles(args.ResourcePath, args.ResourcePath, ref contentFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentFiles.Count > 0)
|
||||||
|
{
|
||||||
|
ParallelOptions parallelOptions = new ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = contentFiles.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
Parallel.ForEach(contentFiles, parallelOptions, relativeFile =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string inFile = $"{args.ResourcePath}/{relativeFile}";
|
||||||
|
|
||||||
|
if (!File.Exists(inFile))
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Asset file '{relativeFile}' not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string outFile = $"{args.ResourceOutPath}/{relativeFile}{PathUtil.ImportedFileExtension}";
|
||||||
|
|
||||||
|
FileInfo inFileInfo = new FileInfo(inFile);
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// STEP 1: GUID META FILE SYNC
|
||||||
|
// Ensure the source file has a backing .meta file generating its Guid
|
||||||
|
// =========================================================================
|
||||||
|
string metaFile = inFile + ".meta";
|
||||||
|
AssetMeta metaData;
|
||||||
|
|
||||||
|
if (!File.Exists(metaFile))
|
||||||
|
{
|
||||||
|
// Generate a brand new meta file to track this asset permanently
|
||||||
|
metaData = new AssetMeta(Guid.NewGuid());
|
||||||
|
string json = JsonSerializer.Serialize(metaData, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(metaFile, json);
|
||||||
|
Console.WriteLine($"[Meta] Generated new tracking ID '{metaData.Id}' for {relativeFile}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Load the existing guid
|
||||||
|
metaData = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFile))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change output file from Name.ext.bin -> /GUID.bin to completely anonymize the actual game package!
|
||||||
|
string cacheOutFile = $"{args.ResourceOutPath}/{metaData.Id}.bin";
|
||||||
|
FileInfo outFileInfo = new FileInfo(cacheOutFile);
|
||||||
|
|
||||||
|
// Rebuild if the source file changed, or if the meta file changed!
|
||||||
|
FileInfo metaFileInfo = new FileInfo(metaFile);
|
||||||
|
bool requiresCompile = !outFileInfo.Exists ||
|
||||||
|
FileUtil.IsNewer(inFileInfo, outFileInfo) ||
|
||||||
|
FileUtil.IsNewer(metaFileInfo, outFileInfo);
|
||||||
|
|
||||||
|
if (!requiresCompile)
|
||||||
|
{
|
||||||
|
// File has not changed since last build, no need to build this one.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string outDir = Path.GetDirectoryName(cacheOutFile)!;
|
||||||
|
if (!Directory.Exists(outDir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(outDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
string ext = Path.GetExtension(inFile).ToLower();
|
||||||
|
if (importers.TryGetValue(ext, out IImporter importer))
|
||||||
|
{
|
||||||
|
importer.Import(inFile, cacheOutFile); // Compile source directly to hash.bin
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rawFileImporter.Import(inFile, cacheOutFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Compiled {relativeFile} -> {metaData.Id}.bin");
|
||||||
|
}
|
||||||
|
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 CopyLibs(string projectPath)
|
||||||
|
{
|
||||||
|
string libDir = $"{AppDomain.CurrentDomain.BaseDirectory}/../../Native/";
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
libDir += "x64";
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
libDir += "lib64";
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
libDir += "osx";
|
||||||
|
}
|
||||||
|
|
||||||
|
libDir = Path.GetFullPath(libDir);
|
||||||
|
foreach (string libFile in Directory.EnumerateFiles(libDir))
|
||||||
|
{
|
||||||
|
FileInfo srcFileInfo = new FileInfo(libFile);
|
||||||
|
FileInfo dstFileInfo = new FileInfo($"{projectPath}/{PathUtil.BuildFolderName}/{Path.GetFileName(libFile)}");
|
||||||
|
if (FileUtil.IsNewer(srcFileInfo, dstFileInfo))
|
||||||
|
{
|
||||||
|
FileUtil.Copy(srcFileInfo, dstFileInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private void CollectAssetFiles(string assetDir, string dir, ref List<string> files)
|
||||||
|
{
|
||||||
|
foreach (string file in Directory.EnumerateFiles(dir))
|
||||||
|
{
|
||||||
|
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() { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public static class FileUtil
|
||||||
|
{
|
||||||
|
public static void Copy(FileInfo srcFile, FileInfo dstFile)
|
||||||
|
{
|
||||||
|
Copy(srcFile.FullName, dstFile.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if the inFileInfo is newer than the outFileInfo.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsNewer(FileInfo inFileInfo, FileInfo outFileInfo)
|
||||||
|
{
|
||||||
|
return !outFileInfo.Exists || outFileInfo.LastWriteTime <= inFileInfo.LastWriteTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public interface IImporter
|
||||||
|
{
|
||||||
|
void Import(string inFile, string outFile);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public class RawFileImporter : IImporter
|
||||||
|
{
|
||||||
|
public void Import(string inFile, string outFile)
|
||||||
|
{
|
||||||
|
FileUtil.Copy(inFile, outFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
using Vortice.ShaderCompiler;
|
||||||
|
using Vortice.SPIRV.Reflect;
|
||||||
|
|
||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
// Values should match the ShaderStage enum in the runtime.
|
||||||
|
public enum ShaderStage
|
||||||
|
{
|
||||||
|
Vertex,
|
||||||
|
Fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values should match the ShaderFormat enum in the runtime.
|
||||||
|
public enum ShaderFormat
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
SPIRV,
|
||||||
|
HLSL,
|
||||||
|
DXBC,
|
||||||
|
DXIL,
|
||||||
|
MSL,
|
||||||
|
METALLIB,
|
||||||
|
SECRET
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShaderImporter : IImporter
|
||||||
|
{
|
||||||
|
private readonly ShaderStage shaderStage;
|
||||||
|
|
||||||
|
public ShaderImporter(ShaderStage shaderStage)
|
||||||
|
{
|
||||||
|
this.shaderStage = shaderStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Import(string inFile, string outFile)
|
||||||
|
{
|
||||||
|
string name = Path.GetFileNameWithoutExtension(inFile);
|
||||||
|
string nameWithExt = Path.GetFileName(inFile);
|
||||||
|
|
||||||
|
// Compile the shader.
|
||||||
|
Result compileResult;
|
||||||
|
using (Compiler compiler = new Compiler())
|
||||||
|
{
|
||||||
|
string shaderSource = File.ReadAllText(inFile);
|
||||||
|
compileResult = compiler.Compile(shaderSource, nameWithExt, ToShaderKind(shaderStage));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compileResult.Status != CompilationStatus.Success)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Failed to compile {nameWithExt}\n{compileResult.ErrorMessage}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compileResult.ErrorsCount > 0 || compileResult.WarningsCount > 0)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(compileResult.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> byteCode = compileResult.GetBytecode();
|
||||||
|
|
||||||
|
// Inspect SPIR-V bytecode for information which the runtime requires to create a shader resource.
|
||||||
|
SpvReflectShaderModule module = new SpvReflectShaderModule();
|
||||||
|
if (!CheckReflectResult(SPIRVReflectApi.spvReflectCreateShaderModule(byteCode, &module), name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint descriptorSetCount = 0;
|
||||||
|
if (!CheckReflectResult(SPIRVReflectApi.spvReflectEnumerateDescriptorSets(&module, &descriptorSetCount, null), name))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int uniformBufferCount = 0;
|
||||||
|
int storageBufferCount = 0;
|
||||||
|
int storageTextureCount = 0;
|
||||||
|
int samplerCount = 0;
|
||||||
|
|
||||||
|
if (descriptorSetCount > 0)
|
||||||
|
{
|
||||||
|
SpvReflectDescriptorSet* descriptorSets = stackalloc SpvReflectDescriptorSet[(int)descriptorSetCount];
|
||||||
|
if (!CheckReflectResult(
|
||||||
|
SPIRVReflectApi.spvReflectEnumerateDescriptorSets(&module, &descriptorSetCount, &descriptorSets),
|
||||||
|
name
|
||||||
|
))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < descriptorSetCount; i++)
|
||||||
|
{
|
||||||
|
SpvReflectDescriptorSet set = descriptorSets[i];
|
||||||
|
for (int j = 0; j < set.binding_count; j++)
|
||||||
|
{
|
||||||
|
SpvReflectDescriptorBinding binding = *set.bindings[j];
|
||||||
|
if (binding.descriptor_type == SpvReflectDescriptorType.UniformBuffer)
|
||||||
|
{
|
||||||
|
uniformBufferCount++;
|
||||||
|
}
|
||||||
|
else if (binding.descriptor_type == SpvReflectDescriptorType.StorageBuffer)
|
||||||
|
{
|
||||||
|
storageBufferCount++;
|
||||||
|
}
|
||||||
|
else if (binding.descriptor_type == SpvReflectDescriptorType.StorageTexelBuffer)
|
||||||
|
{
|
||||||
|
storageTextureCount++;
|
||||||
|
}
|
||||||
|
else if (binding.descriptor_type == SpvReflectDescriptorType.Sampler ||
|
||||||
|
binding.descriptor_type == SpvReflectDescriptorType.CombinedImageSampler)
|
||||||
|
{
|
||||||
|
samplerCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Convert SPIR-V to other bytecode formats here (DX/Consoles).
|
||||||
|
ShaderFormat format = ShaderFormat.SPIRV;
|
||||||
|
|
||||||
|
// Write shader meta-data and bytecode to the output file.
|
||||||
|
using (FileStream stream = new FileStream(outFile, FileMode.Create, FileAccess.Write))
|
||||||
|
{
|
||||||
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
|
{
|
||||||
|
writer.Write((int)format);
|
||||||
|
writer.Write((int)shaderStage);
|
||||||
|
writer.Write(uniformBufferCount);
|
||||||
|
writer.Write(storageBufferCount);
|
||||||
|
writer.Write(storageTextureCount);
|
||||||
|
writer.Write(samplerCount);
|
||||||
|
writer.Write(byteCode.Length);
|
||||||
|
writer.Write(byteCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckReflectResult(SpvReflectResult result, string name)
|
||||||
|
{
|
||||||
|
if (result != SpvReflectResult.Success)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"SpirV-Reflect failure for '{name}': {result}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ShaderKind ToShaderKind(ShaderStage shaderStage)
|
||||||
|
{
|
||||||
|
switch (shaderStage)
|
||||||
|
{
|
||||||
|
case ShaderStage.Vertex: return ShaderKind.VertexShader;
|
||||||
|
case ShaderStage.Fragment: return ShaderKind.FragmentShader;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(shaderStage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nerfed.Builder.Meta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Foundation for JSON-serialized metadata files (e.g. hero.png.meta)
|
||||||
|
/// </summary>
|
||||||
|
public class AssetMeta
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The universally unique identifier for this asset, generated on first import.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The importer version. Useful to force re-imports if your engine updates how it parses textures.
|
||||||
|
/// </summary>
|
||||||
|
public int ImporterVersion { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base constructor needed for JSON deserialization
|
||||||
|
/// </summary>
|
||||||
|
public AssetMeta() { }
|
||||||
|
|
||||||
|
public AssetMeta(Guid id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Configurations>Debug;Test;Release</Configurations>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Test|x64' ">
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Vortice.ShaderCompiler" Version="1.7.3" />
|
||||||
|
<PackageReference Include="Vortice.SPIRV.Reflect" Version="1.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=builder/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=builder_005Cimporters/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=packager/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public class PackageArgs
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public class Packager : IDisposable
|
||||||
|
{
|
||||||
|
public void Run(PackageArgs args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Nerfed.Builder;
|
||||||
|
|
||||||
|
public static class PathUtil
|
||||||
|
{
|
||||||
|
public const string ImportedFileExtension = ".bin";
|
||||||
|
}
|
||||||
@@ -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<BuildArgs> parser = new ArgsParser<BuildArgs>(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<PackageArgs> parser = new ArgsParser<PackageArgs>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
namespace Nerfed.Editor.Components;
|
||||||
|
|
||||||
|
public readonly record struct SelectedInHierachy;
|
||||||
|
public readonly record struct ClickedInHierachy;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Target Name="Runtime ID" AfterTargets="Build">
|
||||||
|
<Message Text="Runtime ID: $(RuntimeIdentifier)" Importance="high"/>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<ItemGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))">
|
||||||
|
<Libs Include="..\Native\x64\**\*.*"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))">
|
||||||
|
<Libs Include="..\Native\lib64\**\*.*"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))">
|
||||||
|
<Libs Include="..\Native\osx\**\*.*"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Target Name="CopyLibs" AfterTargets="Build">
|
||||||
|
<Copy SourceFiles="@(Libs)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true"/>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using Nerfed.Runtime;
|
||||||
|
using Nerfed.Runtime.Graphics;
|
||||||
|
using Nerfed.Runtime.Gui;
|
||||||
|
|
||||||
|
namespace Nerfed.Editor
|
||||||
|
{
|
||||||
|
internal static class EditorGui
|
||||||
|
{
|
||||||
|
private static GuiController guiController;
|
||||||
|
|
||||||
|
internal static void Initialize()
|
||||||
|
{
|
||||||
|
// Create GuiController.
|
||||||
|
guiController = new GuiController(Engine.GraphicsDevice, Engine.MainWindow, Color.DimGray);
|
||||||
|
// Subscribe to GUI update.
|
||||||
|
// GuiController.OnGui call => UpdateDock;
|
||||||
|
// GuiController.OnGui call => UpdateEditorWindows;
|
||||||
|
// GuiController.OnGui call => ...;
|
||||||
|
guiController.OnGui += HandleOnGui;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Update()
|
||||||
|
{
|
||||||
|
// Update GuiController.
|
||||||
|
guiController.Update(Engine.Timestep.TotalSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Render()
|
||||||
|
{
|
||||||
|
// Reneder GuiController.
|
||||||
|
guiController.Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Quit()
|
||||||
|
{
|
||||||
|
guiController.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateDock()
|
||||||
|
{
|
||||||
|
// Setup default dockspace for the main window.
|
||||||
|
uint id = ImGui.GetID("MainDockSpace");
|
||||||
|
ImGui.DockSpaceOverViewport(id, ImGui.GetMainViewport(), ImGuiDockNodeFlags.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateMainMenu()
|
||||||
|
{
|
||||||
|
if (ImGui.BeginMainMenuBar())
|
||||||
|
{
|
||||||
|
if (ImGui.BeginMenu("File"))
|
||||||
|
{
|
||||||
|
if (ImGui.MenuItem("Exit"))
|
||||||
|
{
|
||||||
|
Engine.Quit();
|
||||||
|
}
|
||||||
|
ImGui.EndMenu();
|
||||||
|
}
|
||||||
|
ImGui.EndMainMenuBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleOnGui()
|
||||||
|
{
|
||||||
|
UpdateMainMenu();
|
||||||
|
UpdateDock();
|
||||||
|
|
||||||
|
ImGui.ShowDemoWindow();
|
||||||
|
|
||||||
|
foreach (MoonTools.ECS.System system in Program.editorSystems)
|
||||||
|
{
|
||||||
|
using ProfilerScope scope = new(system.GetType().Name);
|
||||||
|
system.Update(Engine.Timestep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Configurations>Debug;Test;Release</Configurations>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Test|x64' ">
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Nerfed.Runtime\Nerfed.Runtime.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project=".\CopyLibs.targets" />
|
||||||
|
|
||||||
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
<Exec Command=""$(ProjectDir)../Bin/Nerfed.Builder/Nerfed.Builder" -build -resourcePath "$(ProjectDir)Resources" -resourceOutPath "$(TargetDir)Resources" " />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Editor.Systems;
|
||||||
|
using Nerfed.Runtime;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
using Nerfed.Runtime.Systems;
|
||||||
|
using Nerfed.Runtime.Util;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Nerfed.Editor;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
private static readonly World world = new World();
|
||||||
|
private static List<MoonTools.ECS.System> systems = new List<MoonTools.ECS.System>();
|
||||||
|
public static List<MoonTools.ECS.System> editorSystems = new List<MoonTools.ECS.System>();
|
||||||
|
|
||||||
|
private static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Engine.OnInitialize += HandleOnInitialize;
|
||||||
|
Engine.OnUpdate += HandleOnUpdate;
|
||||||
|
Engine.OnRender += HandleOnRender;
|
||||||
|
Engine.OnQuit += HandleOnQuit;
|
||||||
|
|
||||||
|
Engine.Run(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleOnInitialize()
|
||||||
|
{
|
||||||
|
//systems.Add(new ParentSystem(world));
|
||||||
|
systems.Add(new LocalToWorldSystem(world));
|
||||||
|
editorSystems.Add(new EditorProfilerWindow(world));
|
||||||
|
// editorSystems.Add(new EditorHierarchyWindow(world));
|
||||||
|
#if DEBUG
|
||||||
|
editorSystems.Add(new EditorInspectorWindow(world));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Entity ent1 = world.CreateEntity("parent");
|
||||||
|
// world.Set(ent1, new Root());
|
||||||
|
// world.Set(ent1, new LocalTransform(new Vector3(1, 0, 0), Quaternion.Identity, Vector3.One));
|
||||||
|
//
|
||||||
|
// Entity ent2 = world.CreateEntity("child");
|
||||||
|
// world.Set(ent2, new LocalTransform(new Vector3(0, 1, 0), Quaternion.Identity, Vector3.One));
|
||||||
|
// Transform.SetParent(world, ent2, ent1);
|
||||||
|
//
|
||||||
|
// Entity ent3 = world.CreateEntity("entity3");
|
||||||
|
// world.Set(ent3, new Root());
|
||||||
|
// Transform.SetParent(world, ent3, ent2);
|
||||||
|
//
|
||||||
|
// Entity ent4 = world.CreateEntity("entity4");
|
||||||
|
// world.Set(ent4, new Root());
|
||||||
|
//
|
||||||
|
// Entity ent5 = world.CreateBaseEntity("entity5");
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000000; i++)
|
||||||
|
{
|
||||||
|
Entity newEnt = world.CreateBaseEntity();
|
||||||
|
world.Set(newEnt, new LocalTransform(new Vector3(i, i, i), Quaternion.Identity, Vector3.One));
|
||||||
|
|
||||||
|
// Entity parent = newEnt;
|
||||||
|
// for (int j = 0; j < 2; j++) {
|
||||||
|
// Entity newChildEnt = world.CreateEntity();
|
||||||
|
// world.Set(newChildEnt, new LocalTransform(new Vector3(i + j * i, i - j * i, j - i * i), Quaternion.Identity, Vector3.One));
|
||||||
|
// Transform.SetParent(world, newChildEnt, parent);
|
||||||
|
// parent = newChildEnt;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open project.
|
||||||
|
// Setip EditorGui.
|
||||||
|
EditorGui.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleOnUpdate()
|
||||||
|
{
|
||||||
|
foreach (MoonTools.ECS.System system in systems)
|
||||||
|
{
|
||||||
|
using ProfilerScope scope = new(system.GetType().Name);
|
||||||
|
system.Update(Engine.Timestep);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (new ProfilerScope("EditorGui.Update"))
|
||||||
|
{
|
||||||
|
// Editor Update.
|
||||||
|
EditorGui.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try Catch UserCode Update.
|
||||||
|
|
||||||
|
using (new ProfilerScope("world.FinishUpdate"))
|
||||||
|
{
|
||||||
|
world.FinishUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleOnRender()
|
||||||
|
{
|
||||||
|
using (new ProfilerScope("EditorGui.Render"))
|
||||||
|
{
|
||||||
|
EditorGui.Render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleOnQuit()
|
||||||
|
{
|
||||||
|
EditorGui.Quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 outTexCoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||||
|
gl_Position = vec4(outTexCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec4 color;
|
||||||
|
layout (location = 1) in vec2 texCoord;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D Sampler;
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 outputColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
outputColor = color * texture(Sampler, texCoord);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_position;
|
||||||
|
layout (location = 1) in vec2 in_texCoord;
|
||||||
|
layout (location = 2) in vec4 in_color;
|
||||||
|
|
||||||
|
layout (set = 1, binding = 0) uniform ProjectionMatrixBuffer
|
||||||
|
{
|
||||||
|
mat4 projection_matrix;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 color;
|
||||||
|
layout (location = 1) out vec2 texCoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = projection_matrix * vec4(in_position, 0, 1);
|
||||||
|
color = in_color;
|
||||||
|
texCoord = in_texCoord;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 inTexCoord;
|
||||||
|
layout(location = 1) in vec4 inColor;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D msdf;
|
||||||
|
|
||||||
|
layout(set = 3, binding = 0) uniform UBO
|
||||||
|
{
|
||||||
|
float pxRange;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
float median(float r, float g, float b)
|
||||||
|
{
|
||||||
|
return max(min(r, g), min(max(r, g), b));
|
||||||
|
}
|
||||||
|
|
||||||
|
float screenPxRange()
|
||||||
|
{
|
||||||
|
vec2 unitRange = vec2(ubo.pxRange)/vec2(textureSize(msdf, 0));
|
||||||
|
vec2 screenTexSize = vec2(1.0)/fwidth(inTexCoord);
|
||||||
|
return max(0.5*dot(unitRange, screenTexSize), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 msd = texture(msdf, inTexCoord).rgb;
|
||||||
|
float sd = median(msd.r, msd.g, msd.b);
|
||||||
|
float screenPxDistance = screenPxRange() * (sd - 0.5);
|
||||||
|
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||||
|
outColor = mix(vec4(0.0, 0.0, 0.0, 0.0), inColor, opacity);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec3 inPos;
|
||||||
|
layout(location = 1) in vec2 inTexCoord;
|
||||||
|
layout(location = 2) in vec4 inColor;
|
||||||
|
|
||||||
|
layout(location = 0) out vec2 outTexCoord;
|
||||||
|
layout(location = 1) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 1, binding = 0) uniform UBO
|
||||||
|
{
|
||||||
|
mat4 ViewProjection;
|
||||||
|
} ubo;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = ubo.ViewProjection * vec4(inPos, 1.0);
|
||||||
|
outTexCoord = inTexCoord;
|
||||||
|
outColor = inColor;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* This effect is based on the YUV-to-RGBA GLSL shader found in SDL.
|
||||||
|
* Thus, it also released under the zlib license:
|
||||||
|
* http://libsdl.org/license.php
|
||||||
|
*/
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 TexCoord;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 FragColor;
|
||||||
|
|
||||||
|
layout(set = 2, binding = 0) uniform sampler2D YSampler;
|
||||||
|
layout(set = 2, binding = 1) uniform sampler2D USampler;
|
||||||
|
layout(set = 2, binding = 2) uniform sampler2D VSampler;
|
||||||
|
|
||||||
|
/* More info about colorspace conversion:
|
||||||
|
* http://www.equasys.de/colorconversion.html
|
||||||
|
* http://www.equasys.de/colorformat.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
const vec3 offset = vec3(-0.0625, -0.5, -0.5);
|
||||||
|
const vec3 Rcoeff = vec3(1.164, 0.000, 1.793);
|
||||||
|
const vec3 Gcoeff = vec3(1.164, -0.213, -0.533);
|
||||||
|
const vec3 Bcoeff = vec3(1.164, 2.112, 0.000);
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec3 yuv;
|
||||||
|
yuv.x = texture(YSampler, TexCoord).r;
|
||||||
|
yuv.y = texture(USampler, TexCoord).r;
|
||||||
|
yuv.z = texture(VSampler, TexCoord).r;
|
||||||
|
yuv += offset;
|
||||||
|
|
||||||
|
FragColor.r = dot(yuv, Rcoeff);
|
||||||
|
FragColor.g = dot(yuv, Gcoeff);
|
||||||
|
FragColor.b = dot(yuv, Bcoeff);
|
||||||
|
FragColor.a = 1.0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Editor.Components;
|
||||||
|
using Nerfed.Runtime;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
using Nerfed.Runtime.Util;
|
||||||
|
|
||||||
|
namespace Nerfed.Editor.Systems
|
||||||
|
{
|
||||||
|
// Window that draws entities.
|
||||||
|
internal class EditorHierarchyWindow : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
private const ImGuiTreeNodeFlags baseFlags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.OpenOnDoubleClick | ImGuiTreeNodeFlags.SpanAvailWidth;
|
||||||
|
|
||||||
|
//private readonly Filter rootEntitiesWithTransformFilter;
|
||||||
|
//private readonly Filter rootEntitiesFilterBroken;
|
||||||
|
private readonly Filter rootEntitiesFilter;
|
||||||
|
|
||||||
|
private readonly EditorHierachySelectionSystem hierachySelectionSystem;
|
||||||
|
|
||||||
|
public EditorHierarchyWindow(World world) : base(world)
|
||||||
|
{
|
||||||
|
//rootEntitiesWithTransformFilter = FilterBuilder.Include<LocalTransform>().Exclude<Child>().Build();
|
||||||
|
|
||||||
|
// TODO: this doesn't work.
|
||||||
|
//rootEntitiesFilterBroken = FilterBuilder.Exclude<Child>().Build();
|
||||||
|
|
||||||
|
// Maybe the parent/child functions should add a root component when not being a child.
|
||||||
|
rootEntitiesFilter = FilterBuilder.Include<Root>().Build();
|
||||||
|
|
||||||
|
// Maybe instead of a root, if we need a component that is always on an entity and has some use we could create something like a VersionComponent which only hold an int.
|
||||||
|
// The version would update each time something changes on the entity.
|
||||||
|
// Or a EditorComponent, just a component that always gets added when in editor mode.
|
||||||
|
|
||||||
|
hierachySelectionSystem = new EditorHierachySelectionSystem(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
ImGui.Begin("Hierarchy");
|
||||||
|
|
||||||
|
ImGuiTreeNodeFlags flags = baseFlags;
|
||||||
|
flags |= ImGuiTreeNodeFlags.DefaultOpen;
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("World", flags))
|
||||||
|
{
|
||||||
|
if (ImGui.BeginDragDropTarget())
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
ImGuiPayloadPtr payload = ImGui.AcceptDragDropPayload($"{nameof(EditorHierarchyWindow)}");
|
||||||
|
if (payload.NativePtr != null)
|
||||||
|
{
|
||||||
|
Entity* data = (Entity*)payload.Data;
|
||||||
|
Entity child = data[0];
|
||||||
|
|
||||||
|
Log.Info($"Dropped {child.ID}");
|
||||||
|
|
||||||
|
Transform.RemoveParent(World, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
//foreach (Entity entity in rootEntitiesWithTransformFilter.Entities)
|
||||||
|
//{
|
||||||
|
// DrawEntityAndChildren(entity);
|
||||||
|
//}
|
||||||
|
|
||||||
|
foreach (Entity entity in rootEntitiesFilter.Entities)
|
||||||
|
{
|
||||||
|
DrawEntityAndChildren(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
|
||||||
|
hierachySelectionSystem.Update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEntityAndChildren(in Entity entity)
|
||||||
|
{
|
||||||
|
ImGuiTreeNodeFlags flags = baseFlags;
|
||||||
|
|
||||||
|
if (!World.HasInRelation<ChildParentRelation>(entity))
|
||||||
|
{
|
||||||
|
flags |= ImGuiTreeNodeFlags.Leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (World.Has<SelectedInHierachy>(entity))
|
||||||
|
{
|
||||||
|
flags |= ImGuiTreeNodeFlags.Selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx($"{entity.ID} | {GetTag(entity)}", flags))
|
||||||
|
{
|
||||||
|
// TODO: fix selection, look at ImGui 1.91, https://github.com/ocornut/imgui/wiki/Multi-Select
|
||||||
|
// Selection.
|
||||||
|
if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen())
|
||||||
|
{
|
||||||
|
World.Set(entity, new ClickedInHierachy());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag and drop.
|
||||||
|
if (ImGui.BeginDragDropSource())
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (Entity* payload = &entity)
|
||||||
|
{
|
||||||
|
ImGui.SetDragDropPayload($"{nameof(EditorHierarchyWindow)}", (IntPtr)payload, (uint)sizeof(Entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginDragDropTarget())
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
ImGuiPayloadPtr payload = ImGui.AcceptDragDropPayload($"{nameof(EditorHierarchyWindow)}");
|
||||||
|
if (payload.NativePtr != null)
|
||||||
|
{
|
||||||
|
Entity ent = *(Entity*)payload.Data;
|
||||||
|
|
||||||
|
Log.Info($"Dropped {ent.ID}");
|
||||||
|
|
||||||
|
Transform.SetParent(World, ent, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw children.
|
||||||
|
ReverseSpanEnumerator<Entity> childEntities = World.InRelations<ChildParentRelation>(entity);
|
||||||
|
foreach (Entity childEntity in childEntities)
|
||||||
|
{
|
||||||
|
DrawEntityAndChildren(childEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System for handling the selected entities in the hierachy.
|
||||||
|
private class EditorHierachySelectionSystem : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
private readonly Filter selectedEntities;
|
||||||
|
private readonly Filter clickedEntities;
|
||||||
|
|
||||||
|
public EditorHierachySelectionSystem(World world) : base(world)
|
||||||
|
{
|
||||||
|
selectedEntities = FilterBuilder.Include<SelectedInHierachy>().Build();
|
||||||
|
clickedEntities = FilterBuilder.Include<ClickedInHierachy>().Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
|
||||||
|
if (!clickedEntities.Empty && !io.KeyCtrl)
|
||||||
|
{
|
||||||
|
foreach (Entity entity in selectedEntities.Entities)
|
||||||
|
{
|
||||||
|
Remove<SelectedInHierachy>(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Entity entity in clickedEntities.Entities)
|
||||||
|
{
|
||||||
|
// Unselect.
|
||||||
|
if (Has<SelectedInHierachy>(entity))
|
||||||
|
{
|
||||||
|
Remove<SelectedInHierachy>(entity);
|
||||||
|
}
|
||||||
|
// Select.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Set(entity, new SelectedInHierachy());
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove<ClickedInHierachy>(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using ImGuiNET;
|
||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Editor.Components;
|
||||||
|
using Nerfed.Runtime.Serialization;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
namespace Nerfed.Editor.Systems
|
||||||
|
{
|
||||||
|
// Window that draws entities.
|
||||||
|
internal class EditorInspectorWindow : MoonTools.ECS.DebugSystem
|
||||||
|
{
|
||||||
|
private readonly Filter selectedEntityFilter;
|
||||||
|
|
||||||
|
public EditorInspectorWindow(World world) : base(world)
|
||||||
|
{
|
||||||
|
selectedEntityFilter = FilterBuilder.Include<SelectedInHierachy>().Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
ImGui.Begin("Inspector");
|
||||||
|
|
||||||
|
foreach (Entity entity in selectedEntityFilter.Entities)
|
||||||
|
{
|
||||||
|
DrawEntityComponents(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEntityComponents(Entity entity)
|
||||||
|
{
|
||||||
|
World.ComponentTypeEnumerator componentTypes = World.Debug_GetAllComponentTypes(entity);
|
||||||
|
|
||||||
|
// Add button of all types that we can add. Also filter out types we already have.
|
||||||
|
List<Type> componentTypesToAdd = ComponentHelper.AddComponentByType.Keys.ToList();
|
||||||
|
foreach (Type componentType in componentTypes)
|
||||||
|
{
|
||||||
|
componentTypesToAdd.Remove(componentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string popupId = "AddComponentPopup";
|
||||||
|
if (ImGui.Button("Add Component"))
|
||||||
|
{
|
||||||
|
ImGui.OpenPopup(popupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginPopup(popupId))
|
||||||
|
{
|
||||||
|
foreach (Type componentType in componentTypesToAdd)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(componentType.Name))
|
||||||
|
{
|
||||||
|
if (ComponentHelper.AddComponentByType.TryGetValue(componentType, out Action<World, Entity> componentSetter))
|
||||||
|
{
|
||||||
|
componentSetter.Invoke(World, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(16, 16));
|
||||||
|
|
||||||
|
ImGui.Text("ComponentInspectorByType");
|
||||||
|
foreach (Type componentType in componentTypes)
|
||||||
|
{
|
||||||
|
if (ComponentHelper.ComponentInspectorByType.TryGetValue(componentType, out Action<World, Entity> componentInspector))
|
||||||
|
{
|
||||||
|
componentInspector(World, entity);
|
||||||
|
}
|
||||||
|
else if (ComponentHelper.GetComponentByType.TryGetValue(componentType, out Func<World, Entity, ValueType> componentGetter))
|
||||||
|
{
|
||||||
|
ValueType component = componentGetter.Invoke(World, entity);
|
||||||
|
ImGui.Text(component.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text(componentType.Name);
|
||||||
|
}
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(16, 16));
|
||||||
|
|
||||||
|
// ImGui.Text("Reflection");
|
||||||
|
// foreach (Type component in componentTypes)
|
||||||
|
// {
|
||||||
|
// System.Reflection.MethodInfo getMethodInfo = typeof(World).GetMethod("Get");
|
||||||
|
// System.Reflection.MethodInfo getComponentMethod = getMethodInfo.MakeGenericMethod(component);
|
||||||
|
// object result = getComponentMethod.Invoke(World, [entity]);
|
||||||
|
//
|
||||||
|
// // process here
|
||||||
|
// ImGui.Text(result.ToString());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Runtime;
|
||||||
|
|
||||||
|
namespace Nerfed.Editor.Systems
|
||||||
|
{
|
||||||
|
internal class EditorProfilerWindow : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
const ImGuiTableFlags tableFlags = ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.NoBordersInBody | ImGuiTableFlags.ScrollY | ImGuiTableFlags.ScrollX;
|
||||||
|
const ImGuiTreeNodeFlags treeNodeFlags = ImGuiTreeNodeFlags.SpanAllColumns;
|
||||||
|
const ImGuiTreeNodeFlags treeNodeLeafFlags = ImGuiTreeNodeFlags.SpanAllColumns | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen;
|
||||||
|
|
||||||
|
private int selectedFrame = 0;
|
||||||
|
private int previousSelectedFrame = -1;
|
||||||
|
private IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> orderedCombinedData = null;
|
||||||
|
|
||||||
|
public EditorProfilerWindow(World world) : base(world)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
if (Profiler.Frames.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Begin("Profiler");
|
||||||
|
|
||||||
|
ImGui.BeginChild("Toolbar", new System.Numerics.Vector2(0, 0), ImGuiChildFlags.AutoResizeY);
|
||||||
|
if (ImGui.RadioButton("Recording", Profiler.IsRecording))
|
||||||
|
{
|
||||||
|
Profiler.SetActive(!Profiler.IsRecording);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (Profiler.IsRecording)
|
||||||
|
{
|
||||||
|
// Select last frame when recording to see latest frame data.
|
||||||
|
selectedFrame = Profiler.Frames.Count - 1;
|
||||||
|
}
|
||||||
|
if (ImGui.SliderInt(string.Empty, ref selectedFrame, 0, Profiler.Frames.Count - 1))
|
||||||
|
{
|
||||||
|
// Stop recording when browsing frames.
|
||||||
|
Profiler.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profiler.Frame frame = Profiler.Frames.ElementAt(selectedFrame);
|
||||||
|
double ms = frame.ElapsedMilliseconds();
|
||||||
|
double s = 1000;
|
||||||
|
ImGui.Text($"Frame: {frame.FrameCount} ({ms:0.000} ms | {(s / ms):0} fps)");
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
if (!Profiler.IsRecording) {
|
||||||
|
if (previousSelectedFrame != selectedFrame)
|
||||||
|
{
|
||||||
|
previousSelectedFrame = selectedFrame;
|
||||||
|
orderedCombinedData = CalculateCombinedData(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawFlameGraph(frame);
|
||||||
|
|
||||||
|
DrawHierachy(frame);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
DrawCombined(orderedCombinedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawHierachy(Profiler.Frame frame)
|
||||||
|
{
|
||||||
|
if(frame == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.BeginChild("Hierachy", new System.Numerics.Vector2(150, 0), ImGuiChildFlags.ResizeX);
|
||||||
|
|
||||||
|
if (ImGui.BeginTable("ProfilerData", 3, tableFlags, new System.Numerics.Vector2(0, 0)))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.8f, 0);
|
||||||
|
ImGui.TableSetupColumn("thread", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1);
|
||||||
|
ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1);
|
||||||
|
ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
foreach (Profiler.ScopeNode node in frame.RootNodes)
|
||||||
|
{
|
||||||
|
DrawHierachyNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawHierachyNode(Profiler.ScopeNode node)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
bool isOpen = false;
|
||||||
|
bool isLeaf = node.Children.Count == 0;
|
||||||
|
|
||||||
|
if (isLeaf) {
|
||||||
|
ImGui.TreeNodeEx(node.Label, treeNodeLeafFlags);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isOpen = ImGui.TreeNodeEx(node.Label, treeNodeFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{node.ManagedThreadId}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{node.ElapsedMilliseconds():0.000}");
|
||||||
|
|
||||||
|
if (isOpen)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
DrawHierachyNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawCombined(in IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> orderedCombinedData)
|
||||||
|
{
|
||||||
|
if(orderedCombinedData == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.BeginChild("Combined", new System.Numerics.Vector2(0, 0));
|
||||||
|
|
||||||
|
if (ImGui.BeginTable("ProfilerCombinedData", 3, tableFlags, new System.Numerics.Vector2(0, 0)))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.6f, 0);
|
||||||
|
ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.2f, 1);
|
||||||
|
ImGui.TableSetupColumn("calls", ImGuiTableColumnFlags.WidthStretch, 0.2f, 2);
|
||||||
|
ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, (double ms, uint calls)> combinedData in orderedCombinedData)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{combinedData.Key}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{combinedData.Value.ms:0.000}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text($"{combinedData.Value.calls}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IOrderedEnumerable<KeyValuePair<string, (double ms, uint calls)>> CalculateCombinedData(Profiler.Frame frame)
|
||||||
|
{
|
||||||
|
Dictionary<string, (double ms, uint calls)> combinedRecordData = new Dictionary<string, (double ms, uint calls)>(128);
|
||||||
|
foreach (Profiler.ScopeNode node in frame.RootNodes)
|
||||||
|
{
|
||||||
|
CalculateCombinedData(node, in combinedRecordData);
|
||||||
|
}
|
||||||
|
return combinedRecordData.OrderByDescending(x => x.Value.ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CalculateCombinedData(Profiler.ScopeNode node, in Dictionary<string, (double ms, uint calls)> combinedRecordData)
|
||||||
|
{
|
||||||
|
if (combinedRecordData.TryGetValue(node.Label, out (double ms, uint calls) combined))
|
||||||
|
{
|
||||||
|
combinedRecordData[node.Label] = (combined.ms + node.ElapsedMilliseconds(), combined.calls + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
combinedRecordData.Add(node.Label, (node.ElapsedMilliseconds(), 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
CalculateCombinedData(node.Children[i], combinedRecordData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawFlameGraph(Profiler.Frame frame)
|
||||||
|
{
|
||||||
|
if (frame == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerVisualizer.RenderFlameGraph(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Components
|
||||||
|
{
|
||||||
|
public readonly record struct LocalToWorld(Matrix4x4 localToWorldMatrix);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Components
|
||||||
|
{
|
||||||
|
[SceneComponent]
|
||||||
|
public readonly record struct LocalTransform(Vector3 position, Quaternion rotation, Vector3 scale)
|
||||||
|
{
|
||||||
|
public static readonly LocalTransform Identity = new(Vector3.Zero, Quaternion.Identity, Vector3.One);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Components
|
||||||
|
{
|
||||||
|
public readonly record struct Root;
|
||||||
|
//public readonly record struct Parent;
|
||||||
|
//public readonly record struct PreviousParent;
|
||||||
|
public readonly record struct Child;
|
||||||
|
// Describes a relation from the child to the parent.
|
||||||
|
public readonly record struct ChildParentRelation;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Components
|
||||||
|
{
|
||||||
|
[SceneComponent]
|
||||||
|
public readonly record struct Test();
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
|
|
||||||
<Target Name="Runtime ID" AfterTargets="Build">
|
|
||||||
<Message Text="Runtime ID: $(RuntimeIdentifier)" Importance="high"/>
|
|
||||||
</Target>
|
|
||||||
|
|
||||||
<ItemGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))">
|
|
||||||
<Content Include="..\libs\x64\**\*.*" >
|
|
||||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))">
|
|
||||||
<Content Include="..\libs\lib64\**\*.*" >
|
|
||||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))">
|
|
||||||
<Content Include="..\libs\osx\**\*.*" >
|
|
||||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
+57
-59
@@ -1,17 +1,22 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using Nerfed.Runtime.Audio;
|
using Nerfed.Runtime.Audio;
|
||||||
using Nerfed.Runtime.Graphics;
|
using Nerfed.Runtime.Graphics;
|
||||||
using SDL2;
|
using SDL2;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Nerfed.Runtime;
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
public static class Engine
|
public static class Engine
|
||||||
{
|
{
|
||||||
|
public static event Action OnInitialize;
|
||||||
|
public static event Action OnUpdate;
|
||||||
|
public static event Action OnRender;
|
||||||
|
public static event Action OnQuit;
|
||||||
|
|
||||||
public static TimeSpan MaxDeltaTime { get; set; } = TimeSpan.FromMilliseconds(100);
|
public static TimeSpan MaxDeltaTime { get; set; } = TimeSpan.FromMilliseconds(100);
|
||||||
public static bool VSync { get; set; }
|
public static bool VSync { get; set; }
|
||||||
|
|
||||||
public static GraphicsDevice GraphicsDevice { get; private set; }
|
public static GraphicsDevice GraphicsDevice { get; private set; }
|
||||||
public static AudioDevice AudioDevice { get; private set; }
|
//public static AudioDevice AudioDevice { get; private set; }
|
||||||
public static Window MainWindow { get; private set; }
|
public static Window MainWindow { get; private set; }
|
||||||
public static TimeSpan Timestep { get; private set; }
|
public static TimeSpan Timestep { get; private set; }
|
||||||
|
|
||||||
@@ -39,79 +44,75 @@ public static class Engine
|
|||||||
private const string WindowTitle = "Nerfed";
|
private const string WindowTitle = "Nerfed";
|
||||||
//..
|
//..
|
||||||
|
|
||||||
internal static void Run(string[] args)
|
public static void Run(string[] args) {
|
||||||
{
|
|
||||||
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / TargetTimestep);
|
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / TargetTimestep);
|
||||||
gameTimer = Stopwatch.StartNew();
|
gameTimer = Stopwatch.StartNew();
|
||||||
SetFrameLimiter(new FrameLimiterSettings(FrameLimiterMode.Capped, MaxFps));
|
SetFrameLimiter(new FrameLimiterSettings(FrameLimiterMode.Capped, MaxFps));
|
||||||
|
|
||||||
for (int i = 0; i < previousSleepTimes.Length; i += 1)
|
for(int i = 0; i < previousSleepTimes.Length; i += 1) {
|
||||||
{
|
|
||||||
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
|
if(SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0) {
|
||||||
{
|
|
||||||
throw new Exception("Failed to init SDL");
|
throw new Exception("Failed to init SDL");
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsDevice = new GraphicsDevice(BackendFlags.All);
|
GraphicsDevice = new GraphicsDevice(BackendFlags.All);
|
||||||
|
GraphicsDevice.LoadDefaultPipelines();
|
||||||
|
|
||||||
MainWindow = new Window(GraphicsDevice, new WindowCreateInfo(WindowTitle, WindowWidth, WindowHeight, ScreenMode.Windowed));
|
MainWindow = new Window(GraphicsDevice, new WindowCreateInfo(WindowTitle, WindowWidth, WindowHeight, ScreenMode.Windowed));
|
||||||
if (!GraphicsDevice.ClaimWindow(MainWindow, SwapchainComposition.SDR, VSync ? PresentMode.VSync : PresentMode.Mailbox))
|
if(!GraphicsDevice.ClaimWindow(MainWindow, SwapchainComposition.SDR, VSync ? PresentMode.VSync : PresentMode.Mailbox)) {
|
||||||
{
|
|
||||||
throw new Exception("Failed to claim window");
|
throw new Exception("Failed to claim window");
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioDevice = new AudioDevice();
|
//AudioDevice = new AudioDevice();
|
||||||
|
|
||||||
while (!quit)
|
OnInitialize?.Invoke();
|
||||||
{
|
|
||||||
|
while(!quit) {
|
||||||
Tick();
|
Tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnQuit?.Invoke();
|
||||||
|
|
||||||
GraphicsDevice.UnclaimWindow(MainWindow);
|
GraphicsDevice.UnclaimWindow(MainWindow);
|
||||||
MainWindow.Dispose();
|
MainWindow.Dispose();
|
||||||
GraphicsDevice.Dispose();
|
GraphicsDevice.Dispose();
|
||||||
AudioDevice.Dispose();
|
//AudioDevice.Dispose();
|
||||||
SDL.SDL_Quit();
|
SDL.SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the frame limiter settings.
|
/// Updates the frame limiter settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void SetFrameLimiter(FrameLimiterSettings settings)
|
public static void SetFrameLimiter(FrameLimiterSettings settings) {
|
||||||
{
|
|
||||||
framerateCapped = settings.Mode == FrameLimiterMode.Capped;
|
framerateCapped = settings.Mode == FrameLimiterMode.Capped;
|
||||||
|
|
||||||
if (framerateCapped)
|
if(framerateCapped) {
|
||||||
{
|
|
||||||
framerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap);
|
framerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
framerateCapTimeSpan = TimeSpan.Zero;
|
framerateCapTimeSpan = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Quit()
|
public static void Quit() {
|
||||||
{
|
|
||||||
quit = true;
|
quit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Tick()
|
private static void Tick() {
|
||||||
{
|
Profiler.BeginFrame();
|
||||||
|
|
||||||
AdvanceElapsedTime();
|
AdvanceElapsedTime();
|
||||||
|
|
||||||
if (framerateCapped)
|
if(framerateCapped) {
|
||||||
{
|
Profiler.BeginSample("framerateCapped");
|
||||||
|
|
||||||
/* We want to wait until the framerate cap,
|
/* We want to wait until the framerate cap,
|
||||||
* but we don't want to oversleep. Requesting repeated 1ms sleeps and
|
* but we don't want to oversleep. Requesting repeated 1ms sleeps and
|
||||||
* seeing how long we actually slept for lets us estimate the worst case
|
* seeing how long we actually slept for lets us estimate the worst case
|
||||||
* sleep precision so we don't oversleep the next frame.
|
* sleep precision so we don't oversleep the next frame.
|
||||||
*/
|
*/
|
||||||
while (accumulatedDrawTime + worstCaseSleepPrecision < framerateCapTimeSpan)
|
while(accumulatedDrawTime + worstCaseSleepPrecision < framerateCapTimeSpan) {
|
||||||
{
|
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
|
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
|
||||||
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
|
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
|
||||||
@@ -122,23 +123,22 @@ public static class Engine
|
|||||||
* SpinWait(1) works by pausing the thread for very short intervals, so it is
|
* SpinWait(1) works by pausing the thread for very short intervals, so it is
|
||||||
* an efficient and time-accurate way to wait out the rest of the time.
|
* an efficient and time-accurate way to wait out the rest of the time.
|
||||||
*/
|
*/
|
||||||
while (accumulatedDrawTime < framerateCapTimeSpan)
|
while(accumulatedDrawTime < framerateCapTimeSpan) {
|
||||||
{
|
|
||||||
Thread.SpinWait(1);
|
Thread.SpinWait(1);
|
||||||
AdvanceElapsedTime();
|
AdvanceElapsedTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Profiler.EndSample();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not let any step take longer than our maximum.
|
// Do not let any step take longer than our maximum.
|
||||||
if (accumulatedUpdateTime > MaxDeltaTime)
|
if(accumulatedUpdateTime > MaxDeltaTime) {
|
||||||
{
|
|
||||||
accumulatedUpdateTime = MaxDeltaTime;
|
accumulatedUpdateTime = MaxDeltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!quit)
|
if(!quit) {
|
||||||
{
|
while(accumulatedUpdateTime >= Timestep) {
|
||||||
while (accumulatedUpdateTime >= Timestep)
|
Profiler.BeginSample("Update");
|
||||||
{
|
|
||||||
Keyboard.Update();
|
Keyboard.Update();
|
||||||
Mouse.Update();
|
Mouse.Update();
|
||||||
GamePad.Update();
|
GamePad.Update();
|
||||||
@@ -146,21 +146,29 @@ public static class Engine
|
|||||||
ProcessSDLEvents();
|
ProcessSDLEvents();
|
||||||
|
|
||||||
// Tick game here...
|
// Tick game here...
|
||||||
|
Profiler.BeginSample("OnUpdate");
|
||||||
|
OnUpdate?.Invoke();
|
||||||
|
Profiler.EndSample();
|
||||||
|
|
||||||
AudioDevice.WakeThread();
|
//AudioDevice.WakeThread();
|
||||||
accumulatedUpdateTime -= Timestep;
|
accumulatedUpdateTime -= Timestep;
|
||||||
|
Profiler.EndSample();
|
||||||
}
|
}
|
||||||
|
|
||||||
double alpha = accumulatedUpdateTime / Timestep;
|
double alpha = accumulatedUpdateTime / Timestep;
|
||||||
|
|
||||||
// Render here..
|
// Render here..
|
||||||
|
Profiler.BeginSample("OnRender");
|
||||||
|
OnRender?.Invoke();
|
||||||
|
Profiler.EndSample();
|
||||||
|
|
||||||
accumulatedDrawTime -= framerateCapTimeSpan;
|
accumulatedDrawTime -= framerateCapTimeSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Profiler.EndFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TimeSpan AdvanceElapsedTime()
|
private static TimeSpan AdvanceElapsedTime() {
|
||||||
{
|
|
||||||
long currentTicks = gameTimer.Elapsed.Ticks;
|
long currentTicks = gameTimer.Elapsed.Ticks;
|
||||||
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
|
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
|
||||||
accumulatedUpdateTime += timeAdvanced;
|
accumulatedUpdateTime += timeAdvanced;
|
||||||
@@ -169,12 +177,9 @@ public static class Engine
|
|||||||
return timeAdvanced;
|
return timeAdvanced;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessSDLEvents()
|
private static void ProcessSDLEvents() {
|
||||||
{
|
while(SDL.SDL_PollEvent(out SDL.SDL_Event ev) == 1) {
|
||||||
while (SDL.SDL_PollEvent(out SDL.SDL_Event ev) == 1)
|
switch(ev.type) {
|
||||||
{
|
|
||||||
switch (ev.type)
|
|
||||||
{
|
|
||||||
case SDL.SDL_EventType.SDL_QUIT:
|
case SDL.SDL_EventType.SDL_QUIT:
|
||||||
Quit();
|
Quit();
|
||||||
break;
|
break;
|
||||||
@@ -210,16 +215,14 @@ public static class Engine
|
|||||||
/* To calculate the sleep precision of the OS, we take the worst case
|
/* To calculate the sleep precision of the OS, we take the worst case
|
||||||
* time spent sleeping over the results of previous requests to sleep 1ms.
|
* time spent sleeping over the results of previous requests to sleep 1ms.
|
||||||
*/
|
*/
|
||||||
private static void UpdateEstimatedSleepPrecision(TimeSpan timeSpentSleeping)
|
private static void UpdateEstimatedSleepPrecision(TimeSpan timeSpentSleeping) {
|
||||||
{
|
|
||||||
/* It is unlikely that the scheduler will actually be more imprecise than
|
/* It is unlikely that the scheduler will actually be more imprecise than
|
||||||
* 4ms and we don't want to get wrecked by a single long sleep so we cap this
|
* 4ms and we don't want to get wrecked by a single long sleep so we cap this
|
||||||
* value at 4ms for sanity.
|
* value at 4ms for sanity.
|
||||||
*/
|
*/
|
||||||
TimeSpan upperTimeBound = TimeSpan.FromMilliseconds(4);
|
TimeSpan upperTimeBound = TimeSpan.FromMilliseconds(4);
|
||||||
|
|
||||||
if (timeSpentSleeping > upperTimeBound)
|
if(timeSpentSleeping > upperTimeBound) {
|
||||||
{
|
|
||||||
timeSpentSleeping = upperTimeBound;
|
timeSpentSleeping = upperTimeBound;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,17 +231,12 @@ public static class Engine
|
|||||||
* is if we either 1) just got a new worst case, or 2) the worst case was
|
* is if we either 1) just got a new worst case, or 2) the worst case was
|
||||||
* the oldest entry on the list.
|
* the oldest entry on the list.
|
||||||
*/
|
*/
|
||||||
if (timeSpentSleeping >= worstCaseSleepPrecision)
|
if(timeSpentSleeping >= worstCaseSleepPrecision) {
|
||||||
{
|
|
||||||
worstCaseSleepPrecision = timeSpentSleeping;
|
worstCaseSleepPrecision = timeSpentSleeping;
|
||||||
}
|
} else if(previousSleepTimes[sleepTimeIndex] == worstCaseSleepPrecision) {
|
||||||
else if (previousSleepTimes[sleepTimeIndex] == worstCaseSleepPrecision)
|
|
||||||
{
|
|
||||||
TimeSpan maxSleepTime = TimeSpan.MinValue;
|
TimeSpan maxSleepTime = TimeSpan.MinValue;
|
||||||
for (int i = 0; i < previousSleepTimes.Length; i++)
|
for(int i = 0; i < previousSleepTimes.Length; i++) {
|
||||||
{
|
if(previousSleepTimes[i] > maxSleepTime) {
|
||||||
if (previousSleepTimes[i] > maxSleepTime)
|
|
||||||
{
|
|
||||||
maxSleepTime = previousSleepTimes[i];
|
maxSleepTime = previousSleepTimes[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,716 +0,0 @@
|
|||||||
namespace Nerfed.Runtime.Graphics;
|
|
||||||
|
|
||||||
internal class EmbeddedShadersSpirV : IEmbeddedShaders
|
|
||||||
{
|
|
||||||
public ShaderFormat ShaderFormat => ShaderFormat.SPIRV;
|
|
||||||
|
|
||||||
public byte[] FullscreenVert { get; } =
|
|
||||||
[
|
|
||||||
0x3, 0x2, 0x23, 0x7, 0x0, 0x0, 0x1, 0x0, 0xB, 0x0,
|
|
||||||
0x8, 0x0, 0x2E, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x11, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0xB, 0x0,
|
|
||||||
0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x47, 0x4C, 0x53, 0x4C,
|
|
||||||
0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0xE, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0xF, 0x0, 0x8, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0xC, 0x0,
|
|
||||||
0x0, 0x0, 0x1D, 0x0, 0x0, 0x0, 0x3, 0x0, 0x3, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0xC2, 0x1, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x4, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x5, 0x0, 0x9, 0x0,
|
|
||||||
0x0, 0x0, 0x6F, 0x75, 0x74, 0x54, 0x65, 0x78, 0x43, 0x6F,
|
|
||||||
0x6F, 0x72, 0x64, 0x0, 0x5, 0x0, 0x6, 0x0, 0xC, 0x0,
|
|
||||||
0x0, 0x0, 0x67, 0x6C, 0x5F, 0x56, 0x65, 0x72, 0x74, 0x65,
|
|
||||||
0x78, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0x1B, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x50,
|
|
||||||
0x65, 0x72, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x6, 0x0, 0x1B, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73,
|
|
||||||
0x69, 0x74, 0x69, 0x6F, 0x6E, 0x0, 0x6, 0x0, 0x7, 0x0,
|
|
||||||
0x1B, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x67, 0x6C,
|
|
||||||
0x5F, 0x50, 0x6F, 0x69, 0x6E, 0x74, 0x53, 0x69, 0x7A, 0x65,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x7, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x43,
|
|
||||||
0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63,
|
|
||||||
0x65, 0x0, 0x6, 0x0, 0x7, 0x0, 0x1B, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x43, 0x75, 0x6C,
|
|
||||||
0x6C, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x0,
|
|
||||||
0x5, 0x0, 0x3, 0x0, 0x1D, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x9, 0x0, 0x0, 0x0,
|
|
||||||
0x1E, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47, 0x0,
|
|
||||||
0x4, 0x0, 0xC, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x2A, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x4, 0x0, 0x0, 0x0, 0x47, 0x0, 0x3, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x13, 0x0, 0x2, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x21, 0x0, 0x3, 0x0, 0x3, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x16, 0x0, 0x3, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x17, 0x0,
|
|
||||||
0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x8, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x3B, 0x0, 0x4, 0x0, 0x8, 0x0, 0x0, 0x0, 0x9, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x15, 0x0, 0x4, 0x0,
|
|
||||||
0xA, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0xB, 0x0, 0x0, 0x0, 0xC, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0xA, 0x0,
|
|
||||||
0x0, 0x0, 0xE, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x2B, 0x0, 0x4, 0x0, 0xA, 0x0, 0x0, 0x0, 0x10, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0,
|
|
||||||
0x17, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x4, 0x0,
|
|
||||||
0x0, 0x0, 0x15, 0x0, 0x4, 0x0, 0x18, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2B, 0x0,
|
|
||||||
0x4, 0x0, 0x18, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x4, 0x0, 0x1A, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0,
|
|
||||||
0x1E, 0x0, 0x6, 0x0, 0x1B, 0x0, 0x0, 0x0, 0x17, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x1A, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x1C, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x1B, 0x0, 0x0, 0x0,
|
|
||||||
0x3B, 0x0, 0x4, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x1D, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0xA, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x2B, 0x0,
|
|
||||||
0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0xC0, 0x2C, 0x0, 0x5, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
|
|
||||||
0x21, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0xBF,
|
|
||||||
0x2B, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x25, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x80, 0x3F, 0x2C, 0x0, 0x5, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0x24, 0x0,
|
|
||||||
0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x2C, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x36, 0x0,
|
|
||||||
0x5, 0x0, 0x2, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xF8, 0x0,
|
|
||||||
0x2, 0x0, 0x5, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0xA, 0x0, 0x0, 0x0, 0xD, 0x0, 0x0, 0x0, 0xC, 0x0,
|
|
||||||
0x0, 0x0, 0xC4, 0x0, 0x5, 0x0, 0xA, 0x0, 0x0, 0x0,
|
|
||||||
0xF, 0x0, 0x0, 0x0, 0xD, 0x0, 0x0, 0x0, 0xE, 0x0,
|
|
||||||
0x0, 0x0, 0xC7, 0x0, 0x5, 0x0, 0xA, 0x0, 0x0, 0x0,
|
|
||||||
0x11, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0x0, 0x10, 0x0,
|
|
||||||
0x0, 0x0, 0x6F, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x12, 0x0, 0x0, 0x0, 0x11, 0x0, 0x0, 0x0, 0x3D, 0x0,
|
|
||||||
0x4, 0x0, 0xA, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0,
|
|
||||||
0xC, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x5, 0x0, 0xA, 0x0,
|
|
||||||
0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0,
|
|
||||||
0x10, 0x0, 0x0, 0x0, 0x6F, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0,
|
|
||||||
0x50, 0x0, 0x5, 0x0, 0x7, 0x0, 0x0, 0x0, 0x16, 0x0,
|
|
||||||
0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0,
|
|
||||||
0x3E, 0x0, 0x3, 0x0, 0x9, 0x0, 0x0, 0x0, 0x16, 0x0,
|
|
||||||
0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x1F, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x85, 0x0,
|
|
||||||
0x5, 0x0, 0x7, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0,
|
|
||||||
0x1F, 0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x81, 0x0,
|
|
||||||
0x5, 0x0, 0x7, 0x0, 0x0, 0x0, 0x27, 0x0, 0x0, 0x0,
|
|
||||||
0x23, 0x0, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0x51, 0x0,
|
|
||||||
0x5, 0x0, 0x6, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0,
|
|
||||||
0x27, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x51, 0x0,
|
|
||||||
0x5, 0x0, 0x6, 0x0, 0x0, 0x0, 0x2A, 0x0, 0x0, 0x0,
|
|
||||||
0x27, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x50, 0x0,
|
|
||||||
0x7, 0x0, 0x17, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x0, 0x0,
|
|
||||||
0x29, 0x0, 0x0, 0x0, 0x2A, 0x0, 0x0, 0x0, 0x28, 0x0,
|
|
||||||
0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0,
|
|
||||||
0x2C, 0x0, 0x0, 0x0, 0x2D, 0x0, 0x0, 0x0, 0x1D, 0x0,
|
|
||||||
0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0,
|
|
||||||
0x2D, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x0, 0x0, 0xFD, 0x0,
|
|
||||||
0x1, 0x0, 0x38, 0x0, 0x1, 0x0,
|
|
||||||
];
|
|
||||||
|
|
||||||
public byte[] TextMsdfFrag { get; } =
|
|
||||||
[
|
|
||||||
0x3, 0x2, 0x23, 0x7, 0x0, 0x0, 0x1, 0x0, 0xB, 0x0,
|
|
||||||
0x8, 0x0, 0x6C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x11, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x11, 0x0,
|
|
||||||
0x2, 0x0, 0x32, 0x0, 0x0, 0x0, 0xB, 0x0, 0x6, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x47, 0x4C, 0x53, 0x4C, 0x2E, 0x73,
|
|
||||||
0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0xE, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0xF, 0x0, 0x8, 0x0, 0x4, 0x0, 0x0, 0x0,
|
|
||||||
0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x36, 0x0, 0x0, 0x0, 0x64, 0x0, 0x0, 0x0,
|
|
||||||
0x67, 0x0, 0x0, 0x0, 0x10, 0x0, 0x3, 0x0, 0x4, 0x0,
|
|
||||||
0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3, 0x0, 0x3, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0xC2, 0x1, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x4, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x7, 0x0, 0xC, 0x0,
|
|
||||||
0x0, 0x0, 0x6D, 0x65, 0x64, 0x69, 0x61, 0x6E, 0x28, 0x66,
|
|
||||||
0x31, 0x3B, 0x66, 0x31, 0x3B, 0x66, 0x31, 0x3B, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x3, 0x0, 0x9, 0x0, 0x0, 0x0,
|
|
||||||
0x72, 0x0, 0x0, 0x0, 0x5, 0x0, 0x3, 0x0, 0xA, 0x0,
|
|
||||||
0x0, 0x0, 0x67, 0x0, 0x0, 0x0, 0x5, 0x0, 0x3, 0x0,
|
|
||||||
0xB, 0x0, 0x0, 0x0, 0x62, 0x0, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0xF, 0x0, 0x0, 0x0, 0x73, 0x63, 0x72, 0x65,
|
|
||||||
0x65, 0x6E, 0x50, 0x78, 0x52, 0x61, 0x6E, 0x67, 0x65, 0x28,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x5, 0x0, 0x1E, 0x0, 0x0, 0x0,
|
|
||||||
0x75, 0x6E, 0x69, 0x74, 0x52, 0x61, 0x6E, 0x67, 0x65, 0x0,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x3, 0x0, 0x1F, 0x0, 0x0, 0x0,
|
|
||||||
0x55, 0x42, 0x4F, 0x0, 0x6, 0x0, 0x5, 0x0, 0x1F, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70, 0x78, 0x52, 0x61,
|
|
||||||
0x6E, 0x67, 0x65, 0x0, 0x5, 0x0, 0x3, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x75, 0x62, 0x6F, 0x0, 0x5, 0x0, 0x4, 0x0,
|
|
||||||
0x2B, 0x0, 0x0, 0x0, 0x6D, 0x73, 0x64, 0x66, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x6, 0x0, 0x32, 0x0, 0x0, 0x0,
|
|
||||||
0x73, 0x63, 0x72, 0x65, 0x65, 0x6E, 0x54, 0x65, 0x78, 0x53,
|
|
||||||
0x69, 0x7A, 0x65, 0x0, 0x0, 0x0, 0x5, 0x0, 0x5, 0x0,
|
|
||||||
0x36, 0x0, 0x0, 0x0, 0x69, 0x6E, 0x54, 0x65, 0x78, 0x43,
|
|
||||||
0x6F, 0x6F, 0x72, 0x64, 0x0, 0x0, 0x5, 0x0, 0x3, 0x0,
|
|
||||||
0x44, 0x0, 0x0, 0x0, 0x6D, 0x73, 0x64, 0x0, 0x5, 0x0,
|
|
||||||
0x3, 0x0, 0x4A, 0x0, 0x0, 0x0, 0x73, 0x64, 0x0, 0x0,
|
|
||||||
0x5, 0x0, 0x4, 0x0, 0x4B, 0x0, 0x0, 0x0, 0x70, 0x61,
|
|
||||||
0x72, 0x61, 0x6D, 0x0, 0x0, 0x0, 0x5, 0x0, 0x4, 0x0,
|
|
||||||
0x50, 0x0, 0x0, 0x0, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x0,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x4, 0x0, 0x54, 0x0, 0x0, 0x0,
|
|
||||||
0x70, 0x61, 0x72, 0x61, 0x6D, 0x0, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x7, 0x0, 0x59, 0x0, 0x0, 0x0, 0x73, 0x63, 0x72, 0x65,
|
|
||||||
0x65, 0x6E, 0x50, 0x78, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E,
|
|
||||||
0x63, 0x65, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x4, 0x0,
|
|
||||||
0x5E, 0x0, 0x0, 0x0, 0x6F, 0x70, 0x61, 0x63, 0x69, 0x74,
|
|
||||||
0x79, 0x0, 0x5, 0x0, 0x5, 0x0, 0x64, 0x0, 0x0, 0x0,
|
|
||||||
0x6F, 0x75, 0x74, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x4, 0x0, 0x67, 0x0, 0x0, 0x0,
|
|
||||||
0x69, 0x6E, 0x43, 0x6F, 0x6C, 0x6F, 0x72, 0x0, 0x48, 0x0,
|
|
||||||
0x5, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47, 0x0,
|
|
||||||
0x3, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x4, 0x0, 0x21, 0x0, 0x0, 0x0, 0x22, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0,
|
|
||||||
0x21, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x2B, 0x0, 0x0, 0x0,
|
|
||||||
0x22, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x47, 0x0,
|
|
||||||
0x4, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x36, 0x0,
|
|
||||||
0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x4, 0x0, 0x64, 0x0, 0x0, 0x0, 0x1E, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0,
|
|
||||||
0x67, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0x13, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x21, 0x0, 0x3, 0x0, 0x3, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x16, 0x0, 0x3, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x21, 0x0, 0x6, 0x0, 0x8, 0x0, 0x0, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x21, 0x0, 0x3, 0x0, 0xE, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0,
|
|
||||||
0x1C, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x1D, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x1E, 0x0,
|
|
||||||
0x3, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x4, 0x0, 0x20, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x15, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2B, 0x0,
|
|
||||||
0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x24, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x19, 0x0, 0x9, 0x0, 0x28, 0x0, 0x0, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1B, 0x0, 0x3, 0x0,
|
|
||||||
0x29, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x20, 0x0,
|
|
||||||
0x4, 0x0, 0x2A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x29, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x2A, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x17, 0x0, 0x4, 0x0, 0x2E, 0x0, 0x0, 0x0, 0x22, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x80, 0x3F, 0x2C, 0x0, 0x5, 0x0, 0x1C, 0x0, 0x0, 0x0,
|
|
||||||
0x34, 0x0, 0x0, 0x0, 0x33, 0x0, 0x0, 0x0, 0x33, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x35, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x35, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x3A, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3F,
|
|
||||||
0x17, 0x0, 0x4, 0x0, 0x42, 0x0, 0x0, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0,
|
|
||||||
0x43, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x42, 0x0,
|
|
||||||
0x0, 0x0, 0x17, 0x0, 0x4, 0x0, 0x47, 0x0, 0x0, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x15, 0x0,
|
|
||||||
0x4, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x4C, 0x0,
|
|
||||||
0x0, 0x0, 0x4D, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x2B, 0x0, 0x4, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x51, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0x4C, 0x0, 0x0, 0x0, 0x55, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0,
|
|
||||||
0x4, 0x0, 0x63, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x63, 0x0,
|
|
||||||
0x0, 0x0, 0x64, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
|
||||||
0x2C, 0x0, 0x7, 0x0, 0x47, 0x0, 0x0, 0x0, 0x65, 0x0,
|
|
||||||
0x0, 0x0, 0x61, 0x0, 0x0, 0x0, 0x61, 0x0, 0x0, 0x0,
|
|
||||||
0x61, 0x0, 0x0, 0x0, 0x61, 0x0, 0x0, 0x0, 0x20, 0x0,
|
|
||||||
0x4, 0x0, 0x66, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x66, 0x0,
|
|
||||||
0x0, 0x0, 0x67, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x36, 0x0, 0x5, 0x0, 0x2, 0x0, 0x0, 0x0, 0x4, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
|
||||||
0xF8, 0x0, 0x2, 0x0, 0x5, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x43, 0x0, 0x0, 0x0, 0x44, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x4A, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x3B, 0x0, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x4B, 0x0,
|
|
||||||
0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x54, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x59, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x5E, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x29, 0x0, 0x0, 0x0, 0x45, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0x1C, 0x0, 0x0, 0x0, 0x46, 0x0, 0x0, 0x0, 0x36, 0x0,
|
|
||||||
0x0, 0x0, 0x57, 0x0, 0x5, 0x0, 0x47, 0x0, 0x0, 0x0,
|
|
||||||
0x48, 0x0, 0x0, 0x0, 0x45, 0x0, 0x0, 0x0, 0x46, 0x0,
|
|
||||||
0x0, 0x0, 0x4F, 0x0, 0x8, 0x0, 0x42, 0x0, 0x0, 0x0,
|
|
||||||
0x49, 0x0, 0x0, 0x0, 0x48, 0x0, 0x0, 0x0, 0x48, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x44, 0x0,
|
|
||||||
0x0, 0x0, 0x49, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x4E, 0x0, 0x0, 0x0, 0x44, 0x0,
|
|
||||||
0x0, 0x0, 0x4D, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x4F, 0x0, 0x0, 0x0, 0x4E, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x4B, 0x0, 0x0, 0x0,
|
|
||||||
0x4F, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x52, 0x0, 0x0, 0x0, 0x44, 0x0, 0x0, 0x0,
|
|
||||||
0x51, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x53, 0x0, 0x0, 0x0, 0x52, 0x0, 0x0, 0x0,
|
|
||||||
0x3E, 0x0, 0x3, 0x0, 0x50, 0x0, 0x0, 0x0, 0x53, 0x0,
|
|
||||||
0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x56, 0x0, 0x0, 0x0, 0x44, 0x0, 0x0, 0x0, 0x55, 0x0,
|
|
||||||
0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x57, 0x0, 0x0, 0x0, 0x56, 0x0, 0x0, 0x0, 0x3E, 0x0,
|
|
||||||
0x3, 0x0, 0x54, 0x0, 0x0, 0x0, 0x57, 0x0, 0x0, 0x0,
|
|
||||||
0x39, 0x0, 0x7, 0x0, 0x6, 0x0, 0x0, 0x0, 0x58, 0x0,
|
|
||||||
0x0, 0x0, 0xC, 0x0, 0x0, 0x0, 0x4B, 0x0, 0x0, 0x0,
|
|
||||||
0x50, 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x0, 0x3E, 0x0,
|
|
||||||
0x3, 0x0, 0x4A, 0x0, 0x0, 0x0, 0x58, 0x0, 0x0, 0x0,
|
|
||||||
0x39, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x5A, 0x0,
|
|
||||||
0x0, 0x0, 0xF, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x5B, 0x0, 0x0, 0x0, 0x4A, 0x0,
|
|
||||||
0x0, 0x0, 0x83, 0x0, 0x5, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x5C, 0x0, 0x0, 0x0, 0x5B, 0x0, 0x0, 0x0, 0x3A, 0x0,
|
|
||||||
0x0, 0x0, 0x85, 0x0, 0x5, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x5D, 0x0, 0x0, 0x0, 0x5A, 0x0, 0x0, 0x0, 0x5C, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x59, 0x0, 0x0, 0x0,
|
|
||||||
0x5D, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x5F, 0x0, 0x0, 0x0, 0x59, 0x0, 0x0, 0x0,
|
|
||||||
0x81, 0x0, 0x5, 0x0, 0x6, 0x0, 0x0, 0x0, 0x60, 0x0,
|
|
||||||
0x0, 0x0, 0x5F, 0x0, 0x0, 0x0, 0x3A, 0x0, 0x0, 0x0,
|
|
||||||
0xC, 0x0, 0x8, 0x0, 0x6, 0x0, 0x0, 0x0, 0x62, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x0, 0x0,
|
|
||||||
0x60, 0x0, 0x0, 0x0, 0x61, 0x0, 0x0, 0x0, 0x33, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x5E, 0x0, 0x0, 0x0,
|
|
||||||
0x62, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x47, 0x0,
|
|
||||||
0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x67, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x69, 0x0,
|
|
||||||
0x0, 0x0, 0x5E, 0x0, 0x0, 0x0, 0x50, 0x0, 0x7, 0x0,
|
|
||||||
0x47, 0x0, 0x0, 0x0, 0x6A, 0x0, 0x0, 0x0, 0x69, 0x0,
|
|
||||||
0x0, 0x0, 0x69, 0x0, 0x0, 0x0, 0x69, 0x0, 0x0, 0x0,
|
|
||||||
0x69, 0x0, 0x0, 0x0, 0xC, 0x0, 0x8, 0x0, 0x47, 0x0,
|
|
||||||
0x0, 0x0, 0x6B, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x2E, 0x0, 0x0, 0x0, 0x65, 0x0, 0x0, 0x0, 0x68, 0x0,
|
|
||||||
0x0, 0x0, 0x6A, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0,
|
|
||||||
0x64, 0x0, 0x0, 0x0, 0x6B, 0x0, 0x0, 0x0, 0xFD, 0x0,
|
|
||||||
0x1, 0x0, 0x38, 0x0, 0x1, 0x0, 0x36, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x37, 0x0, 0x3, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x37, 0x0,
|
|
||||||
0x3, 0x0, 0x7, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0,
|
|
||||||
0x37, 0x0, 0x3, 0x0, 0x7, 0x0, 0x0, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0xF8, 0x0, 0x2, 0x0, 0xD, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x11, 0x0,
|
|
||||||
0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0xA, 0x0,
|
|
||||||
0x0, 0x0, 0xC, 0x0, 0x7, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x13, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x25, 0x0,
|
|
||||||
0x0, 0x0, 0x11, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x14, 0x0,
|
|
||||||
0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0xA, 0x0,
|
|
||||||
0x0, 0x0, 0xC, 0x0, 0x7, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x16, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x28, 0x0,
|
|
||||||
0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x17, 0x0,
|
|
||||||
0x0, 0x0, 0xB, 0x0, 0x0, 0x0, 0xC, 0x0, 0x7, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0,
|
|
||||||
0x17, 0x0, 0x0, 0x0, 0xC, 0x0, 0x7, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x19, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x28, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x18, 0x0,
|
|
||||||
0x0, 0x0, 0xFE, 0x0, 0x2, 0x0, 0x19, 0x0, 0x0, 0x0,
|
|
||||||
0x38, 0x0, 0x1, 0x0, 0x36, 0x0, 0x5, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0xE, 0x0, 0x0, 0x0, 0xF8, 0x0, 0x2, 0x0, 0x10, 0x0,
|
|
||||||
0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x1D, 0x0, 0x0, 0x0,
|
|
||||||
0x1E, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x1D, 0x0, 0x0, 0x0, 0x32, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x24, 0x0,
|
|
||||||
0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0,
|
|
||||||
0x23, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0x25, 0x0, 0x0, 0x0,
|
|
||||||
0x50, 0x0, 0x5, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x27, 0x0,
|
|
||||||
0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x29, 0x0, 0x0, 0x0, 0x2C, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x64, 0x0, 0x4, 0x0,
|
|
||||||
0x28, 0x0, 0x0, 0x0, 0x2D, 0x0, 0x0, 0x0, 0x2C, 0x0,
|
|
||||||
0x0, 0x0, 0x67, 0x0, 0x5, 0x0, 0x2E, 0x0, 0x0, 0x0,
|
|
||||||
0x2F, 0x0, 0x0, 0x0, 0x2D, 0x0, 0x0, 0x0, 0x23, 0x0,
|
|
||||||
0x0, 0x0, 0x6F, 0x0, 0x4, 0x0, 0x1C, 0x0, 0x0, 0x0,
|
|
||||||
0x30, 0x0, 0x0, 0x0, 0x2F, 0x0, 0x0, 0x0, 0x88, 0x0,
|
|
||||||
0x5, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x0,
|
|
||||||
0x27, 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x3E, 0x0,
|
|
||||||
0x3, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x37, 0x0,
|
|
||||||
0x0, 0x0, 0x36, 0x0, 0x0, 0x0, 0xD1, 0x0, 0x4, 0x0,
|
|
||||||
0x1C, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x37, 0x0,
|
|
||||||
0x0, 0x0, 0x88, 0x0, 0x5, 0x0, 0x1C, 0x0, 0x0, 0x0,
|
|
||||||
0x39, 0x0, 0x0, 0x0, 0x34, 0x0, 0x0, 0x0, 0x38, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x32, 0x0, 0x0, 0x0,
|
|
||||||
0x39, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x1C, 0x0,
|
|
||||||
0x0, 0x0, 0x3B, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x3C, 0x0,
|
|
||||||
0x0, 0x0, 0x32, 0x0, 0x0, 0x0, 0x94, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x0, 0x0, 0x3C, 0x0, 0x0, 0x0, 0x85, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x0, 0x0, 0x3A, 0x0,
|
|
||||||
0x0, 0x0, 0x3D, 0x0, 0x0, 0x0, 0xC, 0x0, 0x7, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x3F, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x0, 0x0,
|
|
||||||
0x33, 0x0, 0x0, 0x0, 0xFE, 0x0, 0x2, 0x0, 0x3F, 0x0,
|
|
||||||
0x0, 0x0, 0x38, 0x0, 0x1, 0x0,
|
|
||||||
];
|
|
||||||
|
|
||||||
public byte[] TextTransformVert { get; } =
|
|
||||||
[
|
|
||||||
0x3, 0x2, 0x23, 0x7, 0x0, 0x0, 0x1, 0x0, 0xB, 0x0,
|
|
||||||
0x8, 0x0, 0x2D, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x11, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0xB, 0x0,
|
|
||||||
0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x47, 0x4C, 0x53, 0x4C,
|
|
||||||
0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0xE, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0xF, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0xD, 0x0, 0x0, 0x0, 0x19, 0x0,
|
|
||||||
0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x27, 0x0, 0x0, 0x0,
|
|
||||||
0x29, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x3, 0x0,
|
|
||||||
0x3, 0x0, 0x2, 0x0, 0x0, 0x0, 0xC2, 0x1, 0x0, 0x0,
|
|
||||||
0x5, 0x0, 0x4, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6D, 0x61,
|
|
||||||
0x69, 0x6E, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x6, 0x0,
|
|
||||||
0xB, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x50, 0x65, 0x72,
|
|
||||||
0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x6, 0x0, 0x6, 0x0, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x67, 0x6C, 0x5F, 0x50, 0x6F, 0x73, 0x69, 0x74,
|
|
||||||
0x69, 0x6F, 0x6E, 0x0, 0x6, 0x0, 0x7, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x50,
|
|
||||||
0x6F, 0x69, 0x6E, 0x74, 0x53, 0x69, 0x7A, 0x65, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x7, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x67, 0x6C, 0x5F, 0x43, 0x6C, 0x69,
|
|
||||||
0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x0,
|
|
||||||
0x6, 0x0, 0x7, 0x0, 0xB, 0x0, 0x0, 0x0, 0x3, 0x0,
|
|
||||||
0x0, 0x0, 0x67, 0x6C, 0x5F, 0x43, 0x75, 0x6C, 0x6C, 0x44,
|
|
||||||
0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65, 0x0, 0x5, 0x0,
|
|
||||||
0x3, 0x0, 0xD, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x5, 0x0, 0x3, 0x0, 0x11, 0x0, 0x0, 0x0, 0x55, 0x42,
|
|
||||||
0x4F, 0x0, 0x6, 0x0, 0x7, 0x0, 0x11, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x56, 0x69, 0x65, 0x77, 0x50, 0x72,
|
|
||||||
0x6F, 0x6A, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x0, 0x0,
|
|
||||||
0x5, 0x0, 0x3, 0x0, 0x13, 0x0, 0x0, 0x0, 0x75, 0x62,
|
|
||||||
0x6F, 0x0, 0x5, 0x0, 0x4, 0x0, 0x19, 0x0, 0x0, 0x0,
|
|
||||||
0x69, 0x6E, 0x50, 0x6F, 0x73, 0x0, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x5, 0x0, 0x25, 0x0, 0x0, 0x0, 0x6F, 0x75, 0x74, 0x54,
|
|
||||||
0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x0, 0x5, 0x0,
|
|
||||||
0x5, 0x0, 0x27, 0x0, 0x0, 0x0, 0x69, 0x6E, 0x54, 0x65,
|
|
||||||
0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x5, 0x0, 0x29, 0x0, 0x0, 0x0, 0x6F, 0x75, 0x74, 0x43,
|
|
||||||
0x6F, 0x6C, 0x6F, 0x72, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x4, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x69, 0x6E, 0x43, 0x6F,
|
|
||||||
0x6C, 0x6F, 0x72, 0x0, 0x48, 0x0, 0x5, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x4, 0x0, 0x0, 0x0, 0x47, 0x0, 0x3, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x48, 0x0, 0x4, 0x0,
|
|
||||||
0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0,
|
|
||||||
0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0x11, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x48, 0x0, 0x5, 0x0, 0x11, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x10, 0x0,
|
|
||||||
0x0, 0x0, 0x47, 0x0, 0x3, 0x0, 0x11, 0x0, 0x0, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x13, 0x0,
|
|
||||||
0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x4, 0x0, 0x13, 0x0, 0x0, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0,
|
|
||||||
0x19, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x25, 0x0, 0x0, 0x0,
|
|
||||||
0x1E, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47, 0x0,
|
|
||||||
0x4, 0x0, 0x27, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x29, 0x0,
|
|
||||||
0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x4, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x1E, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x13, 0x0, 0x2, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x21, 0x0, 0x3, 0x0, 0x3, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x16, 0x0, 0x3, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x17, 0x0,
|
|
||||||
0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x4, 0x0, 0x0, 0x0, 0x15, 0x0, 0x4, 0x0, 0x8, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x2B, 0x0, 0x4, 0x0, 0x8, 0x0, 0x0, 0x0, 0x9, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x4, 0x0,
|
|
||||||
0xA, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x9, 0x0,
|
|
||||||
0x0, 0x0, 0x1E, 0x0, 0x6, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0xA, 0x0,
|
|
||||||
0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0,
|
|
||||||
0xC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0xC, 0x0, 0x0, 0x0,
|
|
||||||
0xD, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x15, 0x0,
|
|
||||||
0x4, 0x0, 0xE, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0xE, 0x0,
|
|
||||||
0x0, 0x0, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x18, 0x0, 0x4, 0x0, 0x10, 0x0, 0x0, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x3, 0x0,
|
|
||||||
0x11, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x20, 0x0,
|
|
||||||
0x4, 0x0, 0x12, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x11, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x12, 0x0,
|
|
||||||
0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x4, 0x0, 0x14, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0,
|
|
||||||
0x17, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x3, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x18, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x18, 0x0, 0x0, 0x0, 0x19, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x1B, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x3F,
|
|
||||||
0x20, 0x0, 0x4, 0x0, 0x21, 0x0, 0x0, 0x0, 0x3, 0x0,
|
|
||||||
0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0,
|
|
||||||
0x23, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x24, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x24, 0x0, 0x0, 0x0, 0x25, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x26, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0,
|
|
||||||
0x3B, 0x0, 0x4, 0x0, 0x26, 0x0, 0x0, 0x0, 0x27, 0x0,
|
|
||||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0,
|
|
||||||
0x21, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0, 0x3, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x2A, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x2A, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x36, 0x0, 0x5, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0xF8, 0x0, 0x2, 0x0, 0x5, 0x0,
|
|
||||||
0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x14, 0x0, 0x0, 0x0,
|
|
||||||
0x15, 0x0, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0xF, 0x0,
|
|
||||||
0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x10, 0x0, 0x0, 0x0,
|
|
||||||
0x16, 0x0, 0x0, 0x0, 0x15, 0x0, 0x0, 0x0, 0x3D, 0x0,
|
|
||||||
0x4, 0x0, 0x17, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x19, 0x0, 0x0, 0x0, 0x51, 0x0, 0x5, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x51, 0x0, 0x5, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x1D, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x51, 0x0, 0x5, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x50, 0x0, 0x7, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x0,
|
|
||||||
0x1D, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x91, 0x0, 0x5, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x1F, 0x0,
|
|
||||||
0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x21, 0x0, 0x0, 0x0,
|
|
||||||
0x22, 0x0, 0x0, 0x0, 0xD, 0x0, 0x0, 0x0, 0xF, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x22, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x23, 0x0,
|
|
||||||
0x0, 0x0, 0x28, 0x0, 0x0, 0x0, 0x27, 0x0, 0x0, 0x0,
|
|
||||||
0x3E, 0x0, 0x3, 0x0, 0x25, 0x0, 0x0, 0x0, 0x28, 0x0,
|
|
||||||
0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x2C, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x0, 0x0, 0x3E, 0x0,
|
|
||||||
0x3, 0x0, 0x29, 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0,
|
|
||||||
0xFD, 0x0, 0x1, 0x0, 0x38, 0x0, 0x1, 0x0,
|
|
||||||
];
|
|
||||||
|
|
||||||
public byte[] VideoYuv2RgbaFrag { get; } =
|
|
||||||
[
|
|
||||||
0x3, 0x2, 0x23, 0x7, 0x0, 0x0, 0x1, 0x0, 0xB, 0x0,
|
|
||||||
0x8, 0x0, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x11, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0xB, 0x0,
|
|
||||||
0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x47, 0x4C, 0x53, 0x4C,
|
|
||||||
0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0xE, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0xF, 0x0, 0x7, 0x0, 0x4, 0x0,
|
|
||||||
0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x11, 0x0, 0x0, 0x0, 0x2E, 0x0,
|
|
||||||
0x0, 0x0, 0x10, 0x0, 0x3, 0x0, 0x4, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x3, 0x0, 0x3, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0xC2, 0x1, 0x0, 0x0, 0x5, 0x0, 0x4, 0x0,
|
|
||||||
0x4, 0x0, 0x0, 0x0, 0x6D, 0x61, 0x69, 0x6E, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x5, 0x0, 0x3, 0x0, 0x9, 0x0, 0x0, 0x0,
|
|
||||||
0x79, 0x75, 0x76, 0x0, 0x5, 0x0, 0x5, 0x0, 0xD, 0x0,
|
|
||||||
0x0, 0x0, 0x59, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x72,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x5, 0x0, 0x11, 0x0,
|
|
||||||
0x0, 0x0, 0x54, 0x65, 0x78, 0x43, 0x6F, 0x6F, 0x72, 0x64,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x5, 0x0, 0x1A, 0x0,
|
|
||||||
0x0, 0x0, 0x55, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x72,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x5, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x56, 0x53, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x72,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x5, 0x0, 0x2E, 0x0,
|
|
||||||
0x0, 0x0, 0x46, 0x72, 0x61, 0x67, 0x43, 0x6F, 0x6C, 0x6F,
|
|
||||||
0x72, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0xD, 0x0,
|
|
||||||
0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x4, 0x0, 0xD, 0x0, 0x0, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0,
|
|
||||||
0x11, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x22, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x47, 0x0,
|
|
||||||
0x4, 0x0, 0x1A, 0x0, 0x0, 0x0, 0x21, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x47, 0x0, 0x4, 0x0, 0x21, 0x0, 0x0, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x47, 0x0, 0x4, 0x0,
|
|
||||||
0x2E, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x13, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0,
|
|
||||||
0x21, 0x0, 0x3, 0x0, 0x3, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x16, 0x0, 0x3, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
|
||||||
0x20, 0x0, 0x4, 0x0, 0x8, 0x0, 0x0, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x19, 0x0, 0x9, 0x0,
|
|
||||||
0xA, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x1, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x1B, 0x0, 0x3, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0xA, 0x0, 0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0xC, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x0,
|
|
||||||
0x3B, 0x0, 0x4, 0x0, 0xC, 0x0, 0x0, 0x0, 0xD, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0,
|
|
||||||
0xF, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x10, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0x10, 0x0, 0x0, 0x0, 0x11, 0x0, 0x0, 0x0,
|
|
||||||
0x1, 0x0, 0x0, 0x0, 0x17, 0x0, 0x4, 0x0, 0x13, 0x0,
|
|
||||||
0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0,
|
|
||||||
0x15, 0x0, 0x4, 0x0, 0x15, 0x0, 0x0, 0x0, 0x20, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0x15, 0x0, 0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x18, 0x0, 0x0, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x3B, 0x0,
|
|
||||||
0x4, 0x0, 0xC, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x15, 0x0,
|
|
||||||
0x0, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0,
|
|
||||||
0x3B, 0x0, 0x4, 0x0, 0xC, 0x0, 0x0, 0x0, 0x21, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0x15, 0x0, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0x2, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0xBD, 0x2B, 0x0,
|
|
||||||
0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0xBF, 0x2C, 0x0, 0x6, 0x0, 0x7, 0x0,
|
|
||||||
0x0, 0x0, 0x2A, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x0,
|
|
||||||
0x29, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0, 0x20, 0x0,
|
|
||||||
0x4, 0x0, 0x2D, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
|
||||||
0x13, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x2D, 0x0,
|
|
||||||
0x0, 0x0, 0x2E, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0,
|
|
||||||
0x2B, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x30, 0x0,
|
|
||||||
0x0, 0x0, 0xF4, 0xFD, 0x94, 0x3F, 0x2B, 0x0, 0x4, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x32, 0x0, 0x0, 0x0, 0x6, 0x81, 0xE5, 0x3F, 0x2C, 0x0,
|
|
||||||
0x6, 0x0, 0x7, 0x0, 0x0, 0x0, 0x33, 0x0, 0x0, 0x0,
|
|
||||||
0x30, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x0, 0x32, 0x0,
|
|
||||||
0x0, 0x0, 0x20, 0x0, 0x4, 0x0, 0x35, 0x0, 0x0, 0x0,
|
|
||||||
0x3, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x2B, 0x0,
|
|
||||||
0x4, 0x0, 0x6, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0,
|
|
||||||
0xAC, 0x1C, 0x5A, 0xBE, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x39, 0x0, 0x0, 0x0, 0xB0, 0x72, 0x8, 0xBF,
|
|
||||||
0x2C, 0x0, 0x6, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3A, 0x0,
|
|
||||||
0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0,
|
|
||||||
0x39, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x0, 0x0, 0x2, 0x2B, 0x7, 0x40,
|
|
||||||
0x2C, 0x0, 0x6, 0x0, 0x7, 0x0, 0x0, 0x0, 0x3F, 0x0,
|
|
||||||
0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x0, 0x0,
|
|
||||||
0x31, 0x0, 0x0, 0x0, 0x2B, 0x0, 0x4, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x42, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x3F,
|
|
||||||
0x2B, 0x0, 0x4, 0x0, 0x15, 0x0, 0x0, 0x0, 0x43, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x36, 0x0, 0x5, 0x0,
|
|
||||||
0x2, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xF8, 0x0, 0x2, 0x0,
|
|
||||||
0x5, 0x0, 0x0, 0x0, 0x3B, 0x0, 0x4, 0x0, 0x8, 0x0,
|
|
||||||
0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0xB, 0x0, 0x0, 0x0, 0xE, 0x0,
|
|
||||||
0x0, 0x0, 0xD, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0xF, 0x0, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x11, 0x0,
|
|
||||||
0x0, 0x0, 0x57, 0x0, 0x5, 0x0, 0x13, 0x0, 0x0, 0x0,
|
|
||||||
0x14, 0x0, 0x0, 0x0, 0xE, 0x0, 0x0, 0x0, 0x12, 0x0,
|
|
||||||
0x0, 0x0, 0x51, 0x0, 0x5, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x17, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x18, 0x0, 0x0, 0x0,
|
|
||||||
0x19, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x16, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x19, 0x0, 0x0, 0x0,
|
|
||||||
0x17, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0xB, 0x0,
|
|
||||||
0x0, 0x0, 0x1B, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0xF, 0x0, 0x0, 0x0, 0x1C, 0x0,
|
|
||||||
0x0, 0x0, 0x11, 0x0, 0x0, 0x0, 0x57, 0x0, 0x5, 0x0,
|
|
||||||
0x13, 0x0, 0x0, 0x0, 0x1D, 0x0, 0x0, 0x0, 0x1B, 0x0,
|
|
||||||
0x0, 0x0, 0x1C, 0x0, 0x0, 0x0, 0x51, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x1D, 0x0,
|
|
||||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0,
|
|
||||||
0x18, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x9, 0x0,
|
|
||||||
0x0, 0x0, 0x1F, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0,
|
|
||||||
0x20, 0x0, 0x0, 0x0, 0x1E, 0x0, 0x0, 0x0, 0x3D, 0x0,
|
|
||||||
0x4, 0x0, 0xB, 0x0, 0x0, 0x0, 0x22, 0x0, 0x0, 0x0,
|
|
||||||
0x21, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0xF, 0x0,
|
|
||||||
0x0, 0x0, 0x23, 0x0, 0x0, 0x0, 0x11, 0x0, 0x0, 0x0,
|
|
||||||
0x57, 0x0, 0x5, 0x0, 0x13, 0x0, 0x0, 0x0, 0x24, 0x0,
|
|
||||||
0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x23, 0x0, 0x0, 0x0,
|
|
||||||
0x51, 0x0, 0x5, 0x0, 0x6, 0x0, 0x0, 0x0, 0x25, 0x0,
|
|
||||||
0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
|
||||||
0x41, 0x0, 0x5, 0x0, 0x18, 0x0, 0x0, 0x0, 0x27, 0x0,
|
|
||||||
0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0,
|
|
||||||
0x3E, 0x0, 0x3, 0x0, 0x27, 0x0, 0x0, 0x0, 0x25, 0x0,
|
|
||||||
0x0, 0x0, 0x3D, 0x0, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0,
|
|
||||||
0x2B, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x81, 0x0,
|
|
||||||
0x5, 0x0, 0x7, 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0,
|
|
||||||
0x2B, 0x0, 0x0, 0x0, 0x2A, 0x0, 0x0, 0x0, 0x3E, 0x0,
|
|
||||||
0x3, 0x0, 0x9, 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0,
|
|
||||||
0x3D, 0x0, 0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x2F, 0x0,
|
|
||||||
0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x94, 0x0, 0x5, 0x0,
|
|
||||||
0x6, 0x0, 0x0, 0x0, 0x34, 0x0, 0x0, 0x0, 0x2F, 0x0,
|
|
||||||
0x0, 0x0, 0x33, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0,
|
|
||||||
0x35, 0x0, 0x0, 0x0, 0x36, 0x0, 0x0, 0x0, 0x2E, 0x0,
|
|
||||||
0x0, 0x0, 0x16, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0,
|
|
||||||
0x36, 0x0, 0x0, 0x0, 0x34, 0x0, 0x0, 0x0, 0x3D, 0x0,
|
|
||||||
0x4, 0x0, 0x7, 0x0, 0x0, 0x0, 0x37, 0x0, 0x0, 0x0,
|
|
||||||
0x9, 0x0, 0x0, 0x0, 0x94, 0x0, 0x5, 0x0, 0x6, 0x0,
|
|
||||||
0x0, 0x0, 0x3B, 0x0, 0x0, 0x0, 0x37, 0x0, 0x0, 0x0,
|
|
||||||
0x3A, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x35, 0x0,
|
|
||||||
0x0, 0x0, 0x3C, 0x0, 0x0, 0x0, 0x2E, 0x0, 0x0, 0x0,
|
|
||||||
0x1F, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x3C, 0x0,
|
|
||||||
0x0, 0x0, 0x3B, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x4, 0x0,
|
|
||||||
0x7, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x0, 0x0, 0x9, 0x0,
|
|
||||||
0x0, 0x0, 0x94, 0x0, 0x5, 0x0, 0x6, 0x0, 0x0, 0x0,
|
|
||||||
0x40, 0x0, 0x0, 0x0, 0x3D, 0x0, 0x0, 0x0, 0x3F, 0x0,
|
|
||||||
0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x35, 0x0, 0x0, 0x0,
|
|
||||||
0x41, 0x0, 0x0, 0x0, 0x2E, 0x0, 0x0, 0x0, 0x26, 0x0,
|
|
||||||
0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x41, 0x0, 0x0, 0x0,
|
|
||||||
0x40, 0x0, 0x0, 0x0, 0x41, 0x0, 0x5, 0x0, 0x35, 0x0,
|
|
||||||
0x0, 0x0, 0x44, 0x0, 0x0, 0x0, 0x2E, 0x0, 0x0, 0x0,
|
|
||||||
0x43, 0x0, 0x0, 0x0, 0x3E, 0x0, 0x3, 0x0, 0x44, 0x0,
|
|
||||||
0x0, 0x0, 0x42, 0x0, 0x0, 0x0, 0xFD, 0x0, 0x1, 0x0,
|
|
||||||
0x38, 0x0, 0x1, 0x0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Runtime.InteropServices;
|
using Nerfed.Runtime.Video;
|
||||||
using Nerfed.Runtime.Video;
|
|
||||||
using RefreshCS;
|
using RefreshCS;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Nerfed.Runtime.Graphics;
|
namespace Nerfed.Runtime.Graphics;
|
||||||
|
|
||||||
@@ -13,12 +13,16 @@ public class GraphicsDevice : IDisposable
|
|||||||
public BackendFlags Backend { get; }
|
public BackendFlags Backend { get; }
|
||||||
public bool DebugMode { get; }
|
public bool DebugMode { get; }
|
||||||
|
|
||||||
|
// Built-in shaders
|
||||||
|
public Shader FullscreenVertexShader { get; private set; }
|
||||||
|
public Shader VideoFragmentShader { get; private set; }
|
||||||
|
public Shader TextVertexShader { get; private set; }
|
||||||
|
public Shader TextFragmentShader { get; private set; }
|
||||||
|
|
||||||
// Built-in video pipeline
|
// Built-in video pipeline
|
||||||
internal GraphicsPipeline VideoPipeline { get; }
|
internal GraphicsPipeline VideoPipeline { get; private set; }
|
||||||
|
|
||||||
// Built-in text shader info
|
// Built-in text shader info
|
||||||
public Shader TextVertexShader;
|
|
||||||
public Shader TextFragmentShader;
|
|
||||||
public VertexInputState TextVertexInputState { get; }
|
public VertexInputState TextVertexInputState { get; }
|
||||||
|
|
||||||
// Built-in samplers
|
// Built-in samplers
|
||||||
@@ -53,85 +57,21 @@ public class GraphicsDevice : IDisposable
|
|||||||
|
|
||||||
Backend = (BackendFlags)Refresh.Refresh_GetBackend(Handle);
|
Backend = (BackendFlags)Refresh.Refresh_GetBackend(Handle);
|
||||||
|
|
||||||
IEmbeddedShaders embeddedShaders;
|
TextVertexInputState = VertexInputState.CreateSingleBinding<FontVertex>();
|
||||||
switch (Backend)
|
|
||||||
{
|
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
|
||||||
case BackendFlags.Vulkan:
|
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
|
||||||
embeddedShaders = new EmbeddedShadersSpirV();
|
|
||||||
break;
|
fencePool = new FencePool(this);
|
||||||
case BackendFlags.D3D11:
|
commandBufferPool = new CommandBufferPool(this);
|
||||||
throw new NotImplementedException("D3D11 embedded shaders");
|
|
||||||
break;
|
|
||||||
case BackendFlags.Metal:
|
|
||||||
throw new NotImplementedException("Metal embedded shaders");
|
|
||||||
break;
|
|
||||||
default: throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader fullscreenVertShader;
|
internal void LoadDefaultPipelines()
|
||||||
Shader textVertShader;
|
|
||||||
Shader textFragShader;
|
|
||||||
Shader videoFragShader;
|
|
||||||
|
|
||||||
using (MemoryStream fullscreenVertStream = new MemoryStream(embeddedShaders.FullscreenVert))
|
|
||||||
{
|
{
|
||||||
fullscreenVertShader = new Shader(
|
FullscreenVertexShader = ResourceManager.Retain<Shader>("Shaders/Fullscreen.vert");
|
||||||
this,
|
VideoFragmentShader = ResourceManager.Retain<Shader>("Shaders/Video.frag");
|
||||||
fullscreenVertStream,
|
TextVertexShader = ResourceManager.Retain<Shader>("Shaders/Text.vert");
|
||||||
"main",
|
TextFragmentShader = ResourceManager.Retain<Shader>("Shaders/Text.frag");
|
||||||
new ShaderCreateInfo
|
|
||||||
{
|
|
||||||
ShaderStage = ShaderStage.Vertex,
|
|
||||||
ShaderFormat = embeddedShaders.ShaderFormat
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (MemoryStream videoYuv2RgbaFragStream = new MemoryStream(embeddedShaders.VideoYuv2RgbaFrag))
|
|
||||||
{
|
|
||||||
videoFragShader = new Shader(
|
|
||||||
this,
|
|
||||||
videoYuv2RgbaFragStream,
|
|
||||||
"main",
|
|
||||||
new ShaderCreateInfo
|
|
||||||
{
|
|
||||||
ShaderStage = ShaderStage.Fragment,
|
|
||||||
ShaderFormat = embeddedShaders.ShaderFormat,
|
|
||||||
SamplerCount = 3
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (MemoryStream textTransformVertStream = new MemoryStream(embeddedShaders.TextTransformVert))
|
|
||||||
{
|
|
||||||
textVertShader = new Shader(
|
|
||||||
this,
|
|
||||||
textTransformVertStream,
|
|
||||||
"main",
|
|
||||||
new ShaderCreateInfo
|
|
||||||
{
|
|
||||||
ShaderStage = ShaderStage.Vertex,
|
|
||||||
ShaderFormat = embeddedShaders.ShaderFormat,
|
|
||||||
UniformBufferCount = 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (MemoryStream textMsdfFragStream = new MemoryStream(embeddedShaders.TextMsdfFrag))
|
|
||||||
{
|
|
||||||
textFragShader = new Shader(
|
|
||||||
this,
|
|
||||||
textMsdfFragStream,
|
|
||||||
"main",
|
|
||||||
new ShaderCreateInfo
|
|
||||||
{
|
|
||||||
ShaderStage = ShaderStage.Fragment,
|
|
||||||
ShaderFormat = embeddedShaders.ShaderFormat,
|
|
||||||
SamplerCount = 1,
|
|
||||||
UniformBufferCount = 1
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoPipeline = new GraphicsPipeline(
|
VideoPipeline = new GraphicsPipeline(
|
||||||
this,
|
this,
|
||||||
@@ -144,25 +84,14 @@ public class GraphicsDevice : IDisposable
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
DepthStencilState = DepthStencilState.Disable,
|
DepthStencilState = DepthStencilState.Disable,
|
||||||
VertexShader = fullscreenVertShader,
|
VertexShader = FullscreenVertexShader,
|
||||||
FragmentShader = videoFragShader,
|
FragmentShader = VideoFragmentShader,
|
||||||
VertexInputState = VertexInputState.Empty,
|
VertexInputState = VertexInputState.Empty,
|
||||||
RasterizerState = RasterizerState.CCW_CullNone,
|
RasterizerState = RasterizerState.CCW_CullNone,
|
||||||
PrimitiveType = PrimitiveType.TriangleList,
|
PrimitiveType = PrimitiveType.TriangleList,
|
||||||
MultisampleState = MultisampleState.None
|
MultisampleState = MultisampleState.None
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
TextVertexShader = textVertShader;
|
|
||||||
TextFragmentShader = textFragShader;
|
|
||||||
|
|
||||||
TextVertexInputState = VertexInputState.CreateSingleBinding<FontVertex>();
|
|
||||||
|
|
||||||
PointSampler = new Sampler(this, SamplerCreateInfo.PointClamp);
|
|
||||||
LinearSampler = new Sampler(this, SamplerCreateInfo.LinearClamp);
|
|
||||||
|
|
||||||
fencePool = new FencePool(this);
|
|
||||||
commandBufferPool = new CommandBufferPool(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -443,6 +372,11 @@ public class GraphicsDevice : IDisposable
|
|||||||
|
|
||||||
resources.Clear();
|
resources.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResourceManager.Release(FullscreenVertexShader);
|
||||||
|
ResourceManager.Release(TextFragmentShader);
|
||||||
|
ResourceManager.Release(TextVertexShader);
|
||||||
|
ResourceManager.Release(VideoFragmentShader);
|
||||||
}
|
}
|
||||||
|
|
||||||
Refresh.Refresh_DestroyDevice(Handle);
|
Refresh.Refresh_DestroyDevice(Handle);
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Nerfed.Runtime.Graphics;
|
|
||||||
|
|
||||||
internal interface IEmbeddedShaders
|
|
||||||
{
|
|
||||||
ShaderFormat ShaderFormat { get; }
|
|
||||||
byte[] FullscreenVert { get; }
|
|
||||||
byte[] TextMsdfFrag { get; }
|
|
||||||
byte[] TextTransformVert { get; }
|
|
||||||
byte[] VideoYuv2RgbaFrag { get; }
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
using RefreshCS;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Nerfed.Runtime.Graphics;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shaders are used to create graphics pipelines.
|
|
||||||
/// Graphics pipelines take a vertex shader and a fragment shader.
|
|
||||||
/// </summary>
|
|
||||||
public class Shader : RefreshResource
|
|
||||||
{
|
|
||||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseShader;
|
|
||||||
|
|
||||||
public uint SamplerCount { get; }
|
|
||||||
public uint StorageTextureCount { get; }
|
|
||||||
public uint StorageBufferCount { get; }
|
|
||||||
public uint UniformBufferCount { get; }
|
|
||||||
|
|
||||||
public unsafe Shader(
|
|
||||||
GraphicsDevice device,
|
|
||||||
string filePath,
|
|
||||||
string entryPointName,
|
|
||||||
in ShaderCreateInfo shaderCreateInfo
|
|
||||||
) : base(device)
|
|
||||||
{
|
|
||||||
using FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
|
||||||
Handle = CreateFromStream(
|
|
||||||
device,
|
|
||||||
stream,
|
|
||||||
entryPointName,
|
|
||||||
shaderCreateInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
SamplerCount = shaderCreateInfo.SamplerCount;
|
|
||||||
StorageTextureCount = shaderCreateInfo.StorageTextureCount;
|
|
||||||
StorageBufferCount = shaderCreateInfo.StorageBufferCount;
|
|
||||||
UniformBufferCount = shaderCreateInfo.UniformBufferCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe Shader(
|
|
||||||
GraphicsDevice device,
|
|
||||||
Stream stream,
|
|
||||||
string entryPointName,
|
|
||||||
in ShaderCreateInfo shaderCreateInfo
|
|
||||||
) : base(device)
|
|
||||||
{
|
|
||||||
Handle = CreateFromStream(
|
|
||||||
device,
|
|
||||||
stream,
|
|
||||||
entryPointName,
|
|
||||||
shaderCreateInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
SamplerCount = shaderCreateInfo.SamplerCount;
|
|
||||||
StorageTextureCount = shaderCreateInfo.StorageTextureCount;
|
|
||||||
StorageBufferCount = shaderCreateInfo.StorageBufferCount;
|
|
||||||
UniformBufferCount = shaderCreateInfo.UniformBufferCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe IntPtr CreateFromStream(
|
|
||||||
GraphicsDevice device,
|
|
||||||
Stream stream,
|
|
||||||
string entryPointName,
|
|
||||||
in ShaderCreateInfo shaderCreateInfo
|
|
||||||
) {
|
|
||||||
void* bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
|
|
||||||
Span<byte> bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
|
|
||||||
stream.ReadExactly(bytecodeSpan);
|
|
||||||
|
|
||||||
Refresh.ShaderCreateInfo refreshShaderCreateInfo;
|
|
||||||
refreshShaderCreateInfo.CodeSize = (nuint) stream.Length;
|
|
||||||
refreshShaderCreateInfo.Code = (byte*) bytecodeBuffer;
|
|
||||||
refreshShaderCreateInfo.EntryPointName = entryPointName;
|
|
||||||
refreshShaderCreateInfo.Stage = (Refresh.ShaderStage) shaderCreateInfo.ShaderStage;
|
|
||||||
refreshShaderCreateInfo.Format = (Refresh.ShaderFormat) shaderCreateInfo.ShaderFormat;
|
|
||||||
refreshShaderCreateInfo.SamplerCount = shaderCreateInfo.SamplerCount;
|
|
||||||
refreshShaderCreateInfo.StorageTextureCount = shaderCreateInfo.StorageTextureCount;
|
|
||||||
refreshShaderCreateInfo.StorageBufferCount = shaderCreateInfo.StorageBufferCount;
|
|
||||||
refreshShaderCreateInfo.UniformBufferCount = shaderCreateInfo.UniformBufferCount;
|
|
||||||
|
|
||||||
IntPtr shaderModule = Refresh.Refresh_CreateShader(
|
|
||||||
device.Handle,
|
|
||||||
refreshShaderCreateInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
NativeMemory.Free(bytecodeBuffer);
|
|
||||||
return shaderModule;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Gui;
|
||||||
|
|
||||||
|
public static unsafe class GuiClipboard
|
||||||
|
{
|
||||||
|
private static IntPtr clipboard;
|
||||||
|
private static readonly Dictionary<Delegate, IntPtr> pinned = new Dictionary<Delegate, IntPtr>();
|
||||||
|
|
||||||
|
public static readonly IntPtr GetFnPtr = GetPointerTo(Get);
|
||||||
|
public static readonly IntPtr SetFnPtr = GetPointerTo(Set);
|
||||||
|
|
||||||
|
private static unsafe void Set(void* userdata, byte* text)
|
||||||
|
{
|
||||||
|
int len = 0; while (text[len] != 0) len++;
|
||||||
|
string str = Encoding.UTF8.GetString(text, len);
|
||||||
|
SDL2.SDL.SDL_SetClipboardText(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe byte* Get(void* userdata)
|
||||||
|
{
|
||||||
|
if (clipboard != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NativeMemory.Free((void*) clipboard);
|
||||||
|
clipboard = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
string str = SDL2.SDL.SDL_GetClipboardText();
|
||||||
|
int length = Encoding.UTF8.GetByteCount(str);
|
||||||
|
byte* bytes = (byte*)(clipboard = (nint)NativeMemory.Alloc((nuint)(length + 1)));
|
||||||
|
|
||||||
|
Encoding.UTF8.GetBytes(str, new Span<byte>(bytes, length));
|
||||||
|
bytes[length] = 0;
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops the delegate pointer from being collected
|
||||||
|
private static IntPtr GetPointerTo<T>(T fn) where T : Delegate
|
||||||
|
{
|
||||||
|
if (pinned.TryGetValue(fn, out nint ptr))
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = Marshal.GetFunctionPointerForDelegate(fn);
|
||||||
|
pinned.Add(fn, ptr);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,639 @@
|
|||||||
|
// ImGuiController with docking and viewport support for MoonWorks/Refresh.
|
||||||
|
// Based on the example im ImGui.NET and MoonWorksDearImGuiScaffold.
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
using Nerfed.Runtime.Graphics;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Gui;
|
||||||
|
|
||||||
|
public class GuiController : IDisposable
|
||||||
|
{
|
||||||
|
public event Action OnGui;
|
||||||
|
|
||||||
|
private readonly string shaderContentPath = Path.Combine(System.AppContext.BaseDirectory, "Content", "Shaders");
|
||||||
|
|
||||||
|
private readonly GraphicsDevice graphicsDevice;
|
||||||
|
private readonly Window mainWindow;
|
||||||
|
private readonly Color clearColor;
|
||||||
|
|
||||||
|
private readonly Platform_CreateWindow createWindow;
|
||||||
|
private readonly Platform_DestroyWindow destroyWindow;
|
||||||
|
private readonly Platform_GetWindowPos getWindowPos;
|
||||||
|
private readonly Platform_ShowWindow showWindow;
|
||||||
|
private readonly Platform_SetWindowPos setWindowPos;
|
||||||
|
private readonly Platform_SetWindowSize setWindowSize;
|
||||||
|
private readonly Platform_GetWindowSize getWindowSize;
|
||||||
|
private readonly Platform_SetWindowFocus setWindowFocus;
|
||||||
|
private readonly Platform_GetWindowFocus getWindowFocus;
|
||||||
|
private readonly Platform_GetWindowMinimized getWindowMinimized;
|
||||||
|
private readonly Platform_SetWindowTitle setWindowTitle;
|
||||||
|
|
||||||
|
private readonly ResourceUploader resourceUploader;
|
||||||
|
private readonly GraphicsPipeline imGuiPipeline;
|
||||||
|
private readonly Shader imGuiVertexShader;
|
||||||
|
private readonly Shader imGuiFragmentShader;
|
||||||
|
private readonly Sampler imGuiSampler;
|
||||||
|
private readonly GuiTextureStorage textureStorage = new GuiTextureStorage();
|
||||||
|
private readonly GuiViewportWindow mainViewportWindow;
|
||||||
|
|
||||||
|
private Texture fontTexture = null;
|
||||||
|
private uint vertexCount = 0;
|
||||||
|
private uint indexCount = 0;
|
||||||
|
private Graphics.Buffer imGuiVertexBuffer = null;
|
||||||
|
private Graphics.Buffer imGuiIndexBuffer = null;
|
||||||
|
private bool frameBegun = false;
|
||||||
|
|
||||||
|
public GuiController(GraphicsDevice graphicsDevice, Window mainWindow, Color clearColor, ImGuiConfigFlags configFlags = ImGuiConfigFlags.NavEnableKeyboard | ImGuiConfigFlags.DockingEnable | ImGuiConfigFlags.ViewportsEnable)
|
||||||
|
{
|
||||||
|
this.graphicsDevice = graphicsDevice;
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
this.clearColor = clearColor;
|
||||||
|
|
||||||
|
resourceUploader = new ResourceUploader(graphicsDevice);
|
||||||
|
|
||||||
|
ImGui.CreateContext();
|
||||||
|
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
io.DisplaySize = new Vector2(mainWindow.Width, mainWindow.Height);
|
||||||
|
io.DisplayFramebufferScale = Vector2.One;
|
||||||
|
|
||||||
|
imGuiVertexShader = ResourceManager.Retain<Shader>("Shaders/ImGui.vert");
|
||||||
|
imGuiFragmentShader = ResourceManager.Retain<Shader>("Shaders/ImGui.frag");
|
||||||
|
|
||||||
|
imGuiSampler = new Sampler(graphicsDevice, SamplerCreateInfo.LinearClamp);
|
||||||
|
|
||||||
|
imGuiPipeline = new GraphicsPipeline(
|
||||||
|
graphicsDevice,
|
||||||
|
new GraphicsPipelineCreateInfo
|
||||||
|
{
|
||||||
|
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
|
||||||
|
new ColorAttachmentDescription(
|
||||||
|
mainWindow.SwapchainFormat,
|
||||||
|
ColorAttachmentBlendState.NonPremultiplied
|
||||||
|
)
|
||||||
|
),
|
||||||
|
DepthStencilState = DepthStencilState.Disable,
|
||||||
|
MultisampleState = MultisampleState.None,
|
||||||
|
PrimitiveType = PrimitiveType.TriangleList,
|
||||||
|
RasterizerState = RasterizerState.CW_CullNone,
|
||||||
|
VertexInputState = VertexInputState.CreateSingleBinding<Position2DTextureColorVertex>(),
|
||||||
|
VertexShader = imGuiVertexShader,
|
||||||
|
FragmentShader = imGuiFragmentShader,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
BuildFontAtlas();
|
||||||
|
|
||||||
|
io.ConfigFlags = configFlags;
|
||||||
|
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
io.SetClipboardTextFn = GuiClipboard.SetFnPtr;
|
||||||
|
io.GetClipboardTextFn = GuiClipboard.GetFnPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
|
||||||
|
ImGuiViewportPtr mainViewport = platformIO.Viewports[0];
|
||||||
|
mainViewport.PlatformHandle = mainWindow.Handle;
|
||||||
|
mainViewportWindow = new GuiViewportWindow(graphicsDevice, mainViewport, mainWindow);
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
createWindow = CreateWindow;
|
||||||
|
destroyWindow = DestroyWindow;
|
||||||
|
getWindowPos = GetWindowPos;
|
||||||
|
showWindow = ShowWindow;
|
||||||
|
setWindowPos = SetWindowPos;
|
||||||
|
setWindowSize = SetWindowSize;
|
||||||
|
getWindowSize = GetWindowSize;
|
||||||
|
setWindowFocus = SetWindowFocus;
|
||||||
|
getWindowFocus = GetWindowFocus;
|
||||||
|
getWindowMinimized = GetWindowMinimized;
|
||||||
|
setWindowTitle = SetWindowTitle;
|
||||||
|
|
||||||
|
platformIO.Platform_CreateWindow = Marshal.GetFunctionPointerForDelegate(createWindow);
|
||||||
|
platformIO.Platform_DestroyWindow = Marshal.GetFunctionPointerForDelegate(destroyWindow);
|
||||||
|
platformIO.Platform_ShowWindow = Marshal.GetFunctionPointerForDelegate(showWindow);
|
||||||
|
platformIO.Platform_SetWindowPos = Marshal.GetFunctionPointerForDelegate(setWindowPos);
|
||||||
|
platformIO.Platform_SetWindowSize = Marshal.GetFunctionPointerForDelegate(setWindowSize);
|
||||||
|
platformIO.Platform_SetWindowFocus = Marshal.GetFunctionPointerForDelegate(setWindowFocus);
|
||||||
|
platformIO.Platform_GetWindowFocus = Marshal.GetFunctionPointerForDelegate(getWindowFocus);
|
||||||
|
platformIO.Platform_GetWindowMinimized = Marshal.GetFunctionPointerForDelegate(getWindowMinimized);
|
||||||
|
platformIO.Platform_SetWindowTitle = Marshal.GetFunctionPointerForDelegate(setWindowTitle);
|
||||||
|
|
||||||
|
ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowPos(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(getWindowPos));
|
||||||
|
ImGuiNative.ImGuiPlatformIO_Set_Platform_GetWindowSize(platformIO.NativePtr, Marshal.GetFunctionPointerForDelegate(getWindowSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
|
||||||
|
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
|
||||||
|
io.BackendFlags |= ImGuiBackendFlags.PlatformHasViewports;
|
||||||
|
io.BackendFlags |= ImGuiBackendFlags.RendererHasViewports;
|
||||||
|
|
||||||
|
UpdatePerFrameImGuiData(1.0 / 60.0);
|
||||||
|
ImGui.NewFrame();
|
||||||
|
frameBegun = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(double deltaTime)
|
||||||
|
{
|
||||||
|
if (frameBegun)
|
||||||
|
{
|
||||||
|
ImGui.Render();
|
||||||
|
ImGui.UpdatePlatformWindows();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePerFrameImGuiData(deltaTime);
|
||||||
|
UpdateInput();
|
||||||
|
UpdateCursor();
|
||||||
|
UpdateMonitors();
|
||||||
|
|
||||||
|
frameBegun = true;
|
||||||
|
ImGui.NewFrame();
|
||||||
|
|
||||||
|
OnGui?.Invoke();
|
||||||
|
|
||||||
|
ImGui.EndFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePerFrameImGuiData(double deltaSeconds)
|
||||||
|
{
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
io.DisplaySize = new Vector2(mainWindow.Width, mainWindow.Height);
|
||||||
|
io.DisplayFramebufferScale = new Vector2(1, 1);
|
||||||
|
io.DeltaTime = (float)deltaSeconds; // DeltaTime is in seconds.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateInput()
|
||||||
|
{
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
|
||||||
|
if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
|
||||||
|
{
|
||||||
|
// For viewports we use the global mouse position.
|
||||||
|
_ = SDL2.SDL.SDL_GetGlobalMouseState(out int x, out int y);
|
||||||
|
io.MousePos = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Without viewports we need to use the relative position.
|
||||||
|
//_ = SDL2.SDL.SDL_GetMouseState(out int x, out int y);
|
||||||
|
io.MousePos = Mouse.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
io.MouseDown[0] = Mouse.IsButtonDown(MouseButton.Left);
|
||||||
|
io.MouseDown[1] = Mouse.IsButtonDown(MouseButton.Right);
|
||||||
|
io.MouseDown[2] = Mouse.IsButtonDown(MouseButton.Middle);
|
||||||
|
|
||||||
|
io.MouseWheel = Mouse.GetWheel();
|
||||||
|
|
||||||
|
io.AddKeyEvent(ImGuiKey.A, Keyboard.IsKeyDown(Key.A));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Z, Keyboard.IsKeyDown(Key.Z));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Y, Keyboard.IsKeyDown(Key.Y));
|
||||||
|
io.AddKeyEvent(ImGuiKey.X, Keyboard.IsKeyDown(Key.X));
|
||||||
|
io.AddKeyEvent(ImGuiKey.C, Keyboard.IsKeyDown(Key.C));
|
||||||
|
io.AddKeyEvent(ImGuiKey.V, Keyboard.IsKeyDown(Key.V));
|
||||||
|
|
||||||
|
io.AddKeyEvent(ImGuiKey.Tab, Keyboard.IsKeyDown(Key.Tab));
|
||||||
|
io.AddKeyEvent(ImGuiKey.LeftArrow, Keyboard.IsKeyDown(Key.Left));
|
||||||
|
io.AddKeyEvent(ImGuiKey.RightArrow, Keyboard.IsKeyDown(Key.Right));
|
||||||
|
io.AddKeyEvent(ImGuiKey.UpArrow, Keyboard.IsKeyDown(Key.Up));
|
||||||
|
io.AddKeyEvent(ImGuiKey.DownArrow, Keyboard.IsKeyDown(Key.Down));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Enter, Keyboard.IsKeyDown(Key.Enter));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Escape, Keyboard.IsKeyDown(Key.Escape));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Delete, Keyboard.IsKeyDown(Key.Delete));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Backspace, Keyboard.IsKeyDown(Key.Backspace));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Home, Keyboard.IsKeyDown(Key.Home));
|
||||||
|
io.AddKeyEvent(ImGuiKey.End, Keyboard.IsKeyDown(Key.End));
|
||||||
|
io.AddKeyEvent(ImGuiKey.PageDown, Keyboard.IsKeyDown(Key.PageDown));
|
||||||
|
io.AddKeyEvent(ImGuiKey.PageUp, Keyboard.IsKeyDown(Key.PageUp));
|
||||||
|
io.AddKeyEvent(ImGuiKey.Insert, Keyboard.IsKeyDown(Key.Insert));
|
||||||
|
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModCtrl, Keyboard.IsKeyDown(Key.LeftControl) || Keyboard.IsKeyDown(Key.RightControl));
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModShift, Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModAlt, Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt));
|
||||||
|
io.AddKeyEvent(ImGuiKey.ModSuper, Keyboard.IsKeyDown(Key.LeftSuper) || Keyboard.IsKeyDown(Key.RightSuper));
|
||||||
|
|
||||||
|
ReadOnlySpan<char> input = Keyboard.GetTextInput();
|
||||||
|
if (!input.IsEmpty)
|
||||||
|
{
|
||||||
|
foreach (char c in input)
|
||||||
|
{
|
||||||
|
if (c == '\t')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
io.AddInputCharacter(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCursor()
|
||||||
|
{
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
|
||||||
|
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiMouseCursor imGuiCursor = ImGui.GetMouseCursor();
|
||||||
|
|
||||||
|
if (imGuiCursor == ImGuiMouseCursor.None || io.MouseDrawCursor)
|
||||||
|
{
|
||||||
|
_ = SDL2.SDL.SDL_ShowCursor(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nint sdlCursor = imGuiCursor switch
|
||||||
|
{
|
||||||
|
ImGuiMouseCursor.Arrow => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_ARROW),
|
||||||
|
ImGuiMouseCursor.TextInput => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_IBEAM),
|
||||||
|
ImGuiMouseCursor.ResizeAll => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZEALL),
|
||||||
|
ImGuiMouseCursor.ResizeNS => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENS),
|
||||||
|
ImGuiMouseCursor.ResizeEW => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZEWE),
|
||||||
|
ImGuiMouseCursor.ResizeNESW => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENESW),
|
||||||
|
ImGuiMouseCursor.ResizeNWSE => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SIZENWSE),
|
||||||
|
ImGuiMouseCursor.Hand => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_HAND),
|
||||||
|
ImGuiMouseCursor.NotAllowed => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NO),
|
||||||
|
_ => SDL2.SDL.SDL_CreateSystemCursor(SDL2.SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_ARROW),
|
||||||
|
};
|
||||||
|
SDL2.SDL.SDL_SetCursor(sdlCursor);
|
||||||
|
_ = SDL2.SDL.SDL_ShowCursor(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UpdateMonitors()
|
||||||
|
{
|
||||||
|
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
|
||||||
|
Marshal.FreeHGlobal(platformIO.NativePtr->Monitors.Data);
|
||||||
|
int videoDisplayCount = SDL2.SDL.SDL_GetNumVideoDisplays();
|
||||||
|
IntPtr data = Marshal.AllocHGlobal(Unsafe.SizeOf<ImGuiPlatformMonitor>() * videoDisplayCount);
|
||||||
|
platformIO.NativePtr->Monitors = new ImVector(videoDisplayCount, videoDisplayCount, data);
|
||||||
|
|
||||||
|
for (int i = 0; i < videoDisplayCount; i++)
|
||||||
|
{
|
||||||
|
_ = SDL2.SDL.SDL_GetDisplayUsableBounds(i, out SDL2.SDL.SDL_Rect usableBounds);
|
||||||
|
_ = SDL2.SDL.SDL_GetDisplayBounds(i, out SDL2.SDL.SDL_Rect bounds);
|
||||||
|
_ = SDL2.SDL.SDL_GetDisplayDPI(i, out float ddpi, out float hdpi, out float vdpi);
|
||||||
|
ImGuiPlatformMonitorPtr monitor = platformIO.Monitors[i];
|
||||||
|
float standardDpi = 96f; // Standard DPI typically used
|
||||||
|
monitor.DpiScale = hdpi / standardDpi;
|
||||||
|
monitor.MainPos = new Vector2(bounds.x, bounds.y);
|
||||||
|
monitor.MainSize = new Vector2(bounds.w, bounds.h);
|
||||||
|
monitor.WorkPos = new Vector2(usableBounds.x, usableBounds.y);
|
||||||
|
monitor.WorkSize = new Vector2(usableBounds.w, usableBounds.h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Render()
|
||||||
|
{
|
||||||
|
if (!frameBegun)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frameBegun = false;
|
||||||
|
|
||||||
|
if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.ViewportsEnable) != 0)
|
||||||
|
{
|
||||||
|
ImGui.Render();
|
||||||
|
ImGui.UpdatePlatformWindows();
|
||||||
|
ImGuiPlatformIOPtr platformIO = ImGui.GetPlatformIO();
|
||||||
|
|
||||||
|
for (int i = 0; i < platformIO.Viewports.Size; i++)
|
||||||
|
{
|
||||||
|
ImGuiViewportPtr vp = platformIO.Viewports[i];
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
|
||||||
|
if (!window.Window.Claimed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateImGuiBuffers(vp.DrawData);
|
||||||
|
|
||||||
|
CommandBuffer commandBuffer = graphicsDevice.AcquireCommandBuffer();
|
||||||
|
Texture swapchainTexture = commandBuffer.AcquireSwapchainTexture(window.Window);
|
||||||
|
|
||||||
|
if (swapchainTexture != null)
|
||||||
|
{
|
||||||
|
RenderCommandLists(commandBuffer, swapchainTexture, vp.DrawData);
|
||||||
|
graphicsDevice.Submit(commandBuffer);
|
||||||
|
//graphicsDevice.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Render();
|
||||||
|
|
||||||
|
if (!mainWindow.Claimed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImDrawDataPtr drawDataPtr = ImGui.GetDrawData();
|
||||||
|
UpdateImGuiBuffers(drawDataPtr);
|
||||||
|
|
||||||
|
CommandBuffer commandBuffer = graphicsDevice.AcquireCommandBuffer();
|
||||||
|
Texture swapchainTexture = commandBuffer.AcquireSwapchainTexture(mainWindow);
|
||||||
|
|
||||||
|
if (swapchainTexture != null)
|
||||||
|
{
|
||||||
|
RenderCommandLists(commandBuffer, swapchainTexture, drawDataPtr);
|
||||||
|
graphicsDevice.Submit(commandBuffer);
|
||||||
|
//graphicsDevice.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UpdateImGuiBuffers(ImDrawDataPtr drawDataPtr)
|
||||||
|
{
|
||||||
|
if (drawDataPtr.TotalVtxCount == 0 || drawDataPtr.CmdListsCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawDataPtr.TotalVtxCount > vertexCount)
|
||||||
|
{
|
||||||
|
imGuiVertexBuffer?.Dispose();
|
||||||
|
|
||||||
|
vertexCount = (uint)(drawDataPtr.TotalVtxCount * 1.5f);
|
||||||
|
imGuiVertexBuffer = Graphics.Buffer.Create<Position2DTextureColorVertex>(
|
||||||
|
graphicsDevice,
|
||||||
|
BufferUsageFlags.Vertex,
|
||||||
|
vertexCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawDataPtr.TotalIdxCount > indexCount)
|
||||||
|
{
|
||||||
|
imGuiIndexBuffer?.Dispose();
|
||||||
|
|
||||||
|
indexCount = (uint)(drawDataPtr.TotalIdxCount * 1.5f);
|
||||||
|
imGuiIndexBuffer = Graphics.Buffer.Create<ushort>(
|
||||||
|
graphicsDevice,
|
||||||
|
BufferUsageFlags.Index,
|
||||||
|
indexCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint vertexOffset = 0;
|
||||||
|
uint indexOffset = 0;
|
||||||
|
|
||||||
|
for (int n = 0; n < drawDataPtr.CmdListsCount; n++)
|
||||||
|
{
|
||||||
|
ImDrawListPtr cmdList = drawDataPtr.CmdLists[n];
|
||||||
|
|
||||||
|
resourceUploader.SetBufferData(
|
||||||
|
imGuiVertexBuffer,
|
||||||
|
vertexOffset,
|
||||||
|
new Span<Position2DTextureColorVertex>(cmdList.VtxBuffer.Data.ToPointer(), cmdList.VtxBuffer.Size),
|
||||||
|
n == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
resourceUploader.SetBufferData(
|
||||||
|
imGuiIndexBuffer,
|
||||||
|
indexOffset,
|
||||||
|
new Span<ushort>(cmdList.IdxBuffer.Data.ToPointer(), cmdList.IdxBuffer.Size),
|
||||||
|
n == 0
|
||||||
|
);
|
||||||
|
|
||||||
|
vertexOffset += (uint)cmdList.VtxBuffer.Size;
|
||||||
|
indexOffset += (uint)cmdList.IdxBuffer.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceUploader.Upload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderCommandLists(CommandBuffer commandBuffer, Texture renderTexture, ImDrawDataPtr drawDataPtr)
|
||||||
|
{
|
||||||
|
Vector2 pos = drawDataPtr.DisplayPos;
|
||||||
|
|
||||||
|
RenderPass renderPass = commandBuffer.BeginRenderPass(
|
||||||
|
new ColorAttachmentInfo(renderTexture, false, clearColor)
|
||||||
|
);
|
||||||
|
|
||||||
|
renderPass.BindGraphicsPipeline(imGuiPipeline);
|
||||||
|
|
||||||
|
// It is possible that the buffers are null (for example nothing is in our main windows viewport, then we exixt early but still clear it).
|
||||||
|
if (imGuiVertexBuffer == null || imGuiIndexBuffer == null)
|
||||||
|
{
|
||||||
|
commandBuffer.EndRenderPass(renderPass);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix4x4 projectionMatrix = Matrix4x4.CreateOrthographicOffCenter(
|
||||||
|
pos.X,
|
||||||
|
pos.X + drawDataPtr.DisplaySize.X,
|
||||||
|
pos.Y + drawDataPtr.DisplaySize.Y,
|
||||||
|
pos.Y,
|
||||||
|
-1.0f,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
TransformVertexUniform vertexUniform = new TransformVertexUniform(projectionMatrix);
|
||||||
|
|
||||||
|
renderPass.BindVertexBuffer(imGuiVertexBuffer);
|
||||||
|
renderPass.BindIndexBuffer(imGuiIndexBuffer, IndexElementSize.Sixteen);
|
||||||
|
|
||||||
|
commandBuffer.PushVertexUniformData(in vertexUniform);
|
||||||
|
|
||||||
|
uint vertexOffset = 0;
|
||||||
|
uint indexOffset = 0;
|
||||||
|
|
||||||
|
for (int n = 0; n < drawDataPtr.CmdListsCount; n++)
|
||||||
|
{
|
||||||
|
ImDrawListPtr cmdList = drawDataPtr.CmdLists[n];
|
||||||
|
|
||||||
|
for (int cmdIndex = 0; cmdIndex < cmdList.CmdBuffer.Size; cmdIndex++)
|
||||||
|
{
|
||||||
|
ImDrawCmdPtr drawCmd = cmdList.CmdBuffer[cmdIndex];
|
||||||
|
|
||||||
|
Texture texture = textureStorage.GetTexture(drawCmd.TextureId);
|
||||||
|
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
Log.Error("Texture or drawCmd.TextureId became null. Fit it!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPass.BindFragmentSampler(new TextureSamplerBinding(texture, imGuiSampler));
|
||||||
|
|
||||||
|
float width = drawCmd.ClipRect.Z - (int)drawCmd.ClipRect.X;
|
||||||
|
float height = drawCmd.ClipRect.W - (int)drawCmd.ClipRect.Y;
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPass.SetScissor(
|
||||||
|
new Rect(
|
||||||
|
(int)drawCmd.ClipRect.X - (int)pos.X,
|
||||||
|
(int)drawCmd.ClipRect.Y - (int)pos.Y,
|
||||||
|
(int)drawCmd.ClipRect.Z - (int)drawCmd.ClipRect.X,
|
||||||
|
(int)drawCmd.ClipRect.W - (int)drawCmd.ClipRect.Y
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
renderPass.DrawIndexedPrimitives(vertexOffset, indexOffset, drawCmd.ElemCount / 3);
|
||||||
|
|
||||||
|
indexOffset += drawCmd.ElemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertexOffset += (uint)cmdList.VtxBuffer.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBuffer.EndRenderPass(renderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Resources
|
||||||
|
private unsafe void BuildFontAtlas()
|
||||||
|
{
|
||||||
|
ResourceUploader resourceUploader = new ResourceUploader(graphicsDevice);
|
||||||
|
|
||||||
|
ImGuiIOPtr io = ImGui.GetIO();
|
||||||
|
|
||||||
|
io.Fonts.GetTexDataAsRGBA32(
|
||||||
|
out nint pixelData,
|
||||||
|
out int width,
|
||||||
|
out int height,
|
||||||
|
out int bytesPerPixel
|
||||||
|
);
|
||||||
|
|
||||||
|
Texture fontTexture = resourceUploader.CreateTexture2D(
|
||||||
|
new Span<byte>((void*)pixelData, width * height * bytesPerPixel),
|
||||||
|
(uint)width,
|
||||||
|
(uint)height
|
||||||
|
);
|
||||||
|
|
||||||
|
resourceUploader.Upload();
|
||||||
|
resourceUploader.Dispose();
|
||||||
|
|
||||||
|
io.Fonts.SetTexID(fontTexture.Handle);
|
||||||
|
io.Fonts.ClearTexData();
|
||||||
|
|
||||||
|
textureStorage.Add(fontTexture); // <-- The fontTexture seems to get lost after some time (CG?).
|
||||||
|
this.fontTexture = fontTexture; // <-- So we also keep a reference to make sure it doesn't happen.
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Window
|
||||||
|
private void CreateWindow(ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
GuiViewportWindow window = new GuiViewportWindow(graphicsDevice, vp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DestroyWindow(ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
window.Dispose();
|
||||||
|
|
||||||
|
vp.PlatformUserData = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowWindow(ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_ShowWindow(window.Window.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void GetWindowPos(ImGuiViewportPtr vp, Vector2* outPos)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_GetWindowPosition(window.Window.Handle, out int x, out int y);
|
||||||
|
*outPos = new Vector2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetWindowPos(ImGuiViewportPtr vp, Vector2 pos)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_SetWindowPosition(window.Window.Handle, (int)pos.X, (int)pos.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetWindowSize(ImGuiViewportPtr vp, Vector2 size)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_SetWindowSize(window.Window.Handle, (int)size.X, (int)size.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void GetWindowSize(ImGuiViewportPtr vp, Vector2* outSize)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_GetWindowSize(window.Window.Handle, out int w, out int h);
|
||||||
|
*outSize = new Vector2(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetWindowFocus(ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
//SDL2.SDL.SDL_SetWindowInputFocus(window.Handle);
|
||||||
|
SDL2.SDL.SDL_RaiseWindow(window.Window.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte GetWindowFocus(ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return (byte)0;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_WindowFlags flags = (SDL2.SDL.SDL_WindowFlags)SDL2.SDL.SDL_GetWindowFlags(window.Window.Handle);
|
||||||
|
|
||||||
|
return (flags & SDL2.SDL.SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS) != 0 ? (byte)1 : (byte)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte GetWindowMinimized(ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return 0;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
SDL2.SDL.SDL_WindowFlags flags = (SDL2.SDL.SDL_WindowFlags)SDL2.SDL.SDL_GetWindowFlags(window.Window.Handle);
|
||||||
|
|
||||||
|
return (flags & SDL2.SDL.SDL_WindowFlags.SDL_WINDOW_MINIMIZED) != 0 ? (byte)1 : (byte)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void SetWindowTitle(ImGuiViewportPtr vp, IntPtr title)
|
||||||
|
{
|
||||||
|
if (vp.PlatformUserData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
GuiViewportWindow window = (GuiViewportWindow)GCHandle.FromIntPtr(vp.PlatformUserData).Target;
|
||||||
|
byte* titlePtr = (byte*)title;
|
||||||
|
int count = 0;
|
||||||
|
while (titlePtr[count] != 0)
|
||||||
|
{
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
SDL2.SDL.SDL_SetWindowTitle(window.Window.Handle, System.Text.Encoding.ASCII.GetString(titlePtr, count));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
fontTexture?.Dispose();
|
||||||
|
imGuiVertexBuffer?.Dispose();
|
||||||
|
imGuiIndexBuffer?.Dispose();
|
||||||
|
ResourceManager.Release(imGuiVertexShader);
|
||||||
|
ResourceManager.Release(imGuiFragmentShader);
|
||||||
|
imGuiPipeline?.Dispose();
|
||||||
|
imGuiSampler?.Dispose();
|
||||||
|
resourceUploader?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Nerfed.Runtime.Graphics;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Gui;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct Position2DTextureColorVertex(Vector2 position, Vector2 texcoord, Color color) : IVertexType
|
||||||
|
{
|
||||||
|
public Vector2 Position = position;
|
||||||
|
public Vector2 TexCoord = texcoord;
|
||||||
|
public Color Color = color;
|
||||||
|
|
||||||
|
public static VertexElementFormat[] Formats { get; } = new VertexElementFormat[3]
|
||||||
|
{
|
||||||
|
VertexElementFormat.Vector2,
|
||||||
|
VertexElementFormat.Vector2,
|
||||||
|
VertexElementFormat.Color
|
||||||
|
};
|
||||||
|
|
||||||
|
public static uint[] Offsets { get; } = new uint[3]
|
||||||
|
{
|
||||||
|
0,
|
||||||
|
8,
|
||||||
|
16
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct TransformVertexUniform(Matrix4x4 projectionMatrix)
|
||||||
|
{
|
||||||
|
public Matrix4x4 ProjectionMatrix = projectionMatrix;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Nerfed.Runtime.Graphics;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Gui;
|
||||||
|
|
||||||
|
public class GuiTextureStorage
|
||||||
|
{
|
||||||
|
private readonly Dictionary<IntPtr, WeakReference<Texture>> pointerToTexture = new Dictionary<IntPtr, WeakReference<Texture>>();
|
||||||
|
|
||||||
|
public IntPtr Add(Texture texture)
|
||||||
|
{
|
||||||
|
if (!pointerToTexture.ContainsKey(texture.Handle))
|
||||||
|
{
|
||||||
|
pointerToTexture.Add(texture.Handle, new WeakReference<Texture>(texture));
|
||||||
|
}
|
||||||
|
return texture.Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Texture GetTexture(IntPtr pointer)
|
||||||
|
{
|
||||||
|
if (!pointerToTexture.TryGetValue(pointer, out WeakReference<Texture> value))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
WeakReference<Texture> result = value;
|
||||||
|
|
||||||
|
if (!result.TryGetTarget(out Texture texture))
|
||||||
|
{
|
||||||
|
pointerToTexture.Remove(pointer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using Nerfed.Runtime.Graphics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Gui
|
||||||
|
{
|
||||||
|
internal class GuiViewportWindow
|
||||||
|
{
|
||||||
|
public Window Window => window;
|
||||||
|
|
||||||
|
private readonly GCHandle gcHandle;
|
||||||
|
private readonly GraphicsDevice graphicsDevice;
|
||||||
|
private readonly ImGuiViewportPtr vp;
|
||||||
|
private readonly Window window;
|
||||||
|
|
||||||
|
public GuiViewportWindow(GraphicsDevice graphicsDevice, ImGuiViewportPtr vp, Window window)
|
||||||
|
{
|
||||||
|
this.graphicsDevice = graphicsDevice;
|
||||||
|
this.vp = vp;
|
||||||
|
this.window = window;
|
||||||
|
|
||||||
|
gcHandle = GCHandle.Alloc(this);
|
||||||
|
|
||||||
|
if (!window.Claimed)
|
||||||
|
{
|
||||||
|
graphicsDevice.ClaimWindow(window, SwapchainComposition.SDR, PresentMode.Immediate); // What PresentMode do we need?
|
||||||
|
}
|
||||||
|
|
||||||
|
vp.PlatformUserData = (IntPtr)gcHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GuiViewportWindow(GraphicsDevice graphicsDevice, ImGuiViewportPtr vp)
|
||||||
|
{
|
||||||
|
this.graphicsDevice = graphicsDevice;
|
||||||
|
this.vp = vp;
|
||||||
|
|
||||||
|
gcHandle = GCHandle.Alloc(this);
|
||||||
|
|
||||||
|
// TODO: Handle all flags.
|
||||||
|
ScreenMode screenMode = ScreenMode.Windowed;
|
||||||
|
bool systemResizable = true;
|
||||||
|
|
||||||
|
if ((vp.Flags & ImGuiViewportFlags.NoDecoration) == ImGuiViewportFlags.NoDecoration)
|
||||||
|
{
|
||||||
|
screenMode = ScreenMode.WindowedBorderless;
|
||||||
|
systemResizable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowCreateInfo info = new WindowCreateInfo("Window Title", (uint)vp.Pos.X, (uint)vp.Pos.Y, screenMode, systemResizable, false);
|
||||||
|
|
||||||
|
window = new Window(graphicsDevice, info);
|
||||||
|
graphicsDevice.ClaimWindow(window, SwapchainComposition.SDR, PresentMode.Immediate); // What PresentMode do we need?
|
||||||
|
|
||||||
|
window.OnMovedEvent += HandleOnMovedEvent;
|
||||||
|
window.OnResizedEvent += HandleOnResizedEvent;
|
||||||
|
window.OnCloseEvent += HandleOnCloseEvent;
|
||||||
|
|
||||||
|
vp.PlatformUserData = (IntPtr)gcHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
window.OnMovedEvent -= HandleOnMovedEvent;
|
||||||
|
window.OnResizedEvent -= HandleOnResizedEvent;
|
||||||
|
window.OnCloseEvent -= HandleOnCloseEvent;
|
||||||
|
|
||||||
|
graphicsDevice.UnclaimWindow(window);
|
||||||
|
window.Dispose();
|
||||||
|
gcHandle.Free();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOnMovedEvent(Window window, int x, int y)
|
||||||
|
{
|
||||||
|
vp.PlatformRequestMove = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOnResizedEvent(Window window, uint w, uint h)
|
||||||
|
{
|
||||||
|
vp.PlatformRequestResize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleOnCloseEvent(Window window)
|
||||||
|
{
|
||||||
|
vp.PlatformRequestClose = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Numerics;
|
using SDL2;
|
||||||
using SDL2;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Nerfed.Runtime;
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
@@ -65,6 +65,8 @@ internal static class Mouse
|
|||||||
|
|
||||||
internal static void Update()
|
internal static void Update()
|
||||||
{
|
{
|
||||||
|
wheelX = 0;
|
||||||
|
wheelY = 0;
|
||||||
Array.Copy(buttonStates, lastButtonStates, buttonStates.Length);
|
Array.Copy(buttonStates, lastButtonStates, buttonStates.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +107,8 @@ internal static class Mouse
|
|||||||
|
|
||||||
private static void ProcessWheelEvent(ref SDL.SDL_MouseWheelEvent ev)
|
private static void ProcessWheelEvent(ref SDL.SDL_MouseWheelEvent ev)
|
||||||
{
|
{
|
||||||
wheelX += ev.x;
|
wheelX = ev.x;
|
||||||
wheelY += ev.y;
|
wheelY = ev.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessMotionEvent(ref SDL.SDL_MouseMotionEvent ev)
|
private static void ProcessMotionEvent(ref SDL.SDL_MouseMotionEvent ev)
|
||||||
|
|||||||
Submodule
+1
Submodule Nerfed.Runtime/Libraries/ImGui.NET added at ae493d92a3
Submodule
+1
Submodule Nerfed.Runtime/Libraries/MoonTools.ECS added at 76b18a6ba9
@@ -1,26 +1,44 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DefaultItemExcludes>$(DefaultItemExcludes);Libraries\**\*</DefaultItemExcludes>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="Libraries\SDL2CS\src\SDL2.cs" />
|
|
||||||
<Compile Include="Libraries\RefreshCS\RefreshCS.cs" />
|
|
||||||
<Compile Include="Libraries\FAudio\csharp\FAudio.cs" />
|
|
||||||
<Compile Include="Libraries\WellspringCS\WellspringCS.cs" />
|
|
||||||
<Compile Include="Libraries\dav1dfile\csharp\dav1dfile.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<PublishAot>true</PublishAot>
|
<PublishAot>true</PublishAot>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<Configurations>Debug;Test;Release</Configurations>
|
||||||
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Import Project=".\CopyLibs.targets" />
|
<PropertyGroup>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);Libraries\**\*</DefaultItemExcludes>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
|
||||||
|
<DefineConstants>TRACE;LOG_INFO;PROFILING</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Test|x64' ">
|
||||||
|
<DefineConstants>TRACE;LOG_ERROR;PROFILING</DefineConstants>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
|
||||||
|
<DefineConstants>TRACE;LOG_ERROR</DefineConstants>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Libraries\FAudio\csharp\FAudio.cs" />
|
||||||
|
<Compile Include="Libraries\ImGui.NET\src\ImGui.NET\**\*.cs" />
|
||||||
|
<Compile Include="Libraries\MoonTools.ECS\src\**\*.cs" />
|
||||||
|
<Compile Include="Libraries\RefreshCS\RefreshCS.cs" />
|
||||||
|
<Compile Include="Libraries\SDL2CS\src\SDL2.cs" />
|
||||||
|
<Compile Include="Libraries\WellspringCS\WellspringCS.cs" />
|
||||||
|
<Compile Include="Libraries\dav1dfile\csharp\dav1dfile.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resource/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
+146
-9
@@ -1,29 +1,166 @@
|
|||||||
using System.Diagnostics;
|
using System.Collections.Concurrent;
|
||||||
using System.Reflection;
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Nerfed.Runtime;
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
public struct ProfilerScope : IDisposable
|
public struct ProfilerScope : IDisposable
|
||||||
{
|
{
|
||||||
public ProfilerScope(string label) {
|
public ProfilerScope(string label)
|
||||||
|
{
|
||||||
Profiler.BeginSample(label);
|
Profiler.BeginSample(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose()
|
||||||
|
{
|
||||||
Profiler.EndSample();
|
Profiler.EndSample();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Profiler
|
public static class Profiler
|
||||||
{
|
{
|
||||||
[Conditional("PROFILER")]
|
public class Frame(uint frameCount)
|
||||||
public static void BeginSample(string label) {
|
{
|
||||||
|
public uint FrameCount { get; } = frameCount;
|
||||||
|
public long StartTime { get; } = Stopwatch.GetTimestamp();
|
||||||
|
public long EndTime { get; private set; }
|
||||||
|
|
||||||
|
// Use a concurrent list to collect all thread root nodes per frame.
|
||||||
|
public ConcurrentBag<ScopeNode> RootNodes = new ConcurrentBag<ScopeNode>();
|
||||||
|
|
||||||
|
internal void End()
|
||||||
|
{
|
||||||
|
EndTime = Stopwatch.GetTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Conditional("PROFILER")]
|
public double ElapsedMilliseconds()
|
||||||
public static void EndSample() {
|
{
|
||||||
|
long elapsedTicks = EndTime - StartTime;
|
||||||
|
return ((double)(elapsedTicks * 1000)) / Stopwatch.Frequency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScopeNode(string label)
|
||||||
|
{
|
||||||
|
public string Label { get; } = label;
|
||||||
|
public long StartTime { get; private set; } = Stopwatch.GetTimestamp(); // Start time in ticks
|
||||||
|
public long EndTime { get; private set; }
|
||||||
|
public int ManagedThreadId { get; } = Environment.CurrentManagedThreadId;
|
||||||
|
public List<ScopeNode> Children { get; } = new List<ScopeNode>();
|
||||||
|
|
||||||
|
internal void End()
|
||||||
|
{
|
||||||
|
EndTime = Stopwatch.GetTimestamp(); // End time in ticks
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ElapsedMilliseconds()
|
||||||
|
{
|
||||||
|
return ((double)(EndTime - StartTime)) * 1000 / Stopwatch.Frequency; // Convert ticks to ms
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a child node (used for nested scopes)
|
||||||
|
internal ScopeNode AddChild(string label)
|
||||||
|
{
|
||||||
|
ScopeNode child = new ScopeNode(label);
|
||||||
|
Children.Add(child);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int maxFrames = 128;
|
||||||
|
|
||||||
|
public static bool IsRecording { get; private set; } = true;
|
||||||
|
|
||||||
|
// Store only the last x amount of frames in memory.
|
||||||
|
public static readonly BoundedQueue<Frame> Frames = new(maxFrames);
|
||||||
|
|
||||||
|
// Use ThreadLocal to store a stack of ScopeNodes per thread and enable tracking of thread-local values.
|
||||||
|
private static readonly ThreadLocal<Stack<ScopeNode>> threadLocalScopes = new ThreadLocal<Stack<ScopeNode>>(() => new Stack<ScopeNode>(), true);
|
||||||
|
|
||||||
|
private static Frame currentFrame = null;
|
||||||
|
private static uint frameCount = 0;
|
||||||
|
|
||||||
|
public static void SetActive(bool isRecording)
|
||||||
|
{
|
||||||
|
IsRecording = isRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("PROFILING")]
|
||||||
|
public static void BeginFrame()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrame = new Frame(frameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("PROFILING")]
|
||||||
|
public static void EndFrame()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Stack<ScopeNode> scopes in threadLocalScopes.Values)
|
||||||
|
{
|
||||||
|
if (scopes.Count > 0)
|
||||||
|
{
|
||||||
|
// Pop the left over root nodes.
|
||||||
|
ScopeNode currentScope = scopes.Pop();
|
||||||
|
currentScope.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the thread-local stack to ensure it's empty for the next frame.
|
||||||
|
scopes.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFrame.End();
|
||||||
|
Frames.Enqueue(currentFrame);
|
||||||
|
frameCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("PROFILING")]
|
||||||
|
public static void BeginSample(string label)
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stack<ScopeNode> scopes = threadLocalScopes.Value; // Get the stack for the current thread
|
||||||
|
|
||||||
|
if (scopes.Count == 0)
|
||||||
|
{
|
||||||
|
// First scope for this thread (new root for this thread)
|
||||||
|
ScopeNode rootScopeNode = new ScopeNode($"Thread-{Environment.CurrentManagedThreadId}");
|
||||||
|
scopes.Push(rootScopeNode);
|
||||||
|
currentFrame.RootNodes.Add(rootScopeNode); // Add root node to the frame list
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new child under the current top of the stack
|
||||||
|
ScopeNode newScope = scopes.Peek().AddChild(label);
|
||||||
|
|
||||||
|
scopes.Push(newScope); // Push new scope to the thread's stack
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("PROFILING")]
|
||||||
|
public static void EndSample()
|
||||||
|
{
|
||||||
|
if (!IsRecording)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stack<ScopeNode> scopes = threadLocalScopes.Value;
|
||||||
|
|
||||||
|
if (scopes.Count > 0)
|
||||||
|
{
|
||||||
|
// Only pop if this is not the root node.
|
||||||
|
//ScopeNode currentScope = scopes.Count > 1 ? scopes.Pop() : scopes.Peek();
|
||||||
|
ScopeNode currentScope = scopes.Pop();
|
||||||
|
currentScope.End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
|
public static class ProfilerVisualizer
|
||||||
|
{
|
||||||
|
private const float barHeight = 20f;
|
||||||
|
private const float barPadding = 2f;
|
||||||
|
|
||||||
|
// Render the flame graph across multiple threads
|
||||||
|
public static void RenderFlameGraph(Profiler.Frame frame)
|
||||||
|
{
|
||||||
|
if (frame == null) return;
|
||||||
|
if (frame.RootNodes == null) return;
|
||||||
|
|
||||||
|
// Calculate the total timeline duration (max end time across all nodes)
|
||||||
|
double totalDuration = frame.EndTime - frame.StartTime;
|
||||||
|
double startTime = frame.StartTime;
|
||||||
|
|
||||||
|
// Precompute the maximum depth for each thread's call stack
|
||||||
|
Dictionary<int, int> threadMaxDepths = new Dictionary<int, int>();
|
||||||
|
foreach (IGrouping<int, Profiler.ScopeNode> threadGroup in frame.RootNodes.GroupBy(node => node.ManagedThreadId))
|
||||||
|
{
|
||||||
|
int maxDepth = 0;
|
||||||
|
foreach (Profiler.ScopeNode rootNode in threadGroup)
|
||||||
|
{
|
||||||
|
maxDepth = Math.Max(maxDepth, GetMaxDepth(rootNode, 0));
|
||||||
|
}
|
||||||
|
threadMaxDepths[threadGroup.Key] = maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a child window to support scrolling
|
||||||
|
ImGui.BeginChild("FlameGraph", new Vector2(0, 64), ImGuiChildFlags.Border | ImGuiChildFlags.ResizeY, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
||||||
|
|
||||||
|
ImDrawListPtr drawList = ImGui.GetWindowDrawList();
|
||||||
|
Vector2 windowPos = ImGui.GetCursorScreenPos();
|
||||||
|
|
||||||
|
// Sort nodes by ThreadID, ensuring main thread (Thread ID 1) is on top
|
||||||
|
IOrderedEnumerable<IGrouping<int, Profiler.ScopeNode>> threadGroups = frame.RootNodes.GroupBy(node => node.ManagedThreadId).OrderBy(g => g.Key);
|
||||||
|
|
||||||
|
// Initial Y position for drawing
|
||||||
|
float baseY = windowPos.Y;
|
||||||
|
bool alternate = false;
|
||||||
|
float contentWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
|
||||||
|
// Draw each thread's flame graph row by row
|
||||||
|
foreach (IGrouping<int, Profiler.ScopeNode> threadGroup in threadGroups)
|
||||||
|
{
|
||||||
|
int threadId = threadGroup.Key;
|
||||||
|
|
||||||
|
// Compute the base Y position for this thread
|
||||||
|
float threadBaseY = baseY;
|
||||||
|
|
||||||
|
// Calculate the maximum height for this thread's flame graph
|
||||||
|
float threadHeight = (threadMaxDepths[threadId] + 1) * (barHeight + barPadding);
|
||||||
|
|
||||||
|
// Draw the alternating background for each thread row
|
||||||
|
uint backgroundColor = ImGui.ColorConvertFloat4ToU32(alternate ? new Vector4(0.2f, 0.2f, 0.2f, 1f) : new Vector4(0.1f, 0.1f, 0.1f, 1f));
|
||||||
|
drawList.AddRectFilled(new Vector2(windowPos.X, threadBaseY), new Vector2(windowPos.X + contentWidth, threadBaseY + threadHeight), backgroundColor);
|
||||||
|
|
||||||
|
alternate = !alternate;
|
||||||
|
|
||||||
|
// Draw each root node in the group (one per thread)
|
||||||
|
foreach (Profiler.ScopeNode rootNode in threadGroup)
|
||||||
|
{
|
||||||
|
RenderNode(drawList, rootNode, startTime, totalDuration, windowPos.X, threadBaseY, 0, contentWidth, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next thread's row (max depth * height per level)
|
||||||
|
baseY += (threadMaxDepths[threadId] + 1) * (barHeight + barPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that ImGui knows the size of the content.
|
||||||
|
ImGui.Dummy(new Vector2(contentWidth, baseY));
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderNode(ImDrawListPtr drawList, Profiler.ScopeNode node, double startTime, double totalDuration, float startX, float baseY, int depth, float contentWidth, bool alternate)
|
||||||
|
{
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
double nodeStartTime = node.StartTime - startTime;
|
||||||
|
double nodeEndTime = node.EndTime - startTime;
|
||||||
|
double nodeDuration = nodeEndTime - nodeStartTime;
|
||||||
|
|
||||||
|
// Calculate the position and width of the bar based on time
|
||||||
|
float xPos = (float)(startX + (nodeStartTime / totalDuration) * contentWidth);
|
||||||
|
float width = (float)((nodeDuration / totalDuration) * contentWidth);
|
||||||
|
|
||||||
|
// Calculate the Y position based on depth
|
||||||
|
float yPos = baseY + (depth * (barHeight + barPadding)) + (barPadding * 0.5f);
|
||||||
|
|
||||||
|
// Define the rectangle bounds for the node
|
||||||
|
Vector2 min = new Vector2(xPos, yPos);
|
||||||
|
Vector2 max = new Vector2(xPos + width, yPos + barHeight);
|
||||||
|
|
||||||
|
// Define color.
|
||||||
|
Vector4 barColor = alternate ? new Vector4(0.4f, 0.6f, 0.9f, 1f) : new Vector4(0.4f, 0.5f, 0.8f, 1f);
|
||||||
|
Vector4 textColor = new Vector4(1f, 1f, 1f, 1f);
|
||||||
|
|
||||||
|
if (depth != 0)
|
||||||
|
{
|
||||||
|
// Draw the bar for the node (colored based on thread depth)
|
||||||
|
drawList.AddRectFilled(min, max, ImGui.ColorConvertFloat4ToU32(barColor));
|
||||||
|
|
||||||
|
// Draw the label if it fits inside the bar
|
||||||
|
string label = $"{node.Label} ({node.ElapsedMilliseconds():0.000} ms)";
|
||||||
|
if (width > ImGui.CalcTextSize(label).X)
|
||||||
|
{
|
||||||
|
drawList.AddText(new Vector2(xPos + barPadding, yPos + barPadding), ImGui.ColorConvertFloat4ToU32(textColor), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tooltip on hover
|
||||||
|
if (ImGui.IsMouseHoveringRect(min, max))
|
||||||
|
{
|
||||||
|
// Show tooltip when hovering over the node
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
ImGui.Text($"{node.Label}");
|
||||||
|
ImGui.Text($"{node.ElapsedMilliseconds():0.000} ms");
|
||||||
|
ImGui.Text($"{node.ManagedThreadId}");
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Aka root node.
|
||||||
|
string label = $"{node.Label}";
|
||||||
|
drawList.AddText(new Vector2(startX + barPadding, yPos + barPadding), ImGui.ColorConvertFloat4ToU32(textColor), label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw each child node under this node
|
||||||
|
foreach (Profiler.ScopeNode child in node.Children)
|
||||||
|
{
|
||||||
|
alternate = !alternate;
|
||||||
|
RenderNode(drawList, child, startTime, totalDuration, startX, baseY, depth + 1, contentWidth, alternate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive function to calculate the maximum depth of the node tree
|
||||||
|
private static int GetMaxDepth(Profiler.ScopeNode node, int currentDepth)
|
||||||
|
{
|
||||||
|
if (node.Children == null || node.Children.Count == 0)
|
||||||
|
{
|
||||||
|
return currentDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxDepth = currentDepth;
|
||||||
|
foreach (Profiler.ScopeNode child in node.Children)
|
||||||
|
{
|
||||||
|
maxDepth = Math.Max(maxDepth, GetMaxDepth(child, currentDepth + 1));
|
||||||
|
}
|
||||||
|
return maxDepth;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Nerfed.Runtime;
|
|
||||||
|
|
||||||
internal class Program
|
|
||||||
{
|
|
||||||
private static void Main(string[] args)
|
|
||||||
{
|
|
||||||
Engine.Run(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
|
public enum ResourceState
|
||||||
|
{
|
||||||
|
Unloaded,
|
||||||
|
Queued,
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class Resource
|
||||||
|
{
|
||||||
|
public Guid Id { get; internal set; }
|
||||||
|
public string Path { get; internal set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Natively tracks if the resource is currently in RAM/VRAM.
|
||||||
|
/// </summary>
|
||||||
|
public ResourceState State { get; internal set; } = ResourceState.Unloaded;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks how many entities or systems currently need this loaded.
|
||||||
|
/// When it hits 0, the ResourceManager handles unloading natively.
|
||||||
|
/// </summary>
|
||||||
|
public int ReferenceCount { get; internal set; } = 0;
|
||||||
|
|
||||||
|
internal abstract void Load(Stream stream);
|
||||||
|
internal abstract void Unload();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attach this component to an entity mapped to raw source-path strings.
|
||||||
|
/// Useful for testing, hardcoded assets, or before full editor-guided GUID injection.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct AssetReferenceComponent(Guid AssetId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed version of an asset reference, preventing the user from accidentally
|
||||||
|
/// assigning a Shader GUID to a Texture component in the Editor.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct TypedAssetReference<TRes>(Guid AssetId) where TRes : Resource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Added to an entity by the AssetStreamingSystem when the physical resource is fully
|
||||||
|
/// loaded in memory and ready to be used by the renderer or physics engine.
|
||||||
|
/// </summary>
|
||||||
|
public struct AssetLoadedTag { }
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A highly scalable, multithreaded resource manager that handles asynchronous asset
|
||||||
|
/// loading and automatic reference-counted memory management.
|
||||||
|
/// </summary>
|
||||||
|
public static class ResourceManager
|
||||||
|
{
|
||||||
|
private const string RootName = "Resources";
|
||||||
|
|
||||||
|
// Track resources by their Guid ID instead of simple strings.
|
||||||
|
private static readonly ConcurrentDictionary<Guid, Resource> _resourceCache = new();
|
||||||
|
|
||||||
|
// Mapping a string path to its runtime Guid identifier
|
||||||
|
private static readonly ConcurrentDictionary<string, Guid> _pathToGuid = new();
|
||||||
|
|
||||||
|
// Queues for background processing
|
||||||
|
private static readonly ConcurrentQueue<Resource> _loadQueue = new();
|
||||||
|
|
||||||
|
// Loader threads
|
||||||
|
private static readonly Thread _loaderThread;
|
||||||
|
private static bool _isRunning = true;
|
||||||
|
|
||||||
|
// A registry of how to create concrete Resource instances from a generic type without massive switch statements.
|
||||||
|
private static readonly Dictionary<Type, Func<Resource>> _resourceFactories = new()
|
||||||
|
{
|
||||||
|
{ typeof(Shader), () => new Shader() }
|
||||||
|
};
|
||||||
|
|
||||||
|
static ResourceManager()
|
||||||
|
{
|
||||||
|
_loaderThread = new Thread(LoaderWorkerLoop)
|
||||||
|
{
|
||||||
|
Name = "Nerfed Asset Loader",
|
||||||
|
IsBackground = true,
|
||||||
|
Priority = ThreadPriority.BelowNormal // Keeps CPU time focused on the main game loop
|
||||||
|
};
|
||||||
|
_loaderThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronously shuts down the loader thread when the engine closes.
|
||||||
|
/// </summary>
|
||||||
|
public static void Shutdown()
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
_loaderThread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new resource type factory so the manager knows how to instantiate it.
|
||||||
|
/// Example: RegisterResourceType<Texture>(() => new Texture());
|
||||||
|
/// </summary>
|
||||||
|
public static void RegisterResourceType<T>(Func<T> factory) where T : Resource
|
||||||
|
{
|
||||||
|
_resourceFactories[typeof(T)] = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Guid associated with a specific asset path, making an initial id pass if required.
|
||||||
|
/// In a fully baked engine, the Guid is known at compile time or baked in the map data.
|
||||||
|
/// </summary>
|
||||||
|
public static Guid GetId(string resourcePath)
|
||||||
|
{
|
||||||
|
return _pathToGuid.GetOrAdd(resourcePath, _ => Guid.NewGuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins an asynchronous load for a resource by its Guid.
|
||||||
|
/// In ECS systems, Entities should strictly prefer this overload over the string one.
|
||||||
|
/// </summary>
|
||||||
|
public static T Retain<T>(Guid id, string expectedPath) where T : Resource
|
||||||
|
{
|
||||||
|
var resource = _resourceCache.GetOrAdd(id, (assetId) =>
|
||||||
|
{
|
||||||
|
if (!_resourceFactories.TryGetValue(typeof(T), out var factory))
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to create resource. No factory registered for {typeof(T).Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var newResource = factory();
|
||||||
|
newResource.Id = assetId;
|
||||||
|
// The path is still required so the background thread knows which file to open from disk.
|
||||||
|
newResource.Path = expectedPath;
|
||||||
|
newResource.State = ResourceState.Unloaded;
|
||||||
|
|
||||||
|
return newResource;
|
||||||
|
});
|
||||||
|
|
||||||
|
lock (resource)
|
||||||
|
{
|
||||||
|
resource.ReferenceCount++;
|
||||||
|
|
||||||
|
if (resource.State == ResourceState.Unloaded)
|
||||||
|
{
|
||||||
|
resource.State = ResourceState.Queued;
|
||||||
|
_loadQueue.Enqueue(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T)resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Begins an asynchronous load utilizing the string path to find the matching Guid.
|
||||||
|
/// This should generally be avoided in tight ECS loops.
|
||||||
|
/// </summary>
|
||||||
|
public static T Retain<T>(string resourcePath) where T : Resource
|
||||||
|
{
|
||||||
|
Guid id = GetId(resourcePath);
|
||||||
|
return Retain<T>(id, resourcePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current loading state of a resource by its Guid without altering its reference count.
|
||||||
|
/// </summary>
|
||||||
|
public static ResourceState GetState(Guid id)
|
||||||
|
{
|
||||||
|
if (_resourceCache.TryGetValue(id, out var resource))
|
||||||
|
{
|
||||||
|
return resource.State;
|
||||||
|
}
|
||||||
|
return ResourceState.Unloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrements the reference count of a resource by its Guid.
|
||||||
|
/// </summary>
|
||||||
|
public static void Release(Guid id)
|
||||||
|
{
|
||||||
|
if (_resourceCache.TryGetValue(id, out var resource))
|
||||||
|
{
|
||||||
|
Release(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrements the reference count of a resource.
|
||||||
|
/// If the count reaches 0, the asset is automatically unloaded from memory.
|
||||||
|
/// </summary>
|
||||||
|
public static void Release(Resource resource)
|
||||||
|
{
|
||||||
|
if (resource == null) return;
|
||||||
|
|
||||||
|
lock (resource)
|
||||||
|
{
|
||||||
|
resource.ReferenceCount--;
|
||||||
|
|
||||||
|
if (resource.ReferenceCount <= 0)
|
||||||
|
{
|
||||||
|
// Fully unused! We should unload it safely.
|
||||||
|
if (resource.State == ResourceState.Loaded)
|
||||||
|
{
|
||||||
|
resource.Unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.State = ResourceState.Unloaded;
|
||||||
|
_resourceCache.TryRemove(resource.Id, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Background thread loop that pulls from the queue and does the slow file I/O operations.
|
||||||
|
/// </summary>
|
||||||
|
private static void LoaderWorkerLoop()
|
||||||
|
{
|
||||||
|
while (_isRunning)
|
||||||
|
{
|
||||||
|
if (_loadQueue.TryDequeue(out var resource))
|
||||||
|
{
|
||||||
|
// Safety check: Was the resource released before we even got around to loading it?
|
||||||
|
if (resource.ReferenceCount <= 0)
|
||||||
|
{
|
||||||
|
resource.State = ResourceState.Unloaded;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resource.State = ResourceState.Loading;
|
||||||
|
string fullPath = Path.Combine(AppContext.BaseDirectory, RootName, resource.Id.ToString()) + ".bin";
|
||||||
|
|
||||||
|
// Do the slow synchronous disk read
|
||||||
|
using var stream = StorageContainer.OpenStream(fullPath);
|
||||||
|
resource.Load(stream);
|
||||||
|
|
||||||
|
resource.State = ResourceState.Loaded;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to background load asset '{resource.Path}': {e.Message}");
|
||||||
|
resource.State = ResourceState.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Sleep cleanly if queue is empty to avoid burning total CPU usage on an infinite while-loop
|
||||||
|
Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Nerfed.Runtime.Resources;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sample component demonstrating how to use strongly-typed asset references
|
||||||
|
/// in a realistic scenario where an entity requires multiple distinct resources.
|
||||||
|
/// </summary>
|
||||||
|
public struct SampleMeshVisualComponent
|
||||||
|
{
|
||||||
|
// The user safely assigns a Mesh GUID in the Editor inspector.
|
||||||
|
public TypedAssetReference<Shader> VertexShader;
|
||||||
|
|
||||||
|
// The user safely assigns a Material GUID in the Editor inspector.
|
||||||
|
public TypedAssetReference<Shader> FragmentShader;
|
||||||
|
|
||||||
|
public SampleMeshVisualComponent(Guid vertexId, Guid fragId) {
|
||||||
|
VertexShader = new TypedAssetReference<Shader>(vertexId);
|
||||||
|
FragmentShader = new TypedAssetReference<Shader>(fragId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Runtime.Scene.Streaming;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Resources;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A typical rendering preparation system that natively resolves and requests
|
||||||
|
/// asynchronous background loading for its own required assets, removing the
|
||||||
|
/// need for a monolithic generic AssetStreaming manager.
|
||||||
|
/// </summary>
|
||||||
|
public class SampleRenderSystem : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
private readonly Filter _meshVisualsFilter;
|
||||||
|
|
||||||
|
public SampleRenderSystem(World world) : base(world) {
|
||||||
|
_meshVisualsFilter = FilterBuilder
|
||||||
|
.Include<SampleMeshVisualComponent>()
|
||||||
|
// Always ignore chunk entities technically "unloading" from RAM
|
||||||
|
.Exclude<ChunkUnloadPendingTag>()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta) {
|
||||||
|
foreach(Entity entity in _meshVisualsFilter.Entities) {
|
||||||
|
SampleMeshVisualComponent visualComp = Get<SampleMeshVisualComponent>(entity);
|
||||||
|
|
||||||
|
// 1. Resolve State
|
||||||
|
ResourceState vertState = ResourceManager.GetState(visualComp.VertexShader.AssetId);
|
||||||
|
ResourceState fragState = ResourceManager.GetState(visualComp.FragmentShader.AssetId);
|
||||||
|
|
||||||
|
// 2. Asynchronously request assets if they don't exist in memory yet
|
||||||
|
if(vertState == ResourceState.Unloaded) {
|
||||||
|
ResourceManager.Retain<Shader>(visualComp.VertexShader.AssetId, "Unknown/Path");
|
||||||
|
}
|
||||||
|
if(fragState == ResourceState.Unloaded) {
|
||||||
|
ResourceManager.Retain<Shader>(visualComp.FragmentShader.AssetId, "Unknown/Path");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Prevent rendering logic unless ALL strictly required assets are fully mapped
|
||||||
|
bool isReadyToDraw = vertState == ResourceState.Loaded && fragState == ResourceState.Loaded;
|
||||||
|
|
||||||
|
if(isReadyToDraw) {
|
||||||
|
// At this exact point, you can safely assume:
|
||||||
|
// 1) The background loading threads are 100% finished processing these shaders.
|
||||||
|
// 2) The GraphicsDevice can safely extract the native handle.
|
||||||
|
|
||||||
|
// e.g. GraphicsDevice.BindShader(visualComp.VertexShader.AssetId);
|
||||||
|
// e.g. GraphicsDevice.DrawPolygons(...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using RefreshCS;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
|
public class Shader : Resource
|
||||||
|
{
|
||||||
|
public IntPtr Handle { get; private set; }
|
||||||
|
|
||||||
|
public uint SamplerCount { get; private set; }
|
||||||
|
public uint StorageTextureCount { get; private set; }
|
||||||
|
public uint StorageBufferCount { get; private set; }
|
||||||
|
public uint UniformBufferCount { get; private set; }
|
||||||
|
|
||||||
|
internal Shader() { }
|
||||||
|
|
||||||
|
internal override unsafe void Load(Stream stream)
|
||||||
|
{
|
||||||
|
using (BinaryReader reader = new BinaryReader(stream))
|
||||||
|
{
|
||||||
|
Refresh.ShaderCreateInfo createInfo;
|
||||||
|
createInfo.Format = (Refresh.ShaderFormat)reader.ReadInt32();
|
||||||
|
createInfo.Stage = (Refresh.ShaderStage)reader.ReadInt32();
|
||||||
|
createInfo.UniformBufferCount = (uint)reader.ReadInt32();
|
||||||
|
createInfo.StorageBufferCount = (uint)reader.ReadInt32();
|
||||||
|
createInfo.StorageTextureCount = (uint)reader.ReadInt32();
|
||||||
|
createInfo.SamplerCount = (uint)reader.ReadInt32();
|
||||||
|
|
||||||
|
int byteCodeSize = reader.ReadInt32();
|
||||||
|
void* byteCodeBuffer = NativeMemory.Alloc((nuint)byteCodeSize);
|
||||||
|
Span<byte> byteCodeSpan = new Span<byte>(byteCodeBuffer, byteCodeSize);
|
||||||
|
|
||||||
|
int bytesRead = 0;
|
||||||
|
while (bytesRead < byteCodeSize)
|
||||||
|
{
|
||||||
|
bytesRead += reader.Read(byteCodeSpan.Slice(bytesRead));
|
||||||
|
}
|
||||||
|
|
||||||
|
createInfo.CodeSize = (nuint)byteCodeSize;
|
||||||
|
createInfo.Code = (byte*)byteCodeBuffer;
|
||||||
|
createInfo.EntryPointName = "main";
|
||||||
|
|
||||||
|
Handle = Refresh.Refresh_CreateShader(Engine.GraphicsDevice.Handle, createInfo);
|
||||||
|
NativeMemory.Free(byteCodeBuffer);
|
||||||
|
|
||||||
|
SamplerCount = createInfo.SamplerCount;
|
||||||
|
StorageTextureCount = createInfo.StorageTextureCount;
|
||||||
|
StorageBufferCount = createInfo.StorageBufferCount;
|
||||||
|
UniformBufferCount = createInfo.UniformBufferCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unload()
|
||||||
|
{
|
||||||
|
Refresh.Refresh_ReleaseShader(Engine.GraphicsDevice.Handle, Handle);
|
||||||
|
Handle = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstraction over a concrete scene format (JSON, binary, …).
|
||||||
|
/// Implementations read and write <see cref="SceneData"/> to a <see cref="Stream"/>,
|
||||||
|
/// making it straightforward to add a compact binary format later without
|
||||||
|
/// changing any of the surrounding scene infrastructure.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISceneSerializer
|
||||||
|
{
|
||||||
|
void Serialize(SceneData scene, Stream stream);
|
||||||
|
SceneData Deserialize(Stream stream);
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Human-readable JSON scene serializer.
|
||||||
|
///
|
||||||
|
/// Example output:
|
||||||
|
/// <code>
|
||||||
|
/// {
|
||||||
|
/// "version": 1,
|
||||||
|
/// "name": "MyScene",
|
||||||
|
/// "entities": [
|
||||||
|
/// {
|
||||||
|
/// "id": "a1b2c3d4-...",
|
||||||
|
/// "tag": "Player",
|
||||||
|
/// "parentId": null,
|
||||||
|
/// "components": [
|
||||||
|
/// {
|
||||||
|
/// "type": "Nerfed.Runtime.Components.LocalTransform",
|
||||||
|
/// "data": {
|
||||||
|
/// "position": { "x": 0.0, "y": 0.0, "z": 0.0 },
|
||||||
|
/// "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
|
||||||
|
/// "scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// ],
|
||||||
|
/// "relations": [
|
||||||
|
/// {
|
||||||
|
/// "type": "Nerfed.Runtime.Components.OwnerRelation",
|
||||||
|
/// "entityA": "a1b2c3d4-...",
|
||||||
|
/// "entityB": "e5f6a7b8-...",
|
||||||
|
/// "data": {}
|
||||||
|
/// }
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class JsonSceneSerializer : ISceneSerializer
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions Options = new() {
|
||||||
|
WriteIndented = true,
|
||||||
|
Converters =
|
||||||
|
{
|
||||||
|
new Vector3JsonConverter(),
|
||||||
|
new QuaternionJsonConverter(),
|
||||||
|
new SceneComponentDataJsonConverter(),
|
||||||
|
new SceneRelationDataJsonConverter(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Serialize(SceneData scene, Stream stream) {
|
||||||
|
JsonSerializer.Serialize(stream, scene, Options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SceneData Deserialize(Stream stream) {
|
||||||
|
return JsonSerializer.Deserialize<SceneData>(stream, Options)
|
||||||
|
?? throw new InvalidOperationException("Failed to deserialize scene: root element was null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Converters
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private sealed class Vector3JsonConverter : JsonConverter<Vector3>
|
||||||
|
{
|
||||||
|
public override Vector3 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
float x = 0f, y = 0f, z = 0f;
|
||||||
|
reader.Read(); // StartObject
|
||||||
|
while(reader.Read() && reader.TokenType != JsonTokenType.EndObject) {
|
||||||
|
string name = reader.GetString()!;
|
||||||
|
reader.Read();
|
||||||
|
switch(name) {
|
||||||
|
case "x": x = reader.GetSingle(); break;
|
||||||
|
case "y": y = reader.GetSingle(); break;
|
||||||
|
case "z": z = reader.GetSingle(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Vector3(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Vector3 value, JsonSerializerOptions options) {
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WriteNumber("x", value.X);
|
||||||
|
writer.WriteNumber("y", value.Y);
|
||||||
|
writer.WriteNumber("z", value.Z);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class QuaternionJsonConverter : JsonConverter<Quaternion>
|
||||||
|
{
|
||||||
|
public override Quaternion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
float x = 0f, y = 0f, z = 0f, w = 1f;
|
||||||
|
reader.Read(); // StartObject
|
||||||
|
while(reader.Read() && reader.TokenType != JsonTokenType.EndObject) {
|
||||||
|
string name = reader.GetString()!;
|
||||||
|
reader.Read();
|
||||||
|
switch(name) {
|
||||||
|
case "x": x = reader.GetSingle(); break;
|
||||||
|
case "y": y = reader.GetSingle(); break;
|
||||||
|
case "z": z = reader.GetSingle(); break;
|
||||||
|
case "w": w = reader.GetSingle(); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Quaternion(x, y, z, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Quaternion value, JsonSerializerOptions options) {
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WriteNumber("x", value.X);
|
||||||
|
writer.WriteNumber("y", value.Y);
|
||||||
|
writer.WriteNumber("z", value.Z);
|
||||||
|
writer.WriteNumber("w", value.W);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Buffers the full JSON object, resolves the CLR component type from the "type" field,
|
||||||
|
/// then deserializes "data" using that concrete type.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class SceneComponentDataJsonConverter : JsonConverter<SceneComponentData>
|
||||||
|
{
|
||||||
|
public override SceneComponentData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
using JsonDocument doc = JsonDocument.ParseValue(ref reader);
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
|
||||||
|
string typeName = root.GetProperty("type").GetString()
|
||||||
|
?? throw new JsonException("Missing or null 'type' field in component data.");
|
||||||
|
|
||||||
|
Type componentType = SceneManager.GetComponentType(typeName)
|
||||||
|
?? throw new JsonException($"Unknown component type '{typeName}'. Ensure the struct is marked with [SceneComponent].");
|
||||||
|
|
||||||
|
string rawData = root.GetProperty("data").GetRawText();
|
||||||
|
ValueType value = (ValueType)JsonSerializer.Deserialize(rawData, componentType, options)!;
|
||||||
|
|
||||||
|
return new SceneComponentData { Type = typeName, Value = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, SceneComponentData value, JsonSerializerOptions options) {
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WriteString("type", value.Type);
|
||||||
|
writer.WritePropertyName("data");
|
||||||
|
JsonSerializer.Serialize(writer, value.Value, value.Value.GetType(), options);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Same pattern as <see cref="SceneComponentDataJsonConverter"/> but for relation data.
|
||||||
|
/// Resolves the type via <see cref="SceneManager.GetRelationType"/>.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class SceneRelationDataJsonConverter : JsonConverter<SceneRelationData>
|
||||||
|
{
|
||||||
|
public override SceneRelationData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
using JsonDocument doc = JsonDocument.ParseValue(ref reader);
|
||||||
|
JsonElement root = doc.RootElement;
|
||||||
|
|
||||||
|
string typeName = root.GetProperty("type").GetString()
|
||||||
|
?? throw new JsonException("Missing or null 'type' field in relation data.");
|
||||||
|
|
||||||
|
Type relationType = SceneManager.GetRelationType(typeName)
|
||||||
|
?? throw new JsonException($"Unknown relation type '{typeName}'. Ensure the struct is marked with [SceneRelation].");
|
||||||
|
|
||||||
|
Guid entityA = root.GetProperty("entityA").GetGuid();
|
||||||
|
Guid entityB = root.GetProperty("entityB").GetGuid();
|
||||||
|
|
||||||
|
string rawData = root.GetProperty("data").GetRawText();
|
||||||
|
ValueType value = (ValueType)JsonSerializer.Deserialize(rawData, relationType, options)!;
|
||||||
|
|
||||||
|
return new SceneRelationData { Type = typeName, EntityA = entityA, EntityB = entityB, Value = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, SceneRelationData value, JsonSerializerOptions options) {
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writer.WriteString("type", value.Type);
|
||||||
|
writer.WriteString("entityA", value.EntityA);
|
||||||
|
writer.WriteString("entityB", value.EntityB);
|
||||||
|
writer.WritePropertyName("data");
|
||||||
|
JsonSerializer.Serialize(writer, value.Value, value.Value.GetType(), options);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an unmanaged struct as a serializable scene component.
|
||||||
|
/// Only types with this attribute will be saved/loaded by the scene system.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Struct, Inherited = false)]
|
||||||
|
public sealed class SceneComponentAttribute : Attribute { }
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
namespace Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Root data model for a scene. A scene and a prefab are the same thing —
|
||||||
|
/// there is no distinction between the two, mirroring Godot's design.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SceneData
|
||||||
|
{
|
||||||
|
/// <summary>Incremented when the file format changes in a breaking way.</summary>
|
||||||
|
public int Version { get; set; } = SceneData.CurrentVersion;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public List<SceneEntityData> Entities { get; set; } = new();
|
||||||
|
/// <summary>All user-defined relations between entities in this scene.</summary>
|
||||||
|
public List<SceneRelationData> Relations { get; set; } = new();
|
||||||
|
|
||||||
|
public const int CurrentVersion = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialized representation of a single entity.
|
||||||
|
/// The <see cref="Id"/> is a scene-local identifier that only exists in the
|
||||||
|
/// serialized data and is used to reconstruct parent–child and relation references.
|
||||||
|
/// It is never stored as a component on a live entity.
|
||||||
|
/// An entity is included if it owns at least one <see cref="SceneComponentAttribute"/> component
|
||||||
|
/// OR participates in at least one <see cref="SceneRelationAttribute"/> relation.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SceneEntityData
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
public string Tag { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scene-local <see cref="Id"/> of this entity's <see cref="Components.ChildParentRelation"/>
|
||||||
|
/// parent, or <c>null</c> if this is a root entity.
|
||||||
|
/// </summary>
|
||||||
|
public Guid? ParentId { get; set; }
|
||||||
|
|
||||||
|
public List<SceneComponentData> Components { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialized representation of a single component value on an entity.
|
||||||
|
/// <see cref="Type"/> is the fully-qualified CLR type name used to resolve the component on load.
|
||||||
|
/// <see cref="Value"/> is the boxed runtime value; each <see cref="ISceneSerializer"/> is
|
||||||
|
/// responsible for converting it to/from its wire format.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SceneComponentData
|
||||||
|
{
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
public ValueType Value { get; set; } = default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialized representation of a relation between two entities.
|
||||||
|
/// <see cref="EntityA"/> and <see cref="EntityB"/> reference scene-local <see cref="SceneEntityData.Id"/> values.
|
||||||
|
/// <see cref="Type"/> identifies the relation kind (must be marked with <see cref="SceneRelationAttribute"/>).
|
||||||
|
/// <see cref="Value"/> holds the relation data payload (may be an empty struct).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SceneRelationData
|
||||||
|
{
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
public Guid EntityA { get; set; }
|
||||||
|
public Guid EntityB { get; set; }
|
||||||
|
public ValueType Value { get; set; } = default!;
|
||||||
|
}
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Central hub for scene serialization and deserialization.
|
||||||
|
///
|
||||||
|
/// On first use the static constructor scans all loaded assemblies for:
|
||||||
|
/// • Structs annotated with <see cref="SceneComponentAttribute"/> — serialized as per-entity component data.
|
||||||
|
/// • Structs annotated with <see cref="SceneRelationAttribute"/> — serialized as cross-entity relation data.
|
||||||
|
///
|
||||||
|
/// The <see cref="Components.ChildParentRelation"/> hierarchy is handled separately via
|
||||||
|
/// <see cref="SceneEntityData.ParentId"/> and does NOT need a <see cref="SceneRelationAttribute"/>.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// <code>
|
||||||
|
/// var serializer = new JsonSceneSerializer();
|
||||||
|
/// SceneManager.Save(world, "Assets/level1.scene", serializer, "Level 1");
|
||||||
|
/// SceneManager.Load(world, "Assets/level1.scene", serializer);
|
||||||
|
/// </code>
|
||||||
|
/// </summary>
|
||||||
|
public static class SceneManager
|
||||||
|
{
|
||||||
|
// Full CLR type name → Type
|
||||||
|
private static readonly Dictionary<string, Type> ComponentRegistry = new();
|
||||||
|
private static readonly Dictionary<string, Type> RelationRegistry = new();
|
||||||
|
|
||||||
|
// Reflection cache so we only build the delegates once per type.
|
||||||
|
private static readonly Dictionary<Type, Func<World, Entity, bool>> HasComponentCache = new();
|
||||||
|
private static readonly Dictionary<Type, Func<World, Entity, ValueType>> GetComponentCache = new();
|
||||||
|
private static readonly Dictionary<Type, Action<World, Entity, ValueType>> SetComponentCache = new();
|
||||||
|
private static readonly Dictionary<Type, Func<World, Entity, bool>> HasOutRelationCache = new();
|
||||||
|
private static readonly Dictionary<Type, Func<World, Entity, Entity[]>> OutRelationsCache = new();
|
||||||
|
private static readonly Dictionary<Type, Func<World, Entity, Entity, ValueType>> GetRelationDataCache = new();
|
||||||
|
private static readonly Dictionary<Type, Action<World, Entity, Entity, ValueType>> RelateCache = new();
|
||||||
|
|
||||||
|
static SceneManager() {
|
||||||
|
foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) {
|
||||||
|
Type[] types;
|
||||||
|
try { types = assembly.GetTypes(); } catch(ReflectionTypeLoadException ex) { types = ex.Types.Where(t => t is not null).ToArray()!; }
|
||||||
|
|
||||||
|
foreach(Type type in types) {
|
||||||
|
if(type.FullName is null) continue;
|
||||||
|
|
||||||
|
if(type.GetCustomAttribute<SceneComponentAttribute>() is not null)
|
||||||
|
ComponentRegistry[type.FullName] = type;
|
||||||
|
|
||||||
|
if(type.GetCustomAttribute<SceneRelationAttribute>() is not null)
|
||||||
|
RelationRegistry[type.FullName] = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Public registry accessors
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public static Type? GetComponentType(string fullName) {
|
||||||
|
ComponentRegistry.TryGetValue(fullName, out Type? type);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type? GetRelationType(string fullName) {
|
||||||
|
RelationRegistry.TryGetValue(fullName, out Type? type);
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<string, Type> RegisteredComponentTypes => ComponentRegistry;
|
||||||
|
public static IReadOnlyDictionary<string, Type> RegisteredRelationTypes => RelationRegistry;
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// High-level Save / Load
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public static void Save(World world, string path, ISceneSerializer serializer, string sceneName = "") {
|
||||||
|
SceneData scene = Extract(world, sceneName);
|
||||||
|
string? directory = Path.GetDirectoryName(path);
|
||||||
|
if(!string.IsNullOrEmpty(directory))
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
using FileStream stream = File.Open(path, FileMode.Create, FileAccess.Write);
|
||||||
|
serializer.Serialize(scene, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<Guid, Entity> Load(World world, string path, ISceneSerializer serializer) {
|
||||||
|
using FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read);
|
||||||
|
SceneData scene = serializer.Deserialize(stream);
|
||||||
|
return Instantiate(world, scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Extract (world → SceneData)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public static SceneData Extract(World world, string name = "") {
|
||||||
|
SceneData scene = new() { Name = name };
|
||||||
|
|
||||||
|
// ── 1. Collect entities ──────────────────────────────────────────────
|
||||||
|
// Include an entity if it has at least one scene component OR if it
|
||||||
|
// appears as an endpoint of at least one scene relation. This ensures
|
||||||
|
// pure grouping nodes and relation-only entities are not dropped.
|
||||||
|
|
||||||
|
Dictionary<uint, Guid> entityToGuid = new();
|
||||||
|
|
||||||
|
void EnsureEntity(Entity e) {
|
||||||
|
if(!entityToGuid.ContainsKey(e.ID))
|
||||||
|
entityToGuid[e.ID] = Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(Entity entity in world.GetAllEntities()) {
|
||||||
|
if(HasAnySceneComponent(world, entity))
|
||||||
|
EnsureEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk all registered relation types and pull in both endpoints.
|
||||||
|
foreach(Type relationType in RelationRegistry.Values) {
|
||||||
|
foreach((Entity a, Entity b) in WorldAllRelations(world, relationType)) {
|
||||||
|
EnsureEntity(a);
|
||||||
|
EnsureEntity(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also include entities that are part of the ChildParentRelation hierarchy
|
||||||
|
// even if they carry no scene components and no user-defined relations.
|
||||||
|
foreach((Entity child, Entity parent) in world.Relations<ChildParentRelation>()) {
|
||||||
|
EnsureEntity(child);
|
||||||
|
EnsureEntity(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. Build entity records (parents must be known before children so
|
||||||
|
// we sort parents-before-children for readable output) ───────────
|
||||||
|
List<SceneEntityData> ordered = BuildSortedEntityList(world, entityToGuid);
|
||||||
|
scene.Entities.AddRange(ordered);
|
||||||
|
|
||||||
|
// ── 3. Build relation records ────────────────────────────────────────
|
||||||
|
foreach((string typeName, Type relationType) in RelationRegistry) {
|
||||||
|
foreach((Entity a, Entity b) in WorldAllRelations(world, relationType)) {
|
||||||
|
if(!entityToGuid.TryGetValue(a.ID, out Guid guidA) ||
|
||||||
|
!entityToGuid.TryGetValue(b.ID, out Guid guidB))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ValueType payload = WorldGetRelationData(world, a, b, relationType);
|
||||||
|
scene.Relations.Add(new SceneRelationData {
|
||||||
|
Type = typeName,
|
||||||
|
EntityA = guidA,
|
||||||
|
EntityB = guidB,
|
||||||
|
Value = payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Instantiate (SceneData → world)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public static Dictionary<Guid, Entity> Instantiate(World world, SceneData scene) {
|
||||||
|
Dictionary<Guid, Entity> guidToEntity = new(scene.Entities.Count);
|
||||||
|
|
||||||
|
// Pass 1 – create all entities.
|
||||||
|
foreach(SceneEntityData entityData in scene.Entities) {
|
||||||
|
Entity entity = world.CreateEntity(entityData.Tag);
|
||||||
|
guidToEntity[entityData.Id] = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 2 – set components and wire up the ChildParentRelation hierarchy.
|
||||||
|
foreach(SceneEntityData entityData in scene.Entities) {
|
||||||
|
Entity entity = guidToEntity[entityData.Id];
|
||||||
|
|
||||||
|
foreach(SceneComponentData componentData in entityData.Components)
|
||||||
|
WorldSetComponent(world, entity, componentData.Type, componentData.Value);
|
||||||
|
|
||||||
|
if(entityData.ParentId is Guid parentGuid && guidToEntity.TryGetValue(parentGuid, out Entity parent)) {
|
||||||
|
world.Set(entity, new Child());
|
||||||
|
world.Relate(entity, parent, new ChildParentRelation());
|
||||||
|
} else {
|
||||||
|
world.Set(entity, new Root());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 3 – restore all user-defined relations.
|
||||||
|
foreach(SceneRelationData relationData in scene.Relations) {
|
||||||
|
if(!guidToEntity.TryGetValue(relationData.EntityA, out Entity entityA) ||
|
||||||
|
!guidToEntity.TryGetValue(relationData.EntityB, out Entity entityB))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
WorldRelate(world, entityA, entityB, relationData.Type, relationData.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return guidToEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Helpers – entity ordering
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns entities sorted so that a parent always appears before its children,
|
||||||
|
// making the JSON file human-readable and easier to diff.
|
||||||
|
private static List<SceneEntityData> BuildSortedEntityList(
|
||||||
|
World world,
|
||||||
|
Dictionary<uint, Guid> entityToGuid) {
|
||||||
|
// Build per-entity data (unsorted first).
|
||||||
|
Dictionary<Guid, SceneEntityData> byGuid = new(entityToGuid.Count);
|
||||||
|
|
||||||
|
foreach((uint entityId, Guid guid) in entityToGuid) {
|
||||||
|
Entity entity = new(entityId);
|
||||||
|
|
||||||
|
Guid? parentId = null;
|
||||||
|
if(world.HasOutRelation<ChildParentRelation>(entity)) {
|
||||||
|
// Iterate all out-relations — an entity may have multiple parents
|
||||||
|
// in theory, but ChildParentRelation is designed as singleton.
|
||||||
|
// We capture the first valid one here.
|
||||||
|
foreach(Entity parent in world.OutRelations<ChildParentRelation>(entity)) {
|
||||||
|
if(entityToGuid.TryGetValue(parent.ID, out Guid parentGuid)) {
|
||||||
|
parentId = parentGuid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SceneComponentData> components = new();
|
||||||
|
foreach((string typeName, Type componentType) in ComponentRegistry) {
|
||||||
|
if(!WorldHasComponent(world, entity, componentType)) continue;
|
||||||
|
ValueType value = WorldGetComponent(world, entity, componentType);
|
||||||
|
components.Add(new SceneComponentData { Type = typeName, Value = value });
|
||||||
|
}
|
||||||
|
|
||||||
|
byGuid[guid] = new SceneEntityData {
|
||||||
|
Id = guid,
|
||||||
|
Tag = world.GetTag(entity),
|
||||||
|
ParentId = parentId,
|
||||||
|
Components = components,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topological sort: parents before children.
|
||||||
|
List<SceneEntityData> sorted = new(byGuid.Count);
|
||||||
|
HashSet<Guid> visited = new(byGuid.Count);
|
||||||
|
|
||||||
|
void Visit(Guid id) {
|
||||||
|
if(!visited.Add(id)) return;
|
||||||
|
SceneEntityData data = byGuid[id];
|
||||||
|
if(data.ParentId is Guid pid && byGuid.ContainsKey(pid))
|
||||||
|
Visit(pid);
|
||||||
|
sorted.Add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(Guid id in byGuid.Keys)
|
||||||
|
Visit(id);
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Reflection helpers – components
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static bool HasAnySceneComponent(World world, Entity entity) {
|
||||||
|
foreach(Type componentType in ComponentRegistry.Values) {
|
||||||
|
if(WorldHasComponent(world, entity, componentType)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool WorldHasComponent(World world, Entity entity, Type componentType) {
|
||||||
|
if(!HasComponentCache.TryGetValue(componentType, out Func<World, Entity, bool>? fn)) {
|
||||||
|
MethodInfo method = FindGenericMethod(nameof(World.Has)).MakeGenericMethod(componentType);
|
||||||
|
fn = (w, e) => (bool)method.Invoke(w, new object[] { e })!;
|
||||||
|
HasComponentCache[componentType] = fn;
|
||||||
|
}
|
||||||
|
return fn(world, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ValueType WorldGetComponent(World world, Entity entity, Type componentType) {
|
||||||
|
if(!GetComponentCache.TryGetValue(componentType, out Func<World, Entity, ValueType>? fn)) {
|
||||||
|
MethodInfo method = FindGenericMethod(nameof(World.Get)).MakeGenericMethod(componentType);
|
||||||
|
fn = (w, e) => (ValueType)method.Invoke(w, new object[] { e })!;
|
||||||
|
GetComponentCache[componentType] = fn;
|
||||||
|
}
|
||||||
|
return fn(world, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WorldSetComponent(World world, Entity entity, string typeName, ValueType value) {
|
||||||
|
if(!ComponentRegistry.TryGetValue(typeName, out Type? componentType)) return;
|
||||||
|
|
||||||
|
if(!SetComponentCache.TryGetValue(componentType, out Action<World, Entity, ValueType>? fn)) {
|
||||||
|
MethodInfo method = FindGenericMethod(nameof(World.Set)).MakeGenericMethod(componentType);
|
||||||
|
fn = (w, e, v) => method.Invoke(w, new object[] { e, v });
|
||||||
|
SetComponentCache[componentType] = fn;
|
||||||
|
}
|
||||||
|
fn(world, entity, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Reflection helpers – relations
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static IEnumerable<(Entity, Entity)> WorldAllRelations(World world, Type relationType) {
|
||||||
|
// World.Relations<T>() returns ReverseSpanEnumerator<(Entity,Entity)>.
|
||||||
|
// We materialise it into a list so the caller can iterate freely.
|
||||||
|
MethodInfo method = FindGenericMethod(nameof(World.Relations)).MakeGenericMethod(relationType);
|
||||||
|
// Returns a boxed ReverseSpanEnumerator; invoke MoveNext/Current via dynamic.
|
||||||
|
// Easiest: call via dynamic to avoid unsafe span-from-box issues.
|
||||||
|
dynamic enumerator = method.Invoke(world, null)!;
|
||||||
|
List<(Entity, Entity)> results = new();
|
||||||
|
while(enumerator.MoveNext())
|
||||||
|
results.Add(enumerator.Current);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ValueType WorldGetRelationData(World world, Entity a, Entity b, Type relationType) {
|
||||||
|
if(!GetRelationDataCache.TryGetValue(relationType, out Func<World, Entity, Entity, ValueType>? fn)) {
|
||||||
|
MethodInfo method = FindGenericMethod(nameof(World.GetRelationData)).MakeGenericMethod(relationType);
|
||||||
|
fn = (w, ea, eb) => (ValueType)method.Invoke(w, new object[] { ea, eb })!;
|
||||||
|
GetRelationDataCache[relationType] = fn;
|
||||||
|
}
|
||||||
|
return fn(world, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WorldRelate(World world, Entity a, Entity b, string typeName, ValueType value) {
|
||||||
|
if(!RelationRegistry.TryGetValue(typeName, out Type? relationType)) return;
|
||||||
|
|
||||||
|
if(!RelateCache.TryGetValue(relationType, out Action<World, Entity, Entity, ValueType>? fn)) {
|
||||||
|
MethodInfo method = FindGenericMethod(nameof(World.Relate)).MakeGenericMethod(relationType);
|
||||||
|
fn = (w, ea, eb, v) => method.Invoke(w, new object[] { ea, eb, v });
|
||||||
|
RelateCache[relationType] = fn;
|
||||||
|
}
|
||||||
|
fn(world, a, b, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Utility
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static MethodInfo FindGenericMethod(string name) {
|
||||||
|
foreach(MethodInfo m in typeof(World).GetMethods(BindingFlags.Public | BindingFlags.Instance)) {
|
||||||
|
if(m.Name == name && m.IsGenericMethodDefinition)
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException($"Could not find generic method '{name}' on {nameof(World)}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Nerfed.Runtime.Scene;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an unmanaged struct as a serializable scene relation kind.
|
||||||
|
/// Both endpoints and the data payload will be saved/loaded by the scene system.
|
||||||
|
/// The <see cref="Components.ChildParentRelation"/> hierarchy is handled separately via
|
||||||
|
/// <see cref="SceneEntityData.ParentId"/> and should NOT be marked with this attribute.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Struct, Inherited = false)]
|
||||||
|
public sealed class SceneRelationAttribute : Attribute { }
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Scene.Streaming;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status of a chunk in the streaming system.
|
||||||
|
/// </summary>
|
||||||
|
public enum ChunkState
|
||||||
|
{
|
||||||
|
Unloaded,
|
||||||
|
Loading,
|
||||||
|
Loaded,
|
||||||
|
Unloading
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A system that manages spatial partitioning. It determines which chunks should be loaded based on observers.
|
||||||
|
/// </summary>
|
||||||
|
public class ChunkStreamingSystem : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
private readonly struct ChunkCoord : IEquatable<ChunkCoord>
|
||||||
|
{
|
||||||
|
public readonly int X;
|
||||||
|
public readonly int Y;
|
||||||
|
public readonly int Z;
|
||||||
|
|
||||||
|
// Pre-calculated on creation
|
||||||
|
public readonly long Id;
|
||||||
|
|
||||||
|
public ChunkCoord(int x, int y, int z)
|
||||||
|
{
|
||||||
|
X = x;
|
||||||
|
Y = y;
|
||||||
|
Z = z;
|
||||||
|
|
||||||
|
// We allocate 21 bits per axis (allowing ~2 million chunks positive and negative).
|
||||||
|
var hashX = (long)x & 0x1FFFFF;
|
||||||
|
var hashY = (long)y & 0x1FFFFF;
|
||||||
|
var hashZ = (long)z & 0x1FFFFF;
|
||||||
|
|
||||||
|
Id = hashX | (hashY << 21) | (hashZ << 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ChunkCoord other) => Id == other.Id;
|
||||||
|
public override bool Equals(object? obj) => obj is ChunkCoord other && Equals(other);
|
||||||
|
public override int GetHashCode() => Id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurable size of a chunk in world coordinates.
|
||||||
|
public float ChunkSize { get; set; } = 64f;
|
||||||
|
|
||||||
|
private readonly Filter _observerFilter;
|
||||||
|
private readonly Filter _chunkMemberFilter;
|
||||||
|
private readonly Filter _unloadedFilter;
|
||||||
|
|
||||||
|
// Active loaded/loading chunks
|
||||||
|
private readonly Dictionary<ChunkCoord, ChunkState> _activeChunks = new();
|
||||||
|
|
||||||
|
// Queue of chunks waiting to be loaded
|
||||||
|
private readonly Queue<ChunkCoord> _pendingLoads = new();
|
||||||
|
|
||||||
|
// Queue of chunks waiting to be completely tagged for unloading
|
||||||
|
private readonly Queue<ChunkCoord> _pendingUnloads = new();
|
||||||
|
|
||||||
|
public ChunkStreamingSystem(World world) : base(world)
|
||||||
|
{
|
||||||
|
_observerFilter = FilterBuilder
|
||||||
|
.Include<ChunkObserverComponent>()
|
||||||
|
.Include<LocalToWorld>() // Needs a world position
|
||||||
|
.Exclude<ChunkUnloadPendingTag>() // Ignore dying observers
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_chunkMemberFilter = FilterBuilder
|
||||||
|
.Include<ChunkMemberComponent>()
|
||||||
|
.Exclude<ChunkUnloadPendingTag>() // Ignore entities already marked for death
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_unloadedFilter = FilterBuilder
|
||||||
|
.Include<ChunkUnloadPendingTag>()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
var requiredChunks = new HashSet<ChunkCoord>();
|
||||||
|
|
||||||
|
// 1. Find all chunks that should be loaded based on observers
|
||||||
|
foreach (var observerEntity in _observerFilter.Entities)
|
||||||
|
{
|
||||||
|
var observer = Get<ChunkObserverComponent>(observerEntity);
|
||||||
|
var transform = Get<LocalToWorld>(observerEntity);
|
||||||
|
|
||||||
|
// Convert world pos to grid coordinates
|
||||||
|
var worldPos = transform.localToWorldMatrix.Translation;
|
||||||
|
var centerChunk = GetChunkCoord(worldPos);
|
||||||
|
|
||||||
|
// Determine chunk radius based on observer radius and chunk size
|
||||||
|
int chunkRadius = (int)MathF.Ceiling(observer.ViewRadius / ChunkSize);
|
||||||
|
|
||||||
|
for (int x = -chunkRadius; x <= chunkRadius; x++)
|
||||||
|
{
|
||||||
|
for (int y = -chunkRadius; y <= chunkRadius; y++)
|
||||||
|
{
|
||||||
|
for (int z = -chunkRadius; z <= chunkRadius; z++)
|
||||||
|
{
|
||||||
|
var coord = new ChunkCoord(centerChunk.X + x, centerChunk.Y + y, centerChunk.Z + z);
|
||||||
|
requiredChunks.Add(coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Unload chunks that are active but no longer required
|
||||||
|
var chunksToUnload = new List<ChunkCoord>();
|
||||||
|
foreach (var activeChunk in _activeChunks.Keys)
|
||||||
|
{
|
||||||
|
if (!requiredChunks.Contains(activeChunk) && _activeChunks[activeChunk] != ChunkState.Unloading)
|
||||||
|
{
|
||||||
|
chunksToUnload.Add(activeChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var coord in chunksToUnload)
|
||||||
|
{
|
||||||
|
_activeChunks[coord] = ChunkState.Unloading;
|
||||||
|
_pendingUnloads.Enqueue(coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Queue newly required chunks
|
||||||
|
foreach (var coord in requiredChunks)
|
||||||
|
{
|
||||||
|
if (!_activeChunks.ContainsKey(coord))
|
||||||
|
{
|
||||||
|
// Mark as unloaded so we don't queue it multiple times
|
||||||
|
_activeChunks[coord] = ChunkState.Unloaded;
|
||||||
|
_pendingLoads.Enqueue(coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Process only ONE chunk load per frame to prevent stuttering
|
||||||
|
if (_pendingLoads.Count > 0)
|
||||||
|
{
|
||||||
|
var chunkToLoad = _pendingLoads.Dequeue();
|
||||||
|
// Double check it wasn't unloaded before we got around to loading it
|
||||||
|
if (_activeChunks.TryGetValue(chunkToLoad, out var state) && state == ChunkState.Unloaded)
|
||||||
|
{
|
||||||
|
LoadChunk(chunkToLoad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Process only ONE chunk unload tagging per frame
|
||||||
|
if (_pendingUnloads.Count > 0)
|
||||||
|
{
|
||||||
|
var chunkToUnload = _pendingUnloads.Dequeue();
|
||||||
|
UnloadChunk(chunkToUnload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkCoord GetChunkCoord(Vector3 worldPos)
|
||||||
|
{
|
||||||
|
return new ChunkCoord(
|
||||||
|
(int)MathF.Floor(worldPos.X / ChunkSize),
|
||||||
|
(int)MathF.Floor(worldPos.Y / ChunkSize),
|
||||||
|
(int)MathF.Floor(worldPos.Z / ChunkSize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadChunk(ChunkCoord coord)
|
||||||
|
{
|
||||||
|
_activeChunks[coord] = ChunkState.Loading;
|
||||||
|
|
||||||
|
// TODO: In a real system, you'd queue async I/O here to read SceneData for this chunk
|
||||||
|
// and spawn the entities. Once they are all spawned, set state to Loaded.
|
||||||
|
|
||||||
|
// For demonstration, immediately set to loaded.
|
||||||
|
_activeChunks[coord] = ChunkState.Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnloadChunk(ChunkCoord coord)
|
||||||
|
{
|
||||||
|
// Instead of destroying everything instantly, we tag the entities as 'Unloaded'
|
||||||
|
// so that they stop participating in rendering/gameplay, and get destroyed slowly
|
||||||
|
// by the ChunkTeardownSystem.
|
||||||
|
long coordId = coord.Id;
|
||||||
|
foreach (var entity in _chunkMemberFilter.Entities)
|
||||||
|
{
|
||||||
|
var chunkMember = Get<ChunkMemberComponent>(entity);
|
||||||
|
if (chunkMember.ChunkId == coordId)
|
||||||
|
{
|
||||||
|
Set(entity, new ChunkUnloadPendingTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately remove it from the required grid so it can be re-loaded
|
||||||
|
// if the player turns around quickly, while older entities are just garbage collected.
|
||||||
|
_activeChunks.Remove(coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Scene.Streaming;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A centralized cleanup system for slowly destroying chunk entities to avoid frame stutters.
|
||||||
|
/// </summary>
|
||||||
|
public class ChunkTeardownSystem : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
private readonly Filter _unloadedFilter;
|
||||||
|
|
||||||
|
// Adjustable limit to prevent massive stutters when unloading chunks.
|
||||||
|
public int MaxEntitiesToDestroyPerFrame { get; set; } = 250;
|
||||||
|
|
||||||
|
public ChunkTeardownSystem(World world) : base(world)
|
||||||
|
{
|
||||||
|
_unloadedFilter = FilterBuilder
|
||||||
|
.Include<ChunkUnloadPendingTag>()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
int destroyed = 0;
|
||||||
|
|
||||||
|
foreach (var entity in _unloadedFilter.Entities)
|
||||||
|
{
|
||||||
|
if (destroyed >= MaxEntitiesToDestroyPerFrame) break;
|
||||||
|
|
||||||
|
Destroy(entity);
|
||||||
|
destroyed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Scene.Streaming;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks an entity as a streaming observer (e.g. the player camera) that causes chunks
|
||||||
|
/// to be loaded around it.
|
||||||
|
/// </summary>
|
||||||
|
public struct ChunkObserverComponent
|
||||||
|
{
|
||||||
|
public float ViewRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tags an entity as belonging to a specific chunk, allowing it to be unloaded when the chunk is out of range.
|
||||||
|
/// </summary>
|
||||||
|
public struct ChunkMemberComponent
|
||||||
|
{
|
||||||
|
// A 64-bit spatial hash combining the X, Y, and Z coordinates.
|
||||||
|
public long ChunkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Added to entities that belong to a chunk that has been unloaded.
|
||||||
|
/// A dedicated system will process and destroy these slowly over multiple frames.
|
||||||
|
/// </summary>
|
||||||
|
public struct ChunkUnloadPendingTag { }
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using ImGuiNET;
|
||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Serialization;
|
||||||
|
|
||||||
|
public static class ComponentHelper
|
||||||
|
{
|
||||||
|
// Auto generate this.
|
||||||
|
public static readonly Dictionary<Type, Func<World, Entity, ValueType>> GetComponentByType = new()
|
||||||
|
{
|
||||||
|
{ typeof(LocalTransform), (world, entity) => world.Get<LocalTransform>(entity) },
|
||||||
|
{ typeof(Root), (world, entity) => world.Get<Root>(entity) },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto generate this.
|
||||||
|
public static readonly Dictionary<Type, Action<World, Entity, ValueType>> SetComponentByType = new()
|
||||||
|
{
|
||||||
|
{ typeof(LocalTransform), (world, entity, component) => world.Set(entity, (LocalTransform)component) },
|
||||||
|
{ typeof(Root), (world, entity, component) => world.Set(entity, (Root)component) },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto generate this, but it should only contain user assignable components (so something like 'root' should be excluded).
|
||||||
|
// Maybe use an attribute for this.
|
||||||
|
public static readonly Dictionary<Type, Action<World, Entity>> AddComponentByType = new()
|
||||||
|
{
|
||||||
|
{ typeof(LocalTransform), (world, entity) => world.Set(entity, LocalTransform.Identity) },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto generate this, but also keep the option for 'custom inspectors'.
|
||||||
|
// Maybe via attribute?
|
||||||
|
public static readonly Dictionary<Type, Action<World, Entity>> ComponentInspectorByType = new()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
typeof(LocalTransform), (world, entity) =>
|
||||||
|
{
|
||||||
|
(Vector3 position, Quaternion rotation, Vector3 scale) = world.Get<LocalTransform>(entity);
|
||||||
|
Vector3 eulerAngles = MathEx.ToEulerAngles(rotation);
|
||||||
|
eulerAngles = new Vector3(float.RadiansToDegrees(eulerAngles.X), float.RadiansToDegrees(eulerAngles.Y), float.RadiansToDegrees(eulerAngles.Z));
|
||||||
|
bool isDirty = false;
|
||||||
|
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
ImGui.Text($"{nameof(LocalTransform)}");
|
||||||
|
isDirty |= ImGui.DragFloat3("Position", ref position, 0.2f, float.MinValue, float.MaxValue /*, "%f0 m" */); // TODO: right format.
|
||||||
|
isDirty |= ImGui.DragFloat3("Rotation", ref eulerAngles);
|
||||||
|
isDirty |= ImGui.DragFloat3("Scale", ref scale);
|
||||||
|
ImGui.EndGroup();
|
||||||
|
|
||||||
|
if (!isDirty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eulerAngles = new Vector3(float.DegreesToRadians(eulerAngles.X), float.DegreesToRadians(eulerAngles.Y), float.DegreesToRadians(eulerAngles.Z));
|
||||||
|
world.Set(entity, new LocalTransform(position, MathEx.ToQuaternion(eulerAngles), scale));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeof(Root), (world, entity) =>
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
ImGui.Text($"{nameof(Root)}");
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
using Nerfed.Runtime.Util;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Explore if having a WorldTransform and LocalTransfom component each holding position, rotation, scale values and the matricies is useful.
|
||||||
|
// Often you need to either get or set these values.
|
||||||
|
// If so, we probably need a utility funciton to do so. Since changing these values means that we need to update all the related data + children as well.
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// When modifying transform all the children need to be updated as well.
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Systems
|
||||||
|
{
|
||||||
|
public class LocalToWorldSystem : MoonTools.ECS.System
|
||||||
|
{
|
||||||
|
public override IReadOnlySet<Type> ReadsComponents { get; } = new HashSet<Type> { typeof(LocalTransform) };
|
||||||
|
public override IReadOnlySet<Type> WritesComponents { get; } = new HashSet<Type> { typeof(LocalToWorld) };
|
||||||
|
|
||||||
|
private readonly bool useParallelFor = true;
|
||||||
|
private const int ParallelForMinCount = 32; // Below this, parallel overhead costs more than it saves.
|
||||||
|
private static readonly System.Threading.Tasks.ParallelOptions ParallelOptions = new()
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = Environment.ProcessorCount
|
||||||
|
};
|
||||||
|
private readonly Filter rootEntitiesFilter;
|
||||||
|
private readonly Filter entitiesWithoutLocalToWorldFilter;
|
||||||
|
private readonly Action<int> updateWorldTransform;
|
||||||
|
private ParallelWriter<LocalToWorld> _parallelWriter;
|
||||||
|
|
||||||
|
public LocalToWorldSystem(World world) : base(world)
|
||||||
|
{
|
||||||
|
rootEntitiesFilter = FilterBuilder.Include<LocalTransform>().Exclude<Child>().Build();
|
||||||
|
if (useParallelFor)
|
||||||
|
{
|
||||||
|
entitiesWithoutLocalToWorldFilter = FilterBuilder.Include<LocalTransform>().Exclude<LocalToWorld>().Build();
|
||||||
|
updateWorldTransform = UpdateWorldTransformByIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update(TimeSpan delta)
|
||||||
|
{
|
||||||
|
if (rootEntitiesFilter.Empty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useParallelFor)
|
||||||
|
{
|
||||||
|
// This check is needed because some entities might not have a LocalToWorld component yet.
|
||||||
|
// Adding this during the loop will break.
|
||||||
|
Profiler.BeginSample("ParallelFor.LocalToWorldCheck");
|
||||||
|
foreach (Entity entity in entitiesWithoutLocalToWorldFilter.Entities) {
|
||||||
|
Set(entity, new LocalToWorld(Matrix4x4.Identity));
|
||||||
|
}
|
||||||
|
Profiler.EndSample();
|
||||||
|
|
||||||
|
// Acquire a ParallelWriter AFTER pre-allocation — all entities now have LocalToWorld.
|
||||||
|
// This writer only permits updating existing values; no structural mutations allowed.
|
||||||
|
_parallelWriter = World.GetParallelWriter<LocalToWorld>();
|
||||||
|
|
||||||
|
Profiler.BeginSample("ParallelFor.LocalToWorldUpdate");
|
||||||
|
if (rootEntitiesFilter.Count >= ParallelForMinCount)
|
||||||
|
{
|
||||||
|
Parallel.For(0, rootEntitiesFilter.Count, ParallelOptions, updateWorldTransform);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not enough work to justify thread overhead — run serially.
|
||||||
|
for (int i = 0; i < rootEntitiesFilter.Count; i++)
|
||||||
|
{
|
||||||
|
updateWorldTransform(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Profiler.EndSample();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (Entity entity in rootEntitiesFilter.Entities)
|
||||||
|
{
|
||||||
|
// Profiler.BeginSample("UpdateWorldTransform");
|
||||||
|
UpdateWorldTransform(entity, Matrix4x4.Identity);
|
||||||
|
// Profiler.EndSample();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWorldTransformByIndex(int entityFilterIndex)
|
||||||
|
{
|
||||||
|
// Profiler.BeginSample("UpdateWorldTransformByIndex");
|
||||||
|
Entity entity = rootEntitiesFilter.NthEntity(entityFilterIndex);
|
||||||
|
UpdateWorldTransform(entity, Matrix4x4.Identity);
|
||||||
|
// Profiler.EndSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWorldTransform(in Entity entity, Matrix4x4 localToWorldMatrix)
|
||||||
|
{
|
||||||
|
if (Has<LocalTransform>(entity))
|
||||||
|
{
|
||||||
|
LocalTransform localTransform = Get<LocalTransform>(entity);
|
||||||
|
localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS());
|
||||||
|
LocalToWorld localToWorld = new(localToWorldMatrix);
|
||||||
|
|
||||||
|
if (useParallelFor)
|
||||||
|
_parallelWriter.Set(entity, localToWorld); // thread-safe: direct write, no structural mutation
|
||||||
|
else
|
||||||
|
Set(entity, localToWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReverseSpanEnumerator<Entity> childEntities = World.InRelations<ChildParentRelation>(entity);
|
||||||
|
foreach (Entity childEntity in childEntities)
|
||||||
|
{
|
||||||
|
UpdateWorldTransform(childEntity, localToWorldMatrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
|
public class BoundedQueue<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>
|
||||||
|
{
|
||||||
|
private readonly Queue<T> queue = null;
|
||||||
|
private readonly int maxSize = 10;
|
||||||
|
private T lastAddedElement;
|
||||||
|
|
||||||
|
public BoundedQueue(int maxSize)
|
||||||
|
{
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
queue = new Queue<T>(maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enqueue(T item)
|
||||||
|
{
|
||||||
|
queue.Enqueue(item);
|
||||||
|
if (queue.Count > maxSize)
|
||||||
|
{
|
||||||
|
queue.Dequeue(); // Remove the oldest element
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAddedElement = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Dequeue()
|
||||||
|
{
|
||||||
|
return queue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Peek()
|
||||||
|
{
|
||||||
|
return queue.Peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T LastAddedElement()
|
||||||
|
{
|
||||||
|
return lastAddedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
queue.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return queue.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return queue.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return queue.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
((ICollection)queue).CopyTo(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => queue.Count;
|
||||||
|
public int Capacity => maxSize;
|
||||||
|
public bool IsSynchronized => ((ICollection)queue).IsSynchronized;
|
||||||
|
public object SyncRoot => ((ICollection)queue).SyncRoot;
|
||||||
|
int IReadOnlyCollection<T>.Count => queue.Count;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Nerfed.Runtime;
|
namespace Nerfed.Runtime;
|
||||||
|
|
||||||
public static class MathEx
|
public static class MathEx
|
||||||
@@ -17,4 +19,51 @@ public static class MathEx
|
|||||||
public static float Remap(float value, float oldMin, float oldMax, float newMin, float newMax) {
|
public static float Remap(float value, float oldMin, float oldMax, float newMin, float newMax) {
|
||||||
return (value - oldMin) / (oldMax - oldMin) * (newMax - newMin) + newMin;
|
return (value - oldMin) / (oldMax - oldMin) * (newMax - newMin) + newMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/70462758/c-sharp-how-to-convert-quaternions-to-euler-angles-xyz
|
||||||
|
public static Quaternion ToQuaternion(Vector3 v)
|
||||||
|
{
|
||||||
|
float cy = (float)Math.Cos(v.Z * 0.5);
|
||||||
|
float sy = (float)Math.Sin(v.Z * 0.5);
|
||||||
|
float cp = (float)Math.Cos(v.Y * 0.5);
|
||||||
|
float sp = (float)Math.Sin(v.Y * 0.5);
|
||||||
|
float cr = (float)Math.Cos(v.X * 0.5);
|
||||||
|
float sr = (float)Math.Sin(v.X * 0.5);
|
||||||
|
|
||||||
|
return new Quaternion
|
||||||
|
{
|
||||||
|
W = (cr * cp * cy + sr * sp * sy),
|
||||||
|
X = (sr * cp * cy - cr * sp * sy),
|
||||||
|
Y = (cr * sp * cy + sr * cp * sy),
|
||||||
|
Z = (cr * cp * sy - sr * sp * cy),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 ToEulerAngles(Quaternion q)
|
||||||
|
{
|
||||||
|
Vector3 angles = new();
|
||||||
|
|
||||||
|
// roll / x
|
||||||
|
double sinrCosp = 2 * (q.W * q.X + q.Y * q.Z);
|
||||||
|
double cosrCosp = 1 - 2 * (q.X * q.X + q.Y * q.Y);
|
||||||
|
angles.X = (float)Math.Atan2(sinrCosp, cosrCosp);
|
||||||
|
|
||||||
|
// pitch / y
|
||||||
|
double sinp = 2 * (q.W * q.Y - q.Z * q.X);
|
||||||
|
if (Math.Abs(sinp) >= 1)
|
||||||
|
{
|
||||||
|
angles.Y = (float)Math.CopySign(Math.PI / 2, sinp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
angles.Y = (float)Math.Asin(sinp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// yaw / z
|
||||||
|
double sinyCosp = 2 * (q.W * q.Z + q.X * q.Y);
|
||||||
|
double cosyCosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z);
|
||||||
|
angles.Z = (float)Math.Atan2(sinyCosp, cosyCosp);
|
||||||
|
|
||||||
|
return angles;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Util;
|
||||||
|
|
||||||
|
public static class RandomId
|
||||||
|
{
|
||||||
|
public static uint GenerateSecureRandomUInt()
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[4];
|
||||||
|
RandomNumberGenerator.Fill(buffer);
|
||||||
|
return BitConverter.ToUInt32(buffer, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using MoonTools.ECS;
|
||||||
|
using Nerfed.Runtime.Components;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Nerfed.Runtime.Util
|
||||||
|
{
|
||||||
|
// https://github.com/needle-mirror/com.unity.entities/blob/master/Unity.Transforms/TransformHelpers.cs
|
||||||
|
public static class Transform
|
||||||
|
{
|
||||||
|
public static Vector3 Forward(in this Matrix4x4 matrix) => new Vector3(matrix.M31, matrix.M32, matrix.M33);
|
||||||
|
public static Vector3 Back(in this Matrix4x4 matrix) => -matrix.Forward();
|
||||||
|
public static Vector3 Up(in this Matrix4x4 matrix) => new Vector3(matrix.M21, matrix.M22, matrix.M23);
|
||||||
|
public static Vector3 Down(in this Matrix4x4 matrix) => -matrix.Up();
|
||||||
|
public static Vector3 Right(in this Matrix4x4 matrix) => new Vector3(matrix.M11, matrix.M12, matrix.M13);
|
||||||
|
public static Vector3 Left(in this Matrix4x4 matrix) => -matrix.Right();
|
||||||
|
//public static Vector3 Translation(in this Matrix4x4 matrix) => new Vector3();
|
||||||
|
//public static Quaternion Rotation(in this Matrix4x4 matrix) => new Quaternion();
|
||||||
|
|
||||||
|
public static Matrix4x4 TRS(in this LocalTransform localTransform)
|
||||||
|
{
|
||||||
|
return Matrix4x4.CreateScale(localTransform.scale) *
|
||||||
|
Matrix4x4.CreateFromQuaternion(localTransform.rotation) *
|
||||||
|
Matrix4x4.CreateTranslation(localTransform.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the parent child relation and adds a child component.
|
||||||
|
// Relation goes from child to parent.
|
||||||
|
public static void SetParent(in World world, in Entity child, in Entity parent)
|
||||||
|
{
|
||||||
|
RemoveParent(world, child);
|
||||||
|
|
||||||
|
world.Relate(child, parent, new ChildParentRelation());
|
||||||
|
world.Set(child, new Child());
|
||||||
|
world.Remove<Root>(child);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes any parent child relation ship, thus making it a 'root' object.
|
||||||
|
public static void RemoveParent(in World world, in Entity child)
|
||||||
|
{
|
||||||
|
if (!world.HasOutRelation<ChildParentRelation>(child))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity parent = world.OutRelationSingleton<ChildParentRelation>(child);
|
||||||
|
|
||||||
|
// TODO: Check if Unrelate all also unrelates incomming relations..?
|
||||||
|
world.Unrelate<ChildParentRelation>(child, parent);
|
||||||
|
world.Remove<Child>(child);
|
||||||
|
world.Set(child, new Root());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Entity CreateBaseEntity(this World world, string tag = "")
|
||||||
|
{
|
||||||
|
Entity entity = world.CreateEntity(tag);
|
||||||
|
world.Set(entity, new Root());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force update the transform data of an entity (and children).
|
||||||
|
// Useful for when you need precise up to date transform data.
|
||||||
|
public static void ForceUpdateLocalToWorld(in World world, in Entity entity)
|
||||||
|
{
|
||||||
|
Matrix4x4 parentLocalToWorldMatrix = Matrix4x4.Identity;
|
||||||
|
|
||||||
|
if (world.HasOutRelation<ChildParentRelation>(entity)) {
|
||||||
|
Entity parent = world.OutRelationSingleton<ChildParentRelation>(entity);
|
||||||
|
|
||||||
|
if (world.Has<LocalToWorld>(parent))
|
||||||
|
{
|
||||||
|
parentLocalToWorldMatrix = world.Get<LocalToWorld>(parent).localToWorldMatrix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ForceUpdateLocalToWorld(world, entity, parentLocalToWorldMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ForceUpdateLocalToWorld(in World world, in Entity entity, Matrix4x4 localToWorldMatrix)
|
||||||
|
{
|
||||||
|
if (world.Has<LocalTransform>(entity))
|
||||||
|
{
|
||||||
|
LocalTransform localTransform = world.Get<LocalTransform>(entity);
|
||||||
|
localToWorldMatrix = Matrix4x4.Multiply(localToWorldMatrix, localTransform.TRS());
|
||||||
|
LocalToWorld localToWorld = new(localToWorldMatrix);
|
||||||
|
world.Set(entity, localToWorld);
|
||||||
|
|
||||||
|
Log.Info($"Entity {entity} | local position {localTransform.position} | world position {localToWorldMatrix.Translation}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReverseSpanEnumerator<Entity> childEntities = world.InRelations<ChildParentRelation>(entity);
|
||||||
|
foreach (Entity childEntity in childEntities)
|
||||||
|
{
|
||||||
|
ForceUpdateLocalToWorld(world, childEntity, localToWorldMatrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ namespace Nerfed.Runtime;
|
|||||||
public enum ScreenMode
|
public enum ScreenMode
|
||||||
{
|
{
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
BorderlessFullscreen,
|
FullscreenBorderless,
|
||||||
Windowed
|
Windowed,
|
||||||
|
WindowedBorderless
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,10 @@ namespace Nerfed.Runtime;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Window
|
public class Window
|
||||||
{
|
{
|
||||||
|
public event Action<Window, uint, uint> OnResizedEvent;
|
||||||
|
public event Action<Window, int, int> OnMovedEvent;
|
||||||
|
public event Action<Window> OnCloseEvent;
|
||||||
|
|
||||||
internal IntPtr Handle { get; }
|
internal IntPtr Handle { get; }
|
||||||
public ScreenMode ScreenMode { get; private set; }
|
public ScreenMode ScreenMode { get; private set; }
|
||||||
public uint Width { get; private set; }
|
public uint Width { get; private set; }
|
||||||
@@ -33,8 +37,6 @@ public class Window
|
|||||||
public string Title { get; private set;}
|
public string Title { get; private set;}
|
||||||
|
|
||||||
private bool IsDisposed;
|
private bool IsDisposed;
|
||||||
private System.Action<uint, uint> SizeChangeCallback = null;
|
|
||||||
|
|
||||||
private static readonly Dictionary<uint, Window> windowsById = new Dictionary<uint, Window>();
|
private static readonly Dictionary<uint, Window> windowsById = new Dictionary<uint, Window>();
|
||||||
|
|
||||||
public Window(GraphicsDevice graphicsDevice, WindowCreateInfo windowCreateInfo)
|
public Window(GraphicsDevice graphicsDevice, WindowCreateInfo windowCreateInfo)
|
||||||
@@ -54,10 +56,14 @@ public class Window
|
|||||||
{
|
{
|
||||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||||
}
|
}
|
||||||
else if (windowCreateInfo.ScreenMode == ScreenMode.BorderlessFullscreen)
|
else if (windowCreateInfo.ScreenMode == ScreenMode.FullscreenBorderless)
|
||||||
{
|
{
|
||||||
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
}
|
}
|
||||||
|
else if(windowCreateInfo.ScreenMode == ScreenMode.WindowedBorderless)
|
||||||
|
{
|
||||||
|
flags |= SDL.SDL_WindowFlags.SDL_WINDOW_BORDERLESS;
|
||||||
|
}
|
||||||
|
|
||||||
if (windowCreateInfo.SystemResizable)
|
if (windowCreateInfo.SystemResizable)
|
||||||
{
|
{
|
||||||
@@ -104,6 +110,9 @@ public class Window
|
|||||||
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
window.ProcessSizeChangedEvent(ref ev.window);
|
window.ProcessSizeChangedEvent(ref ev.window);
|
||||||
break;
|
break;
|
||||||
|
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MOVED:
|
||||||
|
window.ProcessMovedChangedEvent(ref ev.window);
|
||||||
|
break;
|
||||||
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
|
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
|
||||||
window.ProcessCloseEvent(ref ev.window);
|
window.ProcessCloseEvent(ref ev.window);
|
||||||
break;
|
break;
|
||||||
@@ -117,14 +126,17 @@ public class Window
|
|||||||
Width = newWidth;
|
Width = newWidth;
|
||||||
Height = newHeight;
|
Height = newHeight;
|
||||||
|
|
||||||
if (SizeChangeCallback != null)
|
OnResizedEvent?.Invoke(this, Width, Height);
|
||||||
{
|
|
||||||
SizeChangeCallback(newWidth, newHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ProcessMovedChangedEvent(ref SDL.SDL_WindowEvent ev)
|
||||||
|
{
|
||||||
|
OnMovedEvent?.Invoke(this, ev.data1, ev.data2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessCloseEvent(ref SDL.SDL_WindowEvent ev)
|
private void ProcessCloseEvent(ref SDL.SDL_WindowEvent ev)
|
||||||
{
|
{
|
||||||
|
OnCloseEvent?.Invoke(this);
|
||||||
Engine.GraphicsDevice.UnclaimWindow(this);
|
Engine.GraphicsDevice.UnclaimWindow(this);
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
@@ -140,7 +152,7 @@ public class Window
|
|||||||
{
|
{
|
||||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
|
||||||
}
|
}
|
||||||
else if (screenMode == ScreenMode.BorderlessFullscreen)
|
else if (screenMode == ScreenMode.FullscreenBorderless)
|
||||||
{
|
{
|
||||||
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||||
}
|
}
|
||||||
@@ -195,14 +207,6 @@ public class Window
|
|||||||
SDL.SDL_ShowWindow(Handle);
|
SDL.SDL_ShowWindow(Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// You can specify a method to run when the window size changes.
|
|
||||||
/// </summary>
|
|
||||||
public void RegisterSizeChangeCallback(System.Action<uint, uint> sizeChangeCallback)
|
|
||||||
{
|
|
||||||
SizeChangeCallback = sizeChangeCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (!IsDisposed)
|
if (!IsDisposed)
|
||||||
|
|||||||
+28
-6
@@ -5,16 +5,38 @@ VisualStudioVersion = 17.10.35013.160
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nerfed.Runtime", "Nerfed.Runtime\Nerfed.Runtime.csproj", "{98E09BAF-587F-4238-89BD-7693C036C233}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nerfed.Runtime", "Nerfed.Runtime\Nerfed.Runtime.csproj", "{98E09BAF-587F-4238-89BD-7693C036C233}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nerfed.Builder", "Nerfed.Builder\Nerfed.Builder.csproj", "{1B88DE56-2AD8-441E-9B10-073AA43840BF}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nerfed.Editor", "Nerfed.Editor\Nerfed.Editor.csproj", "{FF7D032D-7F0B-4700-A818-0606D66AECF8}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF} = {1B88DE56-2AD8-441E-9B10-073AA43840BF}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Test|x64 = Test|x64
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|x64 = Release|x64
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{98E09BAF-587F-4238-89BD-7693C036C233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF}.Test|x64.ActiveCfg = Test|x64
|
||||||
{98E09BAF-587F-4238-89BD-7693C036C233}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF}.Test|x64.Build.0 = Test|x64
|
||||||
{98E09BAF-587F-4238-89BD-7693C036C233}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF}.Release|x64.ActiveCfg = Release|x64
|
||||||
{98E09BAF-587F-4238-89BD-7693C036C233}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF}.Release|x64.Build.0 = Release|x64
|
||||||
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{1B88DE56-2AD8-441E-9B10-073AA43840BF}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{FF7D032D-7F0B-4700-A818-0606D66AECF8}.Test|x64.ActiveCfg = Test|x64
|
||||||
|
{FF7D032D-7F0B-4700-A818-0606D66AECF8}.Test|x64.Build.0 = Test|x64
|
||||||
|
{FF7D032D-7F0B-4700-A818-0606D66AECF8}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{FF7D032D-7F0B-4700-A818-0606D66AECF8}.Release|x64.Build.0 = Release|x64
|
||||||
|
{FF7D032D-7F0B-4700-A818-0606D66AECF8}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{FF7D032D-7F0B-4700-A818-0606D66AECF8}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{98E09BAF-587F-4238-89BD-7693C036C233}.Test|x64.ActiveCfg = Test|x64
|
||||||
|
{98E09BAF-587F-4238-89BD-7693C036C233}.Test|x64.Build.0 = Test|x64
|
||||||
|
{98E09BAF-587F-4238-89BD-7693C036C233}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{98E09BAF-587F-4238-89BD-7693C036C233}.Release|x64.Build.0 = Release|x64
|
||||||
|
{98E09BAF-587F-4238-89BD-7693C036C233}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{98E09BAF-587F-4238-89BD-7693C036C233}.Debug|x64.Build.0 = Debug|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
# Nerfed
|
# Nerfed
|
||||||
|
|
||||||
nerfed game engine
|
nerfed game engine
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Third-Party Licenses
|
||||||
|
|
||||||
|
This project includes third-party libraries with their respective licenses, which can be found in the [THIRD_PARTY_LICENSES](THIRD_PARTY_LICENSES) file.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Third-Party Licenses
|
||||||
|
|
||||||
|
## ImGui
|
||||||
|
|
||||||
|
**Name:** Dear ImGui
|
||||||
|
**License:** MIT License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2014-2022 Omar Cornut
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
Executable
BIN
Binary file not shown.
@@ -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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user