26 Commits

Author SHA1 Message Date
max
60b85960ff EditorGui setup 2024-07-12 19:09:59 +02:00
max
b003ffbaec Merge branch 'main' into ImGui 2024-07-12 18:08:28 +02:00
max
777059489c updated project structure 2024-07-12 17:59:06 +02:00
max
16b04ea22a Editor dummy code 2024-07-12 17:27:01 +02:00
max
4b824f3205 Add License 2024-07-12 16:38:54 +02:00
max
9890026656 Fix viewports 2024-07-12 16:38:42 +02:00
max
d8b41b0827 Fix viewports 2024-07-12 16:38:30 +02:00
dd3bbf1d5b - Re-added win libs 2024-07-11 23:48:45 +02:00
38080703ec - Deleted ignored folders 2024-07-11 23:47:43 +02:00
fe582c4fba - Added build configurations
- Configured csprojects
- CopyLibs functionality in builder
2024-07-11 23:46:32 +02:00
max
42b978e8c9 working on window handling 2024-07-10 23:18:56 +02:00
max
2c839d8fad Rename and mouse cursor support 2024-07-10 21:50:04 +02:00
97c2b308f1 Added builder
Added shader importer
2024-07-06 23:33:04 +02:00
max
1eb899b240 gui events
fix scroll input
handle gui side, move and closed events
2024-07-06 01:29:12 +02:00
max
7cbb745721 fix library and input 2024-07-05 23:22:11 +02:00
max
1e1ed303ad Added ImGui.NET
Start working on controller.
2024-07-05 21:56:14 +02:00
max
26eb1da3f0 dirty libs? 2024-07-05 15:18:57 +02:00
max
e46b43281f windows libs
updated ignore file
included libs
2024-07-05 15:03:32 +02:00
1c64d6fe54 Added copy libs targets
Added native libs for linux
2024-07-05 14:46:43 +02:00
5d2c350bb8 Removed old library dir 2024-07-05 14:34:37 +02:00
8334a24fd1 Setup entry point + integrated moonworks stuff 2024-07-05 14:32:58 +02:00
max
e7a4a862be Added wellspring and dev1dfile 2024-07-05 14:13:09 +02:00
max
56cb14441f Added FAudio submodule 2024-07-05 14:07:13 +02:00
max
cd8beb0337 fix refresh folder 2024-07-05 13:54:42 +02:00
max
4794fbc647 Updated project libraries 2024-07-05 13:43:54 +02:00
max
8169d43a97 Move runtime libraries into project folder 2024-07-05 13:43:54 +02:00
173 changed files with 18809 additions and 27 deletions

14
.gitignore vendored
View File

@ -1,5 +1,3 @@
imgui.ini
#------------------------- Rider -------------------------
# Source: https://github.com/JetBrains/resharper-rider-samples/blob/master/.gitignore
@ -451,3 +449,15 @@ FodyWeavers.xsd
# Built Visual Studio Code Extensions
*.vsix
#------------------------- Nerfed -------------------------
Bin/
Intermediate/
imgui.ini
# include libs
!/Native/lib64
!/Native/x64

16
.gitmodules vendored
View File

@ -1,6 +1,18 @@
[submodule "Libraries/RefreshCS"]
path = Libraries/RefreshCS
path = Nerfed.Runtime/Libraries/RefreshCS
url = https://github.com/MoonsideGames/RefreshCS.git
[submodule "Libraries/SDL2CS"]
path = Libraries/SDL2CS
path = Nerfed.Runtime/Libraries/SDL2CS
url = https://github.com/flibitijibibo/SDL2-CS.git
[submodule "Nerfed.Runtime/Libraries/FAudio"]
path = Nerfed.Runtime/Libraries/FAudio
url = https://github.com/FNA-XNA/FAudio.git
[submodule "Nerfed.Runtime/Libraries/WellspringCS"]
path = Nerfed.Runtime/Libraries/WellspringCS
url = https://github.com/MoonsideGames/WellspringCS.git
[submodule "Nerfed.Runtime/Libraries/dav1dfile"]
path = Nerfed.Runtime/Libraries/dav1dfile
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

13
.idea/.idea.Nerfed/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.Nerfed.iml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

4
.idea/.idea.Nerfed/.idea/encodings.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

11
.idea/.idea.Nerfed/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/Nerfed.Runtime/Libraries/FAudio" 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>
</project>

7
Directory.Build.props Normal file
View File

@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<OutDir>../Bin/$(MSBuildProjectName)</OutDir>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">../Intermediate/$(MSBuildProjectName)</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>

9
LICENSE Normal file
View File

@ -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
Native/lib64/FAudio.so (Stored with Git LFS) Executable file

Binary file not shown.

BIN
Native/lib64/Refresh.so (Stored with Git LFS) Executable file

Binary file not shown.

BIN
Native/lib64/SDL2.so (Stored with Git LFS) Executable file

Binary file not shown.

BIN
Native/lib64/Wellspring.so (Stored with Git LFS) Executable file

Binary file not shown.

