Add image processing and memory management features

Added new namespace `Misaki.HighPerformance.Image` for image processing, including classes for animated GIF handling and memory management.
Added `AnimatedFrameResult` class for individual frames in animated images.
Added `AnimatedGifEnumerator` class for enumerating frames in animated GIFs.
Added `ColorComponents` enum for different color formats.
Added `ImageInfo` struct for image dimensions and color components.
Added `CRuntime` class for low-level memory management functions.
Added `MemoryStats` class to track memory allocation statistics.
Added utility functions for creating multi-dimensional arrays.
Added new structures for fixed-size UTF-8 encoded strings.
Added benchmarking classes to test new memory management features.

Changed `StbImage.cs` to include new namespaces and functionality for image data manipulation.
Changed project files to target .NET 9.0 and enable new features.
Changed `Arena.cs` and `DynamicArena.cs` to use `nuint` for size parameters.
Changed `BitSet.cs` to enhance bit manipulation methods.
Changed `Program.cs` to run `FunctionPtrBenchmark` for performance testing.

Removed memory tracking code from `AllocationManager.cs`, including the `_allocated` dictionary and related logic.
Removed `Free` method from `IAllocator.cs` interface.
Removed `UNSAFE_COLLECTION_CHECK` preprocessor directive from the codebase.

Refactored various files to improve organization, moving from `Unsafe` to `LowLevel` namespace.
Refactored `MemoryUtilities` class to include new memory operation methods.
Refactored `UnsafeUtilities.cs` to support new collection structures.
This commit is contained in:
2025-07-12 19:48:42 +09:00
parent d306f183de
commit eeff3313b5
72 changed files with 14444 additions and 471 deletions

View File

@@ -0,0 +1,12 @@
namespace Misaki.HighPerformance.Image
{
#if !STBSHARP_INTERNAL
public
#else
internal
#endif
class AnimatedFrameResult : ImageResult
{
public int DelayInMs { get; set; }
}
}

View File

