Fix null warnings in StandardRenderSystem, remove SceneGraph module, temporarily disable nullables in TestGame.

This commit is contained in:
2024-10-15 20:11:06 +02:00
parent d5601c9dea
commit ac43340866
24 changed files with 61 additions and 1065 deletions

View File

@@ -1,12 +0,0 @@
using Voile.Rendering;
using Voile.SceneGraph;
namespace Voile;
public class Circle2d : Drawable2d
{
public override void OnDraw(RenderSystem renderer)
{
renderer.DrawCircle(32f, Color.AliceBlue);
}
}

View File

@@ -4,6 +4,7 @@ using Voile.Utils;
using Voile.Input;
using Voile.Systems.Particles;
using System.Numerics;
using System.Diagnostics.CodeAnalysis;
public class TestGame : Game
{
@@ -12,6 +13,7 @@ public class TestGame : Game
public override void Initialize()
{
InitializeDefault();
_particleSystem = new ParticleSystem();
ResourceManager.AddResourceLoaderAssociation(new ParticleEmitterSettingsResourceLoader());
@@ -33,13 +35,13 @@ public class TestGame : Game
if (!ResourceManager.TryLoad("test_emitter.toml", out _emitterSettings))
{
throw new Exception("Failed to load emitter settings!");
}
}
protected override void Ready()
{
_emitterId = _particleSystem!.CreateEmitter(Renderer.WindowSize / 2, _emitterSettings);
_emitterId = _particleSystem.CreateEmitter(Renderer.WindowSize / 2, _emitterSettings);
}
protected override void Run()
@@ -87,8 +89,8 @@ public class TestGame : Game
Renderer.EndBlended();
}
private ParticleSystem? _particleSystem;
[NotNull] private ParticleSystem _particleSystem;
private int _emitterId;
private ResourceRef<ParticleEmitterSettingsResource>? _emitterSettings;
private ResourceRef<ParticleEmitterSettingsResource> _emitterSettings;
private Logger _logger = new(nameof(TestGame));
}

View File

@@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
<PublishAot>true</PublishAot>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup>

View File

@@ -1,44 +0,0 @@
using Voile;
using Voile.Extensions;
using Voile.SceneGraph;
using Voile.Utils;
public class TestPlayer : RectangleShape2d
{
public float SprintSpeed { get; set; } = 400f;
protected override void OnStart()
{
base.OnStart();
Color = Color.Cyan;
_camera = new Camera2d()
{
Current = true,
};
if (Layer is not null)
{
Layer.AddEntity(_camera);
}
}
protected override void OnUpdate(double dt)
{
base.OnUpdate(dt);
var sprinting = Input.IsActionDown("sprint");
_speed = sprinting ? SprintSpeed : 200f;
var velocity = Input.GetInputDirection("left", "right", "up", "down") * _speed;
Position += velocity * (float)dt;
if (_camera is not null)
{
_camera.Position = _camera.Position.Lerp(Position, dt * 5f);
}
}
private Logger _logger = new(nameof(TestPlayer));
private float _speed = 200f;
private Camera2d? _camera;
}

View File

@@ -1,98 +0,0 @@
using System.Numerics;
using Voile;
using Voile.Rendering;
using Voile.SceneGraph;
using Voile.UI;
using Voile.Utils;
public class UiLayer : Layer
{
protected override void OnStart()
{
base.OnStart();
GetResources();
CreateUiElements();
}
protected override void OnUpdate(double dt)
{
base.OnUpdate(dt);
if (Scene is null) return;
_screenContainer.UpdateRect(Vector2.Zero, Scene.Renderer.WindowSize);
}
protected override void OnBeginDraw(RenderSystem renderer)
{
}
protected override void OnDraw(RenderSystem renderer)
{
_screenContainer.Render(renderer);
}
protected override void OnEndDraw(RenderSystem renderer)
{
}
private void CreateUiElements()
{
if (Scene is null) return;
_screenContainer.UpdateRect(Vector2.Zero, Scene.Renderer.WindowSize);
var style = new PanelStyle()
{
BackgroundColor = new Color(0.2f, 0.2f, 0.2f, 1.0f)
};
_panel = new MarginPanel(style)
{
ExpandRatio = new Vector2(0.5f, 1f),
AbsoluteMargin = Vector2.One * 16f
};
_screenContainer.AddChild(_panel);
var exampleText = new TextLabel()
{
Font = _defaultFont!,
FontSize = 20,
Text = "This Panel will occupy 50% of the screen.\nThis text in particular is inside a panel with an absolute margin of 16px.\nHow cool is that?"
};
var contentPanel = new MarginPanel(new PanelStyle()
{
BackgroundColor = new Color(0.25f, 0.25f, 0.25f, 1.0f)
})
{
AbsoluteMargin = Vector2.One * 32f
};
_panel.AddChild(contentPanel);
var verticalPanel = new MarginPanel(new PanelStyle()
{
BackgroundColor = new Color(0.1f, 0.1f, 0.1f, 1.0f),
})
{
AbsoluteMargin = Vector2.One * 8f,
};
contentPanel.AddChild(verticalPanel);
verticalPanel.AddChild(exampleText);
}
private void GetResources()
{
ResourceManager.TryGetResource("inter_regular", out _defaultFont);
}
private Font? _defaultFont;
private Container _screenContainer = new();
private Panel? _panel;
private Logger _logger = new(nameof(UiLayer));
}

