mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Add support for passing features to WESL compilation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
global using Robust.Client.Interop.RobustNative.Webgpu;
|
||||
|
||||
global using static Robust.Client.Interop.RobustNative.Webgpu.Wgpu;
|
||||
global using static Robust.Shared.Utility.FfiHelper;
|
||||
|
||||
global using unsafe WGPUTexture = Robust.Client.Interop.RobustNative.Webgpu.WGPUTextureImpl*;
|
||||
global using unsafe WGPUDevice = Robust.Client.Interop.RobustNative.Webgpu.WGPUDeviceImpl*;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Rhi.WebGpu;
|
||||
|
||||
@@ -180,64 +178,6 @@ internal sealed unsafe partial class RhiWebGpu
|
||||
return Encoding.UTF8.GetBytes(label);
|
||||
}
|
||||
|
||||
/// <param name="buf">Must be pinned memory or I WILL COME TO YOUR HOUSE!!</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void* BumpAllocate(ref Span<byte> buf, int size)
|
||||
{
|
||||
// Round up to 8 to make sure everything stays aligned inside.
|
||||
var alignedSize = MathHelper.CeilingPowerOfTwo(size, 8);
|
||||
if (buf.Length < alignedSize)
|
||||
ThrowBumpAllocOutOfSpace();
|
||||
|
||||
var ptr = Unsafe.AsPointer(ref MemoryMarshal.AsRef<byte>(buf));
|
||||
buf = buf[alignedSize..];
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T* BumpAllocate<T>(ref Span<byte> buf) where T : unmanaged
|
||||
{
|
||||
var ptr = (T*)BumpAllocate(ref buf, sizeof(T));
|
||||
// Yeah I don't trust myself.
|
||||
*ptr = default;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T* BumpAllocate<T>(ref Span<byte> buf, int count) where T : unmanaged
|
||||
{
|
||||
var size = checked(sizeof(T) * count);
|
||||
var ptr = BumpAllocate(ref buf, size);
|
||||
// Yeah I don't trust myself.
|
||||
new Span<byte>(ptr, size).Clear();
|
||||
return (T*)ptr;
|
||||
}
|
||||
|
||||
// Workaround for C# not having pointers in generics.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T** BumpAllocatePtr<T>(ref Span<byte> buf, int count) where T : unmanaged
|
||||
{
|
||||
var size = checked(sizeof(T*) * count);
|
||||
var ptr = BumpAllocate(ref buf, size);
|
||||
// Yeah I don't trust myself.
|
||||
new Span<byte>(ptr, size).Clear();
|
||||
return (T**)ptr;
|
||||
}
|
||||
|
||||
private static byte* BumpAllocateUtf8(ref Span<byte> buf, string? str)
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
var byteCount = Encoding.UTF8.GetByteCount(str) + 1;
|
||||
var ptr = BumpAllocate(ref buf, byteCount);
|
||||
var dstSpan = new Span<byte>(ptr, byteCount);
|
||||
Encoding.UTF8.GetBytes(str, dstSpan);
|
||||
dstSpan[^1] = 0;
|
||||
|
||||
return (byte*) ptr;
|
||||
}
|
||||
|
||||
private static WGPUStringView BumpAllocateStringView(ref Span<byte> buf, string? str)
|
||||
{
|
||||
if (str == null)
|
||||
@@ -255,12 +195,6 @@ internal sealed unsafe partial class RhiWebGpu
|
||||
};
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowBumpAllocOutOfSpace()
|
||||
{
|
||||
throw new InvalidOperationException("Out of bump allocator space!");
|
||||
}
|
||||
|
||||
private sealed class WgpuPromise<TResult> : IDisposable
|
||||
{
|
||||
|
||||
@@ -31,6 +31,9 @@ internal static unsafe partial class Wesl
|
||||
[DllImport("robust-native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
public static extern void wesl_free_exec_result(WeslExecResult* result);
|
||||
|
||||
[DllImport("robust-native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
public static extern void wesl_free_parse_result(WeslParseResult* result);
|
||||
|
||||
[DllImport("robust-native", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
|
||||
public static extern void wesl_free_translation_unit(WeslTranslationUnit* unit);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
@@ -21,7 +22,32 @@ internal sealed class CompileShaderCommand : IConsoleCommand
|
||||
{
|
||||
var path = args[0];
|
||||
|
||||
var x = _shaderCompiler.CompileToWgsl(new ResPath(path), ImmutableDictionary<string, bool>.Empty);
|
||||
var features = new Dictionary<string, bool>();
|
||||
for (var i = 1; i < args.Length; i++)
|
||||
{
|
||||
var split = args[i].Split('=', 2);
|
||||
var value = split[1].Trim();
|
||||
if (!bool.TryParse(value, out var boolValue))
|
||||
{
|
||||
if (value == "0")
|
||||
{
|
||||
boolValue = false;
|
||||
}
|
||||
else if (value == "1")
|
||||
{
|
||||
boolValue = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError($"Invalid feature value: '{value}'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
features.Add(split[0].Trim(), boolValue);
|
||||
}
|
||||
|
||||
var x = _shaderCompiler.CompileToWgsl(new ResPath(path), features);
|
||||
if (!x.Success)
|
||||
{
|
||||
shell.WriteError("Compilation failed");
|
||||
@@ -41,6 +67,7 @@ internal sealed class CompileShaderCommand : IConsoleCommand
|
||||
"<path>");
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
// Features.
|
||||
return CompletionResult.FromHint("<feature>=<1|0>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Robust.Client.Graphics;
|
||||
|
||||
public interface IShaderCompiler
|
||||
{
|
||||
ShaderCompileResultWgsl CompileToWgsl(ResPath path);
|
||||
ShaderCompileResultWgsl CompileToWgsl(ResPath path, IReadOnlyDictionary<string, bool> features);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -34,6 +35,11 @@ internal sealed class ShaderCompiler : IShaderCompiler, IDisposable
|
||||
RegisterPackage(new ResPath("/Shaders"), "Content");
|
||||
}
|
||||
|
||||
public ShaderCompileResultWgsl CompileToWgsl(ResPath path)
|
||||
{
|
||||
return CompileToWgsl(path, ImmutableDictionary<string, bool>.Empty);
|
||||
}
|
||||
|
||||
public unsafe ShaderCompileResultWgsl CompileToWgsl(ResPath path, IReadOnlyDictionary<string, bool> features)
|
||||
{
|
||||
using var _ = _rwLock.ReadGuard();
|
||||
@@ -53,10 +59,22 @@ internal sealed class ShaderCompiler : IShaderCompiler, IDisposable
|
||||
|
||||
byte[] modulePathNullTerminated = [.. Encoding.UTF8.GetBytes(modulePath), 0];
|
||||
|
||||
void* freeBoolMap = null;
|
||||
WeslBoolMap* boolMap = null;
|
||||
WeslResult result;
|
||||
if (features.Count > 0)
|
||||
{
|
||||
if (!TryWriteBoolMap(stackalloc byte[1024], features, out boolMap))
|
||||
{
|
||||
var heapBuffer = FfiHelper.CreateHeapBumpAllocateBuffer(16384, out freeBoolMap);
|
||||
if (!TryWriteBoolMap(heapBuffer, features, out boolMap))
|
||||
throw new ArgumentException("Too many features specified!");
|
||||
}
|
||||
}
|
||||
|
||||
fixed (byte* pPath = modulePathNullTerminated)
|
||||
{
|
||||
result = Wesl.wesl_compile(null, (sbyte*)pPath, &compileOptions, null, null);
|
||||
result = Wesl.wesl_compile(null, (sbyte*)pPath, &compileOptions, null, boolMap);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -74,9 +92,46 @@ internal sealed class ShaderCompiler : IShaderCompiler, IDisposable
|
||||
finally
|
||||
{
|
||||
Wesl.wesl_free_result(&result);
|
||||
|
||||
if (freeBoolMap != null)
|
||||
NativeMemory.Free(freeBoolMap);
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool TryWriteBoolMap(
|
||||
Span<byte> buffer,
|
||||
IReadOnlyDictionary<string, bool> map,
|
||||
out WeslBoolMap* ptr)
|
||||
{
|
||||
if (!FfiHelper.TryBumpAllocate(ref buffer, out ptr))
|
||||
return false;
|
||||
|
||||
var count = map.Count;
|
||||
if (!FfiHelper.TryBumpAllocate(ref buffer, count, out Ptr<byte>* strings, out var stringsSpan))
|
||||
return false;
|
||||
|
||||
if (!FfiHelper.TryBumpAllocate(ref buffer, count, out bool* bools, out var boolSpan))
|
||||
return false;
|
||||
|
||||
var i = 0;
|
||||
foreach (var (key, value) in map)
|
||||
{
|
||||
boolSpan[i] = value;
|
||||
if (!FfiHelper.TryBumpAllocateUtf8(ref buffer, key, out var stringPtr))
|
||||
return false;
|
||||
|
||||
stringsSpan[i] = stringPtr;
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
ptr->values = bools;
|
||||
ptr->keys = (sbyte**)strings;
|
||||
ptr->len = (UIntPtr)count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe WeslResolverOptions MakeResolverOptions()
|
||||
{
|
||||
return new WeslResolverOptions
|
||||
|
||||
1
Robust.Shared.Utility/GlobalUsings.cs
Normal file
1
Robust.Shared.Utility/GlobalUsings.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using Robust.Shared.Analyzers;
|
||||
@@ -12,6 +12,10 @@
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="Serilog" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
</Project>
|
||||
|
||||
167
Robust.Shared.Utility/Utility/FfiHelper.cs
Normal file
167
Robust.Shared.Utility/Utility/FfiHelper.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Utility;
|
||||
|
||||
internal static unsafe class FfiHelper
|
||||
{
|
||||
internal static Span<byte> CreateHeapBumpAllocateBuffer(int size, out void* ptr)
|
||||
{
|
||||
ptr = NativeMemory.Alloc(checked((nuint)size));
|
||||
|
||||
return new Span<byte>(ptr, size);
|
||||
}
|
||||
|
||||
internal static bool TryBumpAllocate(ref Span<byte> buf, int size, out void* ptr)
|
||||
{
|
||||
// Round up to 8 to make sure everything stays aligned inside.
|
||||
var alignedSize = MathHelper.CeilingPowerOfTwo(size, 8);
|
||||
if (buf.Length < alignedSize)
|
||||
{
|
||||
ptr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = Unsafe.AsPointer(ref MemoryMarshal.AsRef<byte>(buf));
|
||||
buf = buf[alignedSize..];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <param name="buf">Must be pinned memory or I WILL COME TO YOUR HOUSE!!</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void* BumpAllocate(ref Span<byte> buf, int size)
|
||||
{
|
||||
if (!TryBumpAllocate(ref buf, size, out var ptr))
|
||||
ThrowBumpAllocOutOfSpace();
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool TryBumpAllocate<T>(ref Span<byte> buf, out T* ptr) where T : unmanaged
|
||||
{
|
||||
if (TryBumpAllocate(ref buf, sizeof(T), out var voidPtr))
|
||||
{
|
||||
ptr = (T*)voidPtr;
|
||||
// Yeah I don't trust myself.
|
||||
*ptr = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
ptr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static T* BumpAllocate<T>(ref Span<byte> buf) where T : unmanaged
|
||||
{
|
||||
var ptr = (T*)BumpAllocate(ref buf, sizeof(T));
|
||||
// Yeah I don't trust myself.
|
||||
*ptr = default;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool TryBumpAllocate<T>(ref Span<byte> buf, int count, out T* ptr) where T : unmanaged
|
||||
{
|
||||
var size = checked(sizeof(T) * count);
|
||||
if (TryBumpAllocate(ref buf, size, out var voidPtr))
|
||||
{
|
||||
ptr = (T*)voidPtr;
|
||||
new Span<byte>(ptr, size).Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
ptr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool TryBumpAllocate<T>(
|
||||
ref Span<byte> buf,
|
||||
int count,
|
||||
out T* ptr,
|
||||
out Span<T> span)
|
||||
where T : unmanaged
|
||||
{
|
||||
var size = checked(sizeof(T) * count);
|
||||
if (TryBumpAllocate(ref buf, size, out var voidPtr))
|
||||
{
|
||||
ptr = (T*)voidPtr;
|
||||
span = new Span<T>(ptr, size);
|
||||
// Yeah I don't trust myself.
|
||||
span.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
ptr = null;
|
||||
span = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static T* BumpAllocate<T>(ref Span<byte> buf, int count) where T : unmanaged
|
||||
{
|
||||
var size = checked(sizeof(T) * count);
|
||||
var ptr = BumpAllocate(ref buf, size);
|
||||
// Yeah I don't trust myself.
|
||||
new Span<byte>(ptr, size).Clear();
|
||||
return (T*)ptr;
|
||||
}
|
||||
|
||||
// Workaround for C# not having pointers in generics.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static T** BumpAllocatePtr<T>(ref Span<byte> buf, int count) where T : unmanaged
|
||||
{
|
||||
var size = checked(sizeof(T*) * count);
|
||||
var ptr = BumpAllocate(ref buf, size);
|
||||
// Yeah I don't trust myself.
|
||||
new Span<byte>(ptr, size).Clear();
|
||||
return (T**)ptr;
|
||||
}
|
||||
|
||||
internal static bool TryBumpAllocateUtf8(ref Span<byte> buf, string? str, out byte* ptr)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
ptr = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
ptr = (byte*) Unsafe.AsPointer(ref MemoryMarshal.AsRef<byte>(buf));
|
||||
|
||||
if (!Encoding.UTF8.TryGetBytes(str, buf, out var written) || written == buf.Length)
|
||||
{
|
||||
ptr = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
buf[written] = 0; // Nul terminator
|
||||
buf = buf[(written + 1)..];
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static byte* BumpAllocateUtf8(ref Span<byte> buf, string? str)
|
||||
{
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
var byteCount = Encoding.UTF8.GetByteCount(str) + 1;
|
||||
var ptr = BumpAllocate(ref buf, byteCount);
|
||||
var dstSpan = new Span<byte>(ptr, byteCount);
|
||||
Encoding.UTF8.GetBytes(str, dstSpan);
|
||||
dstSpan[^1] = 0;
|
||||
|
||||
return (byte*) ptr;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowBumpAllocOutOfSpace()
|
||||
{
|
||||
throw new InvalidOperationException("Out of bump allocator space!");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace Robust.Shared.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer-wrapper struct so pointers can be sanely stored in generics and records.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The actual type pointed to</typeparam>
|
||||
internal unsafe struct Ptr<T> where T : unmanaged
|
||||
{
|
||||
public T* P;
|
||||
|
||||
public static implicit operator T*(Ptr<T> t) => t.P;
|
||||
public static implicit operator Ptr<T>(T* ptr) => new() { P = ptr };
|
||||
}
|
||||
Reference in New Issue
Block a user