@@ -0,0 +1,149 @@
using Misaki.HighPerformance.Image.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace Misaki.HighPerformance.Image
{
internal class AnimatedGifEnumerator : IEnumerator<AnimatedFrameResult>
{
private readonly StbImage.stbi__context _context;
private StbImage.stbi__gif _gif;
private readonly ColorComponents _colorComponents;
public AnimatedGifEnumerator(Stream input, ColorComponents colorComponents)
{
if (input == null)
throw new ArgumentNullException("input");
_context = new StbImage.stbi__context(input);
if (StbImage.stbi__gif_test(_context) == 0)
throw new Exception("Input stream is not GIF file.");
_gif = new StbImage.stbi__gif();
_colorComponents = colorComponents;
}
public ColorComponents ColorComponents
{
get
{
return _colorComponents;
}
}
public AnimatedFrameResult Current
{
get; private set;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public unsafe bool MoveNext()
{
// Read next frame
int ccomp;
byte two_back;
var result = StbImage.stbi__gif_load_next(_context, _gif, &ccomp, (int)ColorComponents, &two_back);
if (result == null)
return false;
if (Current == null)
{
Current = new AnimatedFrameResult
{
Width = (uint)_gif.w,
Height = (uint)_gif.h,
SourceComponent = (ColorComponents)ccomp,
Component = ColorComponents == ColorComponents.Default ? (ColorComponents)ccomp : ColorComponents
};
Current.SetData(result);
}
Current.DelayInMs = _gif.delay;
return true;
}
public void Reset()
{
throw new NotImplementedException();
}
~AnimatedGifEnumerator()
{
Dispose(false);
}
protected unsafe virtual void Dispose(bool disposing)
{
if (_gif != null)
{
if (_gif._out_ != null)
{
CRuntime.free(_gif._out_);
_gif._out_ = null;
}
if (_gif.history != null)
{
CRuntime.free(_gif.history);
_gif.history = null;
}
if (_gif.background != null)
{
CRuntime.free(_gif.background);
_gif.background = null;
}
_gif = null;
}
}
}
internal class AnimatedGifEnumerable : IEnumerable<AnimatedFrameResult>
{
private readonly Stream _input;
private readonly ColorComponents _colorComponents;
public AnimatedGifEnumerable(Stream input, ColorComponents colorComponents)
{
_input = input;
_colorComponents = colorComponents;
}
public ColorComponents ColorComponents
{
get
{
return _colorComponents;
}
}
public IEnumerator<AnimatedFrameResult> GetEnumerator()
{
return new AnimatedGifEnumerator(_input, ColorComponents);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Misaki.HighPerformance.Image
{
#if !STBSHARP_INTERNAL
public
#else
internal
#endif
enum ColorComponents
{
Default,
R,
RA,
RGB,
RGBA
}
}

View File

@@ -0,0 +1,40 @@
using System.IO;
namespace Misaki.HighPerformance.Image
{
#if !STBSHARP_INTERNAL
public
#else
internal
#endif
struct ImageInfo
{
public int Width;
public int Height;
public ColorComponents ColorComponents;
public int BitsPerChannel;
public static unsafe ImageInfo? FromStream(Stream stream)
{
int width, height, comp;
var context = new StbImage.stbi__context(stream);
var is16Bit = StbImage.stbi__is_16_main(context) == 1;
StbImage.stbi__rewind(context);
var infoResult = StbImage.stbi__info_main(context, &width, &height, &comp);
StbImage.stbi__rewind(context);
if (infoResult == 0)
return null;
return new ImageInfo
{
Width = width,
Height = height,
ColorComponents = (ColorComponents)comp,
BitsPerChannel = is16Bit ? 16 : 8
};
}
}
}

View File

@@ -0,0 +1,98 @@
using Misaki.HighPerformance.Image.Runtime;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Image;
public unsafe class ImageResult : IDisposable
{
private byte* _buffer;
public uint Width
{
get; init;
}
public uint Height
{
get; init;
}
public ColorComponents SourceComponent
{
get; init;
}
public ColorComponents Component
{
get; init;
}
public Span<byte> Data => new(_buffer, (int)(Width * Height * (uint)Component));
internal void SetData(byte* data)
{
CRuntime.free(_buffer);
_buffer = data;
}
internal static unsafe ImageResult FromResult(byte* result, uint width, uint height, ColorComponents comp,
ColorComponents req_comp)
{
if (result == null)
throw new InvalidOperationException(StbImage.stbi__g_failure_reason);
var image = new ImageResult
{
Width = width,
Height = height,
SourceComponent = comp,
Component = req_comp == ColorComponents.Default ? comp : req_comp
};
image._buffer = result;
return image;
}
public static unsafe ImageResult FromStream(Stream stream,
ColorComponents requiredComponents = ColorComponents.Default)
{
int x, y, comp;
var context = new StbImage.stbi__context(stream);
var result = StbImage.stbi__load_and_postprocess_8bit(context, &x, &y, &comp, (int)requiredComponents);
return FromResult(result, (uint)x, (uint)y, (ColorComponents)comp, requiredComponents);
}
public static ImageResult FromMemory(byte[] data, ColorComponents requiredComponents = ColorComponents.Default)
{
using var stream = new MemoryStream(data);
return FromStream(stream, requiredComponents);
}
public static IEnumerable<AnimatedFrameResult> AnimatedGifFramesFromStream(Stream stream, ColorComponents requiredComponents = ColorComponents.Default)
{
return new AnimatedGifEnumerable(stream, requiredComponents);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePtr()
{
return _buffer;
}
public void Dispose()
{
if (_buffer == null)
{
return;
}
CRuntime.free(_buffer);
_buffer = null;
}
}

View File

@@ -0,0 +1,89 @@
using Misaki.HighPerformance.Image.Runtime;
using System;
using System.IO;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Image;
public unsafe class ImageResultFloat : IDisposable
{
private float* _buffer;
public int Width
{
get; init;
}
public int Height
{
get; init;
}
public ColorComponents SourceComponent
{
get; init;
}
public ColorComponents Component
{
get; init;
}
public Span<byte> Data => new(_buffer, (int)(Width * Height * (uint)Component));
internal static unsafe ImageResultFloat FromResult(float* result, int width, int height, ColorComponents comp,
ColorComponents req_comp)
{
if (result == null)
throw new InvalidOperationException(StbImage.stbi__g_failure_reason);
var image = new ImageResultFloat
{
Width = width,
Height = height,
SourceComponent = comp,
Component = req_comp == ColorComponents.Default ? comp : req_comp
};
image._buffer = result;
return image;
}
public static unsafe ImageResultFloat FromStream(Stream stream,
ColorComponents requiredComponents = ColorComponents.Default)
{
int x, y, comp;
var context = new StbImage.stbi__context(stream);
var result = StbImage.stbi__loadf_main(context, &x, &y, &comp, (int)requiredComponents);
return FromResult(result, x, y, (ColorComponents)comp, requiredComponents);
}
public static ImageResultFloat FromMemory(byte[] data,
ColorComponents requiredComponents = ColorComponents.Default)
{
using (var stream = new MemoryStream(data))
{
return FromStream(stream, requiredComponents);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float* GetUnsafePtr()
{
return _buffer;
}
public void Dispose()
{
if (_buffer == null)
{
return;
}
CRuntime.free(_buffer);
_buffer = null;
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>StbImageSharpTeam</Authors>
<Product>StbImageSharp</Product>
<PackageId>StbImageSharp</PackageId>
<TargetFrameworks></TargetFrameworks>
<Description>C# port of the stb_image.h</Description>
<PackageLicenseUrl>Public Domain</PackageLicenseUrl>
<Version>1.0.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<WarningLevel>8</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
<WarningLevel>8</WarningLevel>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,186 @@
using System;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Image.Runtime
{
internal static unsafe class CRuntime
{
private static readonly string numbers = "0123456789";
public static void* malloc(ulong size)
{
return malloc((long)size);
}
public static void* malloc(long size)
{
var ptr = NativeMemory.Alloc((nuint)size);
MemoryStats.Allocated();
return ptr;
}
public static void free(void* ptr)
{
if (ptr == null)
return;
NativeMemory.Free(ptr);
MemoryStats.Freed();
}
public static void memcpy(void* a, void* b, long size)
{
NativeMemory.Copy(b, a, (nuint)size);
}
public static void memcpy(void* a, void* b, ulong size)
{
memcpy(a, b, (long)size);
}
public static void memmove(void* a, void* b, long size)
{
void* temp = null;
try
{
temp = malloc(size);
memcpy(temp, b, size);
memcpy(a, temp, size);
}
finally
{
if (temp != null)
free(temp);
}
}
public static int memcmp(void* a, void* b, long size)
{
var result = 0;
var ap = (byte*)a;
var bp = (byte*)b;
for (long i = 0; i < size; ++i)
{
if (*ap != *bp)
result += 1;
ap++;
bp++;
}
return result;
}
public static void memset(void* ptr, int value, long size)
{
NativeMemory.Fill(ptr, (nuint)size, (byte)value);
}
public static void memset(void* ptr, int value, ulong size)
{
memset(ptr, value, (long)size);
}
public static uint _lrotl(uint x, int y)
{
return x << y | x >> 32 - y;
}
public static void* realloc(void* ptr, long newSize)
{
if (ptr == null)
return malloc(newSize);
var result = NativeMemory.Realloc(ptr, (nuint)newSize);
return result;
}
public static void* realloc(void* a, ulong newSize)
{
return realloc(a, (long)newSize);
}
public static int abs(int v)
{
return Math.Abs(v);
}
public static double pow(double a, double b)
{
return Math.Pow(a, b);
}
public static double ldexp(double number, int exponent)
{
return number * Math.Pow(2, exponent);
}
public static int strcmp(sbyte* src, string token)
{
var result = 0;
for (var i = 0; i < token.Length; ++i)
{
if (src[i] != token[i])
{
++result;
}
}
return result;
}
public static int strncmp(sbyte* src, string token, ulong size)
{
var result = 0;
for (var i = 0; i < Math.Min(token.Length, (int)size); ++i)
{
if (src[i] != token[i])
{
++result;
}
}
return result;
}
public static long strtol(sbyte* start, sbyte** end, int radix)
{
// First step - determine length
var length = 0;
var ptr = start;
while (numbers.IndexOf((char)*ptr) != -1)
{
++ptr;
++length;
}
long result = 0;
// Now build up the number
ptr = start;
while (length > 0)
{
long num = numbers.IndexOf((char)*ptr);
var pow = (long)Math.Pow(10, length - 1);
result += num * pow;
++ptr;
--length;
}
if (end != null)
{
*end = ptr;
}
return result;
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Threading;
namespace Misaki.HighPerformance.Image.Runtime
{
internal unsafe static class MemoryStats
{
private static int _allocations;
public static int Allocations
{
get
{
return _allocations;
}
}
internal static void Allocated()
{
Interlocked.Increment(ref _allocations);
}
internal static void Freed()
{
Interlocked.Decrement(ref _allocations);
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Misaki.HighPerformance.Image.Runtime
{
internal class Utility
{
public static T[][] CreateArray<T>(int d1, int d2)
{
var result = new T[d1][];
for (var i = 0; i < d1; i++)
{
result[i] = new T[d2];
}
return result;
}
}
}

View File

@@ -0,0 +1,494 @@
// Generated by Sichem at 9/16/2024 9:09:30 AM
using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image.Runtime;
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public static int stbi__bmp_test(stbi__context s)
{
var r = stbi__bmp_test_raw(s);
stbi__rewind(s);
return r;
}
public static void* stbi__bmp_load(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri)
{
byte* _out_;
uint mr = 0;
uint mg = 0;
uint mb = 0;
uint ma = 0;
uint all_a = 0;
var pal = Utility.CreateArray<byte>(256, 4);
var psize = 0;
var i = 0;
var j = 0;
var width = 0;
var flip_vertically = 0;
var pad = 0;
var target = 0;
var info = new stbi__bmp_data();
info.all_a = 255;
if (stbi__bmp_parse_header(s, &info) == null)
return null;
flip_vertically = (int)s.img_y > 0 ? 1 : 0;
s.img_y = (uint)CRuntime.abs((int)s.img_y);
if (s.img_y > 1 << 24)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
if (s.img_x > 1 << 24)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
mr = info.mr;
mg = info.mg;
mb = info.mb;
ma = info.ma;
all_a = info.all_a;
if (info.hsz == 12)
{
if (info.bpp < 24)
psize = (info.offset - info.extra_read - 24) / 3;
}
else
{
if (info.bpp < 16)
psize = (info.offset - info.extra_read - info.hsz) >> 2;
}
if (psize == 0)
{
var bytes_read_so_far = (int)s.Stream.Position;
var header_limit = 1024;
var extra_data_limit = 256 * 4;
if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit)
return (byte*)(ulong)(stbi__err("bad header") != 0 ? 0 : 0);
if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit)
return (byte*)(ulong)(stbi__err("bad offset") != 0 ? 0 : 0);
stbi__skip(s, info.offset - bytes_read_so_far);
}
if (info.bpp == 24 && ma == 0xff000000)
s.img_n = 3;
else
s.img_n = ma != 0 ? 4 : 3;
if (req_comp != 0 && req_comp >= 3)
target = req_comp;
else
target = s.img_n;
if (stbi__mad3sizes_valid(target, (int)s.img_x, (int)s.img_y, 0) == 0)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
_out_ = (byte*)stbi__malloc_mad3(target, (int)s.img_x, (int)s.img_y, 0);
if (_out_ == null)
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
if (info.bpp < 16)
{
var z = 0;
if (psize == 0 || psize > 256)
{
CRuntime.free(_out_);
return (byte*)(ulong)(stbi__err("invalid") != 0 ? 0 : 0);
}
for (i = 0; i < psize; ++i)
{
pal[i][2] = stbi__get8(s);
pal[i][1] = stbi__get8(s);
pal[i][0] = stbi__get8(s);
if (info.hsz != 12)
stbi__get8(s);
pal[i][3] = 255;
}
stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
if (info.bpp == 1)
{
width = (int)((s.img_x + 7) >> 3);
}
else if (info.bpp == 4)
{
width = (int)((s.img_x + 1) >> 1);
}
else if (info.bpp == 8)
{
width = (int)s.img_x;
}
else
{
CRuntime.free(_out_);
return (byte*)(ulong)(stbi__err("bad bpp") != 0 ? 0 : 0);
}
pad = -width & 3;
if (info.bpp == 1)
for (j = 0; j < (int)s.img_y; ++j)
{
var bit_offset = 7;
int v = stbi__get8(s);
for (i = 0; i < (int)s.img_x; ++i)
{
var color = (v >> bit_offset) & 0x1;
_out_[z++] = pal[color][0];
_out_[z++] = pal[color][1];
_out_[z++] = pal[color][2];
if (target == 4)
_out_[z++] = 255;
if (i + 1 == (int)s.img_x)
break;
if (--bit_offset < 0)
{
bit_offset = 7;
v = stbi__get8(s);
}
}
stbi__skip(s, pad);
}
else
for (j = 0; j < (int)s.img_y; ++j)
{
for (i = 0; i < (int)s.img_x; i += 2)
{
int v = stbi__get8(s);
var v2 = 0;
if (info.bpp == 4)
{
v2 = v & 15;
v >>= 4;
}
_out_[z++] = pal[v][0];
_out_[z++] = pal[v][1];
_out_[z++] = pal[v][2];
if (target == 4)
_out_[z++] = 255;
if (i + 1 == (int)s.img_x)
break;
v = info.bpp == 8 ? stbi__get8(s) : v2;
_out_[z++] = pal[v][0];
_out_[z++] = pal[v][1];
_out_[z++] = pal[v][2];
if (target == 4)
_out_[z++] = 255;
}
stbi__skip(s, pad);
}
}
else
{
var rshift = 0;
var gshift = 0;
var bshift = 0;
var ashift = 0;
var rcount = 0;
var gcount = 0;
var bcount = 0;
var acount = 0;
var z = 0;
var easy = 0;
stbi__skip(s, info.offset - info.extra_read - info.hsz);
if (info.bpp == 24)
width = (int)(3 * s.img_x);
else if (info.bpp == 16)
width = (int)(2 * s.img_x);
else
width = 0;
pad = -width & 3;
if (info.bpp == 24)
easy = 1;
else if (info.bpp == 32)
if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
easy = 2;
if (easy == 0)
{
if (mr == 0 || mg == 0 || mb == 0)
{
CRuntime.free(_out_);
return (byte*)(ulong)(stbi__err("bad masks") != 0 ? 0 : 0);
}
rshift = stbi__high_bit(mr) - 7;
rcount = stbi__bitcount(mr);
gshift = stbi__high_bit(mg) - 7;
gcount = stbi__bitcount(mg);
bshift = stbi__high_bit(mb) - 7;
bcount = stbi__bitcount(mb);
ashift = stbi__high_bit(ma) - 7;
acount = stbi__bitcount(ma);
if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8)
{
CRuntime.free(_out_);
return (byte*)(ulong)(stbi__err("bad masks") != 0 ? 0 : 0);
}
}
for (j = 0; j < (int)s.img_y; ++j)
{
if (easy != 0)
{
for (i = 0; i < (int)s.img_x; ++i)
{
byte a = 0;
_out_[z + 2] = stbi__get8(s);
_out_[z + 1] = stbi__get8(s);
_out_[z + 0] = stbi__get8(s);
z += 3;
a = (byte)(easy == 2 ? stbi__get8(s) : 255);
all_a |= a;
if (target == 4)
_out_[z++] = a;
}
}
else
{
var bpp = info.bpp;
for (i = 0; i < (int)s.img_x; ++i)
{
var v = bpp == 16 ? (uint)stbi__get16le(s) : stbi__get32le(s);
uint a = 0;
_out_[z++] = (byte)(stbi__shiftsigned(v & mr, rshift, rcount) & 255);
_out_[z++] = (byte)(stbi__shiftsigned(v & mg, gshift, gcount) & 255);
_out_[z++] = (byte)(stbi__shiftsigned(v & mb, bshift, bcount) & 255);
a = (uint)(ma != 0 ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
all_a |= a;
if (target == 4)
_out_[z++] = (byte)(a & 255);
}
}
stbi__skip(s, pad);
}
}
if (target == 4 && all_a == 0)
for (i = (int)(4 * s.img_x * s.img_y - 1); i >= 0; i -= 4)
_out_[i] = 255;
if (flip_vertically != 0)
{
byte t = 0;
for (j = 0; j < (int)s.img_y >> 1; ++j)
{
var p1 = _out_ + j * s.img_x * target;
var p2 = _out_ + (s.img_y - 1 - j) * s.img_x * target;
for (i = 0; i < (int)s.img_x * target; ++i)
{
t = p1[i];
p1[i] = p2[i];
p2[i] = t;
}
}
}
if (req_comp != 0 && req_comp != target)
{
_out_ = stbi__convert_format(_out_, target, req_comp, s.img_x, s.img_y);
if (_out_ == null)
return _out_;
}
*x = (int)s.img_x;
*y = (int)s.img_y;
if (comp != null)
*comp = s.img_n;
return _out_;
}
public static int stbi__bmp_info(stbi__context s, int* x, int* y, int* comp)
{
void* p;
var info = new stbi__bmp_data();
info.all_a = 255;
p = stbi__bmp_parse_header(s, &info);
if (p == null)
{
stbi__rewind(s);
return 0;
}
if (x != null)
*x = (int)s.img_x;
if (y != null)
*y = (int)s.img_y;
if (comp != null)
{
if (info.bpp == 24 && info.ma == 0xff000000)
*comp = 3;
else
*comp = info.ma != 0 ? 4 : 3;
}
return 1;
}
public static int stbi__bmp_test_raw(stbi__context s)
{
var r = 0;
var sz = 0;
if (stbi__get8(s) != 66)
return 0;
if (stbi__get8(s) != 77)
return 0;
stbi__get32le(s);
stbi__get16le(s);
stbi__get16le(s);
stbi__get32le(s);
sz = (int)stbi__get32le(s);
r = sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124 ? 1 : 0;
return r;
}
public static int stbi__bmp_set_mask_defaults(stbi__bmp_data* info, int compress)
{
if (compress == 3)
return 1;
if (compress == 0)
{
if (info->bpp == 16)
{
info->mr = 31u << 10;
info->mg = 31u << 5;
info->mb = 31u << 0;
}
else if (info->bpp == 32)
{
info->mr = 0xffu << 16;
info->mg = 0xffu << 8;
info->mb = 0xffu << 0;
info->ma = 0xffu << 24;
info->all_a = 0;
}
else
{
info->mr = info->mg = info->mb = info->ma = 0;
}
return 1;
}
return 0;
}
public static void* stbi__bmp_parse_header(stbi__context s, stbi__bmp_data* info)
{
var hsz = 0;
if (stbi__get8(s) != 66 || stbi__get8(s) != 77)
return (byte*)(ulong)(stbi__err("not BMP") != 0 ? 0 : 0);
stbi__get32le(s);
stbi__get16le(s);
stbi__get16le(s);
info->offset = (int)stbi__get32le(s);
info->hsz = hsz = (int)stbi__get32le(s);
info->mr = info->mg = info->mb = info->ma = 0;
info->extra_read = 14;
if (info->offset < 0)
return (byte*)(ulong)(stbi__err("bad BMP") != 0 ? 0 : 0);
if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124)
return (byte*)(ulong)(stbi__err("unknown BMP") != 0 ? 0 : 0);
if (hsz == 12)
{
s.img_x = (uint)stbi__get16le(s);
s.img_y = (uint)stbi__get16le(s);
}
else
{
s.img_x = stbi__get32le(s);
s.img_y = stbi__get32le(s);
}
if (stbi__get16le(s) != 1)
return (byte*)(ulong)(stbi__err("bad BMP") != 0 ? 0 : 0);
info->bpp = stbi__get16le(s);
if (hsz != 12)
{
var compress = (int)stbi__get32le(s);
if (compress == 1 || compress == 2)
return (byte*)(ulong)(stbi__err("BMP RLE") != 0 ? 0 : 0);
if (compress >= 4)
return (byte*)(ulong)(stbi__err("BMP JPEG/PNG") != 0 ? 0 : 0);
if (compress == 3 && info->bpp != 16 && info->bpp != 32)
return (byte*)(ulong)(stbi__err("bad BMP") != 0 ? 0 : 0);
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
if (hsz == 40 || hsz == 56)
{
if (hsz == 56)
{
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
}
if (info->bpp == 16 || info->bpp == 32)
{
if (compress == 0)
{
stbi__bmp_set_mask_defaults(info, compress);
}
else if (compress == 3)
{
info->mr = stbi__get32le(s);
info->mg = stbi__get32le(s);
info->mb = stbi__get32le(s);
info->extra_read += 12;
if (info->mr == info->mg && info->mg == info->mb)
return (byte*)(ulong)(stbi__err("bad BMP") != 0 ? 0 : 0);
}
else
{
return (byte*)(ulong)(stbi__err("bad BMP") != 0 ? 0 : 0);
}
}
}
else
{
var i = 0;
if (hsz != 108 && hsz != 124)
return (byte*)(ulong)(stbi__err("bad BMP") != 0 ? 0 : 0);
info->mr = stbi__get32le(s);
info->mg = stbi__get32le(s);
info->mb = stbi__get32le(s);
info->ma = stbi__get32le(s);
if (compress != 3)
stbi__bmp_set_mask_defaults(info, compress);
stbi__get32le(s);
for (i = 0; i < 12; ++i)
stbi__get32le(s);
if (hsz == 124)
{
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
stbi__get32le(s);
}
}
}
return (void*)1;
}
[StructLayout(LayoutKind.Sequential)]
public struct stbi__bmp_data
{
public int bpp;
public int offset;
public int hsz;
public uint mr;
public uint mg;
public uint mb;
public uint ma;
public uint all_a;
public int extra_read;
}
}
}

View File

@@ -0,0 +1,770 @@
// Generated by Sichem at 9/16/2024 9:09:30 AM
using Misaki.HighPerformance.Image.Runtime;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public const int STBI_default = 0;
public const int STBI_grey = 1;
public const int STBI_grey_alpha = 2;
public const int STBI_rgb = 3;
public const int STBI_rgb_alpha = 4;
public const int STBI_ORDER_RGB = 0;
public const int STBI_ORDER_BGR = 1;
public const int STBI__SCAN_load = 0;
public const int STBI__SCAN_type = 1;
public const int STBI__SCAN_header = 2;
public static int stbi__vertically_flip_on_load_global;
public static int stbi__vertically_flip_on_load_local;
public static int stbi__vertically_flip_on_load_set;
public static float stbi__l2h_gamma = 2.2f;
public static float stbi__l2h_scale = 1.0f;
public static float stbi__h2l_gamma_i = 1.0f / 2.2f;
public static float stbi__h2l_scale_i = 1.0f;
public static int stbi__unpremultiply_on_load_global;
public static int stbi__de_iphone_flag_global;
public static int stbi__unpremultiply_on_load_local;
public static int stbi__unpremultiply_on_load_set;
public static int stbi__de_iphone_flag_local;
public static int stbi__de_iphone_flag_set;
public static byte[] stbi__process_marker_tag = { 65, 100, 111, 98, 101, 0 };
public static byte[] stbi__process_frame_header_rgb = { 82, 71, 66 };
public static byte[] stbi__compute_huffman_codes_length_dezigzag =
{ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
public static int[] stbi__shiftsigned_mul_table = { 0, 0xff, 0x55, 0x49, 0x11, 0x21, 0x41, 0x81, 0x01 };
public static int[] stbi__shiftsigned_shift_table = { 0, 0, 0, 1, 0, 2, 4, 6, 0 };
public static void stbi_hdr_to_ldr_gamma(float gamma)
{
stbi__h2l_gamma_i = 1 / gamma;
}
public static void stbi_hdr_to_ldr_scale(float scale)
{
stbi__h2l_scale_i = 1 / scale;
}
public static void stbi_ldr_to_hdr_gamma(float gamma)
{
stbi__l2h_gamma = gamma;
}
public static void stbi_ldr_to_hdr_scale(float scale)
{
stbi__l2h_scale = scale;
}
public static void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
{
stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply;
}
public static void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
{
stbi__de_iphone_flag_global = flag_true_if_should_convert;
}
public static void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
{
stbi__vertically_flip_on_load_global = flag_true_if_should_flip;
}
public static void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
{
stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;
stbi__unpremultiply_on_load_set = 1;
}
public static void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert)
{
stbi__de_iphone_flag_local = flag_true_if_should_convert;
stbi__de_iphone_flag_set = 1;
}
public static void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)
{
stbi__vertically_flip_on_load_local = flag_true_if_should_flip;
stbi__vertically_flip_on_load_set = 1;
}
public static void* stbi__malloc(ulong size)
{
return CRuntime.malloc(size);
}
public static int stbi__addsizes_valid(int a, int b)
{
if (b < 0)
return 0;
return a <= 2147483647 - b ? 1 : 0;
}
public static int stbi__mul2sizes_valid(int a, int b)
{
if (a < 0 || b < 0)
return 0;
if (b == 0)
return 1;
return a <= 2147483647 / b ? 1 : 0;
}
public static int stbi__mad2sizes_valid(int a, int b, int add)
{
return stbi__mul2sizes_valid(a, b) != 0 && stbi__addsizes_valid(a * b, add) != 0 ? 1 : 0;
}
public static int stbi__mad3sizes_valid(int a, int b, int c, int add)
{
return stbi__mul2sizes_valid(a, b) != 0 && stbi__mul2sizes_valid(a * b, c) != 0 &&
stbi__addsizes_valid(a * b * c, add) != 0
? 1
: 0;
}
public static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
{
return stbi__mul2sizes_valid(a, b) != 0 && stbi__mul2sizes_valid(a * b, c) != 0 &&
stbi__mul2sizes_valid(a * b * c, d) != 0 && stbi__addsizes_valid(a * b * c * d, add) != 0
? 1
: 0;
}
public static void* stbi__malloc_mad2(int a, int b, int add)
{
if (stbi__mad2sizes_valid(a, b, add) == 0)
return null;
return stbi__malloc((ulong)(a * b + add));
}
public static void* stbi__malloc_mad3(int a, int b, int c, int add)
{
if (stbi__mad3sizes_valid(a, b, c, add) == 0)
return null;
return stbi__malloc((ulong)(a * b * c + add));
}
public static void* stbi__malloc_mad4(int a, int b, int c, int d, int add)
{
if (stbi__mad4sizes_valid(a, b, c, d, add) == 0)
return null;
return stbi__malloc((ulong)(a * b * c * d + add));
}
public static int stbi__addints_valid(int a, int b)
{
if (a >= 0 != b >= 0)
return 1;
if (a < 0 && b < 0)
return a >= -2147483647 - 1 - b ? 1 : 0;
return a <= 2147483647 - b ? 1 : 0;
}
public static int stbi__mul2shorts_valid(int a, int b)
{
if (b == 0 || b == -1)
return 1;
if (a >= 0 == b >= 0)
return a <= 32767 / b ? 1 : 0;
if (b < 0)
return a <= -32768 / b ? 1 : 0;
return a >= -32768 / b ? 1 : 0;
}
public static float* stbi__ldr_to_hdr(byte* data, int x, int y, int comp)
{
var i = 0;
var k = 0;
var n = 0;
float* output;
if (data == null)
return null;
output = (float*)stbi__malloc_mad4(x, y, comp, sizeof(float), 0);
if (output == null)
{
CRuntime.free(data);
return (float*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
if ((comp & 1) != 0)
n = comp;
else
n = comp - 1;
for (i = 0; i < x * y; ++i)
for (k = 0; k < n; ++k)
output[i * comp + k] =
(float)(CRuntime.pow(data[i * comp + k] / 255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
if (n < comp)
for (i = 0; i < x * y; ++i)
output[i * comp + n] = data[i * comp + n] / 255.0f;
CRuntime.free(data);
return output;
}
public static void* stbi__load_main(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri, int bpc)
{
CRuntime.memset(ri, 0, (ulong)sizeof(stbi__result_info));
ri->bits_per_channel = 8;
ri->channel_order = STBI_ORDER_RGB;
ri->num_channels = 0;
if (stbi__png_test(s) != 0)
return stbi__png_load(s, x, y, comp, req_comp, ri);
if (stbi__bmp_test(s) != 0)
return stbi__bmp_load(s, x, y, comp, req_comp, ri);
if (stbi__gif_test(s) != 0)
return stbi__gif_load(s, x, y, comp, req_comp, ri);
if (stbi__psd_test(s) != 0)
return stbi__psd_load(s, x, y, comp, req_comp, ri, bpc);
if (stbi__jpeg_test(s) != 0)
return stbi__jpeg_load(s, x, y, comp, req_comp, ri);
if (stbi__hdr_test(s) != 0)
{
var hdr = stbi__hdr_load(s, x, y, comp, req_comp, ri);
return stbi__hdr_to_ldr(hdr, *x, *y, req_comp != 0 ? req_comp : *comp);
}
if (stbi__tga_test(s) != 0)
return stbi__tga_load(s, x, y, comp, req_comp, ri);
return (byte*)(ulong)(stbi__err("unknown image type") != 0 ? 0 : 0);
}
public static byte* stbi__convert_16_to_8(ushort* orig, int w, int h, int channels)
{
var i = 0;
var img_len = w * h * channels;
byte* reduced;
reduced = (byte*)stbi__malloc((ulong)img_len);
if (reduced == null)
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
for (i = 0; i < img_len; ++i)
reduced[i] = (byte)((orig[i] >> 8) & 0xFF);
CRuntime.free(orig);
return reduced;
}
public static ushort* stbi__convert_8_to_16(byte* orig, int w, int h, int channels)
{
var i = 0;
var img_len = w * h * channels;
ushort* enlarged;
enlarged = (ushort*)stbi__malloc((ulong)(img_len * 2));
if (enlarged == null)
return (ushort*)(byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
for (i = 0; i < img_len; ++i)
enlarged[i] = (ushort)((orig[i] << 8) + orig[i]);
CRuntime.free(orig);
return enlarged;
}
public static void stbi__vertical_flip(void* image, int w, int h, int bytes_per_pixel)
{
var row = 0;
var bytes_per_row = w * bytes_per_pixel;
var temp = stackalloc byte[2048];
var bytes = (byte*)image;
for (row = 0; row < h >> 1; row++)
{
var row0 = bytes + row * bytes_per_row;
var row1 = bytes + (h - row - 1) * bytes_per_row;
var bytes_left = (ulong)bytes_per_row;
while (bytes_left != 0)
{
var bytes_copy = bytes_left < 2048 * sizeof(byte) ? bytes_left : 2048 * sizeof(byte);
CRuntime.memcpy(temp, row0, bytes_copy);
CRuntime.memcpy(row0, row1, bytes_copy);
CRuntime.memcpy(row1, temp, bytes_copy);
row0 += bytes_copy;
row1 += bytes_copy;
bytes_left -= bytes_copy;
}
}
}
public static void stbi__vertical_flip_slices(void* image, int w, int h, int z, int bytes_per_pixel)
{
var slice = 0;
var slice_size = w * h * bytes_per_pixel;
var bytes = (byte*)image;
for (slice = 0; slice < z; ++slice)
{
stbi__vertical_flip(bytes, w, h, bytes_per_pixel);
bytes += slice_size;
}
}
public static byte* stbi__load_and_postprocess_8bit(stbi__context s, int* x, int* y, int* comp, int req_comp)
{
var ri = new stbi__result_info();
var result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
if (result == null)
return null;
if (ri.bits_per_channel != 8)
{
result = stbi__convert_16_to_8((ushort*)result, *x, *y, req_comp == 0 ? *comp : req_comp);
ri.bits_per_channel = 8;
}
if ((stbi__vertically_flip_on_load_set != 0
? stbi__vertically_flip_on_load_local
: stbi__vertically_flip_on_load_global) != 0)
{
var channels = req_comp != 0 ? req_comp : *comp;
stbi__vertical_flip(result, *x, *y, channels * sizeof(byte));
}
return (byte*)result;
}
public static ushort* stbi__load_and_postprocess_16bit(stbi__context s, int* x, int* y, int* comp, int req_comp)
{
var ri = new stbi__result_info();
var result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
if (result == null)
return null;
if (ri.bits_per_channel != 16)
{
result = stbi__convert_8_to_16((byte*)result, *x, *y, req_comp == 0 ? *comp : req_comp);
ri.bits_per_channel = 16;
}
if ((stbi__vertically_flip_on_load_set != 0
? stbi__vertically_flip_on_load_local
: stbi__vertically_flip_on_load_global) != 0)
{
var channels = req_comp != 0 ? req_comp : *comp;
stbi__vertical_flip(result, *x, *y, channels * sizeof(ushort));
}
return (ushort*)result;
}
public static void stbi__float_postprocess(float* result, int* x, int* y, int* comp, int req_comp)
{
if ((stbi__vertically_flip_on_load_set != 0
? stbi__vertically_flip_on_load_local
: stbi__vertically_flip_on_load_global) != 0 && result != null)
{
var channels = req_comp != 0 ? req_comp : *comp;
stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
}
}
public static float* stbi__loadf_main(stbi__context s, int* x, int* y, int* comp, int req_comp)
{
byte* data;
if (stbi__hdr_test(s) != 0)
{
var ri = new stbi__result_info();
var hdr_data = stbi__hdr_load(s, x, y, comp, req_comp, &ri);
if (hdr_data != null)
stbi__float_postprocess(hdr_data, x, y, comp, req_comp);
return hdr_data;
}
data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);
if (data != null)
return stbi__ldr_to_hdr(data, *x, *y, req_comp != 0 ? req_comp : *comp);
return (float*)(ulong)(stbi__err("unknown image type") != 0 ? 0 : 0);
}
public static int stbi__get16be(stbi__context s)
{
int z = stbi__get8(s);
return (z << 8) + stbi__get8(s);
}
public static uint stbi__get32be(stbi__context s)
{
var z = (uint)stbi__get16be(s);
return (uint)((z << 16) + stbi__get16be(s));
}
public static int stbi__get16le(stbi__context s)
{
int z = stbi__get8(s);
return z + (stbi__get8(s) << 8);
}
public static uint stbi__get32le(stbi__context s)
{
var z = (uint)stbi__get16le(s);
z += (uint)stbi__get16le(s) << 16;
return z;
}
public static byte stbi__compute_y(int r, int g, int b)
{
return (byte)((r * 77 + g * 150 + 29 * b) >> 8);
}
public static byte* stbi__convert_format(byte* data, int img_n, int req_comp, uint x, uint y)
{
var i = 0;
var j = 0;
byte* good;
if (req_comp == img_n)
return data;
good = (byte*)stbi__malloc_mad3(req_comp, (int)x, (int)y, 0);
if (good == null)
{
CRuntime.free(data);
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
for (j = 0; j < (int)y; ++j)
{
var src = data + j * x * img_n;
var dest = good + j * x * req_comp;
switch (img_n * 8 + req_comp)
{
case 1 * 8 + 2:
for (i = (int)(x - 1); i >= 0; --i, src += 1, dest += 2)
{
dest[0] = src[0];
dest[1] = 255;
}
break;
case 1 * 8 + 3:
for (i = (int)(x - 1); i >= 0; --i, src += 1, dest += 3)
dest[0] = dest[1] = dest[2] = src[0];
break;
case 1 * 8 + 4:
for (i = (int)(x - 1); i >= 0; --i, src += 1, dest += 4)
{
dest[0] = dest[1] = dest[2] = src[0];
dest[3] = 255;
}
break;
case 2 * 8 + 1:
for (i = (int)(x - 1); i >= 0; --i, src += 2, dest += 1)
dest[0] = src[0];
break;
case 2 * 8 + 3:
for (i = (int)(x - 1); i >= 0; --i, src += 2, dest += 3)
dest[0] = dest[1] = dest[2] = src[0];
break;
case 2 * 8 + 4:
for (i = (int)(x - 1); i >= 0; --i, src += 2, dest += 4)
{
dest[0] = dest[1] = dest[2] = src[0];
dest[3] = src[1];
}
break;
case 3 * 8 + 4:
for (i = (int)(x - 1); i >= 0; --i, src += 3, dest += 4)
{
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
dest[3] = 255;
}
break;
case 3 * 8 + 1:
for (i = (int)(x - 1); i >= 0; --i, src += 3, dest += 1)
dest[0] = stbi__compute_y(src[0], src[1], src[2]);
break;
case 3 * 8 + 2:
for (i = (int)(x - 1); i >= 0; --i, src += 3, dest += 2)
{
dest[0] = stbi__compute_y(src[0], src[1], src[2]);
dest[1] = 255;
}
break;
case 4 * 8 + 1:
for (i = (int)(x - 1); i >= 0; --i, src += 4, dest += 1)
dest[0] = stbi__compute_y(src[0], src[1], src[2]);
break;
case 4 * 8 + 2:
for (i = (int)(x - 1); i >= 0; --i, src += 4, dest += 2)
{
dest[0] = stbi__compute_y(src[0], src[1], src[2]);
dest[1] = src[3];
}
break;
case 4 * 8 + 3:
for (i = (int)(x - 1); i >= 0; --i, src += 4, dest += 3)
{
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
}
break;
default:
;
CRuntime.free(data);
CRuntime.free(good);
return (byte*)(ulong)(stbi__err("unsupported") != 0 ? 0 : 0);
}
}
CRuntime.free(data);
return good;
}
public static ushort stbi__compute_y_16(int r, int g, int b)
{
return (ushort)((r * 77 + g * 150 + 29 * b) >> 8);
}
public static ushort* stbi__convert_format16(ushort* data, int img_n, int req_comp, uint x, uint y)
{
var i = 0;
var j = 0;
ushort* good;
if (req_comp == img_n)
return data;
good = (ushort*)stbi__malloc((ulong)(req_comp * x * y * 2));
if (good == null)
{
CRuntime.free(data);
return (ushort*)(byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
for (j = 0; j < (int)y; ++j)
{
var src = data + j * x * img_n;
var dest = good + j * x * req_comp;
switch (img_n * 8 + req_comp)
{
case 1 * 8 + 2:
for (i = (int)(x - 1); i >= 0; --i, src += 1, dest += 2)
{
dest[0] = src[0];
dest[1] = 0xffff;
}
break;
case 1 * 8 + 3:
for (i = (int)(x - 1); i >= 0; --i, src += 1, dest += 3)
dest[0] = dest[1] = dest[2] = src[0];
break;
case 1 * 8 + 4:
for (i = (int)(x - 1); i >= 0; --i, src += 1, dest += 4)
{
dest[0] = dest[1] = dest[2] = src[0];
dest[3] = 0xffff;
}
break;
case 2 * 8 + 1:
for (i = (int)(x - 1); i >= 0; --i, src += 2, dest += 1)
dest[0] = src[0];
break;
case 2 * 8 + 3:
for (i = (int)(x - 1); i >= 0; --i, src += 2, dest += 3)
dest[0] = dest[1] = dest[2] = src[0];
break;
case 2 * 8 + 4:
for (i = (int)(x - 1); i >= 0; --i, src += 2, dest += 4)
{
dest[0] = dest[1] = dest[2] = src[0];
dest[3] = src[1];
}
break;
case 3 * 8 + 4:
for (i = (int)(x - 1); i >= 0; --i, src += 3, dest += 4)
{
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
dest[3] = 0xffff;
}
break;
case 3 * 8 + 1:
for (i = (int)(x - 1); i >= 0; --i, src += 3, dest += 1)
dest[0] = stbi__compute_y_16(src[0], src[1], src[2]);
break;
case 3 * 8 + 2:
for (i = (int)(x - 1); i >= 0; --i, src += 3, dest += 2)
{
dest[0] = stbi__compute_y_16(src[0], src[1], src[2]);
dest[1] = 0xffff;
}
break;
case 4 * 8 + 1:
for (i = (int)(x - 1); i >= 0; --i, src += 4, dest += 1)
dest[0] = stbi__compute_y_16(src[0], src[1], src[2]);
break;
case 4 * 8 + 2:
for (i = (int)(x - 1); i >= 0; --i, src += 4, dest += 2)
{
dest[0] = stbi__compute_y_16(src[0], src[1], src[2]);
dest[1] = src[3];
}
break;
case 4 * 8 + 3:
for (i = (int)(x - 1); i >= 0; --i, src += 4, dest += 3)
{
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
}
break;
default:
;
CRuntime.free(data);
CRuntime.free(good);
return (ushort*)(byte*)(ulong)(stbi__err("unsupported") != 0 ? 0 : 0);
}
}
CRuntime.free(data);
return good;
}
public static byte stbi__clamp(int x)
{
if ((uint)x > 255)
{
if (x < 0)
return 0;
if (x > 255)
return 255;
}
return (byte)x;
}
public static byte stbi__blinn_8x8(byte x, byte y)
{
var t = (uint)(x * y + 128);
return (byte)((t + (t >> 8)) >> 8);
}
public static int stbi__bitreverse16(int n)
{
n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
return n;
}
public static int stbi__bit_reverse(int v, int bits)
{
return stbi__bitreverse16(v) >> (16 - bits);
}
public static int stbi__high_bit(uint z)
{
var n = 0;
if (z == 0)
return -1;
if (z >= 0x10000)
{
n += 16;
z >>= 16;
}
if (z >= 0x00100)
{
n += 8;
z >>= 8;
}
if (z >= 0x00010)
{
n += 4;
z >>= 4;
}
if (z >= 0x00004)
{
n += 2;
z >>= 2;
}
if (z >= 0x00002)
n += 1;
return n;
}
public static int stbi__bitcount(uint a)
{
a = (a & 0x55555555) + ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
a = (a + (a >> 4)) & 0x0f0f0f0f;
a = a + (a >> 8);
a = a + (a >> 16);
return (int)(a & 0xff);
}
public static int stbi__shiftsigned(uint v, int shift, int bits)
{
if (shift < 0)
v <<= -shift;
else
v >>= shift;
v >>= 8 - bits;
return (int)(v * stbi__shiftsigned_mul_table[bits]) >> stbi__shiftsigned_shift_table[bits];
}
public static int stbi__info_main(stbi__context s, int* x, int* y, int* comp)
{
if (stbi__jpeg_info(s, x, y, comp) != 0)
return 1;
if (stbi__png_info(s, x, y, comp) != 0)
return 1;
if (stbi__gif_info(s, x, y, comp) != 0)
return 1;
if (stbi__bmp_info(s, x, y, comp) != 0)
return 1;
if (stbi__psd_info(s, x, y, comp) != 0)
return 1;
if (stbi__hdr_info(s, x, y, comp) != 0)
return 1;
if (stbi__tga_info(s, x, y, comp) != 0)
return 1;
return stbi__err("unknown image type");
}
public static int stbi__is_16_main(stbi__context s)
{
if (stbi__png_is16(s) != 0)
return 1;
if (stbi__psd_is16(s) != 0)
return 1;
return 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct stbi__result_info
{
public int bits_per_channel;
public int num_channels;
public int channel_order;
}
}
}

View File

@@ -0,0 +1,545 @@
// Generated by Sichem at 12/24/2021 8:28:15 PM
using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image.Runtime;
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public static int stbi__gif_test(stbi__context s)
{
var r = stbi__gif_test_raw(s);
stbi__rewind(s);
return r;
}
public static void* stbi__gif_load(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri)
{
byte* u = null;
var g = new stbi__gif();
u = stbi__gif_load_next(s, g, comp, req_comp, null);
if (u != null)
{
*x = g.w;
*y = g.h;
if (req_comp != 0 && req_comp != 4)
u = stbi__convert_format(u, 4, req_comp, (uint)g.w, (uint)g.h);
}
else if (g._out_ != null)
{
CRuntime.free(g._out_);
}
CRuntime.free(g.history);
CRuntime.free(g.background);
return u;
}
public static void* stbi__load_gif_main(stbi__context s, int** delays, int* x, int* y, int* z, int* comp,
int req_comp)
{
if (stbi__gif_test(s) != 0)
{
var layers = 0;
byte* u = null;
byte* _out_ = null;
byte* two_back = null;
var g = new stbi__gif();
var stride = 0;
var out_size = 0;
var delays_size = 0;
if (delays != null)
*delays = null;
do
{
u = stbi__gif_load_next(s, g, comp, req_comp, two_back);
if (u != null)
{
*x = g.w;
*y = g.h;
++layers;
stride = g.w * g.h * 4;
if (_out_ != null)
{
void* tmp = (byte*)CRuntime.realloc(_out_, (ulong)(layers * stride));
if (tmp == null)
return stbi__load_gif_main_outofmem(g, _out_, delays);
_out_ = (byte*)tmp;
out_size = layers * stride;
if (delays != null)
{
var new_delays = (int*)CRuntime.realloc(*delays, (ulong)(sizeof(int) * layers));
if (new_delays == null)
return stbi__load_gif_main_outofmem(g, _out_, delays);
*delays = new_delays;
delays_size = layers * sizeof(int);
}
}
else
{
_out_ = (byte*)stbi__malloc((ulong)(layers * stride));
if (_out_ == null)
return stbi__load_gif_main_outofmem(g, _out_, delays);
out_size = layers * stride;
if (delays != null)
{
*delays = (int*)stbi__malloc((ulong)(layers * sizeof(int)));
if (*delays == null)
return stbi__load_gif_main_outofmem(g, _out_, delays);
delays_size = layers * sizeof(int);
}
}
CRuntime.memcpy(_out_ + (layers - 1) * stride, u, (ulong)stride);
if (layers >= 2)
two_back = _out_ - 2 * stride;
if (delays != null)
(*delays)[layers - 1U] = g.delay;
}
} while (u != null);
CRuntime.free(g._out_);
CRuntime.free(g.history);
CRuntime.free(g.background);
if (req_comp != 0 && req_comp != 4)
_out_ = stbi__convert_format(_out_, 4, req_comp, (uint)(layers * g.w), (uint)g.h);
*z = layers;
return _out_;
}
return (byte*)(ulong)(stbi__err("not GIF") != 0 ? 0 : 0);
}
public static int stbi__gif_info(stbi__context s, int* x, int* y, int* comp)
{
return stbi__gif_info_raw(s, x, y, comp);
}
public static int stbi__gif_test_raw(stbi__context s)
{
var sz = 0;
if (stbi__get8(s) != 71 || stbi__get8(s) != 73 || stbi__get8(s) != 70 || stbi__get8(s) != 56)
return 0;
sz = stbi__get8(s);
if (sz != 57 && sz != 55)
return 0;
if (stbi__get8(s) != 97)
return 0;
return 1;
}
public static void stbi__gif_parse_colortable(stbi__context s, byte[][] pal, int num_entries, int transp)
{
var i = 0;
for (i = 0; i < num_entries; ++i)
{
pal[i][2] = stbi__get8(s);
pal[i][1] = stbi__get8(s);
pal[i][0] = stbi__get8(s);
pal[i][3] = (byte)(transp == i ? 0 : 255);
}
}
public static int stbi__gif_header(stbi__context s, stbi__gif g, int* comp, int is_info)
{
byte version = 0;
if (stbi__get8(s) != 71 || stbi__get8(s) != 73 || stbi__get8(s) != 70 || stbi__get8(s) != 56)
return stbi__err("not GIF");
version = stbi__get8(s);
if (version != 55 && version != 57)
return stbi__err("not GIF");
if (stbi__get8(s) != 97)
return stbi__err("not GIF");
stbi__g_failure_reason = "";
g.w = stbi__get16le(s);
g.h = stbi__get16le(s);
g.flags = stbi__get8(s);
g.bgindex = stbi__get8(s);
g.ratio = stbi__get8(s);
g.transparent = -1;
if (g.w > 1 << 24)
return stbi__err("too large");
if (g.h > 1 << 24)
return stbi__err("too large");
if (comp != null)
*comp = 4;
if (is_info != 0)
return 1;
if ((g.flags & 0x80) != 0)
stbi__gif_parse_colortable(s, g.pal, 2 << (g.flags & 7), -1);
return 1;
}
public static int stbi__gif_info_raw(stbi__context s, int* x, int* y, int* comp)
{
var g = new stbi__gif();
if (g == null)
return stbi__err("outofmem");
if (stbi__gif_header(s, g, comp, 1) == 0)
{
stbi__rewind(s);
return 0;
}
if (x != null)
*x = g.w;
if (y != null)
*y = g.h;
return 1;
}
public static void stbi__out_gif_code(stbi__gif g, ushort code)
{
byte* p;
var idx = 0;
if (g.codes[code].prefix >= 0)
stbi__out_gif_code(g, (ushort)g.codes[code].prefix);
if (g.cur_y >= g.max_y)
return;
idx = g.cur_x + g.cur_y;
p = &g._out_[idx];
g.history[idx / 4] = 1;
var c = g.color_table[g.codes[code].suffix];
if (c[3] > 128)
{
p[0] = c[2];
p[1] = c[1];
p[2] = c[0];
p[3] = c[3];
}
g.cur_x += 4;
if (g.cur_x >= g.max_x)
{
g.cur_x = g.start_x;
g.cur_y += g.step;
while (g.cur_y >= g.max_y && g.parse > 0)
{
g.step = (1 << g.parse) * g.line_size;
g.cur_y = g.start_y + (g.step >> 1);
--g.parse;
}
}
}
public static byte* stbi__process_gif_raster(stbi__context s, stbi__gif g)
{
byte lzw_cs = 0;
var len = 0;
var init_code = 0;
uint first = 0;
var codesize = 0;
var codemask = 0;
var avail = 0;
var oldcode = 0;
var bits = 0;
var valid_bits = 0;
var clear = 0;
stbi__gif_lzw* p;
lzw_cs = stbi__get8(s);
if (lzw_cs > 12)
return null;
clear = 1 << lzw_cs;
first = 1;
codesize = lzw_cs + 1;
codemask = (1 << codesize) - 1;
bits = 0;
valid_bits = 0;
for (init_code = 0; init_code < clear; init_code++)
{
g.codes[init_code].prefix = -1;
g.codes[init_code].first = (byte)init_code;
g.codes[init_code].suffix = (byte)init_code;
}
avail = clear + 2;
oldcode = -1;
len = 0;
for (; ; )
if (valid_bits < codesize)
{
if (len == 0)
{
len = stbi__get8(s);
if (len == 0)
return g._out_;
}
--len;
bits |= stbi__get8(s) << valid_bits;
valid_bits += 8;
}
else
{
var code = bits & codemask;
bits >>= codesize;
valid_bits -= codesize;
if (code == clear)
{
codesize = lzw_cs + 1;
codemask = (1 << codesize) - 1;
avail = clear + 2;
oldcode = -1;
first = 0;
}
else if (code == clear + 1)
{
stbi__skip(s, len);
while ((len = stbi__get8(s)) > 0)
stbi__skip(s, len);
return g._out_;
}
else if (code <= avail)
{
if (first != 0)
return (byte*)(ulong)(stbi__err("no clear code") != 0 ? 0 : 0);
if (oldcode >= 0)
{
fixed (stbi__gif_lzw *p2 = &g.codes[avail++])
{
p = p2;
if (avail > 8192)
return (byte*)(ulong)(stbi__err("too many codes") != 0 ? 0 : 0);
p->prefix = (short)oldcode;
p->first = g.codes[oldcode].first;
p->suffix = code == avail ? p->first : g.codes[code].first;
}
}
else if (code == avail)
{
return (byte*)(ulong)(stbi__err("illegal code in raster") != 0 ? 0 : 0);
}
stbi__out_gif_code(g, (ushort)code);
if ((avail & codemask) == 0 && avail <= 0x0FFF)
{
codesize++;
codemask = (1 << codesize) - 1;
}
oldcode = code;
}
else
{
return (byte*)(ulong)(stbi__err("illegal code in raster") != 0 ? 0 : 0);
}
}
}
public static byte* stbi__gif_load_next(stbi__context s, stbi__gif g, int* comp, int req_comp, byte* two_back)
{
var dispose = 0;
var first_frame = 0;
var pi = 0;
var pcount = 0;
first_frame = 0;
if (g._out_ == null)
{
if (stbi__gif_header(s, g, comp, 0) == 0)
return null;
if (stbi__mad3sizes_valid(4, g.w, g.h, 0) == 0)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
pcount = g.w * g.h;
g._out_ = (byte*)stbi__malloc((ulong)(4 * pcount));
g.background = (byte*)stbi__malloc((ulong)(4 * pcount));
g.history = (byte*)stbi__malloc((ulong)pcount);
if (g._out_ == null || g.background == null || g.history == null)
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
CRuntime.memset(g._out_, 0x00, (ulong)(4 * pcount));
CRuntime.memset(g.background, 0x00, (ulong)(4 * pcount));
CRuntime.memset(g.history, 0x00, (ulong)pcount);
first_frame = 1;
}
else
{
dispose = (g.eflags & 0x1C) >> 2;
pcount = g.w * g.h;
if (dispose == 3 && two_back == null)
dispose = 2;
if (dispose == 3)
{
for (pi = 0; pi < pcount; ++pi)
if (g.history[pi] != 0)
CRuntime.memcpy(&g._out_[pi * 4], &two_back[pi * 4], (ulong)4);
}
else if (dispose == 2)
{
for (pi = 0; pi < pcount; ++pi)
if (g.history[pi] != 0)
CRuntime.memcpy(&g._out_[pi * 4], &g.background[pi * 4], (ulong)4);
}
CRuntime.memcpy(g.background, g._out_, (ulong)(4 * g.w * g.h));
}
CRuntime.memset(g.history, 0x00, (ulong)(g.w * g.h));
for (; ; )
{
int tag = stbi__get8(s);
switch (tag)
{
case 0x2C:
{
var x = 0;
var y = 0;
var w = 0;
var h = 0;
byte* o;
x = stbi__get16le(s);
y = stbi__get16le(s);
w = stbi__get16le(s);
h = stbi__get16le(s);
if (x + w > g.w || y + h > g.h)
return (byte*)(ulong)(stbi__err("bad Image Descriptor") != 0 ? 0 : 0);
g.line_size = g.w * 4;
g.start_x = x * 4;
g.start_y = y * g.line_size;
g.max_x = g.start_x + w * 4;
g.max_y = g.start_y + h * g.line_size;
g.cur_x = g.start_x;
g.cur_y = g.start_y;
if (w == 0)
g.cur_y = g.max_y;
g.lflags = stbi__get8(s);
if ((g.lflags & 0x40) != 0)
{
g.step = 8 * g.line_size;
g.parse = 3;
}
else
{
g.step = g.line_size;
g.parse = 0;
}
if ((g.lflags & 0x80) != 0)
{
stbi__gif_parse_colortable(s, g.lpal, 2 << (g.lflags & 7),
(g.eflags & 0x01) != 0 ? g.transparent : -1);
g.color_table = g.lpal;
}
else if ((g.flags & 0x80) != 0)
{
g.color_table = g.pal;
}
else
{
return (byte*)(ulong)(stbi__err("missing color table") != 0 ? 0 : 0);
}
o = stbi__process_gif_raster(s, g);
if (o == null)
return null;
pcount = g.w * g.h;
if (first_frame != 0 && g.bgindex > 0)
for (pi = 0; pi < pcount; ++pi)
if (g.history[pi] == 0)
{
g.pal[g.bgindex][3] = 255;
fixed (byte* ptr = &g.pal[g.bgindex][0])
{
CRuntime.memcpy(&g._out_[pi * 4], ptr, (ulong)4);
}
}
return o;
}
case 0x21:
{
var len = 0;
int ext = stbi__get8(s);
if (ext == 0xF9)
{
len = stbi__get8(s);
if (len == 4)
{
g.eflags = stbi__get8(s);
g.delay = 10 * stbi__get16le(s);
if (g.transparent >= 0)
g.pal[g.transparent][3] = 255;
if ((g.eflags & 0x01) != 0)
{
g.transparent = stbi__get8(s);
if (g.transparent >= 0)
g.pal[g.transparent][3] = 0;
}
else
{
stbi__skip(s, 1);
g.transparent = -1;
}
}
else
{
stbi__skip(s, len);
break;
}
}
while ((len = stbi__get8(s)) != 0)
stbi__skip(s, len);
break;
}
case 0x3B:
return null;
default:
return (byte*)(ulong)(stbi__err("unknown code") != 0 ? 0 : 0);
}
}
}
public static void* stbi__load_gif_main_outofmem(stbi__gif g, byte* _out_, int** delays)
{
CRuntime.free(g._out_);
CRuntime.free(g.history);
CRuntime.free(g.background);
if (_out_ != null)
CRuntime.free(_out_);
if (delays != null && *delays != null)
CRuntime.free(*delays);
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
public class stbi__gif
{
public byte* _out_;
public byte* background;
public int bgindex;
public stbi__gif_lzw[] codes = new stbi__gif_lzw[8192];
public byte[][] color_table;
public int cur_x;
public int cur_y;
public int delay;
public int eflags;
public int flags;
public int h;
public byte* history;
public int lflags;
public int line_size;
public byte[][] lpal = Utility.CreateArray<byte>(256, 4);
public int max_x;
public int max_y;
public byte[][] pal = Utility.CreateArray<byte>(256, 4);
public int parse;
public int ratio;
public int start_x;
public int start_y;
public int step;
public int transparent;
public int w;
}
[StructLayout(LayoutKind.Sequential)]
public struct stbi__gif_lzw
{
public short prefix;
public byte first;
public byte suffix;
}
}
}

View File

@@ -0,0 +1,388 @@
// Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image.Runtime;
// Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public static int stbi__hdr_test(stbi__context s)
{
var r = stbi__hdr_test_core(s, "#?RADIANCE\n");
stbi__rewind(s);
if (r == 0)
{
r = stbi__hdr_test_core(s, "#?RGBE\n");
stbi__rewind(s);
}
return r;
}
public static float* stbi__hdr_load(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri)
{
var rgbe = stackalloc byte[4];
var buffer = stackalloc sbyte[1024];
sbyte* token;
var valid = 0;
var width = 0;
var height = 0;
byte* scanline;
float* hdr_data;
var len = 0;
byte count = 0;
byte value = 0;
var i = 0;
var j = 0;
var k = 0;
var c1 = 0;
var c2 = 0;
var z = 0;
sbyte* headerToken;
headerToken = stbi__hdr_gettoken(s, buffer);
if (CRuntime.strcmp(headerToken, "#?RADIANCE") != 0 && CRuntime.strcmp(headerToken, "#?RGBE") != 0)
return (float*)(ulong)(stbi__err("not HDR") != 0 ? 0 : 0);
for (; ; )
{
token = stbi__hdr_gettoken(s, buffer);
if (token[0] == 0)
break;
if (CRuntime.strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0)
valid = 1;
}
if (valid == 0)
return (float*)(ulong)(stbi__err("unsupported format") != 0 ? 0 : 0);
token = stbi__hdr_gettoken(s, buffer);
if (CRuntime.strncmp(token, "-Y ", 3) != 0)
return (float*)(ulong)(stbi__err("unsupported data layout") != 0 ? 0 : 0);
token += 3;
height = (int)CRuntime.strtol(token, &token, 10);
while (*token == 32)
++token;
if (CRuntime.strncmp(token, "+X ", 3) != 0)
return (float*)(ulong)(stbi__err("unsupported data layout") != 0 ? 0 : 0);
token += 3;
width = (int)CRuntime.strtol(token, null, 10);
if (height > 1 << 24)
return (float*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
if (width > 1 << 24)
return (float*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
*x = width;
*y = height;
if (comp != null)
*comp = 3;
if (req_comp == 0)
req_comp = 3;
if (stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0) == 0)
return (float*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
hdr_data = (float*)stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);
if (hdr_data == null)
return (float*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
if (width < 8 || width >= 32768)
{
for (; j < height; ++j)
{
for (; i < width; ++i)
{
//var rgbe = stackalloc byte[4];
stbi__getn(s, rgbe, 4);
stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
}
}
}
else
{
scanline = null;
for (j = 0; j < height; ++j)
{
c1 = stbi__get8(s);
c2 = stbi__get8(s);
len = stbi__get8(s);
if (c1 != 2 || c2 != 2 || (len & 0x80) != 0)
{
//var rgbe = stackalloc byte[4];
rgbe[0] = (byte)c1;
rgbe[1] = (byte)c2;
rgbe[2] = (byte)len;
rgbe[3] = stbi__get8(s);
stbi__hdr_convert(hdr_data, rgbe, req_comp);
CRuntime.free(scanline);
// goto main_decode_loop
// Do first row
j = 0;
for (i = 1; i < width; ++i)
{
//var rgbe = stackalloc byte[4];
stbi__getn(s, rgbe, 4);
stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
}
// Do the rest of the rows.
for (j = 1; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
//var rgbe = stackalloc byte[4];
stbi__getn(s, rgbe, 4);
stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
}
}
goto finish;
}
len <<= 8;
len |= stbi__get8(s);
if (len != width)
{
CRuntime.free(hdr_data);
CRuntime.free(scanline);
return (float*)(ulong)(stbi__err("invalid decoded scanline length") != 0 ? 0 : 0);
}
if (scanline == null)
{
scanline = (byte*)stbi__malloc_mad2(width, 4, 0);
if (scanline == null)
{
CRuntime.free(hdr_data);
return (float*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
}
for (k = 0; k < 4; ++k)
{
var nleft = 0;
i = 0;
while ((nleft = width - i) > 0)
{
count = stbi__get8(s);
if (count > 128)
{
value = stbi__get8(s);
count -= 128;
if (count == 0 || count > nleft)
{
CRuntime.free(hdr_data);
CRuntime.free(scanline);
return (float*)(ulong)(stbi__err("corrupt") != 0 ? 0 : 0);
}
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = value;
}
else
{
if (count == 0 || count > nleft)
{
CRuntime.free(hdr_data);
CRuntime.free(scanline);
return (float*)(ulong)(stbi__err("corrupt") != 0 ? 0 : 0);
}
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = stbi__get8(s);
}
}
}
for (i = 0; i < width; ++i)
stbi__hdr_convert(hdr_data + (j * width + i) * req_comp, scanline + i * 4, req_comp);
}
if (scanline != null)
CRuntime.free(scanline);
}
finish:
return hdr_data;
}
public static int stbi__hdr_info(stbi__context s, int* x, int* y, int* comp)
{
var buffer = stackalloc sbyte[1024];
sbyte* token;
var valid = 0;
var dummy = 0;
if (x == null)
x = &dummy;
if (y == null)
y = &dummy;
if (comp == null)
comp = &dummy;
if (stbi__hdr_test(s) == 0)
{
stbi__rewind(s);
return 0;
}
for (; ; )
{
token = stbi__hdr_gettoken(s, buffer);
if (token[0] == 0)
break;
if (CRuntime.strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0)
valid = 1;
}
if (valid == 0)
{
stbi__rewind(s);
return 0;
}
token = stbi__hdr_gettoken(s, buffer);
if (CRuntime.strncmp(token, "-Y ", 3) != 0)
{
stbi__rewind(s);
return 0;
}
token += 3;
*y = (int)CRuntime.strtol(token, &token, 10);
while (*token == 32)
++token;
if (CRuntime.strncmp(token, "+X ", 3) != 0)
{
stbi__rewind(s);
return 0;
}
token += 3;
*x = (int)CRuntime.strtol(token, null, 10);
*comp = 3;
return 1;
}
public static byte* stbi__hdr_to_ldr(float* data, int x, int y, int comp)
{
var i = 0;
var k = 0;
var n = 0;
byte* output;
if (data == null)
return null;
output = (byte*)stbi__malloc_mad3(x, y, comp, 0);
if (output == null)
{
CRuntime.free(data);
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
if ((comp & 1) != 0)
n = comp;
else
n = comp - 1;
for (i = 0; i < x * y; ++i)
{
for (k = 0; k < n; ++k)
{
var z = (float)CRuntime.pow(data[i * comp + k] * stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 +
0.5f;
if (z < 0)
z = 0;
if (z > 255)
z = 255;
output[i * comp + k] = (byte)(int)z;
}
if (k < comp)
{
var z = data[i * comp + k] * 255 + 0.5f;
if (z < 0)
z = 0;
if (z > 255)
z = 255;
output[i * comp + k] = (byte)(int)z;
}
}
CRuntime.free(data);
return output;
}
public static int stbi__hdr_test_core(stbi__context s, string signature)
{
var i = 0;
for (i = 0; i < signature.Length; ++i)
if (stbi__get8(s) != signature[i])
return 0;
stbi__rewind(s);
return 1;
}
public static sbyte* stbi__hdr_gettoken(stbi__context z, sbyte* buffer)
{
var len = 0;
sbyte c = 0;
c = (sbyte)stbi__get8(z);
while (stbi__at_eof(z) == 0 && c != 10)
{
buffer[len++] = c;
if (len == 1024 - 1)
{
while (stbi__at_eof(z) == 0 && stbi__get8(z) != 10)
{
}
break;
}
c = (sbyte)stbi__get8(z);
}
buffer[len] = 0;
return buffer;
}
public static void stbi__hdr_convert(float* output, byte* input, int req_comp)
{
if (input[3] != 0)
{
float f1 = 0;
f1 = (float)CRuntime.ldexp(1.0f, input[3] - (128 + 8));
if (req_comp <= 2)
{
output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
}
else
{
output[0] = input[0] * f1;
output[1] = input[1] * f1;
output[2] = input[2] * f1;
}
if (req_comp == 2)
output[1] = 1;
if (req_comp == 4)
output[3] = 1;
}
else
{
switch (req_comp)
{
case 4:
case 3:
if (req_comp == 4)
output[3] = 1;
output[0] = output[1] = output[2] = 0;
break;
case 2:
case 1:
if (req_comp == 2)
output[1] = 1;
output[0] = 0;
break;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,818 @@
// Generated by Sichem at 9/16/2024 9:09:30 AM
using Misaki.HighPerformance.Image.Runtime;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public const int STBI__F_none = 0;
public const int STBI__F_sub = 1;
public const int STBI__F_up = 2;
public const int STBI__F_avg = 3;
public const int STBI__F_paeth = 4;
public const int STBI__F_avg_first = 5;
public static byte[] first_row_filter =
{ STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_sub };
public static byte[] stbi__check_png_header_png_sig = { 137, 80, 78, 71, 13, 10, 26, 10 };
public static byte[] stbi__depth_scale_table = { 0, 0xff, 0x55, 0, 0x11, 0, 0, 0, 0x01 };
public static int stbi__png_test(stbi__context s)
{
var r = 0;
r = stbi__check_png_header(s);
stbi__rewind(s);
return r;
}
public static void* stbi__png_load(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri)
{
var p = new stbi__png();
p.s = s;
return stbi__do_png(p, x, y, comp, req_comp, ri);
}
public static int stbi__png_info(stbi__context s, int* x, int* y, int* comp)
{
var p = new stbi__png();
p.s = s;
return stbi__png_info_raw(p, x, y, comp);
}
public static int stbi__png_is16(stbi__context s)
{
var p = new stbi__png();
p.s = s;
if (stbi__png_info_raw(p, null, null, null) == 0)
return 0;
if (p.depth != 16)
{
stbi__rewind(p.s);
return 0;
}
return 1;
}
public static stbi__pngchunk stbi__get_chunk_header(stbi__context s)
{
var c = new stbi__pngchunk();
c.length = stbi__get32be(s);
c.type = stbi__get32be(s);
return c;
}
public static int stbi__check_png_header(stbi__context s)
{
var i = 0;
for (i = 0; i < 8; ++i)
if (stbi__get8(s) != stbi__check_png_header_png_sig[i])
return stbi__err("bad png sig");
return 1;
}
public static int stbi__paeth(int a, int b, int c)
{
var thresh = c * 3 - (a + b);
var lo = a < b ? a : b;
var hi = a < b ? b : a;
var t0 = hi <= thresh ? lo : c;
var t1 = thresh <= lo ? hi : t0;
return t1;
}
public static void stbi__create_png_alpha_expand8(byte* dest, byte* src, uint x, int img_n)
{
var i = 0;
if (img_n == 1)
for (i = (int)(x - 1); i >= 0; --i)
{
dest[i * 2 + 1] = 255;
dest[i * 2 + 0] = src[i];
}
else
for (i = (int)(x - 1); i >= 0; --i)
{
dest[i * 4 + 3] = 255;
dest[i * 4 + 2] = src[i * 3 + 2];
dest[i * 4 + 1] = src[i * 3 + 1];
dest[i * 4 + 0] = src[i * 3 + 0];
}
}
public static int stbi__create_png_image_raw(stbi__png a, byte* raw, uint raw_len, int out_n, uint x, uint y,
int depth, int color)
{
var bytes = depth == 16 ? 2 : 1;
var s = a.s;
uint i = 0;
uint j = 0;
var stride = (uint)(x * out_n * bytes);
uint img_len = 0;
uint img_width_bytes = 0;
byte* filter_buf;
var all_ok = 1;
var k = 0;
var img_n = s.img_n;
var output_bytes = out_n * bytes;
var filter_bytes = img_n * bytes;
var width = (int)x;
a._out_ = (byte*)stbi__malloc_mad3((int)x, (int)y, output_bytes, 0);
if (a._out_ == null)
return stbi__err("outofmem");
if (stbi__mad3sizes_valid(img_n, (int)x, depth, 7) == 0)
return stbi__err("too large");
img_width_bytes = (uint)((img_n * x * depth + 7) >> 3);
if (stbi__mad2sizes_valid((int)img_width_bytes, (int)y, (int)img_width_bytes) == 0)
return stbi__err("too large");
img_len = (img_width_bytes + 1) * y;
if (raw_len < img_len)
return stbi__err("not enough pixels");
filter_buf = (byte*)stbi__malloc_mad2((int)img_width_bytes, 2, 0);
if (filter_buf == null)
return stbi__err("outofmem");
if (depth < 8)
{
filter_bytes = 1;
width = (int)img_width_bytes;
}
for (j = 0; j < y; ++j)
{
var cur = filter_buf + (j & 1) * img_width_bytes;
var prior = filter_buf + (~j & 1) * img_width_bytes;
var dest = a._out_ + stride * j;
var nk = width * filter_bytes;
int filter = *raw++;
if (filter > 4)
{
all_ok = stbi__err("invalid filter");
break;
}
if (j == 0)
filter = first_row_filter[filter];
switch (filter)
{
case STBI__F_none:
CRuntime.memcpy(cur, raw, (ulong)nk);
break;
case STBI__F_sub:
CRuntime.memcpy(cur, raw, (ulong)filter_bytes);
for (k = filter_bytes; k < nk; ++k)
cur[k] = (byte)((raw[k] + cur[k - filter_bytes]) & 255);
break;
case STBI__F_up:
for (k = 0; k < nk; ++k)
cur[k] = (byte)((raw[k] + prior[k]) & 255);
break;
case STBI__F_avg:
for (k = 0; k < filter_bytes; ++k)
cur[k] = (byte)((raw[k] + (prior[k] >> 1)) & 255);
for (k = filter_bytes; k < nk; ++k)
cur[k] = (byte)((raw[k] + ((prior[k] + cur[k - filter_bytes]) >> 1)) & 255);
break;
case STBI__F_paeth:
for (k = 0; k < filter_bytes; ++k)
cur[k] = (byte)((raw[k] + prior[k]) & 255);
for (k = filter_bytes; k < nk; ++k)
cur[k] = (byte)((raw[k] + stbi__paeth(cur[k - filter_bytes], prior[k],
prior[k - filter_bytes])) & 255);
break;
case STBI__F_avg_first:
CRuntime.memcpy(cur, raw, (ulong)filter_bytes);
for (k = filter_bytes; k < nk; ++k)
cur[k] = (byte)((raw[k] + (cur[k - filter_bytes] >> 1)) & 255);
break;
}
raw += nk;
if (depth < 8)
{
var scale = (byte)(color == 0 ? stbi__depth_scale_table[depth] : 1);
var _in_ = cur;
var _out_ = dest;
byte inb = 0;
var nsmp = (uint)(x * img_n);
if (depth == 4)
for (i = 0; i < nsmp; ++i)
{
if ((i & 1) == 0)
inb = *_in_++;
*_out_++ = (byte)(scale * (inb >> 4));
inb <<= 4;
}
else if (depth == 2)
for (i = 0; i < nsmp; ++i)
{
if ((i & 3) == 0)
inb = *_in_++;
*_out_++ = (byte)(scale * (inb >> 6));
inb <<= 2;
}
else
for (i = 0; i < nsmp; ++i)
{
if ((i & 7) == 0)
inb = *_in_++;
*_out_++ = (byte)(scale * (inb >> 7));
inb <<= 1;
}
if (img_n != out_n)
stbi__create_png_alpha_expand8(dest, dest, x, img_n);
}
else if (depth == 8)
{
if (img_n == out_n)
CRuntime.memcpy(dest, cur, (ulong)(x * img_n));
else
stbi__create_png_alpha_expand8(dest, cur, x, img_n);
}
else if (depth == 16)
{
var dest16 = (ushort*)dest;
var nsmp = (uint)(x * img_n);
if (img_n == out_n)
{
for (i = 0; i < nsmp; ++i, ++dest16, cur += 2)
*dest16 = (ushort)((cur[0] << 8) | cur[1]);
}
else
{
if (img_n == 1)
for (i = 0; i < x; ++i, dest16 += 2, cur += 2)
{
dest16[0] = (ushort)((cur[0] << 8) | cur[1]);
dest16[1] = 0xffff;
}
else
for (i = 0; i < x; ++i, dest16 += 4, cur += 6)
{
dest16[0] = (ushort)((cur[0] << 8) | cur[1]);
dest16[1] = (ushort)((cur[2] << 8) | cur[3]);
dest16[2] = (ushort)((cur[4] << 8) | cur[5]);
dest16[3] = 0xffff;
}
}
}
}
CRuntime.free(filter_buf);
if (all_ok == 0)
return 0;
return 1;
}
public static int stbi__create_png_image(stbi__png a, byte* image_data, uint image_data_len, int out_n,
int depth, int color, int interlaced)
{
var bytes = depth == 16 ? 2 : 1;
var out_bytes = out_n * bytes;
byte* final;
var p = 0;
if (interlaced == 0)
return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a.s.img_x, a.s.img_y, depth,
color);
final = (byte*)stbi__malloc_mad3((int)a.s.img_x, (int)a.s.img_y, out_bytes, 0);
if (final == null)
{
return stbi__err("outofmem");
}
var xorig = stackalloc int[] { 0, 4, 0, 2, 0, 1, 0 };
var yorig = stackalloc int[] { 0, 0, 4, 0, 2, 0, 1 };
var xspc = stackalloc int[] { 8, 8, 4, 4, 2, 2, 1 };
var yspc = stackalloc int[] { 8, 8, 8, 4, 4, 2, 2 };
for (p = 0; p < 7; ++p)
{
var i = 0;
var j = 0;
var x = 0;
var y = 0;
x = (int)((a.s.img_x - xorig[p] + xspc[p] - 1) / xspc[p]);
y = (int)((a.s.img_y - yorig[p] + yspc[p] - 1) / yspc[p]);
if (x != 0 && y != 0)
{
var img_len = (uint)((((a.s.img_n * x * depth + 7) >> 3) + 1) * y);
if (stbi__create_png_image_raw(a, image_data, image_data_len, out_n, (uint)x, (uint)y, depth,
color) == 0)
{
CRuntime.free(final);
return 0;
}
for (j = 0; j < y; ++j)
for (i = 0; i < x; ++i)
{
var out_y = j * yspc[p] + yorig[p];
var out_x = i * xspc[p] + xorig[p];
CRuntime.memcpy(final + out_y * a.s.img_x * out_bytes + out_x * out_bytes,
a._out_ + (j * x + i) * out_bytes, (ulong)out_bytes);
}
CRuntime.free(a._out_);
image_data += img_len;
image_data_len -= img_len;
}
}
a._out_ = final;
return 1;
}
public static int stbi__compute_transparency(stbi__png z, byte* tc, int out_n)
{
var s = z.s;
uint i = 0;
var pixel_count = s.img_x * s.img_y;
var p = z._out_;
if (out_n == 2)
for (i = 0; i < pixel_count; ++i)
{
p[1] = (byte)(p[0] == tc[0] ? 0 : 255);
p += 2;
}
else
for (i = 0; i < pixel_count; ++i)
{
if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
p[3] = 0;
p += 4;
}
return 1;
}
public static int stbi__compute_transparency16(stbi__png z, ushort* tc, int out_n)
{
var s = z.s;
uint i = 0;
var pixel_count = s.img_x * s.img_y;
var p = (ushort*)z._out_;
if (out_n == 2)
for (i = 0; i < pixel_count; ++i)
{
p[1] = (ushort)(p[0] == tc[0] ? 0 : 65535);
p += 2;
}
else
for (i = 0; i < pixel_count; ++i)
{
if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
p[3] = 0;
p += 4;
}
return 1;
}
public static int stbi__expand_png_palette(stbi__png a, byte* palette, int len, int pal_img_n)
{
uint i = 0;
var pixel_count = a.s.img_x * a.s.img_y;
byte* p;
byte* temp_out;
var orig = a._out_;
p = (byte*)stbi__malloc_mad2((int)pixel_count, pal_img_n, 0);
if (p == null)
return stbi__err("outofmem");
temp_out = p;
if (pal_img_n == 3)
for (i = 0; i < pixel_count; ++i)
{
var n = orig[i] * 4;
p[0] = palette[n];
p[1] = palette[n + 1];
p[2] = palette[n + 2];
p += 3;
}
else
for (i = 0; i < pixel_count; ++i)
{
var n = orig[i] * 4;
p[0] = palette[n];
p[1] = palette[n + 1];
p[2] = palette[n + 2];
p[3] = palette[n + 3];
p += 4;
}
CRuntime.free(a._out_);
a._out_ = temp_out;
return 1;
}
public static void stbi__de_iphone(stbi__png z)
{
var s = z.s;
uint i = 0;
var pixel_count = s.img_x * s.img_y;
var p = z._out_;
if (s.img_out_n == 3)
{
for (i = 0; i < pixel_count; ++i)
{
var t = p[0];
p[0] = p[2];
p[2] = t;
p += 3;
}
}
else
{
if ((stbi__unpremultiply_on_load_set != 0
? stbi__unpremultiply_on_load_local
: stbi__unpremultiply_on_load_global) != 0)
for (i = 0; i < pixel_count; ++i)
{
var a = p[3];
var t = p[0];
if (a != 0)
{
var half = (byte)(a / 2);
p[0] = (byte)((p[2] * 255 + half) / a);
p[1] = (byte)((p[1] * 255 + half) / a);
p[2] = (byte)((t * 255 + half) / a);
}
else
{
p[0] = p[2];
p[2] = t;
}
p += 4;
}
else
for (i = 0; i < pixel_count; ++i)
{
var t = p[0];
p[0] = p[2];
p[2] = t;
p += 4;
}
}
}
public static int stbi__parse_png_file(stbi__png z, int scan, int req_comp)
{
var palette = stackalloc byte[1024];
byte pal_img_n = 0;
byte has_trans = 0;
var tc = stackalloc byte[] { 0, 0, 0 };
var tc16 = stackalloc ushort[3];
uint ioff = 0;
uint idata_limit = 0;
uint i = 0;
uint pal_len = 0;
var first = 1;
var k = 0;
var interlace = 0;
var color = 0;
var is_iphone = 0;
var s = z.s;
z.expanded = null;
z.idata = null;
z._out_ = null;
if (stbi__check_png_header(s) == 0)
return 0;
if (scan == STBI__SCAN_type)
return 1;
for (; ; )
{
var c = stbi__get_chunk_header(s);
switch (c.type)
{
case ((uint)67 << 24) + ((uint)103 << 16) + ((uint)66 << 8) + 73:
is_iphone = 1;
stbi__skip(s, (int)c.length);
break;
case ((uint)73 << 24) + ((uint)72 << 16) + ((uint)68 << 8) + 82:
{
var comp = 0;
var filter = 0;
if (first == 0)
return stbi__err("multiple IHDR");
first = 0;
if (c.length != 13)
return stbi__err("bad IHDR len");
s.img_x = stbi__get32be(s);
s.img_y = stbi__get32be(s);
if (s.img_y > 1 << 24)
return stbi__err("too large");
if (s.img_x > 1 << 24)
return stbi__err("too large");
z.depth = stbi__get8(s);
if (z.depth != 1 && z.depth != 2 && z.depth != 4 && z.depth != 8 && z.depth != 16)
return stbi__err("1/2/4/8/16-bit only");
color = stbi__get8(s);
if (color > 6)
return stbi__err("bad ctype");
if (color == 3 && z.depth == 16)
return stbi__err("bad ctype");
if (color == 3)
pal_img_n = 3;
else if ((color & 1) != 0)
return stbi__err("bad ctype");
comp = stbi__get8(s);
if (comp != 0)
return stbi__err("bad comp method");
filter = stbi__get8(s);
if (filter != 0)
return stbi__err("bad filter method");
interlace = stbi__get8(s);
if (interlace > 1)
return stbi__err("bad interlace method");
if (s.img_x == 0 || s.img_y == 0)
return stbi__err("0-pixel image");
if (pal_img_n == 0)
{
s.img_n = ((color & 2) != 0 ? 3 : 1) + ((color & 4) != 0 ? 1 : 0);
if ((1 << 30) / s.img_x / s.img_n < s.img_y)
return stbi__err("too large");
}
else
{
s.img_n = 1;
if ((1 << 30) / s.img_x / 4 < s.img_y)
return stbi__err("too large");
}
break;
}
case ((uint)80 << 24) + ((uint)76 << 16) + ((uint)84 << 8) + 69:
{
if (first != 0)
return stbi__err("first not IHDR");
if (c.length > 256 * 3)
return stbi__err("invalid PLTE");
pal_len = c.length / 3;
if (pal_len * 3 != c.length)
return stbi__err("invalid PLTE");
for (i = 0; i < pal_len; ++i)
{
palette[i * 4 + 0] = stbi__get8(s);
palette[i * 4 + 1] = stbi__get8(s);
palette[i * 4 + 2] = stbi__get8(s);
palette[i * 4 + 3] = 255;
}
break;
}
case ((uint)116 << 24) + ((uint)82 << 16) + ((uint)78 << 8) + 83:
{
if (first != 0)
return stbi__err("first not IHDR");
if (z.idata != null)
return stbi__err("tRNS after IDAT");
if (pal_img_n != 0)
{
if (scan == STBI__SCAN_header)
{
s.img_n = 4;
return 1;
}
if (pal_len == 0)
return stbi__err("tRNS before PLTE");
if (c.length > pal_len)
return stbi__err("bad tRNS len");
pal_img_n = 4;
for (i = 0; i < c.length; ++i)
palette[i * 4 + 3] = stbi__get8(s);
}
else
{
if ((s.img_n & 1) == 0)
return stbi__err("tRNS with alpha");
if (c.length != (uint)s.img_n * 2)
return stbi__err("bad tRNS len");
has_trans = 1;
if (scan == STBI__SCAN_header)
{
++s.img_n;
return 1;
}
if (z.depth == 16)
for (k = 0; k < s.img_n && k < 3; ++k)
tc16[k] = (ushort)stbi__get16be(s);
else
for (k = 0; k < s.img_n && k < 3; ++k)
tc[k] = (byte)((byte)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z.depth]);
}
break;
}
case ((uint)73 << 24) + ((uint)68 << 16) + ((uint)65 << 8) + 84:
{
if (first != 0)
return stbi__err("first not IHDR");
if (pal_img_n != 0 && pal_len == 0)
return stbi__err("no PLTE");
if (scan == STBI__SCAN_header)
{
if (pal_img_n != 0)
s.img_n = pal_img_n;
return 1;
}
if (c.length > 1u << 30)
return stbi__err("IDAT size limit");
if ((int)(ioff + c.length) < (int)ioff)
return 0;
if (ioff + c.length > idata_limit)
{
var idata_limit_old = idata_limit;
byte* p;
if (idata_limit == 0)
idata_limit = c.length > 4096 ? c.length : 4096;
while (ioff + c.length > idata_limit)
idata_limit *= 2;
p = (byte*)CRuntime.realloc(z.idata, (ulong)idata_limit);
if (p == null)
return stbi__err("outofmem");
z.idata = p;
}
if (stbi__getn(s, z.idata + ioff, (int)c.length) == 0)
return stbi__err("outofdata");
ioff += c.length;
break;
}
case ((uint)73 << 24) + ((uint)69 << 16) + ((uint)78 << 8) + 68:
{
uint raw_len = 0;
uint bpl = 0;
if (first != 0)
return stbi__err("first not IHDR");
if (scan != STBI__SCAN_load)
return 1;
if (z.idata == null)
return stbi__err("no IDAT");
bpl = (uint)((s.img_x * z.depth + 7) / 8);
raw_len = (uint)(bpl * s.img_y * s.img_n + s.img_y);
z.expanded = (byte*)stbi_zlib_decode_malloc_guesssize_headerflag((sbyte*)z.idata, (int)ioff,
(int)raw_len, (int*)&raw_len, is_iphone == 0 ? 1 : 0);
if (z.expanded == null)
return 0;
CRuntime.free(z.idata);
z.idata = null;
if ((req_comp == s.img_n + 1 && req_comp != 3 && pal_img_n == 0) || has_trans != 0)
s.img_out_n = s.img_n + 1;
else
s.img_out_n = s.img_n;
if (stbi__create_png_image(z, z.expanded, raw_len, s.img_out_n, z.depth, color, interlace) == 0)
return 0;
if (has_trans != 0)
{
if (z.depth == 16)
{
if (stbi__compute_transparency16(z, tc16, s.img_out_n) == 0)
return 0;
}
else
{
if (stbi__compute_transparency(z, tc, s.img_out_n) == 0)
return 0;
}
}
if (is_iphone != 0 &&
(stbi__de_iphone_flag_set != 0
? stbi__de_iphone_flag_local
: stbi__de_iphone_flag_global) != 0 && s.img_out_n > 2)
stbi__de_iphone(z);
if (pal_img_n != 0)
{
s.img_n = pal_img_n;
s.img_out_n = pal_img_n;
if (req_comp >= 3)
s.img_out_n = req_comp;
if (stbi__expand_png_palette(z, palette, (int)pal_len, s.img_out_n) == 0)
return 0;
}
else if (has_trans != 0)
{
++s.img_n;
}
CRuntime.free(z.expanded);
z.expanded = null;
stbi__get32be(s);
return 1;
}
default:
if (first != 0)
return stbi__err("first not IHDR");
if ((c.type & (1 << 29)) == 0)
{
stbi__parse_png_file_invalid_chunk[0] = (char)((c.type >> 24) & 255);
stbi__parse_png_file_invalid_chunk[1] = (char)((c.type >> 16) & 255);
stbi__parse_png_file_invalid_chunk[2] = (char)((c.type >> 8) & 255);
stbi__parse_png_file_invalid_chunk[3] = (char)((c.type >> 0) & 255);
return stbi__err(new string(stbi__parse_png_file_invalid_chunk));
}
stbi__skip(s, (int)c.length);
break;
}
stbi__get32be(s);
}
}
public static void* stbi__do_png(stbi__png p, int* x, int* y, int* n, int req_comp, stbi__result_info* ri)
{
void* result = null;
if (req_comp < 0 || req_comp > 4)
return (byte*)(ulong)(stbi__err("bad req_comp") != 0 ? 0 : 0);
if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp) != 0)
{
if (p.depth <= 8)
ri->bits_per_channel = 8;
else if (p.depth == 16)
ri->bits_per_channel = 16;
else
return (byte*)(ulong)(stbi__err("bad bits_per_channel") != 0 ? 0 : 0);
result = p._out_;
p._out_ = null;
if (req_comp != 0 && req_comp != p.s.img_out_n)
{
if (ri->bits_per_channel == 8)
result = stbi__convert_format((byte*)result, p.s.img_out_n, req_comp, p.s.img_x, p.s.img_y);
else
result = stbi__convert_format16((ushort*)result, p.s.img_out_n, req_comp, p.s.img_x, p.s.img_y);
p.s.img_out_n = req_comp;
if (result == null)
return result;
}
*x = (int)p.s.img_x;
*y = (int)p.s.img_y;
if (n != null)
*n = p.s.img_n;
}
CRuntime.free(p._out_);
p._out_ = null;
CRuntime.free(p.expanded);
p.expanded = null;
CRuntime.free(p.idata);
p.idata = null;
return result;
}
public static int stbi__png_info_raw(stbi__png p, int* x, int* y, int* comp)
{
if (stbi__parse_png_file(p, STBI__SCAN_header, 0) == 0)
{
stbi__rewind(p.s);
return 0;
}
if (x != null)
*x = (int)p.s.img_x;
if (y != null)
*y = (int)p.s.img_y;
if (comp != null)
*comp = p.s.img_n;
return 1;
}
public class stbi__png
{
public byte* _out_;
public int depth;
public byte* expanded;
public byte* idata;
public stbi__context s;
}
[StructLayout(LayoutKind.Sequential)]
public struct stbi__pngchunk
{
public uint length;
public uint type;
}
}
}

View File

@@ -0,0 +1,312 @@
// Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image.Runtime;
// Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public static int stbi__psd_test(stbi__context s)
{
var r = stbi__get32be(s) == 0x38425053 ? 1 : 0;
stbi__rewind(s);
return r;
}
public static void* stbi__psd_load(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri, int bpc)
{
var pixelCount = 0;
var channelCount = 0;
var compression = 0;
var channel = 0;
var i = 0;
var bitdepth = 0;
var w = 0;
var h = 0;
byte* _out_;
if (stbi__get32be(s) != 0x38425053)
return (byte*)(ulong)(stbi__err("not PSD") != 0 ? 0 : 0);
if (stbi__get16be(s) != 1)
return (byte*)(ulong)(stbi__err("wrong version") != 0 ? 0 : 0);
stbi__skip(s, 6);
channelCount = stbi__get16be(s);
if (channelCount < 0 || channelCount > 16)
return (byte*)(ulong)(stbi__err("wrong channel count") != 0 ? 0 : 0);
h = (int)stbi__get32be(s);
w = (int)stbi__get32be(s);
if (h > 1 << 24)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
if (w > 1 << 24)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
bitdepth = stbi__get16be(s);
if (bitdepth != 8 && bitdepth != 16)
return (byte*)(ulong)(stbi__err("unsupported bit depth") != 0 ? 0 : 0);
if (stbi__get16be(s) != 3)
return (byte*)(ulong)(stbi__err("wrong color format") != 0 ? 0 : 0);
stbi__skip(s, (int)stbi__get32be(s));
stbi__skip(s, (int)stbi__get32be(s));
stbi__skip(s, (int)stbi__get32be(s));
compression = stbi__get16be(s);
if (compression > 1)
return (byte*)(ulong)(stbi__err("bad compression") != 0 ? 0 : 0);
if (stbi__mad3sizes_valid(4, w, h, 0) == 0)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
if (compression == 0 && bitdepth == 16 && bpc == 16)
{
_out_ = (byte*)stbi__malloc_mad3(8, w, h, 0);
ri->bits_per_channel = 16;
}
else
{
_out_ = (byte*)stbi__malloc((ulong)(4 * w * h));
}
if (_out_ == null)
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
pixelCount = w * h;
if (compression != 0)
{
stbi__skip(s, h * channelCount * 2);
for (channel = 0; channel < 4; channel++)
{
byte* p;
p = _out_ + channel;
if (channel >= channelCount)
{
for (i = 0; i < pixelCount; i++, p += 4)
*p = (byte)(channel == 3 ? 255 : 0);
}
else
{
if (stbi__psd_decode_rle(s, p, pixelCount) == 0)
{
CRuntime.free(_out_);
return (byte*)(ulong)(stbi__err("corrupt") != 0 ? 0 : 0);
}
}
}
}
else
{
for (channel = 0; channel < 4; channel++)
if (channel >= channelCount)
{
if (bitdepth == 16 && bpc == 16)
{
var q = (ushort*)_out_ + channel;
var val = (ushort)(channel == 3 ? 65535 : 0);
for (i = 0; i < pixelCount; i++, q += 4)
*q = val;
}
else
{
var p = _out_ + channel;
var val = (byte)(channel == 3 ? 255 : 0);
for (i = 0; i < pixelCount; i++, p += 4)
*p = val;
}
}
else
{
if (ri->bits_per_channel == 16)
{
var q = (ushort*)_out_ + channel;
for (i = 0; i < pixelCount; i++, q += 4)
*q = (ushort)stbi__get16be(s);
}
else
{
var p = _out_ + channel;
if (bitdepth == 16)
for (i = 0; i < pixelCount; i++, p += 4)
*p = (byte)(stbi__get16be(s) >> 8);
else
for (i = 0; i < pixelCount; i++, p += 4)
*p = stbi__get8(s);
}
}
}
if (channelCount >= 4)
{
if (ri->bits_per_channel == 16)
for (i = 0; i < w * h; ++i)
{
var pixel = (ushort*)_out_ + 4 * i;
if (pixel[3] != 0 && pixel[3] != 65535)
{
var a = pixel[3] / 65535.0f;
var ra = 1.0f / a;
var inv_a = 65535.0f * (1 - ra);
pixel[0] = (ushort)(pixel[0] * ra + inv_a);
pixel[1] = (ushort)(pixel[1] * ra + inv_a);
pixel[2] = (ushort)(pixel[2] * ra + inv_a);
}
}
else
for (i = 0; i < w * h; ++i)
{
var pixel = _out_ + 4 * i;
if (pixel[3] != 0 && pixel[3] != 255)
{
var a = pixel[3] / 255.0f;
var ra = 1.0f / a;
var inv_a = 255.0f * (1 - ra);
pixel[0] = (byte)(pixel[0] * ra + inv_a);
pixel[1] = (byte)(pixel[1] * ra + inv_a);
pixel[2] = (byte)(pixel[2] * ra + inv_a);
}
}
}
if (req_comp != 0 && req_comp != 4)
{
if (ri->bits_per_channel == 16)
_out_ = (byte*)stbi__convert_format16((ushort*)_out_, 4, req_comp, (uint)w, (uint)h);
else
_out_ = stbi__convert_format(_out_, 4, req_comp, (uint)w, (uint)h);
if (_out_ == null)
return _out_;
}
if (comp != null)
*comp = 4;
*y = h;
*x = w;
return _out_;
}
public static int stbi__psd_info(stbi__context s, int* x, int* y, int* comp)
{
var channelCount = 0;
var dummy = 0;
var depth = 0;
if (x == null)
x = &dummy;
if (y == null)
y = &dummy;
if (comp == null)
comp = &dummy;
if (stbi__get32be(s) != 0x38425053)
{
stbi__rewind(s);
return 0;
}
if (stbi__get16be(s) != 1)
{
stbi__rewind(s);
return 0;
}
stbi__skip(s, 6);
channelCount = stbi__get16be(s);
if (channelCount < 0 || channelCount > 16)
{
stbi__rewind(s);
return 0;
}
*y = (int)stbi__get32be(s);
*x = (int)stbi__get32be(s);
depth = stbi__get16be(s);
if (depth != 8 && depth != 16)
{
stbi__rewind(s);
return 0;
}
if (stbi__get16be(s) != 3)
{
stbi__rewind(s);
return 0;
}
*comp = 4;
return 1;
}
public static int stbi__psd_is16(stbi__context s)
{
var channelCount = 0;
var depth = 0;
if (stbi__get32be(s) != 0x38425053)
{
stbi__rewind(s);
return 0;
}
if (stbi__get16be(s) != 1)
{
stbi__rewind(s);
return 0;
}
stbi__skip(s, 6);
channelCount = stbi__get16be(s);
if (channelCount < 0 || channelCount > 16)
{
stbi__rewind(s);
return 0;
}
depth = stbi__get16be(s);
if (depth != 16)
{
stbi__rewind(s);
return 0;
}
return 1;
}
public static int stbi__psd_decode_rle(stbi__context s, byte* p, int pixelCount)
{
var count = 0;
var nleft = 0;
var len = 0;
count = 0;
while ((nleft = pixelCount - count) > 0)
{
len = stbi__get8(s);
if (len == 128)
{
}
else if (len < 128)
{
len++;
if (len > nleft)
return 0;
count += len;
while (len != 0)
{
*p = stbi__get8(s);
p += 4;
len--;
}
}
else if (len > 128)
{
byte val = 0;
len = 257 - len;
if (len > nleft)
return 0;
val = stbi__get8(s);
count += len;
while (len != 0)
{
*p = val;
p += 4;
len--;
}
}
}
return 1;
}
}
}

View File

@@ -0,0 +1,374 @@
// Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image.Runtime;
// Generated by Sichem at 12/24/2021 8:28:15 PM
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public static int stbi__tga_test(stbi__context s)
{
var res = 0;
var sz = 0;
var tga_color_type = 0;
stbi__get8(s);
tga_color_type = stbi__get8(s);
if (tga_color_type > 1)
goto errorEnd;
sz = stbi__get8(s);
if (tga_color_type == 1)
{
if (sz != 1 && sz != 9)
goto errorEnd;
stbi__skip(s, 4);
sz = stbi__get8(s);
if (sz != 8 && sz != 15 && sz != 16 && sz != 24 && sz != 32)
goto errorEnd;
stbi__skip(s, 4);
}
else
{
if (sz != 2 && sz != 3 && sz != 10 && sz != 11)
goto errorEnd;
stbi__skip(s, 9);
}
if (stbi__get16le(s) < 1)
goto errorEnd;
if (stbi__get16le(s) < 1)
goto errorEnd;
sz = stbi__get8(s);
if (tga_color_type == 1 && sz != 8 && sz != 16)
goto errorEnd;
if (sz != 8 && sz != 15 && sz != 16 && sz != 24 && sz != 32)
goto errorEnd;
res = 1;
errorEnd:;
stbi__rewind(s);
return res;
}
public static void* stbi__tga_load(stbi__context s, int* x, int* y, int* comp, int req_comp,
stbi__result_info* ri)
{
int tga_offset = stbi__get8(s);
int tga_indexed = stbi__get8(s);
int tga_image_type = stbi__get8(s);
var tga_is_RLE = 0;
var tga_palette_start = stbi__get16le(s);
var tga_palette_len = stbi__get16le(s);
int tga_palette_bits = stbi__get8(s);
var tga_x_origin = stbi__get16le(s);
var tga_y_origin = stbi__get16le(s);
var tga_width = stbi__get16le(s);
var tga_height = stbi__get16le(s);
int tga_bits_per_pixel = stbi__get8(s);
var tga_comp = 0;
var tga_rgb16 = 0;
int tga_inverted = stbi__get8(s);
byte* tga_data;
byte* tga_palette = null;
var i = 0;
var j = 0;
var raw_data = stackalloc byte[4];
raw_data[0] = 0;
raw_data[1] = 0;
raw_data[2] = 0;
raw_data[3] = 0;
var RLE_count = 0;
var RLE_repeating = 0;
var read_next_pixel = 1;
if (tga_height > 1 << 24)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
if (tga_width > 1 << 24)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
if (tga_image_type >= 8)
{
tga_image_type -= 8;
tga_is_RLE = 1;
}
tga_inverted = 1 - ((tga_inverted >> 5) & 1);
if (tga_indexed != 0)
tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
else
tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, tga_image_type == 3 ? 1 : 0, &tga_rgb16);
if (tga_comp == 0)
return (byte*)(ulong)(stbi__err("bad format") != 0 ? 0 : 0);
*x = tga_width;
*y = tga_height;
if (comp != null)
*comp = tga_comp;
if (stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0) == 0)
return (byte*)(ulong)(stbi__err("too large") != 0 ? 0 : 0);
tga_data = (byte*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);
if (tga_data == null)
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
stbi__skip(s, tga_offset);
if (tga_indexed == 0 && tga_is_RLE == 0 && tga_rgb16 == 0)
{
for (i = 0; i < tga_height; ++i)
{
var row = tga_inverted != 0 ? tga_height - i - 1 : i;
var tga_row = tga_data + row * tga_width * tga_comp;
stbi__getn(s, tga_row, tga_width * tga_comp);
}
}
else
{
if (tga_indexed != 0)
{
if (tga_palette_len == 0)
{
CRuntime.free(tga_data);
return (byte*)(ulong)(stbi__err("bad palette") != 0 ? 0 : 0);
}
stbi__skip(s, tga_palette_start);
tga_palette = (byte*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);
if (tga_palette == null)
{
CRuntime.free(tga_data);
return (byte*)(ulong)(stbi__err("outofmem") != 0 ? 0 : 0);
}
if (tga_rgb16 != 0)
{
var pal_entry = tga_palette;
for (i = 0; i < tga_palette_len; ++i)
{
stbi__tga_read_rgb16(s, pal_entry);
pal_entry += tga_comp;
}
}
else if (stbi__getn(s, tga_palette, tga_palette_len * tga_comp) == 0)
{
CRuntime.free(tga_data);
CRuntime.free(tga_palette);
return (byte*)(ulong)(stbi__err("bad palette") != 0 ? 0 : 0);
}
}
for (i = 0; i < tga_width * tga_height; ++i)
{
if (tga_is_RLE != 0)
{
if (RLE_count == 0)
{
int RLE_cmd = stbi__get8(s);
RLE_count = 1 + (RLE_cmd & 127);
RLE_repeating = RLE_cmd >> 7;
read_next_pixel = 1;
}
else if (RLE_repeating == 0)
{
read_next_pixel = 1;
}
}
else
{
read_next_pixel = 1;
}
if (read_next_pixel != 0)
{
if (tga_indexed != 0)
{
var pal_idx = tga_bits_per_pixel == 8 ? stbi__get8(s) : stbi__get16le(s);
if (pal_idx >= tga_palette_len)
pal_idx = 0;
pal_idx *= tga_comp;
for (j = 0; j < tga_comp; ++j)
raw_data[j] = tga_palette[pal_idx + j];
}
else if (tga_rgb16 != 0)
{
stbi__tga_read_rgb16(s, raw_data);
}
else
{
for (j = 0; j < tga_comp; ++j)
raw_data[j] = stbi__get8(s);
}
read_next_pixel = 0;
}
for (j = 0; j < tga_comp; ++j)
tga_data[i * tga_comp + j] = raw_data[j];
--RLE_count;
}
if (tga_inverted != 0)
for (j = 0; j * 2 < tga_height; ++j)
{
var index1 = j * tga_width * tga_comp;
var index2 = (tga_height - 1 - j) * tga_width * tga_comp;
for (i = tga_width * tga_comp; i > 0; --i)
{
var temp = tga_data[index1];
tga_data[index1] = tga_data[index2];
tga_data[index2] = temp;
++index1;
++index2;
}
}
if (tga_palette != null)
CRuntime.free(tga_palette);
}
if (tga_comp >= 3 && tga_rgb16 == 0)
{
var tga_pixel = tga_data;
for (i = 0; i < tga_width * tga_height; ++i)
{
var temp = tga_pixel[0];
tga_pixel[0] = tga_pixel[2];
tga_pixel[2] = temp;
tga_pixel += tga_comp;
}
}
if (req_comp != 0 && req_comp != tga_comp)
tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, (uint)tga_width, (uint)tga_height);
tga_palette_start = tga_palette_len = tga_palette_bits = tga_x_origin = tga_y_origin = 0;
return tga_data;
}
public static int stbi__tga_info(stbi__context s, int* x, int* y, int* comp)
{
var tga_w = 0;
var tga_h = 0;
var tga_comp = 0;
var tga_image_type = 0;
var tga_bits_per_pixel = 0;
var tga_colormap_bpp = 0;
var sz = 0;
var tga_colormap_type = 0;
stbi__get8(s);
tga_colormap_type = stbi__get8(s);
if (tga_colormap_type > 1)
{
stbi__rewind(s);
return 0;
}
tga_image_type = stbi__get8(s);
if (tga_colormap_type == 1)
{
if (tga_image_type != 1 && tga_image_type != 9)
{
stbi__rewind(s);
return 0;
}
stbi__skip(s, 4);
sz = stbi__get8(s);
if (sz != 8 && sz != 15 && sz != 16 && sz != 24 && sz != 32)
{
stbi__rewind(s);
return 0;
}
stbi__skip(s, 4);
tga_colormap_bpp = sz;
}
else
{
if (tga_image_type != 2 && tga_image_type != 3 && tga_image_type != 10 && tga_image_type != 11)
{
stbi__rewind(s);
return 0;
}
stbi__skip(s, 9);
tga_colormap_bpp = 0;
}
tga_w = stbi__get16le(s);
if (tga_w < 1)
{
stbi__rewind(s);
return 0;
}
tga_h = stbi__get16le(s);
if (tga_h < 1)
{
stbi__rewind(s);
return 0;
}
tga_bits_per_pixel = stbi__get8(s);
stbi__get8(s);
if (tga_colormap_bpp != 0)
{
if (tga_bits_per_pixel != 8 && tga_bits_per_pixel != 16)
{
stbi__rewind(s);
return 0;
}
tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, null);
}
else
{
tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, tga_image_type == 3 || tga_image_type == 11 ? 1 : 0,
null);
}
if (tga_comp == 0)
{
stbi__rewind(s);
return 0;
}
if (x != null)
*x = tga_w;
if (y != null)
*y = tga_h;
if (comp != null)
*comp = tga_comp;
return 1;
}
public static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
{
if (is_rgb16 != null)
*is_rgb16 = 0;
switch (bits_per_pixel)
{
case 8:
return STBI_grey;
case 16:
case 15:
if (bits_per_pixel == 16 && is_grey != 0)
return STBI_grey_alpha;
if (is_rgb16 != null)
*is_rgb16 = 1;
return STBI_rgb;
case 24:
case 32:
return bits_per_pixel / 8;
default:
return 0;
}
}
public static void stbi__tga_read_rgb16(stbi__context s, byte* _out_)
{
var px = (ushort)stbi__get16le(s);
ushort fiveBitMask = 31;
var r = (px >> 10) & fiveBitMask;
var g = (px >> 5) & fiveBitMask;
var b = px & fiveBitMask;
_out_[0] = (byte)(r * 255 / 31);
_out_[1] = (byte)(g * 255 / 31);
_out_[2] = (byte)(b * 255 / 31);
}
}
}

View File

@@ -0,0 +1,595 @@
// Generated by Sichem at 9/16/2024 9:09:30 AM
using Misaki.HighPerformance.Image.Runtime;
using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
unsafe partial class StbImage
{
public static byte[] stbi__zdefault_distance =
{ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };
public static byte[] stbi__zdefault_length =
{
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8
};
public static int[] stbi__zdist_base =
{
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097,
6145, 8193, 12289, 16385, 24577, 0, 0
};
public static int[] stbi__zdist_extra =
{ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 };
public static int[] stbi__zlength_base =
{
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195,
227, 258, 0, 0
};
public static int[] stbi__zlength_extra =
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 };
public static sbyte* stbi_zlib_decode_malloc_guesssize(sbyte* buffer, int len, int initial_size, int* outlen)
{
var a = new stbi__zbuf();
var p = (sbyte*)stbi__malloc((ulong)initial_size);
if (p == null)
return null;
a.zbuffer = (byte*)buffer;
a.zbuffer_end = (byte*)buffer + len;
if (stbi__do_zlib(&a, p, initial_size, 1, 1) != 0)
{
if (outlen != null)
*outlen = (int)(a.zout - a.zout_start);
return a.zout_start;
}
CRuntime.free(a.zout_start);
return null;
}
public static sbyte* stbi_zlib_decode_malloc_guesssize_headerflag(sbyte* buffer, int len, int initial_size,
int* outlen, int parse_header)
{
var a = new stbi__zbuf();
var p = (sbyte*)stbi__malloc((ulong)initial_size);
if (p == null)
return null;
a.zbuffer = (byte*)buffer;
a.zbuffer_end = (byte*)buffer + len;
if (stbi__do_zlib(&a, p, initial_size, 1, parse_header) != 0)
{
if (outlen != null)
*outlen = (int)(a.zout - a.zout_start);
return a.zout_start;
}
CRuntime.free(a.zout_start);
return null;
}
public static sbyte* stbi_zlib_decode_malloc(sbyte* buffer, int len, int* outlen)
{
return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
}
public static int stbi_zlib_decode_buffer(sbyte* obuffer, int olen, sbyte* ibuffer, int ilen)
{
var a = new stbi__zbuf();
a.zbuffer = (byte*)ibuffer;
a.zbuffer_end = (byte*)ibuffer + ilen;
if (stbi__do_zlib(&a, obuffer, olen, 0, 1) != 0)
return (int)(a.zout - a.zout_start);
return -1;
}
public static sbyte* stbi_zlib_decode_noheader_malloc(sbyte* buffer, int len, int* outlen)
{
var a = new stbi__zbuf();
var p = (sbyte*)stbi__malloc(16384);
if (p == null)
return null;
a.zbuffer = (byte*)buffer;
a.zbuffer_end = (byte*)buffer + len;
if (stbi__do_zlib(&a, p, 16384, 1, 0) != 0)
{
if (outlen != null)
*outlen = (int)(a.zout - a.zout_start);
return a.zout_start;
}
CRuntime.free(a.zout_start);
return null;
}
public static int stbi_zlib_decode_noheader_buffer(sbyte* obuffer, int olen, sbyte* ibuffer, int ilen)
{
var a = new stbi__zbuf();
a.zbuffer = (byte*)ibuffer;
a.zbuffer_end = (byte*)ibuffer + ilen;
if (stbi__do_zlib(&a, obuffer, olen, 0, 0) != 0)
return (int)(a.zout - a.zout_start);
return -1;
}
public static int stbi__zbuild_huffman(stbi__zhuffman* z, byte* sizelist, int num)
{
var i = 0;
var k = 0;
var code = 0;
var next_code = stackalloc int[16];
var sizes = stackalloc int[17];
CRuntime.memset(sizes, 0, (ulong)(17 * sizeof(int)));
CRuntime.memset(z->fast, 0, (ulong)(512 * sizeof(ushort)));
for (i = 0; i < num; ++i)
++sizes[sizelist[i]];
sizes[0] = 0;
for (i = 1; i < 16; ++i)
if (sizes[i] > 1 << i)
return stbi__err("bad sizes");
code = 0;
for (i = 1; i < 16; ++i)
{
next_code[i] = code;
z->firstcode[i] = (ushort)code;
z->firstsymbol[i] = (ushort)k;
code = code + sizes[i];
if (sizes[i] != 0)
if (code - 1 >= 1 << i)
return stbi__err("bad codelengths");
z->maxcode[i] = code << (16 - i);
code <<= 1;
k += sizes[i];
}
z->maxcode[16] = 0x10000;
for (i = 0; i < num; ++i)
{
int s = sizelist[i];
if (s != 0)
{
var c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
var fastv = (ushort)((s << 9) | i);
z->size[c] = (byte)s;
z->value[c] = (ushort)i;
if (s <= 9)
{
var j = stbi__bit_reverse(next_code[s], s);
while (j < 1 << 9)
{
z->fast[j] = fastv;
j += 1 << s;
}
}
++next_code[s];
}
}
return 1;
}
public static int stbi__zeof(stbi__zbuf* z)
{
return z->zbuffer >= z->zbuffer_end ? 1 : 0;
}
public static byte stbi__zget8(stbi__zbuf* z)
{
return (byte)(stbi__zeof(z) != 0 ? 0 : *z->zbuffer++);
}
public static void stbi__fill_bits(stbi__zbuf* z)
{
do
{
if (z->code_buffer >= 1U << z->num_bits)
{
z->zbuffer = z->zbuffer_end;
return;
}
z->code_buffer |= (uint)stbi__zget8(z) << z->num_bits;
z->num_bits += 8;
} while (z->num_bits <= 24);
}
public static uint stbi__zreceive(stbi__zbuf* z, int n)
{
uint k = 0;
if (z->num_bits < n)
stbi__fill_bits(z);
k = (uint)(z->code_buffer & ((1 << n) - 1));
z->code_buffer >>= n;
z->num_bits -= n;
return k;
}
public static int stbi__zhuffman_decode_slowpath(stbi__zbuf* a, stbi__zhuffman* z)
{
var b = 0;
var s = 0;
var k = 0;
k = stbi__bit_reverse((int)a->code_buffer, 16);
for (s = 9 + 1; ; ++s)
if (k < z->maxcode[s])
break;
if (s >= 16)
return -1;
b = (k >> (16 - s)) - z->firstcode[s] + z->firstsymbol[s];
if (b >= 288)
return -1;
if (z->size[b] != s)
return -1;
a->code_buffer >>= s;
a->num_bits -= s;
return z->value[b];
}
public static int stbi__zhuffman_decode(stbi__zbuf* a, stbi__zhuffman* z)
{
var b = 0;
var s = 0;
if (a->num_bits < 16)
{
if (stbi__zeof(a) != 0)
{
if (a->hit_zeof_once == 0)
{
a->hit_zeof_once = 1;
a->num_bits += 16;
}
else
{
return -1;
}
}
else
{
stbi__fill_bits(a);
}
}
b = z->fast[a->code_buffer & ((1 << 9) - 1)];
if (b != 0)
{
s = b >> 9;
a->code_buffer >>= s;
a->num_bits -= s;
return b & 511;
}
return stbi__zhuffman_decode_slowpath(a, z);
}
public static int stbi__zexpand(stbi__zbuf* z, sbyte* zout, int n)
{
sbyte* q;
uint cur = 0;
uint limit = 0;
uint old_limit = 0;
z->zout = zout;
if (z->z_expandable == 0)
return stbi__err("output buffer limit");
cur = (uint)(z->zout - z->zout_start);
limit = old_limit = (uint)(z->zout_end - z->zout_start);
if (0xffffffff - cur < (uint)n)
return stbi__err("outofmem");
while (cur + n > limit)
{
if (limit > 0xffffffff / 2)
return stbi__err("outofmem");
limit *= 2;
}
q = (sbyte*)CRuntime.realloc(z->zout_start, (ulong)limit);
if (q == null)
return stbi__err("outofmem");
z->zout_start = q;
z->zout = q + cur;
z->zout_end = q + limit;
return 1;
}
public static int stbi__parse_huffman_block(stbi__zbuf* a)
{
var zout = a->zout;
for (; ; )
{
var z = stbi__zhuffman_decode(a, &a->z_length);
if (z < 256)
{
if (z < 0)
return stbi__err("bad huffman code");
if (zout >= a->zout_end)
{
if (stbi__zexpand(a, zout, 1) == 0)
return 0;
zout = a->zout;
}
*zout++ = (sbyte)z;
}
else
{
byte* p;
var len = 0;
var dist = 0;
if (z == 256)
{
a->zout = zout;
if (a->hit_zeof_once != 0 && a->num_bits < 16)
return stbi__err("unexpected end");
return 1;
}
if (z >= 286)
return stbi__err("bad huffman code");
z -= 257;
len = stbi__zlength_base[z];
if (stbi__zlength_extra[z] != 0)
len += (int)stbi__zreceive(a, stbi__zlength_extra[z]);
z = stbi__zhuffman_decode(a, &a->z_distance);
if (z < 0 || z >= 30)
return stbi__err("bad huffman code");
dist = stbi__zdist_base[z];
if (stbi__zdist_extra[z] != 0)
dist += (int)stbi__zreceive(a, stbi__zdist_extra[z]);
if (zout - a->zout_start < dist)
return stbi__err("bad dist");
if (len > a->zout_end - zout)
{
if (stbi__zexpand(a, zout, len) == 0)
return 0;
zout = a->zout;
}
p = (byte*)(zout - dist);
if (dist == 1)
{
var v = *p;
if (len != 0)
do
{
*zout++ = (sbyte)v;
} while (--len != 0);
}
else
{
if (len != 0)
do
{
*zout++ = (sbyte)*p++;
} while (--len != 0);
}
}
}
}
public static int stbi__compute_huffman_codes(stbi__zbuf* a)
{
var z_codelength = new stbi__zhuffman();
var lencodes = stackalloc byte[455];
var codelength_sizes = stackalloc byte[19];
var i = 0;
var n = 0;
var hlit = (int)(stbi__zreceive(a, 5) + 257);
var hdist = (int)(stbi__zreceive(a, 5) + 1);
var hclen = (int)(stbi__zreceive(a, 4) + 4);
var ntot = hlit + hdist;
CRuntime.memset(codelength_sizes, 0, (ulong)(19 * sizeof(byte)));
for (i = 0; i < hclen; ++i)
{
var s = (int)stbi__zreceive(a, 3);
codelength_sizes[stbi__compute_huffman_codes_length_dezigzag[i]] = (byte)s;
}
if (stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19) == 0)
return 0;
n = 0;
while (n < ntot)
{
var c = stbi__zhuffman_decode(a, &z_codelength);
if (c < 0 || c >= 19)
return stbi__err("bad codelengths");
if (c < 16)
{
lencodes[n++] = (byte)c;
}
else
{
byte fill = 0;
if (c == 16)
{
c = (int)(stbi__zreceive(a, 2) + 3);
if (n == 0)
return stbi__err("bad codelengths");
fill = lencodes[n - 1];
}
else if (c == 17)
{
c = (int)(stbi__zreceive(a, 3) + 3);
}
else if (c == 18)
{
c = (int)(stbi__zreceive(a, 7) + 11);
}
else
{
return stbi__err("bad codelengths");
}
if (ntot - n < c)
return stbi__err("bad codelengths");
CRuntime.memset(lencodes + n, fill, (ulong)c);
n += c;
}
}
if (n != ntot)
return stbi__err("bad codelengths");
if (stbi__zbuild_huffman(&a->z_length, lencodes, hlit) == 0)
return 0;
if (stbi__zbuild_huffman(&a->z_distance, lencodes + hlit, hdist) == 0)
return 0;
return 1;
}
public static int stbi__parse_uncompressed_block(stbi__zbuf* a)
{
var header = stackalloc byte[4];
var len = 0;
var nlen = 0;
var k = 0;
if ((a->num_bits & 7) != 0)
stbi__zreceive(a, a->num_bits & 7);
k = 0;
while (a->num_bits > 0)
{
header[k++] = (byte)(a->code_buffer & 255);
a->code_buffer >>= 8;
a->num_bits -= 8;
}
if (a->num_bits < 0)
return stbi__err("zlib corrupt");
while (k < 4)
header[k++] = stbi__zget8(a);
len = header[1] * 256 + header[0];
nlen = header[3] * 256 + header[2];
if (nlen != (len ^ 0xffff))
return stbi__err("zlib corrupt");
if (a->zbuffer + len > a->zbuffer_end)
return stbi__err("read past buffer");
if (a->zout + len > a->zout_end)
if (stbi__zexpand(a, a->zout, len) == 0)
return 0;
CRuntime.memcpy(a->zout, a->zbuffer, (ulong)len);
a->zbuffer += len;
a->zout += len;
return 1;
}
public static int stbi__parse_zlib_header(stbi__zbuf* a)
{
int cmf = stbi__zget8(a);
var cm = cmf & 15;
int flg = stbi__zget8(a);
if (stbi__zeof(a) != 0)
return stbi__err("bad zlib header");
if ((cmf * 256 + flg) % 31 != 0)
return stbi__err("bad zlib header");
if ((flg & 32) != 0)
return stbi__err("no preset dict");
if (cm != 8)
return stbi__err("bad compression");
return 1;
}
public static int stbi__parse_zlib(stbi__zbuf* a, int parse_header)
{
var final = 0;
var type = 0;
if (parse_header != 0)
if (stbi__parse_zlib_header(a) == 0)
return 0;
a->num_bits = 0;
a->code_buffer = 0;
a->hit_zeof_once = 0;
do
{
final = (int)stbi__zreceive(a, 1);
type = (int)stbi__zreceive(a, 2);
if (type == 0)
{
if (stbi__parse_uncompressed_block(a) == 0)
return 0;
}
else if (type == 3)
{
return 0;
}
else
{
if (type == 1)
{
fixed (byte* b = stbi__zdefault_length)
{
if (stbi__zbuild_huffman(&a->z_length, b, 288) == 0)
return 0;
}
fixed (byte* b = stbi__zdefault_distance)
{
if (stbi__zbuild_huffman(&a->z_distance, b, 32) == 0)
return 0;
}
}
else
{
if (stbi__compute_huffman_codes(a) == 0)
return 0;
}
if (stbi__parse_huffman_block(a) == 0)
return 0;
}
} while (final == 0);
return 1;
}
public static int stbi__do_zlib(stbi__zbuf* a, sbyte* obuf, int olen, int exp, int parse_header)
{
a->zout_start = obuf;
a->zout = obuf;
a->zout_end = obuf + olen;
a->z_expandable = exp;
return stbi__parse_zlib(a, parse_header);
}
[StructLayout(LayoutKind.Sequential)]
public struct stbi__zbuf
{
public byte* zbuffer;
public byte* zbuffer_end;
public int num_bits;
public int hit_zeof_once;
public uint code_buffer;
public sbyte* zout;
public sbyte* zout_start;
public sbyte* zout_end;
public int z_expandable;
public stbi__zhuffman z_length;
public stbi__zhuffman z_distance;
}
[StructLayout(LayoutKind.Sequential)]
public struct stbi__zhuffman
{
public fixed ushort fast[512];
public fixed ushort firstcode[16];
public fixed int maxcode[17];
public fixed ushort firstsymbol[16];
public fixed byte size[288];
public fixed ushort value[288];
}
}
}

View File

@@ -0,0 +1,95 @@
using Misaki.HighPerformance.Image.Runtime;
using System;
using System.IO;
using System.Runtime.InteropServices;
using Misaki.HighPerformance.Image;
namespace Misaki.HighPerformance.Image
{
#if !STBSHARP_INTERNAL
public
#else
internal
#endif
static unsafe partial class StbImage
{
public static string stbi__g_failure_reason;
public static readonly char[] stbi__parse_png_file_invalid_chunk = new char[25];
public static int NativeAllocations
{
get
{
return MemoryStats.Allocations;
}
}
public class stbi__context
{
private readonly Stream _stream;
public byte[] _tempBuffer;
public int img_n = 0;
public int img_out_n = 0;
public uint img_x = 0;
public uint img_y = 0;
public stbi__context(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
_stream = stream;
}
public Stream Stream
{
get
{
return _stream;
}
}
}
private static int stbi__err(string str)
{
stbi__g_failure_reason = str;
return 0;
}
public static byte stbi__get8(stbi__context s)
{
var b = s.Stream.ReadByte();
if (b == -1) return 0;
return (byte)b;
}
public static void stbi__skip(stbi__context s, int skip)
{
s.Stream.Seek(skip, SeekOrigin.Current);
}
public static void stbi__rewind(stbi__context s)
{
s.Stream.Seek(0, SeekOrigin.Begin);
}
public static int stbi__at_eof(stbi__context s)
{
return s.Stream.Position == s.Stream.Length ? 1 : 0;
}
public static int stbi__getn(stbi__context s, byte* buf, int size)
{
if (s._tempBuffer == null ||
s._tempBuffer.Length < size)
s._tempBuffer = new byte[size * 2];
var result = s.Stream.Read(s._tempBuffer, 0, size);
Marshal.Copy(s._tempBuffer, 0, new IntPtr(buf), result);
return result;
}
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,6 @@
namespace Misaki.HighPerformance.Jobs;
internal static class WorkerThreadPool
{
private static readonly int _workerThreadCount = Environment.ProcessorCount;
}

View File

@@ -0,0 +1,2 @@
global using static Misaki.HighPerformance.LowLevel.Helpers.MemoryUtilities;
global using SystemUnsfae = System.Runtime.CompilerServices.Unsafe;

View File

@@ -0,0 +1,263 @@
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Exceptions;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
public unsafe struct ArenaAllocator : IAllocator, IDisposable
{
private DynamicArena _arena;
private AllocationHandle _handle;
public readonly ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public ArenaAllocator(uint initialSize)
{
_arena = new DynamicArena(initialSize);
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
var selfPtr = (ArenaAllocator*)instance;
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
return ptr;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
var selfPtr = (ArenaAllocator*)instance;
var newPtr = selfPtr->_arena.Allocate(size, alignment, AllocationOption.None);
MemCpy(newPtr, ptr, size);
// NOTE: We do not free the old pointer here, as it is managed by the arena.
return newPtr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
}
public void Reset()
{
_arena.Reset();
}
public void Dispose()
{
_arena.Dispose();
}
}
public unsafe struct DefaultAllocator : IAllocator
{
private AllocationHandle _handle;
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public DefaultAllocator()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = AlignedAlloc(size, alignment);
AllocationManager.TrackAllocation(ptr, size, instance, &FreeBlock);
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, size);
}
return ptr;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
var newPtr = AlignedRealloc(ptr, size, alignment);
AllocationManager.UpdateAllocation(ptr, newPtr, size, instance, &FreeBlock);
return newPtr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
AlignedFree(ptr);
AllocationManager.RemoveAllocation(ptr);
}
}
public unsafe struct EmptyAllocator : IAllocator
{
private AllocationHandle _handle;
public ref AllocationHandle Handle => ref Unsafe.AsRef(in _handle);
public EmptyAllocator()
{
_handle = new AllocationHandle(Unsafe.AsPointer(ref this), &Allocate, &Reallocate, &FreeBlock);
}
[UnmanagedCallersOnly]
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption)
{
return null;
}
[UnmanagedCallersOnly]
private static void* Reallocate(void* instance, void* ptr, nuint size, nuint alignment)
{
return ptr;
}
[UnmanagedCallersOnly]
private static void FreeBlock(void* instance, void* ptr)
{
}
}
public static unsafe class AllocationManager
{
public readonly struct AllocationInfo
{
public nuint Size
{
get; init;
}
public void* Allocator
{
get; init;
}
public FreeFunc FreeHandler
{
get; init;
}
public StackTrace StackTrace
{
get; init;
}
}
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
private static ArenaAllocator s_arenaAllocator = new(_DEFAULT_ARENA_SIZE);
private static DefaultAllocator s_persistentAllocator = new();
private static EmptyAllocator s_emptyAllocator = new();
private static bool s_debugLayer;
private static Dictionary<nint, AllocationInfo>? s_allocated;
public static ArenaAllocator TempAllocator => s_arenaAllocator;
public static DefaultAllocator PersistentAllocator => s_persistentAllocator;
public static EmptyAllocator EmptyAllocator => s_emptyAllocator;
public static void EnableDebugLayer()
{
s_debugLayer = true;
s_allocated ??= new Dictionary<nint, AllocationInfo>(64);
}
public static ref AllocationHandle GetAllocationHandle(Allocator allocator)
{
switch (allocator)
{
case Allocator.Temp:
return ref s_arenaAllocator.Handle;
case Allocator.Persistent:
return ref s_persistentAllocator.Handle;
case Allocator.External:
return ref s_emptyAllocator.Handle;
default:
throw new ArgumentException("Invalid allocator type.", nameof(allocator));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void TrackAllocation(void* ptr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
{
if (!s_debugLayer || s_allocated == null || ptr == null)
{
return;
}
s_allocated[(nint)ptr] = new AllocationInfo
{
Size = allocationSize,
Allocator = allocator,
FreeHandler = freeFunc,
StackTrace = new StackTrace(true)
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UpdateAllocation(void* oldPtr, void* newPtr, nuint allocationSize, void* allocator, FreeFunc freeFunc)
{
if (!s_debugLayer || s_allocated == null || oldPtr == null || newPtr == null)
{
return;
}
if (s_allocated.Remove((nint)oldPtr, out var info))
{
s_allocated[(nint)newPtr] = new AllocationInfo
{
Size = allocationSize,
Allocator = allocator,
FreeHandler = freeFunc,
StackTrace = info.StackTrace
};
}
else
{
TrackAllocation(newPtr, allocationSize, allocator, freeFunc);
}
}
public static void RemoveAllocation(void* ptr)
{
if (s_allocated == null)
{
return;
}
s_allocated.Remove((nint)ptr);
}
/// <summary>
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
/// </summary>
public static void Dispose()
{
s_arenaAllocator.Dispose();
if (s_allocated != null)
{
nuint unfreeBytes = 0u;
foreach (var pair in s_allocated)
{
unfreeBytes += pair.Value.Size;
pair.Value.FreeHandler(pair.Value.Allocator, (void*)pair.Key);
}
if (unfreeBytes > 0u)
{
throw new MemoryLeakException([.. s_allocated.Values]);
}
s_allocated.Clear();
}
}
}

View File

@@ -1,6 +1,6 @@
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Unsafe.Buffer;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A memory management structure that allocates and resets memory blocks with specified alignment.
@@ -8,17 +8,17 @@ namespace Misaki.HighPerformance.Unsafe.Buffer;
public unsafe struct Arena : IDisposable
{
private byte* _buffer;
private uint _size;
private uint _offset;
private nuint _size;
private nuint _offset;
private bool _disposed;
public Arena(uint size)
public Arena(nuint size)
{
Initialize(size);
}
public void Initialize(uint size)
public void Initialize(nuint size)
{
if (_buffer != null)
{
@@ -36,18 +36,18 @@ public unsafe struct Arena : IDisposable
/// You don't need to free the memory manually, it will be freed when the arena is disposed.
/// </summary>
/// <param name="size">Specifies the amount of memory to allocate in bytes.</param>
/// <param name="alignSize">Defines the alignment requirement for the allocated memory.</param>
/// <param name="alignment">Defines the alignment requirement for the allocated memory.</param>
/// <param name="allocationOption">The option when allocating memory.</param>
/// <returns>A pointer to the allocated memory block or null if the allocation cannot be fulfilled.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(uint size, uint alignSize, AllocationOption allocationOption)
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(DynamicArena));
}
var offset = (_offset + alignSize - 1) & ~(alignSize - 1);
var offset = _offset + alignment - 1 & ~(alignment - 1);
if (offset + size > _size)
{
return null;

View File

@@ -1,6 +1,6 @@
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Unsafe.Buffer;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// A dynamic memory management structure that automatically grows by creating linked arenas
@@ -45,7 +45,7 @@ public unsafe struct DynamicArena : IDisposable
_current = _root;
}
private bool CreateNewNode(uint size)
private bool CreateNewNode(nuint size)
{
var newNode = (ArenaNode*)Malloc(SizeOf<ArenaNode>());
try
@@ -68,10 +68,10 @@ public unsafe struct DynamicArena : IDisposable
/// Allocates a block of memory with specified size and alignment. Creates a new arena if current one is full.
/// </summary>
/// <param name="size">Size of the memory block to allocate in bytes.</param>
/// <param name="alignSize">Alignment requirement for the memory block.</param>
/// <param name="alignment">Alignment requirement for the memory block.</param>
/// <returns>Pointer to the allocated memory block.</returns>
/// <exception cref="ObjectDisposedException">Thrown if the arena has been disposed.</exception>
public void* Allocate(uint size, uint alignSize, AllocationOption allocationType)
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption)
{
if (_root == null)
{
@@ -83,7 +83,7 @@ public unsafe struct DynamicArena : IDisposable
while (current != null)
{
result = current->arena.Allocate(size, alignSize, allocationType);
result = current->arena.Allocate(size, alignment, allocationOption);
if (result != null)
{
return result;

View File

@@ -0,0 +1,926 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 32 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 32 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString32"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedStackString32
{
private ushort _length;
private fixed byte _buffer[30];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString32.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 30));
}
}
}
public FixedStackString32(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 30);
_length = (ushort)actualByteCount;
}
}
public FixedStackString32(string input)
: this(input.AsSpan())
{
}
public FixedStackString32(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString32(ReadOnlySpan<byte> input)
{
if (input.Length > 30)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString32(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 64 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 64 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString64"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedStackString64
{
private ushort _length;
private fixed byte _buffer[62];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString64.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 62));
}
}
}
public FixedStackString64(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 62);
_length = (ushort)actualByteCount;
}
}
public FixedStackString64(string input)
: this(input.AsSpan())
{
}
public FixedStackString64(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString64(ReadOnlySpan<byte> input)
{
if (input.Length > 62)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString64(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 128 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 128 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString128"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedStackString128
{
private ushort _length;
private fixed byte _buffer[126];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString128.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 126));
}
}
}
public FixedStackString128(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 126);
_length = (ushort)actualByteCount;
}
}
public FixedStackString128(string input)
: this(input.AsSpan())
{
}
public FixedStackString128(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString128(ReadOnlySpan<byte> input)
{
if (input.Length > 126)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString128(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 256 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 256 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString256"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedStackString256
{
private ushort _length;
private fixed byte _buffer[254];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString256.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 254));
}
}
}
public FixedStackString256(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 254);
_length = (ushort)actualByteCount;
}
}
public FixedStackString256(string input)
: this(input.AsSpan())
{
}
public FixedStackString256(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString256(ReadOnlySpan<byte> input)
{
if (input.Length > 254)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString256(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 512 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 512 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString512"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedStackString512
{
private ushort _length;
private fixed byte _buffer[510];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString512.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 510));
}
}
}
public FixedStackString512(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 510);
_length = (ushort)actualByteCount;
}
}
public FixedStackString512(string input)
: this(input.AsSpan())
{
}
public FixedStackString512(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString512(ReadOnlySpan<byte> input)
{
if (input.Length > 510)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString512(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 1024 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString1024"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedStackString1024
{
private ushort _length;
private fixed byte _buffer[1022];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString1024.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 1022));
}
}
}
public FixedStackString1024(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 1022);
_length = (ushort)actualByteCount;
}
}
public FixedStackString1024(string input)
: this(input.AsSpan())
{
}
public FixedStackString1024(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString1024(ReadOnlySpan<byte> input)
{
if (input.Length > 1022)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString1024(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 2048 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString2048"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedStackString2048
{
private ushort _length;
private fixed byte _buffer[2046];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString2048.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 2046));
}
}
}
public FixedStackString2048(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 2046);
_length = (ushort)actualByteCount;
}
}
public FixedStackString2048(string input)
: this(input.AsSpan())
{
}
public FixedStackString2048(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString2048(ReadOnlySpan<byte> input)
{
if (input.Length > 2046)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString2048(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length 4096 bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString4096"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedStackString4096
{
private ushort _length;
private fixed byte _buffer[4094];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString4096.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, 4094));
}
}
}
public FixedStackString4096(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, 4094);
_length = (ushort)actualByteCount;
}
}
public FixedStackString4096(string input)
: this(input.AsSpan())
{
}
public FixedStackString4096(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString4096(ReadOnlySpan<byte> input)
{
if (input.Length > 4094)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString4096(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}

View File

@@ -0,0 +1,129 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
/// Represents a stack allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
/// </summary>
/// <remarks>
/// This struct is designed to hold data on the stack. Every copy of this struct causes a copy of the underlying data.
/// If you need a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes, consider using <see cref="Misaki.HighPerformance.Unsafe.Buffer.FixedString<#= i #>"/>.
/// </remarks>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedStackString<#= i #>
{
private ushort _length;
private fixed byte _buffer[<#= i - 2 #>];
public ushort Length => _length;
public string Value
{
get
{
fixed (byte* bufferPtr = _buffer)
{
return Encoding.UTF8.GetString(bufferPtr, _length);
}
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedStackString<#= i #>.");
}
fixed (byte* bufferPtr = _buffer)
{
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(bufferPtr, <#= i - 2 #>));
}
}
}
public FixedStackString<#= i #>(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
fixed (char* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, bufferPtr, <#= i - 2 #>);
_length = (ushort)actualByteCount;
}
}
public FixedStackString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedStackString<#= i #>(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedStackString<#= i #>(ReadOnlySpan<byte> input)
{
if (input.Length > <#= i - 2 #>)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
fixed (byte* bufferPtr = _buffer)
{
SystemUnsfae.CopyBlockUnaligned(bufferPtr, inputPtr, _length);
}
}
public FixedStackString<#= i #>(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsSpan()
{
fixed (byte* ptr = _buffer)
{
return new(ptr, _length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte* GetUnsafePointer()
{
fixed (byte* ptr = _buffer)
{
return ptr;
}
}
public override string ToString()
{
return Value;
}
}
<# } #>

View File

@@ -0,0 +1,894 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 32 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 32)]
public unsafe struct FixedString32 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 30));
}
}
public FixedString32(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 30)
{
throw new ArgumentException("Input string is too long to fit in FixedString32.");
}
_buffer = (byte*)NativeMemory.Alloc(30);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 30);
_length = (ushort)actualByteCount;
}
}
public FixedString32(string input)
: this(input.AsSpan())
{
}
public FixedString32(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString32(ReadOnlySpan<byte> input)
{
if (input.Length > 30)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString32.");
}
_buffer = (byte*)NativeMemory.Alloc(30);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString32(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 64 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 64)]
public unsafe struct FixedString64 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 62));
}
}
public FixedString64(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 62)
{
throw new ArgumentException("Input string is too long to fit in FixedString64.");
}
_buffer = (byte*)NativeMemory.Alloc(62);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 62);
_length = (ushort)actualByteCount;
}
}
public FixedString64(string input)
: this(input.AsSpan())
{
}
public FixedString64(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString64(ReadOnlySpan<byte> input)
{
if (input.Length > 62)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString64.");
}
_buffer = (byte*)NativeMemory.Alloc(62);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString64(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 128 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 128)]
public unsafe struct FixedString128 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 126));
}
}
public FixedString128(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 126)
{
throw new ArgumentException("Input string is too long to fit in FixedString128.");
}
_buffer = (byte*)NativeMemory.Alloc(126);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 126);
_length = (ushort)actualByteCount;
}
}
public FixedString128(string input)
: this(input.AsSpan())
{
}
public FixedString128(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString128(ReadOnlySpan<byte> input)
{
if (input.Length > 126)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString128.");
}
_buffer = (byte*)NativeMemory.Alloc(126);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString128(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 256 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 256)]
public unsafe struct FixedString256 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 254));
}
}
public FixedString256(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 254)
{
throw new ArgumentException("Input string is too long to fit in FixedString256.");
}
_buffer = (byte*)NativeMemory.Alloc(254);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 254);
_length = (ushort)actualByteCount;
}
}
public FixedString256(string input)
: this(input.AsSpan())
{
}
public FixedString256(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString256(ReadOnlySpan<byte> input)
{
if (input.Length > 254)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString256.");
}
_buffer = (byte*)NativeMemory.Alloc(254);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString256(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 512 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 512)]
public unsafe struct FixedString512 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 510));
}
}
public FixedString512(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 510)
{
throw new ArgumentException("Input string is too long to fit in FixedString512.");
}
_buffer = (byte*)NativeMemory.Alloc(510);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 510);
_length = (ushort)actualByteCount;
}
}
public FixedString512(string input)
: this(input.AsSpan())
{
}
public FixedString512(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString512(ReadOnlySpan<byte> input)
{
if (input.Length > 510)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString512.");
}
_buffer = (byte*)NativeMemory.Alloc(510);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString512(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 1024 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 1024)]
public unsafe struct FixedString1024 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 1022));
}
}
public FixedString1024(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 1022)
{
throw new ArgumentException("Input string is too long to fit in FixedString1024.");
}
_buffer = (byte*)NativeMemory.Alloc(1022);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 1022);
_length = (ushort)actualByteCount;
}
}
public FixedString1024(string input)
: this(input.AsSpan())
{
}
public FixedString1024(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString1024(ReadOnlySpan<byte> input)
{
if (input.Length > 1022)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString1024.");
}
_buffer = (byte*)NativeMemory.Alloc(1022);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString1024(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 2048 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 2048)]
public unsafe struct FixedString2048 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 2046));
}
}
public FixedString2048(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 2046)
{
throw new ArgumentException("Input string is too long to fit in FixedString2048.");
}
_buffer = (byte*)NativeMemory.Alloc(2046);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 2046);
_length = (ushort)actualByteCount;
}
}
public FixedString2048(string input)
: this(input.AsSpan())
{
}
public FixedString2048(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString2048(ReadOnlySpan<byte> input)
{
if (input.Length > 2046)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString2048.");
}
_buffer = (byte*)NativeMemory.Alloc(2046);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString2048(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length 4096 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 4096)]
public unsafe struct FixedString4096 : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, 4094));
}
}
public FixedString4096(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > 4094)
{
throw new ArgumentException("Input string is too long to fit in FixedString4096.");
}
_buffer = (byte*)NativeMemory.Alloc(4094);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, 4094);
_length = (ushort)actualByteCount;
}
}
public FixedString4096(string input)
: this(input.AsSpan())
{
}
public FixedString4096(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString4096(ReadOnlySpan<byte> input)
{
if (input.Length > 4094)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString4096.");
}
_buffer = (byte*)NativeMemory.Alloc(4094);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString4096(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}

View File

@@ -0,0 +1,125 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Buffer;
<# for (int i = 32; i <= 4096; i *= 2) { #>
/// <summary>
/// Represents a heap allocated fixed-size UTF-8 encoded string of length <#= i #> bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = <#= i #>)]
public unsafe struct FixedString<#= i #> : IDisposable
{
private ushort _length;
private byte* _buffer;
public ushort Length => _length;
public string Value
{
readonly get
{
return Encoding.UTF8.GetString(_buffer, _length);
}
set
{
if (string.IsNullOrEmpty(value))
{
_length = 0;
return;
}
var maxBytes = Encoding.UTF8.GetByteCount(value);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_length = (ushort)Encoding.UTF8.GetBytes(value, new Span<byte>(_buffer, <#= i - 2 #>));
}
}
public FixedString<#= i #>(ReadOnlySpan<char> input)
{
var maxBytes = Encoding.UTF8.GetByteCount(input);
if (maxBytes > <#= i - 2 #>)
{
throw new ArgumentException("Input string is too long to fit in FixedString<#= i #>.");
}
_buffer = (byte*)NativeMemory.Alloc(<#= i - 2 #>);
fixed (char* inputPtr = input)
{
var actualByteCount = Encoding.UTF8.GetBytes(inputPtr, input.Length, _buffer, <#= i - 2 #>);
_length = (ushort)actualByteCount;
}
}
public FixedString<#= i #>(string input)
: this(input.AsSpan())
{
}
public FixedString<#= i #>(char* input, ushort length)
: this(new Span<char>(input, length))
{
}
public FixedString<#= i #>(ReadOnlySpan<byte> input)
{
if (input.Length > <#= i - 2 #>)
{
throw new ArgumentException("Input byte array is too long to fit in FixedString<#= i #>.");
}
_buffer = (byte*)NativeMemory.Alloc(<#= i - 2 #>);
_length = (ushort)input.Length;
fixed (byte* inputPtr = input)
{
SystemUnsfae.CopyBlockUnaligned(_buffer, inputPtr, _length);
}
}
public FixedString<#= i #>(byte* input, ushort length)
: this(new ReadOnlySpan<byte>(input, length))
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Span<byte> AsSpan()
{
return new(_buffer, _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly byte* GetUnsafePointer()
{
return _buffer;
}
public override string ToString()
{
return Value;
}
public void Dispose()
{
if (_buffer != null)
{
NativeMemory.Free(_buffer);
_length = 0;
_buffer = null;
}
}
}
<# } #>

View File

@@ -1,6 +1,6 @@
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Unsafe.Buffer;
namespace Misaki.HighPerformance.LowLevel.Buffer;
// TODO: Implement a pool for UnsafeArray<T>.
public unsafe static class UnsafeArrayPool

View File

@@ -1,4 +1,4 @@
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
[Flags]
public enum AllocationOption : byte

View File

@@ -0,0 +1,711 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
namespace Misaki.HighPerformance.LowLevel.Collections;
public sealed class BitSet
{
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31
private const int _INDEX_SIZE = 5; // log_2(BitSize + 1)
private const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint
private static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically
/// <summary>
/// Determines the required length of an <see cref="BitSet"/> to hold the passed id or bit.
/// </summary>
/// <param name="id">The id or bit.</param>
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
public static int RequiredLength(int id)
{
return (id >> 5) + int.Sign(id & _BIT_SIZE);
}
/// <summary>
/// Rounds the given length to the next padding size.
/// </summary>
/// <param name="length">The length to round.</param>
/// <returns>The rounded length.</returns>
public static int RoundToPadding(int length)
{
return (length + s_padding - 1) / s_padding * s_padding;
}
/// <summary>
/// The bits from the bitset.
/// </summary>
private uint[] _bits;
/// <summary>
/// The highest bit set.
/// </summary>
private int _highestBit;
/// <summary>
/// The maximum <see cref="_bits"/>-index current in use.
/// </summary>
private int _max;
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// </summary>
public BitSet()
{
_bits = new uint[s_padding];
}
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// </summary>
public BitSet(int minimalLength)
{
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE);
var length = RoundToPadding(uints);
_bits = new uint[length];
}
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// </summary>
public BitSet(params Span<uint> bits)
{
_bits = bits.ToArray();
_highestBit = 0;
_max = _bits.Length * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use
for (var i = 0; i < _bits.Length; i++)
{
if (_bits[i] != 0)
{
_highestBit = Math.Max(_highestBit, i * (_BIT_SIZE + 1) + BitOperations.Log2(_bits[i]) + 1);
}
}
}
/// <summary>
/// The highest uint index in use inside the <see cref="_bits"/>-array.
/// </summary>
public int HighestIndex
{
get => _max;
}
/// <summary>
/// The highest bit set.
/// </summary>
public int HighestBit
{
get => _highestBit;
}
/// <summary>
/// Returns the length of the bitset, how many ints it consists of.
/// </summary>
public int Length
{
get => _bits.Length;
}
/// <summary>
/// Checks whether a bit is set at the index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
{
return false;
}
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0;
}
/// <summary>
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
/// </summary>
/// <param name="index">The index.</param>
public void SetBit(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
{
Array.Resize(ref _bits, RoundToPadding(b));
}
// Track highest set bit
_highestBit = Math.Max(_highestBit, index);
_max = _highestBit / (_BIT_SIZE + 1) + 1;
_bits[b] |= 1u << (index & _BIT_SIZE);
}
/// <summary>
/// Clears the bit at the given index.
/// </summary>
/// <param name="index">The index.</param>
public void ClearBit(int index)
{
var b = index >> _INDEX_SIZE;
if (b >= _bits.Length)
{
return;
}
_bits[b] &= ~(1u << (index & _BIT_SIZE));
}
/// <summary>
/// Sets all bits.
/// </summary>
public void SetAll()
{
var count = _bits.Length;
for (var i = 0; i < count; i++)
{
_bits[i] = 0xffffffff;
}
_highestBit = _bits.Length * (_BIT_SIZE + 1) - 1;
_max = _highestBit / (_BIT_SIZE + 1) + 1;
}
/// <summary>
/// Clears all set bits.
/// </summary>
public void ClearAll()
{
Array.Clear(_bits);
_highestBit = 0;
_max = 0;
}
/// <summary>
/// Finds the next set bit at or after `startIndex`, or -1 if none.
/// </summary>
public int NextSetBit(int startIndex)
{
var wordIndex = startIndex >> _BIT_SIZE;
if (wordIndex >= _bits.Length)
{
return -1;
}
// Mask off bits below startIndex in the first word:
var word = _bits[wordIndex] & ~0u << (startIndex & _MASK);
while (true)
{
if (word != 0)
{
// get the least-significant set bit
var bit = BitOperations.TrailingZeroCount(word);
return (wordIndex << _BIT_SIZE) + bit;
}
wordIndex++;
if (wordIndex >= _bits.Length)
{
return -1;
}
word = _bits[wordIndex];
}
}
/// <summary>
/// Checks if all bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <returns>True if they match, false if not.</returns>
[SkipLocalsInit]
public bool All(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise and
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit & otherBits[i]) != bit)
{
return false;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i++)
{
if (bits[i] != 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise and
for (var i = 0; i < min; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseAnd(vector, otherVector);
if (!Vector.EqualsAll(resultVector, vector))
{
return false;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
{
return false;
}
}
}
return true;
}
/// <summary>
/// Checks if any bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <returns>True if they match, false if not.</returns>
public bool Any(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise and, return true since any is met
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit & otherBits[i]) > 0)
{
return true;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i++)
{
if (bits[i] > 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise and, return true since any is met
for (var i = 0; i < min; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseAnd(vector, otherVector);
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
{
return true;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
{
return false;
}
}
}
return _highestBit <= 0;
}
/// <summary>
/// Checks if none bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <returns>True if none match, false if not.</returns>
public bool None(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise and, return true since any is met
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit & otherBits[i]) != 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise and, return true since any is met
for (var i = 0; i < min; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseAnd(vector, otherVector);
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
{
return false;
}
}
}
return true;
}
/// <summary>
/// Checks if exactly all bits from this instance match those of the other instance.
/// </summary>
/// <param name="other">The other <see cref="BitSet"/>.</param>
/// <returns>True if they match, false if not.</returns>
public bool Exclusive(BitSet other)
{
var min = Math.Min(Math.Min(Length, other.Length), _max);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
var bits = _bits.AsSpan();
var otherBits = other._bits.AsSpan();
// Bitwise xor, if both are not totally equal, return false
for (var i = 0; i < min; i++)
{
var bit = bits[i];
if ((bit ^ otherBits[i]) != 0)
{
return false;
}
}
// handle extra bits on our side that might just be all zero
for (var i = min; i < _max; i++)
{
if (bits[i] != 0)
{
return false;
}
}
}
else
{
// Vectorized bitwise xor, return true since any is met
for (var i = 0; i < min; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
var resultVector = Vector.Xor(vector, otherVector);
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
{
return false;
}
}
// Handle extra bits on our side that might just be all zero.
for (var i = min; i < _max; i += s_padding)
{
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
{
return false;
}
}
}
return true;
}
public static BitSet operator &(BitSet left, BitSet right)
{
var min = Math.Min(left.Length, right.Length);
var result = new BitSet(min);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
for (var i = 0; i < min; i++)
{
result._bits[i] = left._bits[i] & right._bits[i];
}
}
else
{
for (var i = 0; i < min; i += s_padding)
{
var vectorLeft = new Vector<uint>(left._bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(right._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseAnd(vectorLeft, vectorRight);
resultVector.CopyTo(result._bits.AsSpan(i, s_padding));
}
}
return result;
}
public static BitSet operator |(BitSet left, BitSet right)
{
var min = Math.Min(left.Length, right.Length);
var result = new BitSet(min);
if (!Vector.IsHardwareAccelerated || min < s_padding)
{
for (var i = 0; i < min; i++)
{
result._bits[i] = left._bits[i] | right._bits[i];
}
}
else
{
for (var i = 0; i < min; i += s_padding)
{
var vectorLeft = new Vector<uint>(left._bits.AsSpan()[i..]);
var vectorRight = new Vector<uint>(right._bits.AsSpan()[i..]);
var resultVector = Vector.BitwiseOr(vectorLeft, vectorRight);
resultVector.CopyTo(result._bits.AsSpan(i, s_padding));
}
}
return result;
}
public static BitSet operator ~(BitSet bitSet)
{
if (!Vector.IsHardwareAccelerated || bitSet.Length < s_padding)
{
for (var i = 0; i < bitSet.Length; i++)
{
bitSet._bits[i] = ~bitSet._bits[i];
}
}
else
{
for (var i = 0; i < bitSet.Length; i += s_padding)
{
var vector = new Vector<uint>(bitSet._bits.AsSpan()[i..]);
var resultVector = ~vector;
resultVector.CopyTo(bitSet._bits.AsSpan(i, s_padding));
}
}
return bitSet;
}
/// <summary>
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// </summary>
/// <returns>The hash.</returns>
public Span<uint> AsSpan()
{
var max = _highestBit / (_BIT_SIZE + 1) + 1;
return _bits.AsSpan()[..max];
}
/// <summary>
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
/// </summary>
/// <param name="span">The <see cref="Span{T}"/> to copy into.</param>
/// <param name="zero">If true, it will zero the unused space from the <see cref="span"/>.</param>
/// <returns>The <see cref="Span{T}"/>.</returns>
public Span<uint> AsSpan(Span<uint> span, bool zero = true)
{
// Copy everything thats possible from one to another
var length = Math.Min(Length, span.Length);
for (var index = 0; index < length; index++)
{
span[index] = _bits[index];
}
// Zero the rest space which was not overriden due to the copy.
for (var index = length; zero && index < span.Length; index++)
{
span[index] = 0;
}
return span[..Length];
}
/// <summary>
/// Prints the content of this instance.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
// Convert uint to binary form for pretty printing
var binaryBuilder = new StringBuilder();
foreach (var bit in _bits)
{
binaryBuilder.Append(Convert.ToString(bit, 2).PadLeft(32, '0')).Append(',');
}
binaryBuilder.Length--;
return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Length)}: {Length}";
}
}
/// <summary>
/// The <see cref="SpanBitSet"/> struct
/// represents a non resizable collection of bits.
/// Used to set, check and clear bits on a allocated <see cref="BitSet"/> or on the stack.
/// </summary>
public readonly ref struct SpanBitSet
{
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31
// NOTE: Is a byte not 8 bits?
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1)
/// <summary>
/// The bits from the bitset.
/// </summary>
private readonly Span<uint> _bits;
/// <summary>
/// Initializes a new instance of the <see cref="BitSet" /> class.
/// </summary>
public SpanBitSet(Span<uint> bits)
{
_bits = bits;
}
/// <summary>
/// Checks whether a bit is set at the index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index)
{
var b = index >> _BYTE_SIZE;
if (b >= _bits.Length)
{
return false;
}
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0;
}
/// <summary>
/// Sets a bit at the given index.
/// Resizes its internal array if necessary.
/// </summary>
/// <param name="index">The index.</param>
public void SetBit(int index)
{
var b = index >> _BYTE_SIZE;
if (b >= _bits.Length)
{
return;
}
_bits[b] |= 1u << (index & _BIT_SIZE);
}
/// <summary>
/// Clears the bit at the given index.
/// </summary>
/// <param name="index">The index.</param>
public void ClearBit(int index)
{
var b = index >> _BYTE_SIZE;
if (b >= _bits.Length)
{
return;
}
_bits[b] &= ~(1u << (index & _BIT_SIZE));
}
/// <summary>
///
/// </summary>
public void SetAll()
{
var count = _bits.Length;
for (var i = 0; i < count; i++)
{
_bits[i] = 0xffffffff;
}
}
/// <summary>
/// Clears all set bits.
/// </summary>
public void ClearAll()
{
_bits.Clear();
}
/// <summary>
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
/// </summary>
/// <returns>The hash.</returns>
public Span<uint> AsSpan()
{
return _bits;
}
/// <summary>
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
/// </summary>
/// <param name=""></param>
/// <returns>The hash.</returns>
public Span<uint> AsSpan(Span<uint> span, bool zero = true)
{
// Prevent exception because target array is to small for copy operation
var length = Math.Min(_bits.Length, span.Length);
for (var index = 0; index < length; index++)
{
span[index] = _bits[index];
}
// Zero the rest space which was not overriden due to the copy.
for (var index = length; zero && index < span.Length; index++)
{
span[index] = 0;
}
return span[.._bits.Length];
}
/// <summary>
/// Prints the content of this instance.
/// </summary>
/// <returns>The string.</returns>
public override string ToString()
{
// Convert uint to binary form for pretty printing
var binaryBuilder = new StringBuilder();
foreach (var bit in _bits)
{
binaryBuilder.Append(Convert.ToString(bit, 2).PadLeft(32, '0')).Append(',');
}
binaryBuilder.Length--;
return $"{nameof(_bits)}: {string.Join(",", binaryBuilder)}";
}
}

View File

@@ -1,4 +1,4 @@
namespace Misaki.HighPerformance.Unsafe.Collections.Contracts;
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
public unsafe interface IUnsafeCollection<T> : IEnumerable<T>, IDisposable
where T : unmanaged

View File

@@ -0,0 +1,5 @@
namespace Misaki.HighPerformance.LowLevel.Collections.Contracts;
internal class IUnsafeSet<T>
where T : unmanaged, IEquatable<T>
{
}

View File

@@ -1,10 +1,11 @@
using Misaki.HighPerformance.Unsafe.Buffer;
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A structure for managing an array of unmanaged types with unsafe memory operations.
@@ -66,14 +67,22 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
private T* _buffer;
private int _count;
private readonly Allocator _allocator;
private AllocationHandle* _handle;
public readonly int Count => _count;
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _buffer[index];
get
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
}
return ref _buffer[index];
}
}
public readonly bool IsCreated
@@ -88,10 +97,23 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// <summary>
/// Constructs an UnsafeArray with a default size of 1 and uses the Persistent allocator.
/// </summary>
public UnsafeArray() : this(1, Allocator.Persistent)
public UnsafeArray()
: this(1, Allocator.Persistent)
{
}
public UnsafeArray(int count, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero.");
}
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
_buffer = (T*)handle.Alloc(_handle->Allocator, (uint)count * (uint)sizeof(T), (uint)AlignOf<T>(), allocationOption);
_count = count;
}
/// <summary>
/// Initializes a new instance of UnsafeArray with a specified number of elements and an allocation type.
/// </summary>
@@ -100,15 +122,8 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
/// <param name="allocationOption">Determines how the memory should be allocated.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the specified number of elements is less than or equal to zero.</exception>
public UnsafeArray(int count, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(count, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
if (count <= 0)
{
throw new ArgumentOutOfRangeException(nameof(count), "Count must be greater than zero.");
}
_buffer = AllocationManager.Allocate<T>((uint)count, (uint)AlignOf<T>(), allocator, allocationOption);
_count = count;
_allocator = allocator;
}
/// <summary>
@@ -125,7 +140,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
{
_buffer = (T*)buffer;
_count = count;
_allocator = Allocator.External;
_handle = (AllocationHandle*)Unsafe.AsPointer(ref AllocationManager.EmptyAllocator.Handle);
}
/// <inheritdoc/>
@@ -136,7 +151,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return;
}
_buffer = AllocationManager.Realloc(_buffer, (uint)newSize, (uint)AlignOf<T>(), _allocator);
_buffer = (T*)_handle->Realloc(_handle->Allocator, _buffer, (uint)newSize, (uint)AlignOf<T>());
_count = newSize;
}
@@ -162,10 +177,10 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return;
}
AllocationManager.Free(_buffer, _allocator);
_handle->Free(_handle->Allocator, _buffer);
_handle = null;
_buffer = null;
_count = 0;
}
}
}

