Setup entry point + integrated moonworks stuff
This commit is contained in:
parent
e7a4a862be
commit
8334a24fd1
13
.idea/.idea.Nerfed/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.Nerfed/.idea/.gitignore
generated
vendored
Normal 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
4
.idea/.idea.Nerfed/.idea/encodings.xml
generated
Normal 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>
|
8
.idea/.idea.Nerfed/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.Nerfed/.idea/indexLayout.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
10
.idea/.idea.Nerfed/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
.idea/.idea.Nerfed/.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
6
.idea/.idea.Nerfed/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.Nerfed/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
1
Libraries/RefreshCS
Submodule
1
Libraries/RefreshCS
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 07b3484b26b4ff0e09ea0a00a8e320659dda7479
|
1
Libraries/SDL2CS
Submodule
1
Libraries/SDL2CS
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 1eb20e5c690aee9a5188ba9cf06207295c51d935
|
24
Nerfed.Runtime/Assert.cs
Normal file
24
Nerfed.Runtime/Assert.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
78
Nerfed.Runtime/Audio/AudioBuffer.cs
Normal file
78
Nerfed.Runtime/Audio/AudioBuffer.cs
Normal 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);
|
||||
}
|
||||
}
|
146
Nerfed.Runtime/Audio/AudioDataOgg.cs
Normal file
146
Nerfed.Runtime/Audio/AudioDataOgg.cs
Normal 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);
|
||||
}
|
||||
}
|
163
Nerfed.Runtime/Audio/AudioDataQoa.cs
Normal file
163
Nerfed.Runtime/Audio/AudioDataQoa.cs
Normal 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);
|
||||
}
|
||||
}
|
48
Nerfed.Runtime/Audio/AudioDataStreamable.cs
Normal file
48
Nerfed.Runtime/Audio/AudioDataStreamable.cs
Normal 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);
|
||||
}
|
||||
}
|
99
Nerfed.Runtime/Audio/AudioDataWav.cs
Normal file
99
Nerfed.Runtime/Audio/AudioDataWav.cs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
309
Nerfed.Runtime/Audio/AudioDevice.cs
Normal file
309
Nerfed.Runtime/Audio/AudioDevice.cs
Normal 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);
|
||||
}
|
||||
}
|
135
Nerfed.Runtime/Audio/AudioEmitter.cs
Normal file
135
Nerfed.Runtime/Audio/AudioEmitter.cs
Normal 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;
|
||||
}
|
||||
}
|
97
Nerfed.Runtime/Audio/AudioListener.cs
Normal file
97
Nerfed.Runtime/Audio/AudioListener.cs
Normal 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;
|
||||
}
|
||||
}
|
52
Nerfed.Runtime/Audio/AudioResource.cs
Normal file
52
Nerfed.Runtime/Audio/AudioResource.cs
Normal 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);
|
||||
}
|
||||
}
|
9
Nerfed.Runtime/Audio/FilterType.cs
Normal file
9
Nerfed.Runtime/Audio/FilterType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Nerfed.Runtime.Audio;
|
||||
|
||||
public enum FilterType
|
||||
{
|
||||
None,
|
||||
LowPass,
|
||||
BandPass,
|
||||
HighPass
|
||||
}
|
35
Nerfed.Runtime/Audio/Format.cs
Normal file
35
Nerfed.Runtime/Audio/Format.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
6
Nerfed.Runtime/Audio/IPoolable.cs
Normal file
6
Nerfed.Runtime/Audio/IPoolable.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Nerfed.Runtime.Audio;
|
||||
|
||||
public interface IPoolable<T>
|
||||
{
|
||||
static abstract T Create(AudioDevice device, Format format);
|
||||
}
|
27
Nerfed.Runtime/Audio/PersistentVoice.cs
Normal file
27
Nerfed.Runtime/Audio/PersistentVoice.cs
Normal 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));
|
||||
}
|
||||
}
|
82
Nerfed.Runtime/Audio/ReverbEffect.cs
Normal file
82
Nerfed.Runtime/Audio/ReverbEffect.cs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
63
Nerfed.Runtime/Audio/SoundSequence.cs
Normal file
63
Nerfed.Runtime/Audio/SoundSequence.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
8
Nerfed.Runtime/Audio/SoundState.cs
Normal file
8
Nerfed.Runtime/Audio/SoundState.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Nerfed.Runtime.Audio;
|
||||
|
||||
public enum SoundState
|
||||
{
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped
|
||||
}
|
217
Nerfed.Runtime/Audio/SourceVoice.cs
Normal file
217
Nerfed.Runtime/Audio/SourceVoice.cs
Normal 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();
|
||||
}
|
||||
}
|
38
Nerfed.Runtime/Audio/SourceVoicePool.cs
Normal file
38
Nerfed.Runtime/Audio/SourceVoicePool.cs
Normal 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);
|
||||
}
|
||||
}
|
168
Nerfed.Runtime/Audio/StreamingVoice.cs
Normal file
168
Nerfed.Runtime/Audio/StreamingVoice.cs
Normal 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);
|
||||
}
|
||||
}
|
55
Nerfed.Runtime/Audio/SubmixVoice.cs
Normal file
55
Nerfed.Runtime/Audio/SubmixVoice.cs
Normal 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);
|
||||
}
|
||||
}
|
28
Nerfed.Runtime/Audio/TransientVoice.cs
Normal file
28
Nerfed.Runtime/Audio/TransientVoice.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
Nerfed.Runtime/Audio/UpdatingSourceVoice.cs
Normal file
10
Nerfed.Runtime/Audio/UpdatingSourceVoice.cs
Normal 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();
|
||||
}
|
433
Nerfed.Runtime/Audio/Voice.cs
Normal file
433
Nerfed.Runtime/Audio/Voice.cs
Normal 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);
|
||||
}
|
||||
}
|
252
Nerfed.Runtime/Engine.cs
Normal file
252
Nerfed.Runtime/Engine.cs
Normal file
@ -0,0 +1,252 @@
|
||||
using System.Diagnostics;
|
||||
using Nerfed.Runtime.Audio;
|
||||
using Nerfed.Runtime.Graphics;
|
||||
using SDL2;
|
||||
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public static class Engine
|
||||
{
|
||||
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";
|
||||
//..
|
||||
|
||||
internal 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();
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
Tick();
|
||||
}
|
||||
|
||||
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...
|
||||
|
||||
AudioDevice.WakeThread();
|
||||
accumulatedUpdateTime -= Timestep;
|
||||
}
|
||||
|
||||
double alpha = accumulatedUpdateTime / Timestep;
|
||||
|
||||
// Render here..
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
35
Nerfed.Runtime/FrameLimiterSettings.cs
Normal file
35
Nerfed.Runtime/FrameLimiterSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
1948
Nerfed.Runtime/Graphics/Color.cs
Normal file
1948
Nerfed.Runtime/Graphics/Color.cs
Normal file
File diff suppressed because it is too large
Load Diff
1042
Nerfed.Runtime/Graphics/CommandBuffer.cs
Normal file
1042
Nerfed.Runtime/Graphics/CommandBuffer.cs
Normal file
File diff suppressed because it is too large
Load Diff
33
Nerfed.Runtime/Graphics/CommandBufferPool.cs
Normal file
33
Nerfed.Runtime/Graphics/CommandBufferPool.cs
Normal 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);
|
||||
}
|
||||
}
|
171
Nerfed.Runtime/Graphics/ComputePass.cs
Normal file
171
Nerfed.Runtime/Graphics/ComputePass.cs
Normal 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
|
||||
}
|
25
Nerfed.Runtime/Graphics/ComputePassPool.cs
Normal file
25
Nerfed.Runtime/Graphics/ComputePassPool.cs
Normal 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);
|
||||
}
|
||||
}
|
241
Nerfed.Runtime/Graphics/CopyPass.cs
Normal file
241
Nerfed.Runtime/Graphics/CopyPass.cs
Normal 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
|
||||
}
|
25
Nerfed.Runtime/Graphics/CopyPassPool.cs
Normal file
25
Nerfed.Runtime/Graphics/CopyPassPool.cs
Normal 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);
|
||||
}
|
||||
}
|
716
Nerfed.Runtime/Graphics/EmbeddedShadersSpirV.cs
Normal file
716
Nerfed.Runtime/Graphics/EmbeddedShadersSpirV.cs
Normal 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,
|
||||
];
|
||||
}
|
31
Nerfed.Runtime/Graphics/FencePool.cs
Normal file
31
Nerfed.Runtime/Graphics/FencePool.cs
Normal 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);
|
||||
}
|
||||
}
|
16
Nerfed.Runtime/Graphics/Font/Enums.cs
Normal file
16
Nerfed.Runtime/Graphics/Font/Enums.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
public enum HorizontalAlignment
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum VerticalAlignment
|
||||
{
|
||||
Baseline,
|
||||
Top,
|
||||
Middle,
|
||||
Bottom
|
||||
}
|
123
Nerfed.Runtime/Graphics/Font/Font.cs
Normal file
123
Nerfed.Runtime/Graphics/Font/Font.cs
Normal 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);
|
||||
}
|
||||
}
|
26
Nerfed.Runtime/Graphics/Font/Structs.cs
Normal file
26
Nerfed.Runtime/Graphics/Font/Structs.cs
Normal 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
|
||||
];
|
||||
}
|
191
Nerfed.Runtime/Graphics/Font/TextBatch.cs
Normal file
191
Nerfed.Runtime/Graphics/Font/TextBatch.cs
Normal 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);
|
||||
}
|
||||
}
|
466
Nerfed.Runtime/Graphics/GraphicsDevice.cs
Normal file
466
Nerfed.Runtime/Graphics/GraphicsDevice.cs
Normal 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);
|
||||
}
|
||||
}
|
53
Nerfed.Runtime/Graphics/GraphicsResource.cs
Normal file
53
Nerfed.Runtime/Graphics/GraphicsResource.cs
Normal 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);
|
||||
}
|
||||
}
|
10
Nerfed.Runtime/Graphics/IEmbeddedShaders.cs
Normal file
10
Nerfed.Runtime/Graphics/IEmbeddedShaders.cs
Normal 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; }
|
||||
}
|
17
Nerfed.Runtime/Graphics/IVertexType.cs
Normal file
17
Nerfed.Runtime/Graphics/IVertexType.cs
Normal 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; }
|
||||
}
|
455
Nerfed.Runtime/Graphics/ImageUtils.cs
Normal file
455
Nerfed.Runtime/Graphics/ImageUtils.cs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
161
Nerfed.Runtime/Graphics/PackedVector/Alpha8.cs
Normal file
161
Nerfed.Runtime/Graphics/PackedVector/Alpha8.cs
Normal 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
|
||||
}
|
184
Nerfed.Runtime/Graphics/PackedVector/Bgr565.cs
Normal file
184
Nerfed.Runtime/Graphics/PackedVector/Bgr565.cs
Normal 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
|
||||
}
|
178
Nerfed.Runtime/Graphics/PackedVector/Bgra4444.cs
Normal file
178
Nerfed.Runtime/Graphics/PackedVector/Bgra4444.cs
Normal 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
|
||||
}
|
178
Nerfed.Runtime/Graphics/PackedVector/Bgra5551.cs
Normal file
178
Nerfed.Runtime/Graphics/PackedVector/Bgra5551.cs
Normal 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
|
||||
}
|
204
Nerfed.Runtime/Graphics/PackedVector/Byte4.cs
Normal file
204
Nerfed.Runtime/Graphics/PackedVector/Byte4.cs
Normal 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
|
||||
}
|
113
Nerfed.Runtime/Graphics/PackedVector/HalfSingle.cs
Normal file
113
Nerfed.Runtime/Graphics/PackedVector/HalfSingle.cs
Normal 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
|
||||
}
|
138
Nerfed.Runtime/Graphics/PackedVector/HalfTypeHelper.cs
Normal file
138
Nerfed.Runtime/Graphics/PackedVector/HalfTypeHelper.cs
Normal 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
|
||||
}
|
133
Nerfed.Runtime/Graphics/PackedVector/HalfVector2.cs
Normal file
133
Nerfed.Runtime/Graphics/PackedVector/HalfVector2.cs
Normal 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
|
||||
}
|
204
Nerfed.Runtime/Graphics/PackedVector/HalfVector4.cs
Normal file
204
Nerfed.Runtime/Graphics/PackedVector/HalfVector4.cs
Normal 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
|
||||
}
|
38
Nerfed.Runtime/Graphics/PackedVector/IPackedVector.cs
Normal file
38
Nerfed.Runtime/Graphics/PackedVector/IPackedVector.cs
Normal 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;
|
||||
}
|
||||
}
|
141
Nerfed.Runtime/Graphics/PackedVector/NormalizedByte2.cs
Normal file
141
Nerfed.Runtime/Graphics/PackedVector/NormalizedByte2.cs
Normal 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
|
||||
}
|
146
Nerfed.Runtime/Graphics/PackedVector/NormalizedByte4.cs
Normal file
146
Nerfed.Runtime/Graphics/PackedVector/NormalizedByte4.cs
Normal 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
|
||||
}
|
150
Nerfed.Runtime/Graphics/PackedVector/NormalizedShort2.cs
Normal file
150
Nerfed.Runtime/Graphics/PackedVector/NormalizedShort2.cs
Normal file
@ -0,0 +1,150 @@
|
||||
#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 NormalizedShort2 : IPackedVector<uint>, IEquatable<NormalizedShort2>
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public uint PackedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return packedValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
packedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
|
||||
private uint packedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public NormalizedShort2(Vector2 vector)
|
||||
{
|
||||
packedValue = Pack(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
public NormalizedShort2(float x, float y)
|
||||
{
|
||||
packedValue = Pack(x, y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public Vector2 ToVector2()
|
||||
{
|
||||
const float maxVal = 0x7FFF;
|
||||
|
||||
return new Vector2(
|
||||
(short) (packedValue & 0xFFFF) / maxVal,
|
||||
(short) (packedValue >> 0x10) / maxVal
|
||||
);
|
||||
}
|
||||
|
||||
#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 !=(NormalizedShort2 a, NormalizedShort2 b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator ==(NormalizedShort2 a, NormalizedShort2 b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is NormalizedShort2) && Equals((NormalizedShort2) obj);
|
||||
}
|
||||
|
||||
public bool Equals(NormalizedShort2 other)
|
||||
{
|
||||
return packedValue.Equals(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)
|
||||
{
|
||||
const float max = 0x7FFF;
|
||||
const float min = -max;
|
||||
|
||||
uint word2 = (uint) (
|
||||
(int) Math.Clamp(
|
||||
(float) System.Math.Round(x * max),
|
||||
min,
|
||||
max
|
||||
) & 0xFFFF
|
||||
);
|
||||
uint word1 = (uint) ((
|
||||
(int) Math.Clamp(
|
||||
(float) System.Math.Round(y * max),
|
||||
min,
|
||||
max
|
||||
) & 0xFFFF
|
||||
) << 0x10);
|
||||
|
||||
return (word2 | word1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
161
Nerfed.Runtime/Graphics/PackedVector/NormalizedShort4.cs
Normal file
161
Nerfed.Runtime/Graphics/PackedVector/NormalizedShort4.cs
Normal file
@ -0,0 +1,161 @@
|
||||
#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 NormalizedShort4 : IPackedVector<ulong>, IEquatable<NormalizedShort4>
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public ulong PackedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return packedValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
packedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
|
||||
private ulong packedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public NormalizedShort4(Vector4 vector)
|
||||
{
|
||||
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
|
||||
}
|
||||
|
||||
public NormalizedShort4(float x, float y, float z, float w)
|
||||
{
|
||||
packedValue = Pack(x, y, z, w);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public Vector4 ToVector4()
|
||||
{
|
||||
const float maxVal = 0x7FFF;
|
||||
|
||||
return new Vector4(
|
||||
((short) (packedValue & 0xFFFF)) / maxVal,
|
||||
((short) ((packedValue >> 0x10) & 0xFFFF)) / maxVal,
|
||||
((short) ((packedValue >> 0x20) & 0xFFFF)) / maxVal,
|
||||
((short) ((packedValue >> 0x30) & 0xFFFF)) / maxVal
|
||||
);
|
||||
}
|
||||
|
||||
#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 !=(NormalizedShort4 a, NormalizedShort4 b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator ==(NormalizedShort4 a, NormalizedShort4 b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is NormalizedShort4) && Equals((NormalizedShort4) obj);
|
||||
}
|
||||
|
||||
public bool Equals(NormalizedShort4 other)
|
||||
{
|
||||
return packedValue.Equals(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 ulong Pack(float x, float y, float z, float w)
|
||||
{
|
||||
const float max = 0x7FFF;
|
||||
const float min = -max;
|
||||
|
||||
ulong word4 = (
|
||||
(ulong) Math.Clamp(
|
||||
(float) System.Math.Round(x * max),
|
||||
min,
|
||||
max
|
||||
) & 0xFFFF
|
||||
);
|
||||
ulong word3 = (
|
||||
(ulong) Math.Clamp(
|
||||
(float) System.Math.Round(y * max),
|
||||
min,
|
||||
max
|
||||
) & 0xFFFF
|
||||
) << 0x10;
|
||||
ulong word2 = (
|
||||
(ulong) Math.Clamp(
|
||||
(float) System.Math.Round(z * max),
|
||||
min,
|
||||
max
|
||||
) & 0xFFFF
|
||||
) << 0x20;
|
||||
ulong word1 = (
|
||||
(ulong) Math.Clamp(
|
||||
(float) System.Math.Round(w * max),
|
||||
min,
|
||||
max
|
||||
) & 0xFFFF
|
||||
) << 0x30;
|
||||
|
||||
return (word4 | word3 | word2 | word1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
182
Nerfed.Runtime/Graphics/PackedVector/Rg32.cs
Normal file
182
Nerfed.Runtime/Graphics/PackedVector/Rg32.cs
Normal file
@ -0,0 +1,182 @@
|
||||
#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 Rg32 : IPackedVector<uint>, IEquatable<Rg32>, IPackedVector
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the packed value.
|
||||
/// </summary>
|
||||
public uint PackedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return packedValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
packedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
|
||||
private uint packedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of Rg32.
|
||||
/// </summary>
|
||||
/// <param name="x">The x component</param>
|
||||
/// <param name="y">The y component</param>
|
||||
public Rg32(float x, float y)
|
||||
{
|
||||
packedValue = Pack(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of Rg32.
|
||||
/// </summary>
|
||||
/// <param name="vector">
|
||||
/// Vector containing the components for the packed vector.
|
||||
/// </param>
|
||||
public Rg32(Vector2 vector)
|
||||
{
|
||||
packedValue = Pack(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the packed vector in Vector2 format.
|
||||
/// </summary>
|
||||
/// <returns>The packed vector in Vector2 format</returns>
|
||||
public Vector2 ToVector2()
|
||||
{
|
||||
return new Vector2(
|
||||
(packedValue & 0xFFFF) / 65535.0f,
|
||||
(packedValue >> 16) / 65535.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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the packed vector in Vector4 format.
|
||||
/// </summary>
|
||||
/// <returns>The packed vector in Vector4 format</returns>
|
||||
Vector4 IPackedVector.ToVector4()
|
||||
{
|
||||
return new Vector4(ToVector2(), 0.0f, 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 Rg32) && Equals((Rg32) obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares another Rg32 packed vector with the packed vector.
|
||||
/// </summary>
|
||||
/// <param name="other">The Rg32 packed vector to compare.</param>
|
||||
/// <returns>True if the packed vectors are equal.</returns>
|
||||
public bool Equals(Rg32 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 ==(Rg32 lhs, Rg32 rhs)
|
||||
{
|
||||
return lhs.packedValue == rhs.packedValue;
|
||||
}
|
||||
|
||||
public static bool operator !=(Rg32 lhs, Rg32 rhs)
|
||||
{
|
||||
return lhs.packedValue != rhs.packedValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Static Pack Method
|
||||
|
||||
private static uint Pack(float x, float y)
|
||||
{
|
||||
return (uint) (
|
||||
((uint) System.Math.Round(Math.Clamp(x, 0, 1) * 65535.0f)) |
|
||||
(((uint) System.Math.Round(Math.Clamp(y, 0, 1) * 65535.0f)) << 16)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
178
Nerfed.Runtime/Graphics/PackedVector/Rgba1010102.cs
Normal file
178
Nerfed.Runtime/Graphics/PackedVector/Rgba1010102.cs
Normal 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 Rgba1010102 : IPackedVector<uint>, IEquatable<Rgba1010102>, IPackedVector
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the packed value.
|
||||
/// </summary>
|
||||
public uint PackedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return packedValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
packedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
|
||||
private uint packedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of Rgba1010102.
|
||||
/// </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 Rgba1010102(float x, float y, float z, float w)
|
||||
{
|
||||
packedValue = Pack(x, y, z, w);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of Rgba1010102.
|
||||
/// </summary>
|
||||
/// <param name="vector">
|
||||
/// Vector containing the components for the packed vector.
|
||||
/// </param>
|
||||
public Rgba1010102(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 & 0x03FF) / 1023.0f,
|
||||
((packedValue >> 10) & 0x03FF) / 1023.0f,
|
||||
((packedValue >> 20) & 0x03FF) / 1023.0f,
|
||||
(packedValue >> 30) / 3.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 Rgba1010102) && Equals((Rgba1010102) obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares another Rgba1010102 packed vector with the packed vector.
|
||||
/// </summary>
|
||||
/// <param name="other">The Rgba1010102 packed vector to compare.</param>
|
||||
/// <returns>True if the packed vectors are equal.</returns>
|
||||
public bool Equals(Rgba1010102 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 ==(Rgba1010102 lhs, Rgba1010102 rhs)
|
||||
{
|
||||
return lhs.packedValue == rhs.packedValue;
|
||||
}
|
||||
|
||||
public static bool operator !=(Rgba1010102 lhs, Rgba1010102 rhs)
|
||||
{
|
||||
return lhs.packedValue != rhs.packedValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Static Pack Method
|
||||
|
||||
private static uint Pack(float x, float y, float z, float w)
|
||||
{
|
||||
return (uint) (
|
||||
((uint) System.Math.Round(Math.Clamp(x, 0, 1) * 1023.0f)) |
|
||||
((uint) System.Math.Round(Math.Clamp(y, 0, 1) * 1023.0f) << 10) |
|
||||
((uint) System.Math.Round(Math.Clamp(z, 0, 1) * 1023.0f) << 20) |
|
||||
((uint) System.Math.Round(Math.Clamp(w, 0, 1) * 3.0f) << 30)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
178
Nerfed.Runtime/Graphics/PackedVector/Rgba64.cs
Normal file
178
Nerfed.Runtime/Graphics/PackedVector/Rgba64.cs
Normal 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 Rgba64 : IPackedVector<ulong>, IEquatable<Rgba64>, IPackedVector
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the packed value.
|
||||
/// </summary>
|
||||
public ulong PackedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return packedValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
packedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
|
||||
private ulong packedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of Rgba64.
|
||||
/// </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 Rgba64(float x, float y, float z, float w)
|
||||
{
|
||||
packedValue = Pack(x, y, z, w);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of Rgba64.
|
||||
/// </summary>
|
||||
/// <param name="vector">
|
||||
/// Vector containing the components for the packed vector.
|
||||
/// </param>
|
||||
public Rgba64(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 & 0xFFFF) / 65535.0f,
|
||||
((packedValue >> 16) & 0xFFFF) / 65535.0f,
|
||||
((packedValue >> 32) & 0xFFFF) / 65535.0f,
|
||||
(packedValue >> 48) / 65535.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 Rgba64) && Equals((Rgba64) obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares another Rgba64 packed vector with the packed vector.
|
||||
/// </summary>
|
||||
/// <param name="other">The Rgba64 packed vector to compare.</param>
|
||||
/// <returns>True if the packed vectors are equal.</returns>
|
||||
public bool Equals(Rgba64 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 ==(Rgba64 lhs, Rgba64 rhs)
|
||||
{
|
||||
return lhs.packedValue == rhs.packedValue;
|
||||
}
|
||||
|
||||
public static bool operator !=(Rgba64 lhs, Rgba64 rhs)
|
||||
{
|
||||
return lhs.packedValue != rhs.packedValue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Static Pack Method
|
||||
|
||||
private static ulong Pack(float x, float y, float z, float w)
|
||||
{
|
||||
return (ulong) (
|
||||
((ulong) System.Math.Round(Math.Clamp(x, 0, 1) * 65535.0f)) |
|
||||
(((ulong) System.Math.Round(Math.Clamp(y, 0, 1) * 65535.0f)) << 16) |
|
||||
(((ulong) System.Math.Round(Math.Clamp(z, 0, 1) * 65535.0f)) << 32) |
|
||||
(((ulong) System.Math.Round(Math.Clamp(w, 0, 1) * 65535.0f)) << 48)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
134
Nerfed.Runtime/Graphics/PackedVector/Short2.cs
Normal file
134
Nerfed.Runtime/Graphics/PackedVector/Short2.cs
Normal file
@ -0,0 +1,134 @@
|
||||
#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 Short2 : IPackedVector<uint>, IEquatable<Short2>
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public uint PackedValue
|
||||
{
|
||||
get
|
||||
{
|
||||
return packedValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
packedValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Variables
|
||||
|
||||
private uint packedValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public Short2(Vector2 vector)
|
||||
{
|
||||
packedValue = Pack(vector.X, vector.Y);
|
||||
}
|
||||
|
||||
public Short2(float x, float y)
|
||||
{
|
||||
packedValue = Pack(x, y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public Vector2 ToVector2()
|
||||
{
|
||||
return new Vector2(
|
||||
(short) (packedValue & 0xFFFF),
|
||||
(short) (packedValue >> 16)
|
||||
);
|
||||
}
|
||||
|
||||
#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 !=(Short2 a, Short2 b)
|
||||
{
|
||||
return a.packedValue != b.packedValue;
|
||||
}
|
||||
|
||||
public static bool operator ==(Short2 a, Short2 b)
|
||||
{
|
||||
return a.packedValue == b.packedValue;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is Short2) && Equals((Short2) obj);
|
||||
}
|
||||
|
||||
public bool Equals(Short2 other)
|
||||
{
|
||||
return this == other;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return (uint) (
|
||||
((int) System.Math.Round(Math.Clamp(x, -32768, 32767)) & 0x0000FFFF) |
|
||||
(((int) System.Math.Round(Math.Clamp(y, -32768, 32767))) << 16)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
204
Nerfed.Runtime/Graphics/PackedVector/Short4.cs
Normal file
204
Nerfed.Runtime/Graphics/PackedVector/Short4.cs
Normal 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 signed integer values.
|
||||
/// </summary>
|
||||
public struct Short4 : IPackedVector<ulong>, IEquatable<Short4>
|
||||
{
|
||||
#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 Short4 class.
|
||||
/// </summary>
|
||||
/// <param name="vector">
|
||||
/// A vector containing the initial values for the components of the Short4 structure.
|
||||
/// </param>
|
||||
public Short4(Vector4 vector)
|
||||
{
|
||||
packedValue = Pack(vector.X, vector.Y, vector.Z, vector.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the Short4 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 Short4(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(
|
||||
(short) (packedValue & 0xFFFF),
|
||||
(short) ((packedValue >> 16) & 0xFFFF),
|
||||
(short) ((packedValue >> 32) & 0xFFFF),
|
||||
(short) (packedValue >> 48)
|
||||
);
|
||||
}
|
||||
|
||||
#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 !=(Short4 a, Short4 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 ==(Short4 a, Short4 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 Short4) && Equals((Short4) 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(Short4 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 ulong.
|
||||
/// </summary>
|
||||
/// <param name="vector">The vector containing the values to pack.</param>
|
||||
/// <returns>The ulong containing the packed values.</returns>
|
||||
static ulong Pack(float x, float y, float z, float w)
|
||||
{
|
||||
return (ulong) (
|
||||
((long) System.Math.Round(Math.Clamp(x, -32768, 32767)) & 0xFFFF) |
|
||||
(((long) System.Math.Round(Math.Clamp(y, -32768, 32767)) << 16) & 0xFFFF0000) |
|
||||
(((long) System.Math.Round(Math.Clamp(z, -32768, 32767)) << 32) & 0xFFFF00000000) |
|
||||
((long) System.Math.Round(Math.Clamp(w, -32768, 32767)) << 48)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
30
Nerfed.Runtime/Graphics/RefreshResource.cs
Normal file
30
Nerfed.Runtime/Graphics/RefreshResource.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
public abstract class RefreshResource : GraphicsResource
|
||||
{
|
||||
public IntPtr Handle { get => handle; internal set => handle = value; }
|
||||
private IntPtr handle;
|
||||
|
||||
protected abstract Action<IntPtr, IntPtr> ReleaseFunction { get; }
|
||||
|
||||
protected RefreshResource(GraphicsDevice device) : base(device)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
// Atomically call release function in case this is called from the finalizer thread
|
||||
IntPtr toDispose = Interlocked.Exchange(ref handle, IntPtr.Zero);
|
||||
if (toDispose != IntPtr.Zero)
|
||||
{
|
||||
ReleaseFunction(Device.Handle, toDispose);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
1723
Nerfed.Runtime/Graphics/RefreshTypes.cs
Normal file
1723
Nerfed.Runtime/Graphics/RefreshTypes.cs
Normal file
File diff suppressed because it is too large
Load Diff
495
Nerfed.Runtime/Graphics/RenderPass.cs
Normal file
495
Nerfed.Runtime/Graphics/RenderPass.cs
Normal file
@ -0,0 +1,495 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Render passes are begun in command buffers and are used to apply render state and issue draw calls.
|
||||
/// Render passes are pooled and should not be referenced after calling EndRenderPass.
|
||||
/// </summary>
|
||||
public class RenderPass
|
||||
{
|
||||
public nint Handle { get; private set; }
|
||||
|
||||
#if DEBUG
|
||||
internal uint colorAttachmentCount;
|
||||
internal SampleCount colorAttachmentSampleCount;
|
||||
internal TextureFormat colorFormatOne;
|
||||
internal TextureFormat colorFormatTwo;
|
||||
internal TextureFormat colorFormatThree;
|
||||
internal TextureFormat colorFormatFour;
|
||||
internal bool hasDepthStencilAttachment;
|
||||
internal SampleCount depthStencilAttachmentSampleCount;
|
||||
internal TextureFormat depthStencilFormat;
|
||||
|
||||
GraphicsPipeline currentGraphicsPipeline;
|
||||
#endif
|
||||
|
||||
internal void SetHandle(nint handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a graphics pipeline so that rendering work may be performed.
|
||||
/// </summary>
|
||||
/// <param name="graphicsPipeline">The graphics pipeline to bind.</param>
|
||||
public void BindGraphicsPipeline(GraphicsPipeline graphicsPipeline)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertRenderPassPipelineFormatMatch(graphicsPipeline);
|
||||
|
||||
if (colorAttachmentCount > 0)
|
||||
{
|
||||
if (graphicsPipeline.SampleCount != colorAttachmentSampleCount)
|
||||
{
|
||||
throw new System.InvalidOperationException(
|
||||
$"Sample count mismatch! Graphics pipeline sample count: {graphicsPipeline.SampleCount}, Color attachment sample count: {colorAttachmentSampleCount}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDepthStencilAttachment)
|
||||
{
|
||||
if (graphicsPipeline.SampleCount != depthStencilAttachmentSampleCount)
|
||||
{
|
||||
throw new System.InvalidOperationException(
|
||||
$"Sample count mismatch! Graphics pipeline sample count: {graphicsPipeline.SampleCount}, Depth stencil attachment sample count: {depthStencilAttachmentSampleCount}"
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_BindGraphicsPipeline(
|
||||
Handle,
|
||||
graphicsPipeline.Handle
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
currentGraphicsPipeline = graphicsPipeline;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the viewport.
|
||||
/// </summary>
|
||||
public void SetViewport(in Viewport viewport)
|
||||
{
|
||||
Refresh.Refresh_SetViewport(
|
||||
Handle,
|
||||
viewport.ToRefresh()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the scissor area.
|
||||
/// </summary>
|
||||
public void SetScissor(in Rect scissor)
|
||||
{
|
||||
#if DEBUG
|
||||
if (scissor.X < 0 || scissor.Y < 0 || scissor.W <= 0 || scissor.H <= 0)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException("Scissor position cannot be negative and dimensions must be positive!");
|
||||
}
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_SetScissor(
|
||||
Handle,
|
||||
scissor.ToRefresh()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds vertex buffers to be used by subsequent draw calls.
|
||||
/// </summary>
|
||||
/// <param name="bufferBinding">Buffer to bind and associated offset.</param>
|
||||
/// <param name="firstBinding">The index of the first vertex input binding whose state is updated by the command.</param>
|
||||
public unsafe void BindVertexBuffer(
|
||||
in BufferBinding bufferBinding,
|
||||
uint firstBinding = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
#endif
|
||||
|
||||
Refresh.BufferBinding refreshBufferBinding = bufferBinding.ToRefresh();
|
||||
|
||||
Refresh.Refresh_BindVertexBuffers(
|
||||
Handle,
|
||||
firstBinding,
|
||||
&refreshBufferBinding,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds an index buffer to be used by subsequent draw calls.
|
||||
/// </summary>
|
||||
/// <param name="indexBuffer">The index buffer to bind.</param>
|
||||
/// <param name="indexElementSize">The size in bytes of the index buffer elements.</param>
|
||||
/// <param name="offset">The offset index for the buffer.</param>
|
||||
public void BindIndexBuffer(
|
||||
BufferBinding bufferBinding,
|
||||
IndexElementSize indexElementSize
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_BindIndexBuffer(
|
||||
Handle,
|
||||
bufferBinding.ToRefresh(),
|
||||
(Refresh.IndexElementSize)indexElementSize
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds samplers to be used by the vertex shader.
|
||||
/// </summary>
|
||||
/// <param name="textureSamplerBindings">The texture-sampler to bind.</param>
|
||||
public unsafe void BindVertexSampler(
|
||||
in TextureSamplerBinding textureSamplerBinding,
|
||||
uint slot = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
AssertTextureSamplerBindingNonNull(textureSamplerBinding);
|
||||
AssertTextureHasSamplerFlag(textureSamplerBinding.Texture);
|
||||
#endif
|
||||
|
||||
Refresh.TextureSamplerBinding refreshTextureSamplerBinding = textureSamplerBinding.ToRefresh();
|
||||
|
||||
Refresh.Refresh_BindVertexSamplers(
|
||||
Handle,
|
||||
slot,
|
||||
&refreshTextureSamplerBinding,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void BindVertexStorageTexture(
|
||||
in TextureSlice textureSlice,
|
||||
uint slot = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
AssertTextureNonNull(textureSlice.Texture);
|
||||
AssertTextureHasGraphicsStorageFlag(textureSlice.Texture);
|
||||
#endif
|
||||
|
||||
Refresh.TextureSlice refreshTextureSlice = textureSlice.ToRefresh();
|
||||
|
||||
Refresh.Refresh_BindVertexStorageTextures(
|
||||
Handle,
|
||||
slot,
|
||||
&refreshTextureSlice,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void BindVertexStorageBuffer(
|
||||
Buffer buffer,
|
||||
uint slot = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
AssertBufferNonNull(buffer);
|
||||
AssertBufferHasGraphicsStorageFlag(buffer);
|
||||
#endif
|
||||
|
||||
IntPtr bufferHandle = buffer.Handle;
|
||||
|
||||
Refresh.Refresh_BindVertexStorageBuffers(
|
||||
Handle,
|
||||
slot,
|
||||
&bufferHandle,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void BindFragmentSampler(
|
||||
in TextureSamplerBinding textureSamplerBinding,
|
||||
uint slot = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
AssertTextureSamplerBindingNonNull(textureSamplerBinding);
|
||||
AssertTextureHasSamplerFlag(textureSamplerBinding.Texture);
|
||||
#endif
|
||||
|
||||
Refresh.TextureSamplerBinding refreshTextureSamplerBinding = textureSamplerBinding.ToRefresh();
|
||||
|
||||
Refresh.Refresh_BindFragmentSamplers(
|
||||
Handle,
|
||||
slot,
|
||||
&refreshTextureSamplerBinding,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void BindFragmentStorageTexture(
|
||||
in TextureSlice textureSlice,
|
||||
uint slot = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
AssertTextureNonNull(textureSlice.Texture);
|
||||
AssertTextureHasGraphicsStorageFlag(textureSlice.Texture);
|
||||
#endif
|
||||
|
||||
Refresh.TextureSlice refreshTextureSlice = textureSlice.ToRefresh();
|
||||
|
||||
Refresh.Refresh_BindFragmentStorageTextures(
|
||||
Handle,
|
||||
slot,
|
||||
&refreshTextureSlice,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
public unsafe void BindFragmentStorageBuffer(
|
||||
Buffer buffer,
|
||||
uint slot = 0
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
AssertBufferNonNull(buffer);
|
||||
AssertBufferHasGraphicsStorageFlag(buffer);
|
||||
#endif
|
||||
|
||||
IntPtr bufferHandle = buffer.Handle;
|
||||
|
||||
Refresh.Refresh_BindFragmentStorageBuffers(
|
||||
Handle,
|
||||
slot,
|
||||
&bufferHandle,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws using a vertex buffer and an index buffer, and an optional instance count.
|
||||
/// </summary>
|
||||
/// <param name="baseVertex">The starting index offset for the vertex buffer.</param>
|
||||
/// <param name="startIndex">The starting index offset for the index buffer.</param>
|
||||
/// <param name="primitiveCount">The number of primitives to draw.</param>
|
||||
/// <param name="instanceCount">The number of instances to draw.</param>
|
||||
public void DrawIndexedPrimitives(
|
||||
uint baseVertex,
|
||||
uint startIndex,
|
||||
uint primitiveCount,
|
||||
uint instanceCount = 1
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_DrawIndexedPrimitives(
|
||||
Handle,
|
||||
baseVertex,
|
||||
startIndex,
|
||||
primitiveCount,
|
||||
instanceCount
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws using a vertex buffer and an index buffer.
|
||||
/// </summary>
|
||||
/// <param name="baseVertex">The starting index offset for the vertex buffer.</param>
|
||||
/// <param name="startIndex">The starting index offset for the index buffer.</param>
|
||||
/// <param name="primitiveCount">The number of primitives to draw.</param>
|
||||
public void DrawPrimitives(
|
||||
uint vertexStart,
|
||||
uint primitiveCount
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_DrawPrimitives(
|
||||
Handle,
|
||||
vertexStart,
|
||||
primitiveCount
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Similar to DrawPrimitives, but parameters are set from a buffer.
|
||||
/// The buffer must have the Indirect usage flag set.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The draw parameters buffer.</param>
|
||||
/// <param name="offsetInBytes">The offset to start reading from the draw parameters buffer.</param>
|
||||
/// <param name="drawCount">The number of draw parameter sets that should be read from the buffer.</param>
|
||||
/// <param name="stride">The byte stride between sets of draw parameters.</param>
|
||||
public void DrawPrimitivesIndirect(
|
||||
Buffer buffer,
|
||||
uint offsetInBytes,
|
||||
uint drawCount,
|
||||
uint stride
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_DrawPrimitivesIndirect(
|
||||
Handle,
|
||||
buffer.Handle,
|
||||
offsetInBytes,
|
||||
drawCount,
|
||||
stride
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Similar to DrawIndexedPrimitives, but parameters are set from a buffer.
|
||||
/// The buffer must have the Indirect usage flag set.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The draw parameters buffer.</param>
|
||||
/// <param name="offsetInBytes">The offset to start reading from the draw parameters buffer.</param>
|
||||
/// <param name="drawCount">The number of draw parameter sets that should be read from the buffer.</param>
|
||||
/// <param name="stride">The byte stride between sets of draw parameters.</param>
|
||||
public void DrawIndexedPrimitivesIndirect(
|
||||
Buffer buffer,
|
||||
uint offsetInBytes,
|
||||
uint drawCount,
|
||||
uint stride
|
||||
)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertGraphicsPipelineBound();
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_DrawIndexedPrimitivesIndirect(
|
||||
Handle,
|
||||
buffer.Handle,
|
||||
offsetInBytes,
|
||||
drawCount,
|
||||
stride
|
||||
);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private void AssertRenderPassPipelineFormatMatch(GraphicsPipeline graphicsPipeline)
|
||||
{
|
||||
for (int i = 0; i < graphicsPipeline.AttachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
|
||||
{
|
||||
TextureFormat format;
|
||||
if (i == 0)
|
||||
{
|
||||
format = colorFormatOne;
|
||||
}
|
||||
else if (i == 1)
|
||||
{
|
||||
format = colorFormatTwo;
|
||||
}
|
||||
else if (i == 2)
|
||||
{
|
||||
format = colorFormatThree;
|
||||
}
|
||||
else
|
||||
{
|
||||
format = colorFormatFour;
|
||||
}
|
||||
|
||||
TextureFormat pipelineFormat = graphicsPipeline.AttachmentInfo.ColorAttachmentDescriptions[i].Format;
|
||||
if (pipelineFormat != format)
|
||||
{
|
||||
throw new System.InvalidOperationException(
|
||||
$"Color texture format mismatch! Pipeline expects {pipelineFormat}, render pass attachment is {format}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (graphicsPipeline.AttachmentInfo.HasDepthStencilAttachment)
|
||||
{
|
||||
TextureFormat pipelineDepthFormat = graphicsPipeline.AttachmentInfo.DepthStencilFormat;
|
||||
|
||||
if (!hasDepthStencilAttachment)
|
||||
{
|
||||
throw new System.InvalidOperationException("Pipeline expects depth attachment!");
|
||||
}
|
||||
|
||||
if (pipelineDepthFormat != depthStencilFormat)
|
||||
{
|
||||
throw new System.InvalidOperationException(
|
||||
$"Depth texture format mismatch! Pipeline expects {pipelineDepthFormat}, render pass attachment is {depthStencilFormat}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertGraphicsPipelineBound(string message = "No graphics pipeline is bound!")
|
||||
{
|
||||
if (currentGraphicsPipeline == null)
|
||||
{
|
||||
throw new System.InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertTextureNonNull(in TextureSlice textureSlice)
|
||||
{
|
||||
if (textureSlice.Texture == null)
|
||||
{
|
||||
throw new NullReferenceException("Texture must not be null!");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertTextureSamplerBindingNonNull(in TextureSamplerBinding binding)
|
||||
{
|
||||
if (binding.Texture == null || binding.Texture.Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new NullReferenceException("Texture binding must not be null!");
|
||||
}
|
||||
|
||||
if (binding.Sampler == null || binding.Sampler.Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new NullReferenceException("Sampler binding must not be null!");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertTextureHasSamplerFlag(Texture texture)
|
||||
{
|
||||
if ((texture.UsageFlags & TextureUsageFlags.Sampler) == 0)
|
||||
{
|
||||
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.Sampler!");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertTextureHasGraphicsStorageFlag(Texture texture)
|
||||
{
|
||||
if ((texture.UsageFlags & TextureUsageFlags.GraphicsStorage) == 0)
|
||||
{
|
||||
throw new System.ArgumentException("The bound Texture's UsageFlags must include TextureUsageFlags.GraphicsStorage!");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertBufferNonNull(Buffer buffer)
|
||||
{
|
||||
if (buffer == null || buffer.Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new System.NullReferenceException("Buffer must not be null!");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertBufferHasGraphicsStorageFlag(Buffer buffer)
|
||||
{
|
||||
if ((buffer.UsageFlags & BufferUsageFlags.GraphicsStorage) == 0)
|
||||
{
|
||||
throw new System.ArgumentException("The bound Buffer's UsageFlags must include BufferUsageFlag.GraphicsStorage!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
25
Nerfed.Runtime/Graphics/RenderPassPool.cs
Normal file
25
Nerfed.Runtime/Graphics/RenderPassPool.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
internal class RenderPassPool
|
||||
{
|
||||
private ConcurrentQueue<RenderPass> RenderPasses = new ConcurrentQueue<RenderPass>();
|
||||
|
||||
public RenderPass Obtain()
|
||||
{
|
||||
if (RenderPasses.TryDequeue(out RenderPass renderPass))
|
||||
{
|
||||
return renderPass;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new RenderPass();
|
||||
}
|
||||
}
|
||||
|
||||
public void Return(RenderPass renderPass)
|
||||
{
|
||||
RenderPasses.Enqueue(renderPass);
|
||||
}
|
||||
}
|
376
Nerfed.Runtime/Graphics/ResourceUploader.cs
Normal file
376
Nerfed.Runtime/Graphics/ResourceUploader.cs
Normal file
@ -0,0 +1,376 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
// FIXME: can we map the transfer buffer instead of maintaining a local pointer
|
||||
|
||||
/// <summary>
|
||||
/// A convenience structure for creating resources and uploading data to them.
|
||||
///
|
||||
/// Note that Upload or UploadAndWait must be called after the Create methods for the data to actually be uploaded.
|
||||
///
|
||||
/// Note that this structure does not magically keep memory usage down -
|
||||
/// you may want to stagger uploads over multiple submissions to minimize memory usage.
|
||||
/// </summary>
|
||||
public unsafe class ResourceUploader : GraphicsResource
|
||||
{
|
||||
TransferBuffer BufferTransferBuffer;
|
||||
TransferBuffer TextureTransferBuffer;
|
||||
|
||||
byte* bufferData;
|
||||
uint bufferDataOffset = 0;
|
||||
uint bufferDataSize = 1024;
|
||||
|
||||
byte* textureData;
|
||||
uint textureDataOffset = 0;
|
||||
uint textureDataSize = 1024;
|
||||
|
||||
List<(uint, BufferRegion, bool)> BufferUploads = new List<(uint, BufferRegion, bool)>();
|
||||
List<(uint, TextureRegion, bool)> TextureUploads = new List<(uint, TextureRegion, bool)>();
|
||||
|
||||
public ResourceUploader(GraphicsDevice device) : base(device)
|
||||
{
|
||||
bufferData = (byte*) NativeMemory.Alloc(bufferDataSize);
|
||||
textureData = (byte*) NativeMemory.Alloc(textureDataSize);
|
||||
}
|
||||
|
||||
// Buffers
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Buffer with data to be uploaded.
|
||||
/// </summary>
|
||||
public Buffer CreateBuffer<T>(Span<T> data, BufferUsageFlags usageFlags) where T : unmanaged
|
||||
{
|
||||
uint lengthInBytes = (uint) (Marshal.SizeOf<T>() * data.Length);
|
||||
Buffer buffer = new Buffer(Device, usageFlags, lengthInBytes);
|
||||
|
||||
SetBufferData(buffer, 0, data, false);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares upload of data into a Buffer.
|
||||
/// </summary>
|
||||
public void SetBufferData<T>(Buffer buffer, uint bufferOffsetInElements, Span<T> data, bool cycle) where T : unmanaged
|
||||
{
|
||||
uint elementSize = (uint) Marshal.SizeOf<T>();
|
||||
uint offsetInBytes = elementSize * bufferOffsetInElements;
|
||||
uint lengthInBytes = (uint) (elementSize * data.Length);
|
||||
|
||||
uint resourceOffset;
|
||||
fixed (void* spanPtr = data)
|
||||
{
|
||||
resourceOffset = CopyBufferData(spanPtr, lengthInBytes);
|
||||
}
|
||||
|
||||
BufferRegion bufferRegion = new BufferRegion(buffer, offsetInBytes, lengthInBytes);
|
||||
BufferUploads.Add((resourceOffset, bufferRegion, cycle));
|
||||
}
|
||||
|
||||
// Textures
|
||||
|
||||
public Texture CreateTexture2D<T>(Span<T> pixelData, uint width, uint height) where T : unmanaged
|
||||
{
|
||||
Texture texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
|
||||
SetTextureData(texture, pixelData, false);
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D Texture from compressed image data to be uploaded.
|
||||
/// </summary>
|
||||
public Texture CreateTexture2DFromCompressed(Span<byte> compressedImageData)
|
||||
{
|
||||
ImageUtils.ImageInfoFromBytes(compressedImageData, out uint width, out uint height, out uint _);
|
||||
Texture texture = Texture.CreateTexture2D(Device, width, height, TextureFormat.R8G8B8A8, TextureUsageFlags.Sampler);
|
||||
SetTextureDataFromCompressed(texture, compressedImageData);
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D Texture from a compressed image stream to be uploaded.
|
||||
/// </summary>
|
||||
public Texture CreateTexture2DFromCompressed(Stream compressedImageStream)
|
||||
{
|
||||
long length = compressedImageStream.Length;
|
||||
void* buffer = NativeMemory.Alloc((nuint) length);
|
||||
Span<byte> span = new Span<byte>(buffer, (int) length);
|
||||
compressedImageStream.ReadExactly(span);
|
||||
|
||||
Texture texture = CreateTexture2DFromCompressed(span);
|
||||
|
||||
NativeMemory.Free(buffer);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D Texture from a compressed image file to be uploaded.
|
||||
/// </summary>
|
||||
public Texture CreateTexture2DFromCompressed(string compressedImageFilePath)
|
||||
{
|
||||
FileStream fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
|
||||
return CreateTexture2DFromCompressed(fileStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a texture from a DDS stream.
|
||||
/// </summary>
|
||||
public Texture CreateTextureFromDDS(Stream stream)
|
||||
{
|
||||
using BinaryReader reader = new BinaryReader(stream);
|
||||
Texture texture;
|
||||
int faces;
|
||||
ImageUtils.ParseDDS(reader, out TextureFormat format, out int width, out int height, out int levels, out bool isCube);
|
||||
|
||||
if (isCube)
|
||||
{
|
||||
texture = Texture.CreateTextureCube(Device, (uint) width, format, TextureUsageFlags.Sampler, (uint) levels);
|
||||
faces = 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = Texture.CreateTexture2D(Device, (uint) width, (uint) height, format, TextureUsageFlags.Sampler, (uint) levels);
|
||||
faces = 1;
|
||||
}
|
||||
|
||||
for (int face = 0; face < faces; face += 1)
|
||||
{
|
||||
for (int level = 0; level < levels; level += 1)
|
||||
{
|
||||
int levelWidth = width >> level;
|
||||
int levelHeight = height >> level;
|
||||
|
||||
int levelSize = ImageUtils.CalculateDDSLevelSize(levelWidth, levelHeight, format);
|
||||
void* byteBuffer = NativeMemory.Alloc((nuint) levelSize);
|
||||
Span<byte> byteSpan = new Span<byte>(byteBuffer, levelSize);
|
||||
stream.ReadExactly(byteSpan);
|
||||
|
||||
TextureRegion textureRegion = new TextureRegion
|
||||
{
|
||||
TextureSlice = new TextureSlice
|
||||
{
|
||||
Texture = texture,
|
||||
Layer = (uint) face,
|
||||
MipLevel = (uint) level
|
||||
},
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Z = 0,
|
||||
Width = (uint) levelWidth,
|
||||
Height = (uint) levelHeight,
|
||||
Depth = 1
|
||||
};
|
||||
|
||||
SetTextureData(textureRegion, byteSpan, false);
|
||||
|
||||
NativeMemory.Free(byteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a texture from a DDS file.
|
||||
/// </summary>
|
||||
public Texture CreateTextureFromDDS(string path)
|
||||
{
|
||||
FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
return CreateTextureFromDDS(stream);
|
||||
}
|
||||
|
||||
public void SetTextureDataFromCompressed(TextureRegion textureRegion, Span<byte> compressedImageData)
|
||||
{
|
||||
byte* pixelData = ImageUtils.GetPixelDataFromBytes(compressedImageData, out uint _, out uint _, out uint sizeInBytes);
|
||||
Span<byte> pixelSpan = new Span<byte>((void*) pixelData, (int) sizeInBytes);
|
||||
|
||||
SetTextureData(textureRegion, pixelSpan, false);
|
||||
|
||||
ImageUtils.FreePixelData(pixelData);
|
||||
}
|
||||
|
||||
public void SetTextureDataFromCompressed(TextureRegion textureRegion, Stream compressedImageStream)
|
||||
{
|
||||
long length = compressedImageStream.Length;
|
||||
void* buffer = NativeMemory.Alloc((nuint) length);
|
||||
Span<byte> span = new Span<byte>(buffer, (int) length);
|
||||
compressedImageStream.ReadExactly(span);
|
||||
SetTextureDataFromCompressed(textureRegion, span);
|
||||
NativeMemory.Free(buffer);
|
||||
}
|
||||
|
||||
public void SetTextureDataFromCompressed(TextureRegion textureRegion, string compressedImageFilePath)
|
||||
{
|
||||
FileStream fileStream = new FileStream(compressedImageFilePath, FileMode.Open, FileAccess.Read);
|
||||
SetTextureDataFromCompressed(textureRegion, fileStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares upload of pixel data into a TextureSlice.
|
||||
/// </summary>
|
||||
public void SetTextureData<T>(TextureRegion textureRegion, Span<T> data, bool cycle) where T : unmanaged
|
||||
{
|
||||
int elementSize = Marshal.SizeOf<T>();
|
||||
uint dataLengthInBytes = (uint) (elementSize * data.Length);
|
||||
|
||||
uint resourceOffset;
|
||||
fixed (T* dataPtr = data)
|
||||
{
|
||||
resourceOffset = CopyTextureData(dataPtr, dataLengthInBytes, Texture.BytesPerPixel(textureRegion.TextureSlice.Texture.Format));
|
||||
}
|
||||
|
||||
TextureUploads.Add((resourceOffset, textureRegion, cycle));
|
||||
}
|
||||
|
||||
// Upload
|
||||
|
||||
/// <summary>
|
||||
/// Uploads all the data corresponding to the created resources.
|
||||
/// </summary>
|
||||
public void Upload()
|
||||
{
|
||||
CopyToTransferBuffer();
|
||||
|
||||
CommandBuffer commandBuffer = Device.AcquireCommandBuffer();
|
||||
RecordUploadCommands(commandBuffer);
|
||||
Device.Submit(commandBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads and then blocks until the upload is finished.
|
||||
/// This is useful for keeping memory usage down during threaded upload.
|
||||
/// </summary>
|
||||
public void UploadAndWait()
|
||||
{
|
||||
CopyToTransferBuffer();
|
||||
|
||||
CommandBuffer commandBuffer = Device.AcquireCommandBuffer();
|
||||
RecordUploadCommands(commandBuffer);
|
||||
Fence fence = Device.SubmitAndAcquireFence(commandBuffer);
|
||||
Device.WaitForFence(fence);
|
||||
Device.ReleaseFence(fence);
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
private void CopyToTransferBuffer()
|
||||
{
|
||||
if (BufferUploads.Count > 0)
|
||||
{
|
||||
if (BufferTransferBuffer == null || BufferTransferBuffer.Size < bufferDataSize)
|
||||
{
|
||||
BufferTransferBuffer?.Dispose();
|
||||
BufferTransferBuffer = new TransferBuffer(Device, TransferBufferUsage.Upload, bufferDataSize);
|
||||
}
|
||||
|
||||
Span<byte> dataSpan = new Span<byte>(bufferData, (int) bufferDataSize);
|
||||
BufferTransferBuffer.SetData(dataSpan, true);
|
||||
}
|
||||
|
||||
|
||||
if (TextureUploads.Count > 0)
|
||||
{
|
||||
if (TextureTransferBuffer == null || TextureTransferBuffer.Size < textureDataSize)
|
||||
{
|
||||
TextureTransferBuffer?.Dispose();
|
||||
TextureTransferBuffer = new TransferBuffer(Device, TransferBufferUsage.Upload, textureDataSize);
|
||||
}
|
||||
|
||||
Span<byte> dataSpan = new Span<byte>(textureData, (int) textureDataSize);
|
||||
TextureTransferBuffer.SetData(dataSpan, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecordUploadCommands(CommandBuffer commandBuffer)
|
||||
{
|
||||
CopyPass copyPass = commandBuffer.BeginCopyPass();
|
||||
|
||||
foreach ((uint transferOffset, BufferRegion bufferRegion, bool option) in BufferUploads)
|
||||
{
|
||||
copyPass.UploadToBuffer(
|
||||
new TransferBufferLocation(BufferTransferBuffer, transferOffset),
|
||||
bufferRegion,
|
||||
option
|
||||
);
|
||||
}
|
||||
|
||||
foreach ((uint transferOffset, TextureRegion textureRegion, bool option) in TextureUploads)
|
||||
{
|
||||
copyPass.UploadToTexture(
|
||||
new TextureTransferInfo(TextureTransferBuffer, transferOffset),
|
||||
textureRegion,
|
||||
option
|
||||
);
|
||||
}
|
||||
|
||||
commandBuffer.EndCopyPass(copyPass);
|
||||
|
||||
BufferUploads.Clear();
|
||||
TextureUploads.Clear();
|
||||
bufferDataOffset = 0;
|
||||
}
|
||||
|
||||
private uint CopyBufferData(void* ptr, uint lengthInBytes)
|
||||
{
|
||||
if (bufferDataOffset + lengthInBytes >= bufferDataSize)
|
||||
{
|
||||
bufferDataSize = bufferDataOffset + lengthInBytes;
|
||||
bufferData = (byte*) NativeMemory.Realloc(bufferData, bufferDataSize);
|
||||
}
|
||||
|
||||
uint resourceOffset = bufferDataOffset;
|
||||
|
||||
NativeMemory.Copy(ptr, bufferData + bufferDataOffset, lengthInBytes);
|
||||
bufferDataOffset += lengthInBytes;
|
||||
|
||||
return resourceOffset;
|
||||
}
|
||||
|
||||
private uint CopyTextureData(void* ptr, uint lengthInBytes, uint alignment)
|
||||
{
|
||||
textureDataOffset = RoundToAlignment(textureDataOffset, alignment);
|
||||
|
||||
if (textureDataOffset + lengthInBytes >= textureDataSize)
|
||||
{
|
||||
textureDataSize = textureDataOffset + lengthInBytes;
|
||||
textureData = (byte*) NativeMemory.Realloc(textureData, textureDataSize);
|
||||
}
|
||||
|
||||
uint resourceOffset = textureDataOffset;
|
||||
|
||||
NativeMemory.Copy(ptr, textureData + textureDataOffset, lengthInBytes);
|
||||
textureDataOffset += lengthInBytes;
|
||||
|
||||
return resourceOffset;
|
||||
}
|
||||
|
||||
private uint RoundToAlignment(uint value, uint alignment)
|
||||
{
|
||||
return alignment * ((value + alignment - 1) / alignment);
|
||||
}
|
||||
|
||||
// Dispose
|
||||
|
||||
/// <summary>
|
||||
/// It is valid to immediately call Dispose after calling Upload.
|
||||
/// </summary>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BufferTransferBuffer?.Dispose();
|
||||
TextureTransferBuffer?.Dispose();
|
||||
}
|
||||
|
||||
NativeMemory.Free(bufferData);
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
88
Nerfed.Runtime/Graphics/Resources/Buffer.cs
Normal file
88
Nerfed.Runtime/Graphics/Resources/Buffer.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A data container that can be efficiently used by the GPU.
|
||||
/// </summary>
|
||||
public class Buffer : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseBuffer;
|
||||
|
||||
public BufferUsageFlags UsageFlags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size in bytes.
|
||||
/// </summary>
|
||||
public uint Size { get; }
|
||||
|
||||
private string name;
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
|
||||
set
|
||||
{
|
||||
if (Device.DebugMode)
|
||||
{
|
||||
Refresh.Refresh_SetBufferName(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer of appropriate size given a type and element count.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
|
||||
/// <param name="device">The GraphicsDevice.</param>
|
||||
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
|
||||
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
|
||||
/// <returns></returns>
|
||||
public unsafe static Buffer Create<T>(
|
||||
GraphicsDevice device,
|
||||
BufferUsageFlags usageFlags,
|
||||
uint elementCount
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new Buffer(
|
||||
device,
|
||||
usageFlags,
|
||||
(uint) Marshal.SizeOf<T>() * elementCount
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="usageFlags">Specifies how the buffer will be used.</param>
|
||||
/// <param name="sizeInBytes">The length of the array. Cannot be resized.</param>
|
||||
public Buffer(
|
||||
GraphicsDevice device,
|
||||
BufferUsageFlags usageFlags,
|
||||
uint sizeInBytes
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateBuffer(
|
||||
device.Handle,
|
||||
(Refresh.BufferUsageFlags) usageFlags,
|
||||
sizeInBytes
|
||||
);
|
||||
UsageFlags = usageFlags;
|
||||
Size = sizeInBytes;
|
||||
name = "";
|
||||
}
|
||||
|
||||
public static implicit operator BufferBinding(Buffer b)
|
||||
{
|
||||
return new BufferBinding(b, 0);
|
||||
}
|
||||
}
|
91
Nerfed.Runtime/Graphics/Resources/ComputePipeline.cs
Normal file
91
Nerfed.Runtime/Graphics/Resources/ComputePipeline.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using RefreshCS;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Compute pipelines perform arbitrary parallel processing on input data.
|
||||
/// </summary>
|
||||
public class ComputePipeline : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseComputePipeline;
|
||||
|
||||
public uint ReadOnlyStorageTextureCount { get; }
|
||||
public uint ReadOnlyStorageBufferCount { get; }
|
||||
public uint ReadWriteStorageTextureCount { get; }
|
||||
public uint ReadWriteStorageBufferCount { get; }
|
||||
public uint UniformBufferCount { get; }
|
||||
|
||||
public ComputePipeline(
|
||||
GraphicsDevice device,
|
||||
string filePath,
|
||||
string entryPointName,
|
||||
in ComputePipelineCreateInfo computePipelineCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
using FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
Handle = CreateFromStream(device, stream, entryPointName, computePipelineCreateInfo);
|
||||
|
||||
ReadOnlyStorageTextureCount = computePipelineCreateInfo.ReadOnlyStorageTextureCount;
|
||||
ReadOnlyStorageBufferCount = computePipelineCreateInfo.ReadOnlyStorageBufferCount;
|
||||
ReadWriteStorageTextureCount = computePipelineCreateInfo.ReadWriteStorageTextureCount;
|
||||
ReadWriteStorageBufferCount = computePipelineCreateInfo.ReadWriteStorageBufferCount;
|
||||
UniformBufferCount = computePipelineCreateInfo.UniformBufferCount;
|
||||
}
|
||||
|
||||
public ComputePipeline(
|
||||
GraphicsDevice device,
|
||||
Stream stream,
|
||||
string entryPointName,
|
||||
in ComputePipelineCreateInfo computePipelineCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = CreateFromStream(device, stream, entryPointName, computePipelineCreateInfo);
|
||||
|
||||
ReadOnlyStorageTextureCount = computePipelineCreateInfo.ReadOnlyStorageTextureCount;
|
||||
ReadOnlyStorageBufferCount = computePipelineCreateInfo.ReadOnlyStorageBufferCount;
|
||||
ReadWriteStorageTextureCount = computePipelineCreateInfo.ReadWriteStorageTextureCount;
|
||||
ReadWriteStorageBufferCount = computePipelineCreateInfo.ReadWriteStorageBufferCount;
|
||||
UniformBufferCount = computePipelineCreateInfo.UniformBufferCount;
|
||||
}
|
||||
|
||||
private static unsafe nint CreateFromStream(
|
||||
GraphicsDevice device,
|
||||
Stream stream,
|
||||
string entryPointName,
|
||||
in ComputePipelineCreateInfo computePipelineCreateInfo
|
||||
) {
|
||||
byte* bytecodeBuffer = (byte*) NativeMemory.Alloc((nuint) stream.Length);
|
||||
Span<byte> bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
|
||||
stream.ReadExactly(bytecodeSpan);
|
||||
|
||||
Refresh.ComputePipelineCreateInfo refreshPipelineCreateInfo;
|
||||
refreshPipelineCreateInfo.Code = bytecodeBuffer;
|
||||
refreshPipelineCreateInfo.CodeSize = (nuint) stream.Length;
|
||||
refreshPipelineCreateInfo.EntryPointName = entryPointName;
|
||||
refreshPipelineCreateInfo.Format = (Refresh.ShaderFormat) computePipelineCreateInfo.ShaderFormat;
|
||||
refreshPipelineCreateInfo.ReadOnlyStorageTextureCount = computePipelineCreateInfo.ReadOnlyStorageTextureCount;
|
||||
refreshPipelineCreateInfo.ReadOnlyStorageBufferCount = computePipelineCreateInfo.ReadOnlyStorageBufferCount;
|
||||
refreshPipelineCreateInfo.ReadWriteStorageTextureCount = computePipelineCreateInfo.ReadWriteStorageTextureCount;
|
||||
refreshPipelineCreateInfo.ReadWriteStorageBufferCount = computePipelineCreateInfo.ReadWriteStorageBufferCount;
|
||||
refreshPipelineCreateInfo.UniformBufferCount = computePipelineCreateInfo.UniformBufferCount;
|
||||
refreshPipelineCreateInfo.ThreadCountX = computePipelineCreateInfo.ThreadCountX;
|
||||
refreshPipelineCreateInfo.ThreadCountY = computePipelineCreateInfo.ThreadCountY;
|
||||
refreshPipelineCreateInfo.ThreadCountZ = computePipelineCreateInfo.ThreadCountZ;
|
||||
|
||||
IntPtr computePipelineHandle = Refresh.Refresh_CreateComputePipeline(
|
||||
device.Handle,
|
||||
refreshPipelineCreateInfo
|
||||
);
|
||||
|
||||
if (computePipelineHandle == nint.Zero)
|
||||
{
|
||||
throw new Exception("Could not create compute pipeline!");
|
||||
}
|
||||
|
||||
NativeMemory.Free(bytecodeBuffer);
|
||||
return computePipelineHandle;
|
||||
}
|
||||
}
|
25
Nerfed.Runtime/Graphics/Resources/Fence.cs
Normal file
25
Nerfed.Runtime/Graphics/Resources/Fence.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Fences allow you to track the status of a submitted command buffer. <br/>
|
||||
/// You should only acquire a Fence if you will need to track the command buffer. <br/>
|
||||
/// You should make sure to call GraphicsDevice.ReleaseFence when done with a Fence to avoid memory growth. <br/>
|
||||
/// The Fence object itself is basically just a wrapper for the Refresh_Fence. <br/>
|
||||
/// The internal handle is replaced so that we can pool Fence objects to manage garbage.
|
||||
/// </summary>
|
||||
public class Fence : RefreshResource
|
||||
{
|
||||
protected override Action<nint, nint> ReleaseFunction => Refresh.Refresh_ReleaseFence;
|
||||
|
||||
internal Fence(GraphicsDevice device) : base(device)
|
||||
{
|
||||
}
|
||||
|
||||
internal void SetHandle(nint handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
}
|
99
Nerfed.Runtime/Graphics/Resources/GraphicsPipeline.cs
Normal file
99
Nerfed.Runtime/Graphics/Resources/GraphicsPipeline.cs
Normal file
@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Graphics pipelines encapsulate all of the render state in a single object. <br/>
|
||||
/// These pipelines are bound before draw calls are issued.
|
||||
/// </summary>
|
||||
public class GraphicsPipeline : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseGraphicsPipeline;
|
||||
|
||||
public Shader VertexShader;
|
||||
public Shader FragmentShader;
|
||||
public SampleCount SampleCount { get; }
|
||||
|
||||
#if DEBUG
|
||||
internal GraphicsPipelineAttachmentInfo AttachmentInfo { get; }
|
||||
#endif
|
||||
|
||||
public unsafe GraphicsPipeline(
|
||||
GraphicsDevice device,
|
||||
in GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Refresh.GraphicsPipelineCreateInfo refreshGraphicsPipelineCreateInfo;
|
||||
|
||||
Refresh.VertexAttribute* vertexAttributes = (Refresh.VertexAttribute*) NativeMemory.Alloc(
|
||||
(nuint) (graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length * Marshal.SizeOf<Refresh.VertexAttribute>())
|
||||
);
|
||||
|
||||
for (int i = 0; i < graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length; i += 1)
|
||||
{
|
||||
vertexAttributes[i] = graphicsPipelineCreateInfo.VertexInputState.VertexAttributes[i].ToRefresh();
|
||||
}
|
||||
|
||||
Refresh.VertexBinding* vertexBindings = (Refresh.VertexBinding*) NativeMemory.Alloc(
|
||||
(nuint) (graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length * Marshal.SizeOf<Refresh.VertexBinding>())
|
||||
);
|
||||
|
||||
for (int i = 0; i < graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length; i += 1)
|
||||
{
|
||||
vertexBindings[i] = graphicsPipelineCreateInfo.VertexInputState.VertexBindings[i].ToRefresh();
|
||||
}
|
||||
|
||||
Refresh.ColorAttachmentDescription* colorAttachmentDescriptions = stackalloc Refresh.ColorAttachmentDescription[
|
||||
graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length
|
||||
];
|
||||
|
||||
for (int i = 0; i < graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length; i += 1)
|
||||
{
|
||||
colorAttachmentDescriptions[i].Format = (Refresh.TextureFormat) graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[i].Format;
|
||||
colorAttachmentDescriptions[i].BlendState = graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions[i].BlendState.ToRefresh();
|
||||
}
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.VertexShader = graphicsPipelineCreateInfo.VertexShader.Handle;
|
||||
refreshGraphicsPipelineCreateInfo.FragmentShader = graphicsPipelineCreateInfo.FragmentShader.Handle;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexAttributes = vertexAttributes;
|
||||
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexAttributeCount = (uint) graphicsPipelineCreateInfo.VertexInputState.VertexAttributes.Length;
|
||||
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexBindings = vertexBindings;
|
||||
refreshGraphicsPipelineCreateInfo.VertexInputState.VertexBindingCount = (uint) graphicsPipelineCreateInfo.VertexInputState.VertexBindings.Length;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.PrimitiveType = (Refresh.PrimitiveType) graphicsPipelineCreateInfo.PrimitiveType;
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.RasterizerState = graphicsPipelineCreateInfo.RasterizerState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.MultisampleState = graphicsPipelineCreateInfo.MultisampleState.ToRefresh();
|
||||
refreshGraphicsPipelineCreateInfo.DepthStencilState = graphicsPipelineCreateInfo.DepthStencilState.ToRefresh();
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentCount = (uint) graphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions.Length;
|
||||
refreshGraphicsPipelineCreateInfo.AttachmentInfo.ColorAttachmentDescriptions = colorAttachmentDescriptions;
|
||||
refreshGraphicsPipelineCreateInfo.AttachmentInfo.DepthStencilFormat = (Refresh.TextureFormat) graphicsPipelineCreateInfo.AttachmentInfo.DepthStencilFormat;
|
||||
refreshGraphicsPipelineCreateInfo.AttachmentInfo.HasDepthStencilAttachment = Conversions.BoolToInt(graphicsPipelineCreateInfo.AttachmentInfo.HasDepthStencilAttachment);
|
||||
|
||||
refreshGraphicsPipelineCreateInfo.BlendConstants[0] = graphicsPipelineCreateInfo.BlendConstants.R;
|
||||
refreshGraphicsPipelineCreateInfo.BlendConstants[1] = graphicsPipelineCreateInfo.BlendConstants.G;
|
||||
refreshGraphicsPipelineCreateInfo.BlendConstants[2] = graphicsPipelineCreateInfo.BlendConstants.B;
|
||||
refreshGraphicsPipelineCreateInfo.BlendConstants[3] = graphicsPipelineCreateInfo.BlendConstants.A;
|
||||
|
||||
Handle = Refresh.Refresh_CreateGraphicsPipeline(device.Handle, refreshGraphicsPipelineCreateInfo);
|
||||
if (Handle == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception("Could not create graphics pipeline!");
|
||||
}
|
||||
|
||||
NativeMemory.Free(vertexAttributes);
|
||||
NativeMemory.Free(vertexBindings);
|
||||
|
||||
VertexShader = graphicsPipelineCreateInfo.VertexShader;
|
||||
FragmentShader = graphicsPipelineCreateInfo.FragmentShader;
|
||||
SampleCount = graphicsPipelineCreateInfo.MultisampleState.MultisampleCount;
|
||||
|
||||
#if DEBUG
|
||||
AttachmentInfo = graphicsPipelineCreateInfo.AttachmentInfo;
|
||||
#endif
|
||||
}
|
||||
}
|
23
Nerfed.Runtime/Graphics/Resources/Sampler.cs
Normal file
23
Nerfed.Runtime/Graphics/Resources/Sampler.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how a texture will be sampled in a shader.
|
||||
/// </summary>
|
||||
public class Sampler : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseSampler;
|
||||
|
||||
public Sampler(
|
||||
GraphicsDevice device,
|
||||
in SamplerCreateInfo samplerCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateSampler(
|
||||
device.Handle,
|
||||
samplerCreateInfo.ToRefresh()
|
||||
);
|
||||
}
|
||||
}
|
91
Nerfed.Runtime/Graphics/Resources/Shader.cs
Normal file
91
Nerfed.Runtime/Graphics/Resources/Shader.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using RefreshCS;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Shaders are used to create graphics pipelines.
|
||||
/// Graphics pipelines take a vertex shader and a fragment shader.
|
||||
/// </summary>
|
||||
public class Shader : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseShader;
|
||||
|
||||
public uint SamplerCount { get; }
|
||||
public uint StorageTextureCount { get; }
|
||||
public uint StorageBufferCount { get; }
|
||||
public uint UniformBufferCount { get; }
|
||||
|
||||
public unsafe Shader(
|
||||
GraphicsDevice device,
|
||||
string filePath,
|
||||
string entryPointName,
|
||||
in ShaderCreateInfo shaderCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
using FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||
Handle = CreateFromStream(
|
||||
device,
|
||||
stream,
|
||||
entryPointName,
|
||||
shaderCreateInfo
|
||||
);
|
||||
|
||||
SamplerCount = shaderCreateInfo.SamplerCount;
|
||||
StorageTextureCount = shaderCreateInfo.StorageTextureCount;
|
||||
StorageBufferCount = shaderCreateInfo.StorageBufferCount;
|
||||
UniformBufferCount = shaderCreateInfo.UniformBufferCount;
|
||||
}
|
||||
|
||||
public unsafe Shader(
|
||||
GraphicsDevice device,
|
||||
Stream stream,
|
||||
string entryPointName,
|
||||
in ShaderCreateInfo shaderCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = CreateFromStream(
|
||||
device,
|
||||
stream,
|
||||
entryPointName,
|
||||
shaderCreateInfo
|
||||
);
|
||||
|
||||
SamplerCount = shaderCreateInfo.SamplerCount;
|
||||
StorageTextureCount = shaderCreateInfo.StorageTextureCount;
|
||||
StorageBufferCount = shaderCreateInfo.StorageBufferCount;
|
||||
UniformBufferCount = shaderCreateInfo.UniformBufferCount;
|
||||
}
|
||||
|
||||
private static unsafe IntPtr CreateFromStream(
|
||||
GraphicsDevice device,
|
||||
Stream stream,
|
||||
string entryPointName,
|
||||
in ShaderCreateInfo shaderCreateInfo
|
||||
) {
|
||||
void* bytecodeBuffer = NativeMemory.Alloc((nuint) stream.Length);
|
||||
Span<byte> bytecodeSpan = new Span<byte>(bytecodeBuffer, (int) stream.Length);
|
||||
stream.ReadExactly(bytecodeSpan);
|
||||
|
||||
Refresh.ShaderCreateInfo refreshShaderCreateInfo;
|
||||
refreshShaderCreateInfo.CodeSize = (nuint) stream.Length;
|
||||
refreshShaderCreateInfo.Code = (byte*) bytecodeBuffer;
|
||||
refreshShaderCreateInfo.EntryPointName = entryPointName;
|
||||
refreshShaderCreateInfo.Stage = (Refresh.ShaderStage) shaderCreateInfo.ShaderStage;
|
||||
refreshShaderCreateInfo.Format = (Refresh.ShaderFormat) shaderCreateInfo.ShaderFormat;
|
||||
refreshShaderCreateInfo.SamplerCount = shaderCreateInfo.SamplerCount;
|
||||
refreshShaderCreateInfo.StorageTextureCount = shaderCreateInfo.StorageTextureCount;
|
||||
refreshShaderCreateInfo.StorageBufferCount = shaderCreateInfo.StorageBufferCount;
|
||||
refreshShaderCreateInfo.UniformBufferCount = shaderCreateInfo.UniformBufferCount;
|
||||
|
||||
IntPtr shaderModule = Refresh.Refresh_CreateShader(
|
||||
device.Handle,
|
||||
refreshShaderCreateInfo
|
||||
);
|
||||
|
||||
NativeMemory.Free(bytecodeBuffer);
|
||||
return shaderModule;
|
||||
}
|
||||
}
|
326
Nerfed.Runtime/Graphics/Resources/Texture.cs
Normal file
326
Nerfed.Runtime/Graphics/Resources/Texture.cs
Normal file
@ -0,0 +1,326 @@
|
||||
using System;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A multi-dimensional data container that can be efficiently used by the GPU.
|
||||
/// </summary>
|
||||
public class Texture : RefreshResource
|
||||
{
|
||||
public uint Width { get; internal set; }
|
||||
public uint Height { get; internal set; }
|
||||
public uint Depth { get; }
|
||||
public TextureFormat Format { get; internal set; }
|
||||
public bool IsCube { get; }
|
||||
public uint LayerCount { get; }
|
||||
public uint LevelCount { get; }
|
||||
public SampleCount SampleCount { get; }
|
||||
public TextureUsageFlags UsageFlags { get; }
|
||||
public uint Size { get; }
|
||||
|
||||
private string name;
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
|
||||
set
|
||||
{
|
||||
if (Device.DebugMode)
|
||||
{
|
||||
Refresh.Refresh_SetTextureName(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
name = value;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: this allocates a delegate instance
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTexture;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the texture.</param>
|
||||
/// <param name="height">The height of the texture.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTexture2D(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1,
|
||||
SampleCount sampleCount = SampleCount.One
|
||||
) {
|
||||
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = 1,
|
||||
IsCube = false,
|
||||
LayerCount = 1,
|
||||
LevelCount = levelCount,
|
||||
SampleCount = sampleCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 2D texture array.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="width">The width of the texture.</param>
|
||||
/// <param name="height">The height of the texture.</param>
|
||||
/// <param name="layerCount">The layer count of the texture.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTexture2DArray(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
uint layerCount,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = 1,
|
||||
IsCube = false,
|
||||
LayerCount = layerCount,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a 3D texture.
|
||||
/// Note that the width, height and depth all form one slice and cannot be subdivided in a texture slice.
|
||||
/// </summary>
|
||||
public static Texture CreateTexture3D(
|
||||
GraphicsDevice device,
|
||||
uint width,
|
||||
uint height,
|
||||
uint depth,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Depth = depth,
|
||||
IsCube = false,
|
||||
LayerCount = 1,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cube texture.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="size">The length of one side of the cube.</param>
|
||||
/// <param name="format">The format of the texture.</param>
|
||||
/// <param name="usageFlags">Specifies how the texture will be used.</param>
|
||||
/// <param name="levelCount">Specifies the number of mip levels.</param>
|
||||
public static Texture CreateTextureCube(
|
||||
GraphicsDevice device,
|
||||
uint size,
|
||||
TextureFormat format,
|
||||
TextureUsageFlags usageFlags,
|
||||
uint levelCount = 1
|
||||
) {
|
||||
TextureCreateInfo textureCreateInfo = new TextureCreateInfo
|
||||
{
|
||||
Width = size,
|
||||
Height = size,
|
||||
Depth = 1,
|
||||
IsCube = true,
|
||||
LayerCount = 6,
|
||||
LevelCount = levelCount,
|
||||
Format = format,
|
||||
UsageFlags = usageFlags
|
||||
};
|
||||
|
||||
return new Texture(device, textureCreateInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new texture using a TextureCreateInfo struct.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="textureCreateInfo">The parameters to use when creating the texture.</param>
|
||||
public Texture(
|
||||
GraphicsDevice device,
|
||||
in TextureCreateInfo textureCreateInfo
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateTexture(
|
||||
device.Handle,
|
||||
textureCreateInfo.ToRefresh()
|
||||
);
|
||||
|
||||
Format = textureCreateInfo.Format;
|
||||
Width = textureCreateInfo.Width;
|
||||
Height = textureCreateInfo.Height;
|
||||
Depth = textureCreateInfo.Depth;
|
||||
IsCube = textureCreateInfo.IsCube;
|
||||
LayerCount = textureCreateInfo.LayerCount;
|
||||
LevelCount = textureCreateInfo.LevelCount;
|
||||
SampleCount = textureCreateInfo.SampleCount;
|
||||
UsageFlags = textureCreateInfo.UsageFlags;
|
||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
||||
name = "";
|
||||
}
|
||||
|
||||
// Used by Window. Swapchain texture handles are managed by the driver backend.
|
||||
internal Texture(
|
||||
GraphicsDevice device,
|
||||
TextureFormat format
|
||||
) : base(device)
|
||||
{
|
||||
Handle = IntPtr.Zero;
|
||||
|
||||
Format = format;
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
Depth = 1;
|
||||
IsCube = false;
|
||||
LevelCount = 1;
|
||||
SampleCount = SampleCount.One;
|
||||
UsageFlags = TextureUsageFlags.ColorTarget;
|
||||
Size = Width * Height * BytesPerPixel(Format) / BlockSizeSquared(Format);
|
||||
}
|
||||
|
||||
public static uint BytesPerPixel(TextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureFormat.R8:
|
||||
case TextureFormat.R8_UINT:
|
||||
return 1;
|
||||
case TextureFormat.R5G6B5:
|
||||
case TextureFormat.B4G4R4A4:
|
||||
case TextureFormat.A1R5G5B5:
|
||||
case TextureFormat.R16_SFLOAT:
|
||||
case TextureFormat.R8G8_SNORM:
|
||||
case TextureFormat.R8G8_UINT:
|
||||
case TextureFormat.R16_UINT:
|
||||
case TextureFormat.D16_UNORM:
|
||||
return 2;
|
||||
case TextureFormat.R8G8B8A8:
|
||||
case TextureFormat.B8G8R8A8:
|
||||
case TextureFormat.R32_SFLOAT:
|
||||
case TextureFormat.R16G16:
|
||||
case TextureFormat.R16G16_SFLOAT:
|
||||
case TextureFormat.R8G8B8A8_SNORM:
|
||||
case TextureFormat.A2R10G10B10:
|
||||
case TextureFormat.R8G8B8A8_UINT:
|
||||
case TextureFormat.R16G16_UINT:
|
||||
case TextureFormat.D24_UNORM_S8_UINT:
|
||||
case TextureFormat.D32_SFLOAT:
|
||||
return 4;
|
||||
case TextureFormat.D32_SFLOAT_S8_UINT:
|
||||
return 5;
|
||||
case TextureFormat.R16G16B16A16_SFLOAT:
|
||||
case TextureFormat.R16G16B16A16:
|
||||
case TextureFormat.R32G32_SFLOAT:
|
||||
case TextureFormat.R16G16B16A16_UINT:
|
||||
case TextureFormat.BC1:
|
||||
return 8;
|
||||
case TextureFormat.R32G32B32A32_SFLOAT:
|
||||
case TextureFormat.BC2:
|
||||
case TextureFormat.BC3:
|
||||
case TextureFormat.BC7:
|
||||
return 16;
|
||||
default:
|
||||
Log.Error("Texture format not recognized!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint TexelSize(TextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureFormat.BC2:
|
||||
case TextureFormat.BC3:
|
||||
case TextureFormat.BC7:
|
||||
return 16;
|
||||
case TextureFormat.BC1:
|
||||
return 8;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static uint BlockSizeSquared(TextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case TextureFormat.BC1:
|
||||
case TextureFormat.BC2:
|
||||
case TextureFormat.BC3:
|
||||
case TextureFormat.BC7:
|
||||
return 16;
|
||||
case TextureFormat.R8G8B8A8:
|
||||
case TextureFormat.B8G8R8A8:
|
||||
case TextureFormat.R5G6B5:
|
||||
case TextureFormat.A1R5G5B5:
|
||||
case TextureFormat.B4G4R4A4:
|
||||
case TextureFormat.A2R10G10B10:
|
||||
case TextureFormat.R16G16:
|
||||
case TextureFormat.R16G16B16A16:
|
||||
case TextureFormat.R8:
|
||||
case TextureFormat.R8G8_SNORM:
|
||||
case TextureFormat.R8G8B8A8_SNORM:
|
||||
case TextureFormat.R16_SFLOAT:
|
||||
case TextureFormat.R16G16_SFLOAT:
|
||||
case TextureFormat.R16G16B16A16_SFLOAT:
|
||||
case TextureFormat.R32_SFLOAT:
|
||||
case TextureFormat.R32G32_SFLOAT:
|
||||
case TextureFormat.R32G32B32A32_SFLOAT:
|
||||
case TextureFormat.R8_UINT:
|
||||
case TextureFormat.R8G8_UINT:
|
||||
case TextureFormat.R8G8B8A8_UINT:
|
||||
case TextureFormat.R16_UINT:
|
||||
case TextureFormat.R16G16_UINT:
|
||||
case TextureFormat.R16G16B16A16_UINT:
|
||||
case TextureFormat.D16_UNORM:
|
||||
case TextureFormat.D24_UNORM:
|
||||
case TextureFormat.D24_UNORM_S8_UINT:
|
||||
case TextureFormat.D32_SFLOAT:
|
||||
case TextureFormat.D32_SFLOAT_S8_UINT:
|
||||
return 1;
|
||||
default:
|
||||
Log.Error("Texture format not recognized!");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator TextureSlice(Texture t) => new TextureSlice(t);
|
||||
public static implicit operator TextureRegion(Texture t) => new TextureRegion(t);
|
||||
}
|
209
Nerfed.Runtime/Graphics/Resources/TransferBuffer.cs
Normal file
209
Nerfed.Runtime/Graphics/Resources/TransferBuffer.cs
Normal file
@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using RefreshCS;
|
||||
|
||||
namespace Nerfed.Runtime.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// A data container that can efficiently transfer data to and from the GPU.
|
||||
/// </summary>
|
||||
public unsafe class TransferBuffer : RefreshResource
|
||||
{
|
||||
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTransferBuffer;
|
||||
|
||||
/// <summary>
|
||||
/// Size in bytes.
|
||||
/// </summary>
|
||||
public uint Size { get; }
|
||||
|
||||
#if DEBUG
|
||||
public bool Mapped { get; private set; }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a buffer of requested size given a type and element count.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
|
||||
/// <param name="device">The GraphicsDevice.</param>
|
||||
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
|
||||
/// <returns></returns>
|
||||
public unsafe static TransferBuffer Create<T>(
|
||||
GraphicsDevice device,
|
||||
TransferBufferUsage usage,
|
||||
uint elementCount
|
||||
) where T : unmanaged
|
||||
{
|
||||
return new TransferBuffer(
|
||||
device,
|
||||
usage,
|
||||
(uint) Marshal.SizeOf<T>() * elementCount
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TransferBuffer.
|
||||
/// </summary>
|
||||
/// <param name="device">An initialized GraphicsDevice.</param>
|
||||
/// <param name="usage">Whether this will be used to upload buffers or textures.</param>
|
||||
/// <param name="sizeInBytes">The length of the buffer. Cannot be resized.</param>
|
||||
public TransferBuffer(
|
||||
GraphicsDevice device,
|
||||
TransferBufferUsage usage,
|
||||
uint sizeInBytes
|
||||
) : base(device)
|
||||
{
|
||||
Handle = Refresh.Refresh_CreateTransferBuffer(
|
||||
device.Handle,
|
||||
(Refresh.TransferBufferUsage) usage,
|
||||
sizeInBytes
|
||||
);
|
||||
Size = sizeInBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately copies data from a Span to the TransferBuffer.
|
||||
/// Returns the length of the copy in bytes.
|
||||
///
|
||||
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
|
||||
/// that command will still use the correct data at the cost of increased memory usage.
|
||||
///
|
||||
/// If cycle is set to false, the data will be overwritten immediately,
|
||||
/// which could cause a data race.
|
||||
/// </summary>
|
||||
public unsafe uint SetData<T>(
|
||||
Span<T> source,
|
||||
uint bufferOffsetInBytes,
|
||||
bool cycle
|
||||
) where T : unmanaged
|
||||
{
|
||||
int elementSize = Marshal.SizeOf<T>();
|
||||
uint dataLengthInBytes = (uint) (elementSize * source.Length);
|
||||
|
||||
#if DEBUG
|
||||
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
|
||||
AssertNotMapped();
|
||||
#endif
|
||||
|
||||
fixed (T* dataPtr = source)
|
||||
{
|
||||
Refresh.Refresh_SetTransferData(
|
||||
Device.Handle,
|
||||
(nint) dataPtr,
|
||||
new Refresh.TransferBufferRegion
|
||||
{
|
||||
TransferBuffer = Handle,
|
||||
Offset = bufferOffsetInBytes,
|
||||
Size = dataLengthInBytes
|
||||
},
|
||||
Conversions.BoolToInt(cycle)
|
||||
);
|
||||
}
|
||||
|
||||
return dataLengthInBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately copies data from a Span to the TransferBuffer.
|
||||
/// Returns the length of the copy in bytes.
|
||||
///
|
||||
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
|
||||
/// that command will still use the corret data at the cost of increased memory usage.
|
||||
///
|
||||
/// If cycle is set to false, the data will be overwritten immediately,
|
||||
/// which could cause a data race.
|
||||
/// </summary>
|
||||
public unsafe uint SetData<T>(
|
||||
Span<T> source,
|
||||
bool cycle
|
||||
) where T : unmanaged
|
||||
{
|
||||
return SetData(source, 0, cycle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately copies data from the TransferBuffer into a Span.
|
||||
/// </summary>
|
||||
public unsafe void GetData<T>(
|
||||
Span<T> destination,
|
||||
uint bufferOffsetInBytes = 0
|
||||
) where T : unmanaged
|
||||
{
|
||||
int elementSize = Marshal.SizeOf<T>();
|
||||
uint dataLengthInBytes = (uint) (elementSize * destination.Length);
|
||||
|
||||
#if DEBUG
|
||||
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
|
||||
AssertNotMapped();
|
||||
#endif
|
||||
|
||||
fixed (T* dataPtr = destination)
|
||||
{
|
||||
Refresh.Refresh_GetTransferData(
|
||||
Device.Handle,
|
||||
new Refresh.TransferBufferRegion
|
||||
{
|
||||
TransferBuffer = Handle,
|
||||
Offset = bufferOffsetInBytes,
|
||||
Size = dataLengthInBytes
|
||||
},
|
||||
(nint) dataPtr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the transfer buffer into application address space.
|
||||
/// You must call Unmap before encoding transfer commands.
|
||||
/// </summary>
|
||||
public unsafe void Map(bool cycle, out byte* data)
|
||||
{
|
||||
#if DEBUG
|
||||
AssertNotMapped();
|
||||
#endif
|
||||
|
||||
Refresh.Refresh_MapTransferBuffer(
|
||||
Device.Handle,
|
||||
Handle,
|
||||
Conversions.BoolToInt(cycle),
|
||||
out data
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
Mapped = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmaps the transfer buffer.
|
||||
/// The pointer given by Map is no longer valid.
|
||||
/// </summary>
|
||||
public void Unmap()
|
||||
{
|
||||
Refresh.Refresh_UnmapTransferBuffer(
|
||||
Device.Handle,
|
||||
Handle
|
||||
);
|
||||
|
||||
#if DEBUG
|
||||
Mapped = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
|
||||
{
|
||||
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
|
||||
{
|
||||
throw new InvalidOperationException($"Data overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertNotMapped()
|
||||
{
|
||||
if (Mapped)
|
||||
{
|
||||
throw new InvalidOperationException("Transfer buffer must not be mapped!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
BIN
Nerfed.Runtime/Graphics/StockShaders/Binary/fullscreen.vert.spv
Normal file
BIN
Nerfed.Runtime/Graphics/StockShaders/Binary/fullscreen.vert.spv
Normal file
Binary file not shown.
BIN
Nerfed.Runtime/Graphics/StockShaders/Binary/text_msdf.frag.spv
Normal file
BIN
Nerfed.Runtime/Graphics/StockShaders/Binary/text_msdf.frag.spv
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) out vec2 outTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
outTexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(outTexCoord * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
|
||||
}
|
34
Nerfed.Runtime/Graphics/StockShaders/Source/text_msdf.frag
Normal file
34
Nerfed.Runtime/Graphics/StockShaders/Source/text_msdf.frag
Normal file
@ -0,0 +1,34 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 inTexCoord;
|
||||
layout(location = 1) in vec4 inColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D msdf;
|
||||
|
||||
layout(set = 3, binding = 0) uniform UBO
|
||||
{
|
||||
float pxRange;
|
||||
} ubo;
|
||||
|
||||
float median(float r, float g, float b)
|
||||
{
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
float screenPxRange()
|
||||
{
|
||||
vec2 unitRange = vec2(ubo.pxRange)/vec2(textureSize(msdf, 0));
|
||||
vec2 screenTexSize = vec2(1.0)/fwidth(inTexCoord);
|
||||
return max(0.5*dot(unitRange, screenTexSize), 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 msd = texture(msdf, inTexCoord).rgb;
|
||||
float sd = median(msd.r, msd.g, msd.b);
|
||||
float screenPxDistance = screenPxRange() * (sd - 0.5);
|
||||
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||
outColor = mix(vec4(0.0, 0.0, 0.0, 0.0), inColor, opacity);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec3 inPos;
|
||||
layout(location = 1) in vec2 inTexCoord;
|
||||
layout(location = 2) in vec4 inColor;
|
||||
|
||||
layout(location = 0) out vec2 outTexCoord;
|
||||
layout(location = 1) out vec4 outColor;
|
||||
|
||||
layout(set = 1, binding = 0) uniform UBO
|
||||
{
|
||||
mat4 ViewProjection;
|
||||
} ubo;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ubo.ViewProjection * vec4(inPos, 1.0);
|
||||
outTexCoord = inTexCoord;
|
||||
outColor = inColor;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* This effect is based on the YUV-to-RGBA GLSL shader found in SDL.
|
||||
* Thus, it also released under the zlib license:
|
||||
* http://libsdl.org/license.php
|
||||
*/
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 TexCoord;
|
||||
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D YSampler;
|
||||
layout(set = 2, binding = 1) uniform sampler2D USampler;
|
||||
layout(set = 2, binding = 2) uniform sampler2D VSampler;
|
||||
|
||||
/* More info about colorspace conversion:
|
||||
* http://www.equasys.de/colorconversion.html
|
||||
* http://www.equasys.de/colorformat.html
|
||||
*/
|
||||
|
||||
const vec3 offset = vec3(-0.0625, -0.5, -0.5);
|
||||
const vec3 Rcoeff = vec3(1.164, 0.000, 1.793);
|
||||
const vec3 Gcoeff = vec3(1.164, -0.213, -0.533);
|
||||
const vec3 Bcoeff = vec3(1.164, 2.112, 0.000);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 yuv;
|
||||
yuv.x = texture(YSampler, TexCoord).r;
|
||||
yuv.y = texture(USampler, TexCoord).r;
|
||||
yuv.z = texture(VSampler, TexCoord).r;
|
||||
yuv += offset;
|
||||
|
||||
FragColor.r = dot(yuv, Rcoeff);
|
||||
FragColor.g = dot(yuv, Gcoeff);
|
||||
FragColor.b = dot(yuv, Bcoeff);
|
||||
FragColor.a = 1.0;
|
||||
}
|
7
Nerfed.Runtime/Input/ButtonState.cs
Normal file
7
Nerfed.Runtime/Input/ButtonState.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public enum ButtonState
|
||||
{
|
||||
Released,
|
||||
Pressed
|
||||
}
|
224
Nerfed.Runtime/Input/Devices/GamePad.cs
Normal file
224
Nerfed.Runtime/Input/Devices/GamePad.cs
Normal file
@ -0,0 +1,224 @@
|
||||
using SDL2;
|
||||
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public class GamePad
|
||||
{
|
||||
private const int maxGamePads = 4;
|
||||
private static readonly GamePad[] gamePads = new GamePad[maxGamePads];
|
||||
private static readonly GamePad dummy = new GamePad();
|
||||
|
||||
public IntPtr Handle { get; private set; }
|
||||
public bool IsConnected => Handle != IntPtr.Zero;
|
||||
public int JoystickInstanceId { get; private set; } = -1;
|
||||
|
||||
private readonly ButtonState[] buttonStates;
|
||||
private readonly ButtonState[] lastButtonStates;
|
||||
|
||||
static GamePad()
|
||||
{
|
||||
for (int i = 0; i < maxGamePads; i++)
|
||||
{
|
||||
gamePads[i] = new GamePad();
|
||||
}
|
||||
}
|
||||
|
||||
private GamePad()
|
||||
{
|
||||
int gamePadButtonCount = Enum.GetValues<GamePadButton>().Length;
|
||||
buttonStates = new ButtonState[gamePadButtonCount];
|
||||
lastButtonStates = new ButtonState[gamePadButtonCount];
|
||||
}
|
||||
|
||||
public bool IsButtonDown(GamePadButton button)
|
||||
{
|
||||
return buttonStates[(int)button] == ButtonState.Pressed;
|
||||
}
|
||||
|
||||
private void Register(IntPtr handle)
|
||||
{
|
||||
Handle = handle;
|
||||
IntPtr joystickHandle = SDL.SDL_GameControllerGetJoystick(handle);
|
||||
JoystickInstanceId = SDL.SDL_JoystickInstanceID(joystickHandle);
|
||||
Log.Info("Controller connected");
|
||||
}
|
||||
|
||||
private void UnRegister()
|
||||
{
|
||||
Handle = IntPtr.Zero;
|
||||
JoystickInstanceId = -1;
|
||||
Log.Info("Controller disconnected");
|
||||
}
|
||||
|
||||
public static GamePad Get(int slot)
|
||||
{
|
||||
return slot >= 0 && slot < maxGamePads ? gamePads[slot] : dummy;
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
for (int i = 0; i < maxGamePads; i++)
|
||||
{
|
||||
GamePad gamePad = gamePads[i];
|
||||
if (gamePad.IsConnected)
|
||||
{
|
||||
Array.Copy(gamePad.buttonStates, gamePad.lastButtonStates, gamePad.buttonStates.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ProcessEvent(ref SDL.SDL_Event ev)
|
||||
{
|
||||
switch (ev.type)
|
||||
{
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED:
|
||||
ProcessDeviceAdded(ref ev.cdevice);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED:
|
||||
ProcessDeviceRemoved(ref ev.cdevice);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN:
|
||||
ProcessButtonDown(ref ev.cbutton);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP:
|
||||
ProcessButtonUp(ref ev.cbutton);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERAXISMOTION:
|
||||
ProcessAxisMotion(ref ev.caxis);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADDOWN:
|
||||
ProcessTouchPadDown(ref ev.ctouchpad);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADUP:
|
||||
ProcessTouchPadUp(ref ev.ctouchpad);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERTOUCHPADMOTION:
|
||||
ProcessTouchPadMotion(ref ev.ctouchpad);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_CONTROLLERSENSORUPDATE:
|
||||
ProcessSensorUpdate(ref ev.sensor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessDeviceAdded(ref SDL.SDL_ControllerDeviceEvent ev)
|
||||
{
|
||||
int index = ev.which;
|
||||
if (SDL.SDL_IsGameController(index) == SDL.SDL_bool.SDL_TRUE)
|
||||
{
|
||||
int slot = -1;
|
||||
for (int i = 0; i < maxGamePads; i++)
|
||||
{
|
||||
if (!gamePads[i].IsConnected)
|
||||
{
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot == -1)
|
||||
{
|
||||
Log.Warning("Too many gamepads connected");
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr handle = SDL.SDL_GameControllerOpen(index);
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
Log.Error($"Failed to open gamepad: {SDL.SDL_GetError()}");
|
||||
return;
|
||||
}
|
||||
|
||||
gamePads[slot].Register(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private static GamePad GetByJoystickId(int joystickId)
|
||||
{
|
||||
for (int slot = 0; slot < maxGamePads; slot++)
|
||||
{
|
||||
GamePad gamePad = gamePads[slot];
|
||||
if (gamePad.IsConnected && gamePad.JoystickInstanceId == joystickId)
|
||||
{
|
||||
return gamePad;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ProcessDeviceRemoved(ref SDL.SDL_ControllerDeviceEvent ev)
|
||||
{
|
||||
GamePad gamePad = GetByJoystickId(ev.which);
|
||||
gamePad?.UnRegister();
|
||||
}
|
||||
|
||||
private static void ProcessButtonDown(ref SDL.SDL_ControllerButtonEvent ev)
|
||||
{
|
||||
if (TryConvertButton(ev.button, out GamePadButton button))
|
||||
{
|
||||
GamePad gamePad = GetByJoystickId(ev.which);
|
||||
gamePad.buttonStates[(int)button] = ButtonState.Pressed;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessButtonUp(ref SDL.SDL_ControllerButtonEvent ev)
|
||||
{
|
||||
if (TryConvertButton(ev.button, out GamePadButton button))
|
||||
{
|
||||
GamePad gamePad = GetByJoystickId(ev.which);
|
||||
gamePad.buttonStates[(int)button] = ButtonState.Released;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessAxisMotion(ref SDL.SDL_ControllerAxisEvent ev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void ProcessTouchPadDown(ref SDL.SDL_ControllerTouchpadEvent ev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void ProcessTouchPadUp(ref SDL.SDL_ControllerTouchpadEvent ev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void ProcessTouchPadMotion(ref SDL.SDL_ControllerTouchpadEvent ev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void ProcessSensorUpdate(ref SDL.SDL_SensorEvent ev)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static bool TryConvertButton(byte rawButton, out GamePadButton button)
|
||||
{
|
||||
GamePadButton? result = rawButton switch
|
||||
{
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A => GamePadButton.A,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B => GamePadButton.B,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X => GamePadButton.X,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y => GamePadButton.Y,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK => GamePadButton.Back,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START => GamePadButton.Start,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK => GamePadButton.LeftStick,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK => GamePadButton.RightStick,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER => GamePadButton.LeftShoulder,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER => GamePadButton.RightShoulder,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP => GamePadButton.DPadUp,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN => GamePadButton.DPadDown,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT => GamePadButton.DPadLeft,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT => GamePadButton.DPadRight,
|
||||
(byte)SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_TOUCHPAD => GamePadButton.BigButton,
|
||||
_ => null
|
||||
};
|
||||
|
||||
button = result.GetValueOrDefault();
|
||||
return result.HasValue;
|
||||
}
|
||||
}
|
295
Nerfed.Runtime/Input/Devices/Keyboard.cs
Normal file
295
Nerfed.Runtime/Input/Devices/Keyboard.cs
Normal file
@ -0,0 +1,295 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using SDL2;
|
||||
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public static class Keyboard
|
||||
{
|
||||
private static readonly List<char> textInput = new List<char>();
|
||||
|
||||
private const int maxPressedKeys = 8;
|
||||
private static readonly List<Key> keys = new List<Key>(maxPressedKeys);
|
||||
private static readonly List<Key> lastKeys = new List<Key>(maxPressedKeys);
|
||||
|
||||
/// <summary>
|
||||
/// True if the key was pressed or continued to be held down this frame.
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsKeyDown(Key key)
|
||||
{
|
||||
return HasKey(keys, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the key was pressed this frame.
|
||||
/// </summary>
|
||||
public static bool IsKeyPressed(Key key)
|
||||
{
|
||||
return HasKey(keys, key) && !HasKey(lastKeys, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the key was released or continued to be released this frame.
|
||||
/// </summary>
|
||||
public static bool IsKeyUp(Key key)
|
||||
{
|
||||
return !HasKey(keys, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the key was released this frame.
|
||||
/// </summary>
|
||||
public static bool IsKeyReleased(Key key)
|
||||
{
|
||||
return !HasKey(keys, key) && HasKey(lastKeys, key);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<Key> GetPressedKeys()
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(keys);
|
||||
}
|
||||
|
||||
public static ReadOnlySpan<char> GetTextInput()
|
||||
{
|
||||
return CollectionsMarshal.AsSpan(textInput);
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
textInput.Clear();
|
||||
|
||||
lastKeys.Clear();
|
||||
if (keys.Count > 0)
|
||||
{
|
||||
lastKeys.AddRange(keys);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasKey(List<Key> list, Key key)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
if (keys[i] == key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void ProcessEvent(ref SDL.SDL_Event ev)
|
||||
{
|
||||
switch (ev.type)
|
||||
{
|
||||
case SDL.SDL_EventType.SDL_TEXTINPUT:
|
||||
ProcessTextInputEvent(ref ev.text);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_KEYDOWN:
|
||||
ProcessKeyDownEvent(ref ev.key);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_KEYUP:
|
||||
ProcessKeyUpEvent(ref ev.key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ProcessTextInputEvent(ref SDL.SDL_TextInputEvent evt)
|
||||
{
|
||||
// Based on the SDL2# LPUtf8StrMarshaler
|
||||
int MeasureStringLength(byte* ptr)
|
||||
{
|
||||
int bytes;
|
||||
for (bytes = 0; *ptr != 0; ptr += 1, bytes += 1) ;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
fixed (byte* textPtr = evt.text)
|
||||
{
|
||||
int bytes = MeasureStringLength(textPtr);
|
||||
if (bytes > 0)
|
||||
{
|
||||
/* UTF8 will never encode more characters
|
||||
* than bytes in a string, so bytes is a
|
||||
* suitable upper estimate of size needed
|
||||
*/
|
||||
char* charsBuffer = stackalloc char[bytes];
|
||||
int chars = Encoding.UTF8.GetChars(
|
||||
textPtr,
|
||||
bytes,
|
||||
charsBuffer,
|
||||
bytes
|
||||
);
|
||||
|
||||
if (chars > 0)
|
||||
{
|
||||
textInput.AddRange(new ReadOnlySpan<char>(charsBuffer, chars));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessKeyDownEvent(ref SDL.SDL_KeyboardEvent ev)
|
||||
{
|
||||
Key key = ConvertScancode(ev.keysym.scancode);
|
||||
SetKeyState(key, KeyState.Down);
|
||||
}
|
||||
|
||||
private static void ProcessKeyUpEvent(ref SDL.SDL_KeyboardEvent ev)
|
||||
{
|
||||
Key key = ConvertScancode(ev.keysym.scancode);
|
||||
SetKeyState(key, KeyState.Up);
|
||||
}
|
||||
|
||||
public static void SetKeyState(Key key, KeyState state)
|
||||
{
|
||||
if (keys.Count >= maxPressedKeys)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == KeyState.Down)
|
||||
{
|
||||
if (!keys.Contains(key))
|
||||
{
|
||||
keys.Add(key);
|
||||
}
|
||||
}
|
||||
else if (state == KeyState.Up)
|
||||
{
|
||||
keys.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private static Key ConvertScancode(SDL.SDL_Scancode scancode)
|
||||
{
|
||||
switch (scancode)
|
||||
{
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_A: return Key.A;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_B: return Key.B;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_C: return Key.C;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_D: return Key.D;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_E: return Key.E;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F: return Key.F;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_G: return Key.G;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_H: return Key.H;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_I: return Key.I;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_J: return Key.J;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_K: return Key.K;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_L: return Key.L;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_M: return Key.M;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_N: return Key.N;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_O: return Key.O;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_P: return Key.P;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_Q: return Key.Q;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_R: return Key.R;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_S: return Key.S;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_T: return Key.T;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_U: return Key.U;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_V: return Key.V;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_W: return Key.W;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_X: return Key.X;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_Y: return Key.Y;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_Z: return Key.Z;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_1: return Key.D1;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_2: return Key.D2;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_3: return Key.D3;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_4: return Key.D4;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_5: return Key.D5;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_6: return Key.D6;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_7: return Key.D7;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_8: return Key.D8;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_9: return Key.D9;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_0: return Key.D0;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RETURN: return Key.Enter;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE: return Key.Escape;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE: return Key.Backspace;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_TAB: return Key.Tab;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_SPACE: return Key.Space;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_MINUS: return Key.Minus;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_EQUALS: return Key.Equals;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET: return Key.LeftBracket;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET: return Key.RightBracket;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH: return Key.Backslash;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON: return Key.Semicolon;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE: return Key.Apostrophe;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_GRAVE: return Key.Tilde;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_COMMA: return Key.Comma;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_PERIOD: return Key.Period;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_SLASH: return Key.Slash;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK: return Key.CapsLock;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F1: return Key.F1;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F2: return Key.F2;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F3: return Key.F3;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F4: return Key.F4;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F5: return Key.F5;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F6: return Key.F6;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F7: return Key.F7;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F8: return Key.F8;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F9: return Key.F9;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F10: return Key.F10;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F11: return Key.F11;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F12: return Key.F12;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN: return Key.PrintScreen;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK: return Key.Scroll;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_PAUSE: return Key.Pause;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_INSERT: return Key.Insert;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_HOME: return Key.Home;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP: return Key.PageUp;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_DELETE: return Key.Delete;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_END: return Key.End;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN: return Key.PageDown;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RIGHT: return Key.Right;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_LEFT: return Key.Left;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_DOWN: return Key.Down;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_UP: return Key.Up;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR: return Key.NumLock;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE: return Key.Divide;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY: return Key.Multiply;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS: return Key.Subtract;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS: return Key.Add;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_ENTER: return Key.Enter;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_1: return Key.NumPad1;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_2: return Key.NumPad2;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_3: return Key.NumPad3;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_4: return Key.NumPad4;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_5: return Key.NumPad5;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_6: return Key.NumPad6;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_7: return Key.NumPad7;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_8: return Key.NumPad8;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_9: return Key.NumPad9;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_0: return Key.NumPad0;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_PERIOD: return Key.Period;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION: return Key.Apps;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_EQUALS: return Key.Equals;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F13: return Key.F13;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F14: return Key.F14;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F15: return Key.F15;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F16: return Key.F16;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F17: return Key.F17;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F18: return Key.F18;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F19: return Key.F19;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F20: return Key.F20;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F21: return Key.F21;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F22: return Key.F22;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F23: return Key.F23;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_F24: return Key.F24;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_EXECUTE: return Key.Execute;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_HELP: return Key.Help;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_MENU: return Key.Apps;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_SELECT: return Key.Select;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_KP_COMMA: return Key.Comma;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_LCTRL: return Key.LeftControl;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT: return Key.LeftShift;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_LALT: return Key.LeftAlt;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_LGUI: return Key.LeftSuper;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RCTRL: return Key.RightControl;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT: return Key.RightShift;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RALT: return Key.RightAlt;
|
||||
case SDL.SDL_Scancode.SDL_SCANCODE_RGUI: return Key.RightSuper;
|
||||
default: return Key.None;
|
||||
}
|
||||
}
|
||||
}
|
132
Nerfed.Runtime/Input/Devices/Mouse.cs
Normal file
132
Nerfed.Runtime/Input/Devices/Mouse.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using System.Numerics;
|
||||
using SDL2;
|
||||
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
internal static class Mouse
|
||||
{
|
||||
public static Vector2 Position { get; private set; }
|
||||
|
||||
private static readonly ButtonState[] buttonStates;
|
||||
private static readonly ButtonState[] lastButtonStates;
|
||||
private static int wheelX;
|
||||
private static int wheelY;
|
||||
|
||||
static Mouse()
|
||||
{
|
||||
int numButtons = Enum.GetValues<MouseButton>().Length;
|
||||
buttonStates = new ButtonState[numButtons];
|
||||
lastButtonStates = new ButtonState[numButtons];
|
||||
}
|
||||
|
||||
/// /// <summary>
|
||||
/// True if the button is pressed or continued to be held this frame.
|
||||
/// </summary>
|
||||
public static bool IsButtonDown(MouseButton button)
|
||||
{
|
||||
return buttonStates[(int)button] == ButtonState.Pressed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is pressed this frame.
|
||||
/// </summary>
|
||||
public static bool IsButtonPressed(MouseButton button)
|
||||
{
|
||||
return buttonStates[(int)button] == ButtonState.Pressed && lastButtonStates[(int)button] == ButtonState.Released;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is released or continued to be released this frame.
|
||||
/// </summary>
|
||||
/// <param name="button"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsButtonUp(MouseButton button)
|
||||
{
|
||||
return buttonStates[(int)button] == ButtonState.Released;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if the button is released this frame.
|
||||
/// </summary>
|
||||
public static bool IsButtonReleased(MouseButton button)
|
||||
{
|
||||
return buttonStates[(int)button] == ButtonState.Released && lastButtonStates[(int)button] == ButtonState.Pressed;
|
||||
}
|
||||
|
||||
public static int GetWheel()
|
||||
{
|
||||
return wheelY;
|
||||
}
|
||||
|
||||
public static int GetWheelHorizontal()
|
||||
{
|
||||
return wheelX;
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
Array.Copy(buttonStates, lastButtonStates, buttonStates.Length);
|
||||
}
|
||||
|
||||
internal static void ProcessEvent(ref SDL.SDL_Event ev)
|
||||
{
|
||||
switch (ev.type)
|
||||
{
|
||||
case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
|
||||
ProcessButtonDownEvent(ref ev.button);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
|
||||
ProcessButtonUpEvent(ref ev.button);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
|
||||
ProcessWheelEvent(ref ev.wheel);
|
||||
break;
|
||||
case SDL.SDL_EventType.SDL_MOUSEMOTION:
|
||||
ProcessMotionEvent(ref ev.motion);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessButtonDownEvent(ref SDL.SDL_MouseButtonEvent ev)
|
||||
{
|
||||
if (TryConvertButton(ev.button, out MouseButton button))
|
||||
{
|
||||
buttonStates[(int)button] = ButtonState.Pressed;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessButtonUpEvent(ref SDL.SDL_MouseButtonEvent ev)
|
||||
{
|
||||
if (TryConvertButton(ev.button, out MouseButton button))
|
||||
{
|
||||
buttonStates[(int)button] = ButtonState.Released;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessWheelEvent(ref SDL.SDL_MouseWheelEvent ev)
|
||||
{
|
||||
wheelX += ev.x;
|
||||
wheelY += ev.y;
|
||||
}
|
||||
|
||||
private static void ProcessMotionEvent(ref SDL.SDL_MouseMotionEvent ev)
|
||||
{
|
||||
Position = new Vector2(ev.x, ev.y);
|
||||
}
|
||||
|
||||
private static bool TryConvertButton(byte rawButton, out MouseButton button)
|
||||
{
|
||||
MouseButton? result = rawButton switch
|
||||
{
|
||||
(byte)SDL.SDL_BUTTON_LEFT => MouseButton.Left,
|
||||
(byte)SDL.SDL_BUTTON_MIDDLE => MouseButton.Middle,
|
||||
(byte)SDL.SDL_BUTTON_RIGHT => MouseButton.Right,
|
||||
(byte)SDL.SDL_BUTTON_X1 => MouseButton.XButton1,
|
||||
(byte)SDL.SDL_BUTTON_X2 => MouseButton.XButton2,
|
||||
_ => null
|
||||
};
|
||||
|
||||
button = result.GetValueOrDefault();
|
||||
return result.HasValue;
|
||||
}
|
||||
}
|
11
Nerfed.Runtime/Input/GamePadAxis.cs
Normal file
11
Nerfed.Runtime/Input/GamePadAxis.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public enum GamePadAxis
|
||||
{
|
||||
LeftTrigger,
|
||||
RightTrigger,
|
||||
LeftThumbStickX,
|
||||
LeftThumbStickY,
|
||||
RightThumbStickX,
|
||||
RightThumbStickY
|
||||
}
|
20
Nerfed.Runtime/Input/GamePadButton.cs
Normal file
20
Nerfed.Runtime/Input/GamePadButton.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public enum GamePadButton
|
||||
{
|
||||
Start,
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
DPadLeft,
|
||||
DPadRight,
|
||||
DPadUp,
|
||||
DPadDown,
|
||||
RightShoulder,
|
||||
LeftShoulder,
|
||||
LeftStick,
|
||||
RightStick,
|
||||
Back,
|
||||
BigButton
|
||||
}
|
127
Nerfed.Runtime/Input/Key.cs
Normal file
127
Nerfed.Runtime/Input/Key.cs
Normal file
@ -0,0 +1,127 @@
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public enum Key : int // must be int32 (see KeyboardState)
|
||||
{
|
||||
None,
|
||||
Backspace,
|
||||
Tab,
|
||||
Enter,
|
||||
CapsLock,
|
||||
Escape,
|
||||
Space,
|
||||
PageUp,
|
||||
PageDown,
|
||||
End,
|
||||
Home,
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Select,
|
||||
Execute,
|
||||
PrintScreen,
|
||||
Insert,
|
||||
Delete,
|
||||
Help,
|
||||
D0,
|
||||
D1,
|
||||
D2,
|
||||
D3,
|
||||
D4,
|
||||
D5,
|
||||
D6,
|
||||
D7,
|
||||
D8,
|
||||
D9,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
LeftSuper,
|
||||
RightSuper,
|
||||
Apps,
|
||||
NumPad0,
|
||||
NumPad1,
|
||||
NumPad2,
|
||||
NumPad3,
|
||||
NumPad4,
|
||||
NumPad5,
|
||||
NumPad6,
|
||||
NumPad7,
|
||||
NumPad8,
|
||||
NumPad9,
|
||||
Multiply,
|
||||
Add,
|
||||
Separator,
|
||||
Subtract,
|
||||
Decimal,
|
||||
Divide,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
NumLock,
|
||||
Scroll,
|
||||
LeftShift,
|
||||
RightShift,
|
||||
LeftControl,
|
||||
RightControl,
|
||||
LeftAlt,
|
||||
RightAlt,
|
||||
Semicolon,
|
||||
Equals,
|
||||
Comma,
|
||||
Minus,
|
||||
Period,
|
||||
Slash,
|
||||
Tilde,
|
||||
LeftBracket,
|
||||
Backslash,
|
||||
RightBracket,
|
||||
Apostrophe,
|
||||
Pause,
|
||||
|
||||
Count
|
||||
}
|
7
Nerfed.Runtime/Input/KeyState.cs
Normal file
7
Nerfed.Runtime/Input/KeyState.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public enum KeyState
|
||||
{
|
||||
Up,
|
||||
Down
|
||||
}
|
10
Nerfed.Runtime/Input/MouseButton.cs
Normal file
10
Nerfed.Runtime/Input/MouseButton.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Nerfed.Runtime;
|
||||
|
||||
public enum MouseButton
|
||||
{
|
||||
Left,
|
||||
Middle,
|
||||
Right,
|
||||
XButton1,
|
||||
XButton2
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user