425 lines
13 KiB
C#
425 lines
13 KiB
C#
using System.Numerics;
|
|
|
|
using Silk.NET.Windowing;
|
|
using Silk.NET.WebGPU;
|
|
using Silk.NET.Maths;
|
|
using Voile.Utils;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Voile.Rendering
|
|
{
|
|
/// <summary>
|
|
/// A standard, WebGPU-based renderer.
|
|
/// </summary>
|
|
public class StandardRenderSystem : RenderSystem
|
|
{
|
|
/// <inheritdoc />
|
|
public override Vector2 WindowSize { get => _windowSize; set { _windowSize = value; } }
|
|
/// <inheritdoc />
|
|
public override bool ShouldRun => !_window?.IsClosing ?? false;
|
|
|
|
public override Vector2 MonitorSize => throw new NotImplementedException();
|
|
|
|
public override int TargetFps { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
|
public override bool VSync { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
|
public override string WindowTitle { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
|
public override bool Fullscreen { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
|
|
|
/// <inheritdoc />
|
|
public override void Initialize(RendererSettings settings)
|
|
{
|
|
_logger.Info("Initializing WebGPU...");
|
|
|
|
CreateApi();
|
|
CreateInstance();
|
|
CreateSurface();
|
|
CreateAdapter();
|
|
CreateDevice();
|
|
|
|
ConfigureDebugCallback();
|
|
|
|
ConfigureSurface();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void CreateAndInitializeWithWindow(RendererSettings settings)
|
|
{
|
|
CreateWindow(settings.WindowSettings);
|
|
Initialize(settings);
|
|
}
|
|
|
|
public override void CreateWindow(WindowSettings windowSettings)
|
|
{
|
|
// throw new NotImplementedException();
|
|
_logger.Info("Creating window...");
|
|
|
|
WindowOptions windowOptions = WindowOptions.Default;
|
|
windowOptions.Size = new Vector2D<int>((int)windowSettings.Size.X, (int)windowSettings.Size.Y);
|
|
windowOptions.Title = windowSettings.Title;
|
|
windowOptions.API = GraphicsAPI.None;
|
|
|
|
_window = Window.Create(windowOptions);
|
|
_window.Initialize();
|
|
}
|
|
|
|
public override void BeginFrame()
|
|
{
|
|
_window!.DoEvents();
|
|
_window.DoUpdate();
|
|
_window.DoRender();
|
|
|
|
WgpuBeginFrame();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void ClearBackground(Color color)
|
|
{
|
|
_clearColor = color;
|
|
}
|
|
|
|
public override void DrawText(ResourceRef<Font> fontResource, string text, Color color)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void DrawSdfText(string text, int fontSize, Color color)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void BeginCamera2d(Vector2 offset, Vector2 target, float rotation, float zoom)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void EndCamera2d()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void DrawCircle(float radius, Color color)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void DrawDebugText(string text, int fontSize, Color color)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void DrawRectangle(Vector2 size, Color color)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void DrawTexture(ResourceRef<Texture2d> textureResource, Color tint)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void EndFrame()
|
|
{
|
|
WgpuEndFrame();
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
protected override void Shutdown()
|
|
{
|
|
_window!.DoEvents();
|
|
_window.Reset();
|
|
_window.Dispose();
|
|
|
|
ShutdownUnsafe();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override double GetFrameTime()
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void SetTargetFps(int fps)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void SetTransform(Matrix4x4 transform)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void SetWindowTitle(string title)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void SetWindowVSync(bool value)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override bool WindowShouldClose() => throw new NotImplementedException();
|
|
|
|
protected override int GetMonitorWidth(int monitorId)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override int GetMonitorHeight(int monitorId)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override int GetCurrentMonitor()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private unsafe void CreateApi()
|
|
{
|
|
_wgpu = WebGPU.GetApi() ?? throw new Exception("Failed to initialize WebGPU API.");
|
|
}
|
|
|
|
private unsafe void CreateInstance()
|
|
{
|
|
InstanceDescriptor descriptor = new();
|
|
_instance = _wgpu!.CreateInstance(descriptor);
|
|
}
|
|
|
|
private unsafe void CreateSurface()
|
|
{
|
|
_surface = _window.CreateWebGPUSurface(_wgpu, _instance);
|
|
}
|
|
|
|
private unsafe void CreateAdapter()
|
|
{
|
|
if (_wgpu is null)
|
|
{
|
|
throw new Exception("WebGPU API is not initialized!");
|
|
}
|
|
|
|
RequestAdapterOptions adapterOptions = new()
|
|
{
|
|
CompatibleSurface = _surface,
|
|
// TODO: do not force Vulkan, let user choose their own API.
|
|
BackendType = BackendType.Vulkan,
|
|
PowerPreference = PowerPreference.HighPerformance
|
|
};
|
|
|
|
PfnRequestAdapterCallback callback = PfnRequestAdapterCallback.From(
|
|
(status, adapter, msgPtr, userDataPtr) =>
|
|
{
|
|
if (status == RequestAdapterStatus.Success)
|
|
{
|
|
_adapter = adapter;
|
|
|
|
AdapterProperties adapterProperties = new();
|
|
|
|
_wgpu.AdapterGetProperties(_adapter, &adapterProperties);
|
|
|
|
string deviceName = Marshal.PtrToStringAnsi((IntPtr)adapterProperties.Name) ?? string.Empty;
|
|
|
|
_logger.Info($"Retrieved WebGPU Adapter ({deviceName})");
|
|
}
|
|
else
|
|
{
|
|
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
|
|
_logger.Error($"Error while retrieving WebGPU Adapter: {msg}");
|
|
}
|
|
});
|
|
|
|
_wgpu.InstanceRequestAdapter(_instance, adapterOptions, callback, null);
|
|
}
|
|
|
|
private unsafe void CreateDevice()
|
|
{
|
|
if (_wgpu is null)
|
|
{
|
|
throw new Exception("WebGPU API is not initialized!");
|
|
}
|
|
|
|
DeviceDescriptor deviceDescriptor = new();
|
|
|
|
PfnRequestDeviceCallback callback = PfnRequestDeviceCallback.From(
|
|
(status, device, msgPtr, userDataPtr) =>
|
|
{
|
|
if (status == RequestDeviceStatus.Success)
|
|
{
|
|
_device = device;
|
|
_logger.Info($"Retrieved WebGPU Device.");
|
|
}
|
|
else
|
|
{
|
|
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
|
|
_logger.Error($"Error while retrieving WebGPU Device: {msg}");
|
|
}
|
|
});
|
|
|
|
_wgpu.AdapterRequestDevice(_adapter, deviceDescriptor, callback, null);
|
|
}
|
|
|
|
private unsafe void ConfigureSurface()
|
|
{
|
|
if (_wgpu is null)
|
|
{
|
|
throw new Exception("WebGPU API is not initialized!");
|
|
}
|
|
|
|
SurfaceConfiguration configuration = new SurfaceConfiguration
|
|
{
|
|
Device = _device,
|
|
Width = (uint)_window!.Size.X,
|
|
Height = (uint)_window.Size.Y,
|
|
Format = Silk.NET.WebGPU.TextureFormat.Bgra8Unorm,
|
|
PresentMode = PresentMode.Immediate,
|
|
Usage = TextureUsage.RenderAttachment
|
|
};
|
|
|
|
_wgpu.SurfaceConfigure(_surface, configuration);
|
|
}
|
|
|
|
private unsafe void ConfigureDebugCallback()
|
|
{
|
|
if (_wgpu is null)
|
|
{
|
|
throw new Exception("WebGPU API is not initialized!");
|
|
}
|
|
|
|
PfnErrorCallback callback = PfnErrorCallback.From((type, msgPtr, userDataPtr) =>
|
|
{
|
|
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
|
|
_logger.Error($"WebGPU Error: {msg}");
|
|
});
|
|
|
|
_wgpu.DeviceSetUncapturedErrorCallback(_device, callback, null);
|
|
_logger.Info("WGPU Debug Callback Configured.");
|
|
}
|
|
|
|
private unsafe void WgpuBeginFrame()
|
|
{
|
|
if (_wgpu is null)
|
|
{
|
|
throw new Exception("WebGPU API is not initialized!");
|
|
}
|
|
|
|
_queue = _wgpu.DeviceGetQueue(_device);
|
|
|
|
_commandEncoder = _wgpu.DeviceCreateCommandEncoder(_device, null);
|
|
|
|
_wgpu.SurfaceGetCurrentTexture(_surface, ref _surfaceTexture);
|
|
_surfaceTextureView = _wgpu.TextureCreateView(_surfaceTexture.Texture, null);
|
|
|
|
RenderPassColorAttachment colorAttachments = CreateClearColorAttachment(_surfaceTextureView, _clearColor);
|
|
|
|
RenderPassDescriptor renderPassDescriptor = new()
|
|
{
|
|
ColorAttachments = &colorAttachments,
|
|
ColorAttachmentCount = 1
|
|
};
|
|
|
|
_renderPassEncoder = _wgpu.CommandEncoderBeginRenderPass(_commandEncoder, renderPassDescriptor);
|
|
}
|
|
|
|
private unsafe void WgpuEndFrame()
|
|
{
|
|
if (_wgpu is null)
|
|
{
|
|
throw new Exception("WebGPU API is not initialized!");
|
|
}
|
|
|
|
_wgpu.RenderPassEncoderEnd(_renderPassEncoder);
|
|
var commandBuffer = _wgpu.CommandEncoderFinish(_commandEncoder, null);
|
|
_wgpu.CommandEncoderRelease(_commandEncoder);
|
|
|
|
_wgpu.QueueSubmit(_queue, 1, &commandBuffer);
|
|
_wgpu.CommandBufferRelease(commandBuffer);
|
|
|
|
_wgpu.SurfacePresent(_surface);
|
|
_wgpu.TextureViewRelease(_surfaceTextureView);
|
|
|
|
_wgpu.TextureRelease(_surfaceTexture.Texture);
|
|
_wgpu.RenderPassEncoderRelease(_renderPassEncoder);
|
|
}
|
|
|
|
private unsafe void ShutdownUnsafe()
|
|
{
|
|
_wgpu!.DeviceDestroy(_device);
|
|
_wgpu.InstanceRelease(_instance);
|
|
_wgpu.SurfaceRelease(_surface);
|
|
_wgpu.AdapterRelease(_adapter);
|
|
}
|
|
|
|
private Silk.NET.WebGPU.Color VoileColorToWebGPUColor(Color color)
|
|
{
|
|
return new Silk.NET.WebGPU.Color(color.R, color.G, color.B, color.A);
|
|
}
|
|
|
|
private unsafe RenderPassColorAttachment CreateClearColorAttachment(TextureView* view, Color clearColor)
|
|
{
|
|
var colorAttachment = new RenderPassColorAttachment
|
|
{
|
|
View = view,
|
|
LoadOp = LoadOp.Clear,
|
|
ClearValue = VoileColorToWebGPUColor(clearColor),
|
|
StoreOp = StoreOp.Store
|
|
};
|
|
|
|
return colorAttachment;
|
|
}
|
|
|
|
private unsafe RenderPipeline* CreatePipeline(Material fromMaterial)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public override void BeginBlended(BlendMode blendMode)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void EndBlended()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override void ReloadResources()
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public override void DrawRectangleOutline(Vector2 size, Color color, float outlineWidth = 1)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private Vector2 _windowSize = Vector2.Zero;
|
|
private IWindow? _window;
|
|
|
|
private Color _clearColor = Color.Black;
|
|
|
|
private WebGPU? _wgpu;
|
|
private unsafe Instance* _instance;
|
|
private unsafe Surface* _surface;
|
|
private unsafe Adapter* _adapter;
|
|
private unsafe Device* _device;
|
|
private unsafe Queue* _queue;
|
|
private SurfaceTexture _surfaceTexture;
|
|
private unsafe TextureView* _surfaceTextureView;
|
|
private unsafe CommandEncoder* _commandEncoder;
|
|
private unsafe RenderPassEncoder* _renderPassEncoder;
|
|
|
|
private Logger _logger = new(nameof(StandardRenderSystem));
|
|
}
|
|
} |