View File

@@ -1,11 +1,14 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePair<TKey, TValue>> where TKey : unmanaged, IEquatable<TKey> where TValue : unmanaged
public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePair<TKey, TValue>>
where TKey : unmanaged, IEquatable<TKey> where TValue : unmanaged
{
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{
@@ -92,9 +95,14 @@ public unsafe struct UnsafeHashMap<TKey, TValue> : IUnsafeCollection<KeyValuePai
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => new Enumerator((HashMapHelper<TKey>*)UnsafeUtilities.AddressOf(ref _hashMap));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public UnsafeHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
public UnsafeHashMap(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_hashMap = new HashMapHelper<TKey>(capacity, sizeof(TValue), HashMapHelper<TKey>.MINIMAL_CAPACITY, ref handle, allocationOption);
}
public UnsafeHashMap(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
_hashMap = new HashMapHelper<TKey>(capacity, sizeof(TValue), HashMapHelper<TKey>.MINIMAL_CAPACITY, allocator, allocationOption);
}
/// <summary>

View File

@@ -1,9 +1,11 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A collection that provides fast, unsafe operations for managing a set of unmanaged types. It supports adding,
@@ -49,9 +51,14 @@ public unsafe struct UnsafeHashSet<T> : IUnsafeCollection<T>, IEnumerable<T>
public IEnumerator<T> GetEnumerator() => new Enumerator((HashMapHelper<T>*)UnsafeUtilities.AddressOf(ref _hashMap));
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public UnsafeHashSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
public UnsafeHashSet(int capacity, ref AllocationHandle handle, AllocationOption allocationOption = AllocationOption.None)
{
_hashMap = new HashMapHelper<T>(capacity, 0, HashMapHelper<T>.MINIMAL_CAPACITY, ref handle, allocationOption);
}
public UnsafeHashSet(int capacity, Allocator allocator, AllocationOption allocationOption = AllocationOption.None)
: this(capacity, ref AllocationManager.GetAllocationHandle(allocator), allocationOption)
{
_hashMap = new HashMapHelper<T>(capacity, 0, HashMapHelper<T>.MINIMAL_CAPACITY, allocator, allocationOption);
}
/// <summary>

View File

@@ -1,9 +1,9 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A collection that allows for unsafe operations on a list of unmanaged types.
@@ -118,12 +118,10 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
public readonly int Capacity => _array.Count;
public readonly bool IsCreated => _array.IsCreated;
public readonly T this[int index]
public readonly ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _array[index];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => _array[index] = value;
get => ref _array[index];
}
public IEnumerator<T> GetEnumerator() => new Enumerator((UnsafeList<T>*)UnsafeUtilities.AddressOf(ref this));

View File

@@ -1,9 +1,9 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
/// <summary>
/// A structure that implements a queue using unmanaged types for efficient memory management.

View File

@@ -1,9 +1,9 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.Unsafe.Helpers;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Helpers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Collections;
namespace Misaki.HighPerformance.LowLevel.Collections;
public unsafe struct UnsafeStack<T> : IUnsafeCollection<T>
where T : unmanaged

View File

@@ -0,0 +1,52 @@
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.LowLevel.Contracts;
using unsafe AllocFunc = delegate* unmanaged<void*, nuint, nuint, AllocationOption, void*>;
using unsafe FreeFunc = delegate* unmanaged<void*, void*, void>;
using unsafe ReallocFunc = delegate* unmanaged<void*, void*, nuint, nuint, void*>;
public unsafe readonly struct AllocationHandle
{
public void* Allocator
{
get;
}
public AllocFunc Alloc
{
get;
}
public ReallocFunc Realloc
{
get;
}
public FreeFunc Free
{
get;
}
public AllocationHandle(void* allocator, AllocFunc alloc, ReallocFunc realloc, FreeFunc free)
{
Allocator = allocator;
Alloc = alloc;
Realloc = realloc;
Free = free;
}
}
/// <summary>
/// Represents an allocator interface for managing memory allocations.
/// </summary>
/// <remarks>
/// The allocator must be static or pined to a specific memory region.
/// </remarks>
public unsafe interface IAllocator
{
public ref AllocationHandle Handle
{
get;
}
}

View File

@@ -1,27 +1,12 @@
#if DEBUG
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Diagnostics;
using System.Text;
#endif
namespace Misaki.HighPerformance.Unsafe;
namespace Misaki.HighPerformance.LowLevel.Exceptions;
public readonly struct MemoryLeakExceptionInfo
public class MemoryLeakException(params AllocationManager.AllocationInfo[] Infos) : Exception
{
public nuint Size
{
get; init;
}
#if DEBUG
public StackTrace StackTrace
{
get; init;
}
#endif
}
public class MemoryLeakException(params MemoryLeakExceptionInfo[] Infos) : Exception
{
#if DEBUG
private static string GetMessage(StackTrace? stackTrace)
{
if (stackTrace == null)
@@ -43,13 +28,11 @@ public class MemoryLeakException(params MemoryLeakExceptionInfo[] Infos) : Excep
return stringBuilder.ToString();
}
#endif
public override string Message
{
get
{
#if DEBUG
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Found {Infos.Length} memory lakes!");
foreach (var info in Infos)
@@ -58,9 +41,6 @@ public class MemoryLeakException(params MemoryLeakExceptionInfo[] Infos) : Excep
}
return stringBuilder.ToString();
#else
return $"There are still {Infos.Length} buffers that hold {Infos.Sum(i => (uint)i.Size)} bytes in total are not freed yet. Please free them before disposing. Switch to debug mode for more information.";
#endif
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.LowLevel;
public readonly struct FunctionPointer<T>
where T : Delegate
{
private readonly nint _ptr;
private readonly T _delegate;
/// <summary>
/// Gets the native function pointer associated with this function pointer instance.
/// </summary>
public readonly nint Pointer => _ptr;
/// <summary>
/// Gets the delegate instance associated with the specified function pointer.
/// </summary>
/// <remarks>This property uses <see
/// cref="Marshal.GetDelegateForFunctionPointer{TDelegate}"/> to convert the function
/// pointer to a delegate. Ensure that the function pointer is valid and compatible with the delegate type
/// <typeparamref name="T"/>.</remarks>
public T Invoke => _delegate;
/// <summary>
/// Creates a new instance of this function pointer with the following native pointer.
/// </summary>
/// <param name="ptr"></param>
public FunctionPointer(nint ptr)
{
_ptr = ptr;
_delegate = Marshal.GetDelegateForFunctionPointer<T>(ptr);
}
}

View File

@@ -1,9 +1,9 @@
using Misaki.HighPerformance.Unsafe.Buffer;
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Helpers;
namespace Misaki.HighPerformance.LowLevel.Helpers;
public unsafe struct HashMapHelper<TKey> : IDisposable
where TKey : unmanaged, IEquatable<TKey>
@@ -75,8 +75,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
private readonly int _sizeOfTValue;
private readonly int _log2MinGrowth;
private readonly Allocator _allocator;
private readonly AllocationOption _allocationOption;
private AllocationHandle* _handle;
public const int MINIMAL_CAPACITY = 64;
@@ -128,7 +127,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return totalSize;
}
public HashMapHelper(int capacity, int sizeOfTValue, uint minGrowth, Allocator allocator, AllocationOption allocationOption)
public HashMapHelper(int capacity, int sizeOfTValue, uint minGrowth, ref AllocationHandle handle, AllocationOption allocationOption)
{
if (capacity <= 0)
{
@@ -146,13 +145,12 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
_sizeOfTValue = sizeOfTValue;
_log2MinGrowth = BitOperations.Log2(minGrowth);
_allocator = allocator;
_allocationOption = allocationOption;
_handle = (AllocationHandle*)Unsafe.AsPointer(ref handle);
var totalSize = CalculateDataSize(_capacity, _bucketCapacity, sizeOfTValue,
out var keyOffset, out var nextOffset, out var bucketOffset);
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset);
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, allocationOption);
Clear();
}
@@ -169,7 +167,8 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private readonly int GetBucket(in TKey key)
{
return (int)((uint)key.GetHashCode() & _bucketCapacity - 1);
var h = (uint)key.GetHashCode();
return (int)(h & (uint)(_bucketCapacity - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -177,16 +176,16 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{
if ((uint)idx >= (uint)_capacity)
{
throw new InvalidOperationException($"Internal HashMap error. idx {idx}");
throw new InvalidOperationException($"Index {idx} is out of bounds for the hash map with capacity {_capacity}.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset)
private void AllocateBuffer(int totalSize, int keyOffset, int nextOffset, int bucketOffset, AllocationOption allocationOption)
{
var alignSize = sizeof(TKey) > sizeof(int) ? AlignOf<TKey>() : AlignOf<int>();
_buffer = AllocationManager.Allocate<byte>((uint)totalSize, (uint)alignSize, _allocator, _allocationOption);
_buffer = (byte*)_handle->Alloc(_handle->Allocator, (uint)totalSize, (uint)alignSize, allocationOption);
_keys = (TKey*)(_buffer + keyOffset);
_next = (int*)(_buffer + nextOffset);
_buckets = (int*)(_buffer + bucketOffset);
@@ -203,7 +202,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
var oldBuckets = _buckets;
var oldBucketCapacity = _bucketCapacity;
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset);
AllocateBuffer(totalSize, keyOffset, nextOffset, bucketOffset, AllocationOption.None);
_capacity = newCapacity;
_bucketCapacity = newBucketCapacity;
@@ -218,7 +217,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
}
}
Free(oldBuffer);
_handle->Free(_handle->Allocator, oldBuffer);
}
internal void Resize(int newCapacity)
@@ -240,7 +239,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
ResizeExact(capacity, capacity * 2);
}
public int Find(TKey key)
public int Find(in TKey key)
{
if (_allocatedIndex <= 0)
{
@@ -254,8 +253,6 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
if ((uint)entryIdx < (uint)_capacity)
{
var nextPtrs = _next;
var test = UnsafeUtilities.ReadArrayElement<TKey>(_keys, entryIdx);
var test2 = UnsafeUtilities.ReadArrayElement<TKey>(_next, 0);
while (!UnsafeUtilities.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
{
entryIdx = nextPtrs[entryIdx];
@@ -273,7 +270,8 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
public int TryAdd(in TKey key)
{
if (Find(key) != -1)
var k = key;
if (Find(in key) != -1)
{
return -1;
}
@@ -284,7 +282,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
if (_allocatedIndex >= _capacity && _firstFreeIndex < 0)
{
var newCap = Math.Min(MINIMAL_CAPACITY, CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth)));
var newCap = CalcCapacityCeilPow2(_capacity + (1 << _log2MinGrowth));
Resize(newCap);
}
@@ -313,7 +311,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return idx;
}
public int TryRemove(TKey key)
public int TryRemove(in TKey key)
{
if (_capacity == 0)
{
@@ -332,7 +330,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{
if (UnsafeUtilities.ReadArrayElement<TKey>(_keys, entryIdx).Equals(key))
{
++removed;
removed++;
// Found matching element, remove it
if (prevEntry < 0)
@@ -363,7 +361,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
return 0 != removed ? removed : -1;
}
public bool TryGetValue<TValue>(TKey key, out TValue item)
public bool TryGetValue<TValue>(in TKey key, out TValue item)
where TValue : unmanaged
{
var idx = Find(key);
@@ -486,7 +484,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{
if (IsCreated)
{
AllocationManager.Free(_buffer, _allocator);
_handle->Free(_handle->Allocator, _buffer);
_buffer = null;
_keys = null;

View File

@@ -0,0 +1,352 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.LowLevel.Helpers;
public static unsafe partial class MemoryUtilities
{
[DoesNotReturn]
private static void ThrowMustBeNullTerminatedString()
{
throw new ArgumentException("Arg_MustBeNullTerminatedString");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> LoadVector128(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector256<byte> LoadVector256(ref byte start, nuint offset)
=> Unsafe.ReadUnaligned<Vector256<byte>>(ref Unsafe.AddByteOffset(ref start, offset));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector128SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector128<byte>.Count - 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector256SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector256<byte>.Count - 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static nuint GetByteVector512SpanLength(nuint offset, int length)
=> (uint)((length - (int)offset) & ~(Vector512<byte>.Count - 1));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe nuint UnalignedCountVector128(byte* searchSpace)
{
var unaligned = (nint)searchSpace & (Vector128<byte>.Count - 1);
return (uint)((Vector128<byte>.Count - unaligned) & (Vector128<byte>.Count - 1));
}
/// <summary>
/// Searches for the first occurrence of a null byte (0x00) in a given byte array.
/// </summary>
/// <param name="searchSpace">A pointer to the byte array where the search will be performed.</param>
/// <returns>Returns the index of the first null byte found in the array..</returns>
/// <exception cref="ArgumentException">Thrown if the byte array is not null-terminated.</exception>"
public static unsafe int IndexOfNullByte(byte* searchSpace)
{
const int Length = int.MaxValue;
const uint uValue = 0; // Use uint for comparisons to avoid unnecessary 8->32 extensions
nuint offset = 0; // Use nuint for arithmetic to avoid unnecessary 64->32->64 truncations
var lengthToExamine = (nuint)(uint)Length;
if (Vector128.IsHardwareAccelerated)
{
// Avx2 branch also operates on Sse2 sizes, so check is combined.
lengthToExamine = UnalignedCountVector128(searchSpace);
}
SequentialScan:
while (lengthToExamine >= 8)
{
lengthToExamine -= 8;
if (uValue == searchSpace[offset])
goto Found;
if (uValue == searchSpace[offset + 1])
goto Found1;
if (uValue == searchSpace[offset + 2])
goto Found2;
if (uValue == searchSpace[offset + 3])
goto Found3;
if (uValue == searchSpace[offset + 4])
goto Found4;
if (uValue == searchSpace[offset + 5])
goto Found5;
if (uValue == searchSpace[offset + 6])
goto Found6;
if (uValue == searchSpace[offset + 7])
goto Found7;
offset += 8;
}
if (lengthToExamine >= 4)
{
lengthToExamine -= 4;
if (uValue == searchSpace[offset])
goto Found;
if (uValue == searchSpace[offset + 1])
goto Found1;
if (uValue == searchSpace[offset + 2])
goto Found2;
if (uValue == searchSpace[offset + 3])
goto Found3;
offset += 4;
}
while (lengthToExamine > 0)
{
lengthToExamine -= 1;
if (uValue == searchSpace[offset])
goto Found;
offset += 1;
}
// We get past SequentialScan only if IsHardwareAccelerated is true; and remain length is greater than Vector length.
// However, we still have the redundant check to allow the JIT to see that the code is unreachable and eliminate it when the platform does not
// have hardware accelerated. After processing Vector lengths we return to SequentialScan to finish any remaining.
if (Vector512.IsHardwareAccelerated)
{
if (offset < Length)
{
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
{
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
// with no upper bound e.g. String.strlen.
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
// This ensures we do not fault across memory pages while searching for an end of string.
var search = Vector128.Load(searchSpace + offset);
// Same method as below
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
if ((((uint)searchSpace + offset) & (nuint)(Vector512<byte>.Count - 1)) != 0)
{
// Not currently aligned to Vector512 (is aligned to Vector256); this can cause a problem for searches
// with no upper bound e.g. String.strlen.
// Start with a check on Vector256 to align to Vector512, before moving to processing Vector256.
// This ensures we do not fault across memory pages while searching for an end of string.
var search = Vector256.Load(searchSpace + offset);
// Same method as below
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector256<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
lengthToExamine = GetByteVector512SpanLength(offset, Length);
if (lengthToExamine > offset)
{
do
{
var search = Vector512.Load(searchSpace + offset);
var matches = Vector512.Equals(Vector512<byte>.Zero, search).ExtractMostSignificantBits();
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
// So the bit position in 'matches' corresponds to the element offset.
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector512<byte>.Count;
continue;
}
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
} while (lengthToExamine > offset);
}
lengthToExamine = GetByteVector256SpanLength(offset, Length);
if (lengthToExamine > offset)
{
var search = Vector256.Load(searchSpace + offset);
// Same method as above
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector256<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
lengthToExamine = GetByteVector128SpanLength(offset, Length);
if (lengthToExamine > offset)
{
var search = Vector128.Load(searchSpace + offset);
// Same method as above
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
if (offset < Length)
{
lengthToExamine = (Length - offset);
goto SequentialScan;
}
}
}
else if (Vector256.IsHardwareAccelerated)
{
if (offset < Length)
{
if ((((uint)searchSpace + offset) & (nuint)(Vector256<byte>.Count - 1)) != 0)
{
// Not currently aligned to Vector256 (is aligned to Vector128); this can cause a problem for searches
// with no upper bound e.g. String.strlen.
// Start with a check on Vector128 to align to Vector256, before moving to processing Vector256.
// This ensures we do not fault across memory pages while searching for an end of string.
var search = Vector128.Load(searchSpace + offset);
// Same method as below
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
lengthToExamine = GetByteVector256SpanLength(offset, Length);
if (lengthToExamine > offset)
{
do
{
var search = Vector256.Load(searchSpace + offset);
var matches = Vector256.Equals(Vector256<byte>.Zero, search).ExtractMostSignificantBits();
// Note that MoveMask has converted the equal vector elements into a set of bit flags,
// So the bit position in 'matches' corresponds to the element offset.
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector256<byte>.Count;
continue;
}
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
} while (lengthToExamine > offset);
}
lengthToExamine = GetByteVector128SpanLength(offset, Length);
if (lengthToExamine > offset)
{
var search = Vector128.Load(searchSpace + offset);
// Same method as above
var matches = Vector128.Equals(Vector128<byte>.Zero, search).ExtractMostSignificantBits();
if (matches == 0)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
}
else
{
// Find bitflag offset of first match and add to current offset
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
}
if (offset < Length)
{
lengthToExamine = (Length - offset);
goto SequentialScan;
}
}
}
else if (Vector128.IsHardwareAccelerated)
{
if (offset < Length)
{
lengthToExamine = GetByteVector128SpanLength(offset, Length);
while (lengthToExamine > offset)
{
var search = Vector128.Load(searchSpace + offset);
// Same method as above
var compareResult = Vector128.Equals(Vector128<byte>.Zero, search);
if (compareResult == Vector128<byte>.Zero)
{
// Zero flags set so no matches
offset += (nuint)Vector128<byte>.Count;
continue;
}
// Find bitflag offset of first match and add to current offset
var matches = compareResult.ExtractMostSignificantBits();
return (int)(offset + (uint)BitOperations.TrailingZeroCount(matches));
}
if (offset < Length)
{
lengthToExamine = (Length - offset);
goto SequentialScan;
}
}
}
ThrowMustBeNullTerminatedString();
Found: // Workaround for https://github.com/dotnet/runtime/issues/8795
return (int)offset;
Found1:
return (int)(offset + 1);
Found2:
return (int)(offset + 2);
Found3:
return (int)(offset + 3);
Found4:
return (int)(offset + 4);
Found5:
return (int)(offset + 5);
Found6:
return (int)(offset + 6);
Found7:
return (int)(offset + 7);
}
}

View File

@@ -1,9 +1,9 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Helpers;
namespace Misaki.HighPerformance.LowLevel.Helpers;
public static unsafe class MemoryUtilities
public static unsafe partial class MemoryUtilities
{
[StructLayout(LayoutKind.Sequential)]
private struct AlignOfHelper<T>
@@ -102,6 +102,11 @@ public static unsafe class MemoryUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemClear(void* ptr, nuint size)
{
if (ptr == null || size == 0)
{
return;
}
NativeMemory.Clear(ptr, size);
}
@@ -114,6 +119,11 @@ public static unsafe class MemoryUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemSet(void* ptr, byte value, nuint size)
{
if (ptr == null || size == 0)
{
return;
}
NativeMemory.Fill(ptr, size, value);
}
@@ -126,6 +136,11 @@ public static unsafe class MemoryUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MemCpy(void* source, void* destination, nuint size)
{
if (source == null || destination == null || size == 0)
{
return;
}
NativeMemory.Copy(source, destination, size);
}

View File

@@ -1,6 +1,8 @@
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Helpers;
namespace Misaki.HighPerformance.LowLevel.Helpers;
/// <summary>
/// Provides extension methods for copying elements between unsafe collections and spans, converting collections to
@@ -49,7 +51,7 @@ public unsafe static class UnsafeCollectionExtensions
fixed (T* ptr = destination)
{
SystemUnsfae.CopyBlock(ptr + destinationIndex, (byte*)source.GetUnsafePtr() + (sourceIndex * sizeof(T)), (uint)(length * sizeof(T)));
SystemUnsfae.CopyBlock(ptr + destinationIndex, (byte*)source.GetUnsafePtr() + sourceIndex * sizeof(T), (uint)(length * sizeof(T)));
}
}
@@ -94,45 +96,10 @@ public unsafe static class UnsafeCollectionExtensions
fixed (T* ptr = source)
{
SystemUnsfae.CopyBlock((byte*)destination.GetUnsafePtr() + (destinationIndex * sizeof(T)), ptr + sourceIndex, (uint)(length * sizeof(T)));
SystemUnsfae.CopyBlock((byte*)destination.GetUnsafePtr() + destinationIndex * sizeof(T), ptr + sourceIndex, (uint)(length * sizeof(T)));
}
}
/// <summary>
/// Converts an UnsafeCollection of unmanaged types into a standard collection.
/// </summary>
/// <typeparam name="T">Represents a type that is unmanaged, allowing for direct memory manipulation.</typeparam>
/// <param name="source">The UnsafeCollection instance that contains the data to be converted.</param>
/// <returns>A new collection containing the elements from the UnsafeCollection.</returns>
public static T[] ToArray<T>(this IUnsafeCollection<T> source)
where T : unmanaged
{
var array = new T[source.Count];
fixed (T* ptr = array)
{
SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
return array;
}
/// <summary>
/// Converts an unmanaged collection into a list by copying its elements into a new list.
/// </summary>
/// <typeparam name="T">Represents a type that is unmanaged, allowing for direct memory manipulation.</typeparam>
/// <param name="source">The collection from which elements are copied to create the new list.</param>
/// <returns>A list containing the elements from the specified unmanaged collection.</returns>
public static List<T> ToList<T>(this IUnsafeCollection<T> source)
where T : unmanaged
{
var list = new List<T>(source.Count);
fixed (T* ptr = list.ToArray())
{
SystemUnsfae.CopyBlock(ptr, source.GetUnsafePtr(), (uint)(source.Count * sizeof(T)));
}
return list;
}
/// <summary>
/// Converts an UnsafeCollection into a Span for efficient memory access.
/// </summary>
@@ -145,38 +112,27 @@ public unsafe static class UnsafeCollectionExtensions
return new(source.GetUnsafePtr(), source.Count);
}
/// <summary>
/// Finds the index of a specified value in a collection. Returns -1 if the value is not found.
/// </summary>
/// <typeparam name="T">The type of elements in the collection, which must support equality comparison.</typeparam>
/// <param name="source">The collection to search for the specified value.</param>
/// <param name="value">The value to locate within the collection.</param>
/// <param name="index">Outputs the index of the found value or -1 if not found.</param>
public static void IndexOf<T>(this IUnsafeCollection<T> source, T value, out int index)
where T : unmanaged, IEquatable<T>
public static UnsafeArray<T> ToUnsafeArray<T>(this T[] source, Allocator allocator)
where T : unmanaged
{
for (var i = 0; i < source.Count; i++)
var array = new UnsafeArray<T>(source.Length, allocator);
fixed (T* ptr = source)
{
if (UnsafeUtilities.ReadArrayElement<T>(source.GetUnsafePtr(), i).Equals(value))
{
index = i;
return;
}
MemCpy(array.GetUnsafePtr(), ptr, (uint)(source.Length * sizeof(T)));
}
index = -1;
return array;
}
/// <summary>
/// Checks if a specified value exists within an unsafe collection of unmanaged types.
/// </summary>
/// <typeparam name="T">Represents a type that is unmanaged and supports equality comparison.</typeparam>
/// <param name="source">The collection being searched for the specified value.</param>
/// <param name="value">The value being searched for within the collection.</param>
/// <returns>Returns true if the value is found; otherwise, returns false.</returns>
public static bool Conations<T>(this IUnsafeCollection<T> source, T value)
where T : unmanaged, IEquatable<T>
public static UnsafeList<T> ToUnsafeList<T>(this List<T> source, Allocator allocator)
where T : unmanaged
{
source.IndexOf(value, out var index);
return index != -1;
var list = new UnsafeList<T>(source.Count, allocator);
fixed (T* ptr = CollectionsMarshal.AsSpan(source))
{
MemCpy(list.GetUnsafePtr(), ptr, (uint)(source.Count * sizeof(T)));
}
return list;
}
}

View File

@@ -1,7 +1,7 @@
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Unsafe.Helpers;
namespace Misaki.HighPerformance.LowLevel.Helpers;
public static unsafe class UnsafeUtilities
{
@@ -39,7 +39,7 @@ public static unsafe class UnsafeUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* ReadArrayElementUnsafe<T>(void* ptr, int index) where T : unmanaged
{
return (T*)((byte*)ptr + (index * sizeof(T)));
return (T*)((byte*)ptr + index * sizeof(T));
}
/// <summary>

View File

@@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Buffer\FixedString.tt">
<LastGenOutput>FixedString.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="Buffer\FixedStackString.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>FixedStackString.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Buffer\FixedString.cs">
<DependentUpon>FixedString.tt</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Buffer\FixedStackString.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>FixedStackString.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Update="float4.tt">
<LastGenOutput>float4.gen.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="float3.tt">
<LastGenOutput>float3.gen.cs</LastGenOutput>
<Generator>TextTemplatingFileGenerator</Generator>
</None>
<None Update="float2.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>float2.gen.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Float.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Float.tt</DependentUpon>
</Compile>
<Compile Update="float2.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>float2.tt</DependentUpon>
</Compile>
<Compile Update="float3.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>float3.tt</DependentUpon>
</Compile>
<Compile Update="float4.gen.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>float4.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#+
public int[] dimensions = new[] { 2, 3, 4 };
public IEnumerable<string> GenerateSwizzles(string[] pool, int maxLen)
{
IEnumerable<string> Recurse(string prefix, int depth)
{
if (depth == 0)
{
yield return prefix;
}
else
{
foreach (var c in pool)
{
foreach (var s in Recurse(prefix + c, depth - 1))
{
yield return s;
}
}
}
}
return Enumerable.Range(2, maxLen - 1).SelectMany(len => Recurse("", len));
}
#>

View File

@@ -0,0 +1,49 @@
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
namespace Misaki.HighPerformance.Mathematics;
internal static class Vectorize
{
public static Vector128<float> AsVector128(this float2 value)
{
Unsafe.SkipInit(out Vector128<float> result);
Unsafe.WriteUnaligned(ref Unsafe.As<Vector128<float>, byte>(ref result), value);
return result;
}
public static Vector128<float> AsVector128(this float3 value)
{
Unsafe.SkipInit(out Vector128<float> result);
Unsafe.WriteUnaligned(ref Unsafe.As<Vector128<float>, byte>(ref result), value);
return result;
}
public static Vector128<float> AsVector128(this float4 value)
{
Unsafe.SkipInit(out Vector128<float> result);
Unsafe.WriteUnaligned(ref Unsafe.As<Vector128<float>, byte>(ref result), value);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 AsFloat2(this Vector128<float> value)
{
ref var address = ref Unsafe.As<Vector128<float>, byte>(ref value);
return Unsafe.ReadUnaligned<float2>(ref address);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float3 AsFloat3(this Vector128<float> value)
{
ref var address = ref Unsafe.As<Vector128<float>, byte>(ref value);
return Unsafe.ReadUnaligned<float3>(ref address);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float4 AsFloat4(this Vector128<float> value)
{
ref var address = ref Unsafe.As<Vector128<float>, byte>(ref value);
return Unsafe.ReadUnaligned<float4>(ref address);
}
}

View File

@@ -0,0 +1,272 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
public struct float2
{
public float x;
public float y;
public float2(float value)
{
this.x = value;
this.y = value;
}
public float2(float x, float y)
{
this.x = x;
this.y = y;
}
public float2(float3 value)
{
this.x = value.x;
this.y = value.y;
}
public float2(float4 value)
{
this.x = value.x;
this.y = value.y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator +(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator +(float2 lhs, float rhs)
{
return lhs + new float2(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator -(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator -(float2 lhs, float rhs)
{
return lhs - new float2(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator *(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator *(float2 lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator /(float2 lhs, float2 rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator /(float2 lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float2 operator -(float2 value)
{
return (-value.AsVector128()).AsFloat2();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(float2 lhs, float2 rhs)
{
return lhs.AsVector128() == rhs.AsVector128();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(float2 lhs, float2 rhs)
{
return !(lhs == rhs);
}
public readonly float2 xx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.x);
}
public readonly float2 xy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.y);
}
public readonly float2 yx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.x);
}
public readonly float2 yy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.y);
}
public readonly float3 xxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.x);
}
public readonly float3 xxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.y);
}
public readonly float3 xyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.x);
}
public readonly float3 xyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.y);
}
public readonly float3 yxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.x);
}
public readonly float3 yxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.y);
}
public readonly float3 yyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.x);
}
public readonly float3 yyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.y);
}
public readonly float4 xxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.x);
}
public readonly float4 xxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.y);
}
public readonly float4 xxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.x);
}
public readonly float4 xxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.y);
}
public readonly float4 xyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.x);
}
public readonly float4 xyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.y);
}
public readonly float4 xyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.x);
}
public readonly float4 xyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.y);
}
public readonly float4 yxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.x);
}
public readonly float4 yxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.y);
}
public readonly float4 yxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.x);
}
public readonly float4 yxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.y);
}
public readonly float4 yyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.x);
}
public readonly float4 yyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.y);
}
public readonly float4 yyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.x);
}
public readonly float4 yyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.y);
}
public override readonly string ToString()
{
return $"(x: {this.x}, y: {this.y})";
}
}