View File

@@ -1,30 +0,0 @@
using System.Numerics;
using Voile;
using Voile.Rendering;
using Voile.SceneGraph;
public class World : Drawable2d
{
protected override void OnStart()
{
base.OnStart();
_randomPositions = new Vector2[_rectangleAmount];
for (int i = 0; i < _rectangleAmount - 1; i++)
{
_randomPositions[i] = MathUtils.RandomVector2(Position, Vector2.One * 2048f);
}
}
public override void OnDraw(RenderSystem renderer)
{
for (int i = 0; i < _rectangleAmount; i++)
{
renderer.SetTransform(_randomPositions[i], PivotOffset, 0);
renderer.DrawRectangle(Vector2.One * 16f, _rectangleColor);
}
}
private Color _rectangleColor = new Color(0.25f, 0.25f, 0.25f, 1.0f);
private int _rectangleAmount = 6000;
private Vector2[] _randomPositions;
}

View File

@@ -67,6 +67,21 @@ namespace Voile
/// </summary>
public void Start()
{
if (Renderer is null)
{
throw new NullReferenceException("No renderer provided.");
}
if (Input is null)
{
throw new NullReferenceException("No input system provided.");
}
if (ResourceManager is null)
{
throw new NullReferenceException("No ResourceManager provided.");
}
Initialize();
LoadResources();
Ready();

View File

@@ -15,7 +15,7 @@ namespace Voile.Rendering
public class StandardRenderSystem : RenderSystem
{
/// <inheritdoc />
public override Vector2 WindowSize { get; set; }
public override Vector2 WindowSize { get => _windowSize; set { _windowSize = value; } }
/// <inheritdoc />
public override bool ShouldRun => !_window?.IsClosing ?? false;
@@ -189,7 +189,7 @@ namespace Voile.Rendering
private unsafe void CreateApi()
{
_wgpu = WebGPU.GetApi();
_wgpu = WebGPU.GetApi() ?? throw new Exception("Failed to initialize WebGPU API.");
}
private unsafe void CreateInstance()
@@ -205,6 +205,11 @@ namespace Voile.Rendering
private unsafe void CreateAdapter()
{
if (_wgpu is null)
{
throw new Exception("WebGPU API is not initialized!");
}
RequestAdapterOptions adapterOptions = new()
{
CompatibleSurface = _surface,
@@ -224,13 +229,13 @@ namespace Voile.Rendering
_wgpu.AdapterGetProperties(_adapter, &adapterProperties);
string deviceName = Marshal.PtrToStringAnsi((IntPtr)adapterProperties.Name);
string deviceName = Marshal.PtrToStringAnsi((IntPtr)adapterProperties.Name) ?? string.Empty;
_logger.Info($"Retrieved WebGPU Adapter ({deviceName})");
}
else
{
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr);
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
_logger.Error($"Error while retrieving WebGPU Adapter: {msg}");
}
});
@@ -240,6 +245,11 @@ namespace Voile.Rendering
private unsafe void CreateDevice()
{
if (_wgpu is null)
{
throw new Exception("WebGPU API is not initialized!");
}
DeviceDescriptor deviceDescriptor = new();
PfnRequestDeviceCallback callback = PfnRequestDeviceCallback.From(
@@ -252,7 +262,7 @@ namespace Voile.Rendering
}
else
{
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr);
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
_logger.Error($"Error while retrieving WebGPU Device: {msg}");
}
});
@@ -262,6 +272,11 @@ namespace Voile.Rendering
private unsafe void ConfigureSurface()
{
if (_wgpu is null)
{
throw new Exception("WebGPU API is not initialized!");
}
SurfaceConfiguration configuration = new SurfaceConfiguration
{
Device = _device,
@@ -272,14 +287,19 @@ namespace Voile.Rendering
Usage = TextureUsage.RenderAttachment
};
_wgpu!.SurfaceConfigure(_surface, configuration);
_wgpu.SurfaceConfigure(_surface, configuration);
}
private unsafe void ConfigureDebugCallback()
{
if (_wgpu is null)
{
throw new Exception("WebGPU API is not initialized!");
}
PfnErrorCallback callback = PfnErrorCallback.From((type, msgPtr, userDataPtr) =>
{
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr);
string msg = Marshal.PtrToStringAnsi((IntPtr)msgPtr) ?? string.Empty;
_logger.Error($"WebGPU Error: {msg}");
});
@@ -289,6 +309,11 @@ namespace Voile.Rendering
private unsafe void WgpuBeginFrame()
{
if (_wgpu is null)
{
throw new Exception("WebGPU API is not initialized!");
}
_queue = _wgpu.DeviceGetQueue(_device);
_commandEncoder = _wgpu.DeviceCreateCommandEncoder(_device, null);
@@ -309,6 +334,11 @@ namespace Voile.Rendering
private unsafe void WgpuEndFrame()
{
if (_wgpu is null)
{
throw new Exception("WebGPU API is not initialized!");
}
_wgpu.RenderPassEncoderEnd(_renderPassEncoder);
var commandBuffer = _wgpu.CommandEncoderFinish(_commandEncoder, null);
_wgpu.CommandEncoderRelease(_commandEncoder);
@@ -325,7 +355,7 @@ namespace Voile.Rendering
private unsafe void ShutdownUnsafe()
{
_wgpu.DeviceDestroy(_device);
_wgpu!.DeviceDestroy(_device);
_wgpu.InstanceRelease(_instance);
_wgpu.SurfaceRelease(_surface);
_wgpu.AdapterRelease(_adapter);
@@ -365,7 +395,7 @@ namespace Voile.Rendering
}
private Vector2 _windowSize;
private Vector2 _windowSize = Vector2.Zero;
private IWindow? _window;
private Color _clearColor = Color.Black;

View File

@@ -1,25 +0,0 @@
using System.Numerics;
namespace Voile.SceneGraph;
public class Camera2d : Entity2d
{
public Vector2 Offset { get; set; }
public float Zoom { get; set; } = 1f;
public bool Current
{
get => _current; set
{
_current = value;
Layer?.UpdateCurrentCamera();
}
}
protected override void OnStart()
{
base.OnStart();
Offset = Renderer.WindowSize / 2;
}
private bool _current;
}

View File

@@ -1,18 +0,0 @@
using Voile.Rendering;
namespace Voile.SceneGraph
{
public class CircleShape2d : Drawable2d
{
public float Radius { get => _radius; set => _radius = value; }
public Color Color { get => _color; set => _color = value; }
public override void OnDraw(RenderSystem renderer)
{
renderer.DrawCircle(_radius, _color);
}
private float _radius = 16;
private Color _color = Color.White;
}
}

View File

@@ -1,17 +0,0 @@
using System.Numerics;
using Voile.Rendering;
namespace Voile.SceneGraph
{
public abstract class Drawable2d : Entity2d, IDrawable
{
public Vector2 PivotOffset { get; set; }
public void Draw(RenderSystem renderer)
{
renderer.SetTransform(Position, PivotOffset, Rotation);
OnDraw(renderer);
}
public abstract void OnDraw(RenderSystem renderer);
}
}

View File

@@ -1,38 +0,0 @@
using System.Text.Json.Serialization;
using Voile.Audio;
using Voile.Input;
using Voile.Rendering;
namespace Voile.SceneGraph
{
public class Entity
{
[JsonIgnore] public EntityLayer? Layer { get; set; }
[JsonIgnore] public InputSystem Input => Layer!.Scene.Input;
[JsonIgnore] public AudioSystem Audio => Layer!.Scene.Audio;
[JsonIgnore] public RenderSystem Renderer => Layer!.Scene.Renderer;
public int Id { get; set; }
public void Start() => OnStart();
public void Update(double dt) => OnUpdate(dt);
public void ReceiveInput(InputSystem input) => OnInput(input);
protected virtual void OnStart() { }
protected virtual void OnDestroy() { }
protected virtual void OnUpdate(double dt) { }
protected virtual void OnInput(InputSystem input) { }
public void Destroy()
{
OnDestroy();
Layer?.DestroyEntity(Id);
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Entity))]
internal partial class EntitySourceGenerationContext : JsonSerializerContext
{
}
}

View File

@@ -1,10 +0,0 @@
using System.Numerics;
namespace Voile.SceneGraph
{
public class Entity2d : Entity
{
public Vector2 Position { get; set; }
public float Rotation { get; set; }
}
}

View File

@@ -1,9 +0,0 @@
using Voile.Rendering;
namespace Voile.SceneGraph
{
public interface IDrawable
{
public void Draw(RenderSystem renderer);
}
}

View File

@@ -1,15 +0,0 @@
using System.Numerics;
using Voile.Rendering;
namespace Voile.SceneGraph;
public class RectangleShape2d : Drawable2d
{
public Vector2 Size { get; set; } = Vector2.One * 32;
public Color Color { get; set; } = Color.White;
public override void OnDraw(RenderSystem renderer)
{
PivotOffset = Size / 2;
renderer.DrawRectangle(Size, Color);
}
}

View File

@@ -1,23 +0,0 @@
using System.Drawing;
using Voile.Rendering;
namespace Voile.SceneGraph
{
public class Sprite2d : Drawable2d
{
public Texture2d Texture { get => _texture ?? Texture2d.Empty; set => _texture = value; }
protected override void OnStart()
{
var renderer = Layer.Scene.Renderer;
}
public override void OnDraw(RenderSystem renderer)
{
renderer.DrawTexture(_texture!, Color.White);
}
private Texture2d? _texture;
}
}

View File

@@ -1,42 +0,0 @@
using Voile.Rendering;
using System.Drawing;
namespace Voile.SceneGraph
{
public class Text2d : Drawable2d
{
public string Text { get => _text; set => _text = value; }
public Color FontColor { get => _fontColor; set => _fontColor = value; }
public Font Font
{
get => _font; set
{
_isDirty = true;
_font = value;
}
}
public Text2d(Font font)
{
_font = font;
}
public override void OnDraw(RenderSystem renderer)
{
if (_font == null)
{
renderer.DrawDebugText(_text, 20, _fontColor);
}
else
{
renderer.DrawText(_font, _text, _fontColor);
}
}
private string _text = string.Empty;
private Color _fontColor = Color.White;
private Font _font;
private int _fontHandle;
private bool _isDirty;
}
}

View File

@@ -1,116 +0,0 @@
using System.Text.Json.Serialization;
using Voile.Rendering;
using Voile.Utils;
namespace Voile.SceneGraph
{
public class EntityLayer : Layer
{
public List<Entity> Entities { get; set; }
public Camera2d? CurrentCamera { get; set; }
public EntityLayer(List<Entity> entities)
{
Entities = entities;
}
public EntityLayer()
{
Entities = new List<Entity>();
}
public void UpdateCurrentCamera()
{
if (_cameraEntities.Count == 1)
{
CurrentCamera = _cameraEntities[0];
return;
}
else
{
foreach (var camera in _cameraEntities)
{
if (camera.Current) CurrentCamera = camera;
}
}
}
public bool AddEntity(Entity entity)
{
entity.Id = Entities.Count;
entity.Layer = this;
if (entity is Camera2d camera2d)
{
_cameraEntities.Add(camera2d);
UpdateCurrentCamera();
}
Entities.Add(entity);
return true;
}
public void DestroyEntity(int at)
{
Entities.RemoveAt(at);
}
protected override void OnStart()
{
for (int i = 0; i < Entities.Count; i++)
{
var entity = Entities[i];
entity.Layer = this;
entity.Start();
}
}
protected override void OnUpdate(double dt)
{
foreach (var entity in Entities)
{
entity.Update(dt);
}
}
protected override void OnBeginDraw(RenderSystem renderer)
{
if (CurrentCamera is not null)
{
renderer.BeginCamera2d(CurrentCamera.Offset, CurrentCamera.Position, 0f, CurrentCamera.Zoom);
}
}
protected override void OnEndDraw(RenderSystem renderer)
{
if (CurrentCamera is not null)
{
renderer.EndCamera2d();
}
}
protected override void OnDraw(RenderSystem renderer)
{
// TODO: can be done more efficiently, needs rendering redesign.
foreach (var entity in Entities)
{
if (entity is IDrawable drawable)
{
drawable.Draw(renderer);
}
}
}
private List<Camera2d> _cameraEntities = new();
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(EntityLayer))]
[JsonSerializable(typeof(Entity))]
[JsonSerializable(typeof(List<Entity>))]
internal partial class EntityLayerContext : JsonSerializerContext
{
}
}