BIN
Native/lib64/cimgui.so (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/lib64/dav1dfile.so (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/x64/FAudio.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/x64/Refresh.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/x64/SDL2.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/x64/Wellspring.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/x64/cimgui.dll (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Native/x64/dav1dfile.dll (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
namespace Nerfed.Builder;
public class ArgumentAttribute : Attribute
{
public string ArgKey { get; }
public ArgumentAttribute(string argKey)
{
ArgKey = argKey;
}
}

View File

@ -0,0 +1,16 @@
namespace Nerfed.Builder;
public class BuildArgs
{
[Argument("-build")]
public bool Build { get; set; }
[Argument("-projectPath")]
public string ProjectPath { get; set; }
[Argument("-platform")]
public string Platform { get; set; }
[Argument("-content")]
public List<string> ContentFiles { get; set; }
}

View File

@ -0,0 +1,149 @@
using System.Diagnostics;
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();
ShaderImporter shaderImporter = new ShaderImporter();
importers.Add(".vert", shaderImporter); // Vertex shader
importers.Add(".frag", shaderImporter); // Fragment shader
importers.Add(".tesc", shaderImporter); // Tessellation control shader
importers.Add(".tese", shaderImporter); // Tessellation evaluation shader
importers.Add(".geom", shaderImporter); // Geometry shader
importers.Add(".comp", shaderImporter); // Compute shader
}
public void Run(BuildArgs args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
CopyLibs(args.ProjectPath);
List<string> contentFiles = args.ContentFiles;
string absContentPath = $"{args.ProjectPath}/{PathUtil.ContentFolderName}";
// If no files are provided, build all content.
if (args.ContentFiles == null)
{
contentFiles = [];
CollectAssetFiles(absContentPath, absContentPath, ref contentFiles);
}
string importPath = $"{args.ProjectPath}/{PathUtil.ImportFolderName}";
ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = contentFiles.Count
};
Parallel.ForEach(contentFiles, parallelOptions, relativeFile =>
{
try
{
string inFile = $"{args.ProjectPath}/{PathUtil.ContentFolderName}/{relativeFile}";
if (!File.Exists(inFile))
{
Console.Error.WriteLine($"Asset file '{relativeFile}' not found");
return;
}
string outFile = $"{importPath}/{relativeFile}{PathUtil.ImportedFileExtension}";
FileInfo inFileInfo = new FileInfo(inFile);
FileInfo outFileInfo = new FileInfo(outFile);
if (!FileUtil.IsNewer(inFileInfo, outFileInfo))
{
// File has not changed since last build, no need to build this one.
return;
}
string outDir = Path.GetDirectoryName(outFile);
if (!Directory.Exists(outDir))
{
Directory.CreateDirectory(outDir);
}
string ext = Path.GetExtension(inFile).ToLower();
if (importers.TryGetValue(ext, out IImporter importer))
{
importer.Import(inFile, outFile);
}
else
{
rawFileImporter.Import(inFile, outFile);
}
Console.WriteLine(relativeFile);
}
catch (Exception e)
{
Console.Error.WriteLine($"Import error on asset '{relativeFile}': {e.Message}");
}
});
Console.WriteLine($"Build content completed in {stopwatch.Elapsed.TotalSeconds:F2} seconds");
}
private void CopyLibs(string projectPath)
{
string libDir = $"{Directory.GetCurrentDirectory()}/../../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))
{
if (Path.GetExtension(file).Equals(PathUtil.ImportFileExtension, StringComparison.CurrentCultureIgnoreCase))
{
continue;
}
string relativeFile = file.Substring(assetDir.Length, file.Length - assetDir.Length);
if (relativeFile[0] == Path.DirectorySeparatorChar || relativeFile[0] == Path.AltDirectorySeparatorChar)
{
relativeFile = relativeFile.Substring(1, relativeFile.Length - 1);
}
files.Add(relativeFile);
}
foreach (string subDir in Directory.EnumerateDirectories(dir))
{
CollectAssetFiles(assetDir, subDir, ref files);
}
}
public void Dispose() { }
}

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
namespace Nerfed.Builder;
public interface IImporter
{
void Import(string inFile, string outFile);
}

View File

@ -0,0 +1,9 @@
namespace Nerfed.Builder;
public class RawFileImporter : IImporter
{
public void Import(string inFile, string outFile)
{
FileUtil.Copy(inFile, outFile);
}
}

View File

@ -0,0 +1,33 @@
using System.Diagnostics;
namespace Nerfed.Builder;
public class ShaderImporter : IImporter
{
public void Import(string inFile, string outFile)
{
using (Process proc = new Process())
{
string glslc;
if (OperatingSystem.IsWindows())
{
glslc = "Win64/glslc.exe";
}
else if (OperatingSystem.IsLinux())
{
glslc = "Linux/glslc";
}
else
{
throw new PlatformNotSupportedException("No shader compiler found for current platform");
}
proc.StartInfo.FileName = glslc;
proc.StartInfo.Arguments = @$"""{inFile}"" -o ""{outFile}"" -c";
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.Start();
proc.WaitForExit();
}
}
}

View File

@ -0,0 +1,25 @@
<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' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Test|x64' ">
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<Optimize>true</Optimize>
</PropertyGroup>
</Project>

View File

@ -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>

View File

@ -0,0 +1,6 @@
namespace Nerfed.Builder;
public class PackageArgs
{
}

View File

@ -0,0 +1,12 @@
namespace Nerfed.Builder;
public class Packager : IDisposable
{
public void Run(PackageArgs args)
{
}
public void Dispose()
{
}
}

View File

@ -0,0 +1,10 @@
namespace Nerfed.Builder;
public static class PathUtil
{
public const string ImportedFileExtension = ".bin";
public const string BuildFolderName = ".build";
public const string ImportFileExtension = ".import";
public const string ImportFolderName = $"{BuildFolderName}/Import";
public const string ContentFolderName = "Content";
}

70
Nerfed.Builder/Program.cs Normal file
View File

@ -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;
}
}

View File

@ -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>

View File

@ -0,0 +1,71 @@
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();
}
}
}

View File

@ -0,0 +1,30 @@
<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' ">
</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" />
</Project>

42
Nerfed.Editor/Program.cs Normal file
View File

@ -0,0 +1,42 @@
using Nerfed.Runtime;
namespace Nerfed.Editor;
internal class Program
{
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()
{
// Open project.
// Setip EditorGui.
EditorGui.Initialize();
}
private static void HandleOnUpdate()
{
// Editor Update.
EditorGui.Update();
// Try Catch UserCode Update.
}
private static void HandleOnRender()
{
EditorGui.Render();
}
private static void HandleOnQuit()
{
EditorGui.Quit();
}
}

24
Nerfed.Runtime/Assert.cs Normal file
View File

@ -0,0 +1,24 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Nerfed.Runtime;
public class AssertionException(string msg) : Exception(msg);
public static class Assert
{
[Conditional("DEBUG"), DebuggerHidden]
public static void Debug([DoesNotReturnIf(false)] bool cond, [CallerArgumentExpression("cond")] string expression = "") {
if (!cond) {
throw new AssertionException(expression);
}
}
[DebuggerHidden]
public static void Always([DoesNotReturnIf(false)] bool cond, [CallerArgumentExpression("cond")] string expression = "") {
if (!cond) {
throw new AssertionException(expression);
}
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Contains raw audio data in a specified Format. <br/>
/// Submit this to a SourceVoice to play audio.
/// </summary>
public class AudioBuffer : AudioResource
{
IntPtr BufferDataPtr;
uint BufferDataLength;
private bool OwnsBufferData;
public Format Format { get; }
/// <summary>
/// Create a new AudioBuffer.
/// </summary>
/// <param name="ownsBufferData">If true, the buffer data will be destroyed when this AudioBuffer is destroyed.</param>
public AudioBuffer(
AudioDevice device,
Format format,
IntPtr bufferPtr,
uint bufferLengthInBytes,
bool ownsBufferData) : base(device)
{
Format = format;
BufferDataPtr = bufferPtr;
BufferDataLength = bufferLengthInBytes;
OwnsBufferData = ownsBufferData;
}
/// <summary>
/// Create another AudioBuffer from this audio buffer.
/// It will not own the buffer data.
/// </summary>
/// <param name="offset">Offset in bytes from the top of the original buffer.</param>
/// <param name="length">Length in bytes of the new buffer.</param>
/// <returns></returns>
public AudioBuffer Slice(int offset, uint length)
{
return new AudioBuffer(Device, Format, BufferDataPtr + offset, length, false);
}
/// <summary>
/// Create an FAudioBuffer struct from this AudioBuffer.
/// </summary>
/// <param name="loop">Whether we should set the FAudioBuffer to loop.</param>
public FAudio.FAudioBuffer ToFAudioBuffer(bool loop = false)
{
return new FAudio.FAudioBuffer
{
Flags = FAudio.FAUDIO_END_OF_STREAM,
pContext = IntPtr.Zero,
pAudioData = BufferDataPtr,
AudioBytes = BufferDataLength,
PlayBegin = 0,
PlayLength = 0,
LoopBegin = 0,
LoopLength = 0,
LoopCount = loop ? FAudio.FAUDIO_LOOP_INFINITE : 0
};
}
protected override unsafe void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (OwnsBufferData)
{
NativeMemory.Free((void*) BufferDataPtr);
}
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,146 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Streamable audio in Ogg format.
/// </summary>
public class AudioDataOgg : AudioDataStreamable
{
private IntPtr FileDataPtr = IntPtr.Zero;
private IntPtr VorbisHandle = IntPtr.Zero;
private string FilePath;
public override bool Loaded => VorbisHandle != IntPtr.Zero;
public override uint DecodeBufferSize => 32768;
public AudioDataOgg(AudioDevice device, string filePath) : base(device)
{
FilePath = filePath;
IntPtr handle = FAudio.stb_vorbis_open_filename(filePath, out int error, IntPtr.Zero);
if (error != 0)
{
throw new InvalidOperationException("Error loading file!");
}
FAudio.stb_vorbis_info info = FAudio.stb_vorbis_get_info(handle);
Format = new Format
{
Tag = FormatTag.IEEE_FLOAT,
BitsPerSample = 32,
Channels = (ushort) info.channels,
SampleRate = info.sample_rate
};
FAudio.stb_vorbis_close(handle);
}
public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
{
int lengthInFloats = bufferLengthInBytes / sizeof(float);
/* NOTE: this function returns samples per channel, not total samples */
int samples = FAudio.stb_vorbis_get_samples_float_interleaved(
VorbisHandle,
Format.Channels,
(IntPtr) buffer,
lengthInFloats
);
int sampleCount = samples * Format.Channels;
reachedEnd = sampleCount < lengthInFloats;
filledLengthInBytes = sampleCount * sizeof(float);
}
/// <summary>
/// Prepares the Ogg data for streaming.
/// </summary>
public override unsafe void Load()
{
if (!Loaded)
{
FileStream fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
Span<byte> fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
fileStream.ReadExactly(fileDataSpan);
fileStream.Close();
VorbisHandle = FAudio.stb_vorbis_open_memory(FileDataPtr, fileDataSpan.Length, out int error, IntPtr.Zero);
if (error != 0)
{
NativeMemory.Free((void*) FileDataPtr);
Log.Error("Error opening OGG file!");
Log.Error("Error: " + error);
throw new InvalidOperationException("Error opening OGG file!");
}
}
}
public override void Seek(uint sampleFrame)
{
FAudio.stb_vorbis_seek(VorbisHandle, sampleFrame);
}
/// <summary>
/// Unloads the Ogg data, freeing resources.
/// </summary>
public override unsafe void Unload()
{
if (Loaded)
{
FAudio.stb_vorbis_close(VorbisHandle);
NativeMemory.Free((void*) FileDataPtr);
VorbisHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
}
/// <summary>
/// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
/// </summary>
public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
{
IntPtr filePointer = FAudio.stb_vorbis_open_filename(filePath, out int error, IntPtr.Zero);
if (error != 0)
{
throw new InvalidOperationException("Error loading file!");
}
FAudio.stb_vorbis_info info = FAudio.stb_vorbis_get_info(filePointer);
long lengthInFloats =
FAudio.stb_vorbis_stream_length_in_samples(filePointer) * info.channels;
long lengthInBytes = lengthInFloats * Marshal.SizeOf<float>();
void* buffer = NativeMemory.Alloc((nuint) lengthInBytes);
FAudio.stb_vorbis_get_samples_float_interleaved(
filePointer,
info.channels,
(nint) buffer,
(int) lengthInFloats
);
FAudio.stb_vorbis_close(filePointer);
Format format = new Format
{
Tag = FormatTag.IEEE_FLOAT,
BitsPerSample = 32,
Channels = (ushort) info.channels,
SampleRate = info.sample_rate
};
return new AudioBuffer(
device,
format,
(nint) buffer,
(uint) lengthInBytes,
true);
}
}

View File

@ -0,0 +1,163 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Streamable audio in QOA format.
/// </summary>
public class AudioDataQoa : AudioDataStreamable
{
private IntPtr QoaHandle = IntPtr.Zero;
private IntPtr FileDataPtr = IntPtr.Zero;
private string FilePath;
private const uint QOA_MAGIC = 0x716f6166; /* 'qoaf' */
public override bool Loaded => QoaHandle != IntPtr.Zero;
private uint decodeBufferSize;
public override uint DecodeBufferSize => decodeBufferSize;
public AudioDataQoa(AudioDevice device, string filePath) : base(device)
{
FilePath = filePath;
using FileStream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
using BinaryReader reader = new BinaryReader(stream);
UInt64 fileHeader = ReverseEndianness(reader.ReadUInt64());
if ((fileHeader >> 32) != QOA_MAGIC)
{
throw new InvalidOperationException("Specified file is not a QOA file.");
}
uint totalSamplesPerChannel = (uint) (fileHeader & (0xFFFFFFFF));
if (totalSamplesPerChannel == 0)
{
throw new InvalidOperationException("Specified file is not a valid QOA file.");
}
UInt64 frameHeader = ReverseEndianness(reader.ReadUInt64());
uint channels = (uint) ((frameHeader >> 56) & 0x0000FF);
uint samplerate = (uint) ((frameHeader >> 32) & 0xFFFFFF);
uint samplesPerChannelPerFrame = (uint) ((frameHeader >> 16) & 0x00FFFF);
Format = new Format
{
Tag = FormatTag.PCM,
BitsPerSample = 16,
Channels = (ushort) channels,
SampleRate = samplerate
};
decodeBufferSize = channels * samplesPerChannelPerFrame * sizeof(short);
}
public override unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd)
{
int lengthInShorts = bufferLengthInBytes / sizeof(short);
// NOTE: this function returns samples per channel!
uint samples = FAudio.qoa_decode_next_frame(QoaHandle, (short*) buffer);
uint sampleCount = samples * Format.Channels;
reachedEnd = sampleCount < lengthInShorts;
filledLengthInBytes = (int) (sampleCount * sizeof(short));
}
/// <summary>
/// Prepares qoa data for streaming.
/// </summary>
public override unsafe void Load()
{
if (!Loaded)
{
FileStream fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
FileDataPtr = (nint) NativeMemory.Alloc((nuint) fileStream.Length);
Span<byte> fileDataSpan = new Span<byte>((void*) FileDataPtr, (int) fileStream.Length);
fileStream.ReadExactly(fileDataSpan);
fileStream.Close();
QoaHandle = FAudio.qoa_open_from_memory((char*) FileDataPtr, (uint) fileDataSpan.Length, 0);
if (QoaHandle == IntPtr.Zero)
{
NativeMemory.Free((void*) FileDataPtr);
Log.Error("Error opening QOA file!");
throw new InvalidOperationException("Error opening QOA file!");
}
}
}
public override void Seek(uint sampleFrame)
{
FAudio.qoa_seek_frame(QoaHandle, (int) sampleFrame);
}
/// <summary>
/// Unloads the qoa data, freeing resources.
/// </summary>
public override unsafe void Unload()
{
if (Loaded)
{
FAudio.qoa_close(QoaHandle);
NativeMemory.Free((void*) FileDataPtr);
QoaHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
}
/// <summary>
/// Loads the entire qoa file into an AudioBuffer. Useful for static audio.
/// </summary>
public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
{
using FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
void* fileDataPtr = NativeMemory.Alloc((nuint) fileStream.Length);
Span<byte> fileDataSpan = new Span<byte>(fileDataPtr, (int) fileStream.Length);
fileStream.ReadExactly(fileDataSpan);
fileStream.Close();
IntPtr qoaHandle = FAudio.qoa_open_from_memory((char*) fileDataPtr, (uint) fileDataSpan.Length, 0);
if (qoaHandle == 0)
{
NativeMemory.Free(fileDataPtr);
Log.Error("Error opening QOA file!");
throw new InvalidOperationException("Error opening QOA file!");
}
FAudio.qoa_attributes(qoaHandle, out uint channels, out uint samplerate, out uint samples_per_channel_per_frame, out uint total_samples_per_channel);
uint bufferLengthInBytes = total_samples_per_channel * channels * sizeof(short);
void* buffer = NativeMemory.Alloc(bufferLengthInBytes);
FAudio.qoa_decode_entire(qoaHandle, (short*) buffer);
FAudio.qoa_close(qoaHandle);
NativeMemory.Free(fileDataPtr);
Format format = new Format
{
Tag = FormatTag.PCM,
BitsPerSample = 16,
Channels = (ushort) channels,
SampleRate = samplerate
};
return new AudioBuffer(device, format, (nint) buffer, bufferLengthInBytes, true);
}
private static unsafe UInt64 ReverseEndianness(UInt64 value)
{
byte* bytes = (byte*) &value;
return
((UInt64)(bytes[0]) << 56) | ((UInt64)(bytes[1]) << 48) |
((UInt64)(bytes[2]) << 40) | ((UInt64)(bytes[3]) << 32) |
((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
}
}

View File

@ -0,0 +1,48 @@
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Use this in conjunction with a StreamingVoice to play back streaming audio data.
/// </summary>
public abstract class AudioDataStreamable : AudioResource
{
public Format Format { get; protected set; }
public abstract bool Loaded { get; }
public abstract uint DecodeBufferSize { get; }
protected AudioDataStreamable(AudioDevice device) : base(device)
{
}
/// <summary>
/// Loads the raw audio data into memory to prepare it for stream decoding.
/// </summary>
public abstract void Load();
/// <summary>
/// Unloads the raw audio data from memory.
/// </summary>
public abstract void Unload();
/// <summary>
/// Seeks to the given sample frame.
/// </summary>
public abstract void Seek(uint sampleFrame);
/// <summary>
/// Attempts to decodes data of length bufferLengthInBytes into the provided buffer.
/// </summary>
/// <param name="buffer">The buffer that decoded bytes will be placed into.</param>
/// <param name="bufferLengthInBytes">Requested length of decoded audio data.</param>
/// <param name="filledLengthInBytes">How much data was actually filled in by the decode.</param>
/// <param name="reachedEnd">Whether the end of the data was reached on this decode.</param>
public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
Unload();
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
public static class AudioDataWav
{
/// <summary>
/// Create an AudioBuffer containing all the WAV audio data in a file.
/// </summary>
/// <returns></returns>
public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
{
// mostly borrowed from https://github.com/FNA-XNA/FNA/blob/b71b4a35ae59970ff0070dea6f8620856d8d4fec/src/Audio/SoundEffect.cs#L385
// WaveFormatEx data
ushort wFormatTag;
ushort nChannels;
uint nSamplesPerSec;
uint nAvgBytesPerSec;
ushort nBlockAlign;
ushort wBitsPerSample;
using FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using BinaryReader reader = new BinaryReader(stream);
// RIFF Signature
string signature = new string(reader.ReadChars(4));
if (signature != "RIFF")
{
throw new NotSupportedException("Specified stream is not a wave file.");
}
reader.ReadUInt32(); // Riff Chunk Size
string wformat = new string(reader.ReadChars(4));
if (wformat != "WAVE")
{
throw new NotSupportedException("Specified stream is not a wave file.");
}
// WAVE Header
string format_signature = new string(reader.ReadChars(4));
while (format_signature != "fmt ")
{
reader.ReadBytes(reader.ReadInt32());
format_signature = new string(reader.ReadChars(4));
}
int format_chunk_size = reader.ReadInt32();
wFormatTag = reader.ReadUInt16();
nChannels = reader.ReadUInt16();
nSamplesPerSec = reader.ReadUInt32();
nAvgBytesPerSec = reader.ReadUInt32();
nBlockAlign = reader.ReadUInt16();
wBitsPerSample = reader.ReadUInt16();
// Reads residual bytes
if (format_chunk_size > 16)
{
reader.ReadBytes(format_chunk_size - 16);
}
// data Signature
string data_signature = new string(reader.ReadChars(4));
while (data_signature.ToLowerInvariant() != "data")
{
reader.ReadBytes(reader.ReadInt32());
data_signature = new string(reader.ReadChars(4));
}
if (data_signature != "data")
{
throw new NotSupportedException("Specified wave file is not supported.");
}
int waveDataLength = reader.ReadInt32();
void* waveDataBuffer = NativeMemory.Alloc((nuint) waveDataLength);
Span<byte> waveDataSpan = new Span<byte>(waveDataBuffer, waveDataLength);
stream.ReadExactly(waveDataSpan);
Format format = new Format
{
Tag = (FormatTag) wFormatTag,
BitsPerSample = wBitsPerSample,
Channels = nChannels,
SampleRate = nSamplesPerSec
};
return new AudioBuffer(
device,
format,
(nint) waveDataBuffer,
(uint) waveDataLength,
true
);
}
}

View File

@ -0,0 +1,309 @@
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// AudioDevice manages all audio-related concerns.
/// </summary>
public class AudioDevice : IDisposable
{
public IntPtr Handle { get; }
public byte[] Handle3D { get; }
public FAudio.FAudioDeviceDetails DeviceDetails { get; }
private IntPtr trueMasteringVoice;
// this is a fun little trick where we use a submix voice as a "faux" mastering voice
// this lets us maintain API consistency for effects like panning and reverb
private SubmixVoice fauxMasteringVoice;
public SubmixVoice MasteringVoice => fauxMasteringVoice;
public float CurveDistanceScalar = 1f;
public float DopplerScale = 1f;
public float SpeedOfSound = 343.5f;
private readonly HashSet<GCHandle> resourceHandles = new HashSet<GCHandle>();
private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
private SourceVoicePool VoicePool;
private List<SourceVoice> VoicesToReturn = new List<SourceVoice>();
private const int Step = 200;
private TimeSpan UpdateInterval;
private System.Diagnostics.Stopwatch TickStopwatch = new System.Diagnostics.Stopwatch();
private long previousTickTime;
private Thread Thread;
private AutoResetEvent WakeSignal;
internal readonly object StateLock = new object();
private bool Running;
public bool IsDisposed { get; private set; }
internal unsafe AudioDevice()
{
UpdateInterval = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / Step);
FAudio.FAudioCreate(out IntPtr handle, 0, FAudio.FAUDIO_DEFAULT_PROCESSOR);
Handle = handle;
/* Find a suitable device */
FAudio.FAudio_GetDeviceCount(Handle, out uint devices);
if (devices == 0)
{
Log.Error("No audio devices found!");
FAudio.FAudio_Release(Handle);
Handle = IntPtr.Zero;
return;
}
FAudio.FAudioDeviceDetails deviceDetails;
uint i = 0;
for (i = 0; i < devices; i++)
{
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
if ((deviceDetails.Role & FAudio.FAudioDeviceRole.FAudioDefaultGameDevice) == FAudio.FAudioDeviceRole.FAudioDefaultGameDevice)
{
DeviceDetails = deviceDetails;
break;
}
}
if (i == devices)
{
i = 0; /* whatever we'll just use the first one I guess */
FAudio.FAudio_GetDeviceDetails(
Handle,
i,
out deviceDetails
);
DeviceDetails = deviceDetails;
}
/* Init Mastering Voice */
uint result = FAudio.FAudio_CreateMasteringVoice(
Handle,
out trueMasteringVoice,
FAudio.FAUDIO_DEFAULT_CHANNELS,
FAudio.FAUDIO_DEFAULT_SAMPLERATE,
0,
i,
IntPtr.Zero
);
if (result != 0)
{
Log.Error("Failed to create a mastering voice!");
Log.Error("Audio device creation failed!");
return;
}
fauxMasteringVoice = SubmixVoice.CreateFauxMasteringVoice(this);
/* Init 3D Audio */
Handle3D = new byte[FAudio.F3DAUDIO_HANDLE_BYTESIZE];
FAudio.F3DAudioInitialize(
DeviceDetails.OutputFormat.dwChannelMask,
SpeedOfSound,
Handle3D
);
VoicePool = new SourceVoicePool(this);
WakeSignal = new AutoResetEvent(true);
Thread = new Thread(ThreadMain);
Thread.IsBackground = true;
Thread.Start();
Running = true;
TickStopwatch.Start();
previousTickTime = 0;
}
private void ThreadMain()
{
while (Running)
{
lock (StateLock)
{
try
{
ThreadMainTick();
}
catch (Exception e)
{
Log.Error(e.ToString());
}
}
WakeSignal.WaitOne(UpdateInterval);
}
}
private void ThreadMainTick()
{
previousTickTime = TickStopwatch.Elapsed.Ticks;
foreach (UpdatingSourceVoice voice in updatingSourceVoices)
{
voice.Update();
}
foreach (SourceVoice voice in VoicesToReturn)
{
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Remove(updatingSourceVoice);
}
voice.Reset();
VoicePool.Return(voice);
}
VoicesToReturn.Clear();
}
/// <summary>
/// Triggers all pending operations with the given syncGroup value.
/// </summary>
public void TriggerSyncGroup(uint syncGroup)
{
FAudio.FAudio_CommitChanges(Handle, syncGroup);
}
/// <summary>
/// Obtains an appropriate source voice from the voice pool.
/// </summary>
/// <param name="format">The format that the voice must match.</param>
/// <returns>A source voice with the given format.</returns>
public T Obtain<T>(Format format) where T : SourceVoice, IPoolable<T>
{
lock (StateLock)
{
T voice = VoicePool.Obtain<T>(format);
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Add(updatingSourceVoice);
}
return voice;
}
}
/// <summary>
/// Returns the source voice to the voice pool.
/// </summary>
/// <param name="voice"></param>
internal void Return(SourceVoice voice)
{
lock (StateLock)
{
VoicesToReturn.Add(voice);
}
}
internal void WakeThread()
{
WakeSignal.Set();
}
internal void AddResourceReference(GCHandle resourceReference)
{
lock (StateLock)
{
resourceHandles.Add(resourceReference);
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
{
updatingSourceVoices.Add(updatableVoice);
}
}
}
internal void RemoveResourceReference(GCHandle resourceReference)
{
lock (StateLock)
{
resourceHandles.Remove(resourceReference);
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
{
updatingSourceVoices.Remove(updatableVoice);
}
}
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
Running = false;
if (disposing)
{
Thread.Join();
// dispose all source voices first
foreach (GCHandle handle in resourceHandles)
{
if (handle.Target is SourceVoice voice)
{
voice.Dispose();
}
}
// dispose all submix voices except the faux mastering voice
foreach (GCHandle handle in resourceHandles)
{
if (handle.Target is SubmixVoice voice && voice != fauxMasteringVoice)
{
voice.Dispose();
}
}
// dispose the faux mastering voice
fauxMasteringVoice.Dispose();
// dispose the true mastering voice
FAudio.FAudioVoice_DestroyVoice(trueMasteringVoice);
// destroy all other audio resources
foreach (GCHandle handle in resourceHandles)
{
if (handle.Target is AudioResource resource)
{
resource.Dispose();
}
}
resourceHandles.Clear();
}
FAudio.FAudio_Release(Handle);
IsDisposed = true;
}
}
~AudioDevice()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,135 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// An emitter for 3D spatial audio.
/// </summary>
public class AudioEmitter : AudioResource
{
internal FAudio.F3DAUDIO_EMITTER emitterData;
public float DopplerScale
{
get
{
return emitterData.DopplerScaler;
}
set
{
if (value < 0.0f)
{
throw new ArgumentOutOfRangeException("AudioEmitter.DopplerScale must be greater than or equal to 0.0f");
}
emitterData.DopplerScaler = value;
}
}
public Vector3 Forward
{
get
{
return new Vector3(
emitterData.OrientFront.x,
emitterData.OrientFront.y,
-emitterData.OrientFront.z
);
}
set
{
emitterData.OrientFront.x = value.X;
emitterData.OrientFront.y = value.Y;
emitterData.OrientFront.z = -value.Z;
}
}
public Vector3 Position
{
get
{
return new Vector3(
emitterData.Position.x,
emitterData.Position.y,
-emitterData.Position.z
);
}
set
{
emitterData.Position.x = value.X;
emitterData.Position.y = value.Y;
emitterData.Position.z = -value.Z;
}
}
public Vector3 Up
{
get
{
return new Vector3(
emitterData.OrientTop.x,
emitterData.OrientTop.y,
-emitterData.OrientTop.z
);
}
set
{
emitterData.OrientTop.x = value.X;
emitterData.OrientTop.y = value.Y;
emitterData.OrientTop.z = -value.Z;
}
}
public Vector3 Velocity
{
get
{
return new Vector3(
emitterData.Velocity.x,
emitterData.Velocity.y,
-emitterData.Velocity.z
);
}
set
{
emitterData.Velocity.x = value.X;
emitterData.Velocity.y = value.Y;
emitterData.Velocity.z = -value.Z;
}
}
private static readonly float[] stereoAzimuth = new float[]
{
0.0f, 0.0f
};
private static readonly GCHandle stereoAzimuthHandle = GCHandle.Alloc(
stereoAzimuth,
GCHandleType.Pinned
);
public AudioEmitter(AudioDevice device) : base(device)
{
emitterData = new FAudio.F3DAUDIO_EMITTER();
DopplerScale = 1f;
Forward = Vector3.UnitZ;
Position = Vector3.Zero;
Up = Vector3.UnitY;
Velocity = Vector3.Zero;
/* Unexposed variables, defaults based on XNA behavior */
emitterData.pCone = IntPtr.Zero;
emitterData.ChannelCount = 1;
emitterData.ChannelRadius = 1.0f;
emitterData.pChannelAzimuths = stereoAzimuthHandle.AddrOfPinnedObject();
emitterData.pVolumeCurve = IntPtr.Zero;
emitterData.pLFECurve = IntPtr.Zero;
emitterData.pLPFDirectCurve = IntPtr.Zero;
emitterData.pLPFReverbCurve = IntPtr.Zero;
emitterData.pReverbCurve = IntPtr.Zero;
emitterData.CurveDistanceScaler = 1.0f;
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Numerics;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// A listener for 3D spatial audio. Usually attached to a camera.
/// </summary>
public class AudioListener : AudioResource
{
internal FAudio.F3DAUDIO_LISTENER listenerData;
public Vector3 Forward
{
get
{
return new Vector3(
listenerData.OrientFront.x,
listenerData.OrientFront.y,
-listenerData.OrientFront.z
);
}
set
{
listenerData.OrientFront.x = value.X;
listenerData.OrientFront.y = value.Y;
listenerData.OrientFront.z = -value.Z;
}
}
public Vector3 Position
{
get
{
return new Vector3(
listenerData.Position.x,
listenerData.Position.y,
-listenerData.Position.z
);
}
set
{
listenerData.Position.x = value.X;
listenerData.Position.y = value.Y;
listenerData.Position.z = -value.Z;
}
}
public Vector3 Up
{
get
{
return new Vector3(
listenerData.OrientTop.x,
listenerData.OrientTop.y,
-listenerData.OrientTop.z
);
}
set
{
listenerData.OrientTop.x = value.X;
listenerData.OrientTop.y = value.Y;
listenerData.OrientTop.z = -value.Z;
}
}
public Vector3 Velocity
{
get
{
return new Vector3(
listenerData.Velocity.x,
listenerData.Velocity.y,
-listenerData.Velocity.z
);
}
set
{
listenerData.Velocity.x = value.X;
listenerData.Velocity.y = value.Y;
listenerData.Velocity.z = -value.Z;
}
}
public AudioListener(AudioDevice device) : base(device)
{
listenerData = new FAudio.F3DAUDIO_LISTENER();
Forward = Vector3.UnitZ;
Position = Vector3.Zero;
Up = Vector3.UnitY;
Velocity = Vector3.Zero;
/* Unexposed variables, defaults based on XNA behavior */
listenerData.pCone = IntPtr.Zero;
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
public abstract class AudioResource : IDisposable
{
public AudioDevice Device { get; }
public bool IsDisposed { get; private set; }
private GCHandle SelfReference;
protected AudioResource(AudioDevice device)
{
Device = device;
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
Device.AddResourceReference(SelfReference);
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Device.RemoveResourceReference(SelfReference);
SelfReference.Free();
}
IsDisposed = true;
}
}
~AudioResource()
{
#if DEBUG
// If you see this log message, you leaked an audio resource without disposing it!
// We can't clean it up for you because this can cause catastrophic issues.
// You should really fix this when it happens.
Log.Warning($"A resource of type {GetType().Name} was not Disposed.");
#endif
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,9 @@
namespace Nerfed.Runtime.Audio;
public enum FilterType
{
None,
LowPass,
BandPass,
HighPass
}

View File

@ -0,0 +1,35 @@
namespace Nerfed.Runtime.Audio;
public enum FormatTag : ushort
{
Unknown = 0,
PCM = 1,
MSADPCM = 2,
IEEE_FLOAT = 3
}
/// <summary>
/// Describes the format of audio data. Usually specified in an audio file's header information.
/// </summary>
public record struct Format
{
public FormatTag Tag;
public ushort Channels;
public uint SampleRate;
public ushort BitsPerSample;
internal FAudio.FAudioWaveFormatEx ToFAudioFormat()
{
ushort blockAlign = (ushort) ((BitsPerSample / 8) * Channels);
return new FAudio.FAudioWaveFormatEx
{
wFormatTag = (ushort) Tag,
nChannels = Channels,
nSamplesPerSec = SampleRate,
wBitsPerSample = BitsPerSample,
nBlockAlign = blockAlign,
nAvgBytesPerSec = blockAlign * SampleRate
};
}
}

View File

@ -0,0 +1,6 @@
namespace Nerfed.Runtime.Audio;
public interface IPoolable<T>
{
static abstract T Create(AudioDevice device, Format format);
}

View File

@ -0,0 +1,27 @@
namespace Nerfed.Runtime.Audio;
/// <summary>
/// PersistentVoice should be used when you need to maintain a long-term reference to a source voice.
/// </summary>
public class PersistentVoice : SourceVoice, IPoolable<PersistentVoice>
{
public PersistentVoice(AudioDevice device, Format format) : base(device, format)
{
}
public static PersistentVoice Create(AudioDevice device, Format format)
{
return new PersistentVoice(device, format);
}
/// <summary>
/// Adds an AudioBuffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
/// </summary>
/// <param name="buffer">The buffer to submit to the voice.</param>
/// <param name="loop">Whether the voice should loop this buffer.</param>
public void Submit(AudioBuffer buffer, bool loop = false)
{
Submit(buffer.ToFAudioBuffer(loop));
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Use this in conjunction with SourceVoice.SetReverbEffectChain to add reverb to a voice.
/// </summary>
public unsafe class ReverbEffect : SubmixVoice
{
// Defaults based on FAUDIOFX_I3DL2_PRESET_GENERIC
public static FAudio.FAudioFXReverbParameters DefaultParams = new FAudio.FAudioFXReverbParameters
{
WetDryMix = 100.0f,
ReflectionsDelay = 7,
ReverbDelay = 11,
RearDelay = FAudio.FAUDIOFX_REVERB_DEFAULT_REAR_DELAY,
PositionLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION,
PositionRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION,
PositionMatrixLeft = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX,
PositionMatrixRight = FAudio.FAUDIOFX_REVERB_DEFAULT_POSITION_MATRIX,
EarlyDiffusion = 15,
LateDiffusion = 15,
LowEQGain = 8,
LowEQCutoff = 4,
HighEQGain = 8,
HighEQCutoff = 6,
RoomFilterFreq = 5000f,
RoomFilterMain = -10f,
RoomFilterHF = -1f,
ReflectionsGain = -26.0200005f,
ReverbGain = 10.0f,
DecayTime = 1.49000001f,
Density = 100.0f,
RoomSize = FAudio.FAUDIOFX_REVERB_DEFAULT_ROOM_SIZE
};
public FAudio.FAudioFXReverbParameters Params { get; private set; }
public ReverbEffect(AudioDevice audioDevice, uint processingStage) : base(audioDevice, 1, audioDevice.DeviceDetails.OutputFormat.Format.nSamplesPerSec, processingStage)
{
/* Init reverb */
IntPtr reverb;
FAudio.FAudioCreateReverb(out reverb, 0);
FAudio.FAudioEffectChain chain = new FAudio.FAudioEffectChain();
FAudio.FAudioEffectDescriptor descriptor = new FAudio.FAudioEffectDescriptor
{
InitialState = 1,
OutputChannels = 1,
pEffect = reverb
};
chain.EffectCount = 1;
chain.pEffectDescriptors = (nint) (&descriptor);
FAudio.FAudioVoice_SetEffectChain(
Handle,
ref chain
);
FAudio.FAPOBase_Release(reverb);
SetParams(DefaultParams);
}
public void SetParams(in FAudio.FAudioFXReverbParameters reverbParams)
{
Params = reverbParams;
fixed (FAudio.FAudioFXReverbParameters* reverbParamsPtr = &reverbParams)
{
FAudio.FAudioVoice_SetEffectParameters(
Handle,
0,
(nint) reverbParamsPtr,
(uint) Marshal.SizeOf<FAudio.FAudioFXReverbParameters>(),
0
);
}
}
}

View File

@ -0,0 +1,63 @@
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically.
/// </summary>
public class SoundSequence : UpdatingSourceVoice, IPoolable<SoundSequence>
{
public int NeedSoundThreshold = 0;
public delegate void OnSoundNeededFunc();
public OnSoundNeededFunc OnSoundNeeded;
public SoundSequence(AudioDevice device, Format format) : base(device, format)
{
}
public SoundSequence(AudioDevice device, AudioBuffer templateSound) : base(device, templateSound.Format)
{
}
public static SoundSequence Create(AudioDevice device, Format format)
{
return new SoundSequence(device, format);
}
public override void Update()
{
lock (StateLock)
{
if (State != SoundState.Playing) { return; }
if (NeedSoundThreshold > 0)
{
int buffersNeeded = NeedSoundThreshold - (int) BuffersQueued;
for (int i = 0; i < buffersNeeded; i += 1)
{
if (OnSoundNeeded != null)
{
OnSoundNeeded();
}
}
}
}
}
public void EnqueueSound(AudioBuffer buffer)
{
#if DEBUG
if (!(buffer.Format == Format))
{
Log.Warning("Sound sequence audio format mismatch!");
return;
}
#endif
lock (StateLock)
{
Submit(buffer.ToFAudioBuffer());
}
}
}

View File

@ -0,0 +1,8 @@
namespace Nerfed.Runtime.Audio;
public enum SoundState
{
Playing,
Paused,
Stopped
}

View File

@ -0,0 +1,217 @@
using System;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Emits audio from submitted audio buffers.
/// </summary>
public abstract class SourceVoice : Voice
{
private Format format;
public Format Format => format;
protected bool PlaybackInitiated;
/// <summary>
/// The number of buffers queued in the voice.
/// This includes the currently playing voice!
/// </summary>
public uint BuffersQueued
{
get
{
FAudio.FAudioSourceVoice_GetState(
Handle,
out FAudio.FAudioVoiceState state,
FAudio.FAUDIO_VOICE_NOSAMPLESPLAYED
);
return state.BuffersQueued;
}
}
private SoundState state = SoundState.Stopped;
public SoundState State
{
get
{
if (BuffersQueued == 0)
{
Stop();
}
return state;
}
internal set
{
state = value;
}
}
protected object StateLock = new object();
public SourceVoice(
AudioDevice device,
Format format
) : base(device, format.Channels, device.DeviceDetails.OutputFormat.Format.nChannels)
{
this.format = format;
FAudio.FAudioWaveFormatEx fAudioFormat = format.ToFAudioFormat();
FAudio.FAudio_CreateSourceVoice(
device.Handle,
out handle,
ref fAudioFormat,
FAudio.FAUDIO_VOICE_USEFILTER,
FAudio.FAUDIO_DEFAULT_FREQ_RATIO,
IntPtr.Zero,
IntPtr.Zero, // default sends to mastering voice!
IntPtr.Zero
);
SetOutputVoice(device.MasteringVoice);
}
/// <summary>
/// Starts consumption and processing of audio by the voice.
/// Delivers the result to any connected submix or mastering voice.
/// </summary>
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
public void Play(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Start(Handle, 0, syncGroup);
State = SoundState.Playing;
}
}
/// <summary>
/// Pauses playback.
/// All source buffers that are queued on the voice and the current cursor position are preserved.
/// </summary>
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
public void Pause(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
State = SoundState.Paused;
}
}
/// <summary>
/// Stops looping the voice when it reaches the end of the current loop region.
/// If the cursor for the voice is not in a loop region, ExitLoop does nothing.
/// </summary>
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
public void ExitLoop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_ExitLoop(Handle, syncGroup);
}
}
/// <summary>
/// Stops playback and removes all pending audio buffers from the voice queue.
/// </summary>
/// <param name="syncGroup">Optional. Denotes that the operation will be pending until AudioDevice.TriggerSyncGroup is called.</param>
public void Stop(uint syncGroup = FAudio.FAUDIO_COMMIT_NOW)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_Stop(Handle, 0, syncGroup);
FAudio.FAudioSourceVoice_FlushSourceBuffers(Handle);
State = SoundState.Stopped;
}
}
/// <summary>
/// Adds an AudioBuffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
/// </summary>
/// <param name="buffer">The buffer to submit to the voice.</param>
public void Submit(AudioBuffer buffer)
{
Submit(buffer.ToFAudioBuffer());
}
/// <summary>
/// Calculates positional sound. This must be called continuously to update positional sound.
/// </summary>
/// <param name="listener"></param>
/// <param name="emitter"></param>
public unsafe void Apply3D(AudioListener listener, AudioEmitter emitter)
{
Is3D = true;
emitter.emitterData.CurveDistanceScaler = Device.CurveDistanceScalar;
emitter.emitterData.ChannelCount = SourceChannelCount;
FAudio.F3DAUDIO_DSP_SETTINGS dspSettings = new FAudio.F3DAUDIO_DSP_SETTINGS
{
DopplerFactor = DopplerFactor,
SrcChannelCount = SourceChannelCount,
DstChannelCount = DestinationChannelCount,
pMatrixCoefficients = (nint) pMatrixCoefficients
};
FAudio.F3DAudioCalculate(
Device.Handle3D,
ref listener.listenerData,
ref emitter.emitterData,
FAudio.F3DAUDIO_CALCULATE_MATRIX | FAudio.F3DAUDIO_CALCULATE_DOPPLER,
ref dspSettings
);
UpdatePitch();
FAudio.FAudioVoice_SetOutputMatrix(
Handle,
OutputVoice.Handle,
SourceChannelCount,
DestinationChannelCount,
(nint) pMatrixCoefficients,
0
);
}
/// <summary>
/// Specifies that this source voice can be returned to the voice pool.
/// Holding on to the reference after calling this will cause problems!
/// </summary>
public void Return()
{
Stop();
Device.Return(this);
}
/// <summary>
/// Adds an FAudio buffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
/// </summary>
/// <param name="buffer">The buffer to submit to the voice.</param>
protected void Submit(FAudio.FAudioBuffer buffer)
{
lock (StateLock)
{
FAudio.FAudioSourceVoice_SubmitSourceBuffer(
Handle,
ref buffer,
IntPtr.Zero
);
}
}
public override void Reset()
{
Stop();
PlaybackInitiated = false;
base.Reset();
}
}

View File

@ -0,0 +1,38 @@
using System.Collections.Generic;
namespace Nerfed.Runtime.Audio;
internal class SourceVoicePool
{
private AudioDevice Device;
Dictionary<(System.Type, Format), Queue<SourceVoice>> VoiceLists = new Dictionary<(System.Type, Format), Queue<SourceVoice>>();
public SourceVoicePool(AudioDevice device)
{
Device = device;
}
public T Obtain<T>(Format format) where T : SourceVoice, IPoolable<T>
{
if (!VoiceLists.ContainsKey((typeof(T), format)))
{
VoiceLists.Add((typeof(T), format), new Queue<SourceVoice>());
}
Queue<SourceVoice> list = VoiceLists[(typeof(T), format)];
if (list.Count == 0)
{
list.Enqueue(T.Create(Device, format));
}
return (T) list.Dequeue();
}
public void Return(SourceVoice voice)
{
Queue<SourceVoice> list = VoiceLists[(voice.GetType(), voice.Format)];
list.Enqueue(voice);
}
}

View File

@ -0,0 +1,168 @@
using System;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Use in conjunction with an AudioDataStreamable object to play back streaming audio data.
/// </summary>
public class StreamingVoice : UpdatingSourceVoice, IPoolable<StreamingVoice>
{
private const int BUFFER_COUNT = 3;
private readonly IntPtr[] buffers;
private int nextBufferIndex = 0;
private uint BufferSize;
public bool Loop { get; set; }
public AudioDataStreamable AudioData { get; protected set; }
public unsafe StreamingVoice(AudioDevice device, Format format) : base(device, format)
{
buffers = new IntPtr[BUFFER_COUNT];
}
public static StreamingVoice Create(AudioDevice device, Format format)
{
return new StreamingVoice(device, format);
}
/// <summary>
/// Loads and prepares an AudioDataStreamable for streaming playback.
/// This automatically calls Load on the given AudioDataStreamable.
/// </summary>
public void Load(AudioDataStreamable data)
{
lock (StateLock)
{
if (AudioData != null)
{
AudioData.Unload();
}
data.Load();
AudioData = data;
InitializeBuffers();
QueueBuffers();
}
}
/// <summary>
/// Unloads AudioDataStreamable from this voice.
/// This automatically calls Unload on the given AudioDataStreamable.
/// </summary>
public void Unload()
{
lock (StateLock)
{
if (AudioData != null)
{
Stop();
AudioData.Unload();
AudioData = null;
}
}
}
public override void Reset()
{
Unload();
base.Reset();
}
public override void Update()
{
lock (StateLock)
{
if (AudioData == null || State != SoundState.Playing)
{
return;
}
QueueBuffers();
}
}
private void QueueBuffers()
{
int buffersNeeded = BUFFER_COUNT - (int) BuffersQueued; // don't get got by uint underflow!
for (int i = 0; i < buffersNeeded; i += 1)
{
AddBuffer();
}
}
private unsafe void AddBuffer()
{
IntPtr buffer = buffers[nextBufferIndex];
nextBufferIndex = (nextBufferIndex + 1) % BUFFER_COUNT;
AudioData.Decode(
(void*) buffer,
(int) BufferSize,
out int filledLengthInBytes,
out bool reachedEnd
);
if (filledLengthInBytes > 0)
{
FAudio.FAudioBuffer buf = new FAudio.FAudioBuffer
{
AudioBytes = (uint) filledLengthInBytes,
pAudioData = buffer,
PlayLength = (
(uint) (filledLengthInBytes /
Format.Channels /
(uint) (Format.BitsPerSample / 8))
)
};
Submit(buf);
}
if (reachedEnd)
{
/* We have reached the end of the data, what do we do? */
if (Loop)
{
AudioData.Seek(0);
}
}
}
private unsafe void InitializeBuffers()
{
BufferSize = AudioData.DecodeBufferSize;
for (int i = 0; i < BUFFER_COUNT; i += 1)
{
if (buffers[i] != IntPtr.Zero)
{
NativeMemory.Free((void*) buffers[i]);
}
buffers[i] = (IntPtr) NativeMemory.Alloc(BufferSize);
}
}
protected override unsafe void Dispose(bool disposing)
{
if (!IsDisposed)
{
lock (StateLock)
{
Stop();
for (int i = 0; i < BUFFER_COUNT; i += 1)
{
if (buffers[i] != IntPtr.Zero)
{
NativeMemory.Free((void*) buffers[i]);
}
}
}
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,55 @@
using System;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// SourceVoices can send audio to a SubmixVoice for convenient effects processing.
/// Submixes process in order of processingStage, from lowest to highest.
/// Therefore submixes early in a chain should have a low processingStage, and later in the chain they should have a higher one.
/// </summary>
public class SubmixVoice : Voice
{
public SubmixVoice(
AudioDevice device,
uint sourceChannelCount,
uint sampleRate,
uint processingStage
) : base(device, sourceChannelCount, device.DeviceDetails.OutputFormat.Format.nChannels)
{
FAudio.FAudio_CreateSubmixVoice(
device.Handle,
out handle,
sourceChannelCount,
sampleRate,
FAudio.FAUDIO_VOICE_USEFILTER,
processingStage,
IntPtr.Zero,
IntPtr.Zero
);
SetOutputVoice(device.MasteringVoice);
}
private SubmixVoice(
AudioDevice device
) : base(device, device.DeviceDetails.OutputFormat.Format.nChannels, device.DeviceDetails.OutputFormat.Format.nChannels)
{
FAudio.FAudio_CreateSubmixVoice(
device.Handle,
out handle,
device.DeviceDetails.OutputFormat.Format.nChannels,
device.DeviceDetails.OutputFormat.Format.nSamplesPerSec,
FAudio.FAUDIO_VOICE_USEFILTER,
int.MaxValue,
IntPtr.Zero, // default sends to mastering voice
IntPtr.Zero
);
OutputVoice = null;
}
internal static SubmixVoice CreateFauxMasteringVoice(AudioDevice device)
{
return new SubmixVoice(device);
}
}

View File

@ -0,0 +1,28 @@
namespace Nerfed.Runtime.Audio;
/// <summary>
/// TransientVoice is intended for playing one-off sound effects that don't have a long term reference. <br/>
/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back.
/// </summary>
public class TransientVoice : UpdatingSourceVoice, IPoolable<TransientVoice>
{
static TransientVoice IPoolable<TransientVoice>.Create(AudioDevice device, Format format)
{
return new TransientVoice(device, format);
}
public TransientVoice(AudioDevice device, Format format) : base(device, format)
{
}
public override void Update()
{
lock (StateLock)
{
if (PlaybackInitiated && BuffersQueued == 0)
{
Return();
}
}
}
}

View File

@ -0,0 +1,10 @@
namespace Nerfed.Runtime.Audio;
public abstract class UpdatingSourceVoice : SourceVoice
{
protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
{
}
public abstract void Update();
}

View File

@ -0,0 +1,433 @@
using System;
using System.Runtime.InteropServices;
using EasingFunction = System.Func<float, float>;
namespace Nerfed.Runtime.Audio;
/// <summary>
/// Handles audio playback from audio buffer data. Can be configured with a variety of parameters.
/// </summary>
public abstract unsafe class Voice : AudioResource
{
protected IntPtr handle;
public IntPtr Handle => handle;
public uint SourceChannelCount { get; }
public uint DestinationChannelCount { get; }
protected SubmixVoice OutputVoice;
private ReverbEffect ReverbEffect;
protected byte* pMatrixCoefficients;
public bool Is3D { get; protected set; }
private float dopplerFactor;
/// <summary>
/// The strength of the doppler effect on this voice.
/// </summary>
public float DopplerFactor
{
get => dopplerFactor;
set
{
if (dopplerFactor != value)
{
dopplerFactor = value;
UpdatePitch();
}
}
}
private float volume = 1;
/// <summary>
/// The overall volume level for the voice.
/// </summary>
public float Volume
{
get => volume;
internal set
{
value = MathF.Max(0f, value);
if (volume != value)
{
volume = value;
FAudio.FAudioVoice_SetVolume(Handle, volume, 0);
}
}
}
private float pitch = 0;
/// <summary>
/// The pitch of the voice.
/// </summary>
public float Pitch
{
get => pitch;
internal set
{
value = Math.Clamp(value, -1f, 1f);
if (pitch != value)
{
pitch = value;
UpdatePitch();
}
}
}
private const float MAX_FILTER_FREQUENCY = 1f;
private const float MAX_FILTER_ONEOVERQ = 1.5f;
private FAudio.FAudioFilterParameters filterParameters = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
Frequency = 1f,
OneOverQ = 1f
};
/// <summary>
/// The frequency cutoff on the voice filter.
/// </summary>
public float FilterFrequency
{
get => filterParameters.Frequency;
internal set
{
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_FREQUENCY);
if (filterParameters.Frequency != value)
{
filterParameters.Frequency = value;
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
0
);
}
}
}
/// <summary>
/// Reciprocal of Q factor.
/// Controls how quickly frequencies beyond the filter frequency are dampened.
/// </summary>
public float FilterOneOverQ
{
get => filterParameters.OneOverQ;
internal set
{
value = System.Math.Clamp(value, 0.01f, MAX_FILTER_ONEOVERQ);
if (filterParameters.OneOverQ != value)
{
filterParameters.OneOverQ = value;
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
0
);
}
}
}
private FilterType filterType;
/// <summary>
/// The frequency filter that is applied to the voice.
/// </summary>
public FilterType FilterType
{
get => filterType;
set
{
if (filterType != value)
{
filterType = value;
switch (filterType)
{
case FilterType.None:
filterParameters = new FAudio.FAudioFilterParameters
{
Type = FAudio.FAudioFilterType.FAudioLowPassFilter,
Frequency = 1f,
OneOverQ = 1f
};
break;
case FilterType.LowPass:
filterParameters.Type = FAudio.FAudioFilterType.FAudioLowPassFilter;
filterParameters.Frequency = 1f;
break;
case FilterType.BandPass:
filterParameters.Type = FAudio.FAudioFilterType.FAudioBandPassFilter;
break;
case FilterType.HighPass:
filterParameters.Type = FAudio.FAudioFilterType.FAudioHighPassFilter;
filterParameters.Frequency = 0f;
break;
}
FAudio.FAudioVoice_SetFilterParameters(
Handle,
ref filterParameters,
0
);
}
}
}
protected float pan = 0;
/// <summary>
/// Left-right panning. -1 is hard left pan, 1 is hard right pan.
/// </summary>
public float Pan
{
get => pan;
internal set
{
value = Math.Clamp(value, -1f, 1f);
if (pan != value)
{
pan = value;
if (pan < -1f)
{
pan = -1f;
}
if (pan > 1f)
{
pan = 1f;
}
if (Is3D) { return; }
SetPanMatrixCoefficients();
FAudio.FAudioVoice_SetOutputMatrix(
Handle,
OutputVoice.Handle,
SourceChannelCount,
DestinationChannelCount,
(nint) pMatrixCoefficients,
0
);
}
}
}
private float reverb;
/// <summary>
/// The wet-dry mix of the reverb effect.
/// Has no effect if SetReverbEffectChain has not been called.
/// </summary>
public unsafe float Reverb
{
get => reverb;
internal set
{
if (ReverbEffect != null)
{
value = MathF.Max(0, value);
if (reverb != value)
{
reverb = value;
float* outputMatrix = (float*) pMatrixCoefficients;
outputMatrix[0] = reverb;
if (SourceChannelCount == 2)
{
outputMatrix[1] = reverb;
}
FAudio.FAudioVoice_SetOutputMatrix(
Handle,
ReverbEffect.Handle,
SourceChannelCount,
1,
(nint) pMatrixCoefficients,
0
);
}
}
#if DEBUG
if (ReverbEffect == null)
{
Log.Warning("Tried to set reverb value before applying a reverb effect");
}
#endif
}
}
public Voice(AudioDevice device, uint sourceChannelCount, uint destinationChannelCount) : base(device)
{
SourceChannelCount = sourceChannelCount;
DestinationChannelCount = destinationChannelCount;
nuint memsize = 4 * sourceChannelCount * destinationChannelCount;
pMatrixCoefficients = (byte*) NativeMemory.AllocZeroed(memsize);
SetPanMatrixCoefficients();
}
/// <summary>
/// Sets the output voice for this voice.
/// </summary>
/// <param name="send">Where the output should be sent.</param>
public unsafe void SetOutputVoice(SubmixVoice send)
{
OutputVoice = send;
if (ReverbEffect != null)
{
SetReverbEffectChain(ReverbEffect);
}
else
{
FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[1];
sendDesc[0].Flags = 0;
sendDesc[0].pOutputVoice = send.Handle;
FAudio.FAudioVoiceSends sends = new FAudio.FAudioVoiceSends();
sends.SendCount = 1;
sends.pSends = (nint) sendDesc;
FAudio.FAudioVoice_SetOutputVoices(
Handle,
ref sends
);
}
}
/// <summary>
/// Applies a reverb effect chain to this voice.
/// </summary>
public unsafe void SetReverbEffectChain(ReverbEffect reverbEffect)
{
FAudio.FAudioSendDescriptor* sendDesc = stackalloc FAudio.FAudioSendDescriptor[2];
sendDesc[0].Flags = 0;
sendDesc[0].pOutputVoice = OutputVoice.Handle;
sendDesc[1].Flags = 0;
sendDesc[1].pOutputVoice = reverbEffect.Handle;
FAudio.FAudioVoiceSends sends = new FAudio.FAudioVoiceSends();
sends.SendCount = 2;
sends.pSends = (nint) sendDesc;
FAudio.FAudioVoice_SetOutputVoices(
Handle,
ref sends
);
ReverbEffect = reverbEffect;
}
/// <summary>
/// Removes the reverb effect chain from this voice.
/// </summary>
public void RemoveReverbEffectChain()
{
if (ReverbEffect != null)
{
ReverbEffect = null;
reverb = 0;
SetOutputVoice(OutputVoice);
}
}
/// <summary>
/// Resets all voice parameters to defaults.
/// </summary>
public virtual void Reset()
{
RemoveReverbEffectChain();
Volume = 1;
Pan = 0;
Pitch = 0;
FilterType = FilterType.None;
SetOutputVoice(Device.MasteringVoice);
}
// Taken from https://github.com/FNA-XNA/FNA/blob/master/src/Audio/SoundEffectInstance.cs
private unsafe void SetPanMatrixCoefficients()
{
/* Two major things to notice:
* 1. The spec assumes any speaker count >= 2 has Front Left/Right.
* 2. Stereo panning is WAY more complicated than you think.
* The main thing is that hard panning does NOT eliminate an
* entire channel; the two channels are blended on each side.
* -flibit
*/
float* outputMatrix = (float*) pMatrixCoefficients;
if (SourceChannelCount == 1)
{
if (DestinationChannelCount == 1)
{
outputMatrix[0] = 1.0f;
}
else
{
outputMatrix[0] = (pan > 0.0f) ? (1.0f - pan) : 1.0f;
outputMatrix[1] = (pan < 0.0f) ? (1.0f + pan) : 1.0f;
}
}
else
{
if (DestinationChannelCount == 1)
{
outputMatrix[0] = 1.0f;
outputMatrix[1] = 1.0f;
}
else
{
if (pan <= 0.0f)
{
// Left speaker blends left/right channels
outputMatrix[0] = 0.5f * pan + 1.0f;
outputMatrix[1] = 0.5f * -pan;
// Right speaker gets less of the right channel
outputMatrix[2] = 0.0f;
outputMatrix[3] = pan + 1.0f;
}
else
{
// Left speaker gets less of the left channel
outputMatrix[0] = -pan + 1.0f;
outputMatrix[1] = 0.0f;
// Right speaker blends right/left channels
outputMatrix[2] = 0.5f * pan;
outputMatrix[3] = 0.5f * -pan + 1.0f;
}
}
}
}
protected void UpdatePitch()
{
float doppler;
float dopplerScale = Device.DopplerScale;
if (!Is3D || dopplerScale == 0.0f)
{
doppler = 1.0f;
}
else
{
doppler = DopplerFactor * dopplerScale;
}
FAudio.FAudioSourceVoice_SetFrequencyRatio(
Handle,
(float) System.Math.Pow(2.0, pitch) * doppler,
0
);
}
protected override unsafe void Dispose(bool disposing)
{
if (!IsDisposed)
{
NativeMemory.Free(pMatrixCoefficients);
FAudio.FAudioVoice_DestroyVoice(Handle);
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,2 @@
glslangvalidator -V imgui-vertex.glsl -o imgui-vertex.spv -S vert
glslangvalidator -V imgui-frag.glsl -o imgui-frag.spv -S frag

View File

@ -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);
}

Binary file not shown.

View File

@ -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;
}

Binary file not shown.

263
Nerfed.Runtime/Engine.cs Normal file
View File

@ -0,0 +1,263 @@
using Nerfed.Runtime.Audio;
using Nerfed.Runtime.Graphics;
using SDL2;
using System.Diagnostics;
namespace Nerfed.Runtime;
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 bool VSync { get; set; }
public static GraphicsDevice GraphicsDevice { get; private set; }
public static AudioDevice AudioDevice { get; private set; }
public static Window MainWindow { get; private set; }
public static TimeSpan Timestep { get; private set; }
private static bool quit;
private static Stopwatch gameTimer;
private static long previousTicks = 0;
private static TimeSpan accumulatedUpdateTime = TimeSpan.Zero;
private static TimeSpan accumulatedDrawTime = TimeSpan.Zero;
// must be a power of 2 so we can do a bitmask optimization when checking worst case
private const int previousSleepTimeCount = 128;
private const int sleepTimeMask = previousSleepTimeCount - 1;
private static readonly TimeSpan[] previousSleepTimes = new TimeSpan[previousSleepTimeCount];
private static int sleepTimeIndex;
private static TimeSpan worstCaseSleepPrecision = TimeSpan.FromMilliseconds(1);
private static bool framerateCapped;
private static TimeSpan framerateCapTimeSpan = TimeSpan.Zero;
//TODO: These are temp
private const int MaxFps = 300;
private const int TargetTimestep = 60;
private const int WindowWidth = 1280;
private const int WindowHeight = 720;
private const string WindowTitle = "Nerfed";
//..
public static void Run(string[] args)
{
Timestep = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / TargetTimestep);
gameTimer = Stopwatch.StartNew();
SetFrameLimiter(new FrameLimiterSettings(FrameLimiterMode.Capped, MaxFps));
for (int i = 0; i < previousSleepTimes.Length; i += 1)
{
previousSleepTimes[i] = TimeSpan.FromMilliseconds(1);
}
if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_TIMER | SDL.SDL_INIT_GAMECONTROLLER) < 0)
{
throw new Exception("Failed to init SDL");
}
GraphicsDevice = new GraphicsDevice(BackendFlags.All);
MainWindow = new Window(GraphicsDevice, new WindowCreateInfo(WindowTitle, WindowWidth, WindowHeight, ScreenMode.Windowed));
if (!GraphicsDevice.ClaimWindow(MainWindow, SwapchainComposition.SDR, VSync ? PresentMode.VSync : PresentMode.Mailbox))
{
throw new Exception("Failed to claim window");
}
AudioDevice = new AudioDevice();
OnInitialize?.Invoke();
while (!quit)
{
Tick();
}
OnQuit?.Invoke();
GraphicsDevice.UnclaimWindow(MainWindow);
MainWindow.Dispose();
GraphicsDevice.Dispose();
AudioDevice.Dispose();
SDL.SDL_Quit();
}
/// <summary>
/// Updates the frame limiter settings.
/// </summary>
public static void SetFrameLimiter(FrameLimiterSettings settings)
{
framerateCapped = settings.Mode == FrameLimiterMode.Capped;
if (framerateCapped)
{
framerateCapTimeSpan = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / settings.Cap);
}
else
{
framerateCapTimeSpan = TimeSpan.Zero;
}
}
public static void Quit()
{
quit = true;
}
private static void Tick()
{
AdvanceElapsedTime();
if (framerateCapped)
{
/* We want to wait until the framerate cap,
* 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
* sleep precision so we don't oversleep the next frame.
*/
while (accumulatedDrawTime + worstCaseSleepPrecision < framerateCapTimeSpan)
{
Thread.Sleep(1);
TimeSpan timeAdvancedSinceSleeping = AdvanceElapsedTime();
UpdateEstimatedSleepPrecision(timeAdvancedSinceSleeping);
}
/* Now that we have slept into the sleep precision threshold, we need to wait
* for just a little bit longer until the target elapsed time has been reached.
* 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.
*/
while (accumulatedDrawTime < framerateCapTimeSpan)
{
Thread.SpinWait(1);
AdvanceElapsedTime();
}
}
// Do not let any step take longer than our maximum.
if (accumulatedUpdateTime > MaxDeltaTime)
{
accumulatedUpdateTime = MaxDeltaTime;
}
if (!quit)
{
while (accumulatedUpdateTime >= Timestep)
{
Keyboard.Update();
Mouse.Update();
GamePad.Update();
ProcessSDLEvents();
// Tick game here...
OnUpdate?.Invoke();
AudioDevice.WakeThread();
accumulatedUpdateTime -= Timestep;
}
double alpha = accumulatedUpdateTime / Timestep;
// Render here..
OnRender?.Invoke();
accumulatedDrawTime -= framerateCapTimeSpan;
}
}
private static TimeSpan AdvanceElapsedTime()
{
long currentTicks = gameTimer.Elapsed.Ticks;
TimeSpan timeAdvanced = TimeSpan.FromTicks(currentTicks - previousTicks);
accumulatedUpdateTime += timeAdvanced;
accumulatedDrawTime += timeAdvanced;
previousTicks = currentTicks;
return timeAdvanced;
}
private static void ProcessSDLEvents()
{
while (SDL.SDL_PollEvent(out SDL.SDL_Event ev) == 1)
{
switch (ev.type)
{
case SDL.SDL_EventType.SDL_QUIT:
Quit();
break;
case SDL.SDL_EventType.SDL_TEXTINPUT:
case SDL.SDL_EventType.SDL_KEYDOWN:
case SDL.SDL_EventType.SDL_KEYUP:
Keyboard.ProcessEvent(ref ev);
break;
case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
case SDL.SDL_EventType.SDL_MOUSEMOTION:
Mouse.ProcessEvent(ref ev);
break;
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED:
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED:
case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN:
case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP:
case SDL.SDL_EventType.SDL_CONTROLLERAXISMOTION:
case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADDOWN:
case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADUP:
case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADMOTION:
case SDL.SDL_EventType.SDL_CONTROLLERSENSORUPDATE:
GamePad.ProcessEvent(ref ev);
break;
case SDL.SDL_EventType.SDL_WINDOWEVENT:
Window.ProcessEvent(ref ev);
break;
}
}
}
/* 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.
*/
private static void UpdateEstimatedSleepPrecision(TimeSpan timeSpentSleeping)
{
/* 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
* value at 4ms for sanity.
*/
TimeSpan upperTimeBound = TimeSpan.FromMilliseconds(4);
if (timeSpentSleeping > upperTimeBound)
{
timeSpentSleeping = upperTimeBound;
}
/* We know the previous worst case - it's saved in worstCaseSleepPrecision.
* We also know the current index. So the only way the worst case changes
* is if we either 1) just got a new worst case, or 2) the worst case was
* the oldest entry on the list.
*/
if (timeSpentSleeping >= worstCaseSleepPrecision)
{
worstCaseSleepPrecision = timeSpentSleeping;
}
else if (previousSleepTimes[sleepTimeIndex] == worstCaseSleepPrecision)
{
TimeSpan maxSleepTime = TimeSpan.MinValue;
for (int i = 0; i < previousSleepTimes.Length; i++)
{
if (previousSleepTimes[i] > maxSleepTime)
{
maxSleepTime = previousSleepTimes[i];
}
}
worstCaseSleepPrecision = maxSleepTime;
}
previousSleepTimes[sleepTimeIndex] = timeSpentSleeping;
sleepTimeIndex = (sleepTimeIndex + 1) & sleepTimeMask;
}
}

View File

@ -0,0 +1,35 @@
namespace Nerfed.Runtime;
public enum FrameLimiterMode
{
/// <summary>
/// The game will render at the maximum possible framerate that the computing resources allow. <br/>
/// Note that this may lead to overheating, resource starvation, etc.
/// </summary>
Uncapped,
/// <summary>
/// The game will render no more than the specified frames per second.
/// </summary>
Capped
}
/// <summary>
/// The Game's frame limiter setting. Specifies uncapped framerate or a maximum rendering frames per second value. <br/>
/// Note that this is separate from the Game's Update timestep and can be a different value.
/// </summary>
public struct FrameLimiterSettings
{
public FrameLimiterMode Mode;
/// <summary>
/// If Mode is set to Capped, this is the maximum frames per second that will be rendered.
/// </summary>
public int Cap;
public FrameLimiterSettings(
FrameLimiterMode mode,
int cap
) {
Mode = mode;
Cap = cap;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Concurrent;
namespace Nerfed.Runtime.Graphics;
internal class CommandBufferPool
{
private GraphicsDevice GraphicsDevice;
private ConcurrentQueue<CommandBuffer> CommandBuffers = new ConcurrentQueue<CommandBuffer>();
public CommandBufferPool(GraphicsDevice graphicsDevice)
{
GraphicsDevice = graphicsDevice;
}
public CommandBuffer Obtain()
{
if (CommandBuffers.TryDequeue(out CommandBuffer commandBuffer))
{
return commandBuffer;
}
else
{
return new CommandBuffer(GraphicsDevice);
}
}
public void Return(CommandBuffer commandBuffer)
{
commandBuffer.Handle = IntPtr.Zero;
CommandBuffers.Enqueue(commandBuffer);
}
}

View File

@ -0,0 +1,171 @@
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
public class ComputePass
{
public nint Handle { get; private set; }
internal void SetHandle(nint handle)
{
Handle = handle;
}
#if DEBUG
internal bool active;
ComputePipeline currentComputePipeline;
#endif
/// <summary>
/// Binds a compute pipeline so that compute work may be dispatched.
/// </summary>
/// <param name="computePipeline">The compute pipeline to bind.</param>
public void BindComputePipeline(
ComputePipeline computePipeline
) {
#if DEBUG
AssertComputePassActive();
// TODO: validate formats?
#endif
Refresh.Refresh_BindComputePipeline(
Handle,
computePipeline.Handle
);
#if DEBUG
currentComputePipeline = computePipeline;
#endif
}
/// <summary>
/// Binds a texture to be used in the compute shader.
/// This texture must have been created with the ComputeShaderRead usage flag.
/// </summary>
public unsafe void BindStorageTexture(
in TextureSlice textureSlice,
uint slot = 0
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
AssertTextureNonNull(textureSlice.Texture);
AssertTextureHasComputeStorageReadFlag(textureSlice.Texture);
#endif
Refresh.TextureSlice refreshTextureSlice = textureSlice.ToRefresh();
Refresh.Refresh_BindComputeStorageTextures(
Handle,
slot,
&refreshTextureSlice,
1
);
}
/// <summary>
/// Binds a buffer to be used in the compute shader.
/// This buffer must have been created with the ComputeShaderRead usage flag.
/// </summary>
public unsafe void BindStorageBuffer(
Buffer buffer,
uint slot = 0
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
AssertBufferNonNull(buffer);
AssertBufferHasComputeStorageReadFlag(buffer);
#endif
IntPtr bufferHandle = buffer.Handle;
Refresh.Refresh_BindComputeStorageBuffers(
Handle,
slot,
&bufferHandle,
1
);
}
/// <summary>
/// Dispatches compute work.
/// </summary>
public void Dispatch(
uint groupCountX,
uint groupCountY,
uint groupCountZ
) {
#if DEBUG
AssertComputePassActive();
AssertComputePipelineBound();
if (groupCountX < 1 || groupCountY < 1 || groupCountZ < 1)
{
throw new System.ArgumentException("All dimensions for the compute work groups must be >= 1!");
}
#endif
Refresh.Refresh_DispatchCompute(
Handle,
groupCountX,
groupCountY,
groupCountZ
);
}
#if DEBUG
private void AssertComputePassActive(string message = "Render pass is not active!")
{
if (!active)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertComputePipelineBound(string message = "No compute pipeline is bound!")
{
if (currentComputePipeline == null)
{
throw new System.InvalidOperationException(message);
}
}
private void AssertTextureNonNull(in TextureSlice textureSlice)
{
if (textureSlice.Texture == null || textureSlice.Texture.Handle == nint.Zero)
{
throw new System.NullReferenceException("Texture must not be null!");
}
}
private void AssertTextureHasComputeStorageReadFlag(Texture texture)
{
if ((texture.UsageFlags & TextureUsageFlags.ComputeStorageRead) == 0)
{
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.ComputeStorageRead!");
}
}
private void AssertBufferNonNull(Buffer buffer)
{
if (buffer == null || buffer.Handle == nint.Zero)
{
throw new System.NullReferenceException("Buffer must not be null!");
}
}
private void AssertBufferHasComputeStorageReadFlag(Buffer buffer)
{
if ((buffer.UsageFlags & BufferUsageFlags.ComputeStorageRead) == 0)
{
throw new System.ArgumentException("The bound Buffer's UsageFlags must include BufferUsageFlag.ComputeStorageRead!");
}
}
#endif
}

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace Nerfed.Runtime.Graphics;
internal class ComputePassPool
{
private ConcurrentQueue<ComputePass> ComputePasses = new ConcurrentQueue<ComputePass>();
public ComputePass Obtain()
{
if (ComputePasses.TryDequeue(out ComputePass computePass))
{
return computePass;
}
else
{
return new ComputePass();
}
}
public void Return(ComputePass computePass)
{
ComputePasses.Enqueue(computePass);
}
}

View File

@ -0,0 +1,241 @@
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
public class CopyPass
{
public nint Handle { get; private set; }
internal void SetHandle(nint handle)
{
Handle = handle;
}
/// <summary>
/// Uploads data from a TransferBuffer to a TextureSlice.
/// This copy occurs on the GPU timeline.
///
/// Overwriting the contents of the TransferBuffer before the command buffer
/// has finished execution will cause undefined behavior.
///
/// You MAY assume that the copy has finished for subsequent commands.
/// </summary>
/// <param name="cycle">If true, cycles the texture if the given slice is bound.</param>
public void UploadToTexture(
in TextureTransferInfo source,
in TextureRegion destination,
bool cycle
) {
#if DEBUG
AssertTransferBufferNotMapped(source.TransferBuffer);
#endif
Refresh.Refresh_UploadToTexture(
Handle,
source.ToRefresh(),
destination.ToRefresh(),
Conversions.BoolToInt(cycle)
);
}
/// <summary>
/// Uploads the contents of an entire buffer to a 2D texture with no mips.
/// </summary>
public void UploadToTexture(
TransferBuffer source,
Texture destination,
bool cycle
) {
UploadToTexture(
new TextureTransferInfo(source),
new TextureRegion(destination),
cycle
);
}
/// <summary>
/// Uploads data from a TransferBuffer to a Buffer.
/// This copy occurs on the GPU timeline.
///
/// Overwriting the contents of the TransferBuffer before the command buffer
/// has finished execution will cause undefined behavior.
///
/// You MAY assume that the copy has finished for subsequent commands.
/// </summary>
/// <param name="cycle">If true, cycles the buffer if it is bound.</param>
public void UploadToBuffer(
in TransferBufferLocation source,
in BufferRegion destination,
bool cycle
) {
#if DEBUG
AssertBufferBoundsCheck(source.TransferBuffer.Size, source.Offset, destination.Size);
AssertBufferBoundsCheck(destination.Buffer.Size, destination.Offset, destination.Size);
AssertTransferBufferNotMapped(source.TransferBuffer);
#endif
Refresh.Refresh_UploadToBuffer(
Handle,
source.ToRefresh(),
destination.ToRefresh(),
Conversions.BoolToInt(cycle)
);
}
/// <summary>
/// Copies the entire contents of a TransferBuffer to a Buffer.
/// </summary>
public void UploadToBuffer(
TransferBuffer source,
Buffer destination,
bool cycle
) {
UploadToBuffer(
new TransferBufferLocation(source),
new BufferRegion(destination, 0, destination.Size),
cycle
);
}
/// <summary>
/// Copies data element-wise into from a TransferBuffer to a Buffer.
/// </summary>
public void UploadToBuffer<T>(
TransferBuffer source,
Buffer destination,
uint sourceStartElement,
uint destinationStartElement,
uint numElements,
bool cycle
) where T : unmanaged
{
int elementSize = Marshal.SizeOf<T>();
uint dataLengthInBytes = (uint) (elementSize * numElements);
uint srcOffsetInBytes = (uint) (elementSize * sourceStartElement);
uint dstOffsetInBytes = (uint) (elementSize * destinationStartElement);
UploadToBuffer(
new TransferBufferLocation(source, srcOffsetInBytes),
new BufferRegion(destination, dstOffsetInBytes, dataLengthInBytes),
cycle
);
}
/// <summary>
/// Copies the contents of a TextureLocation to another TextureLocation.
/// This copy occurs on the GPU timeline.
///
/// You MAY assume that the copy has finished in subsequent commands.
/// </summary>
public void CopyTextureToTexture(
in TextureLocation source,
in TextureLocation destination,
uint w,
uint h,
uint d,
bool cycle
) {
#if DEBUG
AssertTextureBoundsCheck(source, w, h, d);
AssertTextureBoundsCheck(destination, w, h, d);
#endif
Refresh.Refresh_CopyTextureToTexture(
Handle,
source.ToRefresh(),
destination.ToRefresh(),
w,
h,
d,
Conversions.BoolToInt(cycle)
);
}
/// <summary>
/// Copies data from a Buffer to another Buffer.
/// This copy occurs on the GPU timeline.
///
/// You MAY assume that the copy has finished in subsequent commands.
/// </summary>
public void CopyBufferToBuffer(
in BufferLocation source,
in BufferLocation destination,
uint size,
bool cycle
) {
#if DEBUG
AssertBufferBoundsCheck(source.Buffer.Size, source.Offset, size);
AssertBufferBoundsCheck(destination.Buffer.Size, destination.Offset, size);
#endif
Refresh.Refresh_CopyBufferToBuffer(
Handle,
source.ToRefresh(),
destination.ToRefresh(),
size,
Conversions.BoolToInt(cycle)
);
}
public void DownloadFromBuffer(
in BufferRegion source,
in TransferBufferLocation destination
) {
#if DEBUG
AssertBufferBoundsCheck(source.Buffer.Size, source.Offset, source.Size);
AssertBufferBoundsCheck(destination.TransferBuffer.Size, destination.Offset, source.Size);
AssertTransferBufferNotMapped(destination.TransferBuffer);
#endif
Refresh.Refresh_DownloadFromBuffer(
Handle,
source.ToRefresh(),
destination.ToRefresh()
);
}
public void DownloadFromTexture(
in TextureRegion source,
in TextureTransferInfo destination
) {
#if DEBUG
AssertTransferBufferNotMapped(destination.TransferBuffer);
#endif
Refresh.Refresh_DownloadFromTexture(
Handle,
source.ToRefresh(),
destination.ToRefresh()
);
}
#if DEBUG
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
{
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
{
throw new System.InvalidOperationException($"SetBufferData overflow! buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
}
}
private void AssertTextureBoundsCheck(in TextureLocation textureLocation, uint w, uint h, uint d)
{
if (
textureLocation.X + w > textureLocation.TextureSlice.Texture.Width ||
textureLocation.Y + h > textureLocation.TextureSlice.Texture.Height ||
textureLocation.Z + d > textureLocation.TextureSlice.Texture.Depth
) {
throw new System.InvalidOperationException($"Texture data is out of bounds!");
}
}
private void AssertTransferBufferNotMapped(TransferBuffer transferBuffer)
{
if (transferBuffer.Mapped)
{
throw new System.InvalidOperationException("Transfer buffer must not be mapped!");
}
}
#endif
}

View File

@ -0,0 +1,25 @@
using System.Collections.Concurrent;
namespace Nerfed.Runtime.Graphics;
internal class CopyPassPool
{
private ConcurrentQueue<CopyPass> CopyPasses = new ConcurrentQueue<CopyPass>();
public CopyPass Obtain()
{
if (CopyPasses.TryDequeue(out CopyPass copyPass))
{
return copyPass;
}
else
{
return new CopyPass();
}
}
public void Return(CopyPass copyPass)
{
CopyPasses.Enqueue(copyPass);
}
}

View File

@ -0,0 +1,716 @@
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,
];
}

View File

@ -0,0 +1,31 @@
using System.Collections.Concurrent;
namespace Nerfed.Runtime.Graphics;
internal class FencePool
{
private GraphicsDevice GraphicsDevice;
private ConcurrentQueue<Fence> Fences = new ConcurrentQueue<Fence>();
public FencePool(GraphicsDevice graphicsDevice)
{
GraphicsDevice = graphicsDevice;
}
public Fence Obtain()
{
if (Fences.TryDequeue(out Fence fence))
{
return fence;
}
else
{
return new Fence(GraphicsDevice);
}
}
public void Return(Fence fence)
{
Fences.Enqueue(fence);
}
}

View File

@ -0,0 +1,16 @@
namespace Nerfed.Runtime.Graphics;
public enum HorizontalAlignment
{
Left,
Center,
Right
}
public enum VerticalAlignment
{
Baseline,
Top,
Middle,
Bottom
}

View File

@ -0,0 +1,123 @@
using System.Runtime.InteropServices;
using WellspringCS;
namespace Nerfed.Runtime.Graphics;
public unsafe class Font : GraphicsResource
{
public Texture Texture { get; }
public float PixelsPerEm { get; }
public float DistanceRange { get; }
internal IntPtr Handle { get; }
private byte* StringBytes;
private int StringBytesLength;
/// <summary>
/// Loads a TTF or OTF font from a path for use in MSDF rendering.
/// Note that there must be an msdf-atlas-gen JSON and image file alongside.
/// </summary>
/// <returns></returns>
public static Font Load(GraphicsDevice graphicsDevice, CommandBuffer commandBuffer, string fontPath)
{
FileStream fontFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read);
void* fontFileByteBuffer = NativeMemory.Alloc((nuint)fontFileStream.Length);
Span<byte> fontFileByteSpan = new Span<byte>(fontFileByteBuffer, (int)fontFileStream.Length);
fontFileStream.ReadExactly(fontFileByteSpan);
fontFileStream.Close();
FileStream atlasFileStream = new FileStream(Path.ChangeExtension(fontPath, ".json"), FileMode.Open, FileAccess.Read);
void* atlasFileByteBuffer = NativeMemory.Alloc((nuint)atlasFileStream.Length);
Span<byte> atlasFileByteSpan = new Span<byte>(atlasFileByteBuffer, (int)atlasFileStream.Length);
atlasFileStream.ReadExactly(atlasFileByteSpan);
atlasFileStream.Close();
IntPtr handle = Wellspring.Wellspring_CreateFont(
(IntPtr)fontFileByteBuffer,
(uint)fontFileByteSpan.Length,
(IntPtr)atlasFileByteBuffer,
(uint)atlasFileByteSpan.Length,
out float pixelsPerEm,
out float distanceRange
);
string imagePath = Path.ChangeExtension(fontPath, ".png");
ImageUtils.ImageInfoFromFile(imagePath, out uint width, out uint height, out uint sizeInBytes);
ResourceUploader uploader = new ResourceUploader(graphicsDevice);
Texture texture = uploader.CreateTexture2DFromCompressed(imagePath);
uploader.Upload();
uploader.Dispose();
NativeMemory.Free(fontFileByteBuffer);
NativeMemory.Free(atlasFileByteBuffer);
return new Font(graphicsDevice, handle, texture, pixelsPerEm, distanceRange);
}
private Font(GraphicsDevice device, IntPtr handle, Texture texture, float pixelsPerEm, float distanceRange) : base(device)
{
Handle = handle;
Texture = texture;
PixelsPerEm = pixelsPerEm;
DistanceRange = distanceRange;
StringBytesLength = 32;
StringBytes = (byte*)NativeMemory.Alloc((nuint)StringBytesLength);
}
public unsafe bool TextBounds(
string text,
int pixelSize,
HorizontalAlignment horizontalAlignment,
VerticalAlignment verticalAlignment,
out Wellspring.Rectangle rectangle
)
{
int byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
if (StringBytesLength < byteCount)
{
StringBytes = (byte*)NativeMemory.Realloc(StringBytes, (nuint)byteCount);
}
fixed (char* chars = text)
{
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, StringBytes, byteCount);
byte result = Wellspring.Wellspring_TextBounds(
Handle,
pixelSize,
(Wellspring.HorizontalAlignment)horizontalAlignment,
(Wellspring.VerticalAlignment)verticalAlignment,
(IntPtr)StringBytes,
(uint)byteCount,
out rectangle
);
if (result == 0)
{
Log.Warning("Could not decode string: " + text);
return false;
}
}
return true;
}
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Texture.Dispose();
}
Wellspring.Wellspring_DestroyFont(Handle);
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,26 @@
using System.Numerics;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Graphics;
[StructLayout(LayoutKind.Sequential)]
public struct FontVertex : IVertexType
{
public Vector3 Position;
public Vector2 TexCoord;
public Color Color;
public static VertexElementFormat[] Formats { get; } =
[
VertexElementFormat.Vector3,
VertexElementFormat.Vector2,
VertexElementFormat.Color
];
public static uint[] Offsets { get; } =
[
0,
12,
20
];
}

View File

@ -0,0 +1,191 @@
using System.Numerics;
using System.Runtime.InteropServices;
using WellspringCS;
namespace Nerfed.Runtime.Graphics;
public unsafe class TextBatch : GraphicsResource
{
public const int INITIAL_CHAR_COUNT = 64;
public const int INITIAL_VERTEX_COUNT = INITIAL_CHAR_COUNT * 4;
public const int INITIAL_INDEX_COUNT = INITIAL_CHAR_COUNT * 6;
public IntPtr Handle { get; }
public Buffer VertexBuffer { get; protected set; } = null;
public Buffer IndexBuffer { get; protected set; } = null;
public uint PrimitiveCount { get; protected set; }
public Font CurrentFont { get; private set; }
private readonly GraphicsDevice graphicsDevice;
private readonly int stringBytesLength;
private TransferBuffer TransferBuffer;
private byte* stringBytes;
public TextBatch(GraphicsDevice device) : base(device)
{
graphicsDevice = device;
Handle = Wellspring.Wellspring_CreateTextBatch();
stringBytesLength = 128;
stringBytes = (byte*)NativeMemory.Alloc((nuint)stringBytesLength);
VertexBuffer = Buffer.Create<FontVertex>(graphicsDevice, BufferUsageFlags.Vertex, INITIAL_VERTEX_COUNT);
IndexBuffer = Buffer.Create<uint>(graphicsDevice, BufferUsageFlags.Index, INITIAL_INDEX_COUNT);
TransferBuffer = TransferBuffer.Create<byte>(
graphicsDevice,
TransferBufferUsage.Upload,
VertexBuffer.Size + IndexBuffer.Size
);
}
// Call this to initialize or reset the batch.
public void Start(Font font)
{
Wellspring.Wellspring_StartTextBatch(Handle, font.Handle);
CurrentFont = font;
PrimitiveCount = 0;
}
// Add text with size and color to the batch
public unsafe bool Add(
string text,
int pixelSize,
Color color,
HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment verticalAlignment = VerticalAlignment.Baseline
)
{
int byteCount = System.Text.Encoding.UTF8.GetByteCount(text);
if (stringBytesLength < byteCount)
{
stringBytes = (byte*)NativeMemory.Realloc(stringBytes, (nuint)byteCount);
}
fixed (char* chars = text)
{
System.Text.Encoding.UTF8.GetBytes(chars, text.Length, stringBytes, byteCount);
byte result = Wellspring.Wellspring_AddToTextBatch(
Handle,
pixelSize,
new Wellspring.Color { R = color.R, G = color.G, B = color.B, A = color.A },
(Wellspring.HorizontalAlignment)horizontalAlignment,
(Wellspring.VerticalAlignment)verticalAlignment,
(IntPtr)stringBytes,
(uint)byteCount
);
if (result == 0)
{
Log.Warning("Could not decode string: " + text);
return false;
}
}
return true;
}
// Call this after you have made all the Add calls you want, but before beginning a render pass.
public unsafe void UploadBufferData(CommandBuffer commandBuffer)
{
Wellspring.Wellspring_GetBufferData(
Handle,
out uint vertexCount,
out IntPtr vertexDataPointer,
out uint vertexDataLengthInBytes,
out IntPtr indexDataPointer,
out uint indexDataLengthInBytes
);
Span<byte> vertexSpan = new Span<byte>((void*)vertexDataPointer, (int)vertexDataLengthInBytes);
Span<byte> indexSpan = new Span<byte>((void*)indexDataPointer, (int)indexDataLengthInBytes);
bool newTransferBufferNeeded = false;
if (VertexBuffer.Size < vertexDataLengthInBytes)
{
VertexBuffer.Dispose();
VertexBuffer = new Buffer(graphicsDevice, BufferUsageFlags.Vertex, vertexDataLengthInBytes);
newTransferBufferNeeded = true;
}
if (IndexBuffer.Size < indexDataLengthInBytes)
{
IndexBuffer.Dispose();
IndexBuffer = new Buffer(graphicsDevice, BufferUsageFlags.Index, vertexDataLengthInBytes);
newTransferBufferNeeded = true;
}
if (newTransferBufferNeeded)
{
TransferBuffer.Dispose();
TransferBuffer = new TransferBuffer(
graphicsDevice,
TransferBufferUsage.Upload,
VertexBuffer.Size + IndexBuffer.Size
);
}
if (vertexDataLengthInBytes > 0 && indexDataLengthInBytes > 0)
{
TransferBuffer.SetData(vertexSpan, true);
TransferBuffer.SetData(indexSpan, (uint)vertexSpan.Length, false);
CopyPass copyPass = commandBuffer.BeginCopyPass();
copyPass.UploadToBuffer(
new TransferBufferLocation(TransferBuffer),
new BufferRegion(VertexBuffer, 0, (uint)vertexSpan.Length),
true
);
copyPass.UploadToBuffer(
new TransferBufferLocation(TransferBuffer, (uint)vertexSpan.Length),
new BufferRegion(IndexBuffer, 0, (uint)indexSpan.Length),
true
);
commandBuffer.EndCopyPass(copyPass);
}
PrimitiveCount = vertexCount / 2;
}
// Call this AFTER binding your text pipeline!
public void Render(CommandBuffer commandBuffer, RenderPass renderPass, Matrix4x4 transformMatrix)
{
commandBuffer.PushVertexUniformData(transformMatrix);
commandBuffer.PushFragmentUniformData(CurrentFont.DistanceRange);
renderPass.BindFragmentSampler(
new TextureSamplerBinding(
CurrentFont.Texture,
graphicsDevice.LinearSampler
)
);
renderPass.BindVertexBuffer(VertexBuffer);
renderPass.BindIndexBuffer(IndexBuffer, IndexElementSize.ThirtyTwo);
renderPass.DrawIndexedPrimitives(
0,
0,
PrimitiveCount
);
}
protected override void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
VertexBuffer.Dispose();
IndexBuffer.Dispose();
}
NativeMemory.Free(stringBytes);
Wellspring.Wellspring_DestroyTextBatch(Handle);
}
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,466 @@
using System.Runtime.InteropServices;
using Nerfed.Runtime.Video;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Manages all graphics-related concerns.
/// </summary>
public class GraphicsDevice : IDisposable
{
public IntPtr Handle { get; }
public BackendFlags Backend { get; }
public bool DebugMode { get; }
// Built-in video pipeline
internal GraphicsPipeline VideoPipeline { get; }
// Built-in text shader info
public Shader TextVertexShader;
public Shader TextFragmentShader;
public VertexInputState TextVertexInputState { get; }
// Built-in samplers
public Sampler PointSampler { get; }
public Sampler LinearSampler { get; }
public bool IsDisposed { get; private set; }
internal readonly RenderPassPool RenderPassPool = new RenderPassPool();
internal readonly ComputePassPool ComputePassPool = new ComputePassPool();
internal readonly CopyPassPool CopyPassPool = new CopyPassPool();
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
private readonly CommandBufferPool commandBufferPool;
private readonly FencePool fencePool;
internal GraphicsDevice(BackendFlags preferredBackends)
{
if (preferredBackends == BackendFlags.Invalid)
{
throw new System.Exception("Could not set graphics backend!");
}
bool debugMode = false;
#if DEBUG
debugMode = true;
#endif
Handle = Refresh.Refresh_CreateDevice((Refresh.BackendFlags)preferredBackends, Conversions.BoolToInt(debugMode));
DebugMode = debugMode;
// TODO: check for CreateDevice fail
Backend = (BackendFlags)Refresh.Refresh_GetBackend(Handle);
IEmbeddedShaders embeddedShaders;
switch (Backend)
{
case BackendFlags.Vulkan:
embeddedShaders = new EmbeddedShadersSpirV();
break;
case BackendFlags.D3D11:
throw new NotImplementedException("D3D11 embedded shaders");
break;
case BackendFlags.Metal:
throw new NotImplementedException("Metal embedded shaders");
break;
default: throw new ArgumentOutOfRangeException();
}
Shader fullscreenVertShader;
Shader textVertShader;
Shader textFragShader;
Shader videoFragShader;
using (MemoryStream fullscreenVertStream = new MemoryStream(embeddedShaders.FullscreenVert))
{
fullscreenVertShader = new Shader(
this,
fullscreenVertStream,
"main",
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(
this,
new GraphicsPipelineCreateInfo
{
AttachmentInfo = new GraphicsPipelineAttachmentInfo(
new ColorAttachmentDescription(
TextureFormat.R8G8B8A8,
ColorAttachmentBlendState.None
)
),
DepthStencilState = DepthStencilState.Disable,
VertexShader = fullscreenVertShader,
FragmentShader = videoFragShader,
VertexInputState = VertexInputState.Empty,
RasterizerState = RasterizerState.CCW_CullNone,
PrimitiveType = PrimitiveType.TriangleList,
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>
/// Prepares a window so that frames can be presented to it.
/// </summary>
/// <param name="swapchainComposition">The desired composition of the swapchain. Ignore this unless you are using HDR or tonemapping.</param>
/// <param name="presentMode">The desired presentation mode for the window. Roughly equivalent to V-Sync.</param>
/// <returns>True if successfully claimed.</returns>
public bool ClaimWindow(
Window window,
SwapchainComposition swapchainComposition,
PresentMode presentMode
)
{
if (window.Claimed)
{
Log.Error("Window already claimed!");
return false;
}
bool success = Conversions.IntToBool(
Refresh.Refresh_ClaimWindow(
Handle,
window.Handle,
(Refresh.SwapchainComposition)swapchainComposition,
(Refresh.PresentMode)presentMode
)
);
if (success)
{
window.Claimed = true;
window.SwapchainComposition = swapchainComposition;
window.SwapchainFormat = GetSwapchainFormat(window);
if (window.SwapchainTexture == null)
{
window.SwapchainTexture = new Texture(this, window.SwapchainFormat);
}
}
return success;
}
/// <summary>
/// Unclaims a window, making it unavailable for presenting and freeing associated resources.
/// </summary>
public void UnclaimWindow(Window window)
{
if (window.Claimed)
{
Refresh.Refresh_UnclaimWindow(
Handle,
window.Handle
);
window.Claimed = false;
// The swapchain texture doesn't actually have a permanent texture reference, so we zero the handle before disposing.
window.SwapchainTexture.Handle = IntPtr.Zero;
window.SwapchainTexture.Dispose();
window.SwapchainTexture = null;
}
}
/// <summary>
/// Changes the present mode of a claimed window. Does nothing if the window is not claimed.
/// </summary>
public bool SetSwapchainParameters(
Window window,
SwapchainComposition swapchainComposition,
PresentMode presentMode
)
{
if (!window.Claimed)
{
Log.Error("Cannot set present mode on unclaimed window!");
return false;
}
bool success = Conversions.IntToBool(
Refresh.Refresh_SetSwapchainParameters(
Handle,
window.Handle,
(Refresh.SwapchainComposition)swapchainComposition,
(Refresh.PresentMode)presentMode
)
);
if (success)
{
window.SwapchainComposition = swapchainComposition;
window.SwapchainFormat = GetSwapchainFormat(window);
if (window.SwapchainTexture != null)
{
window.SwapchainTexture.Format = window.SwapchainFormat;
}
}
return success;
}
/// <summary>
/// Acquires a command buffer.
/// This is the start of your rendering process.
/// </summary>
/// <returns></returns>
public CommandBuffer AcquireCommandBuffer()
{
CommandBuffer commandBuffer = commandBufferPool.Obtain();
commandBuffer.SetHandle(Refresh.Refresh_AcquireCommandBuffer(Handle));
#if DEBUG
commandBuffer.ResetStateTracking();
#endif
return commandBuffer;
}
/// <summary>
/// Submits a command buffer to the GPU for processing.
/// </summary>
public void Submit(CommandBuffer commandBuffer)
{
#if DEBUG
if (commandBuffer.Submitted)
{
throw new System.InvalidOperationException("Command buffer already submitted!");
}
#endif
Refresh.Refresh_Submit(
commandBuffer.Handle
);
commandBufferPool.Return(commandBuffer);
#if DEBUG
commandBuffer.Submitted = true;
#endif
}
/// <summary>
/// Submits a command buffer to the GPU for processing and acquires a fence associated with the submission.
/// </summary>
/// <returns></returns>
public Fence SubmitAndAcquireFence(CommandBuffer commandBuffer)
{
IntPtr fenceHandle = Refresh.Refresh_SubmitAndAcquireFence(
commandBuffer.Handle
);
Fence fence = fencePool.Obtain();
fence.SetHandle(fenceHandle);
return fence;
}
/// <summary>
/// Wait for the graphics device to become idle.
/// </summary>
public void Wait()
{
Refresh.Refresh_Wait(Handle);
}
/// <summary>
/// Waits for the given fence to become signaled.
/// </summary>
public unsafe void WaitForFence(Fence fence)
{
IntPtr fenceHandle = fence.Handle;
Refresh.Refresh_WaitForFences(
Handle,
1,
&fenceHandle,
1
);
}
/// <summary>
/// Wait for one or more fences to become signaled.
/// </summary>
/// <param name="waitAll">If true, will wait for all given fences to be signaled.</param>
public unsafe void WaitForFences(Span<Fence> fences, bool waitAll)
{
IntPtr* handlePtr = stackalloc nint[fences.Length];
for (int i = 0; i < fences.Length; i += 1)
{
handlePtr[i] = fences[i].Handle;
}
Refresh.Refresh_WaitForFences(
Handle,
Conversions.BoolToInt(waitAll),
handlePtr,
(uint)fences.Length
);
}
/// <summary>
/// Returns true if the fence is signaled, indicating that the associated command buffer has finished processing.
/// </summary>
/// <exception cref="InvalidOperationException">Throws if the fence query indicates that the graphics device has been lost.</exception>
public bool QueryFence(Fence fence)
{
int result = Refresh.Refresh_QueryFence(Handle, fence.Handle);
if (result < 0)
{
throw new InvalidOperationException("The graphics device has been lost.");
}
return result != 0;
}
/// <summary>
/// Release reference to an acquired fence, enabling it to be reused.
/// </summary>
public void ReleaseFence(Fence fence)
{
Refresh.Refresh_ReleaseFence(Handle, fence.Handle);
fence.Handle = IntPtr.Zero;
fencePool.Return(fence);
}
private TextureFormat GetSwapchainFormat(Window window)
{
if (!window.Claimed)
{
throw new System.ArgumentException("Cannot get swapchain format of unclaimed window!");
}
return (TextureFormat)Refresh.Refresh_GetSwapchainTextureFormat(Handle, window.Handle);
}
internal void AddResourceReference(GCHandle resourceReference)
{
lock (resources)
{
resources.Add(resourceReference);
}
}
internal void RemoveResourceReference(GCHandle resourceReference)
{
lock (resources)
{
resources.Remove(resourceReference);
}
}
private void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
lock (resources)
{
// Dispose video players first to avoid race condition on threaded decoding
foreach (GCHandle resource in resources)
{
if (resource.Target is VideoPlayer player)
{
player.Dispose();
}
}
// Dispose everything else
foreach (GCHandle resource in resources)
{
if (resource.Target is IDisposable disposable)
{
disposable.Dispose();
}
}
resources.Clear();
}
}
Refresh.Refresh_DestroyDevice(Handle);
IsDisposed = true;
}
}
~GraphicsDevice()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Runtime.InteropServices;
namespace Nerfed.Runtime.Graphics;
public abstract class GraphicsResource : IDisposable
{
public GraphicsDevice Device { get; }
private GCHandle SelfReference;
public bool IsDisposed { get; private set; }
protected GraphicsResource(GraphicsDevice device)
{
Device = device;
SelfReference = GCHandle.Alloc(this, GCHandleType.Weak);
Device.AddResourceReference(SelfReference);
}
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
Device.RemoveResourceReference(SelfReference);
SelfReference.Free();
}
IsDisposed = true;
}
}
~GraphicsResource()
{
#if DEBUG
// If you see this log message, you leaked a graphics resource without disposing it!
// We'll try to clean it up for you but you really should fix this.
Log.Warning($"A resource of type {GetType().Name} was not Disposed.");
#endif
Dispose(false);
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View File

@ -0,0 +1,10 @@
namespace Nerfed.Runtime.Graphics;
internal interface IEmbeddedShaders
{
ShaderFormat ShaderFormat { get; }
byte[] FullscreenVert { get; }
byte[] TextMsdfFrag { get; }
byte[] TextTransformVert { get; }
byte[] VideoYuv2RgbaFrag { get; }
}

View File

@ -0,0 +1,17 @@
namespace Nerfed.Runtime.Graphics;
/// <summary>
/// Can be defined on your struct type to enable simplified vertex input state definition.
/// </summary>
public interface IVertexType
{
/// <summary>
/// An ordered list of the types in your vertex struct.
/// </summary>
static abstract VertexElementFormat[] Formats { get; }
/// <summary>
/// An ordered list of the offsets in your vertex struct.
/// </summary>
static abstract uint[] Offsets { get; }
}

View File

@ -0,0 +1,455 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
public static class ImageUtils
{
/// <summary>
/// Gets pointer to pixel data from compressed image byte data.
///
/// The returned pointer must be freed by calling FreePixelData.
/// </summary>
public static unsafe byte* GetPixelDataFromBytes(
Span<byte> data,
out uint width,
out uint height,
out uint sizeInBytes
) {
fixed (byte* ptr = data)
{
byte* pixelData =
Refresh.Refresh_Image_Load(
ptr,
data.Length,
out int w,
out int h,
out int len
);
width = (uint) w;
height = (uint) h;
sizeInBytes = (uint) len;
return pixelData;
}
}
/// <summary>
/// Gets pointer to pixel data from a compressed image stream.
///
/// The returned pointer must be freed by calling FreePixelData.
/// </summary>
public static unsafe byte* GetPixelDataFromStream(
Stream stream,
out uint width,
out uint height,
out uint sizeInBytes
) {
long length = stream.Length;
void* buffer = NativeMemory.Alloc((nuint) length);
Span<byte> span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
byte* pixelData = GetPixelDataFromBytes(span, out width, out height, out sizeInBytes);
NativeMemory.Free(buffer);
return pixelData;
}
/// <summary>
/// Gets pointer to pixel data from a compressed image file.
///
/// The returned pointer must be freed by calling FreePixelData.
/// </summary>
public static unsafe byte* GetPixelDataFromFile(
string path,
out uint width,
out uint height,
out uint sizeInBytes
) {
FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return GetPixelDataFromStream(fileStream, out width, out height, out sizeInBytes);
}
/// <summary>
/// Get metadata from compressed image bytes.
/// </summary>
public static unsafe bool ImageInfoFromBytes(
Span<byte> data,
out uint width,
out uint height,
out uint sizeInBytes
) {
fixed (byte* ptr = data)
{
int result =
Refresh.Refresh_Image_Info(
ptr,
data.Length,
out int w,
out int h,
out int len
);
width = (uint) w;
height = (uint) h;
sizeInBytes = (uint) len;
return Conversions.IntToBool(result);
}
}
/// <summary>
/// Get metadata from a compressed image stream.
/// </summary>
public static unsafe bool ImageInfoFromStream(
Stream stream,
out uint width,
out uint height,
out uint sizeInBytes
) {
long length = stream.Length;
void* buffer = NativeMemory.Alloc((nuint) length);
Span<byte> span = new Span<byte>(buffer, (int) length);
stream.ReadExactly(span);
bool result = ImageInfoFromBytes(span, out width, out height, out sizeInBytes);
NativeMemory.Free(buffer);
return result;
}
/// <summary>
/// Get metadata from a compressed image file.
/// </summary>
public static bool ImageInfoFromFile(
string path,
out uint width,
out uint height,
out uint sizeInBytes
) {
FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return ImageInfoFromStream(fileStream, out width, out height, out sizeInBytes);
}
/// <summary>
/// Frees pixel data obtained from GetPixelData methods.
/// </summary>
public unsafe static void FreePixelData(byte* pixels)
{
Refresh.Refresh_Image_Free(pixels);
}
/// <summary>
/// Saves pixel data contained in a TransferBuffer to a PNG file.
/// </summary>
public static unsafe void SavePNG(
string path,
TransferBuffer transferBuffer,
uint bufferOffsetInBytes,
int width,
int height,
bool bgra
) {
int sizeInBytes = width * height * 4;
byte* pixelsPtr = (byte*) NativeMemory.Alloc((nuint) sizeInBytes);
Span<byte> pixelsSpan = new Span<byte>(pixelsPtr, sizeInBytes);
transferBuffer.GetData(pixelsSpan, bufferOffsetInBytes);
if (bgra)
{
// if data is bgra, we have to swap the R and B channels
byte* rgbaPtr = (byte*) NativeMemory.Alloc((nuint) sizeInBytes);
Span<byte> rgbaSpan = new Span<byte>(rgbaPtr, sizeInBytes);
for (int i = 0; i < sizeInBytes; i += 4)
{
rgbaSpan[i] = pixelsSpan[i + 2];
rgbaSpan[i + 1] = pixelsSpan[i + 1];
rgbaSpan[i + 2] = pixelsSpan[i];
rgbaSpan[i + 3] = pixelsSpan[i + 3];
}
NativeMemory.Free(pixelsPtr);
pixelsPtr = rgbaPtr;
}
Refresh.Refresh_Image_SavePNG(path, pixelsPtr, width, height);
NativeMemory.Free(pixelsPtr);
}
// DDS loading extension, based on MojoDDS
// Taken from https://github.com/FNA-XNA/FNA/blob/1e49f868f595f62bc6385db45949a03186a7cd7f/src/Graphics/Texture.cs#L194
public static void ParseDDS(
BinaryReader reader,
out TextureFormat format,
out int width,
out int height,
out int levels,
out bool isCube
) {
// A whole bunch of magic numbers, yay DDS!
const uint DDS_MAGIC = 0x20534444;
const uint DDS_HEADERSIZE = 124;
const uint DDS_PIXFMTSIZE = 32;
const uint DDSD_HEIGHT = 0x2;
const uint DDSD_WIDTH = 0x4;
const uint DDSD_PITCH = 0x8;
const uint DDSD_LINEARSIZE = 0x80000;
const uint DDSD_REQ = (
DDSD_HEIGHT | DDSD_WIDTH
);
const uint DDSCAPS_MIPMAP = 0x400000;
const uint DDSCAPS_TEXTURE = 0x1000;
const uint DDSCAPS2_CUBEMAP = 0x200;
const uint DDPF_FOURCC = 0x4;
const uint DDPF_RGB = 0x40;
const uint FOURCC_DXT1 = 0x31545844;
const uint FOURCC_DXT3 = 0x33545844;
const uint FOURCC_DXT5 = 0x35545844;
const uint FOURCC_DX10 = 0x30315844;
const uint pitchAndLinear = (
DDSD_PITCH | DDSD_LINEARSIZE
);
// File should start with 'DDS '
if (reader.ReadUInt32() != DDS_MAGIC)
{
throw new NotSupportedException("Not a DDS!");
}
// Texture info
uint size = reader.ReadUInt32();
if (size != DDS_HEADERSIZE)
{
throw new NotSupportedException("Invalid DDS header!");
}
uint flags = reader.ReadUInt32();
if ((flags & DDSD_REQ) != DDSD_REQ)
{
throw new NotSupportedException("Invalid DDS flags!");
}
if ((flags & pitchAndLinear) == pitchAndLinear)
{
throw new NotSupportedException("Invalid DDS flags!");
}
height = reader.ReadInt32();
width = reader.ReadInt32();
reader.ReadUInt32(); // dwPitchOrLinearSize, unused
reader.ReadUInt32(); // dwDepth, unused
levels = reader.ReadInt32();
// "Reserved"
reader.ReadBytes(4 * 11);
// Format info
uint formatSize = reader.ReadUInt32();
if (formatSize != DDS_PIXFMTSIZE)
{
throw new NotSupportedException("Bogus PIXFMTSIZE!");
}
uint formatFlags = reader.ReadUInt32();
uint formatFourCC = reader.ReadUInt32();
uint formatRGBBitCount = reader.ReadUInt32();
uint formatRBitMask = reader.ReadUInt32();
uint formatGBitMask = reader.ReadUInt32();
uint formatBBitMask = reader.ReadUInt32();
uint formatABitMask = reader.ReadUInt32();
// dwCaps "stuff"
uint caps = reader.ReadUInt32();
if ((caps & DDSCAPS_TEXTURE) == 0)
{
throw new NotSupportedException("Not a texture!");
}
isCube = false;
uint caps2 = reader.ReadUInt32();
if (caps2 != 0)
{
if ((caps2 & DDSCAPS2_CUBEMAP) == DDSCAPS2_CUBEMAP)
{
isCube = true;
}
else
{
throw new NotSupportedException("Invalid caps2!");
}
}
reader.ReadUInt32(); // dwCaps3, unused
reader.ReadUInt32(); // dwCaps4, unused
// "Reserved"
reader.ReadUInt32();
// Mipmap sanity check
if ((caps & DDSCAPS_MIPMAP) != DDSCAPS_MIPMAP)
{
levels = 1;
}
// Determine texture format
if ((formatFlags & DDPF_FOURCC) == DDPF_FOURCC)
{
switch (formatFourCC)
{
case 0x71: // D3DFMT_A16B16G16R16F
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 0x74: // D3DFMT_A32B32G32R32F
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case FOURCC_DXT1:
format = TextureFormat.BC1;
break;
case FOURCC_DXT3:
format = TextureFormat.BC2;
break;
case FOURCC_DXT5:
format = TextureFormat.BC3;
break;
case FOURCC_DX10:
// If the fourCC is DX10, there is an extra header with additional format information.
uint dxgiFormat = reader.ReadUInt32();
// These values are taken from the DXGI_FORMAT enum.
switch (dxgiFormat)
{
case 2:
format = TextureFormat.R32G32B32A32_SFLOAT;
break;
case 10:
format = TextureFormat.R16G16B16A16_SFLOAT;
break;
case 71:
format = TextureFormat.BC1;
break;
case 74:
format = TextureFormat.BC2;
break;
case 77:
format = TextureFormat.BC3;
break;
case 98:
format = TextureFormat.BC7;
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
uint resourceDimension = reader.ReadUInt32();
// These values are taken from the D3D10_RESOURCE_DIMENSION enum.
switch (resourceDimension)
{
case 0: // Unknown
case 1: // Buffer
throw new NotSupportedException(
"Unsupported DDS texture format"
);
default:
break;
}
/*
* This flag seemingly only indicates if the texture is a cube map.
* This is already determined above. Cool!
*/
uint miscFlag = reader.ReadUInt32();
/*
* Indicates the number of elements in the texture array.
* We don't support texture arrays so just throw if it's greater than 1.
*/
uint arraySize = reader.ReadUInt32();
if (arraySize > 1)
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
reader.ReadUInt32(); // reserved
break;
default:
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
else if ((formatFlags & DDPF_RGB) == DDPF_RGB)
{
if ( formatRGBBitCount != 32 ||
formatRBitMask != 0x00FF0000 ||
formatGBitMask != 0x0000FF00 ||
formatBBitMask != 0x000000FF ||
formatABitMask != 0xFF000000 )
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
format = TextureFormat.B8G8R8A8;
}
else
{
throw new NotSupportedException(
"Unsupported DDS texture format"
);
}
}
public static int CalculateDDSLevelSize(
int width,
int height,
TextureFormat format
) {
if (format == TextureFormat.R8G8B8A8)
{
return (((width * 32) + 7) / 8) * height;
}
else if (format == TextureFormat.R16G16B16A16_SFLOAT)
{
return (((width * 64) + 7) / 8) * height;
}
else if (format == TextureFormat.R32G32B32A32_SFLOAT)
{
return (((width * 128) + 7) / 8) * height;
}
else
{
int blockSize = 16;
if (format == TextureFormat.BC1)
{
blockSize = 8;
}
width = System.Math.Max(width, 1);
height = System.Math.Max(height, 1);
return (
((width + 3) / 4) *
((height + 3) / 4) *
blockSize
);
}
}
}

View File

@ -0,0 +1,161 @@
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
using System.Numerics;
namespace Nerfed.Runtime.Graphics.PackedVector;
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
/// The x and z components use 5 bits, and the y component uses 6 bits.
/// </summary>
public struct Alpha8 : IPackedVector<byte>, IEquatable<Alpha8>, IPackedVector
{
#region Public Properties
/// <summary>
/// Gets and sets the packed value.
/// </summary>
public byte PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private byte packedValue;
#endregion
#region Public Constructor
/// <summary>
/// Creates a new instance of Alpha8.
/// </summary>
/// <param name="alpha">The alpha component</param>
public Alpha8(float alpha)
{
packedValue = Pack(alpha);
}
#endregion
#region Public Methods
/// <summary>
/// Gets the packed vector in float format.
/// </summary>
/// <returns>The packed vector in Vector3 format</returns>
public float ToAlpha()
{
return (float) (packedValue / 255.0f);
}
#endregion
#region IPackedVector Methods
/// <summary>
/// Sets the packed vector from a Vector4.
/// </summary>
/// <param name="vector">Vector containing the components.</param>
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.W);
}
/// <summary>
/// Gets the packed vector in Vector4 format.
/// </summary>
/// <returns>The packed vector in Vector4 format</returns>
Vector4 IPackedVector.ToVector4()
{
return new Vector4(
0.0f,
0.0f,
0.0f,
(float) (packedValue / 255.0f)
);
}
#endregion
#region Public Static Operators and Override Methods
/// <summary>
/// Compares an object with the packed vector.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns>True if the object is equal to the packed vector.</returns>
public override bool Equals(object obj)
{
return (obj is Alpha8) && Equals((Alpha8) obj);
}
/// <summary>
/// Compares another Bgra5551 packed vector with the packed vector.
/// </summary>
/// <param name="other">The Bgra5551 packed vector to compare.</param>
/// <returns>True if the packed vectors are equal.</returns>
public bool Equals(Alpha8 other)
{
return packedValue == other.packedValue;
}
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return packedValue.ToString("X");
}
/// <summary>
/// Gets a hash code of the packed vector.
/// </summary>
/// <returns>The hash code for the packed vector.</returns>
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public static bool operator ==(Alpha8 lhs, Alpha8 rhs)
{
return lhs.packedValue == rhs.packedValue;
}
public static bool operator !=(Alpha8 lhs, Alpha8 rhs)
{
return lhs.packedValue != rhs.packedValue;
}
#endregion
#region Private Static Pack Method
private static byte Pack(float alpha)
{
return (byte) System.Math.Round(Math.Clamp(alpha, 0, 1) * 255.0f);
}
#endregion
}

View File

@ -0,0 +1,184 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
/// The x and z components use 5 bits, and the y component uses 6 bits.
/// </summary>
public struct Bgr565 : IPackedVector<ushort>, IEquatable<Bgr565>, IPackedVector
{
#region Public Properties
/// <summary>
/// Gets and sets the packed value.
/// </summary>
public ushort PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private ushort packedValue;
#endregion
#region Public Constructors
/// <summary>
/// Creates a new instance of Bgr565.
/// </summary>
/// <param name="x">The x component</param>
/// <param name="y">The y component</param>
/// <param name="z">The z component</param>
public Bgr565(float x, float y, float z)
{
packedValue = Pack(x, y, z);
}
/// <summary>
/// Creates a new instance of Bgr565.
/// </summary>
/// <param name="vector">
/// Vector containing the components for the packed vector.
/// </param>
public Bgr565(Vector3 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z);
}
#endregion
#region Public Methods
/// <summary>
/// Gets the packed vector in Vector3 format.
/// </summary>
/// <returns>The packed vector in Vector3 format</returns>
public Vector3 ToVector3()
{
return new Vector3(
(packedValue >> 11) / 31.0f,
((packedValue >> 5) & 0x3F) / 63.0f,
(packedValue & 0x1F) / 31.0f
);
}
#endregion
#region IPackedVector Methods
/// <summary>
/// Sets the packed vector from a Vector4.
/// </summary>
/// <param name="vector">Vector containing the components.</param>
void IPackedVector.PackFromVector4(Vector4 vector)
{
Pack(vector.X, vector.Y, vector.Z);
}
/// <summary>
/// Gets the packed vector in Vector4 format.
/// </summary>
/// <returns>The packed vector in Vector4 format</returns>
Vector4 IPackedVector.ToVector4()
{
return new Vector4(ToVector3(), 1.0f);
}
#endregion
#region Public Static Operators and Override Methods
/// <summary>
/// Compares an object with the packed vector.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns>True if the object is equal to the packed vector.</returns>
public override bool Equals(object obj)
{
return (obj is Bgr565) && Equals((Bgr565) obj);
}
/// <summary>
/// Compares another Bgr565 packed vector with the packed vector.
/// </summary>
/// <param name="other">The Bgr565 packed vector to compare.</param>
/// <returns>True if the packed vectors are equal.</returns>
public bool Equals(Bgr565 other)
{
return packedValue == other.packedValue;
}
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return packedValue.ToString("X");
}
/// <summary>
/// Gets a hash code of the packed vector.
/// </summary>
/// <returns>The hash code for the packed vector.</returns>
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public static bool operator ==(Bgr565 lhs, Bgr565 rhs)
{
return lhs.packedValue == rhs.packedValue;
}
public static bool operator !=(Bgr565 lhs, Bgr565 rhs)
{
return lhs.packedValue != rhs.packedValue;
}
#endregion
#region Private Static Pack Method
private static ushort Pack(float x, float y, float z)
{
return (ushort) (
(((ushort) System.Math.Round(Math.Clamp(x, 0, 1) * 31.0f)) << 11) |
(((ushort) System.Math.Round(Math.Clamp(y, 0, 1) * 63.0f)) << 5) |
((ushort) System.Math.Round(Math.Clamp(z, 0, 1) * 31.0f))
);
}
#endregion
}

View File

@ -0,0 +1,178 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
/// <summary>
/// Packed vector type containing unsigned normalized values, ranging from 0 to 1, using
/// 4 bits each for x, y, z, and w.
/// </summary>
public struct Bgra4444 : IPackedVector<ushort>, IEquatable<Bgra4444>
{
#region Public Properties
/// <summary>
/// Gets and sets the packed value.
/// </summary>
public ushort PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private ushort packedValue;
#endregion
#region Public Constructors
/// <summary>
/// Creates a new instance of Bgra4444.
/// </summary>
/// <param name="x">The x component</param>
/// <param name="y">The y component</param>
/// <param name="z">The z component</param>
/// <param name="w">The w component</param>
public Bgra4444(float x, float y, float z, float w)
{
packedValue = Pack(x, y, z, w);
}
/// <summary>
/// Creates a new instance of Bgra4444.
/// </summary>
/// <param name="vector">
/// Vector containing the components for the packed vector.
/// </param>
public Bgra4444(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Methods
/// <summary>
/// Gets the packed vector in Vector4 format.
/// </summary>
/// <returns>The packed vector in Vector4 format</returns>
public Vector4 ToVector4()
{
return new Vector4(
((packedValue >> 8) & 0x0F) / 15.0f,
((packedValue >> 4) & 0x0F) / 15.0f,
(packedValue & 0x0F) / 15.0f,
(packedValue >> 12) / 15.0f
);
}
#endregion
#region IPackedVector Methods
/// <summary>
/// Sets the packed vector from a Vector4.
/// </summary>
/// <param name="vector">Vector containing the components.</param>
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Static Operators and Override Methods
/// <summary>
/// Compares an object with the packed vector.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns>True if the object is equal to the packed vector.</returns>
public override bool Equals(object obj)
{
return (obj is Bgra4444) && Equals((Bgra4444) obj);
}
/// <summary>
/// Compares another Bgra4444 packed vector with the packed vector.
/// </summary>
/// <param name="other">The Bgra4444 packed vector to compare.</param>
/// <returns>True if the packed vectors are equal.</returns>
public bool Equals(Bgra4444 other)
{
return packedValue == other.packedValue;
}
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return packedValue.ToString("X");
}
/// <summary>
/// Gets a hash code of the packed vector.
/// </summary>
/// <returns>The hash code for the packed vector.</returns>
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public static bool operator ==(Bgra4444 lhs, Bgra4444 rhs)
{
return lhs.packedValue == rhs.packedValue;
}
public static bool operator !=(Bgra4444 lhs, Bgra4444 rhs)
{
return lhs.packedValue != rhs.packedValue;
}
#endregion
#region Private Static Pack Method
private static ushort Pack(float x, float y, float z, float w)
{
return (ushort) (
(((ushort) System.Math.Round(Math.Clamp(x, 0, 1) * 15.0f)) << 8) |
(((ushort) System.Math.Round(Math.Clamp(y, 0, 1) * 15.0f)) << 4) |
((ushort) System.Math.Round(Math.Clamp(z, 0, 1) * 15.0f)) |
(((ushort) System.Math.Round(Math.Clamp(w, 0, 1) * 15.0f)) << 12)
);
}
#endregion
}

View File

@ -0,0 +1,178 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
/// <summary>
/// Packed vector type containing unsigned normalized values ranging from 0 to 1.
/// The x and z components use 5 bits, and the y component uses 6 bits.
/// </summary>
public struct Bgra5551 : IPackedVector<ushort>, IEquatable<Bgra5551>, IPackedVector
{
#region Public Properties
/// <summary>
/// Gets and sets the packed value.
/// </summary>
public ushort PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private ushort packedValue;
#endregion
#region Public Constructors
/// <summary>
/// Creates a new instance of Bgra5551.
/// </summary>
/// <param name="x">The x component</param>
/// <param name="y">The y component</param>
/// <param name="z">The z component</param>
/// <param name="w">The w component</param>
public Bgra5551(float x, float y, float z, float w)
{
packedValue = Pack(x, y, z, w);
}
/// <summary>
/// Creates a new instance of Bgra5551.
/// </summary>
/// <param name="vector">
/// Vector containing the components for the packed vector.
/// </param>
public Bgra5551(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Methods
/// <summary>
/// Gets the packed vector in Vector4 format.
/// </summary>
/// <returns>The packed vector in Vector4 format</returns>
public Vector4 ToVector4()
{
return new Vector4(
((packedValue >> 10) & 0x1F) / 31.0f,
((packedValue >> 5) & 0x1F) / 31.0f,
(packedValue & 0x1F) / 31.0f,
(float) (packedValue >> 15)
);
}
#endregion
#region IPackedVector Methods
/// <summary>
/// Sets the packed vector from a Vector4.
/// </summary>
/// <param name="vector">Vector containing the components.</param>
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Static Operators and Override Methods
/// <summary>
/// Compares an object with the packed vector.
/// </summary>
/// <param name="obj">The object to compare.</param>
/// <returns>True if the object is equal to the packed vector.</returns>
public override bool Equals(object obj)
{
return (obj is Bgra5551) && Equals((Bgra5551) obj);
}
/// <summary>
/// Compares another Bgra5551 packed vector with the packed vector.
/// </summary>
/// <param name="other">The Bgra5551 packed vector to compare.</param>
/// <returns>True if the packed vectors are equal.</returns>
public bool Equals(Bgra5551 other)
{
return packedValue == other.packedValue;
}
/// <summary>
/// Gets a string representation of the packed vector.
/// </summary>
/// <returns>A string representation of the packed vector.</returns>
public override string ToString()
{
return packedValue.ToString("X");
}
/// <summary>
/// Gets a hash code of the packed vector.
/// </summary>
/// <returns>The hash code for the packed vector.</returns>
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public static bool operator ==(Bgra5551 lhs, Bgra5551 rhs)
{
return lhs.packedValue == rhs.packedValue;
}
public static bool operator !=(Bgra5551 lhs, Bgra5551 rhs)
{
return lhs.packedValue != rhs.packedValue;
}
#endregion
#region Private Static Pack Method
private static ushort Pack(float x, float y, float z, float w)
{
return (ushort) (
(((ushort) System.Math.Round(Math.Clamp(x, 0, 1) * 31.0f)) << 10) |
(((ushort) System.Math.Round(Math.Clamp(y, 0, 1) * 31.0f)) << 5) |
((ushort) System.Math.Round(Math.Clamp(z, 0, 1) * 31.0f)) |
((ushort) System.Math.Round(Math.Clamp(w, 0, 1)) << 15)
);
}
#endregion
}

View File

@ -0,0 +1,204 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
/// <summary>
/// Packed vector type containing four 8-bit unsigned integer values, ranging from 0 to 255.
/// </summary>
public struct Byte4 : IPackedVector<uint>, IEquatable<Byte4>, IPackedVector
{
#region Public Properties
/// <summary>
/// Directly gets or sets the packed representation of the value.
/// </summary>
/// <value>The packed representation of the value.</value>
public uint PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private uint packedValue;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the Byte4 class.
/// </summary>
/// <param name="vector">
/// A vector containing the initial values for the components of the Byte4 structure.
/// </param>
public Byte4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
/// <summary>
/// Initializes a new instance of the Byte4 class.
/// </summary>
/// <param name="x">Initial value for the x component.</param>
/// <param name="y">Initial value for the y component.</param>
/// <param name="z">Initial value for the z component.</param>
/// <param name="w">Initial value for the w component.</param>
public Byte4(float x, float y, float z, float w)
{
packedValue = Pack(x, y, z, w);
}
#endregion
#region Public Methods
/// <summary>
/// Expands the packed representation into a Vector4.
/// </summary>
/// <returns>The expanded vector.</returns>
public Vector4 ToVector4()
{
return new Vector4(
(packedValue & 0xFF),
((packedValue >> 8) & 0xFF),
((packedValue >> 16) & 0xFF),
(packedValue >> 24)
);
}
#endregion
#region IPackedVector Methods
/// <summary>
/// Sets the packed representation from a Vector4.
/// </summary>
/// <param name="vector">The vector to create the packed representation from.</param>
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Static Operators and Override Methods
/// <summary>
/// Compares the current instance of a class to another instance to determine
/// whether they are different.
/// </summary>
/// <param name="a">The object to the left of the equality operator.</param>
/// <param name="b">The object to the right of the equality operator.</param>
/// <returns>True if the objects are different; false otherwise.</returns>
public static bool operator !=(Byte4 a, Byte4 b)
{
return a.packedValue != b.packedValue;
}
/// <summary>
/// Compares the current instance of a class to another instance to determine
/// whether they are the same.
/// </summary>
/// <param name="a">The object to the left of the equality operator.</param>
/// <param name="b">The object to the right of the equality operator.</param>
/// <returns>True if the objects are the same; false otherwise.</returns>
public static bool operator ==(Byte4 a, Byte4 b)
{
return a.packedValue == b.packedValue;
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a
/// specified object.
/// </summary>
/// <param name="obj">The object with which to make the comparison.</param>
/// <returns>
/// True if the current instance is equal to the specified object; false otherwise.
/// </returns>
public override bool Equals(object obj)
{
return (obj is Byte4) && Equals((Byte4) obj);
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a
/// specified object.
/// </summary>
/// <param name="other">The object with which to make the comparison.</param>
/// <returns>
/// True if the current instance is equal to the specified object; false otherwise.
/// </returns>
public bool Equals(Byte4 other)
{
return this == other;
}
/// <summary>
/// Gets the hash code for the current instance.
/// </summary>
/// <returns>Hash code for the instance.</returns>
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
/// <summary>
/// Returns a string representation of the current instance.
/// </summary>
/// <returns>String that represents the object.</returns>
public override string ToString()
{
return packedValue.ToString("X");
}
#endregion
#region Private Static Pack Method
/// <summary>
/// Packs a vector into a uint.
/// </summary>
/// <param name="vector">The vector containing the values to pack.</param>
/// <returns>The ulong containing the packed values.</returns>
static uint Pack(float x, float y, float z, float w)
{
return (uint) (
((uint) System.Math.Round(Math.Clamp(x, 0, 255))) |
((uint) System.Math.Round(Math.Clamp(y, 0, 255)) << 8) |
((uint) System.Math.Round(Math.Clamp(z, 0, 255)) << 16) |
((uint) System.Math.Round(Math.Clamp(w, 0, 255)) << 24)
);
}
#endregion
}

View File

@ -0,0 +1,113 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
public struct HalfSingle : IPackedVector<ushort>, IEquatable<HalfSingle>, IPackedVector
{
#region Public Properties
public ushort PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private ushort packedValue;
#endregion
#region Public Constructors
public HalfSingle(float single)
{
packedValue = HalfTypeHelper.Convert(single);
}
#endregion
#region Public Methods
public float ToSingle()
{
return HalfTypeHelper.Convert(packedValue);
}
#endregion
#region IPackedVector Methods
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = HalfTypeHelper.Convert(vector.X);
}
Vector4 IPackedVector.ToVector4()
{
return new Vector4(ToSingle(), 0.0f, 0.0f, 1.0f);
}
#endregion
#region Public Static Operators and Override Methods
public override bool Equals(object obj)
{
return (obj is HalfSingle) && Equals((HalfSingle) obj);
}
public bool Equals(HalfSingle other)
{
return packedValue == other.packedValue;
}
public override string ToString()
{
return packedValue.ToString("X");
}
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public static bool operator ==(HalfSingle lhs, HalfSingle rhs)
{
return lhs.packedValue == rhs.packedValue;
}
public static bool operator !=(HalfSingle lhs, HalfSingle rhs)
{
return lhs.packedValue != rhs.packedValue;
}
#endregion
}

View File

@ -0,0 +1,138 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Runtime.InteropServices;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
internal static class HalfTypeHelper
{
#region Private Struct uif
[StructLayout(LayoutKind.Explicit)]
private struct uif
{
[FieldOffset(0)]
public float f;
[FieldOffset(0)]
public int i;
[FieldOffset(0)]
public uint u;
}
#endregion
#region Internal Static Methods
internal static ushort Convert(float f)
{
uif uif = new uif();
uif.f = f;
return Convert(uif.i);
}
internal static ushort Convert(int i)
{
int s = (i >> 16) & 0x00008000;
int e = ((i >> 23) & 0x000000ff) - (127 - 15);
int m = i & 0x007fffff;
if (e <= 0)
{
if (e < -10)
{
return (ushort) s;
}
m = m | 0x00800000;
int t = 14 - e;
int a = (1 << (t - 1)) - 1;
int b = (m >> t) & 1;
m = (m + a + b) >> t;
return (ushort) (s | m);
}
else if (e == 0xff - (127 - 15))
{
if (m == 0)
{
return (ushort) (s | 0x7c00);
}
else
{
m >>= 13;
return (ushort) (s | 0x7c00 | m | ((m == 0) ? 1 : 0));
}
}
else
{
m = m + 0x00000fff + ((m >> 13) & 1);
if ((m & 0x00800000) != 0)
{
m = 0;
e += 1;
}
if (e > 30)
{
return (ushort) (s | 0x7c00);
}
return (ushort) (s | (e << 10) | (m >> 13));
}
}
internal static float Convert(ushort value)
{
uint rst;
uint mantissa = (uint) (value & 1023);
uint exp = 0xfffffff2;
if ((value & -33792) == 0)
{
if (mantissa != 0)
{
while ((mantissa & 1024) == 0)
{
exp--;
mantissa = mantissa << 1;
}
mantissa &= 0xfffffbff;
rst = ((uint) ((((uint) value & 0x8000) << 16) | ((exp + 127) << 23))) | (mantissa << 13);
}
else
{
rst = (uint) ((value & 0x8000) << 16);
}
}
else
{
rst = (uint) (((((uint) value & 0x8000) << 16) | ((((((uint) value >> 10) & 0x1f) - 15) + 127) << 23)) | (mantissa << 13));
}
uif uif = new uif();
uif.u = rst;
return uif.f;
}
#endregion
}

View File

@ -0,0 +1,133 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
public struct HalfVector2 : IPackedVector<uint>, IPackedVector, IEquatable<HalfVector2>
{
#region Public Properties
public uint PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private uint packedValue;
#endregion
#region Public Constructors
public HalfVector2(float x, float y)
{
packedValue = PackHelper(x, y);
}
public HalfVector2(Vector2 vector)
{
packedValue = PackHelper(vector.X, vector.Y);
}
#endregion
#region Public Methods
public Vector2 ToVector2()
{
Vector2 vector;
vector.X = HalfTypeHelper.Convert((ushort) packedValue);
vector.Y = HalfTypeHelper.Convert((ushort) (packedValue >> 0x10));
return vector;
}
#endregion
#region IPackedVector Methods
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = PackHelper(vector.X, vector.Y);
}
Vector4 IPackedVector.ToVector4()
{
return new Vector4(ToVector2(), 0.0f, 1.0f);
}
#endregion
#region Public Static Operators and Override Methods
public override string ToString()
{
return packedValue.ToString("X");
}
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public override bool Equals(object obj)
{
return ((obj is HalfVector2) && Equals((HalfVector2) obj));
}
public bool Equals(HalfVector2 other)
{
return packedValue.Equals(other.packedValue);
}
public static bool operator ==(HalfVector2 a, HalfVector2 b)
{
return a.Equals(b);
}
public static bool operator !=(HalfVector2 a, HalfVector2 b)
{
return !a.Equals(b);
}
#endregion
#region Private Static Pack Method
private static uint PackHelper(float vectorX, float vectorY)
{
return (uint) (
HalfTypeHelper.Convert(vectorX) |
((uint) (HalfTypeHelper.Convert(vectorY) << 0x10))
);
}
#endregion
}

View File

@ -0,0 +1,204 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
/// <summary>
/// Packed vector type containing four 16-bit floating-point values.
/// </summary>
public struct HalfVector4 : IPackedVector<ulong>, IPackedVector, IEquatable<HalfVector4>
{
#region Public Properties
/// <summary>
/// Directly gets or sets the packed representation of the value.
/// </summary>
/// <value>The packed representation of the value.</value>
public ulong PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private ulong packedValue;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the HalfVector4 structure.
/// </summary>
/// <param name="x">Initial value for the x component.</param>
/// <param name="y">Initial value for the y component.</param>
/// <param name="z">Initial value for the z component.</param>
/// <param name="w">Initial value for the q component.</param>
public HalfVector4(float x, float y, float z, float w)
{
packedValue = Pack(x, y, z, w);
}
/// <summary>
/// Initializes a new instance of the HalfVector4 structure.
/// </summary>
/// <param name="vector">
/// A vector containing the initial values for the components of the HalfVector4
/// structure.
/// </param>
public HalfVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Methods
/// <summary>
/// Expands the packed representation into a Vector4.
/// </summary>
/// <returns>The expanded vector.</returns>
public Vector4 ToVector4()
{
return new Vector4(
HalfTypeHelper.Convert((ushort) packedValue),
HalfTypeHelper.Convert((ushort) (packedValue >> 0x10)),
HalfTypeHelper.Convert((ushort) (packedValue >> 0x20)),
HalfTypeHelper.Convert((ushort) (packedValue >> 0x30))
);
}
#endregion
#region IPackedVector Methods
/// <summary>
/// Sets the packed representation from a Vector4.
/// </summary>
/// <param name="vector">The vector to create the packed representation from.</param>
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Static Operators and Override Methods
/// <summary>
/// Returns a string representation of the current instance.
/// </summary>
/// <returns>String that represents the object.</returns>
public override string ToString()
{
return packedValue.ToString("X");
}
/// <summary>
/// Gets the hash code for the current instance.
/// </summary>
/// <returns>Hash code for the instance.</returns>
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a
/// specified object.
/// </summary>
/// <param name="obj">The object with which to make the comparison.</param>
/// <returns>
/// True if the current instance is equal to the specified object; false otherwise.
/// </returns>
public override bool Equals(object obj)
{
return ((obj is HalfVector4) && Equals((HalfVector4) obj));
}
/// <summary>
/// Returns a value that indicates whether the current instance is equal to a
/// specified object.
/// </summary>
/// <param name="other">The object with which to make the comparison.</param>
/// <returns>
/// True if the current instance is equal to the specified object; false otherwise.
/// </returns>
public bool Equals(HalfVector4 other)
{
return packedValue.Equals(other.packedValue);
}
/// <summary>
/// Compares the current instance of a class to another instance to determine
/// whether they are the same.
/// </summary>
/// <param name="a">The object to the left of the equality operator.</param>
/// <param name="b">The object to the right of the equality operator.</param>
/// <returns>True if the objects are the same; false otherwise.</returns>
public static bool operator ==(HalfVector4 a, HalfVector4 b)
{
return a.Equals(b);
}
/// <summary>
/// Compares the current instance of a class to another instance to determine
/// whether they are different.
/// </summary>
/// <param name="a">The object to the left of the equality operator.</param>
/// <param name="b">The object to the right of the equality operator.</param>
/// <returns>True if the objects are different; false otherwise.</returns>
public static bool operator !=(HalfVector4 a, HalfVector4 b)
{
return !a.Equals(b);
}
#endregion
#region Private Static Pack Method
/// <summary>
/// Packs a vector into a ulong.
/// </summary>
/// <param name="vector">The vector containing the values to pack.</param>
/// <returns>The ulong containing the packed values.</returns>
private static ulong Pack(float x, float y, float z, float w)
{
return (ulong) (
((ulong) HalfTypeHelper.Convert(x)) |
(((ulong) HalfTypeHelper.Convert(y) << 0x10)) |
(((ulong) HalfTypeHelper.Convert(z) << 0x20)) |
(((ulong) HalfTypeHelper.Convert(w) << 0x30))
);
}
#endregion
}

View File

@ -0,0 +1,38 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
using System.Numerics;
namespace Nerfed.Runtime.Graphics.PackedVector;
// http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.packedvector.ipackedvector.aspx
public interface IPackedVector
{
void PackFromVector4(Vector4 vector);
Vector4 ToVector4();
}
// PackedVector Generic interface
// http://msdn.microsoft.com/en-us/library/bb197661.aspx
public interface IPackedVector<TPacked> : IPackedVector
{
TPacked PackedValue
{
get;
set;
}
}

View File

@ -0,0 +1,141 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
public struct NormalizedByte2 : IPackedVector<ushort>, IEquatable<NormalizedByte2>
{
#region Public Properties
public ushort PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private ushort packedValue;
#endregion
#region Public Constructors
public NormalizedByte2(Vector2 vector)
{
packedValue = Pack(vector.X, vector.Y);
}
public NormalizedByte2(float x, float y)
{
packedValue = Pack(x, y);
}
#endregion
#region Public Methods
public Vector2 ToVector2()
{
return new Vector2(
((sbyte) (packedValue & 0xFF)) / 127.0f,
((sbyte) ((packedValue >> 8) & 0xFF)) / 127.0f
);
}
#endregion
#region IPackedVector Methods
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y);
}
Vector4 IPackedVector.ToVector4()
{
return new Vector4(ToVector2(), 0.0f, 1.0f);
}
#endregion
#region Public Static Operators and Override Methods
public static bool operator !=(NormalizedByte2 a, NormalizedByte2 b)
{
return a.packedValue != b.packedValue;
}
public static bool operator ==(NormalizedByte2 a, NormalizedByte2 b)
{
return a.packedValue == b.packedValue;
}
public override bool Equals(object obj)
{
return (obj is NormalizedByte2) && Equals((NormalizedByte2) obj);
}
public bool Equals(NormalizedByte2 other)
{
return packedValue == other.packedValue;
}
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public override string ToString()
{
return packedValue.ToString("X");
}
#endregion
#region Private Static Pack Method
private static ushort Pack(float x, float y)
{
int byte2 = (
((ushort)
System.Math.Round(Math.Clamp(x, -1.0f, 1.0f) * 127.0f)
)
) & 0x00FF;
int byte1 = (
((ushort)
System.Math.Round(Math.Clamp(y, -1.0f, 1.0f) * 127.0f)
) << 8
) & 0xFF00;
return (ushort) (byte2 | byte1);
}
#endregion
}

View File

@ -0,0 +1,146 @@
#region License
/* MoonWorks - Game Development Framework
* Copyright 2021 Evan Hemsley
*/
/* Derived from code by Ethan Lee (Copyright 2009-2021).
* Released under the Microsoft Public License.
* See fna.LICENSE for details.
* Derived from code by the Mono.Xna Team (Copyright 2006).
* Released under the MIT License. See monoxna.LICENSE for details.
*/
#endregion
#region Using Statements
using System;
using System.Numerics;
#endregion
namespace Nerfed.Runtime.Graphics.PackedVector;
public struct NormalizedByte4 : IPackedVector<uint>, IEquatable<NormalizedByte4>
{
#region Public Properties
public uint PackedValue
{
get
{
return packedValue;
}
set
{
packedValue = value;
}
}
#endregion
#region Private Variables
private uint packedValue;
#endregion
#region Public Constructors
public NormalizedByte4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
public NormalizedByte4(float x, float y, float z, float w)
{
packedValue = Pack(x, y, z, w);
}
#endregion
#region Public Methods
public Vector4 ToVector4()
{
return new Vector4(
((sbyte) (packedValue & 0xFF)) / 127.0f,
((sbyte) ((packedValue >> 8) & 0xFF)) / 127.0f,
((sbyte) ((packedValue >> 16) & 0xFF)) / 127.0f,
((sbyte) ((packedValue >> 24) & 0xFF)) / 127.0f
);
}
#endregion
#region IPackedVector Methods
void IPackedVector.PackFromVector4(Vector4 vector)
{
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
}
#endregion
#region Public Static Operators and Override Methods
public static bool operator !=(NormalizedByte4 a, NormalizedByte4 b)
{
return a.packedValue != b.packedValue;
}
public static bool operator ==(NormalizedByte4 a, NormalizedByte4 b)
{
return a.packedValue == b.packedValue;
}
public override bool Equals(object obj)
{
return (obj is NormalizedByte4) && Equals((NormalizedByte4) obj);
}
public bool Equals(NormalizedByte4 other)
{
return packedValue == other.packedValue;
}
public override int GetHashCode()
{
return packedValue.GetHashCode();
}
public override string ToString()
{
return packedValue.ToString("X");
}
#endregion
#region Private Static Pack Method
private static uint Pack(float x, float y, float z, float w)
{
uint byte4 = (
(uint) System.Math.Round(Math.Clamp(x, -1.0f, 1.0f) * 127.0f)
) & 0x000000FF;
uint byte3 = (
(
(uint) System.Math.Round(Math.Clamp(y, -1.0f, 1.0f) * 127.0f)
) << 8
) & 0x0000FF00;
uint byte2 = (
(
(uint) System.Math.Round(Math.Clamp(z, -1.0f, 1.0f) * 127.0f)
) << 16
) & 0x00FF0000;
uint byte1 = (
(
(uint) System.Math.Round(Math.Clamp(w, -1.0f, 1.0f) * 127.0f)
) << 24
) & 0xFF000000;
return byte4 | byte3 | byte2 | byte1;
}
#endregion
}

Some files were not shown because too many files have changed in this diff Show More