View File

@@ -0,0 +1,161 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Utilities.ttinclude" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
<#
var dimension = 2;
var components = new[] { "x", "y" };
var structName = $"float{dimension}";
#>
public struct <#= structName #>
{
<# for(int i = 0; i < dimension; i++) { #>
public float <#= components[i] #>;
<# } #>
public <#= structName #>(float value)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = value;
<# } #>
}
public <#= structName #>(<#= string.Join(", ", components.Take(dimension).Select(c => $"float {c}")) #>)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = <#= components[i] #>;
<# } #>
}
<# foreach (var otherDim in dimensions.Where(d => d != dimension))
{
string otherStructName = $"float{otherDim}";
if (otherDim < dimension)
{
#>
public <#= structName #>(<#= otherStructName #> value)
{
<# for(int i = 0; i < Math.Min(dimension, otherDim); i++) { #>
this.<#= components[i] #> = value.<#= components[i] #>;
<#
}
for(int i = otherDim; i < dimension; i++) {
#>
this.<#= components[i] #> = 0.0f;
<# } #>
}
<# }
else
{ #>
public <#= structName #>(<#= otherStructName #> value)
{
<#
for(int i = 0; i < dimension; i++) {
#>
this.<#= components[i] #> = value.<#= components[i] #>;
<# } #>
}
<#
}
}
#>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator +(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator +(<#= structName #> lhs, float rhs)
{
return lhs + new <#= structName #>(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator -(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator -(<#= structName #> lhs, float rhs)
{
return lhs - new <#= structName #>(rhs);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator *(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator *(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator /(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator /(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static <#= structName #> operator -(<#= structName #> value)
{
return (-value.AsVector128()).AsFloat<#= dimension #>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(<#= structName #> lhs, <#= structName #> rhs)
{
return lhs.AsVector128() == rhs.AsVector128();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(<#= structName #> lhs, <#= structName #> rhs)
{
return !(lhs == rhs);
}
<#
var validComponents = components.Take(dimension).ToArray();
var swizzles = GenerateSwizzles(validComponents, 4);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"float{targetDim}";
#>
public readonly <#= targetStruct #> <#= swizzle #>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new <#= targetStruct #>(<#= string.Join(", ", swizzle.Select(c => $"this.{c}")) #>);
}
<#
}
#>
public override readonly string ToString()
{
return $"(<#= string.Join(", ", components.Take(dimension).Select(c => $"{c}: {{this.{c}}}")) #>)";
}
}

View File

@@ -0,0 +1,791 @@
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
public struct float3
{
public float x;
public float y;
public float z;
public float3(float value)
{
this.x = value;
this.y = value;
this.z = value;
}
public float3(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
public float3(float2 value)
{
this.x = value.x;
this.y = value.y;
this.z = 0.0f;
}
public float3(float4 value)
{
this.x = value.x;
this.y = value.y;
this.z = value.z;
}
public static float3 operator +(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat3();
}
public static float3 operator +(float3 lhs, float rhs)
{
return lhs + new float3(rhs);
}
public static float3 operator -(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat3();
}
public static float3 operator -(float3 lhs, float rhs)
{
return lhs - new float3(rhs);
}
public static float3 operator *(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat3();
}
public static float3 operator *(float3 lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat3();
}
public static float3 operator /(float3 lhs, float3 rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat3();
}
public static float3 operator /(float3 lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat3();
}
public static float3 operator -(float3 value)
{
return (-value.AsVector128()).AsFloat3();
}
public float2 xx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.x);
}
public float2 xy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.y);
}
public float2 xz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.x, this.z);
}
public float2 yx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.x);
}
public float2 yy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.y);
}
public float2 yz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.y, this.z);
}
public float2 zx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.z, this.x);
}
public float2 zy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.z, this.y);
}
public float2 zz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float2(this.z, this.z);
}
public float3 xxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.x);
}
public float3 xxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.y);
}
public float3 xxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.x, this.z);
}
public float3 xyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.x);
}
public float3 xyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.y);
}
public float3 xyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.y, this.z);
}
public float3 xzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.z, this.x);
}
public float3 xzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.z, this.y);
}
public float3 xzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.x, this.z, this.z);
}
public float3 yxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.x);
}
public float3 yxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.y);
}
public float3 yxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.x, this.z);
}
public float3 yyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.x);
}
public float3 yyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.y);
}
public float3 yyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.y, this.z);
}
public float3 yzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.z, this.x);
}
public float3 yzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.z, this.y);
}
public float3 yzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.y, this.z, this.z);
}
public float3 zxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.x, this.x);
}
public float3 zxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.x, this.y);
}
public float3 zxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.x, this.z);
}
public float3 zyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.y, this.x);
}
public float3 zyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.y, this.y);
}
public float3 zyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.y, this.z);
}
public float3 zzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.z, this.x);
}
public float3 zzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.z, this.y);
}
public float3 zzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float3(this.z, this.z, this.z);
}
public float4 xxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.x);
}
public float4 xxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.y);
}
public float4 xxxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.x, this.z);
}
public float4 xxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.x);
}
public float4 xxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.y);
}
public float4 xxyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.y, this.z);
}
public float4 xxzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.z, this.x);
}
public float4 xxzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.z, this.y);
}
public float4 xxzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.x, this.z, this.z);
}
public float4 xyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.x);
}
public float4 xyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.y);
}
public float4 xyxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.x, this.z);
}
public float4 xyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.x);
}
public float4 xyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.y);
}
public float4 xyyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.y, this.z);
}
public float4 xyzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.z, this.x);
}
public float4 xyzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.z, this.y);
}
public float4 xyzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.y, this.z, this.z);
}
public float4 xzxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.x, this.x);
}
public float4 xzxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.x, this.y);
}
public float4 xzxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.x, this.z);
}
public float4 xzyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.y, this.x);
}
public float4 xzyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.y, this.y);
}
public float4 xzyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.y, this.z);
}
public float4 xzzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.z, this.x);
}
public float4 xzzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.z, this.y);
}
public float4 xzzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.x, this.z, this.z, this.z);
}
public float4 yxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.x);
}
public float4 yxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.y);
}
public float4 yxxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.x, this.z);
}
public float4 yxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.x);
}
public float4 yxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.y);
}
public float4 yxyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.y, this.z);
}
public float4 yxzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.z, this.x);
}
public float4 yxzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.z, this.y);
}
public float4 yxzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.x, this.z, this.z);
}
public float4 yyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.x);
}
public float4 yyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.y);
}
public float4 yyxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.x, this.z);
}
public float4 yyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.x);
}
public float4 yyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.y);
}
public float4 yyyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.y, this.z);
}
public float4 yyzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.z, this.x);
}
public float4 yyzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.z, this.y);
}
public float4 yyzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.y, this.z, this.z);
}
public float4 yzxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.x, this.x);
}
public float4 yzxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.x, this.y);
}
public float4 yzxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.x, this.z);
}
public float4 yzyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.y, this.x);
}
public float4 yzyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.y, this.y);
}
public float4 yzyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.y, this.z);
}
public float4 yzzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.z, this.x);
}
public float4 yzzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.z, this.y);
}
public float4 yzzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.y, this.z, this.z, this.z);
}
public float4 zxxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.x, this.x);
}
public float4 zxxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.x, this.y);
}
public float4 zxxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.x, this.z);
}
public float4 zxyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.y, this.x);
}
public float4 zxyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.y, this.y);
}
public float4 zxyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.y, this.z);
}
public float4 zxzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.z, this.x);
}
public float4 zxzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.z, this.y);
}
public float4 zxzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.x, this.z, this.z);
}
public float4 zyxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.x, this.x);
}
public float4 zyxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.x, this.y);
}
public float4 zyxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.x, this.z);
}
public float4 zyyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.y, this.x);
}
public float4 zyyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.y, this.y);
}
public float4 zyyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.y, this.z);
}
public float4 zyzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.z, this.x);
}
public float4 zyzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.z, this.y);
}
public float4 zyzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.y, this.z, this.z);
}
public float4 zzxx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.x, this.x);
}
public float4 zzxy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.x, this.y);
}
public float4 zzxz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.x, this.z);
}
public float4 zzyx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.y, this.x);
}
public float4 zzyy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.y, this.y);
}
public float4 zzyz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.y, this.z);
}
public float4 zzzx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.z, this.x);
}
public float4 zzzy
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.z, this.y);
}
public float4 zzzz
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new float4(this.z, this.z, this.z, this.z);
}
public override readonly string ToString()
{
return $"(x: {this.x}, y: {this.y}, z: {this.z})";
}
}