View File

@@ -1,11 +0,0 @@
namespace Voile.SceneGraph
{
public interface IMainLoop
{
void Init();
void Start();
void Update();
double DeltaTime { get; }
bool ShouldRun { get; }
}
}

View File

@@ -1,29 +0,0 @@
using Voile.Resources;
using Voile.Rendering;
using System.Text.Json.Serialization;
using Voile.Input;
namespace Voile.SceneGraph
{
public abstract class Layer : IDrawable
{
[JsonIgnore] public Scene? Scene { get; set; }
[JsonIgnore] public InputSystem? Input { get; set; }
[JsonIgnore] public ResourceManager ResourceManager => Scene!.ResourceManager;
public void BeginDraw(RenderSystem renderer) => OnBeginDraw(renderer);
public void Draw(RenderSystem renderer) => OnDraw(renderer);
public void EndDraw(RenderSystem renderer) => OnEndDraw(renderer);
public void Start() => OnStart();
public void Update(double dt) => OnUpdate(dt);
public void ReceiveInput(InputSystem input) => OnInput(input);
protected virtual void OnStart() { }
protected virtual void OnUpdate(double dt) { }
protected virtual void OnInput(InputSystem input) { }
protected abstract void OnBeginDraw(RenderSystem renderer);
protected abstract void OnDraw(RenderSystem renderer);
protected abstract void OnEndDraw(RenderSystem renderer);
}
}

