mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Merge branch '23-05-06-webgpu' into 25-10-04-claudia
Well I did my best solving conflicts but it sure as hell doesn't compile.
This commit is contained in:
1
Resources/Shaders/Include/Std.wgsl
Normal file
1
Resources/Shaders/Include/Std.wgsl
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
66
Resources/Shaders/Internal/default-sprite.wgsl
Normal file
66
Resources/Shaders/Internal/default-sprite.wgsl
Normal file
@@ -0,0 +1,66 @@
|
||||
// Group 0: global constants.
|
||||
struct UniformConstants {
|
||||
time: f32
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> Constants: UniformConstants;
|
||||
|
||||
// Group 1: parameters that change infrequently in a draw pass.
|
||||
struct UniformView {
|
||||
projViewMatrix: mat3x2f,
|
||||
screenPixelSize: vec2f
|
||||
}
|
||||
|
||||
@group(1) @binding(0) var<uniform> View: UniformView;
|
||||
|
||||
|
||||
// Group 2: per-draw parameters.
|
||||
@group(2) @binding(0)
|
||||
var mainTexture: texture_2d<f32>;
|
||||
@group(2) @binding(1)
|
||||
var mainSampler: sampler;
|
||||
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2f,
|
||||
@location(1) texCoord: vec2f,
|
||||
@location(2) color: vec4f
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4f,
|
||||
@location(0) texCoord: vec2f,
|
||||
@location(1) color: vec4f,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(input: VertexInput) -> VertexOutput {
|
||||
var transformed = View.projViewMatrix * vec3(input.position, 1.0);
|
||||
|
||||
transformed += 1.0;
|
||||
transformed /= View.screenPixelSize * 2.0;
|
||||
transformed = floor(transformed + 0.5);
|
||||
transformed *= View.screenPixelSize * 2.0;
|
||||
transformed -= 1.0;
|
||||
|
||||
var out: VertexOutput;
|
||||
out.position = vec4(transformed, 0.0, 1.0);
|
||||
out.texCoord = input.texCoord;
|
||||
out.color = srgb_to_linear(input.color);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(input: VertexOutput) -> @location(0) vec4f {
|
||||
var color = textureSample(mainTexture, mainSampler, input.texCoord);
|
||||
color = color * input.color;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
fn srgb_to_linear(srgb: vec4f) -> vec4f {
|
||||
let higher = pow((srgb.rgb + 0.055) / 1.055, vec3(2.4));
|
||||
let lower = srgb.rgb / 12.92;
|
||||
let s = max(vec3(0.0), sign(srgb.rgb - 0.04045));
|
||||
return vec4(mix(lower, higher, s), srgb.a);
|
||||
}
|
||||
@@ -32,14 +32,5 @@ namespace Robust.Client.Graphics
|
||||
/// Our sub region within our source, in pixel coordinates.
|
||||
/// </summary>
|
||||
public UIBox2 SubRegion { get; }
|
||||
|
||||
public override Color GetPixel(int x, int y)
|
||||
{
|
||||
DebugTools.Assert(x < SubRegion.Right);
|
||||
DebugTools.Assert(y < SubRegion.Top);
|
||||
int xTranslated = x + (int) SubRegion.Left;
|
||||
int yTranslated = y + (int) SubRegion.Top;
|
||||
return this.SourceTexture[xTranslated, yTranslated];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,22 +30,5 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// To be clear: You shouldn't change this. This just helps with understanding where Primitive Restart is being used.
|
||||
private const ushort PrimitiveRestartIndex = ushort.MaxValue;
|
||||
|
||||
private enum Renderer : sbyte
|
||||
{
|
||||
// Auto: Try all supported renderers (not necessarily the renderers shown here)
|
||||
Auto = default,
|
||||
OpenGL = 1,
|
||||
Explode = -1,
|
||||
}
|
||||
|
||||
private enum RendererOpenGLVersion : byte
|
||||
{
|
||||
Auto = default,
|
||||
GL33 = 1,
|
||||
GL31 = 2,
|
||||
GLES3 = 3,
|
||||
GLES2 = 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
private readonly ClydeDebugStats _debugStats = new();
|
||||
private ClydeDebugInfo? _debugInfo;
|
||||
|
||||
private sealed record ClydeDebugInfo(
|
||||
OpenGLVersion OpenGLVersion,
|
||||
string Renderer,
|
||||
string Vendor,
|
||||
string VersionString,
|
||||
bool Overriding,
|
||||
string WindowingApi) : IClydeDebugInfo;
|
||||
private sealed record ClydeDebugInfo(string WindowingApi) : IClydeDebugInfo;
|
||||
|
||||
private sealed class ClydeDebugStats : IClydeDebugStats
|
||||
{
|
||||
|
||||
@@ -6,7 +6,6 @@ using Robust.Shared.Log;
|
||||
#endif
|
||||
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -112,10 +111,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (!reg.IsVisible) // Only send this for open windows
|
||||
return;
|
||||
|
||||
var loaded = RtToLoaded(reg.RenderTarget);
|
||||
loaded.Size = reg.FramebufferSize;
|
||||
// var loaded = RtToLoaded(reg.RenderTarget);
|
||||
// loaded.Size = reg.FramebufferSize;
|
||||
|
||||
_glContext!.WindowResized(reg, oldSize);
|
||||
Rhi.WindowRecreateSwapchain(reg);
|
||||
|
||||
var eventArgs = new WindowResizedEventArgs(
|
||||
oldSize,
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
private GLContextBase? _glContext;
|
||||
|
||||
// Current OpenGL version we managed to initialize with.
|
||||
private RendererOpenGLVersion _openGLVersion;
|
||||
|
||||
private void InitGLContextManager()
|
||||
{
|
||||
// Advanced GL contexts currently disabled due to lack of testing etc.
|
||||
if (OperatingSystem.IsWindows() && _cfg.GetCVar(CVars.DisplayAngle))
|
||||
{
|
||||
if (_cfg.GetCVar(CVars.DisplayAngleCustomSwapChain))
|
||||
{
|
||||
_sawmillOgl.Debug("Trying custom swap chain ANGLE.");
|
||||
var ctxAngle = new GLContextAngle(this);
|
||||
|
||||
if (ctxAngle.TryInitialize())
|
||||
{
|
||||
_sawmillOgl.Debug("Successfully initialized custom ANGLE");
|
||||
_glContext = ctxAngle;
|
||||
|
||||
ctxAngle.EarlyInit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (_cfg.GetCVar(CVars.DisplayEgl))
|
||||
{
|
||||
_sawmillOgl.Debug("Trying EGL");
|
||||
var ctxEgl = new GLContextEgl(this);
|
||||
ctxEgl.InitializePublic();
|
||||
_glContext = ctxEgl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (OperatingSystem.IsLinux() && _cfg.GetCVar(CVars.DisplayEgl))
|
||||
{
|
||||
_sawmillOgl.Debug("Trying EGL");
|
||||
var ctxEgl = new GLContextEgl(this);
|
||||
ctxEgl.InitializePublic();
|
||||
_glContext = ctxEgl;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
_glContext = new GLContextWindow(this);
|
||||
}
|
||||
|
||||
private struct GLContextSpec
|
||||
{
|
||||
public int Major;
|
||||
public int Minor;
|
||||
public GLContextProfile Profile;
|
||||
public GLContextCreationApi CreationApi;
|
||||
// Used by GLContextWindow to figure out which GL version managed to initialize.
|
||||
public RendererOpenGLVersion OpenGLVersion;
|
||||
}
|
||||
|
||||
private enum GLContextProfile
|
||||
{
|
||||
Compatibility,
|
||||
Core,
|
||||
Es
|
||||
}
|
||||
|
||||
private enum GLContextCreationApi
|
||||
{
|
||||
Native,
|
||||
Egl,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
// OpenGL feature detection go here.
|
||||
/*// OpenGL feature detection go here.
|
||||
|
||||
private bool _hasGLKhrDebug;
|
||||
private bool _glDebuggerPresent;
|
||||
@@ -224,6 +220,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_sawmillOgl.Debug("OpenGL Extensions: {0}", extensions);
|
||||
return new HashSet<string>(extensions.Split(' '));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -16,6 +15,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/*
|
||||
|
||||
private readonly Dictionary<EntityUid, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
|
||||
new();
|
||||
|
||||
@@ -528,5 +529,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared;
|
||||
@@ -15,6 +15,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -34,9 +35,55 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private List<Overlay> _overlays = new();
|
||||
|
||||
private SpriteBatch? _spriteBatch;
|
||||
|
||||
private void RenderInit()
|
||||
{
|
||||
LoadStockTextures();
|
||||
|
||||
_spriteBatch = new SpriteBatch(this, Rhi);
|
||||
_renderHandle = new RenderHandle(this, _entityManager, _spriteBatch);
|
||||
}
|
||||
|
||||
private void DrawSplash()
|
||||
{
|
||||
var splashTex = _cfg.GetCVar(CVars.DisplaySplashLogo);
|
||||
if (string.IsNullOrEmpty(splashTex))
|
||||
return;
|
||||
|
||||
var backbuffer = _mainWindow!.CurSwapchainView!;
|
||||
|
||||
var size = _mainWindow!.FramebufferSize;
|
||||
|
||||
_spriteBatch!.Start();
|
||||
_spriteBatch.BeginPass(size, backbuffer);
|
||||
|
||||
var texture = _resourceCache.GetResource<TextureResource>(splashTex).Texture;
|
||||
|
||||
var pos = (size - texture.Size) / 2;
|
||||
|
||||
_spriteBatch.Draw((ClydeTexture) texture, pos, Color.White);
|
||||
|
||||
_spriteBatch.EndPass();
|
||||
_spriteBatch.Finish();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
CheckTransferringScreenshots();
|
||||
AcquireSwapchainTextures();
|
||||
|
||||
try
|
||||
{
|
||||
RenderCore();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// wgpu will be very annoyed if we don't do the present state machine correctly.
|
||||
// So make sure we ALWAYS present.
|
||||
PresentWindows();
|
||||
}
|
||||
|
||||
/*CheckTransferringScreenshots();
|
||||
|
||||
var allMinimized = true;
|
||||
foreach (var windowReg in _windows)
|
||||
@@ -118,7 +165,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_prof.WriteValue("Lights", ProfData.Int32(_debugStats.TotalLights));
|
||||
_prof.WriteValue("Shadow Lights", ProfData.Int32(_debugStats.ShadowLights));
|
||||
_prof.WriteValue("Occluders", ProfData.Int32(_debugStats.Occluders));
|
||||
}*/
|
||||
}
|
||||
|
||||
private void RenderCore()
|
||||
{
|
||||
if (_drawingSplash)
|
||||
{
|
||||
DrawSplash();
|
||||
return;
|
||||
}
|
||||
|
||||
_spriteBatch!.Start();
|
||||
_userInterfaceManager.Render(_renderHandle);
|
||||
_spriteBatch.Finish();
|
||||
}
|
||||
|
||||
public void RenderNow(IRenderTarget renderTarget, Action<IRenderHandle> callback)
|
||||
@@ -134,6 +194,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
null);
|
||||
}
|
||||
|
||||
/*
|
||||
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
// Check that entity manager has started.
|
||||
@@ -616,5 +677,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
return new Box2Rotated(aabb, rotation, aabb.Center);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using ES20 = OpenToolkit.Graphics.ES20;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
/*
|
||||
private void GLClearColor(Color color)
|
||||
{
|
||||
GL.ClearColor(color.R, color.G, color.B, color.A);
|
||||
@@ -290,5 +281,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
return proc;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Contains various layout/rendering structs used inside Clyde.
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets up VAO layout for Vertex2D for base and raw shader types.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static unsafe void SetupVAOLayout()
|
||||
{
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
// Texture Coords (2).
|
||||
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(2);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
// NOTE: This is:
|
||||
// + Directly cast from DrawVertexUV2DColor!!!
|
||||
// + GLContextWindow does it's own thing with this for winblit, be careful!
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[PublicAPI]
|
||||
private readonly struct Vertex2D
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly Vector2 TextureCoordinates;
|
||||
public readonly Vector2 TextureCoordinates2;
|
||||
// Note that this color is in linear space.
|
||||
public readonly Color Modulate;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Color modulate)
|
||||
{
|
||||
Position = position;
|
||||
TextureCoordinates = textureCoordinates;
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate)
|
||||
{
|
||||
Position = position;
|
||||
TextureCoordinates = textureCoordinates;
|
||||
TextureCoordinates2 = textureCoordinates2;
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a)
|
||||
: this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a))
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(float x, float y, float u, float v, Color modulate)
|
||||
: this(new Vector2(x, y), new Vector2(u, v), modulate)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(Vector2 position, float u, float v, Color modulate)
|
||||
: this(position, new Vector2(u, v), modulate)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Vertex2D: {Position}, {TextureCoordinates}, {Modulate}";
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 28 * sizeof(float))]
|
||||
[PublicAPI]
|
||||
private struct ProjViewMatrices : IAppliableUniformSet
|
||||
{
|
||||
[FieldOffset(0 * sizeof(float))] public Vector3 ProjMatrixC0;
|
||||
[FieldOffset(4 * sizeof(float))] public Vector3 ProjMatrixC1;
|
||||
[FieldOffset(8 * sizeof(float))] public Vector3 ProjMatrixC2;
|
||||
|
||||
[FieldOffset(12 * sizeof(float))] public Vector3 ViewMatrixC0;
|
||||
[FieldOffset(16 * sizeof(float))] public Vector3 ViewMatrixC1;
|
||||
[FieldOffset(20 * sizeof(float))] public Vector3 ViewMatrixC2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ProjViewMatrices(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix)
|
||||
{
|
||||
// We put the rows of the input matrix into the columns of our GPU matrices
|
||||
// this transpose is required, as in C#, we premultiply vectors with matrices
|
||||
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
|
||||
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
|
||||
// memory layout is the same (or would be, if Matrix3x2 didn't have an
|
||||
// implicit column)
|
||||
ProjMatrixC0 = new Vector3(projMatrix.M11, projMatrix.M12, 0);
|
||||
ProjMatrixC1 = new Vector3(projMatrix.M21, projMatrix.M22, 0);
|
||||
ProjMatrixC2 = new Vector3(projMatrix.M31, projMatrix.M32, 1);
|
||||
|
||||
ViewMatrixC0 = new Vector3(viewMatrix.M11, viewMatrix.M12, 0);
|
||||
ViewMatrixC1 = new Vector3(viewMatrix.M21, viewMatrix.M22, 0);
|
||||
ViewMatrixC2 = new Vector3(viewMatrix.M31, viewMatrix.M32, 1);
|
||||
}
|
||||
|
||||
public void Apply(Clyde clyde, GLShaderProgram program)
|
||||
{
|
||||
program.SetUniformMaybe("projectionMatrix", new Matrix3x2(
|
||||
ProjMatrixC0.X, ProjMatrixC0.Y, // Implicit 0
|
||||
ProjMatrixC1.X, ProjMatrixC1.Y, // Implicit 0
|
||||
ProjMatrixC2.X, ProjMatrixC2.Y // Implicit 1
|
||||
));
|
||||
program.SetUniformMaybe("viewMatrix", new Matrix3x2(
|
||||
ViewMatrixC0.X, ViewMatrixC0.Y, // Implicit 0
|
||||
ViewMatrixC1.X, ViewMatrixC1.Y, // Implicit 0
|
||||
ViewMatrixC2.X, ViewMatrixC2.Y // Implicit 1
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = sizeof(float) * 4)]
|
||||
[PublicAPI]
|
||||
private struct UniformConstants : IAppliableUniformSet
|
||||
{
|
||||
[FieldOffset(0)] public Vector2 ScreenPixelSize;
|
||||
[FieldOffset(2 * sizeof(float))] public float Time;
|
||||
|
||||
public UniformConstants(Vector2 screenPixelSize, float time)
|
||||
{
|
||||
ScreenPixelSize = screenPixelSize;
|
||||
Time = time;
|
||||
}
|
||||
|
||||
public void Apply(Clyde clyde, GLShaderProgram program)
|
||||
{
|
||||
program.SetUniformMaybe("SCREEN_PIXEL_SIZE", ScreenPixelSize);
|
||||
program.SetUniformMaybe("TIME", Time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Buffers;
|
||||
using Robust.Client.GameObjects;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
@@ -28,6 +27,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal partial class Clyde
|
||||
{
|
||||
/*
|
||||
// Horizontal width, in pixels, of the shadow maps used to render regular lights.
|
||||
private const int ShadowMapSize = 512;
|
||||
|
||||
@@ -93,12 +93,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private ClydeTexture ShadowTexture => _shadowRenderTarget.Texture;
|
||||
|
||||
private (PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)[] _lightsToRenderList = default!;
|
||||
*/
|
||||
|
||||
private LightCapacityComparer _lightCap = new();
|
||||
private ShadowCapacityComparer _shadowCap = new ShadowCapacityComparer();
|
||||
|
||||
private float _maxLightRadius;
|
||||
|
||||
/*
|
||||
private unsafe void InitLighting()
|
||||
{
|
||||
_cfg.OnValueChanged(CVars.MaxLightRadius, val => { _maxLightRadius = val;}, true);
|
||||
@@ -1271,5 +1273,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_lightsToRenderList = new (PointLightComponent, Vector2, float , Angle)[value];
|
||||
DebugTools.Assert(_maxLights >= _maxShadowcastingLights);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -19,14 +20,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly IEntityManager _entities;
|
||||
private readonly SpriteBatch _spriteBatch;
|
||||
|
||||
public DrawingHandleScreen DrawingHandleScreen { get; }
|
||||
public DrawingHandleWorld DrawingHandleWorld { get; }
|
||||
|
||||
public RenderHandle(Clyde clyde, IEntityManager entities)
|
||||
public RenderHandle(Clyde clyde, IEntityManager entities, SpriteBatch spriteBatch)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_entities = entities;
|
||||
_spriteBatch = spriteBatch;
|
||||
|
||||
var white = _clyde.GetStockTexture(ClydeStockTexture.White);
|
||||
DrawingHandleScreen = new DrawingHandleScreenImpl(white, this);
|
||||
@@ -35,7 +38,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void SetModelTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_clyde.DrawSetModelTransform(matrix);
|
||||
matrix.Transpose(out var transposed);
|
||||
_spriteBatch.SetModelTransform((Matrix3x2)transposed);
|
||||
}
|
||||
|
||||
public Matrix3x2 GetModelTransform()
|
||||
@@ -45,7 +49,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void SetProjView(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
_clyde.DrawSetProjViewTransform(proj, view);
|
||||
// _clyde.DrawSetProjViewTransform(proj, view);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,7 +72,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var (w, h) = clydeTexture.Size;
|
||||
var sr = new Box2(csr.Left / w, (h - csr.Top) / h, csr.Right / w, (h - csr.Bottom) / h);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
_spriteBatch.Draw(clydeTexture, bl, br, tl, tr, modulate, sr);
|
||||
// _clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -90,7 +95,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
// _clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
|
||||
@@ -130,12 +135,52 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void RenderInRenderTarget(IRenderTarget target, Action a, Color? clearColor)
|
||||
{
|
||||
_clyde.RenderInRenderTarget((RenderTargetBase) target, a, clearColor);
|
||||
// TODO: Save/restore SpriteBatch state
|
||||
|
||||
RhiTextureView targetTexture;
|
||||
RhiTextureView? depthView = null;
|
||||
Vector2i targetSize;
|
||||
|
||||
var loaded = _clyde.RtToLoaded((RenderTargetBase)target);
|
||||
|
||||
if (loaded.IsWindow)
|
||||
{
|
||||
targetTexture = loaded.Window!.CurSwapchainView!;
|
||||
targetSize = loaded.Window!.FramebufferSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var loadedTex = _clyde._loadedTextures[loaded.TextureHandle];
|
||||
targetTexture = loadedTex.DefaultRhiView;
|
||||
|
||||
depthView = loaded.DepthSencilTextureView;
|
||||
targetSize = loaded.Size;
|
||||
}
|
||||
|
||||
_spriteBatch.BeginPass(targetSize, targetTexture, clearColor);
|
||||
|
||||
a();
|
||||
|
||||
_spriteBatch.EndPass();
|
||||
}
|
||||
|
||||
public void SetScissor(UIBox2i? scissorBox)
|
||||
{
|
||||
_clyde.DrawSetScissor(scissorBox);
|
||||
if (scissorBox is { } box)
|
||||
{
|
||||
var (targetWidth, targetHeight) = _spriteBatch.CurrentTargetSize;
|
||||
|
||||
_spriteBatch.SetScissor(
|
||||
Math.Max(0, box.Left),
|
||||
Math.Max(0, box.Top),
|
||||
Math.Min(targetWidth, box.Width),
|
||||
Math.Min(targetHeight, box.Height)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_spriteBatch.ClearScissor();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -165,6 +210,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
TransformComponent? xform = null,
|
||||
SharedTransformSystem? xformSystem = null)
|
||||
{
|
||||
/*
|
||||
if (_entities.Deleted(entity))
|
||||
{
|
||||
throw new ArgumentException("Tried to draw an entity has been deleted.", nameof(entity));
|
||||
@@ -216,11 +262,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Reset to screen space
|
||||
SetProjView(oldProj, oldView);
|
||||
SetModelTransform(oldModel);
|
||||
*/
|
||||
}
|
||||
|
||||
public void DrawLine(Vector2 a, Vector2 b, Color color)
|
||||
{
|
||||
_clyde.DrawLine(a, b, color);
|
||||
// _clyde.DrawLine(a, b, color);
|
||||
}
|
||||
|
||||
public void UseShader(ShaderInstance? shader)
|
||||
@@ -232,7 +279,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var clydeShader = (ClydeShaderInstance?) shader;
|
||||
|
||||
_clyde.DrawUseShader(clydeShader ?? _clyde._defaultShader);
|
||||
// _clyde.DrawUseShader(clydeShader?.Handle ?? _clyde._defaultShader.Handle);
|
||||
}
|
||||
|
||||
public ShaderInstance? GetShader()
|
||||
@@ -244,24 +291,27 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void Viewport(Box2i viewport)
|
||||
{
|
||||
_clyde.DrawViewport(viewport);
|
||||
// _clyde.DrawViewport(viewport);
|
||||
}
|
||||
|
||||
public void UseRenderTarget(IRenderTarget? renderTarget)
|
||||
{
|
||||
var target = (RenderTexture?) renderTarget;
|
||||
/*var target = (RenderTexture?) renderTarget;
|
||||
|
||||
_clyde.DrawRenderTarget(target?.Handle ?? default);
|
||||
_clyde.DrawRenderTarget(target?.Handle ?? default);*/
|
||||
}
|
||||
|
||||
/*
|
||||
public void Clear(Color color, int stencil = 0, ClearBufferMask mask = ClearBufferMask.ColorBufferBit)
|
||||
{
|
||||
_clyde.DrawClear(color, stencil, mask);
|
||||
}
|
||||
*/
|
||||
|
||||
public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, Texture texture,
|
||||
ReadOnlySpan<DrawVertexUV2DColor> vertices)
|
||||
{
|
||||
/*
|
||||
if (!(texture is ClydeTexture clydeTexture))
|
||||
{
|
||||
throw new ArgumentException("Texture must be a basic texture.");
|
||||
@@ -270,20 +320,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var castSpan = MemoryMarshal.Cast<DrawVertexUV2DColor, Vertex2D>(vertices);
|
||||
|
||||
_clyde.DrawPrimitives(primitiveTopology, clydeTexture.TextureId, castSpan);
|
||||
*/
|
||||
}
|
||||
|
||||
public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, Texture texture,
|
||||
ReadOnlySpan<ushort> indices,
|
||||
ReadOnlySpan<DrawVertexUV2DColor> vertices)
|
||||
{
|
||||
if (!(texture is ClydeTexture clydeTexture))
|
||||
/*if (!(texture is ClydeTexture clydeTexture))
|
||||
{
|
||||
throw new ArgumentException("Texture must be a basic texture.");
|
||||
}
|
||||
|
||||
var castSpan = MemoryMarshal.Cast<DrawVertexUV2DColor, Vertex2D>(vertices);
|
||||
|
||||
_clyde.DrawPrimitives(primitiveTopology, clydeTexture.TextureId, indices, castSpan);
|
||||
_clyde.DrawPrimitives(primitiveTopology, clydeTexture.TextureId, indices, castSpan);*/
|
||||
}
|
||||
|
||||
// ---- (end) ----
|
||||
|
||||
@@ -1,34 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
// ReSharper disable once IdentifierTypo
|
||||
using RTCF = Robust.Client.Graphics.RenderTargetColorFormat;
|
||||
using PIF = OpenToolkit.Graphics.OpenGL4.PixelInternalFormat;
|
||||
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
|
||||
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private readonly Dictionary<ClydeHandle, LoadedRenderTarget> _renderTargets =
|
||||
new();
|
||||
private readonly Dictionary<ClydeHandle, LoadedRenderTarget> _renderTargets = new();
|
||||
|
||||
private readonly ConcurrentQueue<ClydeHandle> _renderTargetDisposeQueue
|
||||
= new();
|
||||
|
||||
// This is always kept up-to-date, except in CreateRenderTarget (because it restores the old value)
|
||||
// It is used for SRGB emulation.
|
||||
// It, like _mainWindowRenderTarget, is initialized in Clyde's constructor
|
||||
private LoadedRenderTarget _currentBoundRenderTarget;
|
||||
// private readonly ConcurrentQueue<ClydeHandle> _renderTargetDisposeQueue = new();
|
||||
|
||||
|
||||
public IRenderTexture CreateLightRenderTarget(Vector2i size, string? name = null, bool depthStencil = true)
|
||||
@@ -56,176 +45,77 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DebugTools.Assert(size.X != 0);
|
||||
DebugTools.Assert(size.Y != 0);
|
||||
|
||||
// Cache currently bound framebuffers
|
||||
// so if somebody creates a framebuffer while drawing it won't ruin everything.
|
||||
// Note that this means _currentBoundRenderTarget goes temporarily out of sync here
|
||||
var boundDrawBuffer = GL.GetInteger(
|
||||
_isGLES2 ? GetPName.FramebufferBinding : GetPName.DrawFramebufferBinding);
|
||||
var boundReadBuffer = 0;
|
||||
if (_hasGLReadFramebuffer)
|
||||
{
|
||||
boundReadBuffer = GL.GetInteger(GetPName.ReadFramebufferBinding);
|
||||
}
|
||||
|
||||
// Generate FBO.
|
||||
var fbo = new GLHandle(GL.GenFramebuffer());
|
||||
|
||||
// Bind color attachment to FBO.
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, fbo.Handle);
|
||||
CheckGlError();
|
||||
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.Framebuffer, fbo, name);
|
||||
|
||||
var (width, height) = size;
|
||||
|
||||
ClydeTexture textureObject;
|
||||
GLHandle depthStencilBuffer = default;
|
||||
|
||||
var estPixSize = 0L;
|
||||
RhiTexture? depthStencilTexture = null;
|
||||
RhiTextureView? depthStencilTextureView = null;
|
||||
|
||||
// Color attachment.
|
||||
{
|
||||
var texture = new GLHandle(GL.GenTexture());
|
||||
CheckGlError();
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, texture.Handle);
|
||||
CheckGlError();
|
||||
|
||||
ApplySampleParameters(sampleParameters);
|
||||
|
||||
var colorFormat = format.ColorFormat;
|
||||
if ((!_hasGLSrgb) && (colorFormat == RTCF.Rgba8Srgb))
|
||||
var textureFormat = format.ColorFormat switch
|
||||
{
|
||||
// If SRGB is not supported, switch formats.
|
||||
// The shaders will have to compensate.
|
||||
// Note that a check is performed on the *original* format.
|
||||
colorFormat = RTCF.Rgba8;
|
||||
}
|
||||
// This isn't good
|
||||
if (!_hasGLFloatFramebuffers)
|
||||
{
|
||||
switch (colorFormat)
|
||||
{
|
||||
case RTCF.R32F:
|
||||
case RTCF.RG32F:
|
||||
case RTCF.R11FG11FB10F:
|
||||
case RTCF.Rgba16F:
|
||||
_sawmillOgl.Warning("The framebuffer {0} [{1}] is trying to be floating-point when that's not supported. Forcing Rgba8.", name == null ? "[unnamed]" : name, size);
|
||||
colorFormat = RTCF.Rgba8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to specify the correct pixel type and formats even if we're not uploading any data.
|
||||
// Not doing this (just sending red/byte) is fine on desktop GL but illegal on ES.
|
||||
// @formatter:off
|
||||
var (internalFormat, pixFormat, pixType) = colorFormat switch
|
||||
{
|
||||
RTCF.Rgba8 => (PIF.Rgba8, PF.Rgba, PT.UnsignedByte),
|
||||
RTCF.Rgba16F => (PIF.Rgba16f, PF.Rgba, PT.Float),
|
||||
RTCF.Rgba8Srgb => (PIF.Srgb8Alpha8, PF.Rgba, PT.UnsignedByte),
|
||||
RTCF.R11FG11FB10F => (PIF.R11fG11fB10f, PF.Rgb, PT.Float),
|
||||
RTCF.R32F => (PIF.R32f, PF.Red, PT.Float),
|
||||
RTCF.RG32F => (PIF.Rg32f, PF.Rg, PT.Float),
|
||||
RTCF.R8 => (PIF.R8, PF.Red, PT.UnsignedByte),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format.ColorFormat), format.ColorFormat, null)
|
||||
RenderTargetColorFormat.Rgba8 => RhiTextureFormat.BGRA8Unorm,
|
||||
RenderTargetColorFormat.Rgba8Srgb => RhiTextureFormat.BGRA8UnormSrgb,
|
||||
RenderTargetColorFormat.R32F => RhiTextureFormat.R32Float,
|
||||
RenderTargetColorFormat.RG32F => RhiTextureFormat.RG32Float,
|
||||
RenderTargetColorFormat.Rgba16F => RhiTextureFormat.RGBA16Float,
|
||||
RenderTargetColorFormat.R11FG11FB10F => RhiTextureFormat.RG11B10Ufloat,
|
||||
RenderTargetColorFormat.R8 => RhiTextureFormat.R8Unorm,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
// @formatter:on
|
||||
|
||||
if (_isGLES2)
|
||||
{
|
||||
(internalFormat, pixFormat, pixType) = colorFormat switch
|
||||
{
|
||||
RTCF.Rgba8 => (PIF.Rgba, PF.Rgba, PT.UnsignedByte),
|
||||
RTCF.R8 => (PIF.Rgba, PF.Rgba, PT.UnsignedByte),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format.ColorFormat), format.ColorFormat, null)
|
||||
};
|
||||
}
|
||||
|
||||
estPixSize += EstPixelSize(internalFormat);
|
||||
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, internalFormat, width, height, 0, pixFormat,
|
||||
pixType, IntPtr.Zero);
|
||||
CheckGlError();
|
||||
|
||||
if (!_isGLES)
|
||||
{
|
||||
GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0,
|
||||
texture.Handle,
|
||||
0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// OpenGL ES uses a different name, and has an odd added target argument
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0,
|
||||
TextureTarget.Texture2D, texture.Handle, 0);
|
||||
}
|
||||
CheckGlError();
|
||||
|
||||
// Check on original format is NOT a bug, this is so srgb emulation works
|
||||
textureObject = GenTexture(texture, size, format.ColorFormat == RTCF.Rgba8Srgb, name == null ? null : $"{name}-color", TexturePixelType.RenderTarget);
|
||||
(textureObject, _) = CreateBlankTextureCore(
|
||||
size,
|
||||
name != null ? $"RT_{name}_color" : null,
|
||||
textureFormat,
|
||||
sampleParameters ?? TextureSampleParameters.Default,
|
||||
format.ColorFormat == RenderTargetColorFormat.Rgba8Srgb
|
||||
);
|
||||
}
|
||||
|
||||
// Depth/stencil buffers.
|
||||
if (format.HasDepthStencil)
|
||||
{
|
||||
depthStencilBuffer = new GLHandle(GL.GenRenderbuffer());
|
||||
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, depthStencilBuffer.Handle);
|
||||
CheckGlError();
|
||||
var depthLabel = name != null ? $"RT_{name}_depthStencil" : null;
|
||||
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.Renderbuffer, depthStencilBuffer,
|
||||
name == null ? null : $"{name}-depth-stencil");
|
||||
depthStencilTexture = Rhi.CreateTexture(new RhiTextureDescriptor(
|
||||
new RhiExtent3D(size.X, size.Y),
|
||||
RhiTextureFormat.Depth24PlusStencil8,
|
||||
RhiTextureUsage.RenderAttachment,
|
||||
Label: depthLabel
|
||||
));
|
||||
|
||||
GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, width,
|
||||
height);
|
||||
CheckGlError();
|
||||
|
||||
estPixSize += 4;
|
||||
|
||||
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment,
|
||||
RenderbufferTarget.Renderbuffer, depthStencilBuffer.Handle);
|
||||
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.StencilAttachment,
|
||||
RenderbufferTarget.Renderbuffer, depthStencilBuffer.Handle);
|
||||
CheckGlError();
|
||||
depthStencilTextureView = depthStencilTexture.CreateView(new RhiTextureViewDescriptor
|
||||
{
|
||||
Aspect = RhiTextureAspect.All,
|
||||
Dimension = RhiTextureViewDimension.Dim2D,
|
||||
Format = RhiTextureFormat.Depth24PlusStencil8,
|
||||
Label = depthLabel,
|
||||
MipLevelCount = 1,
|
||||
ArrayLayerCount = 1,
|
||||
BaseArrayLayer = 0,
|
||||
BaseMipLevel = 0
|
||||
});
|
||||
}
|
||||
|
||||
// This should always pass but OpenGL makes it easy to check for once so let's.
|
||||
var status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||
CheckGlError();
|
||||
DebugTools.Assert(status == FramebufferErrorCode.FramebufferComplete,
|
||||
$"new framebuffer has bad status {status}");
|
||||
|
||||
// Re-bind previous framebuffers (thus _currentBoundRenderTarget is back in sync)
|
||||
GL.BindFramebuffer(
|
||||
_isGLES2 ? FramebufferTarget.Framebuffer : FramebufferTarget.DrawFramebuffer,
|
||||
boundDrawBuffer);
|
||||
CheckGlError();
|
||||
if (_hasGLReadFramebuffer)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, boundReadBuffer);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
var pressure = estPixSize * size.X * size.Y;
|
||||
|
||||
var handle = AllocRid();
|
||||
var renderTarget = new RenderTexture(size, textureObject, this, handle);
|
||||
var data = new LoadedRenderTarget
|
||||
{
|
||||
IsWindow = false,
|
||||
IsSrgb = textureObject.IsSrgb,
|
||||
DepthStencilHandle = depthStencilBuffer,
|
||||
FramebufferHandle = fbo,
|
||||
Size = size,
|
||||
TextureHandle = textureObject.TextureId,
|
||||
MemoryPressure = pressure,
|
||||
ColorFormat = format.ColorFormat,
|
||||
DepthStencilTexture = depthStencilTexture,
|
||||
DepthSencilTextureView = depthStencilTextureView
|
||||
MemoryPressure = pressure,
|
||||
SampleParameters = sampleParameters,
|
||||
Instance = new WeakReference<RenderTargetBase>(renderTarget),
|
||||
Name = name,
|
||||
};
|
||||
|
||||
//GC.AddMemoryPressure(pressure);
|
||||
var renderTarget = new RenderTexture(size, textureObject, this, handle);
|
||||
_renderTargets.Add(handle, data);
|
||||
return renderTarget;
|
||||
}
|
||||
@@ -233,38 +123,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void DeleteRenderTexture(ClydeHandle handle)
|
||||
{
|
||||
if (!_renderTargets.TryGetValue(handle, out var renderTarget))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugTools.Assert(renderTarget.FramebufferHandle != default);
|
||||
DebugTools.Assert(!renderTarget.IsWindow, "Cannot delete window-backed render targets directly.");
|
||||
|
||||
GL.DeleteFramebuffer(renderTarget.FramebufferHandle.Handle);
|
||||
renderTarget.FramebufferHandle = default;
|
||||
CheckGlError();
|
||||
_renderTargets.Remove(handle);
|
||||
DeleteTexture(renderTarget.TextureHandle);
|
||||
|
||||
if (renderTarget.DepthStencilHandle != default)
|
||||
{
|
||||
GL.DeleteRenderbuffer(renderTarget.DepthStencilHandle.Handle);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
//GC.RemoveMemoryPressure(renderTarget.MemoryPressure);
|
||||
}
|
||||
|
||||
private void BindRenderTargetFull(LoadedRenderTarget rt)
|
||||
{
|
||||
BindRenderTargetImmediate(rt);
|
||||
_currentRenderTarget = rt;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void BindRenderTargetFull(RenderTargetBase rt)
|
||||
{
|
||||
BindRenderTargetFull(RtToLoaded(rt));
|
||||
renderTarget.DepthStencilTexture?.Dispose();
|
||||
renderTarget.DepthSencilTextureView?.Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -279,22 +146,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void BindRenderTargetImmediate(LoadedRenderTarget rt)
|
||||
{
|
||||
// NOTE: It's critically important that this be the "focal point" of all framebuffer bindings.
|
||||
if (rt.IsWindow)
|
||||
{
|
||||
_glContext!.BindWindowRenderTarget(rt.WindowId);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, rt.FramebufferHandle.Handle);
|
||||
CheckGlError();
|
||||
}
|
||||
_currentBoundRenderTarget = rt;
|
||||
}
|
||||
|
||||
/*
|
||||
private void FlushRenderTargetDispose()
|
||||
{
|
||||
while (_renderTargetDisposeQueue.TryDequeue(out var handle))
|
||||
@@ -302,6 +154,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DeleteRenderTexture(handle);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public IEnumerable<(RenderTargetBase, LoadedRenderTarget)> GetLoadedRenderTextures()
|
||||
{
|
||||
@@ -317,8 +170,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
internal sealed class LoadedRenderTarget
|
||||
{
|
||||
public bool IsWindow;
|
||||
public WindowId WindowId;
|
||||
public string? Name;
|
||||
public WindowReg? Window;
|
||||
|
||||
public Vector2i Size;
|
||||
public bool IsSrgb;
|
||||
@@ -328,19 +180,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public RTCF ColorFormat;
|
||||
|
||||
// Remaining properties only apply if the render target is NOT a window.
|
||||
// Handle to the framebuffer object.
|
||||
public GLHandle FramebufferHandle;
|
||||
|
||||
// Handle to the loaded clyde texture managing the color attachment.
|
||||
public ClydeHandle TextureHandle;
|
||||
|
||||
// Renderbuffer handle
|
||||
public GLHandle DepthStencilHandle;
|
||||
public long MemoryPressure;
|
||||
|
||||
public TextureSampleParameters? SampleParameters;
|
||||
|
||||
public required WeakReference<RenderTargetBase> Instance;
|
||||
// Depth/stencil attachment.
|
||||
public RhiTexture? DepthStencilTexture;
|
||||
public RhiTextureView? DepthSencilTextureView;
|
||||
}
|
||||
|
||||
internal abstract class RenderTargetBase : IRenderTarget
|
||||
@@ -348,9 +193,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
protected readonly Clyde Clyde;
|
||||
private bool _disposed;
|
||||
|
||||
public bool MakeGLFence;
|
||||
public nint LastGLSync;
|
||||
|
||||
protected RenderTargetBase(Clyde clyde, ClydeHandle handle)
|
||||
{
|
||||
Clyde = clyde;
|
||||
@@ -359,9 +201,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public abstract Vector2i Size { get; }
|
||||
|
||||
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null) where T : unmanaged, IPixel<T>
|
||||
public void CopyPixelsToMemory<T>(CopyPixelsDelegate<T> callback, UIBox2i? subRegion = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
Clyde.CopyRenderTargetPixels(Handle, subRegion, callback);
|
||||
throw new NotImplementedException();
|
||||
//Clyde.CopyRenderTargetPixels(Handle, subRegion, callback);
|
||||
}
|
||||
|
||||
public ClydeHandle Handle { get; }
|
||||
@@ -396,7 +240,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
protected virtual void DisposeDeferredImpl()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
~RenderTargetBase()
|
||||
@@ -432,11 +275,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
protected override void DisposeDeferredImpl()
|
||||
{
|
||||
Clyde._renderTargetDisposeQueue.Enqueue(Handle);
|
||||
throw new NotImplementedException();
|
||||
// Clyde._renderTargetDisposeQueue.Enqueue(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenderWindow : RenderTargetBase
|
||||
internal sealed class RenderWindow : RenderTargetBase
|
||||
{
|
||||
public override Vector2i Size => Clyde._renderTargets[Handle].Size;
|
||||
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/*
|
||||
// The amount of quads we can render with ushort indices, leaving open 65536 for primitive restart.
|
||||
private const ushort MaxBatchQuads = (2 << 13) - 1; // In human terms: (2**16/4)-1 = 16383
|
||||
|
||||
@@ -825,7 +822,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_batchLoadedTexture = null;
|
||||
}
|
||||
*/
|
||||
#1#
|
||||
}
|
||||
|
||||
private void FinishBatch()
|
||||
@@ -1145,16 +1142,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLCaps = glcaps;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum GLCaps : ushort
|
||||
{
|
||||
// If you add flags here make sure to update PopRenderState!
|
||||
None = 0,
|
||||
|
||||
Blending = 1 << 0,
|
||||
|
||||
Stencilling = 1 << 2,
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
67
Robust.Client/Graphics/Clyde/Clyde.Rhi.cs
Normal file
67
Robust.Client/Graphics/Clyde/Clyde.Rhi.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde;
|
||||
|
||||
internal sealed partial class Clyde
|
||||
{
|
||||
public RhiBase Rhi { get; private set; } = default!;
|
||||
|
||||
private void InitRhi()
|
||||
{
|
||||
DebugTools.Assert(_windowing != null);
|
||||
DebugTools.Assert(_mainWindow != null);
|
||||
|
||||
var graphicsApi = _cfg.GetCVar(CVars.DisplayRhi);
|
||||
_logManager.GetSawmill("clyde.rhi").Debug("Initializing RHI {RhiName}", graphicsApi);
|
||||
|
||||
Rhi = graphicsApi switch
|
||||
{
|
||||
"webGpu" => new RhiWebGpu(this, _deps),
|
||||
_ => throw new Exception($"Unknown RHI: {graphicsApi}")
|
||||
};
|
||||
|
||||
Rhi.Init();
|
||||
}
|
||||
|
||||
private void AcquireSwapchainTextures()
|
||||
{
|
||||
// wgpu doesn't like it if we don't do anything with the swap chain images we acquire.
|
||||
// To be safe, let's just always clear them every frame.
|
||||
|
||||
var encoder = Rhi.CreateCommandEncoder(new RhiCommandEncoderDescriptor("Clear acquired swap chains"));
|
||||
|
||||
foreach (var window in _windows)
|
||||
{
|
||||
window.CurSwapchainView?.Dispose();
|
||||
window.CurSwapchainView = Rhi.CreateTextureViewForWindow(window);
|
||||
|
||||
var pass = encoder.BeginRenderPass(new RhiRenderPassDescriptor(
|
||||
new[]
|
||||
{
|
||||
new RhiRenderPassColorAttachment(window.CurSwapchainView, RhiLoadOp.Clear, RhiStoreOp.Store)
|
||||
}
|
||||
));
|
||||
|
||||
pass.End();
|
||||
}
|
||||
|
||||
Rhi.Queue.Submit(encoder.Finish());
|
||||
}
|
||||
|
||||
private void PresentWindows()
|
||||
{
|
||||
foreach (var window in _windows)
|
||||
{
|
||||
if (window.CurSwapchainView == null)
|
||||
return;
|
||||
|
||||
window.CurSwapchainView.Dispose();
|
||||
window.CurSwapchainView = null;
|
||||
|
||||
Rhi.WindowPresent(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
|
||||
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -27,17 +24,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//
|
||||
// For CopyPixels on render targets, the copy and transfer is started immediately when the function is called.
|
||||
|
||||
/*
|
||||
private readonly List<QueuedScreenshot> _queuedScreenshots = new();
|
||||
private readonly List<TransferringPixelCopy> _transferringPixelCopies = new();
|
||||
*/
|
||||
|
||||
public void Screenshot(ScreenshotType type, CopyPixelsDelegate<Rgb24> callback, UIBox2i? subRegion = null)
|
||||
{
|
||||
_queuedScreenshots.Add(new QueuedScreenshot(type, callback, subRegion));
|
||||
// _queuedScreenshots.Add(new QueuedScreenshot(type, callback, subRegion));
|
||||
}
|
||||
|
||||
private void TakeScreenshot(ScreenshotType type)
|
||||
{
|
||||
if (_queuedScreenshots.Count == 0)
|
||||
/*if (_queuedScreenshots.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -53,10 +52,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
DoCopyPixels(ScreenSize, subRegion, callback);
|
||||
_queuedScreenshots.RemoveSwap(i--);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private void CopyRenderTargetPixels<T>(
|
||||
/*private void CopyRenderTargetPixels<T>(
|
||||
ClydeHandle renderTarget,
|
||||
UIBox2i? subRegion,
|
||||
CopyPixelsDelegate<T> callback)
|
||||
@@ -218,6 +217,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Vector2i Size,
|
||||
// Funny callback dance to handle the generics.
|
||||
Action<TransferringPixelCopy> TransferContinue,
|
||||
Delegate Callback);
|
||||
Delegate Callback);*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -41,8 +40,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class LoadedShader
|
||||
{
|
||||
[ViewVariables]
|
||||
public GLShaderProgram Program = default!;
|
||||
// [ViewVariables]
|
||||
// public GLShaderProgram Program = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public string? Name;
|
||||
@@ -89,7 +88,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public ClydeHandle LoadShader(ParsedShader shader, string? name = null, Dictionary<string,string>? defines = null)
|
||||
{
|
||||
var (vertBody, fragBody) = GetShaderCode(shader);
|
||||
/*var (vertBody, fragBody) = GetShaderCode(shader);
|
||||
|
||||
var program = _compileProgram(vertBody, fragBody, BaseShaderAttribLocations, name, defines: defines);
|
||||
|
||||
@@ -106,12 +105,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
};
|
||||
var handle = AllocRid();
|
||||
_loadedShaders.Add(handle, loaded);
|
||||
return handle;
|
||||
return handle;*/
|
||||
return default;
|
||||
}
|
||||
|
||||
public void ReloadShader(ClydeHandle handle, ParsedShader newShader)
|
||||
{
|
||||
var loaded = _loadedShaders[handle];
|
||||
/*var loaded = _loadedShaders[handle];
|
||||
|
||||
var (vertBody, fragBody) = GetShaderCode(newShader);
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
program.BindBlock(UniProjViewMatrices, BindingIndexProjView);
|
||||
program.BindBlock(UniUniformConstants, BindingIndexUniformConstants);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public ShaderInstance InstanceShader(ShaderSourceResource source, bool? lighting = null, ShaderBlendMode? mode = null)
|
||||
@@ -142,6 +142,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return instance;
|
||||
}
|
||||
|
||||
/*
|
||||
private void LoadStockShaders()
|
||||
{
|
||||
_shaderLibrary = ReadEmbeddedShader("z-library.glsl");
|
||||
@@ -162,6 +163,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
}
|
||||
*/
|
||||
|
||||
private string ReadEmbeddedShader(string fileName)
|
||||
{
|
||||
@@ -172,6 +174,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
/*
|
||||
private GLShaderProgram _compileProgram(string vertexSource, string fragmentSource,
|
||||
(string, uint)[] attribLocations, string? name = null, bool includeLib=true, Dictionary<string,string>? defines=null)
|
||||
{
|
||||
@@ -279,6 +282,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
fragmentShader?.Delete();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private (string vertBody, string fragBody) GetShaderCode(ParsedShader shader)
|
||||
{
|
||||
|
||||
578
Robust.Client/Graphics/Clyde/Clyde.SpriteBatch.cs
Normal file
578
Robust.Client/Graphics/Clyde/Clyde.SpriteBatch.cs
Normal file
@@ -0,0 +1,578 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using RVector2 = Robust.Shared.Maths.Vector2;
|
||||
using RVector4 = Robust.Shared.Maths.Vector4;
|
||||
using SVector2 = System.Numerics.Vector2;
|
||||
using SVector4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde;
|
||||
|
||||
internal partial class Clyde
|
||||
{
|
||||
// Implementation of a sprite batch on the new RHI.
|
||||
// Basically, the old rendering API.
|
||||
|
||||
private sealed class SpriteBatch
|
||||
{
|
||||
private const int VertexSize = 32;
|
||||
private const int UniformPassSize = 32;
|
||||
|
||||
private const uint BindGroup0 = 0;
|
||||
private const uint BindGroup1 = 1;
|
||||
private const uint BindGroup2 = 2;
|
||||
|
||||
private readonly Clyde _clyde;
|
||||
private readonly RhiBase _rhi;
|
||||
|
||||
private readonly GpuExpansionBuffer _vertexBufferPool;
|
||||
|
||||
private readonly RhiBuffer _uniformConstantsBuffer;
|
||||
private readonly GpuExpansionBuffer _uniformPassPool;
|
||||
|
||||
private readonly RhiRenderPipeline _pipeline;
|
||||
|
||||
private readonly RhiBindGroupLayout _group0Layout;
|
||||
private readonly RhiBindGroupLayout _group1Layout;
|
||||
private readonly RhiBindGroupLayout _group2Layout;
|
||||
|
||||
// Active recording state
|
||||
private ValueList<RhiBindGroup> _tempBindGroups;
|
||||
private RhiCommandEncoder? _commandEncoder;
|
||||
private int _vertexStartIndex;
|
||||
private RhiBuffer? _currentVertexBuffer;
|
||||
|
||||
private RhiRenderPassEncoder? _passEncoder;
|
||||
private Vector2i _currentPassSize;
|
||||
|
||||
// Active batch state
|
||||
private int _curBatchSize = 0;
|
||||
|
||||
private DirtyStateFlags _dirtyStateFlags;
|
||||
private State<ClydeHandle> _stateTexture;
|
||||
private State<(uint x, uint y, uint w, uint h)> _stateScissorRect;
|
||||
private bool _scissorSkip;
|
||||
|
||||
// Other state
|
||||
private Matrix3x2 _modelTransform;
|
||||
|
||||
public bool IsInRenderPass => _passEncoder != null;
|
||||
|
||||
public SpriteBatch(Clyde clyde, RhiBase rhi)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_rhi = rhi;
|
||||
|
||||
_vertexBufferPool = new GpuExpansionBuffer(
|
||||
_rhi,
|
||||
8192 * VertexSize,
|
||||
RhiBufferUsageFlags.Vertex | RhiBufferUsageFlags.CopyDst,
|
||||
label: "_vertexBuffer"
|
||||
);
|
||||
|
||||
_uniformConstantsBuffer = _rhi.CreateBuffer(new RhiBufferDescriptor(
|
||||
sizeof(float),
|
||||
RhiBufferUsageFlags.Uniform,
|
||||
Label: "_uniformConstantsBuffer"
|
||||
));
|
||||
|
||||
var uniformPoolSize = MathHelper.CeilingPowerOfTwo(
|
||||
UniformPassSize,
|
||||
(int)_rhi.DeviceLimits.MinUniformBufferOffsetAlignment
|
||||
) * 20;
|
||||
|
||||
_uniformPassPool = new GpuExpansionBuffer(
|
||||
_rhi,
|
||||
uniformPoolSize,
|
||||
RhiBufferUsageFlags.Uniform | RhiBufferUsageFlags.CopyDst,
|
||||
label: "_uniformPassBuffer");
|
||||
|
||||
var res = clyde._resourceCache;
|
||||
var shaderSource = res.ContentFileReadAllText("/Shaders/Internal/default-sprite.wgsl");
|
||||
|
||||
using var shader = _rhi.CreateShaderModule(new RhiShaderModuleDescriptor(
|
||||
shaderSource,
|
||||
"default-sprite.wgsl"
|
||||
));
|
||||
|
||||
_group0Layout = _rhi.CreateBindGroupLayout(new RhiBindGroupLayoutDescriptor(
|
||||
new[]
|
||||
{
|
||||
new RhiBindGroupLayoutEntry(
|
||||
0,
|
||||
RhiShaderStage.Vertex | RhiShaderStage.Fragment,
|
||||
new RhiBufferBindingLayout(MinBindingSize: 4)
|
||||
)
|
||||
},
|
||||
"SpriteBatch bind group 0 (constants)"
|
||||
));
|
||||
|
||||
_group1Layout = _rhi.CreateBindGroupLayout(new RhiBindGroupLayoutDescriptor(
|
||||
new[]
|
||||
{
|
||||
new RhiBindGroupLayoutEntry(
|
||||
0,
|
||||
RhiShaderStage.Vertex | RhiShaderStage.Fragment,
|
||||
new RhiBufferBindingLayout(MinBindingSize: 32)
|
||||
)
|
||||
},
|
||||
"SpriteBatch bind group 1 (view)"
|
||||
));
|
||||
|
||||
_group2Layout = _rhi.CreateBindGroupLayout(new RhiBindGroupLayoutDescriptor(
|
||||
new[]
|
||||
{
|
||||
new RhiBindGroupLayoutEntry(
|
||||
0,
|
||||
RhiShaderStage.Fragment,
|
||||
new RhiTextureBindingLayout()
|
||||
),
|
||||
new RhiBindGroupLayoutEntry(
|
||||
1,
|
||||
RhiShaderStage.Fragment,
|
||||
new RhiSamplerBindingLayout()
|
||||
)
|
||||
},
|
||||
"SpriteBatch bind group 2 (draw)"
|
||||
));
|
||||
|
||||
var layout = _rhi.CreatePipelineLayout(new RhiPipelineLayoutDescriptor(
|
||||
new[] { _group0Layout, _group1Layout, _group2Layout }, "SpriteBatch pipeline layout"
|
||||
));
|
||||
|
||||
_pipeline = _rhi.CreateRenderPipeline(new RhiRenderPipelineDescriptor(
|
||||
layout,
|
||||
new RhiVertexState(
|
||||
new RhiProgrammableStage(shader, "vs_main"),
|
||||
new[]
|
||||
{
|
||||
new RhiVertexBufferLayout(32, RhiVertexStepMode.Vertex, new[]
|
||||
{
|
||||
new RhiVertexAttribute(RhiVertexFormat.Float32x2, 0, 0),
|
||||
new RhiVertexAttribute(RhiVertexFormat.Float32x2, 8, 1),
|
||||
new RhiVertexAttribute(RhiVertexFormat.Float32x4, 16, 2),
|
||||
})
|
||||
}
|
||||
),
|
||||
new RhiPrimitiveState(),
|
||||
null,
|
||||
new RhiMultisampleState(),
|
||||
new RhiFragmentState(
|
||||
new RhiProgrammableStage(shader, "fs_main"),
|
||||
new[]
|
||||
{
|
||||
new RhiColorTargetState(
|
||||
RhiTextureFormat.BGRA8UnormSrgb,
|
||||
new RhiBlendState(
|
||||
new RhiBlendComponent(
|
||||
RhiBlendOperation.Add,
|
||||
RhiBlendFactor.SrcAlpha,
|
||||
RhiBlendFactor.OneMinusSrcAlpha
|
||||
),
|
||||
new RhiBlendComponent(RhiBlendOperation.Add, RhiBlendFactor.One, RhiBlendFactor.One)
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
"SpriteBatch pipeline"
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: this name sucks
|
||||
public Vector2i CurrentTargetSize => _currentPassSize;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Clear();
|
||||
|
||||
_commandEncoder = _rhi.CreateCommandEncoder(new RhiCommandEncoderDescriptor());
|
||||
}
|
||||
|
||||
public void BeginPass(Vector2i size, RhiTextureView targetTexture, Color? clearColor = null)
|
||||
{
|
||||
ClearPassState();
|
||||
|
||||
_currentPassSize = size;
|
||||
|
||||
var projMatrix = default(Matrix3x2);
|
||||
projMatrix.M11 = 2f / size.X;
|
||||
projMatrix.M22 = -2f / size.Y;
|
||||
projMatrix.M31 = -1;
|
||||
projMatrix.M32 = 1;
|
||||
|
||||
var viewMatrix = Matrix3x2.Identity;
|
||||
var projView = viewMatrix * projMatrix;
|
||||
|
||||
var uniformPass = _uniformPassPool.AllocateAligned<UniformView>(
|
||||
1,
|
||||
(int) _rhi.DeviceLimits.MinUniformBufferOffsetAlignment,
|
||||
out var uniformPos
|
||||
);
|
||||
|
||||
uniformPass[0] = new UniformView
|
||||
{
|
||||
ProjViewMatrix = ShaderMat3x2F.Transpose(projView),
|
||||
ScreenPixelSize = new SVector2(1f / size.X, 1f / size.Y)
|
||||
};
|
||||
|
||||
var rhiClearColor = clearColor == null ? new RhiColor(0, 0, 0, 1) : Color.FromSrgb(clearColor.Value);
|
||||
|
||||
_passEncoder = _commandEncoder!.BeginRenderPass(new RhiRenderPassDescriptor(
|
||||
new[]
|
||||
{
|
||||
new RhiRenderPassColorAttachment(
|
||||
targetTexture,
|
||||
RhiLoadOp.Clear,
|
||||
RhiStoreOp.Store,
|
||||
ClearValue: rhiClearColor)
|
||||
}
|
||||
));
|
||||
|
||||
_passEncoder.SetPipeline(_pipeline);
|
||||
|
||||
var constantsGroup = AllocTempBindGroup(new RhiBindGroupDescriptor(
|
||||
_group0Layout,
|
||||
new[] { new RhiBindGroupEntry(0, new RhiBufferBinding(_uniformConstantsBuffer)) }
|
||||
));
|
||||
|
||||
_passEncoder.SetBindGroup(BindGroup0, constantsGroup);
|
||||
|
||||
var passGroup = AllocTempBindGroup(new RhiBindGroupDescriptor(
|
||||
_group1Layout,
|
||||
new[]
|
||||
{
|
||||
new RhiBindGroupEntry(
|
||||
0,
|
||||
new RhiBufferBinding(uniformPos.Buffer, (ulong) uniformPos.ByteOffset, UniformPassSize)
|
||||
)
|
||||
}
|
||||
));
|
||||
|
||||
_passEncoder.SetBindGroup(BindGroup1, passGroup);
|
||||
}
|
||||
|
||||
public void EndPass()
|
||||
{
|
||||
FlushBatch();
|
||||
|
||||
_passEncoder!.End();
|
||||
_passEncoder = null;
|
||||
}
|
||||
|
||||
public void Finish()
|
||||
{
|
||||
DebugTools.Assert(_passEncoder == null, "Must end render pass before finishing the sprite batch!");
|
||||
|
||||
_uniformPassPool.Flush();
|
||||
_vertexBufferPool.Flush();
|
||||
|
||||
var buffer = _commandEncoder!.Finish();
|
||||
_rhi.Queue.Submit(buffer);
|
||||
|
||||
_commandEncoder = null;
|
||||
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void Draw(ClydeTexture texture, RVector2 position, Color color)
|
||||
{
|
||||
var textureHandle = texture.TextureId;
|
||||
|
||||
SetTexture(textureHandle);
|
||||
ValidateBatchState();
|
||||
|
||||
var vertices = AllocateVertexSpace(6);
|
||||
|
||||
var width = texture.Width;
|
||||
var height = texture.Height;
|
||||
|
||||
var bl = (SVector2)(position);
|
||||
var br = (SVector2)(position + (width, 0));
|
||||
var tr = (SVector2)(position + (width, height));
|
||||
var tl = (SVector2)(position + (0, height));
|
||||
|
||||
var sBl = SVector2.Transform(bl, _modelTransform);
|
||||
var sBr = SVector2.Transform(br, _modelTransform);
|
||||
var sTl = SVector2.Transform(tl, _modelTransform);
|
||||
var sTr = SVector2.Transform(tr, _modelTransform);
|
||||
|
||||
var asColor = Unsafe.As<Color, SVector4>(ref color);
|
||||
|
||||
vertices[0] = new Vertex2D(sBl, new SVector2(0, 1), asColor);
|
||||
vertices[1] = new Vertex2D(sBr, new SVector2(1, 1), asColor);
|
||||
vertices[2] = new Vertex2D(sTr, new SVector2(1, 0), asColor);
|
||||
vertices[3] = new Vertex2D(sTr, new SVector2(1, 0), asColor);
|
||||
vertices[4] = new Vertex2D(sTl, new SVector2(0, 0), asColor);
|
||||
vertices[5] = new Vertex2D(sBl, new SVector2(0, 1), asColor);
|
||||
|
||||
_vertexStartIndex += 6;
|
||||
_curBatchSize += 6;
|
||||
}
|
||||
|
||||
public void Draw(
|
||||
ClydeTexture texture,
|
||||
RVector2 bl, RVector2 br, RVector2 tl, RVector2 tr,
|
||||
in Color color,
|
||||
in Box2 region)
|
||||
{
|
||||
var textureHandle = texture.TextureId;
|
||||
|
||||
SetTexture(textureHandle);
|
||||
ValidateBatchState();
|
||||
|
||||
var vertices = AllocateVertexSpace(6);
|
||||
|
||||
var asColor = Unsafe.As<Color, SVector4>(ref Unsafe.AsRef(color));
|
||||
|
||||
var sBl = SVector2.Transform((SVector2)bl, _modelTransform);
|
||||
var sBr = SVector2.Transform((SVector2)br, _modelTransform);
|
||||
var sTl = SVector2.Transform((SVector2)tl, _modelTransform);
|
||||
var sTr = SVector2.Transform((SVector2)tr, _modelTransform);
|
||||
|
||||
vertices[0] = new Vertex2D(sBl, (SVector2)region.BottomLeft, asColor);
|
||||
vertices[1] = new Vertex2D(sBr, (SVector2)region.BottomRight, asColor);
|
||||
vertices[2] = new Vertex2D(sTr, (SVector2)region.TopRight, asColor);
|
||||
vertices[3] = vertices[2];
|
||||
vertices[4] = new Vertex2D(sTl, (SVector2)region.TopLeft, asColor);
|
||||
vertices[5] = vertices[0];
|
||||
|
||||
_vertexStartIndex += 6;
|
||||
_curBatchSize += 6;
|
||||
}
|
||||
|
||||
private Span<Vertex2D> AllocateVertexSpace(int count)
|
||||
{
|
||||
var space = _vertexBufferPool.Allocate<Vertex2D>(count, out var position);
|
||||
|
||||
DebugTools.Assert(
|
||||
position.ByteOffset % VertexSize == 0,
|
||||
"Vertices should be getting allocated in exact multiples of vertex size!"
|
||||
);
|
||||
|
||||
if (position.Buffer != _currentVertexBuffer)
|
||||
{
|
||||
FlushBatch();
|
||||
|
||||
_passEncoder!.SetVertexBuffer(0, position.Buffer);
|
||||
|
||||
_currentVertexBuffer = position.Buffer;
|
||||
_vertexStartIndex = position.ByteOffset / VertexSize;
|
||||
}
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
public void SetScissor(int x, int y, int width, int height)
|
||||
{
|
||||
checked
|
||||
{
|
||||
SetScissor((uint)x, (uint)y, (uint)width, (uint)height);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetScissor(uint x, uint y, uint width, uint height)
|
||||
{
|
||||
_stateScissorRect.Set((x, y, width, height), ref _dirtyStateFlags, DirtyStateFlags.ScissorRect);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ClearScissor()
|
||||
{
|
||||
SetScissor(0u, 0u, (uint)_currentPassSize.X, (uint)_currentPassSize.Y);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetTexture(ClydeHandle handle)
|
||||
{
|
||||
_stateTexture.Set(handle, ref _dirtyStateFlags, DirtyStateFlags.Texture);
|
||||
}
|
||||
|
||||
private void ValidateBatchState()
|
||||
{
|
||||
if (_dirtyStateFlags == 0)
|
||||
return;
|
||||
|
||||
DirtyStateFlags sureFlags = 0;
|
||||
sureFlags |= CheckSureState(ref _stateTexture, DirtyStateFlags.Texture);
|
||||
sureFlags |= CheckSureState(ref _stateScissorRect, DirtyStateFlags.ScissorRect);
|
||||
|
||||
if (sureFlags == 0)
|
||||
return;
|
||||
|
||||
// Something changed. Save the active batch so we can change other rendering parameters.
|
||||
FlushBatch();
|
||||
|
||||
_dirtyStateFlags = 0;
|
||||
|
||||
if ((sureFlags & DirtyStateFlags.Texture) != 0)
|
||||
{
|
||||
var loaded = _clyde._loadedTextures[_stateTexture.Active];
|
||||
|
||||
FlushBatch();
|
||||
|
||||
var group = AllocTempBindGroup(new RhiBindGroupDescriptor(
|
||||
_group2Layout,
|
||||
new[]
|
||||
{
|
||||
new RhiBindGroupEntry(0, loaded.DefaultRhiView),
|
||||
new RhiBindGroupEntry(1, loaded.RhiSampler),
|
||||
}
|
||||
));
|
||||
|
||||
_passEncoder!.SetBindGroup(BindGroup2, group);
|
||||
}
|
||||
|
||||
if ((sureFlags & DirtyStateFlags.ScissorRect) != 0)
|
||||
{
|
||||
// wgpu doesn't support 0-size scissor rects right now.
|
||||
// We ignore draws if the scissor state says 0 width.
|
||||
// TODO: Fix this wgpu-side
|
||||
if (_stateScissorRect.Active.w != 0 && _stateScissorRect.Active.h != 0)
|
||||
{
|
||||
_passEncoder!.SetScissorRect(
|
||||
_stateScissorRect.Active.x,
|
||||
_stateScissorRect.Active.y,
|
||||
_stateScissorRect.Active.w,
|
||||
_stateScissorRect.Active.h
|
||||
);
|
||||
_scissorSkip = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_scissorSkip = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DirtyStateFlags CheckSureState<T>(ref State<T> state, DirtyStateFlags thisFlag) where T : IEquatable<T>
|
||||
{
|
||||
// Not really sure this is worth checking again overhead-wise.
|
||||
if ((_dirtyStateFlags & thisFlag) == 0)
|
||||
return 0;
|
||||
|
||||
if (state.Active.Equals(state.New))
|
||||
return 0;
|
||||
|
||||
state.Active = state.New;
|
||||
return thisFlag;
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushBatch()
|
||||
{
|
||||
if (_curBatchSize == 0)
|
||||
return;
|
||||
|
||||
if (!_scissorSkip)
|
||||
{
|
||||
_passEncoder!.Draw(
|
||||
(uint)_curBatchSize,
|
||||
1,
|
||||
(uint)(_vertexStartIndex - _curBatchSize),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
_curBatchSize = 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ClearPassState();
|
||||
|
||||
_vertexStartIndex = 0;
|
||||
_currentVertexBuffer = null;
|
||||
|
||||
foreach (var bindGroup in _tempBindGroups)
|
||||
{
|
||||
bindGroup.Dispose();
|
||||
}
|
||||
|
||||
_tempBindGroups.Clear();
|
||||
}
|
||||
|
||||
private void ClearPassState()
|
||||
{
|
||||
_curBatchSize = 0;
|
||||
_currentVertexBuffer = null;
|
||||
_vertexStartIndex = 0;
|
||||
|
||||
_dirtyStateFlags = 0;
|
||||
_stateTexture = default;
|
||||
_stateScissorRect = default;
|
||||
|
||||
_modelTransform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
public void SetModelTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_modelTransform = matrix;
|
||||
}
|
||||
|
||||
private RhiBindGroup AllocTempBindGroup(in RhiBindGroupDescriptor descriptor)
|
||||
{
|
||||
var bindGroup = _rhi.CreateBindGroup(descriptor);
|
||||
_tempBindGroups.Add(bindGroup);
|
||||
return bindGroup;
|
||||
}
|
||||
|
||||
private struct Vertex2D
|
||||
{
|
||||
public SVector2 Position;
|
||||
public SVector2 TexCoord;
|
||||
public SVector4 Color;
|
||||
|
||||
public Vertex2D(SVector2 position, SVector2 texCoord, SVector4 color)
|
||||
{
|
||||
Position = position;
|
||||
TexCoord = texCoord;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
private struct UniformView
|
||||
{
|
||||
public ShaderMat3x2F ProjViewMatrix;
|
||||
public SVector2 ScreenPixelSize;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum DirtyStateFlags : ushort
|
||||
{
|
||||
// @formatter:off
|
||||
None = 0,
|
||||
Texture = 1 << 0,
|
||||
ScissorRect = 1 << 1
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private struct State<T> where T : IEquatable<T>
|
||||
{
|
||||
public T Active;
|
||||
public T New;
|
||||
|
||||
public void Set(T newValue, ref DirtyStateFlags flags, DirtyStateFlags thisFlag)
|
||||
{
|
||||
if ((flags & thisFlag) != 0)
|
||||
{
|
||||
// Property is already dirty, just write it with no further checks.
|
||||
New = newValue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Quick case: setting to same value.
|
||||
if (Active.Equals(newValue))
|
||||
return;
|
||||
|
||||
// Value modified, mark as dirty.
|
||||
New = newValue;
|
||||
flags |= thisFlag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Data;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -15,10 +15,6 @@ using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
|
||||
using PIF = OpenToolkit.Graphics.OpenGL4.PixelInternalFormat;
|
||||
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
|
||||
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -33,7 +29,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private readonly ConcurrentQueue<ClydeHandle> _textureDisposeQueue = new();
|
||||
|
||||
public OwnedTexture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
public OwnedTexture LoadTextureFromPNGStream(
|
||||
Stream stream,
|
||||
string? name = null,
|
||||
TextureLoadParameters? loadParams = null)
|
||||
{
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
@@ -44,82 +42,240 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return LoadTextureFromImage(image, name, loadParams);
|
||||
}
|
||||
|
||||
public OwnedTexture LoadTextureFromImage<T>(Image<T> image, string? name = null,
|
||||
TextureLoadParameters? loadParams = null) where T : unmanaged, IPixel<T>
|
||||
public OwnedTexture LoadTextureFromImage<T>(
|
||||
Image<T> image,
|
||||
string? name = null,
|
||||
TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
var pixelType = typeof(T);
|
||||
|
||||
if (!_hasGLTextureSwizzle)
|
||||
{
|
||||
// If texture swizzle isn't available we have to pre-process the images to apply it ourselves
|
||||
// and then upload as RGBA8.
|
||||
// Yes this is inefficient but the alternative is modifying the shaders,
|
||||
// which I CBA to do.
|
||||
// Even 8 year old iGPUs support texture swizzle.
|
||||
if (pixelType == typeof(A8))
|
||||
{
|
||||
// Disable sRGB so stuff doesn't get interpreter wrong.
|
||||
actualParams.Srgb = false;
|
||||
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
|
||||
return LoadTextureFromImage(img, name, loadParams);
|
||||
}
|
||||
|
||||
if (pixelType == typeof(L8) && !actualParams.Srgb)
|
||||
{
|
||||
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
|
||||
return LoadTextureFromImage(img, name, loadParams);
|
||||
}
|
||||
}
|
||||
var (instance, loaded) = CreateBlankTextureCore<T>((image.Width, image.Height), name, loadParams);
|
||||
|
||||
// Flip image because OpenGL reads images upside down.
|
||||
using var copy = FlipClone(image);
|
||||
|
||||
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var span = copy.GetPixelSpan();
|
||||
fixed (T* ptr = span)
|
||||
{
|
||||
// Still bound.
|
||||
DoTexUpload(copy.Width, copy.Height, actualParams.Srgb, ptr);
|
||||
}
|
||||
Rhi.Queue.WriteTexture(
|
||||
new RhiImageCopyTexture
|
||||
{
|
||||
Texture = loaded.RhiTexture,
|
||||
Aspect = RhiTextureAspect.All,
|
||||
Origin = new RhiOrigin3D(),
|
||||
MipLevel = 0
|
||||
},
|
||||
MemoryMarshal.Cast<T, byte>(span),
|
||||
new RhiImageDataLayout(0, (uint) (sizeof(T) * image.Width), (uint) image.Height),
|
||||
new RhiExtent3D(image.Width, image.Height)
|
||||
);
|
||||
}
|
||||
|
||||
return texture;
|
||||
return instance;
|
||||
}
|
||||
|
||||
public unsafe OwnedTexture CreateBlankTexture<T>(
|
||||
public OwnedTexture CreateBlankTexture<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
if (!_hasGLTextureSwizzle)
|
||||
{
|
||||
// Actually create RGBA32 texture if missing texture swizzle.
|
||||
// This is fine (TexturePixelType that's stored) because all other APIs do the same.
|
||||
if (typeof(T) == typeof(A8) || typeof(T) == typeof(L8))
|
||||
{
|
||||
return CreateBlankTexture<Rgba32>(size, name, loadParams);
|
||||
}
|
||||
}
|
||||
|
||||
var texture = CreateBaseTextureInternal<T>(
|
||||
size.X, size.Y,
|
||||
actualParams,
|
||||
name);
|
||||
|
||||
// Texture still bound, run glTexImage2D with null data param to specify bounds.
|
||||
DoTexUpload<T>(size.X, size.Y, actualParams.Srgb, null);
|
||||
|
||||
return texture;
|
||||
var (instance, _) = CreateBlankTextureCore<T>(size, name, loadParams);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private (ClydeTexture, LoadedTexture) CreateBlankTextureCore<T>(
|
||||
Vector2i size,
|
||||
string? name = null,
|
||||
in TextureLoadParameters? loadParams = null)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
DebugTools.Assert(_gameThread == Thread.CurrentThread);
|
||||
|
||||
var actualParams = loadParams ?? TextureLoadParameters.Default;
|
||||
var srgb = actualParams.Srgb;
|
||||
|
||||
var format = GetPixelTextureFormat<T>(srgb);
|
||||
|
||||
return CreateBlankTextureCore(size, name, format, actualParams.SampleParameters, srgb);
|
||||
}
|
||||
|
||||
private (ClydeTexture, LoadedTexture) CreateBlankTextureCore(
|
||||
Vector2i size,
|
||||
string? name,
|
||||
RhiTextureFormat format,
|
||||
TextureSampleParameters sampleParams,
|
||||
bool srgb)
|
||||
{
|
||||
var rhiTexture = Rhi.CreateTexture(new RhiTextureDescriptor(
|
||||
new RhiExtent3D(size.X, size.Y),
|
||||
format,
|
||||
RhiTextureUsage.TextureBinding | RhiTextureUsage.CopySrc | RhiTextureUsage.CopyDst,
|
||||
Label: name
|
||||
));
|
||||
|
||||
var rhiTextureView = rhiTexture.CreateView(new RhiTextureViewDescriptor
|
||||
{
|
||||
Aspect = RhiTextureAspect.All,
|
||||
Dimension = RhiTextureViewDimension.Dim2D,
|
||||
Format = format,
|
||||
Label = name,
|
||||
MipLevelCount = 1,
|
||||
ArrayLayerCount = 1,
|
||||
BaseArrayLayer = 0,
|
||||
BaseMipLevel = 0
|
||||
});
|
||||
|
||||
var addressMode = sampleParams.WrapMode switch
|
||||
{
|
||||
TextureWrapMode.None => RhiAddressMode.ClampToEdge,
|
||||
TextureWrapMode.Repeat => RhiAddressMode.Repeat,
|
||||
TextureWrapMode.MirroredRepeat => RhiAddressMode.MirrorRepeat,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var filter = sampleParams.Filter ? RhiFilterMode.Linear : RhiFilterMode.Nearest;
|
||||
|
||||
// TODO: Cache samplers somewhere, we can't actually make infinite of these and they're simple enough.
|
||||
var rhiSampler = Rhi.CreateSampler(new RhiSamplerDescriptor(
|
||||
AddressModeU: addressMode,
|
||||
AddressModeV: addressMode,
|
||||
MagFilter: filter,
|
||||
MinFilter: filter,
|
||||
Label: name
|
||||
));
|
||||
|
||||
var (width, height) = size;
|
||||
|
||||
var id = AllocRid();
|
||||
var instance = new ClydeTexture(id, size, srgb, this);
|
||||
var loaded = new LoadedTexture
|
||||
{
|
||||
RhiTexture = rhiTexture,
|
||||
DefaultRhiView = rhiTextureView,
|
||||
RhiSampler = rhiSampler,
|
||||
Width = width,
|
||||
Height = height,
|
||||
Format = format,
|
||||
Name = name,
|
||||
};
|
||||
|
||||
_loadedTextures.Add(id, loaded);
|
||||
|
||||
return (instance, loaded);
|
||||
}
|
||||
|
||||
private static RhiTextureFormat GetPixelTextureFormat<T>(bool srgb) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
Rgba32 => srgb ? RhiTextureFormat.RGBA8UnormSrgb : RhiTextureFormat.RGBA8Unorm,
|
||||
_ => throw new ArgumentException("Unsupported pixel format")
|
||||
};
|
||||
}
|
||||
|
||||
private void TextureSetSubImage<T>(
|
||||
ClydeTexture clydeTexture,
|
||||
Vector2i topLeft,
|
||||
Image<T> sourceImage,
|
||||
in UIBox2i sourceRegion)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (sourceRegion.Left < 0 ||
|
||||
sourceRegion.Top < 0 ||
|
||||
sourceRegion.Right > sourceRegion.Width ||
|
||||
sourceRegion.Bottom > sourceRegion.Height)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(sourceRegion), "Source rectangle out of bounds.");
|
||||
}
|
||||
|
||||
var size = sourceRegion.Width * sourceRegion.Height;
|
||||
|
||||
T[]? pooled = null;
|
||||
// C# won't let me use an if due to the stackalloc.
|
||||
var copyBuffer = size < 16 * 16
|
||||
? stackalloc T[size]
|
||||
: (pooled = ArrayPool<T>.Shared.Rent(size)).AsSpan(0, size);
|
||||
|
||||
var srcSpan = sourceImage.GetPixelSpan();
|
||||
var w = sourceImage.Width;
|
||||
FlipCopySubRegion(sourceRegion, w, srcSpan, copyBuffer);
|
||||
|
||||
SetSubImageImpl<T>(clydeTexture, topLeft, (sourceRegion.Width, sourceRegion.Height), copyBuffer);
|
||||
|
||||
if (pooled != null)
|
||||
ArrayPool<T>.Shared.Return(pooled);
|
||||
}
|
||||
|
||||
private void TextureSetSubImage<T>(
|
||||
ClydeTexture clydeTexture,
|
||||
Vector2i topLeft,
|
||||
Vector2i size,
|
||||
ReadOnlySpan<T> buffer)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
T[]? pooled = null;
|
||||
// C# won't let me use an if due to the stackalloc.
|
||||
var copyBuffer = buffer.Length < 16 * 16
|
||||
? stackalloc T[buffer.Length]
|
||||
: (pooled = ArrayPool<T>.Shared.Rent(buffer.Length)).AsSpan(0, buffer.Length);
|
||||
|
||||
FlipCopy(buffer, copyBuffer, size.X, size.Y);
|
||||
|
||||
SetSubImageImpl<T>(clydeTexture, topLeft, size, copyBuffer);
|
||||
|
||||
if (pooled != null)
|
||||
ArrayPool<T>.Shared.Return(pooled);
|
||||
}
|
||||
|
||||
|
||||
private unsafe void SetSubImageImpl<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Vector2i size,
|
||||
ReadOnlySpan<T> buf)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
var loaded = _loadedTextures[texture.TextureId];
|
||||
var format = GetPixelTextureFormat<T>(loaded.IsSrgb);
|
||||
|
||||
if (format != loaded.Format)
|
||||
{
|
||||
// TODO:
|
||||
//if (loaded.TexturePixelType == TexturePixelType.RenderTarget)
|
||||
// throw new InvalidOperationException("Cannot modify texture for render target directly.");
|
||||
|
||||
throw new InvalidOperationException("Mismatching pixel type for texture.");
|
||||
}
|
||||
|
||||
if (loaded.Width < dstTl.X + size.X || loaded.Height < dstTl.Y + size.Y)
|
||||
throw new ArgumentOutOfRangeException(nameof(size), "Destination rectangle out of bounds.");
|
||||
|
||||
var dstY = loaded.Height - dstTl.Y - size.Y;
|
||||
|
||||
Rhi.Queue.WriteTexture(
|
||||
new RhiImageCopyTexture(
|
||||
loaded.RhiTexture,
|
||||
0,
|
||||
new RhiOrigin3D(dstTl.X, dstY)
|
||||
),
|
||||
buf,
|
||||
new RhiImageDataLayout(0, (uint) (size.X * sizeof(T)), (uint) size.Y),
|
||||
new RhiExtent3D(size.X, size.Y)
|
||||
);
|
||||
|
||||
// GL.TexSubImage2D(
|
||||
// TextureTarget.Texture2D,
|
||||
// 0,
|
||||
// dstTl.X, dstY,
|
||||
// size.X, size.Y,
|
||||
// pf, pt,
|
||||
// (IntPtr) aPtr);
|
||||
// CheckGlError();
|
||||
}
|
||||
|
||||
/*
|
||||
private unsafe void DoTexUpload<T>(int width, int height, bool srgb, T* ptr) where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (sizeof(T) < 4)
|
||||
@@ -256,7 +412,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
private (PIF pif, PF pf, PT pt) PixelEnums<T>(bool srgb)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
@@ -279,7 +437,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
private ClydeTexture GenTexture(
|
||||
GLHandle glHandle,
|
||||
Vector2i size,
|
||||
@@ -314,9 +474,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
return instance;
|
||||
}
|
||||
*/
|
||||
|
||||
private void DeleteTexture(ClydeHandle textureHandle)
|
||||
{
|
||||
/*
|
||||
if (!_loadedTextures.TryGetValue(textureHandle, out var loadedTexture))
|
||||
{
|
||||
// Already deleted I guess.
|
||||
@@ -327,154 +489,22 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CheckGlError();
|
||||
_loadedTextures.Remove(textureHandle);
|
||||
//GC.RemoveMemoryPressure(loadedTexture.MemoryPressure);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImage<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Image<T> img,
|
||||
in UIBox2i srcBox)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (srcBox.Left < 0 ||
|
||||
srcBox.Top < 0 ||
|
||||
srcBox.Right > srcBox.Width ||
|
||||
srcBox.Bottom > srcBox.Height)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(srcBox), "Source rectangle out of bounds.");
|
||||
}
|
||||
|
||||
var size = srcBox.Width * srcBox.Height;
|
||||
|
||||
T[]? pooled = null;
|
||||
// C# won't let me use an if due to the stackalloc.
|
||||
var copyBuffer = size < 16 * 16
|
||||
? stackalloc T[size]
|
||||
: (pooled = ArrayPool<T>.Shared.Rent(size)).AsSpan(0, size);
|
||||
|
||||
var srcSpan = img.GetPixelSpan();
|
||||
var w = img.Width;
|
||||
FlipCopySubRegion(srcBox, w, srcSpan, copyBuffer);
|
||||
|
||||
SetSubImageImpl<T>(texture, dstTl, (srcBox.Width, srcBox.Height), copyBuffer);
|
||||
|
||||
if (pooled != null)
|
||||
ArrayPool<T>.Shared.Return(pooled);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImage<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Vector2i size,
|
||||
ReadOnlySpan<T> buf)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
T[]? pooled = null;
|
||||
// C# won't let me use an if due to the stackalloc.
|
||||
var copyBuffer = buf.Length < 16 * 16
|
||||
? stackalloc T[buf.Length]
|
||||
: (pooled = ArrayPool<T>.Shared.Rent(buf.Length)).AsSpan(0, buf.Length);
|
||||
|
||||
FlipCopy(buf, copyBuffer, size.X, size.Y);
|
||||
|
||||
SetSubImageImpl<T>(texture, dstTl, size, copyBuffer);
|
||||
|
||||
if (pooled != null)
|
||||
ArrayPool<T>.Shared.Return(pooled);
|
||||
}
|
||||
|
||||
private unsafe void SetSubImageImpl<T>(
|
||||
ClydeTexture texture,
|
||||
Vector2i dstTl,
|
||||
Vector2i size,
|
||||
ReadOnlySpan<T> buf)
|
||||
where T : unmanaged, IPixel<T>
|
||||
{
|
||||
if (!_hasGLTextureSwizzle && (typeof(T) == typeof(A8) || typeof(T) == typeof(L8)))
|
||||
{
|
||||
var swizzleBuf = ArrayPool<Rgba32>.Shared.Rent(buf.Length);
|
||||
|
||||
var destSpan = swizzleBuf.AsSpan(0, buf.Length);
|
||||
if (typeof(T) == typeof(A8))
|
||||
ApplyA8Swizzle(MemoryMarshal.Cast<T, A8>(buf), destSpan);
|
||||
else if (typeof(T) == typeof(L8))
|
||||
ApplyL8Swizzle(MemoryMarshal.Cast<T, L8>(buf), destSpan);
|
||||
|
||||
SetSubImageImpl<Rgba32>(texture, dstTl, size, destSpan);
|
||||
ArrayPool<Rgba32>.Shared.Return(swizzleBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
var loaded = _loadedTextures[texture.TextureId];
|
||||
var pixType = GetTexturePixelType<T>();
|
||||
|
||||
if (pixType != loaded.TexturePixelType)
|
||||
{
|
||||
if (loaded.TexturePixelType == TexturePixelType.RenderTarget)
|
||||
throw new InvalidOperationException("Cannot modify texture for render target directly.");
|
||||
|
||||
throw new InvalidOperationException("Mismatching pixel type for texture.");
|
||||
}
|
||||
|
||||
if (loaded.Width < dstTl.X + size.X || loaded.Height < dstTl.Y + size.Y)
|
||||
throw new ArgumentOutOfRangeException(nameof(size), "Destination rectangle out of bounds.");
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
// sRGB doesn't matter since that only changes the internalFormat, which we don't need here.
|
||||
var (_, pf, pt) = PixelEnums<T>(srgb: false);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
CheckGlError();
|
||||
|
||||
fixed (T* aPtr = buf)
|
||||
{
|
||||
var dstY = loaded.Height - dstTl.Y - size.Y;
|
||||
GL.TexSubImage2D(
|
||||
TextureTarget.Texture2D,
|
||||
0,
|
||||
dstTl.X, dstY,
|
||||
size.X, size.Y,
|
||||
pf, pt,
|
||||
(IntPtr) aPtr);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (sizeof(T) != 4)
|
||||
{
|
||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||
CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private static TexturePixelType GetTexturePixelType<T>() where T : unmanaged, IPixel<T>
|
||||
{
|
||||
return default(T) switch
|
||||
{
|
||||
Rgba32 => TexturePixelType.Rgba32,
|
||||
L8 => TexturePixelType.L8,
|
||||
A8 => TexturePixelType.A8,
|
||||
_ => throw new NotSupportedException("Unsupported pixel type."),
|
||||
};
|
||||
*/
|
||||
}
|
||||
|
||||
private void LoadStockTextures()
|
||||
{
|
||||
var white = new Image<Rgba32>(1, 1);
|
||||
white[0, 0] = new Rgba32(255, 255, 255, 255);
|
||||
_stockTextureWhite = (ClydeTexture) Texture.LoadFromImage(white, name: "StockTextureWhite");
|
||||
_stockTextureWhite = (ClydeTexture)LoadTextureFromImage(white, name: "Stock Texture: white");
|
||||
|
||||
var black = new Image<Rgba32>(1, 1);
|
||||
black[0, 0] = new Rgba32(0, 0, 0, 255);
|
||||
_stockTextureBlack = (ClydeTexture) Texture.LoadFromImage(black, name: "StockTextureBlack");
|
||||
_stockTextureBlack = (ClydeTexture)LoadTextureFromImage(black, name: "Stock Texture: black");
|
||||
|
||||
var blank = new Image<Rgba32>(1, 1);
|
||||
blank[0, 0] = new Rgba32(0, 0, 0, 0);
|
||||
_stockTextureTransparent = (ClydeTexture) Texture.LoadFromImage(blank, name: "StockTextureTransparent");
|
||||
_stockTextureTransparent = (ClydeTexture)LoadTextureFromImage(blank, name: "Stock Texture: transparent");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -573,32 +603,27 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal sealed class LoadedTexture
|
||||
{
|
||||
public GLHandle OpenGLObject;
|
||||
public RhiTexture RhiTexture = default!;
|
||||
public RhiTextureView DefaultRhiView = default!;
|
||||
public RhiSampler RhiSampler = default!;
|
||||
|
||||
public int Width;
|
||||
public int Height;
|
||||
public bool IsSrgb;
|
||||
public RhiTextureFormat Format;
|
||||
public string? Name;
|
||||
public long MemoryPressure;
|
||||
public TexturePixelType TexturePixelType;
|
||||
|
||||
public Vector2i Size => (Width, Height);
|
||||
public required WeakReference<ClydeTexture> TextureInstance;
|
||||
}
|
||||
|
||||
internal enum TexturePixelType : byte
|
||||
{
|
||||
RenderTarget = 0,
|
||||
Rgba32,
|
||||
A8,
|
||||
L8,
|
||||
public bool IsSrgb => Format is RhiTextureFormat.BGRA8UnormSrgb or RhiTextureFormat.RGBA8UnormSrgb;
|
||||
}
|
||||
|
||||
private void FlushTextureDispose()
|
||||
{
|
||||
while (_textureDisposeQueue.TryDequeue(out var handle))
|
||||
/*while (_textureDisposeQueue.TryDequeue(out var handle))
|
||||
{
|
||||
DeleteTexture(handle);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
internal sealed class ClydeTexture : OwnedTexture
|
||||
@@ -610,17 +635,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
{
|
||||
_clyde.SetSubImage(this, topLeft, sourceImage, sourceRegion);
|
||||
_clyde.TextureSetSubImage(this, topLeft, sourceImage, sourceRegion);
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
|
||||
{
|
||||
_clyde.SetSubImage(this, topLeft, size, buffer);
|
||||
_clyde.TextureSetSubImage(this, topLeft, size, buffer);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_clyde.IsMainThread())
|
||||
/*if (_clyde.IsMainThread())
|
||||
{
|
||||
// Main thread, do direct GL deletion.
|
||||
_clyde.DeleteTexture(TextureId);
|
||||
@@ -629,7 +654,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Finalizer thread
|
||||
_clyde._textureDisposeQueue.Enqueue(TextureId);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
internal ClydeTexture(ClydeHandle id, Vector2i size, bool srgb, Clyde clyde) : base(size)
|
||||
@@ -648,32 +673,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
return $"ClydeTexture: ({TextureId})";
|
||||
}
|
||||
|
||||
public override unsafe Color GetPixel(int x, int y)
|
||||
{
|
||||
if (!_clyde._loadedTextures.TryGetValue(TextureId, out var loaded))
|
||||
{
|
||||
throw new DataException("Texture not found");
|
||||
}
|
||||
|
||||
var curTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
var bufSize = 4 * loaded.Size.X * loaded.Size.Y;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(bufSize);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
GL.GetTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, (IntPtr) p);
|
||||
}
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
|
||||
|
||||
var pixelPos = (loaded.Size.X * (loaded.Size.Y - y - 1) + x) * 4;
|
||||
var color = new Color(buffer[pixelPos+0], buffer[pixelPos+1], buffer[pixelPos+2], buffer[pixelPos+3]);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
public Texture GetStockTexture(ClydeStockTexture stockTexture)
|
||||
@@ -697,5 +696,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
yield return (textureInstance, loaded);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyTexture : OwnedTexture
|
||||
{
|
||||
public DummyTexture(Vector2i size) : base(size)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Image<T> sourceImage, in UIBox2i sourceRegion)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetSubImage<T>(Vector2i topLeft, Vector2i size, ReadOnlySpan<T> buffer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
name: $"{name}-MainRenderTarget")
|
||||
};
|
||||
|
||||
RegenLightRts(viewport);
|
||||
// RegenLightRts(viewport);
|
||||
|
||||
_viewports.Add(handle, new WeakReference<Viewport>(viewport));
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
void IClydeViewport.Render()
|
||||
{
|
||||
_clyde.RenderViewport(this);
|
||||
// _clyde.RenderViewport(this);
|
||||
}
|
||||
|
||||
public MapCoordinates LocalToWorld(Vector2 point)
|
||||
@@ -198,7 +198,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpaceBelowWorld, viewportBounds);
|
||||
// _clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpaceBelowWorld, viewportBounds);
|
||||
}
|
||||
|
||||
public void RenderScreenOverlaysAbove(
|
||||
@@ -206,7 +206,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
IViewportControl control,
|
||||
in UIBox2i viewportBounds)
|
||||
{
|
||||
_clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
|
||||
// _clyde.RenderOverlaysDirect(this, control, handle, OverlaySpace.ScreenSpace, viewportBounds);
|
||||
}
|
||||
|
||||
~Viewport()
|
||||
@@ -217,12 +217,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
// TODO: These shouldn't be ?. disposes. Temporary thing for WebGPU.
|
||||
RenderTarget.Dispose();
|
||||
LightRenderTarget.Dispose();
|
||||
WallMaskRenderTarget.Dispose();
|
||||
WallBleedIntermediateRenderTarget1.Dispose();
|
||||
WallBleedIntermediateRenderTarget2.Dispose();
|
||||
LightRenderTarget?.Dispose();
|
||||
WallMaskRenderTarget?.Dispose();
|
||||
WallBleedIntermediateRenderTarget1?.Dispose();
|
||||
WallBleedIntermediateRenderTarget2?.Dispose();
|
||||
|
||||
_clyde.DisposeViewport(DisposeData(referenceSelf: false));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
@@ -16,7 +17,6 @@ using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TerraFX.Interop.Windows;
|
||||
using FrameEventArgs = Robust.Shared.Timing.FrameEventArgs;
|
||||
using GL = OpenToolkit.Graphics.OpenGL4.GL;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -27,21 +27,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<int, MonitorHandle> _monitorHandles = new();
|
||||
|
||||
private int _primaryMonitorId;
|
||||
private WindowReg? _mainWindow;
|
||||
internal WindowReg? _mainWindow;
|
||||
|
||||
private IWindowingImpl? _windowing;
|
||||
#pragma warning disable 414
|
||||
// Keeping this for if/when we ever get a new renderer.
|
||||
private Renderer _chosenRenderer;
|
||||
#pragma warning restore 414
|
||||
internal IWindowingImpl? _windowing;
|
||||
|
||||
private ResPath? _windowIconPath;
|
||||
private Thread? _windowingThread;
|
||||
private bool _vSync;
|
||||
private WindowMode _windowMode;
|
||||
private WindowReg? _currentHoveredWindow;
|
||||
private bool _threadWindowBlit;
|
||||
private bool EffectiveThreadWindowBlit => _threadWindowBlit && !_isGLES;
|
||||
|
||||
public event Action<TextEnteredEventArgs>? TextEntered;
|
||||
public event Action<TextEditingEventArgs>? TextEditing;
|
||||
@@ -91,11 +85,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private bool InitWindowing()
|
||||
{
|
||||
if (OperatingSystem.IsWindows() && _cfg.GetCVar(CVars.DisplayAngleEs3On10_0))
|
||||
{
|
||||
Environment.SetEnvironmentVariable("ANGLE_FEATURE_OVERRIDES_ENABLED", "allowES3OnFL10_0");
|
||||
}
|
||||
|
||||
var iconPath = _cfg.GetCVar(CVars.DisplayWindowIconSet);
|
||||
if (!string.IsNullOrWhiteSpace(iconPath))
|
||||
_windowIconPath = new ResPath(iconPath);
|
||||
@@ -127,10 +116,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return _windowing.Init();
|
||||
}
|
||||
|
||||
private bool TryInitMainWindow(GLContextSpec? glSpec, [NotNullWhen(false)] out string? error)
|
||||
private bool TryInitMainWindow([NotNullWhen(false)] out string? error)
|
||||
{
|
||||
DebugTools.AssertNotNull(_glContext);
|
||||
|
||||
var width = _cfg.GetCVar(CVars.DisplayWidth);
|
||||
var height = _cfg.GetCVar(CVars.DisplayHeight);
|
||||
var prevWidth = width;
|
||||
@@ -155,7 +142,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Fullscreen = fullscreen
|
||||
};
|
||||
|
||||
var (reg, err) = SharedWindowCreate(glSpec, parameters, null, isMain: true);
|
||||
var (reg, err) = SharedWindowCreate(parameters, isMain: true);
|
||||
|
||||
if (reg == null)
|
||||
{
|
||||
@@ -178,43 +165,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private unsafe bool InitMainWindowAndRenderer()
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_glContext);
|
||||
|
||||
_chosenRenderer = Renderer.OpenGL;
|
||||
|
||||
var succeeded = false;
|
||||
string? lastError = null;
|
||||
|
||||
if (_glContext!.RequireWindowGL)
|
||||
{
|
||||
var specs = _glContext!.SpecsToTry;
|
||||
|
||||
foreach (var glSpec in specs)
|
||||
{
|
||||
if (!TryInitMainWindow(glSpec, out lastError))
|
||||
{
|
||||
_sawmillWin.Debug($"OpenGL {glSpec.OpenGLVersion} unsupported: {lastError}");
|
||||
continue;
|
||||
}
|
||||
|
||||
succeeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!TryInitMainWindow(out lastError))
|
||||
_sawmillWin.Debug($"Failed to create window: {lastError}");
|
||||
else
|
||||
{
|
||||
if (!TryInitMainWindow(null, out lastError))
|
||||
_sawmillWin.Debug($"Failed to create window: {lastError}");
|
||||
else
|
||||
succeeded = true;
|
||||
}
|
||||
succeeded = true;
|
||||
|
||||
// We should have a main window now.
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
// _openGLVersion must be set by _glContext.
|
||||
DebugTools.Assert(_openGLVersion != RendererOpenGLVersion.Auto);
|
||||
|
||||
if (!succeeded)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
@@ -242,15 +204,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_earlyGLInit)
|
||||
InitOpenGL();
|
||||
InitRhi();
|
||||
|
||||
_sawmillOgl.Debug("Setting viewport and rendering splash...");
|
||||
|
||||
GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y);
|
||||
CheckGlError();
|
||||
_debugInfo = new ClydeDebugInfo(_windowing!.GetDescription());
|
||||
|
||||
// Quickly do a render with _drawingSplash = true so the screen isn't blank.
|
||||
|
||||
RenderInit();
|
||||
Render();
|
||||
|
||||
return true;
|
||||
@@ -308,17 +268,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IClydeWindow CreateWindow(WindowCreateParameters parameters)
|
||||
{
|
||||
DebugTools.AssertNotNull(_windowing);
|
||||
DebugTools.AssertNotNull(_glContext);
|
||||
DebugTools.AssertNotNull(_mainWindow);
|
||||
|
||||
var glSpec = _glContext!.GetNewWindowSpec();
|
||||
|
||||
_glContext.BeforeSharedWindowCreateUnbind();
|
||||
|
||||
var (reg, error) = SharedWindowCreate(
|
||||
glSpec,
|
||||
parameters,
|
||||
glSpec == null ? null : _mainWindow,
|
||||
isMain: false);
|
||||
|
||||
// Rebinding is handed by WindowCreated in the GL context.
|
||||
@@ -329,17 +282,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return reg!.Handle;
|
||||
}
|
||||
|
||||
private (WindowReg?, string? error) SharedWindowCreate(
|
||||
GLContextSpec? glSpec,
|
||||
WindowCreateParameters parameters,
|
||||
WindowReg? share,
|
||||
bool isMain)
|
||||
private (WindowReg?, string? error) SharedWindowCreate(WindowCreateParameters parameters, bool isMain)
|
||||
{
|
||||
WindowReg? owner = null;
|
||||
if (parameters.Owner != null)
|
||||
owner = ((WindowHandle)parameters.Owner).Reg;
|
||||
|
||||
var (reg, error) = _windowing!.WindowCreate(glSpec, parameters, share, owner);
|
||||
var (reg, error) = _windowing!.WindowCreate(parameters, owner);
|
||||
|
||||
if (reg != null)
|
||||
{
|
||||
@@ -366,7 +315,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
reg.RenderTarget = renderTarget;
|
||||
|
||||
_glContext!.WindowCreated(glSpec, reg);
|
||||
if (!isMain)
|
||||
Rhi.WindowCreated(reg);
|
||||
}
|
||||
|
||||
// Pass through result whether successful or not, caller handles it.
|
||||
@@ -385,7 +335,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
reg.IsDisposed = true;
|
||||
|
||||
_glContext!.WindowDestroyed(reg);
|
||||
Rhi.WindowDestroy(reg);
|
||||
|
||||
_windowing!.WindowDestroy(reg);
|
||||
|
||||
_windows.Remove(reg);
|
||||
@@ -402,22 +353,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DispatchEvents();
|
||||
}
|
||||
|
||||
private void SwapAllBuffers()
|
||||
{
|
||||
_glContext?.SwapAllBuffers();
|
||||
}
|
||||
|
||||
public bool VsyncEnabled
|
||||
{
|
||||
get => _vSync;
|
||||
set
|
||||
{
|
||||
if (_vSync == value)
|
||||
return;
|
||||
|
||||
_vSync = value;
|
||||
_glContext?.UpdateVSync();
|
||||
}
|
||||
set => _vSync = value;
|
||||
}
|
||||
|
||||
private void WindowModeChanged(int mode)
|
||||
@@ -485,7 +424,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public IFileDialogManagerImplementation? FileDialogImpl => _windowing as IFileDialogManagerImplementation;
|
||||
|
||||
private abstract class WindowReg
|
||||
internal abstract class WindowReg
|
||||
{
|
||||
public bool IsDisposed;
|
||||
|
||||
@@ -504,6 +443,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool IsVisible;
|
||||
public IClydeWindow? Owner;
|
||||
|
||||
public RhiWebGpu.WindowData? RhiWebGpuData;
|
||||
public RhiTextureView? CurSwapchainView;
|
||||
|
||||
public bool DisposeOnClose;
|
||||
|
||||
public bool IsMainWindow;
|
||||
@@ -514,7 +456,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public Action<WindowResizedEventArgs>? Resized;
|
||||
}
|
||||
|
||||
private sealed class WindowHandle : IClydeWindowInternal
|
||||
internal sealed class WindowHandle : IClydeWindowInternal
|
||||
{
|
||||
// So funny story
|
||||
// When this class was a record, the C# compiler on .NET 5 stack overflowed
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using OpenToolkit;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
@@ -24,9 +20,6 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
/// <summary>
|
||||
@@ -34,6 +27,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// </summary>
|
||||
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
@@ -53,56 +47,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IReloadManager _reloads = default!;
|
||||
|
||||
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
|
||||
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
|
||||
|
||||
private GLBuffer BatchVBO = default!;
|
||||
private GLBuffer BatchEBO = default!;
|
||||
private GLHandle BatchVAO;
|
||||
|
||||
// VBO to draw a single quad.
|
||||
private GLBuffer QuadVBO = default!;
|
||||
private GLHandle QuadVAO;
|
||||
|
||||
// VBO to blit to the window
|
||||
// VAO is per-window and not stored (not necessary!)
|
||||
private GLBuffer WindowVBO = default!;
|
||||
|
||||
private bool _drawingSplash = true;
|
||||
|
||||
private GLShaderProgram? _currentProgram;
|
||||
|
||||
private float _lightResolutionScale = 0.5f;
|
||||
private int _maxLights = 2048;
|
||||
private int _maxOccluders = 2048;
|
||||
private int _maxShadowcastingLights = 128;
|
||||
private bool _enableSoftShadows = true;
|
||||
|
||||
private bool _checkGLErrors;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ISawmill _clydeSawmill = default!;
|
||||
private ISawmill _sawmillOgl = default!;
|
||||
private ISawmill _sawmillWin = default!;
|
||||
|
||||
private IBindingsContext _glBindingsContext = default!;
|
||||
private bool _earlyGLInit;
|
||||
private bool _threadWindowApi;
|
||||
|
||||
public Clyde()
|
||||
{
|
||||
_currentBoundRenderTarget = default!;
|
||||
_currentRenderTarget = default!;
|
||||
SixLabors.ImageSharp.Configuration.Default.PreferContiguousImageBuffers = true;
|
||||
}
|
||||
|
||||
public bool InitializePreWindowing()
|
||||
{
|
||||
_clydeSawmill = _logManager.GetSawmill("clyde");
|
||||
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
|
||||
_sawmillWin = _logManager.GetSawmill("clyde.win");
|
||||
|
||||
_reloads.Register("/Shaders", "*.swsl");
|
||||
_reloads.Register("/Textures/Shaders", "*.swsl");
|
||||
_reloads.Register("/Textures", "*.jpg");
|
||||
@@ -113,26 +76,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_reloads.OnChanged += OnChange;
|
||||
_proto.PrototypesReloaded += OnProtoReload;
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
|
||||
/*
|
||||
_cfg.OnValueChanged(CVars.LightResolutionScale, LightResolutionScaleChanged, true);
|
||||
_cfg.OnValueChanged(CVars.MaxShadowcastingLights, MaxShadowcastingLightsChanged, true);
|
||||
_cfg.OnValueChanged(CVars.LightSoftShadows, SoftShadowsChanged, true);
|
||||
_cfg.OnValueChanged(CVars.MaxLightCount, MaxLightsChanged, true);
|
||||
_cfg.OnValueChanged(CVars.MaxOccluderCount, MaxOccludersChanged, true);
|
||||
_cfg.OnValueChanged(CVars.RenderTileEdges, RenderTileEdgesChanges, true);
|
||||
*/
|
||||
// I can't be bothered to tear down and set these threads up in a cvar change handler.
|
||||
|
||||
// Windows and Linux can be trusted to not explode with threaded windowing,
|
||||
// macOS cannot.
|
||||
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux())
|
||||
_cfg.OverrideDefault(CVars.DisplayThreadWindowApi, true);
|
||||
#if MACOS
|
||||
// Trust macOS to not need threaded window blitting.
|
||||
// (threaded window blitting is a workaround to avoid having to frequently MakeCurrent() on Windows, as it is broken).
|
||||
_cfg.OverrideDefault(CVars.DisplayThreadWindowBlit, false);
|
||||
#endif
|
||||
_threadWindowBlit = _cfg.GetCVar(CVars.DisplayThreadWindowBlit);
|
||||
|
||||
_threadWindowApi = _cfg.GetCVar(CVars.DisplayThreadWindowApi);
|
||||
|
||||
InitKeys();
|
||||
@@ -178,7 +139,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
InitSystems();
|
||||
|
||||
InitGLContextManager();
|
||||
if (!InitMainWindowAndRenderer())
|
||||
return false;
|
||||
|
||||
@@ -206,7 +166,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_windowing?.FlushDispose();
|
||||
FlushShaderInstanceDispose();
|
||||
FlushRenderTargetDispose();
|
||||
// FlushRenderTargetDispose();
|
||||
FlushTextureDispose();
|
||||
FlushViewportDispose();
|
||||
}
|
||||
@@ -215,17 +175,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_drawingSplash = false;
|
||||
|
||||
InitLighting();
|
||||
// InitLighting();
|
||||
}
|
||||
|
||||
public IClydeDebugInfo DebugInfo { get; private set; } = default!;
|
||||
public IClydeDebugInfo DebugInfo => _debugInfo ?? throw new InvalidOperationException("Not initialized yet");
|
||||
public IClydeDebugStats DebugStats => _debugStats;
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
// This cvar does not modify the actual GL version requested or anything,
|
||||
// it overrides the version we detect to detect GL features.
|
||||
RegisterBlockCVars();
|
||||
// RegisterBlockCVars();
|
||||
}
|
||||
|
||||
public void RegisterGridEcsEvents()
|
||||
@@ -242,362 +202,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_entityManager.EventBus.UnsubscribeEvent<GridRemovalEvent>(EventSource.Local, this);
|
||||
}
|
||||
|
||||
private void GLInitBindings(bool gles)
|
||||
{
|
||||
_glBindingsContext = _glContext!.BindingsContext;
|
||||
GL.LoadBindings(_glBindingsContext);
|
||||
|
||||
if (gles)
|
||||
{
|
||||
// On GLES we use some OES and KHR functions so make sure to initialize them.
|
||||
OpenToolkit.Graphics.ES20.GL.LoadBindings(_glBindingsContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOpenGL()
|
||||
{
|
||||
_isGLES = _openGLVersion is RendererOpenGLVersion.GLES2 or RendererOpenGLVersion.GLES3;
|
||||
_isGLES2 = _openGLVersion is RendererOpenGLVersion.GLES2;
|
||||
_isCore = _openGLVersion is RendererOpenGLVersion.GL33;
|
||||
|
||||
GLInitBindings(_isGLES);
|
||||
|
||||
var vendor = GL.GetString(StringName.Vendor);
|
||||
var renderer = GL.GetString(StringName.Renderer);
|
||||
var version = GL.GetString(StringName.Version);
|
||||
// GLES2 doesn't allow you to query major/minor version. Seriously.
|
||||
var major = _openGLVersion == RendererOpenGLVersion.GLES2 ? 2 : GL.GetInteger(GetPName.MajorVersion);
|
||||
var minor = _openGLVersion == RendererOpenGLVersion.GLES2 ? 0 :GL.GetInteger(GetPName.MinorVersion);
|
||||
|
||||
_sawmillOgl.Debug("OpenGL Vendor: {0}", vendor);
|
||||
_sawmillOgl.Debug("OpenGL Renderer: {0}", renderer);
|
||||
_sawmillOgl.Debug("OpenGL Version: {0}", version);
|
||||
|
||||
var overrideVersion = ParseGLOverrideVersion();
|
||||
|
||||
if (overrideVersion != null)
|
||||
{
|
||||
(major, minor) = overrideVersion.Value;
|
||||
_sawmillOgl.Debug("OVERRIDING detected GL version to: {0}.{1}", major, minor);
|
||||
}
|
||||
|
||||
DetectOpenGLFeatures(major, minor);
|
||||
SetupDebugCallback();
|
||||
|
||||
LoadVendorSettings(vendor, renderer, version);
|
||||
|
||||
var glVersion = new OpenGLVersion((byte) major, (byte) minor, _isGLES, _isCore);
|
||||
|
||||
DebugInfo = new ClydeDebugInfo(
|
||||
glVersion,
|
||||
renderer,
|
||||
vendor,
|
||||
version,
|
||||
overrideVersion != null,
|
||||
_windowing!.GetDescription());
|
||||
|
||||
IsBlending = true;
|
||||
if (_hasGLSrgb && !_isGLES)
|
||||
{
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
CheckGlError();
|
||||
}
|
||||
if (_hasGLPrimitiveRestart)
|
||||
{
|
||||
GL.Enable(EnableCap.PrimitiveRestart);
|
||||
CheckGlError();
|
||||
GL.PrimitiveRestartIndex(PrimitiveRestartIndex);
|
||||
CheckGlError();
|
||||
}
|
||||
if (_hasGLPrimitiveRestartFixedIndex)
|
||||
{
|
||||
GL.Enable(EnableCap.PrimitiveRestartFixedIndex);
|
||||
CheckGlError();
|
||||
}
|
||||
if (!HasGLAnyVertexArrayObjects)
|
||||
{
|
||||
_sawmillOgl.Warning("NO VERTEX ARRAY OBJECTS! Things will probably go terribly, terribly wrong (no fallback path yet)");
|
||||
}
|
||||
|
||||
ResetBlendFunc();
|
||||
|
||||
CheckGlError();
|
||||
|
||||
// Primitive Restart's presence or lack thereof changes the amount of required memory.
|
||||
InitRenderingBatchBuffers();
|
||||
|
||||
_sawmillOgl.Debug("Loading stock textures...");
|
||||
|
||||
LoadStockTextures();
|
||||
|
||||
_sawmillOgl.Debug("Loading stock shaders...");
|
||||
|
||||
LoadStockShaders();
|
||||
|
||||
_sawmillOgl.Debug("Creating various GL objects...");
|
||||
|
||||
CreateMiscGLObjects();
|
||||
|
||||
_sawmillOgl.Debug("Setting up RenderHandle...");
|
||||
|
||||
_renderHandle = new RenderHandle(this, _entityManager);
|
||||
}
|
||||
|
||||
private (int major, int minor)? ParseGLOverrideVersion()
|
||||
{
|
||||
var overrideGLVersion = _cfg.GetCVar(CVars.DisplayOGLOverrideVersion);
|
||||
if (string.IsNullOrEmpty(overrideGLVersion))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var split = overrideGLVersion.Split(".");
|
||||
if (split.Length != 2)
|
||||
{
|
||||
_sawmillOgl.Warning("display.ogl_override_version is in invalid format");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!int.TryParse(split[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var major)
|
||||
|| !int.TryParse(split[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var minor))
|
||||
{
|
||||
_sawmillOgl.Warning("display.ogl_override_version is in invalid format");
|
||||
return null;
|
||||
}
|
||||
|
||||
return (major, minor);
|
||||
}
|
||||
|
||||
private unsafe void CreateMiscGLObjects()
|
||||
{
|
||||
// Quad drawing.
|
||||
{
|
||||
Span<Vertex2D> quadVertices = stackalloc[]
|
||||
{
|
||||
new Vertex2D(1, 0, 1, 1, Color.White),
|
||||
new Vertex2D(0, 0, 0, 1, Color.White),
|
||||
new Vertex2D(1, 1, 1, 0, Color.White),
|
||||
new Vertex2D(0, 1, 0, 0, Color.White)
|
||||
};
|
||||
|
||||
QuadVBO = new GLBuffer<Vertex2D>(this, BufferTarget.ArrayBuffer, BufferUsageHint.StaticDraw,
|
||||
quadVertices,
|
||||
nameof(QuadVBO));
|
||||
|
||||
QuadVAO = MakeQuadVao();
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
// Window VBO
|
||||
{
|
||||
Span<Vertex2D> winVertices = stackalloc[]
|
||||
{
|
||||
new Vertex2D(-1, 1, 0, 1, Color.White),
|
||||
new Vertex2D(-1, -1, 0, 0, Color.White),
|
||||
new Vertex2D(1, 1, 1, 1, Color.White),
|
||||
new Vertex2D(1, -1, 1, 0, Color.White),
|
||||
};
|
||||
|
||||
WindowVBO = new GLBuffer<Vertex2D>(
|
||||
this,
|
||||
BufferTarget.ArrayBuffer,
|
||||
BufferUsageHint.StaticDraw,
|
||||
winVertices,
|
||||
nameof(WindowVBO));
|
||||
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
// Batch rendering
|
||||
{
|
||||
BatchVBO = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
sizeof(Vertex2D) * BatchVertexData.Length, nameof(BatchVBO));
|
||||
|
||||
BatchVAO = new GLHandle(GenVertexArray());
|
||||
BindVertexArray(BatchVAO.Handle);
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, BatchVAO, nameof(BatchVAO));
|
||||
SetupVAOLayout();
|
||||
|
||||
CheckGlError();
|
||||
|
||||
BatchEBO = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
sizeof(ushort) * BatchIndexData.Length, nameof(BatchEBO));
|
||||
}
|
||||
|
||||
ProjViewUBO = new GLUniformBuffer<ProjViewMatrices>(this, BindingIndexProjView, nameof(ProjViewUBO));
|
||||
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
|
||||
|
||||
screenBufferHandle = new GLHandle(GL.GenTexture());
|
||||
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
|
||||
ApplySampleParameters(new TextureSampleParameters() { Filter = false, WrapMode = TextureWrapMode.MirroredRepeat});
|
||||
// TODO: This is atrocious and broken and awful why did I merge this
|
||||
ScreenBufferTexture = GenTexture(screenBufferHandle, (1920, 1080), true, null, TexturePixelType.Rgba32);
|
||||
}
|
||||
|
||||
private GLHandle MakeQuadVao()
|
||||
{
|
||||
var vao = new GLHandle(GenVertexArray());
|
||||
BindVertexArray(vao.Handle);
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, nameof(QuadVAO));
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, QuadVBO.ObjectHandle);
|
||||
SetupVAOLayout();
|
||||
|
||||
return vao;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private unsafe void SetupDebugCallback()
|
||||
{
|
||||
if (!_hasGLKhrDebug)
|
||||
{
|
||||
_sawmillOgl.Debug("KHR_debug not present, OpenGL debug logging not enabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.DebugOutput);
|
||||
GL.Enable(EnableCap.DebugOutputSynchronous);
|
||||
|
||||
_debugMessageCallbackInstance ??= DebugMessageCallback;
|
||||
|
||||
// OpenTK seemed to have trouble marshalling the delegate so do it manually.
|
||||
|
||||
var procName = _isGLKhrDebugESExtension ? "glDebugMessageCallbackKHR" : "glDebugMessageCallback";
|
||||
var glDebugMessageCallback = (delegate* unmanaged[Stdcall] <nint, nint, void>) LoadGLProc(procName);
|
||||
var funcPtr = Marshal.GetFunctionPointerForDelegate(_debugMessageCallbackInstance);
|
||||
glDebugMessageCallback(funcPtr, new IntPtr(0x3005));
|
||||
}
|
||||
|
||||
private void DebugMessageCallback(DebugSource source, DebugType type, int id, DebugSeverity severity,
|
||||
int length, IntPtr message, IntPtr userParam)
|
||||
{
|
||||
var contents = $"{source}: " + Marshal.PtrToStringAnsi(message, length);
|
||||
|
||||
var category = "ogl.debug";
|
||||
switch (type)
|
||||
{
|
||||
case DebugType.DebugTypePerformance:
|
||||
category += ".performance";
|
||||
break;
|
||||
case DebugType.DebugTypeOther:
|
||||
category += ".other";
|
||||
break;
|
||||
case DebugType.DebugTypeError:
|
||||
category += ".error";
|
||||
break;
|
||||
case DebugType.DebugTypeDeprecatedBehavior:
|
||||
category += ".deprecated";
|
||||
break;
|
||||
case DebugType.DebugTypeUndefinedBehavior:
|
||||
category += ".ub";
|
||||
break;
|
||||
case DebugType.DebugTypePortability:
|
||||
category += ".portability";
|
||||
break;
|
||||
case DebugType.DebugTypeMarker:
|
||||
case DebugType.DebugTypePushGroup:
|
||||
case DebugType.DebugTypePopGroup:
|
||||
// These are inserted by our own code so I imagine they're not necessary to log?
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
|
||||
var sawmill = _logManager.GetSawmill(category);
|
||||
|
||||
switch (severity)
|
||||
{
|
||||
case DebugSeverity.DontCare:
|
||||
sawmill.Info(contents);
|
||||
break;
|
||||
case DebugSeverity.DebugSeverityNotification:
|
||||
sawmill.Info(contents);
|
||||
break;
|
||||
case DebugSeverity.DebugSeverityHigh:
|
||||
sawmill.Error(contents);
|
||||
break;
|
||||
case DebugSeverity.DebugSeverityMedium:
|
||||
sawmill.Error(contents);
|
||||
break;
|
||||
case DebugSeverity.DebugSeverityLow:
|
||||
sawmill.Warning(contents);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(severity), severity, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static DebugProc? _debugMessageCallbackInstance;
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void ObjectLabelMaybe(ObjectLabelIdentifier identifier, uint name, string? label)
|
||||
{
|
||||
if (label == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_hasGLKhrDebug || !_glDebuggerPresent)
|
||||
return;
|
||||
|
||||
if (_isGLKhrDebugESExtension)
|
||||
{
|
||||
GL.Khr.ObjectLabel((ObjectIdentifier) identifier, name, label.Length, label);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.ObjectLabel(identifier, name, label.Length, label);
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void ObjectLabelMaybe(ObjectLabelIdentifier identifier, GLHandle name, string? label)
|
||||
{
|
||||
ObjectLabelMaybe(identifier, name.Handle, label);
|
||||
}
|
||||
|
||||
private PopDebugGroup DebugGroup(string group)
|
||||
{
|
||||
PushDebugGroupMaybe(group);
|
||||
return new PopDebugGroup(this);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void PushDebugGroupMaybe(string group)
|
||||
{
|
||||
// ANGLE spams console log messages when using debug groups, so let's only use them if we're debugging GL.
|
||||
if (!_hasGLKhrDebug || !_glDebuggerPresent)
|
||||
return;
|
||||
|
||||
if (_isGLKhrDebugESExtension)
|
||||
{
|
||||
GL.Khr.PushDebugGroup((DebugSource) DebugSourceExternal.DebugSourceApplication, 0, group.Length, group);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.PushDebugGroup(DebugSourceExternal.DebugSourceApplication, 0, group.Length, group);
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void PopDebugGroupMaybe()
|
||||
{
|
||||
if (!_hasGLKhrDebug || !_glDebuggerPresent)
|
||||
return;
|
||||
|
||||
if (_isGLKhrDebugESExtension)
|
||||
{
|
||||
GL.Khr.PopDebugGroup();
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.PopDebugGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_glContext?.Shutdown();
|
||||
ShutdownWindowing();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -26,6 +27,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[UsedImplicitly]
|
||||
internal sealed class ClydeHeadless : IClydeInternal
|
||||
{
|
||||
public RhiBase Rhi => throw new NotImplementedException();
|
||||
|
||||
// Would it make sense to report a fake resolution like 720p here so code doesn't break? idk.
|
||||
public IClydeWindow MainWindow { get; }
|
||||
public Vector2i ScreenSize => (1280, 720);
|
||||
@@ -349,11 +352,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// Just do nothing on mutate.
|
||||
}
|
||||
|
||||
public override Color GetPixel(int x, int y)
|
||||
{
|
||||
return Color.Black;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummyShaderInstance : ShaderInstance
|
||||
@@ -491,11 +489,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed class DummyDebugInfo : IClydeDebugInfo
|
||||
{
|
||||
public OpenGLVersion OpenGLVersion { get; } = new(3, 3, isES: false, isCore: true);
|
||||
public string Renderer => "ClydeHeadless";
|
||||
public string Vendor => "Space Wizards Federation";
|
||||
public string VersionString { get; } = $"3.3.0 WIZARDS {typeof(DummyDebugInfo).Assembly.GetName().Version}";
|
||||
public bool Overriding => false;
|
||||
public string WindowingApi => "The vast abyss";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,582 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using TerraFX.Interop.DirectX;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static Robust.Client.Graphics.Clyde.Egl;
|
||||
using static TerraFX.Interop.DirectX.D3D_DRIVER_TYPE;
|
||||
using static TerraFX.Interop.DirectX.D3D_FEATURE_LEVEL;
|
||||
using static TerraFX.Interop.DirectX.DXGI_FORMAT;
|
||||
using static TerraFX.Interop.DirectX.DXGI_SWAP_EFFECT;
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
using static TerraFX.Interop.DirectX.DirectX;
|
||||
using static TerraFX.Interop.DirectX.D3D11;
|
||||
using static TerraFX.Interop.DirectX.DXGI;
|
||||
using GL = OpenToolkit.Graphics.OpenGL4.GL;
|
||||
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Explicit ANGLE GL context with manual DXGI/D3D device and swap chain management.
|
||||
/// </summary>
|
||||
private sealed unsafe class GLContextAngle : GLContextBase
|
||||
{
|
||||
// Thanks to mpv's implementation of context_angle for inspiration/hints.
|
||||
// https://github.com/mpv-player/mpv/blob/f8e62d3d82dd0a3d06f9a557d756f0ad78118cc7/video/out/opengl/context_angle.c
|
||||
|
||||
// NOTE: This class only handles GLES3/D3D11.
|
||||
// For anything lower we just let ANGLE fall back and do the work 100%.
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private IDXGIFactory1* _factory;
|
||||
private IDXGIAdapter1* _adapter;
|
||||
private ID3D11Device* _device;
|
||||
private D3D_FEATURE_LEVEL _deviceFl;
|
||||
private void* _eglDevice;
|
||||
private void* _eglDisplay;
|
||||
private void* _eglContext;
|
||||
private void* _eglConfig;
|
||||
|
||||
private bool _es3;
|
||||
private uint _swapInterval;
|
||||
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
public override GLContextSpec[] SpecsToTry => Array.Empty<GLContextSpec>();
|
||||
public override bool RequireWindowGL => false;
|
||||
public override bool EarlyContextInit => true;
|
||||
public override bool HasBrokenWindowSrgb => false;
|
||||
|
||||
public GLContextAngle(Clyde clyde) : base(clyde)
|
||||
{
|
||||
_sawmill = clyde._logManager.GetSawmill("clyde.ogl.angle");
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
{
|
||||
// Do not initialize GL context on the window directly, we use ANGLE.
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void UpdateVSync()
|
||||
{
|
||||
_swapInterval = (uint) (Clyde._vSync ? 1 : 0);
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
{
|
||||
var data = new WindowData
|
||||
{
|
||||
Reg = reg
|
||||
};
|
||||
_windowData[reg.Id] = data;
|
||||
|
||||
var hWnd = (HWND) Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
|
||||
|
||||
// todo: exception management.
|
||||
CreateSwapChain1(hWnd, data);
|
||||
|
||||
_factory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER);
|
||||
|
||||
var rt = Clyde.RtToLoaded(reg.RenderTarget);
|
||||
rt.FlipY = true;
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
UpdateVSync();
|
||||
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void DestroyBackbuffer(WindowData data)
|
||||
{
|
||||
if (data.EglBackbuffer != null)
|
||||
{
|
||||
if (data.Reg.IsMainWindow)
|
||||
eglMakeCurrent(_eglDisplay, null, null, null);
|
||||
eglDestroySurface(_eglDisplay, data.EglBackbuffer);
|
||||
|
||||
data.EglBackbuffer = null;
|
||||
}
|
||||
|
||||
data.Backbuffer->Release();
|
||||
data.Backbuffer = null;
|
||||
}
|
||||
|
||||
private void SetupBackbuffer(WindowData data)
|
||||
{
|
||||
DebugTools.Assert(data.Backbuffer == null, "Backbuffer must have been released!");
|
||||
DebugTools.Assert(data.EglBackbuffer == null, "EGL Backbuffer must have been released!");
|
||||
|
||||
fixed (ID3D11Texture2D** texPtr = &data.Backbuffer)
|
||||
{
|
||||
ThrowIfFailed("GetBuffer", data.SwapChain->GetBuffer(0, __uuidof<ID3D11Texture2D>(), (void**) texPtr));
|
||||
}
|
||||
|
||||
var attributes = stackalloc int[]
|
||||
{
|
||||
EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGBA,
|
||||
EGL_TEXTURE_TARGET, EGL_TEXTURE_2D,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
data.EglBackbuffer = eglCreatePbufferFromClientBuffer(
|
||||
_eglDisplay,
|
||||
EGL_D3D_TEXTURE_ANGLE,
|
||||
data.Backbuffer,
|
||||
_eglConfig,
|
||||
attributes);
|
||||
}
|
||||
|
||||
private void CreateSwapChain1(HWND hWnd, WindowData data)
|
||||
{
|
||||
var desc = new DXGI_SWAP_CHAIN_DESC
|
||||
{
|
||||
BufferDesc =
|
||||
{
|
||||
Width = (uint) data.Reg.FramebufferSize.X,
|
||||
Height = (uint) data.Reg.FramebufferSize.Y,
|
||||
Format = Clyde._hasGLSrgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM
|
||||
},
|
||||
SampleDesc =
|
||||
{
|
||||
Count = 1
|
||||
},
|
||||
OutputWindow = hWnd,
|
||||
BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT,
|
||||
BufferCount = 2,
|
||||
SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
|
||||
Windowed = 1
|
||||
};
|
||||
|
||||
fixed (IDXGISwapChain** swapPtr = &data.SwapChain)
|
||||
{
|
||||
ThrowIfFailed("CreateSwapChain", _factory->CreateSwapChain(
|
||||
(IUnknown*) _device,
|
||||
&desc,
|
||||
swapPtr
|
||||
));
|
||||
}
|
||||
|
||||
SetupBackbuffer(data);
|
||||
}
|
||||
|
||||
public override void WindowDestroyed(WindowReg reg)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
|
||||
DestroyBackbuffer(data);
|
||||
data.SwapChain->Release();
|
||||
|
||||
_windowData.Remove(reg.Id);
|
||||
}
|
||||
|
||||
public bool TryInitialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
TryInitializeCore();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Failed to initialize custom ANGLE: {e}");
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EarlyInit()
|
||||
{
|
||||
// Early GL context init so that feature detection runs before window creation,
|
||||
// and so that we can know _hasGLSrgb in window creation.
|
||||
eglMakeCurrent(_eglDisplay, null, null, _eglContext);
|
||||
Clyde.InitOpenGL();
|
||||
Clyde._earlyGLInit = true;
|
||||
}
|
||||
|
||||
private void TryInitializeCore()
|
||||
{
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
|
||||
_sawmill.Debug($"EGL client extensions: {extensions}!");
|
||||
|
||||
CreateD3D11Device();
|
||||
CreateEglContext();
|
||||
}
|
||||
|
||||
private void CreateEglContext()
|
||||
{
|
||||
_eglDevice = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE, _device, null);
|
||||
if (_eglDevice == (void*) EGL_NO_DEVICE_EXT)
|
||||
throw new Exception("eglCreateDeviceANGLE failed.");
|
||||
|
||||
_eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, _eglDevice, null);
|
||||
if (_eglDisplay == null)
|
||||
throw new Exception("eglGetPlatformDisplayEXT failed.");
|
||||
|
||||
int major;
|
||||
int minor;
|
||||
if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
|
||||
throw new Exception("eglInitialize failed.");
|
||||
|
||||
var vendor = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VENDOR));
|
||||
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
|
||||
|
||||
_sawmill.Debug("EGL initialized!");
|
||||
_sawmill.Debug($"EGL vendor: {vendor}!");
|
||||
_sawmill.Debug($"EGL version: {version}!");
|
||||
_sawmill.Debug($"EGL extensions: {extensions}!");
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
|
||||
throw new Exception("eglBindAPI failed.");
|
||||
|
||||
var attribs = stackalloc int[]
|
||||
{
|
||||
// EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_STENCIL_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
var numConfigs = 0;
|
||||
if (eglChooseConfig(_eglDisplay, attribs, null, 0, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
var configs = stackalloc void*[numConfigs];
|
||||
if (eglChooseConfig(_eglDisplay, attribs, configs, numConfigs, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
if (numConfigs == 0)
|
||||
throw new Exception("No compatible EGL configurations returned!");
|
||||
|
||||
_sawmill.Debug($"{numConfigs} EGL configs possible!");
|
||||
|
||||
for (var i = 0; i < numConfigs; i++)
|
||||
{
|
||||
_sawmill.Debug(DumpEglConfig(_eglDisplay, configs[i]));
|
||||
}
|
||||
|
||||
_eglConfig = configs[0];
|
||||
|
||||
int supportedRenderableTypes;
|
||||
eglGetConfigAttrib(_eglDisplay, _eglConfig, EGL_RENDERABLE_TYPE, &supportedRenderableTypes);
|
||||
|
||||
_es3 = (supportedRenderableTypes & EGL_OPENGL_ES3_BIT) != 0;
|
||||
|
||||
var createAttribs = stackalloc int[]
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, _es3 ? 3 : 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
_eglContext = eglCreateContext(_eglDisplay, _eglConfig, null, createAttribs);
|
||||
if (_eglContext == (void*) EGL_NO_CONTEXT)
|
||||
throw new Exception("eglCreateContext failed!");
|
||||
|
||||
_sawmill.Debug("EGL context created!");
|
||||
|
||||
Clyde._openGLVersion = _es3 ? RendererOpenGLVersion.GLES3 : RendererOpenGLVersion.GLES2;
|
||||
}
|
||||
|
||||
private void CreateD3D11Device()
|
||||
{
|
||||
IDXGIDevice1* dxgiDevice = null;
|
||||
|
||||
try
|
||||
{
|
||||
fixed (IDXGIFactory1** ptr = &_factory)
|
||||
{
|
||||
ThrowIfFailed(nameof(CreateDXGIFactory1), CreateDXGIFactory1(__uuidof<IDXGIFactory1>(), (void**) ptr));
|
||||
}
|
||||
|
||||
// Try to find the correct adapter if specified.
|
||||
var adapterName = Clyde._cfg.GetCVar(CVars.DisplayAdapter);
|
||||
|
||||
if (adapterName != "")
|
||||
{
|
||||
_adapter = TryFindAdapterWithName(adapterName);
|
||||
|
||||
if (_adapter == null)
|
||||
{
|
||||
_sawmill.Warning($"Unable to find display adapter with requested name: {adapterName}");
|
||||
}
|
||||
|
||||
_sawmill.Debug($"Found display adapter with name: {adapterName}");
|
||||
}
|
||||
|
||||
#pragma warning disable CA1416
|
||||
#pragma warning disable CS0162
|
||||
IDXGIFactory6* factory6;
|
||||
if (_adapter == null && _factory->QueryInterface(__uuidof<IDXGIFactory6>(), (void**) &factory6) == 0)
|
||||
{
|
||||
var gpuPref = (DXGI_GPU_PREFERENCE) Clyde._cfg.GetCVar(CVars.DisplayGpuPreference);
|
||||
IDXGIAdapter1* adapter;
|
||||
for (var adapterIndex = 0u;
|
||||
factory6->EnumAdapterByGpuPreference(
|
||||
adapterIndex,
|
||||
gpuPref,
|
||||
__uuidof<IDXGIAdapter1>(),
|
||||
(void**)&adapter) != DXGI_ERROR_NOT_FOUND;
|
||||
adapterIndex++)
|
||||
{
|
||||
/*
|
||||
DXGI_ADAPTER_DESC1 aDesc;
|
||||
ThrowIfFailed("GetDesc1", adapter->GetDesc1(&aDesc));
|
||||
|
||||
var aDescName = new ReadOnlySpan<char>(aDesc.Description, 128);
|
||||
|
||||
Logger.DebugS("clyde.ogl.angle", aDescName.ToString());
|
||||
|
||||
adapter->Release();
|
||||
*/
|
||||
_adapter = adapter;
|
||||
break;
|
||||
}
|
||||
|
||||
factory6->Release();
|
||||
}
|
||||
#pragma warning restore CS0162
|
||||
#pragma warning restore CA1416
|
||||
|
||||
Span<D3D_FEATURE_LEVEL> featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
||||
{
|
||||
// 11_0 can do GLES3
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
// 9_3 can do GLES2
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
// If we get a 9_1 FL we can't do D3D11 based ANGLE,
|
||||
// but ANGLE can do it manually via the D3D9 renderer.
|
||||
// In this case, abort custom swap chain and let ANGLE handle everything.
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
|
||||
if (Clyde._cfg.GetCVar(CVars.DisplayAngleForceEs2))
|
||||
{
|
||||
featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
||||
{
|
||||
// Don't allow FL 11_0 so ANGLE is forced to init GLES2.
|
||||
D3D_FEATURE_LEVEL_9_3,
|
||||
D3D_FEATURE_LEVEL_9_1
|
||||
};
|
||||
}
|
||||
|
||||
if (Clyde._cfg.GetCVar(CVars.DisplayAngleForce10_0))
|
||||
{
|
||||
featureLevels = stackalloc D3D_FEATURE_LEVEL[]
|
||||
{
|
||||
D3D_FEATURE_LEVEL_10_0,
|
||||
};
|
||||
}
|
||||
|
||||
fixed (ID3D11Device** device = &_device)
|
||||
fixed (D3D_FEATURE_LEVEL* fl = &featureLevels[0])
|
||||
{
|
||||
ThrowIfFailed("D3D11CreateDevice", D3D11CreateDevice(
|
||||
(IDXGIAdapter*) _adapter,
|
||||
_adapter == null ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN,
|
||||
HMODULE.NULL,
|
||||
0,
|
||||
fl,
|
||||
(uint) featureLevels.Length,
|
||||
D3D11_SDK_VERSION,
|
||||
device,
|
||||
null,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
// Get adapter from the device.
|
||||
|
||||
ThrowIfFailed("QueryInterface", _device->QueryInterface(__uuidof<IDXGIDevice1>(), (void**) &dxgiDevice));
|
||||
|
||||
fixed (IDXGIAdapter1** ptrAdapter = &_adapter)
|
||||
{
|
||||
ThrowIfFailed("GetParent", dxgiDevice->GetParent(__uuidof<IDXGIAdapter1>(), (void**) ptrAdapter));
|
||||
}
|
||||
|
||||
_deviceFl = _device->GetFeatureLevel();
|
||||
|
||||
DXGI_ADAPTER_DESC1 desc;
|
||||
ThrowIfFailed("GetDesc1", _adapter->GetDesc1(&desc));
|
||||
|
||||
var descName = ((ReadOnlySpan<char>)desc.Description).TrimEnd('\0');
|
||||
|
||||
_sawmill.Debug("Successfully created D3D11 device!");
|
||||
_sawmill.Debug($"D3D11 Device Adapter: {descName.ToString()}");
|
||||
_sawmill.Debug($"D3D11 Device FL: {_deviceFl}");
|
||||
|
||||
if (_deviceFl == D3D_FEATURE_LEVEL_9_1)
|
||||
{
|
||||
throw new Exception(
|
||||
"D3D11 device has too low FL (need at least 9_3). Aborting custom swap chain!");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (dxgiDevice != null)
|
||||
dxgiDevice->Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
// Shut down ANGLE.
|
||||
if (_eglDisplay != null)
|
||||
eglTerminate(_eglDisplay);
|
||||
|
||||
if (_eglDevice != null)
|
||||
eglReleaseDeviceANGLE(_eglDevice);
|
||||
|
||||
// Shut down D3D11/DXGI
|
||||
if (_factory != null)
|
||||
_factory->Release();
|
||||
|
||||
if (_adapter != null)
|
||||
_adapter->Release();
|
||||
|
||||
if (_device != null)
|
||||
_device->Release();
|
||||
}
|
||||
|
||||
public override void SwapAllBuffers()
|
||||
{
|
||||
foreach (var data in _windowData.Values)
|
||||
{
|
||||
data.SwapChain->Present(_swapInterval, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
DestroyBackbuffer(data);
|
||||
|
||||
ThrowIfFailed("ResizeBuffers", data.SwapChain->ResizeBuffers(
|
||||
2,
|
||||
(uint) reg.FramebufferSize.X, (uint) reg.FramebufferSize.Y,
|
||||
Clyde._hasGLSrgb ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
0));
|
||||
|
||||
SetupBackbuffer(data);
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
||||
}
|
||||
|
||||
private IDXGIAdapter1* TryFindAdapterWithName(string name)
|
||||
{
|
||||
uint idx = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
IDXGIAdapter1* adapter;
|
||||
var hr = _factory->EnumAdapters1(idx++, &adapter);
|
||||
if (hr == DXGI_ERROR_NOT_FOUND)
|
||||
break;
|
||||
|
||||
ThrowIfFailed("EnumAdapters1", hr);
|
||||
|
||||
DXGI_ADAPTER_DESC1 desc;
|
||||
ThrowIfFailed("GetDesc1", adapter->GetDesc1(&desc));
|
||||
|
||||
var descName = ((ReadOnlySpan<char>)desc.Description);
|
||||
|
||||
if (descName.StartsWith(name))
|
||||
return adapter;
|
||||
|
||||
adapter->Release();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void* GetProcAddress(string name)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[128];
|
||||
var len = Encoding.UTF8.GetBytes(name, buf);
|
||||
buf[len] = 0;
|
||||
|
||||
fixed (byte* ptr = &buf[0])
|
||||
{
|
||||
return eglGetProcAddress(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BindWindowRenderTarget(WindowId rtWindowId)
|
||||
{
|
||||
var data = _windowData[rtWindowId];
|
||||
var result = eglMakeCurrent(_eglDisplay, data.EglBackbuffer, data.EglBackbuffer, _eglContext);
|
||||
if (result == EGL_FALSE)
|
||||
throw new Exception("eglMakeCurrent failed.");
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
Clyde.CheckGlError();
|
||||
}
|
||||
|
||||
private static void ThrowIfFailed(string methodName, HRESULT hr)
|
||||
{
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
}
|
||||
}
|
||||
|
||||
private static string DumpEglConfig(void* display, void* config)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"cfg: {Get(EGL_CONFIG_ID):000} | ");
|
||||
sb.AppendFormat(
|
||||
"R/G/B/A/D/S: {0}/{1}/{2}/{3}/{4:00}/{5} | ",
|
||||
Get(EGL_RED_SIZE), Get(EGL_GREEN_SIZE), Get(EGL_BLUE_SIZE), Get(EGL_ALPHA_SIZE),
|
||||
Get(EGL_DEPTH_SIZE), Get(EGL_STENCIL_SIZE));
|
||||
|
||||
// COLOR_BUFFER_TYPE
|
||||
sb.Append($"CBT: {Get(EGL_COLOR_BUFFER_TYPE)} | ");
|
||||
sb.Append($"CC: {Get(EGL_CONFIG_CAVEAT)} | ");
|
||||
sb.Append($"CONF: {Get(EGL_CONFORMANT)} | ");
|
||||
sb.Append($"NAT: {Get(EGL_NATIVE_VISUAL_ID)} | ");
|
||||
sb.Append($"SAMPLES: {Get(EGL_SAMPLES)} | ");
|
||||
sb.Append($"SAMPLE_BUFFERS: {Get(EGL_SAMPLE_BUFFERS)} | ");
|
||||
sb.Append($"ORIENTATION: {Get(EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE)} | ");
|
||||
sb.Append($"RENDERABLE: {Get(EGL_RENDERABLE_TYPE)}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
int Get(int attrib)
|
||||
{
|
||||
int value;
|
||||
if (eglGetConfigAttrib(display, config, attrib, &value) == EGL_FALSE)
|
||||
throw new Exception("eglGetConfigAttrib failed!");
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowData
|
||||
{
|
||||
public WindowReg Reg = default!;
|
||||
|
||||
public IDXGISwapChain* SwapChain;
|
||||
public ID3D11Texture2D* Backbuffer;
|
||||
public void* EglBackbuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
using System;
|
||||
using OpenToolkit;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages OpenGL contexts for the windowing system.
|
||||
/// </summary>
|
||||
private abstract class GLContextBase
|
||||
{
|
||||
protected readonly Clyde Clyde;
|
||||
|
||||
public IBindingsContext BindingsContext { get; }
|
||||
|
||||
public GLContextBase(Clyde clyde)
|
||||
{
|
||||
Clyde = clyde;
|
||||
BindingsContext = new BindingsContextImpl(this);
|
||||
}
|
||||
|
||||
public GLContextSpec? GetNewWindowSpec()
|
||||
{
|
||||
return SpecWithOpenGLVersion(Clyde._openGLVersion);
|
||||
}
|
||||
|
||||
public virtual bool EarlyContextInit => false;
|
||||
|
||||
public abstract GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version);
|
||||
|
||||
public abstract void UpdateVSync();
|
||||
public abstract void WindowCreated(GLContextSpec? spec, WindowReg reg);
|
||||
public abstract void WindowDestroyed(WindowReg reg);
|
||||
|
||||
public abstract void Shutdown();
|
||||
|
||||
public abstract GLContextSpec[] SpecsToTry { get; }
|
||||
public abstract bool RequireWindowGL { get; }
|
||||
public abstract bool HasBrokenWindowSrgb { get; }
|
||||
|
||||
protected static GLContextSpec GetVersionSpec(RendererOpenGLVersion version)
|
||||
{
|
||||
var spec = new GLContextSpec { OpenGLVersion = version };
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case RendererOpenGLVersion.GL33:
|
||||
spec.Major = 3;
|
||||
spec.Minor = 3;
|
||||
spec.Profile = GLContextProfile.Core;
|
||||
spec.CreationApi = GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
case RendererOpenGLVersion.GL31:
|
||||
spec.Major = 3;
|
||||
spec.Minor = 1;
|
||||
spec.Profile = GLContextProfile.Compatibility;
|
||||
spec.CreationApi = GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
case RendererOpenGLVersion.GLES3:
|
||||
spec.Major = 3;
|
||||
spec.Minor = 0;
|
||||
spec.Profile = GLContextProfile.Es;
|
||||
// Initializing ES on Windows EGL so that we can use ANGLE.
|
||||
spec.CreationApi = OperatingSystem.IsWindows()
|
||||
? GLContextCreationApi.Egl
|
||||
: GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
case RendererOpenGLVersion.GLES2:
|
||||
spec.Major = 2;
|
||||
spec.Minor = 0;
|
||||
spec.Profile = GLContextProfile.Es;
|
||||
// Initializing ES on Windows EGL so that we can use ANGLE.
|
||||
spec.CreationApi = OperatingSystem.IsWindows()
|
||||
? GLContextCreationApi.Egl
|
||||
: GLContextCreationApi.Native;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
public abstract void SwapAllBuffers();
|
||||
public abstract void WindowResized(WindowReg reg, Vector2i oldSize);
|
||||
|
||||
public abstract unsafe void* GetProcAddress(string name);
|
||||
|
||||
public abstract void BindWindowRenderTarget(WindowId rtWindowId);
|
||||
|
||||
public virtual void BeforeSharedWindowCreateUnbind()
|
||||
{
|
||||
}
|
||||
|
||||
private sealed class BindingsContextImpl : IBindingsContext
|
||||
{
|
||||
private readonly GLContextBase _context;
|
||||
|
||||
public BindingsContextImpl(GLContextBase context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public unsafe IntPtr GetProcAddress(string procName)
|
||||
{
|
||||
return (nint)_context.GetProcAddress(procName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static Robust.Client.Graphics.Clyde.Egl;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Context manager that uses EGL directly so that we get better control over multi-window management.
|
||||
/// </summary>
|
||||
private sealed unsafe class GLContextEgl : GLContextBase
|
||||
{
|
||||
// TODO: Currently this class uses ANGLE and Windows-specific initialization code.
|
||||
// It could be made more general purpose later if anybody ever gets adventurous with like, Wayland.
|
||||
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
|
||||
private void* _eglDisplay;
|
||||
private void* _eglContext;
|
||||
private void* _eglConfig;
|
||||
|
||||
public override bool HasBrokenWindowSrgb => Clyde._isGLES && OperatingSystem.IsWindows();
|
||||
|
||||
public GLContextEgl(Clyde clyde) : base(clyde)
|
||||
{
|
||||
_sawmill = clyde._logManager.GetSawmill("clyde.ogl.egl");
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void UpdateVSync()
|
||||
{
|
||||
var interval = Clyde._vSync ? 1 : 0;
|
||||
|
||||
eglSwapInterval(_eglDisplay, interval);
|
||||
}
|
||||
|
||||
public void InitializePublic()
|
||||
{
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(null, EGL_EXTENSIONS));
|
||||
_sawmill.Debug($"EGL client extensions: {extensions}!");
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
{
|
||||
var data = new WindowData
|
||||
{
|
||||
Reg = reg
|
||||
};
|
||||
_windowData[reg.Id] = data;
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
Initialize(data);
|
||||
|
||||
var attribs = stackalloc int[]
|
||||
{
|
||||
EGL_GL_COLORSPACE, EGL_GL_COLORSPACE_SRGB,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Set up window surface.
|
||||
var hWNd = Clyde._windowing!.WindowGetWin32Window(reg)!.Value;
|
||||
data.EglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (void*) hWNd, attribs);
|
||||
if (data.EglSurface == (void*) EGL_NO_SURFACE)
|
||||
throw new Exception("eglCreateWindowSurface failed.");
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var window = Clyde._windowing!.WindowGetX11Id(reg)!.Value;
|
||||
data.EglSurface = eglCreateWindowSurface(_eglDisplay, _eglConfig, (void*) window, attribs);
|
||||
if (data.EglSurface == (void*) EGL_NO_SURFACE)
|
||||
throw new Exception("eglCreateWindowSurface failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("EGL is not currently supported outside Windows ANGLE or X11 Linux");
|
||||
}
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
var result = eglMakeCurrent(_eglDisplay, data.EglSurface, data.EglSurface, _eglContext);
|
||||
if (result == EGL_FALSE)
|
||||
throw new Exception("eglMakeCurrent failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowDestroyed(WindowReg reg)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
eglDestroySurface(_eglDisplay, data.EglSurface);
|
||||
}
|
||||
|
||||
private void Initialize(WindowData mainWindow)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Setting up ANGLE without manually selecting a D3D11 device requires a windows DC.
|
||||
mainWindow.DC = Windows.GetDC((HWND)Clyde._windowing!.WindowGetWin32Window(mainWindow.Reg)!.Value);
|
||||
|
||||
_eglDisplay = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, (void*) mainWindow.DC, null);
|
||||
if (_eglDisplay == null)
|
||||
throw new Exception("eglGetPlatformDisplayEXT failed.");
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var xDisplay = Clyde._windowing!.WindowGetX11Display(mainWindow.Reg)!.Value;
|
||||
_eglDisplay = eglGetDisplay((void*) xDisplay);
|
||||
if (mainWindow.EglSurface == (void*) EGL_NO_SURFACE)
|
||||
throw new Exception("eglCreateWindowSurface failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("EGL is not currently supported outside Windows ANGLE or X11 Linux");
|
||||
}
|
||||
|
||||
int major;
|
||||
int minor;
|
||||
if (eglInitialize(_eglDisplay, &major, &minor) == EGL_FALSE)
|
||||
throw new Exception("eglInitialize failed.");
|
||||
|
||||
var vendor = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VENDOR));
|
||||
var version = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_VERSION));
|
||||
var extensions = Marshal.PtrToStringUTF8((nint) eglQueryString(_eglDisplay, EGL_EXTENSIONS));
|
||||
|
||||
_sawmill.Debug("EGL initialized!");
|
||||
_sawmill.Debug($"EGL vendor: {vendor}!");
|
||||
_sawmill.Debug($"EGL version: {version}!");
|
||||
_sawmill.Debug($"EGL extensions: {extensions}!");
|
||||
|
||||
if (eglBindAPI(EGL_OPENGL_ES_API) != EGL_TRUE)
|
||||
throw new Exception("eglBindAPI failed.");
|
||||
|
||||
var attribs = stackalloc int[]
|
||||
{
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_STENCIL_SIZE, 8,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
var numConfigs = 0;
|
||||
if (eglChooseConfig(_eglDisplay, attribs, null, 0, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
var configs = stackalloc void*[numConfigs];
|
||||
if (eglChooseConfig(_eglDisplay, attribs, configs, numConfigs, &numConfigs) == EGL_FALSE)
|
||||
throw new Exception("eglChooseConfig failed.");
|
||||
|
||||
if (numConfigs == 0)
|
||||
throw new Exception("No compatible EGL configurations returned!");
|
||||
|
||||
_sawmill.Debug($"{numConfigs} EGL configs possible!");
|
||||
|
||||
for (var i = 0; i < numConfigs; i++)
|
||||
{
|
||||
_sawmill.Debug(DumpEglConfig(_eglDisplay, configs[i]));
|
||||
}
|
||||
|
||||
_eglConfig = configs[0];
|
||||
|
||||
var createAttribs = stackalloc int[]
|
||||
{
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
_eglContext = eglCreateContext(_eglDisplay, _eglConfig, null, createAttribs);
|
||||
if (_eglContext == (void*) EGL_NO_CONTEXT)
|
||||
throw new Exception("eglCreateContext failed!");
|
||||
|
||||
_sawmill.Debug("EGL context created!");
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
if (_eglDisplay != null)
|
||||
{
|
||||
eglMakeCurrent(_eglDisplay, null, null, null);
|
||||
eglTerminate(_eglDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
public override GLContextSpec[] SpecsToTry => Array.Empty<GLContextSpec>();
|
||||
public override bool RequireWindowGL => false;
|
||||
|
||||
public override void SwapAllBuffers()
|
||||
{
|
||||
foreach (var data in _windowData.Values)
|
||||
{
|
||||
eglSwapBuffers(_eglDisplay, data.EglSurface);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
// Nada..?
|
||||
}
|
||||
|
||||
public override void* GetProcAddress(string name)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[128];
|
||||
var len = Encoding.UTF8.GetBytes(name, buf);
|
||||
buf[len] = 0;
|
||||
|
||||
fixed (byte* ptr = &buf[0])
|
||||
{
|
||||
return eglGetProcAddress(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BindWindowRenderTarget(WindowId rtWindowId)
|
||||
{
|
||||
var data = _windowData[rtWindowId];
|
||||
var result = eglMakeCurrent(_eglDisplay, data.EglSurface, data.EglSurface, _eglContext);
|
||||
if (result == EGL_FALSE)
|
||||
throw new Exception("eglMakeCurrent failed.");
|
||||
}
|
||||
|
||||
private static string DumpEglConfig(void* display, void* config)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"cfg: {Get(EGL_CONFIG_ID):00} | ");
|
||||
sb.AppendFormat(
|
||||
"R/G/B/A/D/S: {0}/{1}/{2}/{3}/{4:00}/{5} | ",
|
||||
Get(EGL_RED_SIZE), Get(EGL_GREEN_SIZE), Get(EGL_BLUE_SIZE), Get(EGL_ALPHA_SIZE),
|
||||
Get(EGL_DEPTH_SIZE), Get(EGL_STENCIL_SIZE));
|
||||
|
||||
// COLOR_BUFFER_TYPE
|
||||
sb.Append($"CBT: {Get(EGL_COLOR_BUFFER_TYPE)} | ");
|
||||
sb.Append($"CC: {Get(EGL_CONFIG_CAVEAT)} | ");
|
||||
sb.Append($"CONF: {Get(EGL_CONFORMANT)} | ");
|
||||
sb.Append($"NAT: {Get(EGL_NATIVE_VISUAL_ID)} | ");
|
||||
sb.Append($"SAMPLES: {Get(EGL_SAMPLES)} | ");
|
||||
sb.Append($"SAMPLE_BUFFERS: {Get(EGL_SAMPLE_BUFFERS)}");
|
||||
|
||||
return sb.ToString();
|
||||
|
||||
int Get(int attrib)
|
||||
{
|
||||
int value;
|
||||
if (eglGetConfigAttrib(display, config, attrib, &value) == EGL_FALSE)
|
||||
throw new Exception("eglGetConfigAttrib failed!");
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WindowData
|
||||
{
|
||||
public WindowReg Reg = default!;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// Windows DC for this window.
|
||||
// Only used for main window.
|
||||
public nint DC;
|
||||
|
||||
public void* EglSurface;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// GL Context(s) provided by the windowing system (GLFW, SDL2...)
|
||||
/// </summary>
|
||||
private sealed class GLContextWindow : GLContextBase
|
||||
{
|
||||
private readonly Dictionary<WindowId, WindowData> _windowData = new();
|
||||
|
||||
public override GLContextSpec[] SpecsToTry
|
||||
{
|
||||
get
|
||||
{
|
||||
// Compat mode: only GLES2.
|
||||
if (Clyde._cfg.GetCVar(CVars.DisplayCompat))
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES3),
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES2)
|
||||
};
|
||||
}
|
||||
|
||||
var requestedVersion = (RendererOpenGLVersion) Clyde._cfg.GetCVar(CVars.DisplayOpenGLVersion);
|
||||
if (requestedVersion != RendererOpenGLVersion.Auto)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
GetVersionSpec(requestedVersion)
|
||||
};
|
||||
}
|
||||
|
||||
return new[]
|
||||
{
|
||||
GetVersionSpec(RendererOpenGLVersion.GL33),
|
||||
GetVersionSpec(RendererOpenGLVersion.GL31),
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES3),
|
||||
GetVersionSpec(RendererOpenGLVersion.GLES2),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RequireWindowGL => true;
|
||||
// ANGLE does not support main window sRGB.
|
||||
public override bool HasBrokenWindowSrgb => Clyde._isGLES && OperatingSystem.IsWindows();
|
||||
|
||||
public GLContextWindow(Clyde clyde) : base(clyde)
|
||||
{
|
||||
}
|
||||
|
||||
public override GLContextSpec? SpecWithOpenGLVersion(RendererOpenGLVersion version)
|
||||
{
|
||||
return GetVersionSpec(version);
|
||||
}
|
||||
|
||||
public override void UpdateVSync()
|
||||
{
|
||||
if (Clyde._mainWindow == null)
|
||||
return;
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow);
|
||||
Clyde._windowing.GLSwapInterval(Clyde._mainWindow, Clyde._vSync ? 1 : 0);
|
||||
}
|
||||
|
||||
public override void WindowCreated(GLContextSpec? spec, WindowReg reg)
|
||||
{
|
||||
reg.RenderTarget.MakeGLFence = true;
|
||||
|
||||
var data = new WindowData
|
||||
{
|
||||
Reg = reg
|
||||
};
|
||||
|
||||
_windowData[reg.Id] = data;
|
||||
|
||||
if (reg.IsMainWindow)
|
||||
{
|
||||
Clyde._openGLVersion = spec!.Value.OpenGLVersion;
|
||||
UpdateVSync();
|
||||
}
|
||||
else
|
||||
{
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow);
|
||||
|
||||
CreateWindowRenderTexture(data);
|
||||
InitWindowBlitThread(data);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowDestroyed(WindowReg reg)
|
||||
{
|
||||
var data = _windowData[reg.Id];
|
||||
data.BlitDoneEvent?.Set();
|
||||
// Set events so blit thread properly wakes up and notices it needs to shut down.
|
||||
data.BlitStartEvent?.Set();
|
||||
|
||||
_windowData.Remove(reg.Id);
|
||||
}
|
||||
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
// Nada, window system shutdown handles it.
|
||||
}
|
||||
|
||||
public override void SwapAllBuffers()
|
||||
{
|
||||
BlitSecondaryWindows();
|
||||
|
||||
Clyde._windowing!.WindowSwapBuffers(Clyde._mainWindow!);
|
||||
}
|
||||
|
||||
public override void WindowResized(WindowReg reg, Vector2i oldSize)
|
||||
{
|
||||
if (reg.IsMainWindow)
|
||||
return;
|
||||
|
||||
// Recreate render texture for the window.
|
||||
var data = _windowData[reg.Id];
|
||||
data.RenderTexture!.Dispose();
|
||||
CreateWindowRenderTexture(data);
|
||||
}
|
||||
|
||||
public override unsafe void* GetProcAddress(string name)
|
||||
{
|
||||
return Clyde._windowing!.GLGetProcAddress(name);
|
||||
}
|
||||
|
||||
public override void BindWindowRenderTarget(WindowId rtWindowId)
|
||||
{
|
||||
var data = _windowData[rtWindowId];
|
||||
if (data.Reg.IsMainWindow)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
Clyde.CheckGlError();
|
||||
}
|
||||
else
|
||||
{
|
||||
var loaded = Clyde.RtToLoaded(data.RenderTexture!);
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, loaded.FramebufferHandle.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
public override void BeforeSharedWindowCreateUnbind()
|
||||
{
|
||||
Clyde._windowing!.GLMakeContextCurrent(null);
|
||||
}
|
||||
|
||||
private void BlitSecondaryWindows()
|
||||
{
|
||||
// Only got main window.
|
||||
if (Clyde._windows.Count == 1)
|
||||
return;
|
||||
|
||||
if (!Clyde._hasGLFenceSync && Clyde._cfg.GetCVar(CVars.DisplayForceSyncWindows))
|
||||
{
|
||||
GL.Finish();
|
||||
}
|
||||
|
||||
if (Clyde.EffectiveThreadWindowBlit)
|
||||
{
|
||||
foreach (var window in _windowData.Values)
|
||||
{
|
||||
if (window.Reg.IsMainWindow)
|
||||
continue;
|
||||
|
||||
window.BlitDoneEvent!.Reset();
|
||||
window.BlitStartEvent!.Set();
|
||||
window.BlitDoneEvent.Wait();
|
||||
window.UnlockBeforeSwap = Clyde._cfg.GetCVar(CVars.DisplayThreadUnlockBeforeSwap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var window in _windowData.Values)
|
||||
{
|
||||
if (window.Reg.IsMainWindow)
|
||||
continue;
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(window.Reg);
|
||||
BlitThreadDoSecondaryWindowBlit(window);
|
||||
}
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThreadDoSecondaryWindowBlit(WindowData window)
|
||||
{
|
||||
if (Clyde._hasGLFenceSync)
|
||||
{
|
||||
// 0xFFFFFFFFFFFFFFFFUL is GL_TIMEOUT_IGNORED
|
||||
var rt = window.Reg.RenderTarget;
|
||||
var sync = rt.LastGLSync;
|
||||
GL.WaitSync(sync, WaitSyncFlags.None, unchecked((long) 0xFFFFFFFFFFFFFFFFUL));
|
||||
Clyde.CheckGlError();
|
||||
}
|
||||
|
||||
GL.Viewport(0, 0, window.Reg.FramebufferSize.X, window.Reg.FramebufferSize.Y);
|
||||
Clyde.CheckGlError();
|
||||
|
||||
Clyde.SetTexture(TextureUnit.Texture0, window.RenderTexture!.Texture);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
Clyde.CheckGlError();
|
||||
|
||||
if (window.UnlockBeforeSwap)
|
||||
{
|
||||
window.BlitDoneEvent?.Set();
|
||||
}
|
||||
Clyde._windowing!.WindowSwapBuffers(window.Reg);
|
||||
if (!window.UnlockBeforeSwap)
|
||||
{
|
||||
window.BlitDoneEvent?.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void BlitThreadInit(WindowData reg)
|
||||
{
|
||||
Clyde._windowing!.GLMakeContextCurrent(reg.Reg);
|
||||
Clyde._windowing.GLSwapInterval(reg.Reg, 0);
|
||||
|
||||
Clyde.SetupDebugCallback();
|
||||
|
||||
if (!Clyde._isGLES)
|
||||
GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
var vao = GL.GenVertexArray();
|
||||
GL.BindVertexArray(vao);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, Clyde.WindowVBO.ObjectHandle);
|
||||
// Vertex Coords
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
|
||||
var program = Clyde._compileProgram(
|
||||
Clyde._winBlitShaderVert,
|
||||
Clyde._winBlitShaderFrag,
|
||||
new (string, uint)[]
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
},
|
||||
includeLib: false);
|
||||
|
||||
GL.UseProgram(program.Handle);
|
||||
var loc = GL.GetUniformLocation(program.Handle, "tex");
|
||||
Clyde.SetTexture(TextureUnit.Texture0, reg.RenderTexture!.Texture);
|
||||
GL.Uniform1(loc, 0);
|
||||
}
|
||||
|
||||
private void InitWindowBlitThread(WindowData reg)
|
||||
{
|
||||
if (Clyde.EffectiveThreadWindowBlit)
|
||||
{
|
||||
reg.BlitStartEvent = new ManualResetEventSlim();
|
||||
reg.BlitDoneEvent = new ManualResetEventSlim();
|
||||
reg.BlitThread = new Thread(() => BlitThread(reg))
|
||||
{
|
||||
Name = $"WinBlitThread ID:{reg.Reg.Id}",
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
// System.Console.WriteLine("A");
|
||||
reg.BlitThread.Start();
|
||||
// Wait for thread to finish init.
|
||||
reg.BlitDoneEvent.Wait();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Binds GL context.
|
||||
BlitThreadInit(reg);
|
||||
|
||||
Clyde._windowing!.GLMakeContextCurrent(Clyde._mainWindow!);
|
||||
}
|
||||
}
|
||||
|
||||
private void BlitThread(WindowData reg)
|
||||
{
|
||||
BlitThreadInit(reg);
|
||||
|
||||
reg.BlitDoneEvent!.Set();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
reg.BlitStartEvent!.Wait();
|
||||
if (reg.Reg.IsDisposed)
|
||||
{
|
||||
BlitThreadCleanup(reg);
|
||||
return;
|
||||
}
|
||||
|
||||
reg.BlitStartEvent!.Reset();
|
||||
|
||||
// Do channel blit.
|
||||
BlitThreadDoSecondaryWindowBlit(reg);
|
||||
}
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
// ok channel closed, we exit.
|
||||
e.Handle(ec => ec is ChannelClosedException);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlitThreadCleanup(WindowData reg)
|
||||
{
|
||||
reg.BlitDoneEvent!.Dispose();
|
||||
reg.BlitStartEvent!.Dispose();
|
||||
}
|
||||
|
||||
private void CreateWindowRenderTexture(WindowData reg)
|
||||
{
|
||||
reg.RenderTexture?.Dispose();
|
||||
|
||||
reg.RenderTexture = Clyde.CreateRenderTarget(
|
||||
reg.Reg.FramebufferSize,
|
||||
new RenderTargetFormatParameters
|
||||
{
|
||||
ColorFormat = RenderTargetColorFormat.Rgba8Srgb,
|
||||
HasDepthStencil = true
|
||||
},
|
||||
name: $"{reg.Reg.Id}-RenderTexture");
|
||||
// Necessary to correctly sync multi-context blitting.
|
||||
reg.RenderTexture.MakeGLFence = true;
|
||||
}
|
||||
|
||||
private sealed class WindowData
|
||||
{
|
||||
public WindowReg Reg = default!;
|
||||
|
||||
public RenderTexture? RenderTexture;
|
||||
// Used EXCLUSIVELY to run the two rendering commands to blit to the window.
|
||||
public Thread? BlitThread;
|
||||
public ManualResetEventSlim? BlitStartEvent;
|
||||
public ManualResetEventSlim? BlitDoneEvent;
|
||||
public bool UnlockBeforeSwap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an OpenGL buffer object.
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
private class GLBuffer
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
public BufferTarget Type { get; }
|
||||
public uint ObjectHandle { get; private set; }
|
||||
public BufferUsageHint UsageHint { get; }
|
||||
public string? Name { get; }
|
||||
|
||||
public GLBuffer(Clyde clyde, BufferTarget type, BufferUsageHint usage, string? name = null)
|
||||
{
|
||||
_clyde = clyde;
|
||||
Type = type;
|
||||
Name = name;
|
||||
UsageHint = usage;
|
||||
|
||||
GL.GenBuffers(1, out uint handle);
|
||||
clyde.CheckGlError();
|
||||
ObjectHandle = handle;
|
||||
|
||||
GL.BindBuffer(type, handle);
|
||||
clyde.CheckGlError();
|
||||
|
||||
_clyde.ObjectLabelMaybe(ObjectLabelIdentifier.Buffer, ObjectHandle, name);
|
||||
}
|
||||
|
||||
public GLBuffer(Clyde clyde, BufferTarget type, BufferUsageHint usage, int size, string? name = null)
|
||||
: this(clyde, type, usage, name)
|
||||
{
|
||||
Reallocate(size);
|
||||
}
|
||||
|
||||
public GLBuffer(Clyde clyde, BufferTarget type, BufferUsageHint usage, Span<byte> initialize,
|
||||
string? name = null)
|
||||
: this(clyde, type, usage, name)
|
||||
{
|
||||
Reallocate(initialize);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Use()
|
||||
{
|
||||
DebugTools.Assert(ObjectHandle != 0);
|
||||
|
||||
GL.BindBuffer(Type, ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
GL.DeleteBuffer(ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
ObjectHandle = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>glBufferSubData</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteSubData<T>(int start, Span<T> data) where T : unmanaged
|
||||
{
|
||||
Use();
|
||||
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = byteSpan)
|
||||
{
|
||||
GL.BufferSubData(Type, (IntPtr) start, byteSpan.Length, (IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>glBufferSubData</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteSubData<T>(Span<T> data) where T : unmanaged
|
||||
{
|
||||
Use();
|
||||
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = byteSpan)
|
||||
{
|
||||
GL.BufferSubData(Type, IntPtr.Zero, byteSpan.Length, (IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>glBufferSubData</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteSubData<T>(in T data) where T : unmanaged
|
||||
{
|
||||
Use();
|
||||
unsafe
|
||||
{
|
||||
fixed (T* ptr = &data)
|
||||
{
|
||||
GL.BufferSubData(Type, IntPtr.Zero, sizeof(T), (IntPtr) ptr);
|
||||
}
|
||||
}
|
||||
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>glBufferData</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reallocate<T>(Span<T> data) where T : unmanaged
|
||||
{
|
||||
Use();
|
||||
var byteSpan = MemoryMarshal.AsBytes(data);
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* ptr = byteSpan)
|
||||
{
|
||||
GL.BufferData(Type, byteSpan.Length, (IntPtr) ptr, UsageHint);
|
||||
}
|
||||
}
|
||||
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>glBufferData</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reallocate<T>(in T data) where T : unmanaged
|
||||
{
|
||||
Use();
|
||||
unsafe
|
||||
{
|
||||
fixed (T* ptr = &data)
|
||||
{
|
||||
GL.BufferData(Type, sizeof(T), (IntPtr) ptr, UsageHint);
|
||||
}
|
||||
}
|
||||
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>glBufferData</c>
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reallocate(int size)
|
||||
{
|
||||
Use();
|
||||
GL.BufferData(Type, size, IntPtr.Zero, UsageHint);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Subtype of buffers so that we can have a generic constructor.
|
||||
/// Functionally equivalent to <see cref="GLBuffer"/> otherwise.
|
||||
/// </summary>
|
||||
private sealed class GLBuffer<T> : GLBuffer where T : unmanaged
|
||||
{
|
||||
public GLBuffer(Clyde clyde, BufferTarget type, BufferUsageHint usage, Span<T> initialize,
|
||||
string? name = null)
|
||||
: base(clyde, type, usage, MemoryMarshal.AsBytes(initialize), name)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
// Go through the commit log if you wanna find why this struct exists.
|
||||
// And why there's no implicit operator.
|
||||
/// <summary>
|
||||
/// Basically just a handle around the integer object handles returned by OpenGL.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
internal struct GLHandle : IEquatable<GLHandle>
|
||||
{
|
||||
public readonly uint Handle;
|
||||
|
||||
public GLHandle(int handle) : this((uint) handle)
|
||||
{
|
||||
}
|
||||
|
||||
public GLHandle(uint handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public bool Equals(GLHandle other)
|
||||
{
|
||||
return Handle == other.Handle;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is GLHandle other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Handle.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(GLHandle)}: {Handle}";
|
||||
}
|
||||
|
||||
public static bool operator ==(GLHandle a, GLHandle b)
|
||||
{
|
||||
return a.Handle == b.Handle;
|
||||
}
|
||||
|
||||
public static bool operator !=(GLHandle a, GLHandle b)
|
||||
{
|
||||
return a.Handle != b.Handle;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
private sealed class GLShader
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
|
||||
public GLShader(Clyde clyde, ShaderType type, string shaderSource, string? name=null)
|
||||
{
|
||||
_clyde = clyde;
|
||||
Compile(type, shaderSource);
|
||||
if (name != null)
|
||||
{
|
||||
_clyde.ObjectLabelMaybe(ObjectLabelIdentifier.Shader, ObjectHandle, name);
|
||||
}
|
||||
}
|
||||
|
||||
public uint ObjectHandle { get; private set; } = 0;
|
||||
public ShaderType Type { get; private set; }
|
||||
|
||||
private void Compile(ShaderType type, string shaderSource)
|
||||
{
|
||||
ObjectHandle = (uint)GL.CreateShader(type);
|
||||
Type = type;
|
||||
GL.ShaderSource((int) ObjectHandle, shaderSource);
|
||||
_clyde.CheckGlError();
|
||||
GL.CompileShader(ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
|
||||
GL.GetShader(ObjectHandle, ShaderParameter.CompileStatus, out var compiled);
|
||||
_clyde.CheckGlError();
|
||||
if (compiled != 1)
|
||||
{
|
||||
var message = GL.GetShaderInfoLog((int) ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
Delete();
|
||||
throw new ShaderCompilationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (ObjectHandle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GL.DeleteShader(ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
ObjectHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,625 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an utility class. It does not check whether the OpenGL state machine is set up correctly.
|
||||
/// You've been warned:
|
||||
/// using things like <see cref="SetUniformTexture" /> if this buffer isn't bound WILL mess things up!
|
||||
/// </summary>
|
||||
private sealed class GLShaderProgram
|
||||
{
|
||||
private readonly sbyte?[] _uniformIntCache = new sbyte?[UniCount];
|
||||
private readonly Dictionary<string, int> _uniformCache = new();
|
||||
public uint Handle = 0;
|
||||
private GLShader? _fragmentShader;
|
||||
private GLShader? _vertexShader;
|
||||
public string? Name { get; }
|
||||
private readonly Clyde _clyde;
|
||||
|
||||
public GLShaderProgram(Clyde clyde, string? name = null)
|
||||
{
|
||||
_clyde = clyde;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Add(GLShader shader)
|
||||
{
|
||||
ClearCaches();
|
||||
switch (shader.Type)
|
||||
{
|
||||
case ShaderType.VertexShader:
|
||||
_vertexShader = shader;
|
||||
break;
|
||||
case ShaderType.FragmentShader:
|
||||
_fragmentShader = shader;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Tried to add unsupported shader type!");
|
||||
}
|
||||
}
|
||||
|
||||
public void Link((string, uint)[] attribLocations)
|
||||
{
|
||||
ClearCaches();
|
||||
Handle = (uint) GL.CreateProgram();
|
||||
_clyde.CheckGlError();
|
||||
if (Name != null)
|
||||
{
|
||||
_clyde.ObjectLabelMaybe(ObjectLabelIdentifier.Program, Handle, Name);
|
||||
}
|
||||
|
||||
if (_vertexShader != null)
|
||||
{
|
||||
GL.AttachShader(Handle, _vertexShader.ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
if (_fragmentShader != null)
|
||||
{
|
||||
GL.AttachShader(Handle, _fragmentShader.ObjectHandle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
foreach (var (varName, loc) in attribLocations)
|
||||
{
|
||||
// OpenGL 3.1 is ass and doesn't allow you to specify layout(location = X) in shaders.
|
||||
// So we have to manually do it here.
|
||||
// Ugh.
|
||||
|
||||
GL.BindAttribLocation(Handle, loc, varName);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
GL.LinkProgram(Handle);
|
||||
_clyde.CheckGlError();
|
||||
|
||||
GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out var compiled);
|
||||
_clyde.CheckGlError();
|
||||
if (compiled != 1)
|
||||
{
|
||||
throw new ShaderCompilationException(GL.GetProgramInfoLog((int) Handle));
|
||||
}
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
if (_clyde._currentProgram == this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ForceUse();
|
||||
}
|
||||
|
||||
public void ForceUse()
|
||||
{
|
||||
DebugTools.Assert(Handle != 0);
|
||||
|
||||
_clyde._currentProgram = this;
|
||||
GL.UseProgram(Handle);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
if (Handle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GL.DeleteProgram(Handle);
|
||||
_clyde.CheckGlError();
|
||||
Handle = 0;
|
||||
}
|
||||
|
||||
public int GetUniform(string name)
|
||||
{
|
||||
if (!TryGetUniform(name, out var result))
|
||||
{
|
||||
ThrowCouldNotGetUniform(name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int GetUniform(int id)
|
||||
{
|
||||
if (!TryGetUniform(id, out var result))
|
||||
{
|
||||
ThrowCouldNotGetUniform($"[id {id}]");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool TryGetUniform(string name, out int index)
|
||||
{
|
||||
DebugTools.Assert(Handle != 0);
|
||||
|
||||
if (_uniformCache.TryGetValue(name, out index))
|
||||
{
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
index = GL.GetUniformLocation(Handle, name);
|
||||
_clyde.CheckGlError();
|
||||
_uniformCache.Add(name, index);
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
public bool TryGetUniform(int id, out int index)
|
||||
{
|
||||
DebugTools.Assert(Handle != 0);
|
||||
DebugTools.Assert(id < UniCount);
|
||||
|
||||
var value = _uniformIntCache[id];
|
||||
if (value.HasValue)
|
||||
{
|
||||
index = value.Value;
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
return InitIntUniform(id, out index);
|
||||
}
|
||||
|
||||
private bool InitIntUniform(int id, out int index)
|
||||
{
|
||||
string name;
|
||||
switch (id)
|
||||
{
|
||||
case UniIModUV:
|
||||
name = UniModUV;
|
||||
break;
|
||||
case UniILightTexture:
|
||||
name = UniLightTexture;
|
||||
break;
|
||||
case UniIMainTexture:
|
||||
name = UniMainTexture;
|
||||
break;
|
||||
case UniIModelMatrix:
|
||||
name = UniModelMatrix;
|
||||
break;
|
||||
case UniITexturePixelSize:
|
||||
name = UniTexturePixelSize;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
index = GL.GetUniformLocation(Handle, name);
|
||||
_clyde.CheckGlError();
|
||||
_uniformIntCache[id] = (sbyte)index;
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
public bool HasUniform(string name) => TryGetUniform(name, out _);
|
||||
public bool HasUniform(int id) => TryGetUniform(id, out _);
|
||||
|
||||
public void BindBlock(string blockName, uint blockBinding)
|
||||
{
|
||||
var index = (uint) GL.GetUniformBlockIndex(Handle, blockName);
|
||||
_clyde.CheckGlError();
|
||||
GL.UniformBlockBinding(Handle, index, blockBinding);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, int integer)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, integer);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, float single)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, single);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, float single)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, single);
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, float[] singles)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, singles.Length, singles);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, float[] singles)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, singles.Length, singles);
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix3x2 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, in Matrix3x2 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void SetUniformDirect(int slot, in Matrix3x2 value)
|
||||
{
|
||||
// We put the rows of the input matrix into the columns of our GPU matrices
|
||||
// this transpose is required, as in C#, we premultiply vectors with matrices
|
||||
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
|
||||
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
|
||||
// memory layout is the same (or would be, if Matrix3x2 didn't have an
|
||||
// implicit column)
|
||||
var buf = stackalloc float[9]{
|
||||
value.M11, value.M12, 0,
|
||||
value.M21, value.M22, 0,
|
||||
value.M31, value.M32, 1
|
||||
};
|
||||
GL.UniformMatrix3(slot, 1, false, (float*)buf);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix4x4 matrix, bool transpose=true)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix, transpose);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void SetUniformDirect(int uniformId, in Matrix4x4 value, bool transpose=true)
|
||||
{
|
||||
Matrix4x4 tmpTranspose = value;
|
||||
if (transpose)
|
||||
{
|
||||
// transposition not supported on GLES2, & no access to _hasGLES
|
||||
tmpTranspose = Matrix4x4.Transpose(value);
|
||||
}
|
||||
GL.UniformMatrix4(uniformId, 1, false, (float*) &tmpTranspose);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Vector4 vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, in Vector4 vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, in Vector4 vector)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Vector4* ptr = &vector)
|
||||
{
|
||||
GL.Uniform4(slot, 1, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Color color, bool convertToLinear = true)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, color, convertToLinear);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, in Color color, bool convertToLinear = true)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, color, convertToLinear);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, in Color color, bool convertToLinear = true)
|
||||
{
|
||||
var converted = color;
|
||||
if (convertToLinear)
|
||||
{
|
||||
converted = Color.FromSrgb(color);
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
GL.Uniform4(slot, 1, (float*) &converted);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, Color[] colors)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, colors);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, Color[] colors, bool convertToLinear = true)
|
||||
{
|
||||
scoped Span<Color> colorsToPass;
|
||||
if (convertToLinear)
|
||||
{
|
||||
colorsToPass = stackalloc Color[colors.Length];
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
colorsToPass[i] = Color.FromSrgb(colors[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
colorsToPass = colors;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (Color* ptr = &colorsToPass[0])
|
||||
{
|
||||
GL.Uniform4(slot, colorsToPass.Length, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Vector3 vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, in Vector3 vector)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Vector3* ptr = &vector)
|
||||
{
|
||||
GL.Uniform3(slot, 1, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Vector2 vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, in Vector2 vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, in Vector2 vector)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Vector2* ptr = &vector)
|
||||
{
|
||||
GL.Uniform2(slot, 1, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, Vector2[] vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, Vector2[] vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, Vector2[] vectors)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Vector2* ptr = &vectors[0])
|
||||
{
|
||||
GL.Uniform2(slot, vectors.Length, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, bool[] bools)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, bools);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, bool[] bools)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, bools);
|
||||
}
|
||||
|
||||
private void SetUniformDirect(int slot, bool[] bools)
|
||||
{
|
||||
Span<int> intBools = stackalloc int[bools.Length];
|
||||
|
||||
for (var i = 0; i < bools.Length; i++)
|
||||
{
|
||||
intBools[i] = bools[i] ? 1 : 0;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (int* intBoolsPtr = intBools)
|
||||
{
|
||||
GL.Uniform1(slot, bools.Length, intBoolsPtr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformTexture(string uniformName, TextureUnit textureUnit)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformTextureDirect(uniformId, textureUnit);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformTextureDirect(int slot, TextureUnit value)
|
||||
{
|
||||
GL.Uniform1(slot, value - TextureUnit.Texture0);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniformTextureMaybe(string uniformName, TextureUnit value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformTextureDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformTextureMaybe(int uniformName, TextureUnit value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformTextureDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Vector4 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(int uniformName, in Vector4 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Color value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(int uniformName, in Color value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Matrix3x2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Matrix4x4 value, bool transpose=true)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value, transpose);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(int uniformName, in Matrix3x2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Vector2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Vector2i value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(int uniformName, in Vector2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
SetUniformDirect(slot, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, int value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
GL.Uniform1(slot, value);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, float value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
GL.Uniform1(slot, value);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCaches()
|
||||
{
|
||||
_uniformCache.Clear();
|
||||
for (var i = 0; i < UniCount; i++)
|
||||
{
|
||||
_uniformIntCache[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowCouldNotGetUniform(string name)
|
||||
{
|
||||
throw new ArgumentException($"Could not get uniform \"{name}\"!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
internal partial class Clyde
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents some set of uniforms that can be backed by a uniform buffer or by regular uniforms.
|
||||
/// Implies you're using this on a properly setup struct.
|
||||
/// </summary>
|
||||
private interface IAppliableUniformSet
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the uniform set directly to a program.
|
||||
/// </summary>
|
||||
void Apply(Clyde clyde, GLShaderProgram program);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents some set of uniforms that can be backed by a uniform buffer or by regular uniforms.
|
||||
/// </summary>
|
||||
private sealed class GLUniformBuffer<T> where T : unmanaged, IAppliableUniformSet
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly int _index;
|
||||
|
||||
/// <summary>
|
||||
/// GPU Buffer (only used when uniform buffers are available)
|
||||
/// </summary>
|
||||
private GLBuffer? _implUBO = null;
|
||||
|
||||
/// <summary>
|
||||
/// Mirror (only used when uniform buffers are unavailable)
|
||||
/// </summary>
|
||||
private T _implMirror;
|
||||
|
||||
public GLUniformBuffer(Clyde clyde, int index, string? name = null)
|
||||
{
|
||||
_clyde = clyde;
|
||||
_index = index;
|
||||
if (_clyde._hasGLUniformBuffers)
|
||||
{
|
||||
_implUBO = new GLBuffer(_clyde, BufferTarget.UniformBuffer, BufferUsageHint.StreamDraw, name);
|
||||
unsafe {
|
||||
_implUBO.Reallocate(sizeof(T));
|
||||
}
|
||||
|
||||
Rebind();
|
||||
}
|
||||
}
|
||||
|
||||
public void Rebind()
|
||||
{
|
||||
if (_implUBO != null)
|
||||
{
|
||||
GL.BindBufferBase(BufferRangeTarget.UniformBuffer, _index, (int) _implUBO!.ObjectHandle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the buffer contents.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reallocate(in T data)
|
||||
{
|
||||
if (_implUBO != null)
|
||||
{
|
||||
_implUBO.Reallocate(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_implMirror = data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is important for use on GLES2 - it ensures the uniforms in the specific program are up-to-date.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Apply(GLShaderProgram program)
|
||||
{
|
||||
if (_implUBO == null)
|
||||
{
|
||||
_implMirror.Apply(_clyde, program);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Robust.Client/Graphics/Clyde/GpuExpansionBuffer.cs
Normal file
140
Robust.Client/Graphics/Clyde/GpuExpansionBuffer.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde;
|
||||
|
||||
internal sealed class GpuExpansionBuffer : IDisposable
|
||||
{
|
||||
private readonly RhiBase _rhi;
|
||||
private readonly RhiBufferUsageFlags _usage;
|
||||
private readonly string? _label;
|
||||
|
||||
private readonly byte[] _tempBuffer;
|
||||
|
||||
private int IndividualBufferSize => _tempBuffer.Length;
|
||||
|
||||
private ValueList<RhiBuffer> _gpuBuffers;
|
||||
private int _curBufferPos;
|
||||
private int _curBufferIdx;
|
||||
|
||||
public GpuExpansionBuffer(
|
||||
RhiBase rhi,
|
||||
int individualBufferSize,
|
||||
RhiBufferUsageFlags usage,
|
||||
string? label = null)
|
||||
{
|
||||
if ((usage & RhiBufferUsageFlags.CopyDst) == 0)
|
||||
throw new ArgumentException($"Buffer usages must include {nameof(RhiBufferUsageFlags.CopyDst)}");
|
||||
|
||||
_rhi = rhi;
|
||||
_usage = usage;
|
||||
_label = label;
|
||||
_tempBuffer = new byte[individualBufferSize];
|
||||
|
||||
// Make sure we have at least one buffer to start with.
|
||||
AllocateBuffer();
|
||||
DebugTools.Assert(_curBufferIdx < _gpuBuffers.Count, "Must have one buffer after creation");
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
if (_curBufferPos == 0)
|
||||
return;
|
||||
|
||||
FlushCurrentBuffer();
|
||||
_curBufferIdx = 0;
|
||||
|
||||
// TODO: automatically cull GPU buffers if they remain unused for many frames.
|
||||
}
|
||||
|
||||
// TODO: Verify this is safe to expose to content.
|
||||
public Span<T> Allocate<T>(int count, out Position position)
|
||||
where T : unmanaged
|
||||
{
|
||||
return AllocateAligned<T>(count, 1, out position);
|
||||
}
|
||||
|
||||
// TODO: Verify this is safe to expose to content.
|
||||
public unsafe Span<T> AllocateAligned<T>(int count, int alignment, out Position position)
|
||||
where T : unmanaged
|
||||
{
|
||||
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
|
||||
throw new TypeArgumentException();
|
||||
|
||||
var length = checked(count * sizeof(T));
|
||||
if (length > IndividualBufferSize)
|
||||
throw new ArgumentException("Requested allocation is larger than what could fit inside a single buffer.");
|
||||
|
||||
var posRounded = MathHelper.CeilingPowerOfTwo(_curBufferPos, alignment);
|
||||
if (posRounded + length > IndividualBufferSize)
|
||||
{
|
||||
FlushCurrentBuffer();
|
||||
|
||||
DebugTools.Assert(_curBufferPos == 0, "We must have a fresh buffer now");
|
||||
|
||||
posRounded = 0;
|
||||
}
|
||||
|
||||
position = new Position(_gpuBuffers[_curBufferIdx], posRounded);
|
||||
|
||||
var byteSpan = _tempBuffer.AsSpan(posRounded, length);
|
||||
_curBufferPos = posRounded + length;
|
||||
var itemSpan = MemoryMarshal.Cast<byte, T>(byteSpan);
|
||||
|
||||
DebugTools.Assert(itemSpan.Length == count);
|
||||
DebugTools.Assert(_curBufferPos <= _tempBuffer.Length);
|
||||
|
||||
return itemSpan;
|
||||
}
|
||||
|
||||
private void FlushCurrentBuffer()
|
||||
{
|
||||
DebugTools.Assert(_curBufferPos != 0, "We have nothing written, flushing the buffer makes no sense!");
|
||||
|
||||
// Temp buffer full. Upload buffer to GPU
|
||||
var curBuffer = _gpuBuffers[_curBufferIdx];
|
||||
_rhi.Queue.WriteBuffer(curBuffer, 0, _tempBuffer.AsSpan(0, _curBufferPos));
|
||||
|
||||
_curBufferIdx += 1;
|
||||
if (_curBufferIdx == _gpuBuffers.Count)
|
||||
{
|
||||
// Out of spare buffers, make a new one.
|
||||
AllocateBuffer();
|
||||
DebugTools.Assert(_curBufferIdx < _gpuBuffers.Count, "Must have one buffer after creation");
|
||||
}
|
||||
|
||||
_curBufferPos = 0;
|
||||
}
|
||||
|
||||
private RhiBuffer AllocateBuffer()
|
||||
{
|
||||
var idx = _gpuBuffers.Count;
|
||||
var buffer = _rhi.CreateBuffer(new RhiBufferDescriptor(
|
||||
(ulong) IndividualBufferSize,
|
||||
_usage,
|
||||
false,
|
||||
_label == null ? null : $"{_label}-{idx}")
|
||||
);
|
||||
|
||||
_gpuBuffers.Add(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var gpuBuffer in _gpuBuffers)
|
||||
{
|
||||
gpuBuffer.Dispose();
|
||||
}
|
||||
|
||||
_gpuBuffers.Clear();
|
||||
}
|
||||
|
||||
public record struct Position(RhiBuffer Buffer, int ByteOffset);
|
||||
}
|
||||
106
Robust.Client/Graphics/Clyde/Rhi/Rhi.Internal.cs
Normal file
106
Robust.Client/Graphics/Clyde/Rhi/Rhi.Internal.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
public abstract partial class RhiBase
|
||||
{
|
||||
//
|
||||
// Clyde <-> RHI API.
|
||||
//
|
||||
|
||||
internal abstract void Init();
|
||||
internal abstract void Shutdown();
|
||||
|
||||
/// <summary>
|
||||
/// A window was created by Clyde. It should be initialized by the RHI to make it ready for rendering.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not get called for the main window.
|
||||
/// </remarks>
|
||||
internal abstract void WindowCreated(Clyde.WindowReg reg);
|
||||
|
||||
/// <summary>
|
||||
/// A window is about to be destroyed by Clyde. Clean up resources for it.
|
||||
/// </summary>
|
||||
internal abstract void WindowDestroy(Clyde.WindowReg reg);
|
||||
|
||||
/// <summary>
|
||||
/// Recreate the native swap chain, in case it has become suboptimal (e.g. due to window resizing).
|
||||
/// </summary>
|
||||
internal abstract void WindowRecreateSwapchain(Clyde.WindowReg reg);
|
||||
|
||||
internal abstract RhiTextureView CreateTextureViewForWindow(Clyde.WindowReg reg);
|
||||
internal abstract void WindowPresent(Clyde.WindowReg reg);
|
||||
|
||||
//
|
||||
// RHI-internal API to de-OOP the public RHI API.
|
||||
//
|
||||
|
||||
internal abstract RhiRenderPassEncoder CommandEncoderBeginRenderPass(
|
||||
RhiCommandEncoder encoder,
|
||||
in RhiRenderPassDescriptor descriptor
|
||||
);
|
||||
|
||||
internal abstract RhiCommandBuffer CommandEncoderFinish(in RhiCommandEncoder encoder,
|
||||
in RhiCommandBufferDescriptor descriptor);
|
||||
|
||||
internal abstract void RenderPassEncoderSetPipeline(
|
||||
in RhiRenderPassEncoder encoder,
|
||||
RhiRenderPipeline pipeline
|
||||
);
|
||||
|
||||
internal abstract void RenderPassEncoderDraw(
|
||||
in RhiRenderPassEncoder encoder,
|
||||
uint vertexCount,
|
||||
uint instanceCount,
|
||||
uint firstVertex,
|
||||
uint firstInstance
|
||||
);
|
||||
|
||||
internal abstract void RenderPassEncoderEnd(RhiRenderPassEncoder encoder);
|
||||
|
||||
internal abstract void QueueSubmit(RhiQueue queue, RhiCommandBuffer[] commandBuffers);
|
||||
|
||||
internal abstract void QueueWriteTexture(
|
||||
RhiQueue queue,
|
||||
in RhiImageCopyTexture destination,
|
||||
ReadOnlySpan<byte> data,
|
||||
in RhiImageDataLayout dataLayout,
|
||||
RhiExtent3D size
|
||||
);
|
||||
|
||||
public abstract void QueueWriteBuffer(RhiBuffer buffer, ulong bufferOffset, ReadOnlySpan<byte> data);
|
||||
|
||||
internal abstract RhiTextureView TextureCreateView(RhiTexture texture, in RhiTextureViewDescriptor descriptor);
|
||||
internal abstract void TextureViewDrop(RhiTextureView textureView);
|
||||
internal abstract void BindGroupDrop(RhiBindGroup rhiBindGroup);
|
||||
|
||||
internal abstract void RenderPassEncoderSetBindGroup(
|
||||
RhiRenderPassEncoder encoder,
|
||||
uint index,
|
||||
RhiBindGroup? bindGroup
|
||||
);
|
||||
|
||||
internal abstract void RenderPassEncoderSetVertexBuffer(RhiRenderPassEncoder encoder,
|
||||
uint slot,
|
||||
RhiBuffer? buffer,
|
||||
ulong offset,
|
||||
ulong? size);
|
||||
|
||||
internal abstract void RenderPassEncoderSetScissorRect(RhiRenderPassEncoder encoder,
|
||||
uint x,
|
||||
uint y,
|
||||
uint w,
|
||||
uint h);
|
||||
|
||||
internal abstract void CommandBufferDrop(RhiCommandBuffer commandBuffer);
|
||||
|
||||
internal abstract RhiBufferMapState BufferGetMapState(RhiBuffer buffer);
|
||||
internal abstract ValueTask BufferMapAsync(RhiBuffer buffer, RhiMapModeFlags mode, nuint offset, nuint size);
|
||||
internal abstract RhiMappedBufferRange BufferGetMappedRange(RhiBuffer buffer, nuint offset, nuint size);
|
||||
internal abstract void BufferUnmap(RhiBuffer buffer);
|
||||
internal abstract void BufferDrop(RhiBuffer buffer);
|
||||
}
|
||||
|
||||
internal record struct RhiHandle(long Value);
|
||||
31
Robust.Client/Graphics/Clyde/Rhi/Rhi.ShaderTypes.cs
Normal file
31
Robust.Client/Graphics/Clyde/Rhi/Rhi.ShaderTypes.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent to a WGSL <c>mat3x2f</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This matrix is columnar and 3 columns, 2 rows. This is different from the rest of the engine and .NET.
|
||||
/// </remarks>
|
||||
public struct ShaderMat3x2F
|
||||
{
|
||||
public float M11;
|
||||
public float M12;
|
||||
public float M21;
|
||||
public float M22;
|
||||
public float M31;
|
||||
public float M32;
|
||||
|
||||
public static ShaderMat3x2F Transpose(in Matrix3x2 matrix)
|
||||
{
|
||||
var ret = default(ShaderMat3x2F);
|
||||
ret.M11 = matrix.M11;
|
||||
ret.M12 = matrix.M12;
|
||||
ret.M21 = matrix.M21;
|
||||
ret.M22 = matrix.M22;
|
||||
ret.M31 = matrix.M31;
|
||||
ret.M32 = matrix.M32;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
1182
Robust.Client/Graphics/Clyde/Rhi/Rhi.cs
Normal file
1182
Robust.Client/Graphics/Clyde/Rhi/Rhi.cs
Normal file
File diff suppressed because it is too large
Load Diff
117
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.BindGroup.cs
Normal file
117
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.BindGroup.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, BindGroupLayoutReg> _bindGroupLayoutRegistry = new();
|
||||
private readonly Dictionary<RhiHandle, BindGroupReg> _bindGroupRegistry = new();
|
||||
|
||||
|
||||
internal override void BindGroupDrop(RhiBindGroup rhiBindGroup)
|
||||
{
|
||||
_wgpu.BindGroupDrop(_bindGroupRegistry[rhiBindGroup.Handle].Native);
|
||||
_bindGroupRegistry.Remove(rhiBindGroup.Handle);
|
||||
}
|
||||
|
||||
public override RhiBindGroupLayout CreateBindGroupLayout(in RhiBindGroupLayoutDescriptor descriptor)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[512];
|
||||
|
||||
var pDescriptor = BumpAllocate<BindGroupLayoutDescriptor>(ref buffer);
|
||||
pDescriptor->Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
var entries = descriptor.Entries;
|
||||
pDescriptor->EntryCount = (uint)entries.Length;
|
||||
pDescriptor->Entries = BumpAllocate<BindGroupLayoutEntry>(ref buffer, entries.Length);
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
{
|
||||
ref var entry = ref entries[i];
|
||||
var pEntry = &pDescriptor->Entries[i];
|
||||
|
||||
pEntry->Binding = entry.Binding;
|
||||
pEntry->Visibility = (ShaderStage)entry.Visibility;
|
||||
|
||||
switch (entry.Layout)
|
||||
{
|
||||
case RhiSamplerBindingLayout sampler:
|
||||
pEntry->Sampler.Type = (SamplerBindingType)sampler.Type;
|
||||
break;
|
||||
case RhiTextureBindingLayout texture:
|
||||
pEntry->Texture.Multisampled = texture.Multisampled;
|
||||
pEntry->Texture.SampleType = (TextureSampleType)texture.SampleType;
|
||||
pEntry->Texture.ViewDimension =
|
||||
(TextureViewDimension)ValidateTextureViewDimension(texture.ViewDimension);
|
||||
break;
|
||||
case RhiBufferBindingLayout layoutBuffer:
|
||||
pEntry->Buffer.Type = (BufferBindingType) layoutBuffer.Type;
|
||||
pEntry->Buffer.HasDynamicOffset = layoutBuffer.HasDynamicOffset;
|
||||
pEntry->Buffer.MinBindingSize = layoutBuffer.MinBindingSize;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
var native = _webGpu.DeviceCreateBindGroupLayout(_wgpuDevice, pDescriptor);
|
||||
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_bindGroupLayoutRegistry.Add(handle, new BindGroupLayoutReg { Native = native });
|
||||
return new RhiBindGroupLayout(this, handle);
|
||||
}
|
||||
|
||||
public override RhiBindGroup CreateBindGroup(in RhiBindGroupDescriptor descriptor)
|
||||
{
|
||||
// TODO: SAFETY
|
||||
Span<byte> buffer = stackalloc byte[1024];
|
||||
|
||||
var pDescriptor = BumpAllocate<BindGroupDescriptor>(ref buffer);
|
||||
pDescriptor->Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
pDescriptor->Layout = _bindGroupLayoutRegistry[descriptor.Layout.Handle].Native;
|
||||
|
||||
var entries = descriptor.Entries;
|
||||
pDescriptor->EntryCount = (uint) entries.Length;
|
||||
pDescriptor->Entries = BumpAllocate<BindGroupEntry>(ref buffer, entries.Length);
|
||||
for (var i = 0; i < entries.Length; i++)
|
||||
{
|
||||
ref var entry = ref descriptor.Entries[i];
|
||||
var pEntry = &pDescriptor->Entries[i];
|
||||
|
||||
pEntry->Binding = entry.Binding;
|
||||
switch (entry.Resource)
|
||||
{
|
||||
case RhiSampler rhiSampler:
|
||||
pEntry->Sampler = _samplerRegistry[rhiSampler.Handle].Native;
|
||||
break;
|
||||
case RhiTextureView rhiTextureView:
|
||||
pEntry->TextureView = _textureViewRegistry[rhiTextureView.Handle].Native;
|
||||
break;
|
||||
case RhiBufferBinding bufferBinding:
|
||||
pEntry->Buffer = _bufferRegistry[bufferBinding.Buffer.Handle].Native;
|
||||
pEntry->Offset = bufferBinding.Offset;
|
||||
pEntry->Size = bufferBinding.Size ?? WebGPU.WholeSize;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
var bindGroup = _webGpu.DeviceCreateBindGroup(_wgpuDevice, pDescriptor);
|
||||
|
||||
var handle = AllocRhiHandle();
|
||||
_bindGroupRegistry.Add(handle, new BindGroupReg { Native = bindGroup });
|
||||
return new RhiBindGroup(this, handle);
|
||||
}
|
||||
|
||||
private sealed class BindGroupLayoutReg
|
||||
{
|
||||
public BindGroupLayout* Native;
|
||||
}
|
||||
|
||||
private sealed class BindGroupReg
|
||||
{
|
||||
public BindGroup* Native;
|
||||
}
|
||||
}
|
||||
147
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Buffer.cs
Normal file
147
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Buffer.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Internal.TypeSystem;
|
||||
using Silk.NET.WebGPU;
|
||||
using Buffer = Silk.NET.WebGPU.Buffer;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, BufferReg> _bufferRegistry = new();
|
||||
|
||||
public override unsafe RhiBuffer CreateBuffer(in RhiBufferDescriptor descriptor)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[512];
|
||||
var pDescriptor = BumpAllocate<BufferDescriptor>(ref buffer);
|
||||
pDescriptor->Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
pDescriptor->MappedAtCreation = descriptor.MappedAtCreation;
|
||||
pDescriptor->Size = descriptor.Size;
|
||||
pDescriptor->Usage = (BufferUsage) descriptor.Usage;
|
||||
|
||||
var native = _webGpu.DeviceCreateBuffer(_wgpuDevice, pDescriptor);
|
||||
|
||||
var handle = AllocRhiHandle();
|
||||
_bufferRegistry.Add(handle, new BufferReg { Native = native });
|
||||
var rhiBuffer= new RhiBuffer(this, handle);
|
||||
|
||||
if (pDescriptor->MappedAtCreation)
|
||||
{
|
||||
rhiBuffer.Mapping = new RhiBuffer.ActiveMapping(rhiBuffer) { Valid = true };
|
||||
}
|
||||
|
||||
return rhiBuffer;
|
||||
}
|
||||
|
||||
internal override unsafe RhiBufferMapState BufferGetMapState(RhiBuffer buffer)
|
||||
{
|
||||
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
|
||||
return (RhiBufferMapState) _webGpu.BufferGetMapState(nativeBuffer);
|
||||
}
|
||||
|
||||
internal override async ValueTask BufferMapAsync(RhiBuffer buffer, RhiMapModeFlags mode, nuint offset, nuint size)
|
||||
{
|
||||
// TODO: Probably need some more locks here idk.
|
||||
// So people can't map the buffer at the same time as or something.
|
||||
|
||||
buffer.MapState = RhiBufferMapState.Pending;
|
||||
|
||||
WgpuMapBufferAsyncResult result;
|
||||
using (var promise = new WgpuPromise<WgpuMapBufferAsyncResult>())
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
|
||||
|
||||
_webGpu.BufferMapAsync(
|
||||
nativeBuffer,
|
||||
(MapMode) mode,
|
||||
offset,
|
||||
size,
|
||||
new PfnBufferMapCallback(&WgpuMapBufferAsyncCallback),
|
||||
promise.UserData
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: are we handling the error correctly, here?
|
||||
result = await promise.Task;
|
||||
|
||||
buffer.Mapping = new RhiBuffer.ActiveMapping(buffer) { Valid = true };
|
||||
}
|
||||
|
||||
if (result.Status != BufferMapAsyncStatus.Success)
|
||||
throw new RhiException(result.Status.ToString());
|
||||
|
||||
buffer.MapState = RhiBufferMapState.Mapped;
|
||||
}
|
||||
|
||||
internal override unsafe RhiMappedBufferRange BufferGetMappedRange(RhiBuffer buffer, nuint offset, nuint size)
|
||||
{
|
||||
if (size > int.MaxValue)
|
||||
throw new ArgumentException("Mapped area too big!");
|
||||
|
||||
if (buffer.Mapping == null)
|
||||
throw new InvalidOperationException("Buffer is not mapped");
|
||||
|
||||
lock (buffer.Mapping)
|
||||
{
|
||||
if (!buffer.Mapping.Valid)
|
||||
{
|
||||
// Not sure if this is possible, but can't hurt.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
|
||||
var mapped = _webGpu.BufferGetMappedRange(nativeBuffer, offset, size);
|
||||
|
||||
return new RhiMappedBufferRange(buffer.Mapping, mapped, (int) size);
|
||||
}
|
||||
}
|
||||
|
||||
internal override unsafe void BufferUnmap(RhiBuffer buffer)
|
||||
{
|
||||
if (buffer.Mapping == null)
|
||||
throw new InvalidOperationException("Buffer is not mapped!");
|
||||
|
||||
lock (buffer.Mapping)
|
||||
{
|
||||
if (!buffer.Mapping.Valid)
|
||||
{
|
||||
// Not sure if this is possible, but can't hurt.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (buffer.Mapping.ActiveSpans > 0)
|
||||
throw new InvalidOperationException("Current thread has buffer accessible as span, cannot unmap!");
|
||||
|
||||
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
|
||||
_webGpu.BufferUnmap(nativeBuffer);
|
||||
|
||||
buffer.Mapping.Valid = false;
|
||||
buffer.Mapping = null;
|
||||
buffer.MapState = RhiBufferMapState.Unmapped;
|
||||
}
|
||||
}
|
||||
|
||||
internal override unsafe void BufferDrop(RhiBuffer buffer)
|
||||
{
|
||||
_wgpu.BufferDrop(_bufferRegistry[buffer.Handle].Native);
|
||||
_bufferRegistry.Remove(buffer.Handle);
|
||||
}
|
||||
|
||||
private sealed unsafe class BufferReg
|
||||
{
|
||||
public Buffer* Native;
|
||||
}
|
||||
|
||||
private record struct WgpuMapBufferAsyncResult(BufferMapAsyncStatus Status);
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static unsafe void WgpuMapBufferAsyncCallback(BufferMapAsyncStatus status, void* userdata)
|
||||
{
|
||||
WgpuPromise<WgpuMapBufferAsyncResult>.SetResult(userdata, new WgpuMapBufferAsyncResult(status));
|
||||
}
|
||||
}
|
||||
24
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.CommandBuffer.cs
Normal file
24
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.CommandBuffer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, CommandBufferReg> _commandBufferRegistry = new();
|
||||
|
||||
/// <summary>
|
||||
/// Command buffer was dropped natively, either via explicit call or implicit side effect (e.g. queue submit).
|
||||
/// </summary>
|
||||
private void CommandBufferDropped(RhiCommandBuffer commandBuffer)
|
||||
{
|
||||
_commandBufferRegistry.Remove(commandBuffer.Handle);
|
||||
GC.SuppressFinalize(commandBuffer);
|
||||
}
|
||||
|
||||
internal override void CommandBufferDrop(RhiCommandBuffer commandBuffer)
|
||||
{
|
||||
_wgpu.CommandBufferDrop(_commandBufferRegistry[commandBuffer.Handle].Native);
|
||||
CommandBufferDropped(commandBuffer);
|
||||
}
|
||||
}
|
||||
225
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.CommandEncoder.cs
Normal file
225
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.CommandEncoder.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.WebGPU;
|
||||
using Buffer = Silk.NET.WebGPU.Buffer;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, CommandEncoderReg> _commandEncoderRegistry = new();
|
||||
private readonly Dictionary<RhiHandle, RenderPassEncoderReg> _renderPassEncoderRegistry = new();
|
||||
|
||||
public override RhiCommandEncoder CreateCommandEncoder(in RhiCommandEncoderDescriptor descriptor)
|
||||
{
|
||||
CommandEncoder* nativeEncoder;
|
||||
fixed (byte* pLabel = MakeLabel(descriptor.Label))
|
||||
{
|
||||
var nativeDescriptor = new CommandEncoderDescriptor
|
||||
{
|
||||
Label = pLabel
|
||||
};
|
||||
|
||||
nativeEncoder = _webGpu.DeviceCreateCommandEncoder(_wgpuDevice, &nativeDescriptor);
|
||||
}
|
||||
|
||||
// TODO: thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_commandEncoderRegistry.Add(handle, new CommandEncoderReg { Native = nativeEncoder });
|
||||
return new RhiCommandEncoder(this, handle);
|
||||
}
|
||||
|
||||
internal override RhiRenderPassEncoder CommandEncoderBeginRenderPass(
|
||||
RhiCommandEncoder encoder,
|
||||
in RhiRenderPassDescriptor descriptor)
|
||||
{
|
||||
// TODO: Ensure not disposed
|
||||
// TODO: Thread safety
|
||||
|
||||
Span<byte> buffer = stackalloc byte[512];
|
||||
|
||||
var pDescriptor = BumpAllocate<RenderPassDescriptor>(ref buffer);
|
||||
pDescriptor->Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
|
||||
var colorAttachments = descriptor.ColorAttachments;
|
||||
pDescriptor->ColorAttachmentCount = (uint)colorAttachments.Length;
|
||||
pDescriptor->ColorAttachments = BumpAllocate<RenderPassColorAttachment>(ref buffer, colorAttachments.Length);
|
||||
for (var i = 0; i < colorAttachments.Length; i++)
|
||||
{
|
||||
ref var attachment = ref colorAttachments[i];
|
||||
var pAttachment = &pDescriptor->ColorAttachments[i];
|
||||
pAttachment->View = _textureViewRegistry[attachment.View.Handle].Native;
|
||||
if (attachment.ResolveTarget is { } resolveTarget)
|
||||
pAttachment->ResolveTarget = _textureViewRegistry[resolveTarget.Handle].Native;
|
||||
pAttachment->ClearValue = WgpuColor(attachment.ClearValue);
|
||||
pAttachment->LoadOp = (LoadOp)attachment.LoadOp;
|
||||
pAttachment->StoreOp = (StoreOp)attachment.StoreOp;
|
||||
}
|
||||
|
||||
if (descriptor.DepthStencilAttachment is { } depthStencilAttachment)
|
||||
{
|
||||
var pDepthStencilAttachment = BumpAllocate<RenderPassDepthStencilAttachment>(ref buffer);
|
||||
pDescriptor->DepthStencilAttachment = pDepthStencilAttachment;
|
||||
|
||||
pDepthStencilAttachment->View = _textureViewRegistry[depthStencilAttachment.View.Handle].Native;
|
||||
pDepthStencilAttachment->DepthLoadOp = (LoadOp)depthStencilAttachment.DepthLoadOp;
|
||||
pDepthStencilAttachment->DepthStoreOp = (StoreOp)depthStencilAttachment.DepthStoreOp;
|
||||
pDepthStencilAttachment->DepthClearValue = depthStencilAttachment.DepthClearValue;
|
||||
pDepthStencilAttachment->DepthReadOnly = depthStencilAttachment.DepthReadOnly;
|
||||
pDepthStencilAttachment->StencilLoadOp = (LoadOp)depthStencilAttachment.StencilLoadOp;
|
||||
pDepthStencilAttachment->StencilStoreOp = (StoreOp)depthStencilAttachment.StencilStoreOp;
|
||||
pDepthStencilAttachment->StencilClearValue = depthStencilAttachment.StencilClearValue;
|
||||
pDepthStencilAttachment->StencilReadOnly = depthStencilAttachment.StencilReadOnly;
|
||||
}
|
||||
|
||||
if (descriptor.OcclusionQuerySet != null)
|
||||
throw new NotImplementedException();
|
||||
|
||||
var pDescriptorMaxDrawCount = BumpAllocate<RenderPassDescriptorMaxDrawCount>(ref buffer);
|
||||
pDescriptor->NextInChain = (ChainedStruct*)pDescriptorMaxDrawCount;
|
||||
pDescriptorMaxDrawCount->Chain.SType = SType.RenderPassDescriptorMaxDrawCount;
|
||||
pDescriptorMaxDrawCount->MaxDrawCount = descriptor.MaxDrawCount;
|
||||
|
||||
var nativeEncoder = _webGpu.CommandEncoderBeginRenderPass(
|
||||
_commandEncoderRegistry[encoder.Handle].Native,
|
||||
pDescriptor
|
||||
);
|
||||
|
||||
// TODO: thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_renderPassEncoderRegistry.Add(handle, new RenderPassEncoderReg { Native = nativeEncoder });
|
||||
return new RhiRenderPassEncoder(this, handle);
|
||||
}
|
||||
|
||||
internal override void RenderPassEncoderSetPipeline(
|
||||
in RhiRenderPassEncoder encoder,
|
||||
RhiRenderPipeline pipeline)
|
||||
{
|
||||
// TODO: safety
|
||||
_webGpu.RenderPassEncoderSetPipeline(
|
||||
_renderPassEncoderRegistry[encoder.Handle].Native,
|
||||
_renderPipelineRegistry[pipeline.Handle].Native
|
||||
);
|
||||
}
|
||||
|
||||
internal override void RenderPassEncoderDraw(
|
||||
in RhiRenderPassEncoder encoder,
|
||||
uint vertexCount,
|
||||
uint instanceCount,
|
||||
uint firstVertex,
|
||||
uint firstInstance)
|
||||
{
|
||||
// TODO: safety
|
||||
_webGpu.RenderPassEncoderDraw(
|
||||
_renderPassEncoderRegistry[encoder.Handle].Native,
|
||||
vertexCount,
|
||||
instanceCount,
|
||||
firstVertex,
|
||||
firstInstance
|
||||
);
|
||||
}
|
||||
|
||||
internal override void RenderPassEncoderEnd(RhiRenderPassEncoder encoder)
|
||||
{
|
||||
// TODO: safety
|
||||
var handle = encoder.Handle;
|
||||
|
||||
_webGpu.RenderPassEncoderEnd(_renderPassEncoderRegistry[handle].Native);
|
||||
RenderPassEncoderDropped(handle);
|
||||
}
|
||||
|
||||
internal override void RenderPassEncoderSetBindGroup(
|
||||
RhiRenderPassEncoder encoder,
|
||||
uint index,
|
||||
RhiBindGroup? bindGroup)
|
||||
{
|
||||
_webGpu.RenderPassEncoderSetBindGroup(
|
||||
_renderPassEncoderRegistry[encoder.Handle].Native,
|
||||
index,
|
||||
_bindGroupRegistry[bindGroup!.Handle].Native,
|
||||
0, null
|
||||
);
|
||||
}
|
||||
|
||||
internal override void RenderPassEncoderSetVertexBuffer(
|
||||
RhiRenderPassEncoder encoder,
|
||||
uint slot,
|
||||
RhiBuffer? buffer,
|
||||
ulong offset,
|
||||
ulong? size)
|
||||
{
|
||||
Buffer* nativeBuffer = null;
|
||||
if (buffer != null)
|
||||
nativeBuffer = _bufferRegistry[buffer.Handle].Native;
|
||||
|
||||
_webGpu.RenderPassEncoderSetVertexBuffer(
|
||||
_renderPassEncoderRegistry[encoder.Handle].Native,
|
||||
slot,
|
||||
nativeBuffer,
|
||||
offset,
|
||||
size ?? WebGPU.WholeSize
|
||||
);
|
||||
}
|
||||
|
||||
internal override void RenderPassEncoderSetScissorRect(
|
||||
RhiRenderPassEncoder encoder,
|
||||
uint x, uint y, uint w, uint h)
|
||||
{
|
||||
// TODO: safety
|
||||
_webGpu.RenderPassEncoderSetScissorRect(
|
||||
_renderPassEncoderRegistry[encoder.Handle].Native,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h
|
||||
);
|
||||
}
|
||||
|
||||
internal override RhiCommandBuffer CommandEncoderFinish(
|
||||
in RhiCommandEncoder encoder,
|
||||
in RhiCommandBufferDescriptor descriptor)
|
||||
{
|
||||
// TODO: safety
|
||||
var handle = encoder.Handle;
|
||||
|
||||
Span<byte> buffer = stackalloc byte[512];
|
||||
var pDescriptor = BumpAllocate<CommandBufferDescriptor>(ref buffer);
|
||||
pDescriptor->Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
|
||||
var nativeBuffer = _webGpu.CommandEncoderFinish(
|
||||
_commandEncoderRegistry[handle].Native,
|
||||
pDescriptor
|
||||
);
|
||||
|
||||
CommandEncoderDropped(handle);
|
||||
|
||||
var bufferHandle = AllocRhiHandle();
|
||||
_commandBufferRegistry.Add(bufferHandle, new CommandBufferReg { Native = nativeBuffer });
|
||||
return new RhiCommandBuffer(this, bufferHandle);
|
||||
}
|
||||
|
||||
private void CommandEncoderDropped(RhiHandle encoder)
|
||||
{
|
||||
_commandEncoderRegistry.Remove(encoder);
|
||||
}
|
||||
|
||||
private void RenderPassEncoderDropped(RhiHandle encoder)
|
||||
{
|
||||
_renderPassEncoderRegistry.Remove(encoder);
|
||||
}
|
||||
|
||||
private sealed class CommandEncoderReg
|
||||
{
|
||||
public CommandEncoder* Native;
|
||||
}
|
||||
|
||||
private sealed class RenderPassEncoderReg
|
||||
{
|
||||
public RenderPassEncoder* Native;
|
||||
}
|
||||
|
||||
private sealed class CommandBufferReg
|
||||
{
|
||||
public CommandBuffer* Native;
|
||||
}
|
||||
}
|
||||
12
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Drop.cs
Normal file
12
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Drop.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
// wgpu-native and Dawn can't agree on how resources should be dropped
|
||||
// Dawn wants reference counting, wgpu doesn't.
|
||||
// This will (in the future) abstract the two.
|
||||
|
||||
private void WgpuDropTextureView(TextureView* tv) => _wgpu.TextureViewDrop(tv);
|
||||
}
|
||||
239
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Helpers.cs
Normal file
239
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Helpers.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Maths;
|
||||
using Silk.NET.WebGPU;
|
||||
using Color = Silk.NET.WebGPU.Color;
|
||||
using RColor = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private static string WgpuVersionToString(uint version)
|
||||
{
|
||||
var a = (version >> 24) & 0xFF;
|
||||
var b = (version >> 16) & 0xFF;
|
||||
var c = (version >> 08) & 0xFF;
|
||||
var d = (version >> 00) & 0xFF;
|
||||
|
||||
return $"{a}.{b}.{c}.{d}";
|
||||
}
|
||||
|
||||
private static Color WgpuColor(RhiColor color) => new()
|
||||
{
|
||||
R = color.R,
|
||||
G = color.G,
|
||||
B = color.B,
|
||||
A = color.A
|
||||
};
|
||||
|
||||
private static Extent3D WgpuExtent3D(RhiExtent3D extent)
|
||||
{
|
||||
return new Extent3D
|
||||
{
|
||||
Width = extent.Width,
|
||||
Height = extent.Height,
|
||||
DepthOrArrayLayers = extent.Depth
|
||||
};
|
||||
}
|
||||
|
||||
private static Origin3D WgpuOrigin3D(RhiOrigin3D origin)
|
||||
{
|
||||
return new Origin3D
|
||||
{
|
||||
X = origin.X,
|
||||
Y = origin.Y,
|
||||
Z = origin.Z
|
||||
};
|
||||
}
|
||||
|
||||
private static RhiTextureFormat ValidateTextureFormat(RhiTextureFormat format)
|
||||
{
|
||||
if (format is 0 or >= RhiTextureFormat.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiTextureFormat)}");
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
private static RhiTextureDimension ValidateTextureDimension(RhiTextureDimension dimension)
|
||||
{
|
||||
if (dimension > RhiTextureDimension.Dim3D)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiTextureDimension)}");
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private static RhiTextureUsage ValidateTextureUsage(RhiTextureUsage usage)
|
||||
{
|
||||
if (usage >= RhiTextureUsage.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiTextureUsage)}");
|
||||
|
||||
return usage;
|
||||
}
|
||||
|
||||
private static RhiTextureViewDimension ValidateTextureViewDimension(RhiTextureViewDimension dimension)
|
||||
{
|
||||
if (dimension >= RhiTextureViewDimension.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiTextureViewDimension)}");
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
private static RhiTextureAspect ValidateTextureAspect(RhiTextureAspect aspect)
|
||||
{
|
||||
if (aspect >= RhiTextureAspect.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiTextureAspect)}");
|
||||
|
||||
return aspect;
|
||||
}
|
||||
|
||||
private static RhiAddressMode ValidateAddressMode(RhiAddressMode addressMode)
|
||||
{
|
||||
if (addressMode >= RhiAddressMode.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiAddressMode)}");
|
||||
|
||||
return addressMode;
|
||||
}
|
||||
|
||||
private static RhiFilterMode ValidateFilterMode(RhiFilterMode filterMode)
|
||||
{
|
||||
if (filterMode >= RhiFilterMode.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiFilterMode)}");
|
||||
|
||||
return filterMode;
|
||||
}
|
||||
|
||||
private static RhiMipmapFilterMode ValidateMipmapFilterMode(RhiMipmapFilterMode mipmapFilterMode)
|
||||
{
|
||||
if (mipmapFilterMode >= RhiMipmapFilterMode.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiMipmapFilterMode)}");
|
||||
|
||||
return mipmapFilterMode;
|
||||
}
|
||||
|
||||
private static RhiCompareFunction ValidateCompareFunction(RhiCompareFunction compareFunction)
|
||||
{
|
||||
if (compareFunction >= RhiCompareFunction.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiCompareFunction)}");
|
||||
|
||||
return compareFunction;
|
||||
}
|
||||
|
||||
private static PowerPreference ValidatePowerPreference(RhiPowerPreference powerPreference)
|
||||
{
|
||||
if (powerPreference >= RhiPowerPreference.Final)
|
||||
throw new ArgumentException($"Invalid {nameof(RhiPowerPreference)}");
|
||||
|
||||
return (PowerPreference) powerPreference;
|
||||
}
|
||||
|
||||
private static string MarshalFromString(byte* str)
|
||||
{
|
||||
return Marshal.PtrToStringUTF8((nint)str)!;
|
||||
}
|
||||
|
||||
[return: NotNullIfNotNull(nameof(label))]
|
||||
private static byte[]? MakeLabel(string? label)
|
||||
{
|
||||
// TODO: Replace with stackalloc
|
||||
|
||||
if (label == null)
|
||||
return null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowBumpAllocOutOfSpace()
|
||||
{
|
||||
throw new InvalidOperationException("Out of bump allocator space!");
|
||||
}
|
||||
|
||||
private sealed class WgpuPromise<TResult> : IDisposable
|
||||
{
|
||||
private readonly TaskCompletionSource<TResult> _tcs;
|
||||
private GCHandle _gcHandle;
|
||||
public Task<TResult> Task => _tcs.Task;
|
||||
public void* UserData => (void*) GCHandle.ToIntPtr(_gcHandle);
|
||||
|
||||
public WgpuPromise()
|
||||
{
|
||||
_tcs = new TaskCompletionSource<TResult>();
|
||||
_gcHandle = GCHandle.Alloc(this);
|
||||
}
|
||||
|
||||
public static void SetResult(void* userdata, TResult result)
|
||||
{
|
||||
var self = (WgpuPromise<TResult>)GCHandle.FromIntPtr((nint) userdata).Target!;
|
||||
self._tcs.SetResult(result);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gcHandle.Free();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Queue.cs
Normal file
88
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Queue.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
public override RhiQueue Queue { get; }
|
||||
|
||||
// queue is ignored as parameter, since WebGPU only supports one queue for now.
|
||||
|
||||
internal override void QueueWriteTexture(
|
||||
RhiQueue queue,
|
||||
in RhiImageCopyTexture destination,
|
||||
ReadOnlySpan<byte> data,
|
||||
in RhiImageDataLayout dataLayout,
|
||||
RhiExtent3D size)
|
||||
{
|
||||
// TODO: Thread safety
|
||||
var nativeTexture = _textureRegistry[destination.Texture.Handle].Native;
|
||||
|
||||
var nativeDestination = new ImageCopyTexture
|
||||
{
|
||||
Aspect = (TextureAspect)ValidateTextureAspect(destination.Aspect),
|
||||
Texture = nativeTexture,
|
||||
Origin = WgpuOrigin3D(destination.Origin),
|
||||
MipLevel = destination.MipLevel
|
||||
};
|
||||
|
||||
var nativeDataLayout = new TextureDataLayout
|
||||
{
|
||||
// TODO: Validation
|
||||
Offset = dataLayout.Offset,
|
||||
BytesPerRow = dataLayout.BytesPerRow,
|
||||
RowsPerImage = dataLayout.RowsPerImage
|
||||
};
|
||||
|
||||
var nativeSize = WgpuExtent3D(size);
|
||||
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
_webGpu.QueueWriteTexture(
|
||||
_wgpuQueue,
|
||||
&nativeDestination,
|
||||
pData, (nuint) data.Length,
|
||||
&nativeDataLayout,
|
||||
&nativeSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override void QueueWriteBuffer(RhiBuffer buffer, ulong bufferOffset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var nativeBuffer = _bufferRegistry[buffer.Handle].Native;
|
||||
|
||||
fixed (byte* pData = data)
|
||||
{
|
||||
_webGpu.QueueWriteBuffer(
|
||||
_wgpuQueue,
|
||||
nativeBuffer,
|
||||
bufferOffset,
|
||||
pData,
|
||||
(nuint) data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal override void QueueSubmit(RhiQueue queue, RhiCommandBuffer[] commandBuffers)
|
||||
{
|
||||
// TODO: Safety
|
||||
|
||||
var pBuffers = stackalloc CommandBuffer*[commandBuffers.Length];
|
||||
for (var i = 0; i < commandBuffers.Length; i++)
|
||||
{
|
||||
pBuffers[i] = _commandBufferRegistry[commandBuffers[i].Handle].Native;
|
||||
}
|
||||
|
||||
_webGpu.QueueSubmit(
|
||||
_wgpuQueue,
|
||||
(uint) commandBuffers.Length,
|
||||
pBuffers
|
||||
);
|
||||
|
||||
foreach (var commandBuffer in commandBuffers)
|
||||
{
|
||||
CommandBufferDropped(commandBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Registry.cs
Normal file
10
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Registry.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed partial class RhiWebGpu
|
||||
{
|
||||
private long _rhiHandleCounter;
|
||||
|
||||
private RhiHandle AllocRhiHandle() => new(Interlocked.Increment(ref _rhiHandleCounter));
|
||||
}
|
||||
232
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.RenderPipeline.cs
Normal file
232
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.RenderPipeline.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, RenderPipelineReg> _renderPipelineRegistry = new();
|
||||
private readonly Dictionary<RhiHandle, PipelineLayoutReg> _pipelineLayoutRegistry = new();
|
||||
|
||||
public override RhiPipelineLayout CreatePipelineLayout(in RhiPipelineLayoutDescriptor descriptor)
|
||||
{
|
||||
// TODO: SAFETY
|
||||
|
||||
Span<byte> buffer = stackalloc byte[128];
|
||||
var pDescriptor = BumpAllocate<PipelineLayoutDescriptor>(ref buffer);
|
||||
pDescriptor->Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
|
||||
var layouts = descriptor.BindGroupLayouts;
|
||||
pDescriptor->BindGroupLayoutCount = (uint) layouts.Length;
|
||||
pDescriptor->BindGroupLayouts = BumpAllocatePtr<BindGroupLayout>(ref buffer, layouts.Length);
|
||||
for (var i = 0; i < layouts.Length; i++)
|
||||
{
|
||||
pDescriptor->BindGroupLayouts[i] = _bindGroupLayoutRegistry[layouts[i].Handle].Native;
|
||||
}
|
||||
|
||||
var native = _webGpu.DeviceCreatePipelineLayout(_wgpuDevice, pDescriptor);
|
||||
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_pipelineLayoutRegistry.Add(handle, new PipelineLayoutReg { Native = native });
|
||||
return new RhiPipelineLayout(this, handle);
|
||||
}
|
||||
|
||||
public override RhiRenderPipeline CreateRenderPipeline(in RhiRenderPipelineDescriptor descriptor)
|
||||
{
|
||||
// TODO: THREAD SAFETY
|
||||
// TODO: INPUT VALIDATION
|
||||
|
||||
var vertexShader = _shaderModuleRegistry[descriptor.Vertex.ProgrammableStage.ShaderModule.Handle].Native;
|
||||
|
||||
const int bufferSize = 8192;
|
||||
var bufferPtr = NativeMemory.AlignedAlloc(bufferSize, 8);
|
||||
|
||||
RenderPipeline* nativePipeline;
|
||||
try
|
||||
{
|
||||
var buffer = new Span<byte>(bufferPtr, bufferSize);
|
||||
|
||||
RenderPipelineDescriptor pipelineDesc = default;
|
||||
pipelineDesc.Label = BumpAllocateUtf8(ref buffer, descriptor.Label);
|
||||
|
||||
// Pipeline layout
|
||||
switch (descriptor.Layout)
|
||||
{
|
||||
case RhiPipelineLayout pipelineLayout:
|
||||
pipelineDesc.Layout = _pipelineLayoutRegistry[pipelineLayout.Handle].Native;
|
||||
break;
|
||||
|
||||
case RhiAutoLayoutMode:
|
||||
throw new NotSupportedException("wgpu does not support auto layout yet");
|
||||
// Default case: no layout given, do nothing
|
||||
}
|
||||
|
||||
// Vertex state
|
||||
pipelineDesc.Vertex.Module = vertexShader;
|
||||
pipelineDesc.Vertex.EntryPoint = BumpAllocateUtf8(
|
||||
ref buffer,
|
||||
descriptor.Vertex.ProgrammableStage.EntryPoint);
|
||||
|
||||
WgpuProgrammableConstants(
|
||||
ref buffer,
|
||||
descriptor.Vertex.ProgrammableStage.Constants,
|
||||
out pipelineDesc.Vertex.ConstantCount,
|
||||
out pipelineDesc.Vertex.Constants);
|
||||
|
||||
var buffers = descriptor.Vertex.Buffers;
|
||||
pipelineDesc.Vertex.BufferCount = (uint)buffers.Length;
|
||||
pipelineDesc.Vertex.Buffers = BumpAllocate<VertexBufferLayout>(ref buffer, buffers.Length);
|
||||
for (var i = 0; i < buffers.Length; i++)
|
||||
{
|
||||
ref var bufferLayout = ref pipelineDesc.Vertex.Buffers[i];
|
||||
bufferLayout.ArrayStride = buffers[i].ArrayStride;
|
||||
bufferLayout.StepMode = (VertexStepMode)buffers[i].StepMode;
|
||||
|
||||
var attributes = buffers[i].Attributes;
|
||||
bufferLayout.AttributeCount = (uint)attributes.Length;
|
||||
bufferLayout.Attributes = BumpAllocate<VertexAttribute>(ref buffer, attributes.Length);
|
||||
for (var j = 0; j < attributes.Length; j++)
|
||||
{
|
||||
ref var attribute = ref bufferLayout.Attributes[j];
|
||||
attribute.Format = (VertexFormat)attributes[j].Format;
|
||||
attribute.Offset = attributes[j].Offset;
|
||||
attribute.ShaderLocation = attributes[j].ShaderLocation;
|
||||
}
|
||||
}
|
||||
|
||||
// Primitive state
|
||||
pipelineDesc.Primitive.Topology = (PrimitiveTopology)descriptor.Primitive.Topology;
|
||||
pipelineDesc.Primitive.StripIndexFormat = (IndexFormat)descriptor.Primitive.StripIndexformat;
|
||||
pipelineDesc.Primitive.FrontFace = (FrontFace)descriptor.Primitive.FrontFace;
|
||||
pipelineDesc.Primitive.CullMode = (CullMode)descriptor.Primitive.CullMode;
|
||||
|
||||
var pPrimitiveDepthClipControl = BumpAllocate<PrimitiveDepthClipControl>(ref buffer);
|
||||
pPrimitiveDepthClipControl->Chain.SType = SType.PrimitiveDepthClipControl;
|
||||
pipelineDesc.Primitive.NextInChain = &pPrimitiveDepthClipControl->Chain;
|
||||
|
||||
pPrimitiveDepthClipControl->UnclippedDepth = descriptor.Primitive.UnclippedDepth;
|
||||
|
||||
// Depth stencil state
|
||||
if (descriptor.DepthStencil is { } depthStencil)
|
||||
{
|
||||
var pDepthStencil = BumpAllocate<DepthStencilState>(ref buffer);
|
||||
pipelineDesc.DepthStencil = pDepthStencil;
|
||||
|
||||
pDepthStencil->Format = (TextureFormat)depthStencil.Format;
|
||||
pDepthStencil->DepthWriteEnabled = depthStencil.DepthWriteEnabled;
|
||||
pDepthStencil->DepthCompare = (CompareFunction)depthStencil.DepthCompare;
|
||||
pDepthStencil->StencilFront = WgpuStencilFaceState(depthStencil.StencilFront ?? new RhiStencilFaceState());
|
||||
pDepthStencil->StencilBack = WgpuStencilFaceState(depthStencil.StencilBack ?? new RhiStencilFaceState());
|
||||
pDepthStencil->StencilReadMask = depthStencil.StencilReadMask;
|
||||
pDepthStencil->StencilWriteMask = depthStencil.StencilWriteMask;
|
||||
pDepthStencil->DepthBias = depthStencil.DepthBias;
|
||||
pDepthStencil->DepthBiasSlopeScale = depthStencil.DepthBiasSlopeScale;
|
||||
pDepthStencil->DepthBiasClamp = depthStencil.DepthBiasClamp;
|
||||
}
|
||||
|
||||
// Multisample state
|
||||
pipelineDesc.Multisample.Count = descriptor.Multisample.Count;
|
||||
pipelineDesc.Multisample.Mask = descriptor.Multisample.Mask;
|
||||
pipelineDesc.Multisample.AlphaToCoverageEnabled = descriptor.Multisample.AlphaToCoverageEnabled;
|
||||
|
||||
// Fragment state
|
||||
if (descriptor.Fragment is { } fragment)
|
||||
{
|
||||
var fragmentShader = _shaderModuleRegistry[fragment.ProgrammableStage.ShaderModule.Handle].Native;
|
||||
|
||||
var pFragment = BumpAllocate<FragmentState>(ref buffer);
|
||||
pipelineDesc.Fragment = pFragment;
|
||||
|
||||
pFragment->Module = fragmentShader;
|
||||
pFragment->EntryPoint = BumpAllocateUtf8(ref buffer, fragment.ProgrammableStage.EntryPoint);
|
||||
|
||||
WgpuProgrammableConstants(
|
||||
ref buffer,
|
||||
fragment.ProgrammableStage.Constants,
|
||||
out pFragment->ConstantCount,
|
||||
out pFragment->Constants);
|
||||
|
||||
var targets = fragment.Targets;
|
||||
pFragment->TargetCount = (uint)targets.Length;
|
||||
pFragment->Targets = BumpAllocate<ColorTargetState>(ref buffer, targets.Length);
|
||||
for (var i = 0; i < targets.Length; i++)
|
||||
{
|
||||
ref var target = ref pFragment->Targets[i];
|
||||
target.Format = (TextureFormat)targets[i].Format;
|
||||
|
||||
if (targets[i].Blend is { } blend)
|
||||
{
|
||||
var pBlend = BumpAllocate<BlendState>(ref buffer);
|
||||
target.Blend = pBlend;
|
||||
|
||||
pBlend->Alpha = WgpuBlendComponent(blend.Alpha);
|
||||
pBlend->Color = WgpuBlendComponent(blend.Color);
|
||||
}
|
||||
|
||||
target.WriteMask = (ColorWriteMask)targets[i].WriteMask;
|
||||
}
|
||||
}
|
||||
|
||||
nativePipeline = _webGpu.DeviceCreateRenderPipeline(_wgpuDevice, &pipelineDesc);
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMemory.AlignedFree(bufferPtr);
|
||||
}
|
||||
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_renderPipelineRegistry.Add(handle, new RenderPipelineReg { Native = nativePipeline });
|
||||
return new RhiRenderPipeline(this, handle);
|
||||
}
|
||||
|
||||
private static StencilFaceState WgpuStencilFaceState(in RhiStencilFaceState state)
|
||||
{
|
||||
return new StencilFaceState
|
||||
{
|
||||
Compare = (CompareFunction)state.Compare,
|
||||
FailOp = (StencilOperation)state.FailOp,
|
||||
DepthFailOp = (StencilOperation)state.DepthFailOp,
|
||||
PassOp = (StencilOperation)state.PassOp
|
||||
};
|
||||
}
|
||||
|
||||
private static void WgpuProgrammableConstants(
|
||||
ref Span<byte> buffer,
|
||||
RhiConstantEntry[] constants,
|
||||
out uint constantCount,
|
||||
out ConstantEntry* pConstants)
|
||||
{
|
||||
constantCount = (uint)constants.Length;
|
||||
pConstants = BumpAllocate<ConstantEntry>(ref buffer, constants.Length);
|
||||
for (var i = 0; i < constants.Length; i++)
|
||||
{
|
||||
ref var constant = ref pConstants[i];
|
||||
constant.Key = BumpAllocateUtf8(ref buffer, constants[i].Key);
|
||||
constant.Value = constants[i].Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static BlendComponent WgpuBlendComponent(in RhiBlendComponent component)
|
||||
{
|
||||
return new BlendComponent
|
||||
{
|
||||
Operation = (BlendOperation)component.Operation,
|
||||
DstFactor = (BlendFactor)component.DstFactor,
|
||||
SrcFactor = (BlendFactor)component.SrcFactor,
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class RenderPipelineReg
|
||||
{
|
||||
public RenderPipeline* Native;
|
||||
}
|
||||
|
||||
private sealed class PipelineLayoutReg
|
||||
{
|
||||
public PipelineLayout* Native;
|
||||
}
|
||||
}
|
||||
51
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Sampler.cs
Normal file
51
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Sampler.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, SamplerReg> _samplerRegistry = new();
|
||||
|
||||
public override RhiSampler CreateSampler(in RhiSamplerDescriptor descriptor)
|
||||
{
|
||||
var addressModeU = ValidateAddressMode(descriptor.AddressModeU);
|
||||
var addressModeV = ValidateAddressMode(descriptor.AddressModeV);
|
||||
var addressModeW = ValidateAddressMode(descriptor.AddressModeW);
|
||||
var magFilter = ValidateFilterMode(descriptor.MagFilter);
|
||||
var minFilter = ValidateFilterMode(descriptor.MinFilter);
|
||||
var mipmapFilter = ValidateMipmapFilterMode(descriptor.MipmapFilter);
|
||||
var compare = ValidateCompareFunction(descriptor.Compare);
|
||||
|
||||
Sampler* sampler;
|
||||
fixed (byte* label = MakeLabel(descriptor.Label))
|
||||
{
|
||||
var samplerDesc = new SamplerDescriptor
|
||||
{
|
||||
AddressModeU = (AddressMode) addressModeU,
|
||||
AddressModeV = (AddressMode) addressModeV,
|
||||
AddressModeW = (AddressMode) addressModeW,
|
||||
MagFilter = (FilterMode) magFilter,
|
||||
MinFilter = (FilterMode) minFilter,
|
||||
MipmapFilter = (MipmapFilterMode) mipmapFilter,
|
||||
LodMinClamp = descriptor.LodMinClamp,
|
||||
LodMaxClamp = descriptor.LodMaxClamp,
|
||||
Compare = (CompareFunction) compare,
|
||||
MaxAnisotropy = descriptor.MaxAnisotropy,
|
||||
Label = label
|
||||
};
|
||||
|
||||
sampler = _webGpu.DeviceCreateSampler(_wgpuDevice, &samplerDesc);
|
||||
}
|
||||
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_samplerRegistry.Add(handle, new SamplerReg { Native = sampler });
|
||||
return new RhiSampler(this, handle);
|
||||
}
|
||||
|
||||
private sealed class SamplerReg
|
||||
{
|
||||
public Sampler* Native;
|
||||
}
|
||||
}
|
||||
40
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.ShaderModule.cs
Normal file
40
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.ShaderModule.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, ShaderModuleReg> _shaderModuleRegistry = new();
|
||||
|
||||
public override RhiShaderModule CreateShaderModule(in RhiShaderModuleDescriptor descriptor)
|
||||
{
|
||||
var codeBytes = Encoding.UTF8.GetBytes(descriptor.Code);
|
||||
|
||||
ShaderModule* shaderModule;
|
||||
fixed (byte* pCode = codeBytes)
|
||||
fixed (byte* pLabel = MakeLabel(descriptor.Label))
|
||||
{
|
||||
var descWgsl = new ShaderModuleWGSLDescriptor();
|
||||
descWgsl.Code = pCode;
|
||||
descWgsl.Chain.SType = SType.ShaderModuleWgsldescriptor;
|
||||
|
||||
var desc = new ShaderModuleDescriptor();
|
||||
desc.Label = pLabel;
|
||||
desc.NextInChain = (ChainedStruct*) (&descWgsl);
|
||||
|
||||
shaderModule = _webGpu.DeviceCreateShaderModule(_wgpuDevice, &desc);
|
||||
}
|
||||
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_shaderModuleRegistry.Add(handle, new ShaderModuleReg { Native = shaderModule });
|
||||
return new RhiShaderModule(this, handle);
|
||||
}
|
||||
|
||||
private sealed class ShaderModuleReg
|
||||
{
|
||||
public ShaderModule* Native;
|
||||
}
|
||||
}
|
||||
139
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Texture.cs
Normal file
139
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Texture.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
using Silk.NET.WebGPU;
|
||||
using WGPUTexture = Silk.NET.WebGPU.Texture;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
private readonly Dictionary<RhiHandle, TextureReg> _textureRegistry = new();
|
||||
private readonly Dictionary<RhiHandle, TextureViewReg> _textureViewRegistry = new();
|
||||
|
||||
public override RhiTexture CreateTexture(in RhiTextureDescriptor descriptor)
|
||||
{
|
||||
var format = descriptor.Format;
|
||||
var dimension = descriptor.Dimension;
|
||||
var usage = descriptor.Usage;
|
||||
ValidateTextureFormat(format);
|
||||
ValidateTextureDimension(dimension);
|
||||
ValidateTextureUsage(usage);
|
||||
|
||||
// TODO: Copy to stackalloc instead.
|
||||
var viewFormats = descriptor.ViewFormats?.ToArray() ?? Array.Empty<RhiTextureFormat>();
|
||||
foreach (var vf in viewFormats)
|
||||
{
|
||||
ValidateTextureFormat(vf);
|
||||
}
|
||||
|
||||
DebugTools.Assert(
|
||||
sizeof(RhiTextureFormat) == sizeof(TextureFormat),
|
||||
"Pointer to view formats array is cast directly to pass to native, sizes must match");
|
||||
|
||||
WGPUTexture* texturePtr;
|
||||
fixed (byte* label = MakeLabel(descriptor.Label))
|
||||
fixed (RhiTextureFormat* pViewFormats = viewFormats)
|
||||
{
|
||||
var webGpuDesc = new TextureDescriptor
|
||||
{
|
||||
SampleCount = descriptor.SampleCount,
|
||||
MipLevelCount = descriptor.MipLevelCount,
|
||||
Dimension = (TextureDimension) dimension,
|
||||
Format = (TextureFormat) format,
|
||||
Label = label,
|
||||
Size = WgpuExtent3D(descriptor.Size),
|
||||
Usage = (TextureUsage) usage,
|
||||
ViewFormats = (TextureFormat*) pViewFormats,
|
||||
ViewFormatCount = checked((uint) viewFormats.Length),
|
||||
};
|
||||
|
||||
texturePtr = _webGpu.DeviceCreateTexture(_wgpuDevice, &webGpuDesc);
|
||||
}
|
||||
|
||||
if (texturePtr == null)
|
||||
throw new RhiException("Texture creation failed");
|
||||
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_textureRegistry.Add(handle, new TextureReg { Native = texturePtr });
|
||||
return new RhiTexture(this, handle);
|
||||
}
|
||||
|
||||
internal override RhiTextureView TextureCreateView(RhiTexture texture, in RhiTextureViewDescriptor descriptor)
|
||||
{
|
||||
// TODO: Thread safety
|
||||
var nativeTexture = _textureRegistry[texture.Handle].Native;
|
||||
|
||||
var format = ValidateTextureFormat(descriptor.Format);
|
||||
var dimension = ValidateTextureViewDimension(descriptor.Dimension);
|
||||
var aspect = ValidateTextureAspect(descriptor.Aspect);
|
||||
|
||||
var mipLevelCount = descriptor.MipLevelCount;
|
||||
var arrayLayerCount = descriptor.ArrayLayerCount;
|
||||
|
||||
if (mipLevelCount == 0)
|
||||
throw new ArgumentException($"Invalid {nameof(descriptor.MipLevelCount)}");
|
||||
|
||||
if (arrayLayerCount == 0)
|
||||
throw new ArgumentException($"Invalid {nameof(descriptor.ArrayLayerCount)}");
|
||||
|
||||
TextureView* textureView;
|
||||
fixed (byte* label = MakeLabel(descriptor.Label))
|
||||
{
|
||||
var webGpuDesc = new TextureViewDescriptor
|
||||
{
|
||||
Format = (TextureFormat) format,
|
||||
Dimension = (TextureViewDimension) dimension,
|
||||
Aspect = (TextureAspect) aspect,
|
||||
Label = label,
|
||||
BaseMipLevel = descriptor.BaseMipLevel,
|
||||
MipLevelCount = mipLevelCount,
|
||||
BaseArrayLayer = descriptor.BaseArrayLayer,
|
||||
ArrayLayerCount = descriptor.ArrayLayerCount
|
||||
};
|
||||
|
||||
textureView = _webGpu.TextureCreateView(nativeTexture, &webGpuDesc);
|
||||
}
|
||||
|
||||
return AllocRhiTextureView(textureView);
|
||||
}
|
||||
|
||||
internal override void TextureViewDrop(RhiTextureView textureView)
|
||||
{
|
||||
_wgpu.TextureViewDrop(_textureViewRegistry[textureView.Handle].Native);
|
||||
|
||||
_textureViewRegistry.Remove(textureView.Handle);
|
||||
}
|
||||
|
||||
internal override RhiTextureView CreateTextureViewForWindow(Clyde.WindowReg reg)
|
||||
{
|
||||
// TODO: Thread safety
|
||||
|
||||
var swapChain = reg.RhiWebGpuData!.SwapChain;
|
||||
|
||||
// This creates a new texture view handle.
|
||||
var textureView = _webGpu.SwapChainGetCurrentTextureView(swapChain);
|
||||
|
||||
return AllocRhiTextureView(textureView);
|
||||
}
|
||||
|
||||
private RhiTextureView AllocRhiTextureView(TextureView* native)
|
||||
{
|
||||
// TODO: Thread safety
|
||||
var handle = AllocRhiHandle();
|
||||
_textureViewRegistry.Add(handle, new TextureViewReg { Native = native });
|
||||
return new RhiTextureView(this, handle);
|
||||
}
|
||||
|
||||
private sealed class TextureReg
|
||||
{
|
||||
public WGPUTexture* Native;
|
||||
}
|
||||
|
||||
private sealed class TextureViewReg
|
||||
{
|
||||
public TextureView* Native;
|
||||
}
|
||||
}
|
||||
146
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Window.cs
Normal file
146
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.Window.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using Robust.Shared.Utility;
|
||||
using Silk.NET.WebGPU;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu
|
||||
{
|
||||
public sealed class WindowData
|
||||
{
|
||||
public Surface* Surface;
|
||||
public SwapChain* SwapChain;
|
||||
}
|
||||
|
||||
private void CreateSurfaceForWindow(Clyde.WindowReg window)
|
||||
{
|
||||
DebugTools.Assert(_clyde._windowing != null);
|
||||
|
||||
SurfaceDescriptor surfaceDesc = default;
|
||||
SurfaceDescriptorFromWindowsHWND surfaceDescHwnd;
|
||||
SurfaceDescriptorFromXlibWindow surfaceDescX11;
|
||||
SurfaceDescriptorFromMetalLayer surfaceDescMetal;
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var hInstance = _clyde._windowing.WindowGetWin32Instance(window);
|
||||
var hWnd = _clyde._windowing.WindowGetWin32Window(window);
|
||||
|
||||
surfaceDescHwnd = new SurfaceDescriptorFromWindowsHWND
|
||||
{
|
||||
Chain =
|
||||
{
|
||||
SType = SType.SurfaceDescriptorFromWindowsHwnd
|
||||
},
|
||||
Hinstance = hInstance,
|
||||
Hwnd = hWnd
|
||||
};
|
||||
|
||||
surfaceDesc.NextInChain = (ChainedStruct*) (&surfaceDescHwnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
var xDisplay = _clyde._windowing.WindowGetX11Display(window);
|
||||
var xWindow = _clyde._windowing.WindowGetX11Id(window);
|
||||
|
||||
if (xDisplay != null && xWindow != null)
|
||||
{
|
||||
surfaceDescX11 = new SurfaceDescriptorFromXlibWindow
|
||||
{
|
||||
Chain =
|
||||
{
|
||||
SType = SType.SurfaceDescriptorFromXlibWindow
|
||||
},
|
||||
Display = ((IntPtr) xDisplay.Value).ToPointer(),
|
||||
Window = xWindow.Value
|
||||
};
|
||||
|
||||
surfaceDesc.NextInChain = (ChainedStruct*) (&surfaceDescX11);
|
||||
}
|
||||
else
|
||||
{
|
||||
var metalLayer = _clyde._windowing.WindowGetMetalLayer(window);
|
||||
|
||||
if (metalLayer != null)
|
||||
{
|
||||
surfaceDescMetal = new SurfaceDescriptorFromMetalLayer
|
||||
{
|
||||
Chain =
|
||||
{
|
||||
SType = SType.SurfaceDescriptorFromMetalLayer
|
||||
},
|
||||
Layer = ((IntPtr) metalLayer.Value).ToPointer()
|
||||
};
|
||||
|
||||
surfaceDesc.NextInChain = (ChainedStruct*) (&surfaceDescMetal);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var surface = _webGpu.InstanceCreateSurface(_wgpuInstance, &surfaceDesc);
|
||||
window.RhiWebGpuData = new WindowData
|
||||
{
|
||||
Surface = surface
|
||||
};
|
||||
}
|
||||
|
||||
private void CreateSwapChainForWindow(Clyde.WindowReg window)
|
||||
{
|
||||
// TODO: Safety
|
||||
var rhiData = window.RhiWebGpuData!;
|
||||
|
||||
var format = TextureFormat.Bgra8UnormSrgb;
|
||||
_sawmill.Debug($"Preferred surface format is {format}");
|
||||
|
||||
var swapChainDesc = new SwapChainDescriptor
|
||||
{
|
||||
Format = format,
|
||||
Height = (uint)window.FramebufferSize.Y,
|
||||
Width = (uint)window.FramebufferSize.X,
|
||||
Usage = TextureUsage.RenderAttachment,
|
||||
PresentMode = PresentMode.Fifo
|
||||
};
|
||||
|
||||
var swapChain = _webGpu.DeviceCreateSwapChain(_wgpuDevice, rhiData.Surface, &swapChainDesc);
|
||||
rhiData.SwapChain = swapChain;
|
||||
|
||||
_sawmill.Debug("WebGPU Surface created!");
|
||||
}
|
||||
|
||||
internal override void WindowCreated(Clyde.WindowReg reg)
|
||||
{
|
||||
CreateSurfaceForWindow(reg);
|
||||
CreateSwapChainForWindow(reg);
|
||||
}
|
||||
|
||||
internal override void WindowDestroy(Clyde.WindowReg reg)
|
||||
{
|
||||
var rhiData = reg.RhiWebGpuData!;
|
||||
|
||||
_wgpu.SwapChainDrop(rhiData.SwapChain);
|
||||
_wgpu.SurfaceDrop(rhiData.Surface);
|
||||
|
||||
reg.RhiWebGpuData = null;
|
||||
}
|
||||
|
||||
internal override void WindowRecreateSwapchain(Clyde.WindowReg reg)
|
||||
{
|
||||
var rhiData = reg.RhiWebGpuData!;
|
||||
|
||||
_wgpu.SwapChainDrop(rhiData.SwapChain);
|
||||
|
||||
CreateSwapChainForWindow(reg);
|
||||
}
|
||||
|
||||
internal override void WindowPresent(Clyde.WindowReg reg)
|
||||
{
|
||||
// TODO: Safety
|
||||
var rhiData = reg.RhiWebGpuData!;
|
||||
|
||||
_webGpu.SwapChainPresent(rhiData.SwapChain);
|
||||
}
|
||||
}
|
||||
356
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.cs
Normal file
356
Robust.Client/Graphics/Clyde/Rhi/RhiWebGpu.cs
Normal file
@@ -0,0 +1,356 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Silk.NET.WebGPU;
|
||||
using Silk.NET.WebGPU.Extensions.WGPU;
|
||||
using LogLevel = Silk.NET.WebGPU.Extensions.WGPU.LogLevel;
|
||||
using RLogLevel = Robust.Shared.Log.LogLevel;
|
||||
using RColor = Robust.Shared.Maths.Color;
|
||||
|
||||
#pragma warning disable CS8500
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde.Rhi;
|
||||
|
||||
internal sealed unsafe partial class RhiWebGpu : RhiBase
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly IConfigurationManager _cfg;
|
||||
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly ISawmill _apiLogSawmill;
|
||||
|
||||
private WebGPU _webGpu = default!;
|
||||
private Wgpu _wgpu = default!;
|
||||
private Instance* _wgpuInstance;
|
||||
private Adapter* _wgpuAdapter;
|
||||
private Device* _wgpuDevice;
|
||||
private Queue* _wgpuQueue;
|
||||
|
||||
private RhiLimits? _deviceLimits;
|
||||
private RhiAdapterProperties? _adapterProperties;
|
||||
private string _description = "not initialized";
|
||||
|
||||
public override RhiLimits DeviceLimits =>
|
||||
_deviceLimits ?? throw new InvalidOperationException("Not initialized yet");
|
||||
|
||||
public override RhiAdapterProperties AdapterProperties =>
|
||||
_adapterProperties ?? throw new InvalidOperationException("Not initialized yet");
|
||||
|
||||
public override string Description => _description;
|
||||
|
||||
public RhiWebGpu(Clyde clyde, IDependencyCollection dependencies)
|
||||
{
|
||||
var logMgr = dependencies.Resolve<ILogManager>();
|
||||
_cfg = dependencies.Resolve<IConfigurationManager>();
|
||||
|
||||
_clyde = clyde;
|
||||
_sawmill = logMgr.GetSawmill("clyde.rhi.webGpu");
|
||||
_apiLogSawmill = logMgr.GetSawmill("clyde.rhi.webGpu.apiLog");
|
||||
|
||||
Queue = new RhiQueue(this, default);
|
||||
}
|
||||
|
||||
internal override void Init()
|
||||
{
|
||||
_sawmill.Info("Initializing WebGPU RHI!");
|
||||
|
||||
InitInstance();
|
||||
|
||||
CreateSurfaceForWindow(_clyde._mainWindow!);
|
||||
|
||||
_sawmill.Debug("WebGPU main surface created!");
|
||||
|
||||
InitAdapterAndDevice(_clyde._mainWindow!.RhiWebGpuData!.Surface);
|
||||
|
||||
CreateSwapChainForWindow(_clyde._mainWindow!);
|
||||
}
|
||||
|
||||
private void InitInstance()
|
||||
{
|
||||
var context = WebGPU.CreateDefaultContext(new[]
|
||||
{ "wgpu_native.dll", "libwgpu_native.so", "libwgpu_native.dylib" });
|
||||
_webGpu = new WebGPU(context);
|
||||
_wgpu = new Wgpu(context);
|
||||
|
||||
var wgpuVersion = WgpuVersionToString(_wgpu.GetVersion());
|
||||
_sawmill.Debug($"wgpu-native loaded, version: {wgpuVersion}");
|
||||
|
||||
_description = $"WebGPU (wgpu-native {wgpuVersion})";
|
||||
|
||||
InitLogging();
|
||||
|
||||
Span<byte> buffer = stackalloc byte[128];
|
||||
var pInstanceDescriptor = BumpAllocate<InstanceDescriptor>(ref buffer);
|
||||
|
||||
// Specify instance extras for wgpu-native.
|
||||
var pInstanceExtras = BumpAllocate<InstanceExtras>(ref buffer);
|
||||
pInstanceDescriptor->NextInChain = (ChainedStruct*)pInstanceExtras;
|
||||
pInstanceExtras->Chain.SType = (SType)NativeSType.STypeInstanceExtras;
|
||||
pInstanceExtras->Backends = (uint)GetInstanceBackendCfg();
|
||||
|
||||
_wgpuInstance = _webGpu.CreateInstance(pInstanceDescriptor);
|
||||
|
||||
_sawmill.Debug("WebGPU instance created!");
|
||||
}
|
||||
|
||||
private InstanceBackend GetInstanceBackendCfg()
|
||||
{
|
||||
var configured = _cfg.GetCVar(CVars.DisplayWgpuBackends);
|
||||
if (configured == "all")
|
||||
return InstanceBackend.Primary | InstanceBackend.Secondary;
|
||||
|
||||
var backends = InstanceBackend.None;
|
||||
foreach (var opt in configured.Split(","))
|
||||
{
|
||||
backends |= opt switch
|
||||
{
|
||||
"vulkan" => InstanceBackend.Vulkan,
|
||||
"gl" => InstanceBackend.GL,
|
||||
"metal" => InstanceBackend.Metal,
|
||||
"dx12" => InstanceBackend.DX12,
|
||||
"dx11" => InstanceBackend.DX11,
|
||||
"browser" => InstanceBackend.BrowserWebGpu,
|
||||
_ => throw new ArgumentException($"Unknown wgpu backend: '{opt}'")
|
||||
};
|
||||
}
|
||||
|
||||
return backends;
|
||||
}
|
||||
|
||||
private void InitAdapterAndDevice(Surface* forSurface)
|
||||
{
|
||||
var powerPreference = ValidatePowerPreference(
|
||||
(RhiPowerPreference)_cfg.GetCVar(CVars.DisplayGpuPowerPreference)
|
||||
);
|
||||
|
||||
var requestAdapterOptions = new RequestAdapterOptions
|
||||
{
|
||||
CompatibleSurface = forSurface,
|
||||
PowerPreference = powerPreference
|
||||
};
|
||||
|
||||
WgpuRequestAdapterResult result;
|
||||
_webGpu.InstanceRequestAdapter(
|
||||
_wgpuInstance,
|
||||
&requestAdapterOptions,
|
||||
new PfnRequestAdapterCallback(&WgpuRequestAdapterCallback),
|
||||
&result);
|
||||
|
||||
if (result.Status != RequestAdapterStatus.Success)
|
||||
throw new RhiException($"Adapter request failed: {result.Message}");
|
||||
|
||||
_sawmill.Debug("WebGPU adapter created!");
|
||||
|
||||
_wgpuAdapter = result.Adapter.P;
|
||||
|
||||
AdapterProperties adapterProps = default;
|
||||
_webGpu.AdapterGetProperties(_wgpuAdapter, &adapterProps);
|
||||
|
||||
SupportedLimits adapterLimits = default;
|
||||
_webGpu.AdapterGetLimits(_wgpuAdapter, &adapterLimits);
|
||||
|
||||
_sawmill.Debug($"adapter name: {MarshalFromString(adapterProps.Name)}");
|
||||
_sawmill.Debug($"adapter vendor: {MarshalFromString(adapterProps.VendorName)} ({adapterProps.VendorID})");
|
||||
_sawmill.Debug($"adapter driver: {MarshalFromString(adapterProps.DriverDescription)}");
|
||||
_sawmill.Debug($"adapter architecture: {MarshalFromString(adapterProps.Architecture)}");
|
||||
_sawmill.Debug($"adapter backend: {adapterProps.BackendType}");
|
||||
_sawmill.Debug($"adapter type: {adapterProps.AdapterType}");
|
||||
_sawmill.Debug($"adapter UBO alignment: {adapterLimits.Limits.MinUniformBufferOffsetAlignment}");
|
||||
|
||||
_adapterProperties = new RhiAdapterProperties(
|
||||
adapterProps.VendorID,
|
||||
MarshalFromString(adapterProps.VendorName),
|
||||
MarshalFromString(adapterProps.Architecture),
|
||||
MarshalFromString(adapterProps.Name),
|
||||
MarshalFromString(adapterProps.DriverDescription),
|
||||
(RhiAdapterType) adapterProps.AdapterType,
|
||||
(RhiBackendType) adapterProps.BackendType
|
||||
);
|
||||
|
||||
// Default limits, from WebGPU spec.
|
||||
var requiredLimits = new RequiredLimits();
|
||||
if (false)
|
||||
{
|
||||
// GLES3.0
|
||||
requiredLimits.Limits.MaxComputeWorkgroupStorageSize = 16384;
|
||||
requiredLimits.Limits.MaxComputeInvocationsPerWorkgroup = 256;
|
||||
requiredLimits.Limits.MaxComputeWorkgroupSizeX = 256;
|
||||
requiredLimits.Limits.MaxComputeWorkgroupSizeY = 256;
|
||||
requiredLimits.Limits.MaxComputeWorkgroupSizeZ = 256;
|
||||
requiredLimits.Limits.MaxComputeWorkgroupsPerDimension = 65536;
|
||||
requiredLimits.Limits.MaxDynamicStorageBuffersPerPipelineLayout = 0;
|
||||
requiredLimits.Limits.MaxStorageBuffersPerShaderStage = 4;
|
||||
requiredLimits.Limits.MaxStorageBufferBindingSize = 134217728;
|
||||
}
|
||||
|
||||
// Required minimums
|
||||
requiredLimits.Limits.MinStorageBufferOffsetAlignment = 256;
|
||||
requiredLimits.Limits.MinUniformBufferOffsetAlignment = 256;
|
||||
|
||||
requiredLimits.Limits.MaxTextureDimension1D = 8192;
|
||||
requiredLimits.Limits.MaxTextureDimension2D = 8192;
|
||||
requiredLimits.Limits.MaxTextureDimension3D = 2048;
|
||||
requiredLimits.Limits.MaxTextureArrayLayers = 256;
|
||||
requiredLimits.Limits.MaxBindGroups = 4;
|
||||
requiredLimits.Limits.MaxBindingsPerBindGroup = 1000;
|
||||
requiredLimits.Limits.MaxDynamicUniformBuffersPerPipelineLayout = 8;
|
||||
requiredLimits.Limits.MaxSampledTexturesPerShaderStage = 16;
|
||||
requiredLimits.Limits.MaxSamplersPerShaderStage = 16;
|
||||
requiredLimits.Limits.MaxUniformBuffersPerShaderStage = 12;
|
||||
requiredLimits.Limits.MaxUniformBufferBindingSize = 65536;
|
||||
requiredLimits.Limits.MaxVertexBuffers = 8;
|
||||
requiredLimits.Limits.MaxVertexAttributes = 16;
|
||||
requiredLimits.Limits.MaxVertexBufferArrayStride = 2048;
|
||||
requiredLimits.Limits.MaxInterStageShaderComponents = 60;
|
||||
requiredLimits.Limits.MaxInterStageShaderVariables = 16;
|
||||
requiredLimits.Limits.MaxColorAttachments = 8;
|
||||
requiredLimits.Limits.MaxColorAttachmentBytesPerSample = 32;
|
||||
requiredLimits.Limits.MaxBufferSize = 268435456;
|
||||
|
||||
// Custom limits
|
||||
// Take as low UBO alignment as we can get.
|
||||
requiredLimits.Limits.MinUniformBufferOffsetAlignment = adapterLimits.Limits.MinUniformBufferOffsetAlignment;
|
||||
|
||||
var deviceDesc = new DeviceDescriptor();
|
||||
deviceDesc.RequiredLimits = &requiredLimits;
|
||||
WgpuRequestDeviceResult deviceResult;
|
||||
_webGpu.AdapterRequestDevice(
|
||||
_wgpuAdapter,
|
||||
&deviceDesc,
|
||||
new PfnRequestDeviceCallback(&WgpuRequestDeviceCallback),
|
||||
&deviceResult);
|
||||
|
||||
if (deviceResult.Status != RequestDeviceStatus.Success)
|
||||
throw new Exception($"Device request failed: {deviceResult.Message}");
|
||||
|
||||
_sawmill.Debug("WebGPU device created!");
|
||||
|
||||
_wgpuDevice = deviceResult.Device;
|
||||
_wgpuQueue = _webGpu.DeviceGetQueue(_wgpuDevice);
|
||||
|
||||
ref var limits = ref requiredLimits.Limits;
|
||||
|
||||
_deviceLimits = new RhiLimits(
|
||||
limits.MaxTextureDimension1D,
|
||||
limits.MaxTextureDimension2D,
|
||||
limits.MaxTextureDimension3D,
|
||||
limits.MaxTextureArrayLayers,
|
||||
limits.MaxBindGroups,
|
||||
limits.MaxBindingsPerBindGroup,
|
||||
limits.MaxDynamicUniformBuffersPerPipelineLayout,
|
||||
limits.MaxDynamicStorageBuffersPerPipelineLayout,
|
||||
limits.MaxSampledTexturesPerShaderStage,
|
||||
limits.MaxSamplersPerShaderStage,
|
||||
limits.MaxStorageBuffersPerShaderStage,
|
||||
limits.MaxStorageTexturesPerShaderStage,
|
||||
limits.MaxUniformBuffersPerShaderStage,
|
||||
limits.MaxUniformBufferBindingSize,
|
||||
limits.MaxStorageBufferBindingSize,
|
||||
limits.MinUniformBufferOffsetAlignment,
|
||||
limits.MinStorageBufferOffsetAlignment,
|
||||
limits.MaxVertexBuffers,
|
||||
limits.MaxBufferSize,
|
||||
limits.MaxVertexAttributes,
|
||||
limits.MaxVertexBufferArrayStride,
|
||||
limits.MaxInterStageShaderComponents,
|
||||
limits.MaxInterStageShaderVariables,
|
||||
limits.MaxColorAttachments,
|
||||
limits.MaxColorAttachmentBytesPerSample,
|
||||
limits.MaxComputeWorkgroupStorageSize,
|
||||
limits.MaxComputeInvocationsPerWorkgroup,
|
||||
limits.MaxComputeWorkgroupSizeX,
|
||||
limits.MaxComputeWorkgroupSizeY,
|
||||
limits.MaxComputeWorkgroupSizeZ,
|
||||
limits.MaxComputeWorkgroupsPerDimension
|
||||
);
|
||||
|
||||
InitErrorCallback();
|
||||
}
|
||||
|
||||
private void InitLogging()
|
||||
{
|
||||
// TODO: clear this.
|
||||
var gcHandle = GCHandle.Alloc(this);
|
||||
|
||||
_wgpu.SetLogCallback(new PfnLogCallback(&LogCallback), (void*)GCHandle.ToIntPtr(gcHandle));
|
||||
_wgpu.SetLogLevel(LogLevel.Warn);
|
||||
}
|
||||
|
||||
private void InitErrorCallback()
|
||||
{
|
||||
// TODO: clear this.
|
||||
var gcHandle = GCHandle.Alloc(this);
|
||||
|
||||
_webGpu.DeviceSetUncapturedErrorCallback(
|
||||
_wgpuDevice,
|
||||
new PfnErrorCallback(&UncapturedErrorCallback),
|
||||
(void*)GCHandle.ToIntPtr(gcHandle));
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void LogCallback(LogLevel level, byte* message, void* userdata)
|
||||
{
|
||||
var self = (RhiWebGpu)GCHandle.FromIntPtr((nint)userdata).Target!;
|
||||
var messageString = Marshal.PtrToStringUTF8((nint)message)!;
|
||||
|
||||
var robustLevel = level switch
|
||||
{
|
||||
LogLevel.Error => RLogLevel.Error,
|
||||
LogLevel.Warn => RLogLevel.Warning,
|
||||
LogLevel.Info => RLogLevel.Info,
|
||||
LogLevel.Debug => RLogLevel.Debug,
|
||||
LogLevel.Trace or _ => RLogLevel.Verbose,
|
||||
};
|
||||
|
||||
self._apiLogSawmill.Log(robustLevel, messageString);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void UncapturedErrorCallback(ErrorType level, byte* message, void* userdata)
|
||||
{
|
||||
var self = (RhiWebGpu)GCHandle.FromIntPtr((nint)userdata).Target!;
|
||||
var messageString = Marshal.PtrToStringUTF8((nint)message)!;
|
||||
|
||||
self._apiLogSawmill.Error(messageString);
|
||||
}
|
||||
|
||||
internal override void Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
private record struct WgpuRequestAdapterResult(RequestAdapterStatus Status, Ptr<Adapter> Adapter, string Message);
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void WgpuRequestAdapterCallback(
|
||||
RequestAdapterStatus status,
|
||||
Adapter* adapter,
|
||||
byte* message,
|
||||
void* userdata)
|
||||
{
|
||||
*(WgpuRequestAdapterResult*)userdata = new WgpuRequestAdapterResult(
|
||||
status,
|
||||
adapter,
|
||||
MarshalFromString(message));
|
||||
}
|
||||
|
||||
private record struct WgpuRequestDeviceResult(RequestDeviceStatus Status, Ptr<Device> Device, string Message);
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void WgpuRequestDeviceCallback(
|
||||
RequestDeviceStatus status,
|
||||
Device* device,
|
||||
byte* message,
|
||||
void* userdata)
|
||||
{
|
||||
*(WgpuRequestDeviceResult*)userdata = new WgpuRequestDeviceResult(
|
||||
status,
|
||||
device,
|
||||
MarshalFromString(message));
|
||||
}
|
||||
}
|
||||
46
Robust.Client/Graphics/Clyde/ShaderParser2.cs
Normal file
46
Robust.Client/Graphics/Clyde/ShaderParser2.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde;
|
||||
|
||||
/// <summary>
|
||||
/// Simple C-style preprocessor for WGSL.
|
||||
/// </summary>
|
||||
public static partial class ShaderParser2
|
||||
{
|
||||
private static readonly Regex RegexInclude = MyRegex();
|
||||
|
||||
public static string Magic(string source, Func<string, string> resolveInclude)
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
|
||||
Process(writer, source, resolveInclude);
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
private static void Process(StringWriter writer, string source, Func<string, string> resolveInclude)
|
||||
{
|
||||
var reader = new StringReader(source);
|
||||
|
||||
while (reader.ReadLine() is { } line)
|
||||
{
|
||||
var match = RegexInclude.Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups[1].Value;
|
||||
var included = resolveInclude(name);
|
||||
|
||||
Process(writer, included, resolveInclude);
|
||||
continue;
|
||||
}
|
||||
|
||||
writer.WriteLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex("^\\s*#\\s*include\\s*<\\s*([^>]+)\\s*>")]
|
||||
private static partial Regex MyRegex();
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef HAS_VARYING_ATTRIBUTE
|
||||
#define texture2D texture
|
||||
#define varying in
|
||||
#define attribute in
|
||||
#define gl_FragColor colourOutput
|
||||
out highp vec4 colourOutput;
|
||||
#endif
|
||||
|
||||
varying highp vec2 UV;
|
||||
|
||||
uniform sampler2D tex;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = texture2D(tex, UV);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef HAS_VARYING_ATTRIBUTE
|
||||
#define texture2D texture
|
||||
#define varying out
|
||||
#define attribute in
|
||||
#endif
|
||||
|
||||
|
||||
// Vertex position.
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
|
||||
varying vec2 UV;
|
||||
|
||||
void main()
|
||||
{
|
||||
UV = tCoord;
|
||||
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
}
|
||||
@@ -264,7 +264,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
// As far as I can tell, sometimes entering fullscreen just disables vsync.
|
||||
// Hilarious!
|
||||
_clyde._glContext?.UpdateVSync();
|
||||
// _clyde._glContext?.UpdateVSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,9 +253,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
) : CmdBase;
|
||||
|
||||
private sealed record CmdWinCreate(
|
||||
GLContextSpec? GLSpec,
|
||||
WindowCreateParameters Parameters,
|
||||
nint ShareWindow,
|
||||
nint OwnerWindow,
|
||||
TaskCompletionSource<GlfwWindowCreateResult> Tcs
|
||||
) : CmdBase;
|
||||
|
||||
@@ -240,41 +240,40 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public nint? WindowGetWin32Window(WindowReg window)
|
||||
// GLFW doesn't have the Metal view/layer API that SDL2 does.
|
||||
// It might be possible though.
|
||||
public nint? WindowGetMetalLayer(WindowReg window) => null;
|
||||
|
||||
public HWND WindowGetWin32Window(WindowReg window)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
return null;
|
||||
return default;
|
||||
|
||||
var reg = (GlfwWindowReg) window;
|
||||
try
|
||||
{
|
||||
return GLFW.GetWin32Window(reg.GlfwWindow);
|
||||
return (HWND) GLFW.GetWin32Window(reg.GlfwWindow);
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public HINSTANCE WindowGetWin32Instance(WindowReg window)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public (WindowReg?, string? error) WindowCreate(
|
||||
GLContextSpec? spec,
|
||||
WindowCreateParameters parameters,
|
||||
WindowReg? share,
|
||||
WindowReg? owner)
|
||||
{
|
||||
Window* sharePtr = null;
|
||||
if (share is GlfwWindowReg glfwReg)
|
||||
sharePtr = glfwReg.GlfwWindow;
|
||||
|
||||
Window* ownerPtr = null;
|
||||
if (owner is GlfwWindowReg glfwOwnerReg)
|
||||
ownerPtr = glfwOwnerReg.GlfwWindow;
|
||||
|
||||
var task = SharedWindowCreate(
|
||||
spec,
|
||||
parameters,
|
||||
sharePtr,
|
||||
ownerPtr);
|
||||
var task = SharedWindowCreate(parameters, ownerPtr);
|
||||
|
||||
// Block the main thread (to avoid stuff like texture uploads being problematic).
|
||||
WaitWindowCreate(task);
|
||||
@@ -308,10 +307,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private Task<GlfwWindowCreateResult> SharedWindowCreate(
|
||||
GLContextSpec? glSpec,
|
||||
WindowCreateParameters parameters,
|
||||
Window* share, Window* owner)
|
||||
private Task<GlfwWindowCreateResult> SharedWindowCreate(WindowCreateParameters parameters, Window* owner)
|
||||
{
|
||||
//
|
||||
// IF YOU'RE WONDERING WHY THIS IS TASK-BASED:
|
||||
@@ -331,9 +327,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Yes we ping-pong this TCS through the window thread and back, deal with it.
|
||||
var tcs = new TaskCompletionSource<GlfwWindowCreateResult>();
|
||||
SendCmd(new CmdWinCreate(
|
||||
glSpec,
|
||||
parameters,
|
||||
(nint) share,
|
||||
(nint) owner,
|
||||
tcs));
|
||||
|
||||
@@ -349,9 +343,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void WinThreadWinCreate(CmdWinCreate cmd)
|
||||
{
|
||||
var (glSpec, parameters, share, owner, tcs) = cmd;
|
||||
var (parameters, owner, tcs) = cmd;
|
||||
|
||||
var window = CreateGlfwWindowForRenderer(glSpec, parameters, (Window*) share, (Window*) owner);
|
||||
var window = CreateGlfwWindowForRenderer(parameters, (Window*) owner);
|
||||
|
||||
if (window == null)
|
||||
{
|
||||
@@ -394,57 +388,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
private Window* CreateGlfwWindowForRenderer(
|
||||
GLContextSpec? spec,
|
||||
WindowCreateParameters parameters,
|
||||
Window* contextShare,
|
||||
Window* ownerWindow)
|
||||
{
|
||||
GLFW.WindowHint(WindowHintString.X11ClassName, "RobustToolbox");
|
||||
GLFW.WindowHint(WindowHintString.X11InstanceName, "RobustToolbox");
|
||||
GLFW.WindowHint(WindowHintBool.ScaleToMonitor, true);
|
||||
|
||||
if (spec == null)
|
||||
{
|
||||
// No OpenGL context requested.
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.NoApi);
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = spec.Value;
|
||||
|
||||
#if DEBUG
|
||||
GLFW.WindowHint(WindowHintBool.OpenGLDebugContext, true);
|
||||
#endif
|
||||
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMajor, s.Major);
|
||||
GLFW.WindowHint(WindowHintInt.ContextVersionMinor, s.Minor);
|
||||
GLFW.WindowHint(WindowHintBool.OpenGLForwardCompat, s.Profile != GLContextProfile.Compatibility);
|
||||
GLFW.WindowHint(WindowHintBool.SrgbCapable, true);
|
||||
|
||||
switch (s.Profile)
|
||||
{
|
||||
case GLContextProfile.Compatibility:
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
|
||||
break;
|
||||
case GLContextProfile.Core:
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core);
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlApi);
|
||||
break;
|
||||
case GLContextProfile.Es:
|
||||
GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Any);
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.OpenGlEsApi);
|
||||
break;
|
||||
}
|
||||
|
||||
GLFW.WindowHint(WindowHintContextApi.ContextCreationApi,
|
||||
s.CreationApi == GLContextCreationApi.Egl
|
||||
? ContextApi.EglContextApi
|
||||
: ContextApi.NativeContextApi);
|
||||
|
||||
if (s.CreationApi == GLContextCreationApi.Egl)
|
||||
WsiShared.EnsureEglAvailable();
|
||||
}
|
||||
GLFW.WindowHint(WindowHintClientApi.ClientApi, ClientApi.NoApi);
|
||||
|
||||
Monitor* monitor = null;
|
||||
if (parameters.Monitor != null &&
|
||||
@@ -474,7 +425,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
parameters.Width, parameters.Height,
|
||||
parameters.Title,
|
||||
parameters.Fullscreen ? monitor : null,
|
||||
contextShare);
|
||||
null);
|
||||
|
||||
// Check if window failed to create.
|
||||
if (window == null)
|
||||
@@ -692,32 +643,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void GLMakeContextCurrent(WindowReg? window)
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
CheckWindowDisposed(window);
|
||||
|
||||
var reg = (GlfwWindowReg)window;
|
||||
|
||||
GLFW.MakeContextCurrent(reg.GlfwWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
GLFW.MakeContextCurrent(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void GLSwapInterval(WindowReg reg, int interval)
|
||||
{
|
||||
GLFW.SwapInterval(interval);
|
||||
}
|
||||
|
||||
public void* GLGetProcAddress(string procName)
|
||||
{
|
||||
return (void*) GLFW.GetProcAddress(procName);
|
||||
}
|
||||
|
||||
public void TextInputSetRect(WindowReg reg, UIBox2i rect, int cursor)
|
||||
{
|
||||
// Not supported on GLFW.
|
||||
|
||||
@@ -4,12 +4,13 @@ using Robust.Client.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
partial class Clyde
|
||||
{
|
||||
private interface IWindowingImpl
|
||||
internal interface IWindowingImpl
|
||||
{
|
||||
// Lifecycle stuff
|
||||
bool Init();
|
||||
@@ -31,9 +32,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Window API.
|
||||
(WindowReg?, string? error) WindowCreate(
|
||||
GLContextSpec? spec,
|
||||
WindowCreateParameters parameters,
|
||||
WindowReg? share,
|
||||
WindowReg? owner);
|
||||
|
||||
void WindowDestroy(WindowReg reg);
|
||||
@@ -45,7 +44,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
void WindowSwapBuffers(WindowReg window);
|
||||
uint? WindowGetX11Id(WindowReg window);
|
||||
nint? WindowGetX11Display(WindowReg window);
|
||||
nint? WindowGetWin32Window(WindowReg window);
|
||||
nint? WindowGetMetalLayer(WindowReg window);
|
||||
HWND WindowGetWin32Window(WindowReg window);
|
||||
HINSTANCE WindowGetWin32Instance(WindowReg window);
|
||||
|
||||
// Keyboard
|
||||
string? KeyGetName(Keyboard.Key key);
|
||||
@@ -56,12 +57,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
void UpdateMainWindowMode();
|
||||
|
||||
// OpenGL-related stuff.
|
||||
// Note: you should probably go through GLContextBase instead, which calls these functions.
|
||||
void GLMakeContextCurrent(WindowReg? reg);
|
||||
void GLSwapInterval(WindowReg reg, int interval);
|
||||
unsafe void* GLGetProcAddress(string procName);
|
||||
|
||||
// Misc
|
||||
void RunOnWindowThread(Action a);
|
||||
|
||||
|
||||
@@ -221,10 +221,7 @@ internal partial class Clyde
|
||||
|
||||
private sealed class CmdWinCreate : CmdBase
|
||||
{
|
||||
public required GLContextSpec? GLSpec;
|
||||
public required WindowCreateParameters Parameters;
|
||||
public required nint ShareWindow;
|
||||
public required nint ShareContext;
|
||||
public required nint OwnerWindow;
|
||||
public required TaskCompletionSource<Sdl3WindowCreateResult> Tcs;
|
||||
}
|
||||
@@ -232,6 +229,7 @@ internal partial class Clyde
|
||||
private sealed class CmdWinDestroy : CmdBase
|
||||
{
|
||||
public nint Window;
|
||||
public nint MetalView;
|
||||
public bool HadOwner;
|
||||
}
|
||||
|
||||
@@ -240,7 +238,6 @@ internal partial class Clyde
|
||||
public Sdl3WindowReg? Reg;
|
||||
public string? Error;
|
||||
}
|
||||
|
||||
private sealed class CmdRunAction : CmdBase
|
||||
{
|
||||
public required Action Action;
|
||||
|
||||
@@ -10,7 +10,6 @@ using Robust.Shared.Utility;
|
||||
using SharpFont;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -114,7 +113,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
if (bitmap.Pitch != 0)
|
||||
{
|
||||
Image<A8> img;
|
||||
Image<Rgba32> img;
|
||||
switch (bitmap.PixelMode)
|
||||
{
|
||||
case PixelMode.Mono:
|
||||
@@ -131,13 +130,13 @@ namespace Robust.Client.Graphics
|
||||
span = new ReadOnlySpan<A8>((void*) bitmap.Buffer, bitmap.Pitch * bitmap.Rows);
|
||||
}
|
||||
|
||||
img = new Image<A8>(bitmap.Width, bitmap.Rows);
|
||||
img = new Image<Rgba32>(bitmap.Width, bitmap.Rows);
|
||||
|
||||
span.Blit(
|
||||
BlitA8ToRgba32(
|
||||
span,
|
||||
bitmap.Pitch,
|
||||
UIBox2i.FromDimensions(0, 0, bitmap.Pitch, bitmap.Rows),
|
||||
img,
|
||||
(0, 0));
|
||||
img);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -199,14 +198,14 @@ namespace Robust.Client.Graphics
|
||||
|
||||
OwnedTexture GenSheet()
|
||||
{
|
||||
var sheet = _clyde.CreateBlankTexture<A8>((SheetWidth, SheetHeight),
|
||||
var sheet = _clyde.CreateBlankTexture<Rgba32>((SheetWidth, SheetHeight),
|
||||
$"font-{face.FamilyName}-{instance.Size}-{(uint) (_baseFontDpi * scale)}-sheet{scaled.AtlasTextures.Count}");
|
||||
scaled.AtlasTextures.Add(sheet);
|
||||
return sheet;
|
||||
}
|
||||
}
|
||||
|
||||
private static Image<A8> MonoBitMapToImage(FTBitmap bitmap)
|
||||
private static Image<Rgba32> MonoBitMapToImage(FTBitmap bitmap)
|
||||
{
|
||||
DebugTools.Assert(bitmap.PixelMode == PixelMode.Mono);
|
||||
DebugTools.Assert(bitmap.Pitch > 0);
|
||||
@@ -217,7 +216,7 @@ namespace Robust.Client.Graphics
|
||||
span = new ReadOnlySpan<byte>((void*) bitmap.Buffer, bitmap.Rows * bitmap.Pitch);
|
||||
}
|
||||
|
||||
var bitmapImage = new Image<A8>(bitmap.Width, bitmap.Rows);
|
||||
var bitmapImage = new Image<Rgba32>(bitmap.Width, bitmap.Rows);
|
||||
for (var y = 0; y < bitmap.Rows; y++)
|
||||
{
|
||||
for (var x = 0; x < bitmap.Width; x++)
|
||||
@@ -226,13 +225,39 @@ namespace Robust.Client.Graphics
|
||||
var bitIndex = x % 8;
|
||||
|
||||
var bit = (span[byteIndex] & (1 << (7 - bitIndex))) != 0;
|
||||
bitmapImage[x, y] = new A8(bit ? byte.MaxValue : byte.MinValue);
|
||||
bitmapImage[x, y] = new Rgba32(255, 255, 255, bit ? byte.MaxValue : byte.MinValue);
|
||||
}
|
||||
}
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
|
||||
private static void BlitA8ToRgba32(
|
||||
ReadOnlySpan<A8> source,
|
||||
int sourceWidth,
|
||||
UIBox2i sourceRect,
|
||||
Image<Rgba32> destination)
|
||||
{
|
||||
var dstSpan = ImageOps.GetPixelSpan(destination);
|
||||
var dstWidth = destination.Width;
|
||||
var srcHeight = sourceRect.Height;
|
||||
var srcWidth = sourceRect.Width;
|
||||
|
||||
for (var y = 0; y < srcHeight; y++)
|
||||
{
|
||||
var sourceRowOffset = sourceWidth * (y + sourceRect.Top) + sourceRect.Left;
|
||||
var destRowOffset = dstWidth * y;
|
||||
|
||||
var srcRow = source[sourceRowOffset..(sourceRowOffset + srcWidth)];
|
||||
var dstRow = dstSpan[destRowOffset..(destRowOffset + srcWidth)];
|
||||
|
||||
for (var i = 0; i < srcRow.Length; i++)
|
||||
{
|
||||
dstRow[i] = new Rgba32(255, 255, 255, srcRow[i].PackedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FontFaceHandle : IFontFaceHandle
|
||||
{
|
||||
public Face Face { get; }
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -17,6 +18,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public interface IClyde
|
||||
{
|
||||
RhiBase Rhi { get; }
|
||||
|
||||
IClydeWindow MainWindow { get; }
|
||||
IRenderTarget MainWindowRenderTarget => MainWindow.RenderTarget;
|
||||
|
||||
|
||||
@@ -2,12 +2,6 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
internal interface IClydeDebugInfo
|
||||
{
|
||||
OpenGLVersion OpenGLVersion { get; }
|
||||
|
||||
string Renderer { get; }
|
||||
string Vendor { get; }
|
||||
string VersionString { get; }
|
||||
bool Overriding { get; }
|
||||
string WindowingApi { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,10 @@ public abstract class Texture : IRsiStateLike
|
||||
/// </summary>
|
||||
public Vector2i Size { get; /*protected set;*/ }
|
||||
|
||||
public Color this[int x, int y] => this.GetPixel(x, y);
|
||||
|
||||
protected Texture(Vector2i size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
protected Texture(Vector2i size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
|
||||
Texture IDirectionalTextureProvider.Default => this;
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
@@ -216,7 +214,7 @@ namespace Robust.Client.ResourceManagement
|
||||
// The array must be sorted from biggest to smallest first.
|
||||
Array.Sort(atlasList, (b, a) => a.AtlasSheet.Height.CompareTo(b.AtlasSheet.Height));
|
||||
|
||||
var maxSize = Math.Min(GL.GetInteger(GetPName.MaxTextureSize), _configurationManager.GetCVar(CVars.ResRSIAtlasSize));
|
||||
var maxSize = _configurationManager.GetCVar(CVars.ResRSIAtlasSize);
|
||||
|
||||
// THIS IS NOT GUARANTEED TO HAVE ANY PARTICULARLY LOGICAL ORDERING.
|
||||
// E.G you could have atlas 1 RSIs appear *before* you're done seeing atlas 2 RSIs.
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
<PackageReference Include="DiscordRichPresence" PrivateAssets="compile" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" PrivateAssets="compile" />
|
||||
<PackageReference Include="Silk.NET.WebGPU" PrivateAssets="compile" />
|
||||
<PackageReference Include="Silk.NET.WebGPU.Extensions.WGPU" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.provider.sqlite3" Condition="'$(UseSystemSqlite)' == 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.NFluidsynth" PrivateAssets="compile" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" PrivateAssets="compile" />
|
||||
<PackageReference Include="OpenTK.Audio.OpenAL" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sdl" PrivateAssets="compile" />
|
||||
|
||||
@@ -40,15 +40,14 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
|
||||
_textBuilder.Clear();
|
||||
|
||||
var rhi = _clydeInternal.Rhi;
|
||||
var info = _clydeInternal.DebugInfo;
|
||||
var stats = _clydeInternal.DebugStats;
|
||||
|
||||
_textBuilder.AppendLine($@"Renderer: {info.Renderer}
|
||||
Vendor: {info.Vendor}
|
||||
Version: {info.VersionString}");
|
||||
|
||||
if (info.Overriding)
|
||||
_textBuilder.Append($"Version override: {info.OpenGLVersion}\n");
|
||||
_textBuilder.AppendLine($@"RHI: {rhi.Description}
|
||||
Adapter: {rhi.AdapterProperties.Name}
|
||||
Vendor: {rhi.AdapterProperties.VendorName} ({rhi.AdapterProperties.VendorID})
|
||||
Driver: {rhi.AdapterProperties.Driver}");
|
||||
|
||||
_textBuilder.Append($"Windowing: {info.WindowingApi}\n");
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
<DevWindowTabPerf Name="Perf" />
|
||||
<DevWindowTabTextures Name="Textures" />
|
||||
<DevWindowTabRenderTargets Name="RenderTargets" />
|
||||
<DevWindowTabRenderer Name="Renderer" />
|
||||
</TabContainer>
|
||||
</Control>
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Robust.Client.UserInterface
|
||||
TabContainer.SetTabTitle(Perf, "Profiling");
|
||||
TabContainer.SetTabTitle(Textures, Loc.GetString("dev-window-tab-textures-title"));
|
||||
TabContainer.SetTabTitle(RenderTargets, Loc.GetString("dev-window-tab-render-targets-title"));
|
||||
TabContainer.SetTabTitle(Renderer, "Renderer");
|
||||
|
||||
Stylesheet =
|
||||
new DefaultStylesheet(IoCManager.Resolve<IResourceCache>(), IoCManager.Resolve<IUserInterfaceManager>()).Stylesheet;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Robust.Client.UserInterface.DevWindowTabRenderer">
|
||||
<TabContainer>
|
||||
<ScrollContainer Name="TabProperties">
|
||||
<GridContainer Name="PropertiesGrid" Columns="2" Margin="2" />
|
||||
</ScrollContainer>
|
||||
<ScrollContainer Name="TabLimits">
|
||||
<GridContainer Name="LimitsGrid" Columns="2" Margin="2" />
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,90 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde.Rhi;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DevWindowTabRenderer : Control
|
||||
{
|
||||
public DevWindowTabRenderer()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var clyde = IoCManager.Resolve<IClyde>();
|
||||
var rhi = clyde.Rhi;
|
||||
|
||||
LoadLimits(rhi.DeviceLimits);
|
||||
LoadProperties(rhi);
|
||||
|
||||
TabContainer.SetTabTitle(TabProperties, "Properties");
|
||||
TabContainer.SetTabTitle(TabLimits, "Limits");
|
||||
}
|
||||
|
||||
private void LoadProperties(RhiBase rhi)
|
||||
{
|
||||
Add("RHI description", rhi.Description);
|
||||
|
||||
var adapter = rhi.AdapterProperties;
|
||||
|
||||
Add("Adapter name", adapter.Name);
|
||||
Add("Adapter vendor", $"{adapter.VendorName} ({adapter.VendorID})");
|
||||
Add("Adapter driver", $"{adapter.Driver}");
|
||||
Add("Adapter architecture", $"{adapter.Architecture}");
|
||||
Add("Adapter backend", $"{adapter.BackendType}");
|
||||
Add("Adapter type", $"{adapter.AdapterType}");
|
||||
|
||||
void Add<T>(string name, T field) where T : notnull
|
||||
{
|
||||
PropertiesGrid.AddChild(new Label { Text = name, Margin = new Thickness(0, 0, 8, 0) });
|
||||
PropertiesGrid.AddChild(new Label { Text = field.ToString() });
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadLimits(RhiLimits limits)
|
||||
{
|
||||
// @formatter:off
|
||||
Add("MaxTextureDimension1D", limits.MaxTextureDimension1D);
|
||||
Add("MaxTextureDimension2D", limits.MaxTextureDimension2D);
|
||||
Add("MaxTextureDimension3D", limits.MaxTextureDimension3D);
|
||||
Add("MaxTextureArrayLayers", limits.MaxTextureArrayLayers);
|
||||
Add("MaxBindGroups", limits.MaxBindGroups);
|
||||
Add("MaxBindingsPerBindGroup", limits.MaxBindingsPerBindGroup);
|
||||
Add("MaxDynamicUniformBuffersPerPipelineLayout", limits.MaxDynamicUniformBuffersPerPipelineLayout);
|
||||
Add("MaxDynamicStorageBuffersPerPipelineLayout", limits.MaxDynamicStorageBuffersPerPipelineLayout);
|
||||
Add("MaxSampledTexturesPerShaderStage", limits.MaxSampledTexturesPerShaderStage);
|
||||
Add("MaxSamplersPerShaderStage", limits.MaxSamplersPerShaderStage);
|
||||
Add("MaxStorageBuffersPerShaderStage", limits.MaxStorageBuffersPerShaderStage);
|
||||
Add("MaxStorageTexturesPerShaderStage", limits.MaxStorageTexturesPerShaderStage);
|
||||
Add("MaxUniformBuffersPerShaderStage", limits.MaxUniformBuffersPerShaderStage);
|
||||
Add("MaxUniformBufferBindingSize", limits.MaxUniformBufferBindingSize);
|
||||
Add("MaxStorageBufferBindingSize", limits.MaxStorageBufferBindingSize);
|
||||
Add("MinUniformBufferOffsetAlignment", limits.MinUniformBufferOffsetAlignment);
|
||||
Add("MinStorageBufferOffsetAlignment", limits.MinStorageBufferOffsetAlignment);
|
||||
Add("MaxVertexBuffers", limits.MaxVertexBuffers);
|
||||
Add("MaxBufferSize", limits.MaxBufferSize);
|
||||
Add("MaxVertexAttributes", limits.MaxVertexAttributes);
|
||||
Add("MaxVertexBufferArrayStride", limits.MaxVertexBufferArrayStride);
|
||||
Add("MaxInterStageShaderComponents", limits.MaxInterStageShaderComponents);
|
||||
Add("MaxInterStageShaderVariables", limits.MaxInterStageShaderVariables);
|
||||
Add("MaxColorAttachments", limits.MaxColorAttachments);
|
||||
Add("MaxColorAttachmentBytesPerSample", limits.MaxColorAttachmentBytesPerSample);
|
||||
Add("MaxComputeWorkgroupStorageSize", limits.MaxComputeWorkgroupStorageSize);
|
||||
Add("MaxComputeInvocationsPerWorkgroup", limits.MaxComputeInvocationsPerWorkgroup);
|
||||
Add("MaxComputeWorkgroupSizeX", limits.MaxComputeWorkgroupSizeX);
|
||||
Add("MaxComputeWorkgroupSizeY", limits.MaxComputeWorkgroupSizeY);
|
||||
Add("MaxComputeWorkgroupSizeZ", limits.MaxComputeWorkgroupSizeZ);
|
||||
Add("MaxComputeWorkgroupsPerDimension", limits.MaxComputeWorkgroupsPerDimension);
|
||||
// @formatter:on
|
||||
|
||||
void Add<T>(string name, T field) where T : notnull
|
||||
{
|
||||
LimitsGrid.AddChild(new Label { Text = name, Margin = new Thickness(0, 0, 8, 0) });
|
||||
LimitsGrid.AddChild(new Label { Text = field.ToString() });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ internal sealed partial class UserInterfaceManager
|
||||
|
||||
if (control is WindowRoot root)
|
||||
{
|
||||
control.Measure(root.Window.RenderTarget.Size / root.UIScale);
|
||||
control.Measure(root.Window.Size / root.UIScale);
|
||||
}
|
||||
else if (control.PreviousMeasure.HasValue)
|
||||
{
|
||||
@@ -46,7 +46,7 @@ internal sealed partial class UserInterfaceManager
|
||||
|
||||
if (control is WindowRoot root)
|
||||
{
|
||||
control.Arrange(UIBox2.FromDimensions(Vector2.Zero, root.Window.RenderTarget.Size / root.UIScale));
|
||||
control.Arrange(UIBox2.FromDimensions(Vector2.Zero, root.Window.Size / root.UIScale));
|
||||
}
|
||||
else if (control.PreviousArrange.HasValue)
|
||||
{
|
||||
@@ -155,7 +155,10 @@ internal sealed partial class UserInterfaceManager
|
||||
|
||||
using (_prof.Group("Main"))
|
||||
{
|
||||
DoRender(_windowsToRoot[_clyde.MainWindow.Id]);
|
||||
renderHandle.RenderInRenderTarget(_clyde.MainWindow.RenderTarget, () =>
|
||||
{
|
||||
DoRender(_windowsToRoot[_clyde.MainWindow.Id]);
|
||||
}, Color.Pink);
|
||||
}
|
||||
|
||||
void DoRender(WindowRoot root)
|
||||
|
||||
@@ -102,8 +102,7 @@ internal partial class UserInterfaceManager
|
||||
//Grab the OS UIScale or the value set through CVAR debug
|
||||
var osScale = _configurationManager.GetCVar(CVars.DisplayUIScale);
|
||||
osScale = osScale == 0f ? root.Window.ContentScale.X : osScale;
|
||||
|
||||
var windowSize = root.Window.RenderTarget.Size;
|
||||
var windowSize = root.Window.Size;
|
||||
//Only run autoscale if it is enabled, otherwise default to just use OS UIScale
|
||||
if (!_autoScaleEnabled || root.DisableAutoScaling || windowSize.X <= 0 || windowSize.Y <= 0)
|
||||
return osScale;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Utility
|
||||
{
|
||||
internal static class OpenTKConversions
|
||||
{
|
||||
public static OpenToolkit.Mathematics.Color4 ConvertOpenTK(this Color color)
|
||||
{
|
||||
return new(color.R, color.G, color.B, color.A);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -759,6 +760,39 @@ namespace Robust.Shared.Maths
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds <paramref name="value"/> up to a multiple of <paramref name="power"/>,
|
||||
/// where <paramref name="power"/> is a power of two.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to round up.</param>
|
||||
/// <param name="power">The value to round up to a multiple of. Must be a power of two.</param>
|
||||
/// <typeparam name="T">The type of integer to operate on.</typeparam>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T CeilingPowerOfTwo<T>(T value, T power)
|
||||
where T : IBinaryInteger<T>
|
||||
{
|
||||
Debug.Assert((power & (power - T.One)) == T.Zero, "Power must be a power of two");
|
||||
|
||||
return (value + (power - T.One)) & ~(power - T.One);
|
||||
}
|
||||
|
||||
// Non-generic function has less overhead without compiler optimizations.
|
||||
/// <summary>
|
||||
/// Rounds <paramref name="value"/> up to a multiple of <paramref name="power"/>,
|
||||
/// where <paramref name="power"/> is a power of two.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to round up.</param>
|
||||
/// <param name="power">The value to round up to a multiple of. Must be a power of two.</param>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int CeilingPowerOfTwo(int value, int power)
|
||||
{
|
||||
Debug.Assert(BitOperations.IsPow2(power), "Power must be a power of two");
|
||||
|
||||
return (value + (power - 1)) & ~(power - 1);
|
||||
}
|
||||
|
||||
#endregion Public Members
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1037,136 +1037,12 @@ namespace Robust.Shared
|
||||
|
||||
// Clyde related enums are in Clyde.Constants.cs.
|
||||
|
||||
/// <summary>
|
||||
/// Which renderer to use to render the game.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DisplayRenderer =
|
||||
CVarDef.Create("display.renderer", 0, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use compatibility mode.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can change certain behaviors like GL version selection to try to avoid driver crashes/bugs.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> DisplayCompat =
|
||||
CVarDef.Create("display.compat", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Which OpenGL version to use for the OpenGL renderer.
|
||||
/// Values correspond to the (private) RendererOpenGLVersion enum in Clyde.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DisplayOpenGLVersion =
|
||||
CVarDef.Create("display.opengl_version", 0, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// On Windows, use ANGLE as OpenGL implementation.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayAngle =
|
||||
CVarDef.Create("display.angle", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Use a custom DXGI swap chain when using ANGLE.
|
||||
/// Should improve performance and fixes main window sRGB handling with ANGLE.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayAngleCustomSwapChain =
|
||||
CVarDef.Create("display.angle_custom_swap_chain", true, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Force ANGLE to create a GLES2 context (not a compatible GLES3 context).
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayAngleForceEs2 =
|
||||
CVarDef.Create("display.angle_force_es2", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Force ANGLE to create a context from a D3D11 FL 10_0 device.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayAngleForce10_0 =
|
||||
CVarDef.Create("display.angle_force_10_0", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Force usage of DXGI 1.1 when using custom swap chain setup.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayAngleDxgi1 =
|
||||
CVarDef.Create("display.angle_dxgi1", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Try to use the display adapter with this name, if the current renderer supports selecting it.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DisplayAdapter =
|
||||
CVarDef.Create("display.adapter", "", CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// What type of GPU to prefer when creating a graphics context, for things such as hybrid GPU laptops.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This setting is not always respect depending on platform and rendering API used.
|
||||
/// Values are:
|
||||
/// 0 = unspecified (DXGI_GPU_PREFERENCE_UNSPECIFIED)
|
||||
/// 1 = minimum power (DXGI_GPU_PREFERENCE_MINIMUM_POWER)
|
||||
/// 2 = high performance (DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE)
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> DisplayGpuPreference =
|
||||
CVarDef.Create("display.gpu_preference", 2, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Use EGL to create GL context instead of GLFW, if possible.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This only tries to use EGL if on a platform like X11 or Windows (w/ ANGLE) where it is possible.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> DisplayEgl =
|
||||
CVarDef.Create("display.egl", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Enable allowES3OnFL10_0 on ANGLE.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayAngleEs3On10_0 =
|
||||
CVarDef.Create("display.angle_es3_on_10_0", true, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Base DPI to render fonts at. This can be further scaled based on <c>display.uiScale</c>.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DisplayFontDpi =
|
||||
CVarDef.Create("display.fontdpi", 96, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Override detected OpenGL version, for testing.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DisplayOGLOverrideVersion =
|
||||
CVarDef.Create("display.ogl_override_version", string.Empty, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Run <c>glCheckError()</c> after (almost) every GL call.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayOGLCheckErrors =
|
||||
CVarDef.Create("display.ogl_check_errors", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Forces synchronization of multi-window rendering with <c>glFinish</c> when GL fence sync is unavailable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this is disabled multi-window rendering on GLES2 might run better, dunno.
|
||||
/// It technically causes UB thanks to the OpenGL spec with cross-context sync. Hope that won't happen.
|
||||
/// Let's be real the OpenGL specification is basically just a suggestion to drivers anyways so who cares.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> DisplayForceSyncWindows =
|
||||
CVarDef.Create<bool>("display.force_sync_windows", true, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Use a separate thread for multi-window blitting.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayThreadWindowBlit =
|
||||
CVarDef.Create("display.thread_window_blit", true, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic flag for testing. When using a separate thread for multi-window blitting,
|
||||
/// should the worker be unblocked before the SwapBuffers(). Setting to true may improve
|
||||
/// performance but may cause crashes or rendering errors.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayThreadUnlockBeforeSwap =
|
||||
CVarDef.Create("display.thread_unlock_before_swap", false, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Buffer size of input command channel from windowing thread to main game thread.
|
||||
/// </summary>
|
||||
@@ -1219,6 +1095,37 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<bool> DisplayThreadWindowApi =
|
||||
CVarDef.Create("display.thread_window_api", false, CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<string> DisplayRhi =
|
||||
CVarDef.Create("display.rhi", "webGpu", CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Comma-separated list of backends to try to use, if using WebGPU RHI with wgpu.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If set to "all", all backends will be tried.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<string> DisplayWgpuBackends =
|
||||
CVarDef.Create("display.wgpu_backends", "all", CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Try to use the display adapter with this name, if the current renderer supports selecting it.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> DisplayAdapter =
|
||||
CVarDef.Create("display.adapter", "", CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// What type of GPU to prefer when creating a graphics context, for things such as hybrid GPU laptops.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This setting is not always respect depending on platform and rendering API used.
|
||||
/// Values are:
|
||||
/// 0 = unspecified
|
||||
/// 1 = low power
|
||||
/// 2 = high performance
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> DisplayGpuPowerPreference =
|
||||
CVarDef.Create("display.gpu_power_preference", 2, CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* AUDIO
|
||||
*/
|
||||
|
||||
@@ -323,5 +323,27 @@ namespace Robust.UnitTesting.Shared.Utility
|
||||
{
|
||||
return MathHelper.CeilMultipleOfPowerOfTwo(value, powerOfTwo);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(5, 1, ExpectedResult = 5)]
|
||||
[TestCase(1, 4, ExpectedResult = 4)]
|
||||
[TestCase(4, 4, ExpectedResult = 4)]
|
||||
[TestCase(5, 4, ExpectedResult = 8)]
|
||||
[TestCase(19, 4, ExpectedResult = 20)]
|
||||
public int TestCeilingPowerOfTwoInt(int value, int power)
|
||||
{
|
||||
return MathHelper.CeilingPowerOfTwo(value, power);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(5, 1, ExpectedResult = 5)]
|
||||
[TestCase(1, 4, ExpectedResult = 4)]
|
||||
[TestCase(4, 4, ExpectedResult = 4)]
|
||||
[TestCase(5, 4, ExpectedResult = 8)]
|
||||
[TestCase(19, 4, ExpectedResult = 20)]
|
||||
public short TestCeilingPowerOfTwoGeneric(short value, short power)
|
||||
{
|
||||
return MathHelper.CeilingPowerOfTwo(value, power);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user