View File

@@ -0,0 +1,140 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Utilities.ttinclude" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
<#
var dimension = 3;
var components = new[] { "x", "y", "z" };
var structName = $"float{dimension}";
#>
public struct <#= structName #>
{
<# for(int i = 0; i < dimension; i++) { #>
public float <#= components[i] #>;
<# } #>
public <#= structName #>(float value)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = value;
<# } #>
}
public <#= structName #>(<#= string.Join(", ", components.Take(dimension).Select(c => $"float {c}")) #>)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = <#= components[i] #>;
<# } #>
}
<# foreach (var otherDim in dimensions.Where(d => d != dimension))
{
string otherStructName = $"float{otherDim}";
if (otherDim < dimension)
{
#>
public <#= structName #>(<#= otherStructName #> value)
{
<# for(int i = 0; i < Math.Min(dimension, otherDim); i++) { #>
this.<#= components[i] #> = value.<#= components[i] #>;
<#
}
// Fill remaining components with 0
for(int i = otherDim; i < dimension; i++) {
#>
this.<#= components[i] #> = 0.0f;
<# } #>
}
<# }
else
{ #>
public <#= structName #>(<#= otherStructName #> value)
{
<#
for(int i = 0; i < dimension; i++) {
#>
this.<#= components[i] #> = value.<#= components[i] #>;
<# } #>
}
<#
}
}
#>
public static <#= structName #> operator +(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator +(<#= structName #> lhs, float rhs)
{
return lhs + new <#= structName #>(rhs);
}
public static <#= structName #> operator -(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> lhs, float rhs)
{
return lhs - new <#= structName #>(rhs);
}
public static <#= structName #> operator *(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator *(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> value)
{
return (-value.AsVector128()).AsFloat<#= dimension #>();
}
<#
var validComponents = components.Take(dimension).ToArray();
var swizzles = GenerateSwizzles(validComponents, 4);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"float{targetDim}";
#>
public <#= targetStruct #> <#= swizzle #>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new <#= targetStruct #>(<#= string.Join(", ", swizzle.Select(c => $"this.{c}")) #>);
}
<#
}
#>
public override readonly string ToString()
{
return $"(<#= string.Join(", ", components.Take(dimension).Select(c => $"{c}: {{this.{c}}}")) #>)";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file="Utilities.ttinclude" #>
<#@ output extension=".gen.cs" #>
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Mathematics;
<#
var dimension = 4;
var components = new[] { "x", "y", "z", "w" };
var structName = $"float{dimension}";
#>
public struct <#= structName #>
{
<# for(int i = 0; i < dimension; i++) { #>
public float <#= components[i] #>;
<# } #>
public <#= structName #>(float value)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = value;
<# } #>
}
public <#= structName #>(<#= string.Join(", ", components.Take(dimension).Select(c => $"float {c}")) #>)
{
<# for(int i = 0; i < dimension; i++) { #>
this.<#= components[i] #> = <#= components[i] #>;
<# } #>
}
<# foreach (var otherDim in dimensions.Where(d => d != dimension))
{
string otherStructName = $"float{otherDim}";
if (otherDim < dimension)
{
#>
public <#= structName #>(<#= otherStructName #> value)
{
<# for(int i = 0; i < Math.Min(dimension, otherDim); i++) { #>
this.<#= components[i] #> = value.<#= components[i] #>;
<#
}
// Fill remaining components with 0
for(int i = otherDim; i < dimension; i++) {
#>
this.<#= components[i] #> = 0.0f;
<# } #>
}
<# }
else
{ #>
public <#= structName #>(<#= otherStructName #> value)
{
<#
for(int i = 0; i < dimension; i++) {
#>
this.<#= components[i] #> = value.<#= components[i] #>;
<# } #>
}
<#
}
}
#>
public static <#= structName #> operator +(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() + rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator +(<#= structName #> lhs, float rhs)
{
return lhs + new <#= structName #>(rhs);
}
public static <#= structName #> operator -(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() - rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> lhs, float rhs)
{
return lhs - new <#= structName #>(rhs);
}
public static <#= structName #> operator *(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() * rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator *(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() * rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, <#= structName #> rhs)
{
return (lhs.AsVector128() / rhs.AsVector128()).AsFloat<#= dimension #>();
}
public static <#= structName #> operator /(<#= structName #> lhs, float rhs)
{
return (lhs.AsVector128() / rhs).AsFloat<#= dimension #>();
}
public static <#= structName #> operator -(<#= structName #> value)
{
return (-value.AsVector128()).AsFloat<#= dimension #>();
}
<#
var validComponents = components.Take(dimension).ToArray();
var swizzles = GenerateSwizzles(validComponents, 4);
foreach (var swizzle in swizzles)
{
var targetDim = swizzle.Length;
var targetStruct = $"float{targetDim}";
#>
public <#= targetStruct #> <#= swizzle #>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new <#= targetStruct #>(<#= string.Join(", ", swizzle.Select(c => $"this.{c}")) #>);
}
<#
}
#>
public override readonly string ToString()
{
return $"(<#= string.Join(", ", components.Take(dimension).Select(c => $"{c}: {{this.{c}}}")) #>)";
}
}

View File

@@ -1,6 +1,6 @@
using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.Unsafe.Buffer;
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Test;
@@ -13,7 +13,6 @@ public unsafe class CollectionBenchmark
[GlobalSetup]
public void Setup()
{
AllocationManager.Initialize();
}
[Benchmark]
@@ -34,7 +33,8 @@ public unsafe class CollectionBenchmark
{
array[i] = i;
}
AllocationManager.ResetCurrent();
AllocationManager.TempAllocator.Reset();
}
[Benchmark]