View File

@@ -1,28 +0,0 @@
using System.Text.Json.Serialization;
namespace Voile.SceneGraph
{
public class SerializedScene : Resource
{
public Dictionary<string, Layer> Layers { get; set; }
public byte[]? Buffer { get; set; }
public long BufferSize { get; set; }
public SerializedScene(string path, byte[] buffer) : base(path)
{
Buffer = buffer;
}
}
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(SerializedScene))]
[JsonSerializable(typeof(Dictionary<string, Layer>))]
[JsonSerializable(typeof(Entity2d))]
[JsonSerializable(typeof(Layer))]
[JsonSerializable(typeof(EntityLayer))]
internal partial class SerializedSceneContext : JsonSerializerContext
{
}
}

View File

@@ -1,23 +0,0 @@
using Voile.Resources;
using Voile.Utils;
namespace Voile.SceneGraph
{
public class SerializedSceneSaver : IResourceSaver<SerializedScene>
{
public bool TrySave(string path, in SerializedScene resource)
{
if (resource.Buffer is null)
{
_logger.Error($"Tried to save a resource at \"{path}\" with a null buffer!");
return false;
}
File.WriteAllBytes(path, resource.Buffer);
return true;
}
private Logger _logger = new(nameof(SerializedSceneSaver));
}
}

View File

@@ -1,131 +0,0 @@
using System.Text.Json;
using Voile.Audio;
using Voile.Input;
using Voile.Rendering;
using Voile.Resources;
namespace Voile.SceneGraph
{
public class Scene : IMainLoop
{
public RenderSystem Renderer { get => _renderer; set => _renderer = value; }
public InputSystem? Input { get => _input; set => _input = value; }
public AudioSystem? Audio => _audioBackend;
public ResourceManager ResourceManager => _resourceManager;
public double DeltaTime => _renderer.FrameTime;
public bool ShouldRun => Renderer.ShouldRun;
public Scene(SceneSettings settings)
{
_renderer = settings.Renderer;
_input = settings.InputHandler;
_audioBackend = settings.AudioBackend;
_resourceManager = settings.ResourceManager;
}
public static Scene FromSerialized(SerializedScene serializedScene, SceneSettings settings)
{
var scene = new Scene(settings);
scene.WithLayers(serializedScene.Layers);
return scene;
}
public bool TrySerialize(out SerializedScene serializedScene)
{
serializedScene = new SerializedScene(string.Empty, new byte[] { })
{
Layers = _layers
};
serializedScene.Buffer = JsonSerializer.SerializeToUtf8Bytes(serializedScene, new JsonSerializerOptions
{
TypeInfoResolver = SerializedSceneContext.Default
});
serializedScene.BufferSize = serializedScene.Buffer.LongLength;
return true;
}
public void WithLayers(Dictionary<string, Layer> layers)
{
_layers = layers;
}
public void Init() => SetupRenderer();
public void Start()
{
foreach (var layer in _layers.Values)
{
if (_input is not null)
{
layer.Input = _input;
}
layer.Start();
}
}
public void Update()
{
foreach (var layer in _layers)
{
layer.Value.Update(DeltaTime);
}
Audio?.Update(DeltaTime);
}
public void AddLayer(string name, Layer layer)
{
layer.Scene = this;
_layers.Add(name, layer);
}
public void BeginDraw()
{
Renderer.BeginFrame();
Renderer.ClearBackground(Color.Black);
foreach (var layer in _layers.Values)
{
layer.BeginDraw(_renderer);
layer.Draw(_renderer);
}
Renderer.ResetTransform();
}
public void EndDraw()
{
foreach (var layer in _layers.Values)
{
layer.EndDraw(_renderer);
}
Renderer.EndFrame();
}
private void SetupRenderer()
{
Renderer.Initialize(new RendererSettings { Msaa = Msaa.Msaa4x, UseVSync = true });
}
private Dictionary<string, Layer> _layers = new();
private RenderSystem _renderer;
private AudioSystem? _audioBackend;
private InputSystem? _input;
private ResourceManager _resourceManager;
}
public struct SceneSettings
{
public RenderSystem Renderer { get; set; }
public AudioSystem AudioBackend { get; set; }
public InputSystem InputHandler { get; set; }
public ResourceManager ResourceManager { get; set; }
}
}

View File

@@ -1,332 +0,0 @@
using System.Numerics;
using ImGuiNET;
using Raylib_cs;
using Voile.Input;
using Voile.Rendering;
using KeyboardKey = Voile.Input.KeyboardKey;
using MouseButton = Voile.Input.MouseButton;
namespace Voile.SceneGraph
{
public class ImGuiRenderLayer : Layer
{
protected override void OnDraw(RenderSystem renderer)
{
Layout();
_controller.Draw(renderer);
}
protected virtual void Layout() { }
protected override void OnStart()
{
_controller = new ImGuiController();
_controller.Load(Scene.Renderer.WindowSize);
}
protected override void OnUpdate(double dt)
{
// _controller.Update(dt, Input);
}
protected override void OnBeginDraw(RenderSystem renderer)
{
throw new NotImplementedException();
}
protected override void OnEndDraw(RenderSystem renderer)
{
throw new NotImplementedException();
}
private ImGuiController _controller;
}
public class ImGuiController : IDisposable, IDrawable
{
public ImGuiController()
{
_context = ImGui.CreateContext();
ImGui.SetCurrentContext(_context);
}
public void Dispose()
{
ImGui.DestroyContext();
}
public void Load(Vector2 size)
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.AddFontDefault();
Resize(size);
LoadFontTexture();
SetupInput();
ImGui.NewFrame();
}
unsafe void LoadFontTexture()
{
ImGuiIOPtr io = ImGui.GetIO();
io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height);
// TODO: use engine API instead of Raylib.
Image image = new Image
{
data = pixels,
width = width,
height = height,
mipmaps = 1,
format = PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,
};
_fontTexture = Raylib.LoadTextureFromImage(image);
io.Fonts.SetTexID(new IntPtr(_fontTexture.id));
io.Fonts.ClearTexData();
}
private void SetupInput()
{
ImGuiIOPtr io = ImGui.GetIO();
// Setup config flags
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
// Setup back-end capabilities flags
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
io.KeyMap[(int)ImGuiKey.Tab] = (int)KeyboardKey.Tab;
io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)KeyboardKey.Left;
io.KeyMap[(int)ImGuiKey.RightArrow] = (int)KeyboardKey.Right;
io.KeyMap[(int)ImGuiKey.UpArrow] = (int)KeyboardKey.Up;
io.KeyMap[(int)ImGuiKey.DownArrow] = (int)KeyboardKey.Down;
io.KeyMap[(int)ImGuiKey.PageUp] = (int)KeyboardKey.PageUp;
io.KeyMap[(int)ImGuiKey.PageDown] = (int)KeyboardKey.PageDown;
io.KeyMap[(int)ImGuiKey.Home] = (int)KeyboardKey.Home;
io.KeyMap[(int)ImGuiKey.End] = (int)KeyboardKey.End;
io.KeyMap[(int)ImGuiKey.Insert] = (int)KeyboardKey.Insert;
io.KeyMap[(int)ImGuiKey.Delete] = (int)KeyboardKey.Delete;
io.KeyMap[(int)ImGuiKey.Backspace] = (int)KeyboardKey.Backspace;
io.KeyMap[(int)ImGuiKey.Space] = (int)KeyboardKey.Spacebar;
io.KeyMap[(int)ImGuiKey.Enter] = (int)KeyboardKey.Enter;
io.KeyMap[(int)ImGuiKey.Escape] = (int)KeyboardKey.Escape;
io.KeyMap[(int)ImGuiKey.A] = (int)KeyboardKey.A;
io.KeyMap[(int)ImGuiKey.C] = (int)KeyboardKey.C;
io.KeyMap[(int)ImGuiKey.V] = (int)KeyboardKey.V;
io.KeyMap[(int)ImGuiKey.X] = (int)KeyboardKey.X;
io.KeyMap[(int)ImGuiKey.Y] = (int)KeyboardKey.Y;
io.KeyMap[(int)ImGuiKey.Z] = (int)KeyboardKey.Z;
}
public void Resize(Vector2 size)
{
ImGuiIOPtr io = ImGui.GetIO();
io.DisplaySize = size / _scaleFactor;
}
public void Update(double dt, InputSystem input)
{
ImGuiIOPtr io = ImGui.GetIO();
io.DisplayFramebufferScale = Vector2.One;
io.DeltaTime = (float)dt;
UpdateKeyboard(input);
UpdateMouse(input);
ImGui.NewFrame();
}
private void UpdateKeyboard(InputSystem input)
{
ImGuiIOPtr io = ImGui.GetIO();
// Modifiers are not reliable across systems
io.KeyCtrl = io.KeysDown[(int)KeyboardKey.LeftControl] || io.KeysDown[(int)KeyboardKey.RightControl];
io.KeyShift = io.KeysDown[(int)KeyboardKey.LeftShift] || io.KeysDown[(int)KeyboardKey.RightShift];
io.KeyAlt = io.KeysDown[(int)KeyboardKey.LeftAlt] || io.KeysDown[(int)KeyboardKey.RightAlt];
io.KeySuper = io.KeysDown[(int)KeyboardKey.LeftSuper] || io.KeysDown[(int)KeyboardKey.RightSuper];
// Key states
for (int i = (int)KeyboardKey.Spacebar; i < (int)KeyboardKey.KBMenu + 1; i++)
{
io.KeysDown[i] = input.IsKeyboardKeyDown((KeyboardKey)i);
}
// Key input
int keyPressed = input.GetCharPressed();
if (keyPressed != 0)
{
io.AddInputCharacter((uint)keyPressed);
}
}
private void UpdateMouse(InputSystem input)
{
ImGuiIOPtr io = ImGui.GetIO();
// Store button states
for (int i = 0; i < io.MouseDown.Count; i++)
{
io.MouseDown[i] = input.IsMouseButtonDown((MouseButton)i);
}
// Mouse scroll
io.MouseWheel += input.GetMouseWheelMovement();
// Mouse position
Vector2 mousePosition = io.MousePos;
// TODO:
// bool focused = Raylib.IsWindowFocused();
if (io.WantSetMousePos)
{
input.SetMousePosition(mousePosition);
}
else
{
io.MousePos = input.GetMousePosition();
}
// Mouse cursor state
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) == 0 || input.IsCursorHidden())
{
ImGuiMouseCursor cursor = ImGui.GetMouseCursor();
if (cursor == ImGuiMouseCursor.None || io.MouseDrawCursor)
{
input.HideCursor();
}
else
{
input.ShowCursor();
}
}
}
private void Render()
{
ImGui.Render();
}
private void RenderCommandLists(ImDrawDataPtr data)
{
// Scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fbWidth = (int)(data.DisplaySize.X * data.FramebufferScale.X);
int fbHeight = (int)(data.DisplaySize.Y * data.FramebufferScale.Y);
// Avoid rendering if display is minimized or if the command list is empty
if (fbWidth <= 0 || fbHeight <= 0 || data.CmdListsCount == 0)
{
return;
}
Rlgl.rlDrawRenderBatchActive();
Rlgl.rlDisableBackfaceCulling();
Rlgl.rlEnableScissorTest();
data.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale);
for (int n = 0; n < data.CmdListsCount; n++)
{
int idxOffset = 0;
ImDrawListPtr cmdList = data.CmdListsRange[n];
// Vertex buffer and index buffer generated by DearImGui
ImPtrVector<ImDrawVertPtr> vtxBuffer = cmdList.VtxBuffer;
ImVector<ushort> idxBuffer = cmdList.IdxBuffer;
for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++)
{
ImDrawCmdPtr pcmd = cmdList.CmdBuffer[cmdi];
// Scissor rect
Vector2 pos = data.DisplayPos;
int rectX = (int)((pcmd.ClipRect.X - pos.X) * data.FramebufferScale.X);
int rectY = (int)((pcmd.ClipRect.Y - pos.Y) * data.FramebufferScale.Y);
int rectW = (int)((pcmd.ClipRect.Z - rectX) * data.FramebufferScale.Y);
int rectH = (int)((pcmd.ClipRect.W - rectY) * data.FramebufferScale.Y);
Rlgl.rlScissor(rectX, Raylib.GetScreenHeight() - (rectY + rectH), rectW, rectH);
if (pcmd.UserCallback != IntPtr.Zero)
{
// pcmd.UserCallback(cmdList, pcmd);
idxOffset += (int)pcmd.ElemCount;
}
else
{
DrawTriangles(pcmd.ElemCount, idxOffset, (int)pcmd.VtxOffset, idxBuffer, vtxBuffer, pcmd.TextureId);
idxOffset += (int)pcmd.ElemCount;
Rlgl.rlDrawRenderBatchActive();
}
}
}
Rlgl.rlSetTexture(0);
Rlgl.rlDisableScissorTest();
Rlgl.rlEnableBackfaceCulling();
}
private Color GetColor(uint hexValue)
{
Color color = new Color();
color.R = (byte)(hexValue & 0xFF) / 255f;
color.G = (byte)((hexValue >> 8) & 0xFF) / 255f;
color.B = (byte)((hexValue >> 16) & 0xFF) / 255f;
color.A = (byte)((hexValue >> 24) & 0xFF) / 255f;
return color;
}
void DrawTriangleVertex(ImDrawVertPtr idxVert)
{
Color c = GetColor(idxVert.col);
Rlgl.rlColor4ub((byte)Math.Round(c.R * 255f), (byte)Math.Round(c.G * 255f), (byte)Math.Round(c.B * 255f), (byte)Math.Round(c.A * 255f));
Rlgl.rlTexCoord2f(idxVert.uv.X, idxVert.uv.Y);
Rlgl.rlVertex2f(idxVert.pos.X, idxVert.pos.Y);
}
// Draw the imgui triangle data
private void DrawTriangles(uint count, int idxOffset, int vtxOffset, ImVector<ushort> idxBuffer, ImPtrVector<ImDrawVertPtr> idxVert, IntPtr textureId)
{
ushort index = 0;
ImDrawVertPtr vertex;
if (Rlgl.rlCheckRenderBatchLimit((int)count * 3))
{
Rlgl.rlDrawRenderBatchActive();
}
Rlgl.rlBegin(DrawMode.TRIANGLES);
Rlgl.rlSetTexture((uint)textureId);
for (int i = 0; i <= (count - 3); i += 3)
{
index = idxBuffer[idxOffset + i];
vertex = idxVert[vtxOffset + index];
DrawTriangleVertex(vertex);
index = idxBuffer[idxOffset + i + 1];
vertex = idxVert[vtxOffset + index];
DrawTriangleVertex(vertex);
index = idxBuffer[idxOffset + i + 2];
vertex = idxVert[vtxOffset + index];
DrawTriangleVertex(vertex);
}
Rlgl.rlEnd();
}
public void Draw(RenderSystem renderer)
{
ImGui.Render();
RenderCommandLists(ImGui.GetDrawData());
}
private IntPtr _context;
private Texture2D _fontTexture;
private Vector2 _scaleFactor = Vector2.One;
}
}