Compare commits

..

2 Commits

Author SHA1 Message Date
fef20f05b7 updated GGXMipGenerationJobSPMD to use IJobParallel 2026-05-20 19:19:45 +09:00
27569e9eb5 added IUnsafeBitSet 2026-05-19 11:35:32 +09:00
3 changed files with 90 additions and 51 deletions

View File

@@ -78,3 +78,21 @@ public interface IUnsafeHashCollection<T> : IDisposable
/// <param name="option">Specifies allocation options that may affect how memory is managed during the resize operation.</param> /// <param name="option">Specifies allocation options that may affect how memory is managed during the resize operation.</param>
void Resize(int newSize, AllocationOption option); void Resize(int newSize, AllocationOption option);
} }
public interface IUnsafeBitSet
{
int Count
{
get;
}
int NextSetBit(int startIndex);
void SetBit(int index);
void ClearBit(int index);
bool IsSet(int index);
void SetAll();
void ClearAll();
Span<uint> AsSpan();
}

View File

@@ -1,4 +1,5 @@
using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections.Contracts;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
@@ -33,7 +34,7 @@ internal class UnsafeBitSetDebugView
} }
[DebuggerTypeProxy(typeof(UnsafeBitSetDebugView))] [DebuggerTypeProxy(typeof(UnsafeBitSetDebugView))]
public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet> public unsafe struct UnsafeBitSet : IUnsafeBitSet, IDisposable, IEquatable<UnsafeBitSet>
{ {
public ref struct Iterator public ref struct Iterator
{ {
@@ -54,9 +55,9 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
} }
} }
internal const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 internal const int BIT_SIZE = sizeof(uint) * 8 - 1; // 31
internal const int _INDEX_SIZE = 5; // log_2(BitSize + 1) internal const int INDEX_SIZE = 5; // log_2(BitSize + 1)
internal const int _MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint internal const int MASK = (1 << 5) - 1; // 0x1F, the mask to get the bit index inside a uint
internal static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically internal static readonly int s_padding = Vector<uint>.Count; // The padding used for vectorization, the amount of uints required for being vectorized basically
private UnsafeArray<uint> _bits; private UnsafeArray<uint> _bits;
@@ -76,7 +77,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// <summary> /// <summary>
/// Gets the total number of bits represented by the current instance. /// Gets the total number of bits represented by the current instance.
/// </summary> /// </summary>
public readonly int Count => _bits.Count << _INDEX_SIZE; public readonly int Count => _bits.Count << INDEX_SIZE;
public readonly bool IsCreated => _bits.IsCreated; public readonly bool IsCreated => _bits.IsCreated;
@@ -96,7 +97,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// <param name="option">The allocation option.</param> /// <param name="option">The allocation option.</param>
public UnsafeBitSet(int minimalLength, AllocationHandle handle, AllocationOption option = AllocationOption.None) public UnsafeBitSet(int minimalLength, AllocationHandle handle, AllocationOption option = AllocationOption.None)
{ {
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE); var uints = (minimalLength >> INDEX_SIZE) + int.Sign(minimalLength & BIT_SIZE);
var length = RoundToPadding(uints); var length = RoundToPadding(uints);
_bits = new UnsafeArray<uint>(length, handle, option); _bits = new UnsafeArray<uint>(length, handle, option);
@@ -113,12 +114,12 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
_bits.CopyFrom(bits); _bits.CopyFrom(bits);
_highestBit = 0; _highestBit = 0;
_max = _bits.Count * (_BIT_SIZE + 1) - 1; // Calculate the maximum index in use _max = _bits.Count * (BIT_SIZE + 1) - 1; // Calculate the maximum index in use
for (var i = 0; i < _bits.Count; i++) for (var i = 0; i < _bits.Count; i++)
{ {
if (_bits[i] != 0) if (_bits[i] != 0)
{ {
_highestBit = Math.Max(_highestBit, i * (_BIT_SIZE + 1) + BitOperations.Log2(_bits[i]) + 1); _highestBit = Math.Max(_highestBit, i * (BIT_SIZE + 1) + BitOperations.Log2(_bits[i]) + 1);
} }
} }
} }
@@ -137,7 +138,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RequiredLength(int id) public static int RequiredLength(int id)
{ {
return (id >> _INDEX_SIZE) + int.Sign(id & _BIT_SIZE); return (id >> INDEX_SIZE) + int.Sign(id & BIT_SIZE);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -155,13 +156,13 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool IsSet(int index) public readonly bool IsSet(int index)
{ {
var b = index >> _INDEX_SIZE; var b = index >> INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
{ {
return false; return false;
} }
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0; return (_bits[b] & 1 << (index & BIT_SIZE)) != 0;
} }
/// <summary> /// <summary>
@@ -172,7 +173,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetBit(int index) public void SetBit(int index)
{ {
var b = index >> _INDEX_SIZE; var b = index >> INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
{ {
_bits.Resize(index); _bits.Resize(index);
@@ -181,8 +182,8 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
// Track highest set bit // Track highest set bit
_highestBit = Math.Max(_highestBit, index); _highestBit = Math.Max(_highestBit, index);
_max = _highestBit / (_BIT_SIZE + 1) + 1; _max = _highestBit / (BIT_SIZE + 1) + 1;
_bits[b] |= 1u << (index & _BIT_SIZE); _bits[b] |= 1u << (index & BIT_SIZE);
} }
/// <summary> /// <summary>
@@ -192,13 +193,13 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ClearBit(int index) public void ClearBit(int index)
{ {
var b = index >> _INDEX_SIZE; var b = index >> INDEX_SIZE;
if (b >= _bits.Count) if (b >= _bits.Count)
{ {
return; return;
} }
_bits[b] &= ~(1u << (index & _BIT_SIZE)); _bits[b] &= ~(1u << (index & BIT_SIZE));
} }
/// <summary> /// <summary>
@@ -209,8 +210,8 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
{ {
_bits.AsSpan().Fill(0xffffffff); _bits.AsSpan().Fill(0xffffffff);
_highestBit = _bits.Count * (_BIT_SIZE + 1) - 1; _highestBit = _bits.Count * (BIT_SIZE + 1) - 1;
_max = _highestBit / (_BIT_SIZE + 1) + 1; _max = _highestBit / (BIT_SIZE + 1) + 1;
} }
/// <summary> /// <summary>
@@ -229,21 +230,21 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// </summary> /// </summary>
public readonly int NextSetBit(int startIndex) public readonly int NextSetBit(int startIndex)
{ {
var wordIndex = startIndex >> _BIT_SIZE; var wordIndex = startIndex >> BIT_SIZE;
if (wordIndex >= _bits.Count) if (wordIndex >= _bits.Count)
{ {
return -1; return -1;
} }
// Mask off bits below startIndex in the first word: // Mask off bits below startIndex in the first word:
var word = _bits[wordIndex] & ~0u << (startIndex & _MASK); var word = _bits[wordIndex] & ~0u << (startIndex & MASK);
while (true) while (true)
{ {
if (word != 0) if (word != 0)
{ {
// get the least-significant set bit // get the least-significant set bit
var bit = BitOperations.TrailingZeroCount(word); var bit = BitOperations.TrailingZeroCount(word);
return (wordIndex << _BIT_SIZE) + bit; return (wordIndex << BIT_SIZE) + bit;
} }
wordIndex++; wordIndex++;
@@ -259,7 +260,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
public void Resize(int minimalLength, AllocationOption option = AllocationOption.None) public void Resize(int minimalLength, AllocationOption option = AllocationOption.None)
{ {
var oldSize = _bits.Count; var oldSize = _bits.Count;
var uints = (minimalLength >> _INDEX_SIZE) + int.Sign(minimalLength & _BIT_SIZE); var uints = (minimalLength >> INDEX_SIZE) + int.Sign(minimalLength & BIT_SIZE);
var length = RoundToPadding(uints); var length = RoundToPadding(uints);
_bits.Resize(length, option); _bits.Resize(length, option);
@@ -712,7 +713,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// <returns>The <see cref="Span{T}"/>.</returns> /// <returns>The <see cref="Span{T}"/>.</returns>
public readonly Span<uint> AsSpan() public readonly Span<uint> AsSpan()
{ {
var max = _highestBit / (_BIT_SIZE + 1) + 1; var max = _highestBit / (BIT_SIZE + 1) + 1;
return _bits.AsSpan()[..max]; return _bits.AsSpan()[..max];
} }
@@ -824,7 +825,7 @@ public unsafe struct UnsafeBitSet : IDisposable, IEquatable<UnsafeBitSet>
/// represents a non resizable collection of bits. /// represents a non resizable collection of bits.
/// Used to set, check and clear bits on a allocated <see cref="UnsafeBitSet"/> or on the stack. /// Used to set, check and clear bits on a allocated <see cref="UnsafeBitSet"/> or on the stack.
/// </summary> /// </summary>
public readonly ref struct SpanBitSet : IEquatable<SpanBitSet> public readonly ref struct SpanBitSet : IUnsafeBitSet, IEquatable<SpanBitSet>
{ {
public ref struct Iterator public ref struct Iterator
{ {
@@ -845,15 +846,17 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
} }
} }
private const int _BIT_SIZE = sizeof(uint) * 8 - 1; // 31 private const int BIT_SIZE = sizeof(uint) * 8 - 1; // 31
// NOTE: Is a byte not 8 bits? // NOTE: Is a byte not 8 bits?
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1) private const int BYTE_SIZE = 5; // log_2(BitSize + 1)
/// <summary> /// <summary>
/// The bits from the bitset. /// The bits from the bitset.
/// </summary> /// </summary>
private readonly Span<uint> _bits; private readonly Span<uint> _bits;
public int Count => _bits.Length << BYTE_SIZE;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UnsafeBitSet" /> class. /// Initializes a new instance of the <see cref="UnsafeBitSet" /> class.
/// </summary> /// </summary>
@@ -874,13 +877,13 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
/// <returns>True if it is, otherwise false</returns> /// <returns>True if it is, otherwise false</returns>
public bool IsSet(int index) public bool IsSet(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> BYTE_SIZE;
if (b >= _bits.Length) if (b >= _bits.Length)
{ {
return false; return false;
} }
return (_bits[b] & 1 << (index & _BIT_SIZE)) != 0; return (_bits[b] & 1 << (index & BIT_SIZE)) != 0;
} }
/// <summary> /// <summary>
@@ -890,13 +893,13 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
public void SetBit(int index) public void SetBit(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> BYTE_SIZE;
if (b >= _bits.Length) if (b >= _bits.Length)
{ {
return; return;
} }
_bits[b] |= 1u << (index & _BIT_SIZE); _bits[b] |= 1u << (index & BIT_SIZE);
} }
/// <summary> /// <summary>
@@ -905,13 +908,13 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
/// <param name="index">The index.</param> /// <param name="index">The index.</param>
public void ClearBit(int index) public void ClearBit(int index)
{ {
var b = index >> _BYTE_SIZE; var b = index >> BYTE_SIZE;
if (b >= _bits.Length) if (b >= _bits.Length)
{ {
return; return;
} }
_bits[b] &= ~(1u << (index & _BIT_SIZE)); _bits[b] &= ~(1u << (index & BIT_SIZE));
} }
/// <summary> /// <summary>
@@ -936,21 +939,21 @@ public readonly ref struct SpanBitSet : IEquatable<SpanBitSet>
public int NextSetBit(int startIndex) public int NextSetBit(int startIndex)
{ {
var wordIndex = startIndex >> _BIT_SIZE; var wordIndex = startIndex >> BIT_SIZE;
if (wordIndex >= _bits.Length) if (wordIndex >= _bits.Length)
{ {
return -1; return -1;
} }
// Mask off bits below startIndex in the first word: // Mask off bits below startIndex in the first word:
var word = _bits[wordIndex] & ~0u << (startIndex & UnsafeBitSet._MASK); var word = _bits[wordIndex] & ~0u << (startIndex & UnsafeBitSet.MASK);
while (true) while (true)
{ {
if (word != 0) if (word != 0)
{ {
// get the least-significant set bit // get the least-significant set bit
var bit = BitOperations.TrailingZeroCount(word); var bit = BitOperations.TrailingZeroCount(word);
return (wordIndex << _BIT_SIZE) + bit; return (wordIndex << BIT_SIZE) + bit;
} }
wordIndex++; wordIndex++;

View File

@@ -172,7 +172,7 @@ internal unsafe struct GGXMipGenerationJobSPMD : IJobSPMD<float, int>
} }
} }
internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallelFor internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallel
where TFloat : unmanaged, ISPMDLane<TFloat, float> where TFloat : unmanaged, ISPMDLane<TFloat, float>
where TInt : unmanaged, ISPMDLane<TInt, int> where TInt : unmanaged, ISPMDLane<TInt, int>
{ {
@@ -248,22 +248,13 @@ internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallelFor
return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 4); return MathV.GatherVector3<TFloat, float>(img, idx.GetUnsafePtr(), 4);
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void Execute(int loopIndex, ref readonly JobExecutionContext ctx) private readonly void ProcessPixel(int local_i, MipLevel* pLevel)
{ {
var m = 0;
while (m < numMipLevels - 1 && loopIndex >= pMipLevels[m + 1].offset)
{
m++;
}
var pLevel = &pMipLevels[m];
var w = (int)pLevel->width; var w = (int)pLevel->width;
var h = (int)pLevel->height; var h = (int)pLevel->height;
var pData = pLevel->data; var pData = pLevel->data;
var local_i = loopIndex - pLevel->offset;
var x = local_i % w; var x = local_i % w;
var y = local_i / w; var y = local_i / w;
var u = (float)x / (w - 1); var u = (float)x / (w - 1);
@@ -358,6 +349,33 @@ internal unsafe struct GGXMipGenerationJobSPMD<TFloat, TInt> : IJobParallelFor
pData[out_idx + 1] = prefilteredColor.y; pData[out_idx + 1] = prefilteredColor.y;
pData[out_idx + 2] = prefilteredColor.z; pData[out_idx + 2] = prefilteredColor.z;
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Execute(int startIndex, int endIndex, ref readonly JobExecutionContext ctx)
{
var m = 0;
while (m < numMipLevels - 1 && startIndex >= pMipLevels[m + 1].offset)
{
m++;
}
var i = startIndex;
while (i < endIndex)
{
var pLevel = &pMipLevels[m];
var next = m + 1 < numMipLevels ? pMipLevels[m + 1].offset : int.MaxValue;
var stop = Math.Min(endIndex, next);
for (; i < stop; i++)
{
var local_i = i - pLevel->offset;
ProcessPixel(local_i, pLevel);
}
m++;
}
}
} }
[SimpleJob(RunStrategy.ColdStart, launchCount: 1, warmupCount: 0, iterationCount: 1, invocationCount: 1, id: "QuickRun")] [SimpleJob(RunStrategy.ColdStart, launchCount: 1, warmupCount: 0, iterationCount: 1, invocationCount: 1, id: "QuickRun")]
@@ -388,8 +406,8 @@ public unsafe class GGXMipGenerationBenchmark
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
//const string imagePath = "F:\\c\\SimpleRayTracer\\native\\assets\\hdri\\golden_gate_hills_1k.hdr"; const string imagePath = "F:\\c\\SimpleRayTracer\\native\\assets\\hdri\\golden_gate_hills_1k.hdr";
const string imagePath = "C:\\Users\\Misaki\\Downloads\\grasslands_sunset_4k.hdr"; //const string imagePath = "C:\\Users\\Misaki\\Downloads\\grasslands_sunset_4k.hdr";
using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read);
_image = ImageResultFloat.FromStream(stream, ColorComponents.RGB); _image = ImageResultFloat.FromStream(stream, ColorComponents.RGB);
@@ -517,7 +535,7 @@ public unsafe class GGXMipGenerationBenchmark
radicalInverse_VdCLut = _radicalInverse_VdCLut radicalInverse_VdCLut = _radicalInverse_VdCLut
}; };
handle = _jobScheduler.ScheduleParallelFor(in job, _totalPixel, 64); handle = _jobScheduler.ScheduleParallel(in job, _totalPixel, 64);
} }
else else
{ {
@@ -529,7 +547,7 @@ public unsafe class GGXMipGenerationBenchmark
radicalInverse_VdCLut = _radicalInverse_VdCLut radicalInverse_VdCLut = _radicalInverse_VdCLut
}; };
handle = _jobScheduler.ScheduleParallelFor(in job, _totalPixel, 64); handle = _jobScheduler.ScheduleParallel(in job, _totalPixel, 64);
} }
_jobScheduler.Wait(handle); _jobScheduler.Wait(handle);