View File

@@ -0,0 +1,47 @@
using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.LowLevel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Test;
[MemoryDiagnoser]
public unsafe class FunctionPtrBenchmark
{
private delegate float FunctionPointerDelegate(float a, float b);
private float _sink;
private FunctionPointer<FunctionPointerDelegate> _addManaged;
private delegate* unmanaged<float, float, float> _addUnmanaged;
public FunctionPtrBenchmark()
{
_addManaged = new(Marshal.GetFunctionPointerForDelegate<FunctionPointerDelegate>(Add));
_addUnmanaged = &AddUnmanaged;
}
private float Add(float a, float b)
{
return a + b;
}
[UnmanagedCallersOnly]
private static float AddUnmanaged(float a, float b)
{
return a + b;
}
[Benchmark(Baseline = true)]
[MethodImpl(MethodImplOptions.NoInlining)]
public void InvokeManaged()
{
_sink = _addManaged.Invoke(1.0f, 2.0f);
}
[Benchmark]
public void InvokeUnmanaged()
{
_sink = _addUnmanaged(1.0f, 2.0f);
}
}

View File

@@ -0,0 +1,114 @@
using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.LowLevel.Collections;
namespace Misaki.HighPerformance.Test;
public class HashMapBenchmark
{
private UnsafeHashMap<int, float> _unsafeHashMap;
private Dictionary<int, float> _dictionary = null!;
[Params(10, 100, 1000)]
public int count;
[IterationSetup]
public void Setup()
{
//_unsafeHashMap = new UnsafeHashMap<int, float>(count, Allocator.Persistent);
_dictionary = new Dictionary<int, float>(count);
for (var i = 0; i < count; i++)
{
//_unsafeHashMap.Add(i, i);
_dictionary.Add(i, i);
}
}
[Benchmark]
public void UnsafeHashMapAdd()
{
for (var i = 0; i < count; i++)
{
_unsafeHashMap.Add(count + i, i);
}
}
[Benchmark(Baseline = true)]
public void DictionaryAdd()
{
for (var i = 0; i < count; i++)
{
_dictionary.Add(count + i, i);
}
}
public void UnsafeHashMapRemove()
{
for (var i = 0; i < count; i++)
{
_unsafeHashMap.Remove(i);
}
}
public void DictionaryRemove()
{
for (var i = 0; i < count; i++)
{
_dictionary.Remove(i);
}
}
public void UnsafeHashMapRandomRead()
{
for (var i = 0; i < count; i++)
{
var value = Random.Shared.Next(0, count);
if (_unsafeHashMap.TryGetValue(value, out var result))
{
var r2 = result + result;
}
}
}
public void DictionaryRandomRead()
{
for (var i = 0; i < count; i++)
{
var value = Random.Shared.Next(0, count);
if (_dictionary.TryGetValue(value, out var result))
{
var r2 = result + result;
}
}
}
public void UnsafeHashMapRandomWrite()
{
for (var i = 0; i < count; i++)
{
var value = Random.Shared.Next(0, count);
if (_unsafeHashMap.TryGetValue(value, out var result))
{
_unsafeHashMap[value] = result + 1;
}
}
}
public void DictionaryRandomWrite()
{
for (var i = 0; i < count; i++)
{
var value = Random.Shared.Next(0, count);
if (_dictionary.TryGetValue(value, out var result))
{
_dictionary[value] = result + 1;
}
}
}
[IterationCleanup]
public void Cleanup()
{
//_unsafeHashMap.Dispose();
}
}

View File

@@ -1,5 +1,5 @@
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.LowLevel.Collections;
using System.Numerics;
using System.Runtime.CompilerServices;

View File

@@ -14,7 +14,10 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance.Unsafe\Misaki.HighPerformance.Unsafe.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance.Image\Misaki.HighPerformance.Image.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance.Jobs\Misaki.HighPerformance.Jobs.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance.LowLevel\Misaki.HighPerformance.LowLevel.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance.Mathematics\Misaki.HighPerformance.Mathematics.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup>

View File

@@ -1,7 +1,7 @@
using BenchmarkDotNet.Attributes;
using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.Test.Jobs;
using Misaki.HighPerformance.Unsafe.Collections;
using System.Numerics;
namespace Misaki.HighPerformance.Test;

View File

@@ -1,4 +1,4 @@
using BenchmarkDotNet.Running;
using Misaki.HighPerformance.Test;
BenchmarkRunner.Run<CollectionBenchmark>();
BenchmarkRunner.Run<FunctionPtrBenchmark>();

View File

@@ -1,2 +0,0 @@
global using static Misaki.HighPerformance.Unsafe.Helpers.MemoryUtilities;
global using SystemUnsfae = System.Runtime.CompilerServices.Unsafe;

View File

@@ -1,32 +0,0 @@
using Misaki.HighPerformance.Unsafe.Collections;
using Misaki.HighPerformance.Unsafe.Collections.Contracts;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Unsafe.Buffer;
[StructLayout(LayoutKind.Sequential)]
public unsafe struct AllocationHandler : IAllocator
{
public unsafe T* Allocate<T>(uint size, uint alignSize, AllocationOption allocationOption)
where T : unmanaged
{
throw new NotImplementedException();
}
public unsafe T* Reallocate<T>(T* buffer, uint size, uint alignSize)
where T : unmanaged
{
throw new NotImplementedException();
}
public unsafe void Free<T>(T* buffer, uint size, uint alignSize)
where T : unmanaged
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@@ -1,207 +0,0 @@
#define UNSAFE_COLLECTION_CHECK
using Misaki.HighPerformance.Unsafe.Collections;
using System.Runtime.CompilerServices;
#if UNSAFE_COLLECTION_CHECK
#if DEBUG
using System.Diagnostics;
#endif
#endif
namespace Misaki.HighPerformance.Unsafe.Buffer;
// TODO: Custom allocator
public static unsafe class AllocationManager
{
private const uint _DEFAULT_ARENA_SIZE = 512 * 1024;
private static ThreadLocal<DynamicArena>? _threadLocalArena;
#if UNSAFE_COLLECTION_CHECK
private static Dictionary<IntPtr, MemoryLeakExceptionInfo> _allocated = null!;
#endif
/// <summary>
/// Initializes the AllocationManager with a specified initial size for the memory arena.
/// </summary>
/// <param name="initialSize">The initial size in bytes for the memory arena.</param>
public static void Initialize(uint initialSize = _DEFAULT_ARENA_SIZE)
{
if (initialSize <= 0)
{
return;
}
_threadLocalArena = new ThreadLocal<DynamicArena>(() => new DynamicArena(initialSize), true);
#if UNSAFE_COLLECTION_CHECK
_allocated = new Dictionary<nint, MemoryLeakExceptionInfo>(32);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EnsureInitialized()
{
if (_threadLocalArena == null)
{
throw new InvalidOperationException("The AllocationManager has not been initialized.");
}
}
public static T* Allocate<T>(uint size, uint alignSize, Allocator allocator, AllocationOption allocationOption)
where T : unmanaged
{
if (allocationOption.HasFlag(AllocationOption.UnTracked))
{
return (T*)AlignedAlloc(size, alignSize);
}
EnsureInitialized();
T* buffer;
switch (allocator)
{
case Allocator.Temp:
buffer = (T*)_threadLocalArena!.Value.Allocate(size * (uint)sizeof(T), alignSize, allocationOption);
break;
case Allocator.Persistent:
var allocationSize = size * (nuint)sizeof(T);
buffer = (T*)AlignedAlloc(allocationSize, alignSize);
#if UNSAFE_COLLECTION_CHECK
_allocated[(IntPtr)buffer] = new MemoryLeakExceptionInfo
{
Size = allocationSize,
#if DEBUG
StackTrace = new StackTrace(true)
#endif
};
#endif
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(buffer, allocationSize);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(allocator), "Invalid allocator type.");
}
return buffer;
}
public static T* Realloc<T>(T* buffer, uint size, uint alignSize, Allocator allocator)
where T : unmanaged
{
EnsureInitialized();
T* newBuffer;
switch (allocator)
{
case Allocator.Temp:
newBuffer = (T*)_threadLocalArena!.Value.Allocate(size * (uint)sizeof(T), alignSize, AllocationOption.None);
break;
case Allocator.Persistent:
var allocationSize = size * (nuint)sizeof(T);
newBuffer = (T*)AlignedRealloc(buffer, allocationSize, alignSize);
#if UNSAFE_COLLECTION_CHECK
// If the allocation map can not find the old value, it means that it was a untracked allocation
if (_allocated.Remove((IntPtr)buffer))
{
_allocated[(IntPtr)newBuffer] = new MemoryLeakExceptionInfo
{
Size = allocationSize,
#if DEBUG
StackTrace = new StackTrace(true)
#endif
};
}
#endif
break;
default:
throw new ArgumentOutOfRangeException(nameof(allocator), "Invalid allocator type.");
}
return newBuffer;
}
public static void Free(void* ptr, Allocator allocator)
{
if (allocator == Allocator.Persistent)
{
AlignedFree(ptr);
#if UNSAFE_COLLECTION_CHECK
_allocated.Remove((IntPtr)ptr);
#endif
}
}
/// <summary>
/// Resets the memory arenas on all of the threads.
/// </summary>
public static void ResetAll()
{
if (_threadLocalArena == null)
{
return;
}
foreach (var arena in _threadLocalArena.Values)
{
arena.Reset();
}
}
/// <summary>
/// Resets the current thread-local arena.
/// </summary>
public static void ResetCurrent()
{
if (_threadLocalArena == null)
{
return;
}
_threadLocalArena.Value.Reset();
}
/// <summary>
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
/// </summary>
#if UNSAFE_COLLECTION_CHECK
/// <exception cref="MemoryLeakException">Thrown if there are still allocated buffers that have not been freed.</exception>
#endif
public static void Dispose()
{
if (_threadLocalArena == null)
{
return;
}
foreach (var arena in _threadLocalArena.Values)
{
arena.Dispose();
}
_threadLocalArena.Dispose();
#if UNSAFE_COLLECTION_CHECK
nuint unfreeBytes = 0u;
foreach (var pair in _allocated)
{
unfreeBytes += pair.Value.Size;
AlignedFree((void*)pair.Key);
}
if (unfreeBytes > 0u)
{
throw new MemoryLeakException([.. _allocated.Values]);
}
_allocated.Clear();
#endif
}
}

View File

@@ -1,13 +0,0 @@
namespace Misaki.HighPerformance.Unsafe.Collections.Contracts;
internal unsafe interface IAllocator : IDisposable
{
public T* Allocate<T>(uint size, uint alignSize, AllocationOption allocationOption)
where T : unmanaged;
public T* Reallocate<T>(T* buffer, uint size, uint alignSize)
where T : unmanaged;
public void Free<T>(T* buffer, uint size, uint alignSize)
where T : unmanaged;
}

View File

@@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Collections\Allocator\" />
</ItemGroup>
</Project>

View File

@@ -5,10 +5,16 @@ VisualStudioVersion = 17.14.35821.62
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance", "Misaki.HighPerformance\Misaki.HighPerformance.csproj", "{275B2E80-9B2A-4567-A157-F147A6B28A0F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Unsafe", "Misaki.HighPerformance.Unsafe\Misaki.HighPerformance.Unsafe.csproj", "{0DD1B42E-BA40-4F22-9565-5A3977139B66}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.LowLevel", "Misaki.HighPerformance.LowLevel\Misaki.HighPerformance.LowLevel.csproj", "{0DD1B42E-BA40-4F22-9565-5A3977139B66}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Test", "Misaki.HighPerformance.Test\Misaki.HighPerformance.Test.csproj", "{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Jobs", "Misaki.HighPerformance.Jobs\Misaki.HighPerformance.Jobs.csproj", "{1E8D7815-8C5A-4799-B573-65D9B4D5D95C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Image", "Misaki.HighPerformance.Image\Misaki.HighPerformance.Image.csproj", "{35E6E7FD-0DC4-4D28-93C1-2D17EF92F535}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Misaki.HighPerformance.Mathematics", "Misaki.HighPerformance.Mathematics\Misaki.HighPerformance.Mathematics.csproj", "{29B2478E-D823-4761-B87F-E249D66E33F3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +33,18 @@ Global
{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90EFF5B8-22CD-4B6A-83AB-48E0E97610EA}.Release|Any CPU.Build.0 = Release|Any CPU
{1E8D7815-8C5A-4799-B573-65D9B4D5D95C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E8D7815-8C5A-4799-B573-65D9B4D5D95C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E8D7815-8C5A-4799-B573-65D9B4D5D95C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E8D7815-8C5A-4799-B573-65D9B4D5D95C}.Release|Any CPU.Build.0 = Release|Any CPU
{35E6E7FD-0DC4-4D28-93C1-2D17EF92F535}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35E6E7FD-0DC4-4D28-93C1-2D17EF92F535}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35E6E7FD-0DC4-4D28-93C1-2D17EF92F535}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35E6E7FD-0DC4-4D28-93C1-2D17EF92F535}.Release|Any CPU.Build.0 = Release|Any CPU
{29B2478E-D823-4761-B87F-E249D66E33F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29B2478E-D823-4761-B87F-E249D66E33F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29B2478E-D823-4761-B87F-E249D66E33F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29B2478E-D823-4761-B87F-E249D66E33F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE