diff --git a/.editorconfig b/.editorconfig
index f8e79e5..ce75165 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -19,7 +19,7 @@ insert_final_newline = false
# Organize usings
dotnet_separate_import_directive_groups = false
-dotnet_sort_system_directives_first = false
+dotnet_sort_system_directives_first = true
file_header_template = unset
# this. and Me. preferences
@@ -101,7 +101,7 @@ csharp_style_conditional_delegate_call = true
# Modifier preferences
csharp_prefer_static_local_function = true
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
+csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async
# Code-block preferences
csharp_prefer_braces = true
diff --git a/DoomDeathmatch/src/Program.cs b/DoomDeathmatch/src/Program.cs
index 2989378..9e8ab43 100644
--- a/DoomDeathmatch/src/Program.cs
+++ b/DoomDeathmatch/src/Program.cs
@@ -14,7 +14,7 @@ internal abstract class Program
public static void Main(string[] args)
{
- var engine = new Engine.Engine(1280, 720, false, "Doom Deathmatch");
+ var engine = new Engine.Engine(480, 270, false, "Doom Deathmatch");
engine.Run();
}
}
\ No newline at end of file
diff --git a/DoomDeathmatch/src/QuadVertex.cs b/DoomDeathmatch/src/QuadVertex.cs
index 5582151..b29ae81 100644
--- a/DoomDeathmatch/src/QuadVertex.cs
+++ b/DoomDeathmatch/src/QuadVertex.cs
@@ -1,4 +1,4 @@
-using Engine.Renderer.Buffer.Vertex;
+using Engine.Graphics.Buffer.Vertex;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using Half = System.Half;
diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj
index 67df8d7..efd2e0e 100644
--- a/Engine/Engine.csproj
+++ b/Engine/Engine.csproj
@@ -11,12 +11,30 @@
-
-
+
+
+
+
+
+
+ ResXFileCodeGenerator
+ Test.Designer.cs
+ PreserveNewest
+
+
+
+
+
+ True
+ True
+ ShaderResource.resx
+
+
+
diff --git a/Engine/assets/shader/mesh.shader b/Engine/assets/shader/mesh.shader
new file mode 100644
index 0000000..3d4ec80
--- /dev/null
+++ b/Engine/assets/shader/mesh.shader
@@ -0,0 +1,34 @@
+// #type vertex
+#version 460 core
+
+uniform mat4 uProjectionMatrix;
+uniform mat4 uViewMatrix;
+
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec3 aNormal;
+layout (location = 2) in vec2 aTexCoords;
+layout (location = 3) in mat4 aModelMatrix;
+
+layout (location = 0) out vec2 oTexCoords;
+
+void main()
+{
+ gl_Position = uProjectionMatrix * uViewMatrix * aModelMatrix * vec4(aPos, 1.0);
+ oTexCoords = aTexCoords;
+}
+
+// #type fragment
+#version 460 core
+
+uniform sampler2D uTexture;
+
+layout (location = 0) in vec2 iTexCoords;
+
+layout (location = 0) out vec4 FragColor;
+
+void main()
+{
+ FragColor = texture(uTexture, iTexCoords);
+ if (FragColor.a == 0.0)
+ discard;
+}
\ No newline at end of file
diff --git a/Engine/assets/shader/quad.shader b/Engine/assets/shader/quad.shader
new file mode 100644
index 0000000..b92e1b0
--- /dev/null
+++ b/Engine/assets/shader/quad.shader
@@ -0,0 +1,46 @@
+// #type vertex
+#version 460 core
+
+uniform mat4 uProjectionMatrix;
+uniform mat4 uViewMatrix;
+
+layout (location = 0) in vec3 aPosition;
+layout (location = 1) in vec2 aUV;
+layout (location = 2) in vec4 aColor;
+layout (location = 3) in int aTextureId;
+layout (location = 4) in mat4 aModel;
+
+layout (location = 0) out vec4 oColor;
+layout (location = 1) out vec2 oUV;
+layout (location = 2) out int oTextureId;
+
+void main()
+{
+ oColor = aColor;
+ oUV = aUV;
+ oTextureId = aTextureId;
+
+ gl_Position = uProjectionMatrix * uViewMatrix * aModel * vec4(aPosition, 1.0);
+}
+
+// #type fragment
+#version 460 core
+
+uniform sampler2D uTexture[16];
+
+layout (location = 0) in vec4 iColor;
+layout (location = 1) in vec2 iUV;
+layout (location = 2) flat in int iTextureId;
+
+layout (location = 0) out vec4 FragColor;
+
+void main()
+{
+ FragColor = iColor;
+
+ if (iTextureId >= 0)
+ FragColor *= texture(uTexture[iTextureId], iUV);
+
+ if (FragColor.a == 0.0)
+ discard;
+}
\ No newline at end of file
diff --git a/Engine/src/Asset/Image.cs b/Engine/src/Asset/Image.cs
index 25bd691..8b1ee4b 100644
--- a/Engine/src/Asset/Image.cs
+++ b/Engine/src/Asset/Image.cs
@@ -1,18 +1,18 @@
-using Engine.Renderer.Pixel;
-using Engine.Renderer.Texture;
+using Engine.Graphics.Pixel;
+using Engine.Graphics.Texture;
namespace Engine.Asset;
public class Image(T[,] parPixels)
where T : struct, IPixel
{
- public int Width { get; } = parPixels.GetLength(0);
- public int Height { get; } = parPixels.GetLength(1);
+ public int Width { get; } = parPixels.GetLength(1);
+ public int Height { get; } = parPixels.GetLength(0);
public T[,] Pixels { get; } = parPixels;
- public T this[int parX, int parY] => Pixels[parX, parY];
+ public T this[int parY, int parX] => Pixels[parY, parX];
- public Image(int parWidth, int parHeight) : this(new T[parWidth, parHeight])
+ public Image(int parWidth, int parHeight) : this(new T[parHeight, parWidth])
{
}
diff --git a/Engine/src/Asset/Mesh/Mesh.cs b/Engine/src/Asset/Mesh/Mesh.cs
index 683fd5a..aa02542 100644
--- a/Engine/src/Asset/Mesh/Mesh.cs
+++ b/Engine/src/Asset/Mesh/Mesh.cs
@@ -1,4 +1,4 @@
-using Engine.Renderer.Buffer.Vertex;
+using Engine.Graphics.Buffer.Vertex;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
@@ -15,6 +15,11 @@ public class Mesh
private readonly List _vertices = [];
private readonly List _indices = [];
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Indices, Vertices);
+ }
+
public record struct Vertex : IVertex
{
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
diff --git a/Engine/src/Engine.cs b/Engine/src/Engine.cs
index e6fee70..853b80d 100644
--- a/Engine/src/Engine.cs
+++ b/Engine/src/Engine.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using Engine.Graphics;
using Engine.Input;
using Engine.Scene;
using OpenTK.Graphics.OpenGL;
@@ -12,14 +13,15 @@ namespace Engine;
public sealed class Engine
{
- public Renderer.Renderer Renderer => _renderer;
+ public Renderer Renderer => _renderer;
public SceneManager SceneManager => _sceneManager;
private readonly Window _window;
- private readonly Renderer.Renderer _renderer;
+ private readonly Renderer _renderer;
private readonly SceneManager _sceneManager = new();
private readonly ILogger _logger;
private readonly IInputHandler _inputHandler;
+ private readonly IPresenter _presenter;
private Thread? _updateThread;
@@ -30,12 +32,20 @@ public sealed class Engine
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
Title = parTitle,
StartVisible = !parHeadless,
- APIVersion = new Version(4, 5),
+ APIVersion = new Version(4, 6),
Profile = ContextProfile.Compatability
};
_window = new Window(this, new NativeWindow(settings), parHeadless);
- _renderer = new Renderer.Renderer(parWidth, parHeight);
+ _renderer = new Renderer(parWidth, parHeight);
+ if (!parHeadless)
+ {
+ _window.Resize += parArgs =>
+ {
+ _renderer.Resize(parArgs.Width, parArgs.Height);
+ };
+ _presenter = _window;
+ }
Thread.CurrentThread.Name = "RendererThread";
@@ -53,18 +63,36 @@ public sealed class Engine
_logger = Log.ForContext();
}
+ public Engine(int parWidth, int parHeight, Func parPresenter) : this(parWidth, parHeight, true)
+ {
+ _presenter = parPresenter(this);
+ }
+
public void Run()
{
_updateThread = new Thread(RunUpdate);
_updateThread.Start();
- GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
-
- GL.Viewport(0, 0, _window.Width, _window.Height);
-
- while (!_window.IsExiting)
+ var timer = Stopwatch.StartNew();
+ var deltaTime = 0.0;
+ while (!_presenter.IsExiting)
{
- _window.Update();
+ var time = deltaTime;
+ _renderer.Commit(_ => GL.ClearColor(0.6f, 0.0f, 0.6f, 1.0f));
+ _renderer.Commit(_ => GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit));
+ _renderer.Commit(parRenderer =>
+ {
+ parRenderer.QuadRenderer.Commit(
+ Matrix4.CreateScale(MathF.Sin((float)time) * 2) * Matrix4.CreateRotationZ(MathF.Sin((float)time)),
+ new Vector4((MathF.Sin((float)time * 3) + 1.0f) / 2, (MathF.Sin((float)time) + 1.0f) / 2, (MathF.Sin((float)time * 5) + 1.0f) / 2, 1.0f));
+ parRenderer.QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity);
+ parRenderer.QuadRenderer.Reset();
+ });
+ _renderer.Render();
+ _presenter.Present(_renderer.TextureInternal);
+
+ _presenter.Update(deltaTime);
+ deltaTime = timer.Elapsed.TotalSeconds;
}
_updateThread.Join();
@@ -73,11 +101,12 @@ public sealed class Engine
private void RunUpdate()
{
var timer = Stopwatch.StartNew();
- while (!_window.IsExiting)
+ var deltaTime = 0.0;
+ while (!_presenter.IsExiting)
{
- _window.Update();
- _sceneManager.Update(timer.Elapsed.TotalSeconds);
+ _sceneManager.Update(deltaTime);
timer.Restart();
+ deltaTime = timer.Elapsed.TotalSeconds;
}
}
}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Buffer/IndexBuffer.cs b/Engine/src/Graphics/Buffer/IndexBuffer.cs
similarity index 69%
rename from Engine/src/Renderer/Buffer/IndexBuffer.cs
rename to Engine/src/Graphics/Buffer/IndexBuffer.cs
index 81ef8c8..2d7b13a 100644
--- a/Engine/src/Renderer/Buffer/IndexBuffer.cs
+++ b/Engine/src/Graphics/Buffer/IndexBuffer.cs
@@ -1,13 +1,13 @@
using OpenTK.Graphics.OpenGL;
using Serilog;
-namespace Engine.Renderer.Buffer;
+namespace Engine.Graphics.Buffer;
public class IndexBuffer : OpenGlObject
{
internal int Count { get; }
- public IndexBuffer(int parCount, BufferStorageFlags parFlags)
+ public IndexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
{
Count = parCount;
@@ -19,6 +19,19 @@ public class IndexBuffer : OpenGlObject
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
}
+
+ public IndexBuffer(uint[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None)
+ {
+ Count = parData.Length;
+
+ GL.CreateBuffers(1, out int handle);
+ Handle = handle;
+
+ GL.NamedBufferStorage(Handle, Count * sizeof(uint), parData, parFlags);
+
+ Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
+ }
+
public void UploadData(uint[] parData)
{
UploadData(0, parData);
diff --git a/Engine/src/Renderer/Buffer/Vertex/IVertex.cs b/Engine/src/Graphics/Buffer/Vertex/IVertex.cs
similarity index 93%
rename from Engine/src/Renderer/Buffer/Vertex/IVertex.cs
rename to Engine/src/Graphics/Buffer/Vertex/IVertex.cs
index 9276fc8..ee7d3a4 100644
--- a/Engine/src/Renderer/Buffer/Vertex/IVertex.cs
+++ b/Engine/src/Graphics/Buffer/Vertex/IVertex.cs
@@ -2,7 +2,7 @@
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Buffer.Vertex;
+namespace Engine.Graphics.Buffer.Vertex;
public interface IVertex
{
@@ -23,7 +23,7 @@ public interface IVertex
return false;
}
- IOrderedEnumerable? fields = GetFields(parType);
+ var fields = GetFields(parType);
var totalSize = 0;
foreach (var field in fields)
{
diff --git a/Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs b/Engine/src/Graphics/Buffer/Vertex/VertexAttribute.cs
similarity index 85%
rename from Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs
rename to Engine/src/Graphics/Buffer/Vertex/VertexAttribute.cs
index 2a79028..630af55 100644
--- a/Engine/src/Renderer/Buffer/Vertex/VertexAttribute.cs
+++ b/Engine/src/Graphics/Buffer/Vertex/VertexAttribute.cs
@@ -1,6 +1,6 @@
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Buffer.Vertex;
+namespace Engine.Graphics.Buffer.Vertex;
[AttributeUsage(AttributeTargets.Field)]
public class VertexAttribute : Attribute
@@ -10,8 +10,9 @@ public class VertexAttribute : Attribute
public bool Normalized { get; }
public int RepeatCount { get; }
- public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, bool parNormalized = false,
- int parRepeatCount = 1)
+ public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, int parRepeatCount = 1,
+ bool parNormalized = false
+ )
{
if (parComponentCount <= 0)
{
diff --git a/Engine/src/Renderer/Buffer/VertexArray.cs b/Engine/src/Graphics/Buffer/VertexArray.cs
similarity index 91%
rename from Engine/src/Renderer/Buffer/VertexArray.cs
rename to Engine/src/Graphics/Buffer/VertexArray.cs
index d3d3f1b..9a5d0e9 100644
--- a/Engine/src/Renderer/Buffer/VertexArray.cs
+++ b/Engine/src/Graphics/Buffer/VertexArray.cs
@@ -1,15 +1,16 @@
using System.Reflection;
using System.Runtime.InteropServices;
-using Engine.Renderer.Buffer.Vertex;
+using Engine.Graphics.Buffer.Vertex;
using OpenTK.Graphics.OpenGL;
using Serilog;
-namespace Engine.Renderer.Buffer;
+namespace Engine.Graphics.Buffer;
public class VertexArray : OpenGlObject
{
// private IndexBuffer? _boundIndexBuffer;
// private readonly Dictionary> _boundVertexBuffers = new();
+ private int _enabledAttribs = 0;
public VertexArray()
{
@@ -42,15 +43,14 @@ public class VertexArray : OpenGlObject
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
- var location = 0;
foreach (var field in fields)
{
var attribute = field.GetCustomAttribute()!;
var offset = Marshal.OffsetOf(field.Name).ToInt32();
- SetupAttribute(attribute, location, offset, parBindingIndex);
+ SetupAttribute(attribute, _enabledAttribs, offset, parBindingIndex);
- location += attribute.RepeatCount;
+ _enabledAttribs += attribute.RepeatCount;
}
GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor);
diff --git a/Engine/src/Graphics/Buffer/VertexBuffer.cs b/Engine/src/Graphics/Buffer/VertexBuffer.cs
new file mode 100644
index 0000000..88fc71e
--- /dev/null
+++ b/Engine/src/Graphics/Buffer/VertexBuffer.cs
@@ -0,0 +1,108 @@
+using System.Runtime.InteropServices;
+using Engine.Graphics.Buffer.Vertex;
+using OpenTK.Graphics.OpenGL;
+using Serilog;
+
+namespace Engine.Graphics.Buffer;
+
+public class VertexBuffer : OpenGlObject
+ where T : struct, IVertex
+{
+ internal int Count { get; }
+
+ private readonly int _stride = Marshal.SizeOf();
+
+ public VertexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
+ {
+ if (!IVertex.IsValid(typeof(T)))
+ {
+ throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
+ }
+
+ if (parCount <= 0)
+ {
+ throw new ArgumentException("Count must be greater than 0");
+ }
+
+ Count = parCount;
+
+ GL.CreateBuffers(1, out int handle);
+ Handle = handle;
+
+ GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, parFlags);
+
+ Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
+ }
+
+ public VertexBuffer(T[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None)
+ {
+ if (!IVertex.IsValid(typeof(T)))
+ {
+ throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
+ }
+
+ if (parData.Length <= 0)
+ {
+ throw new ArgumentException("Count must be greater than 0");
+ }
+
+ Count = parData.Length;
+
+ GL.CreateBuffers(1, out int handle);
+ Handle = handle;
+
+ GL.NamedBufferStorage(Handle, Count * _stride, parData, parFlags);
+
+ Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
+ }
+
+ public void UploadData(T[] parData)
+ {
+ UploadData(0, parData, parData.Length);
+ }
+
+ public void UploadData(T[] parData, int parCount)
+ {
+ UploadData(0, parData, parCount);
+ }
+
+ public void UploadData(int parOffset, T[] parData, int parCount)
+ {
+ if (parOffset < 0)
+ {
+ throw new ArgumentException("Offset must be greater than 0");
+ }
+
+ if (parCount <= 0)
+ {
+ throw new ArgumentException("Count must be greater than 0");
+ }
+
+ if (parData.Length < parCount)
+ {
+ throw new ArgumentException("Data array is too small");
+ }
+
+ if (parCount + parOffset > Count)
+ {
+ throw new ArgumentException("Data array is too large");
+ }
+
+ GL.NamedBufferSubData(Handle, parOffset * _stride, parCount * _stride, parData);
+ }
+
+ public override void Bind()
+ {
+ GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
+ }
+
+ public override void Unbind()
+ {
+ GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+ }
+
+ protected override void Destroy()
+ {
+ GL.DeleteBuffer(Handle);
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Camera/ICamera.cs b/Engine/src/Graphics/Camera/ICamera.cs
similarity index 84%
rename from Engine/src/Renderer/Camera/ICamera.cs
rename to Engine/src/Graphics/Camera/ICamera.cs
index 5a8d140..f1caed7 100644
--- a/Engine/src/Renderer/Camera/ICamera.cs
+++ b/Engine/src/Graphics/Camera/ICamera.cs
@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
-namespace Engine.Renderer.Camera;
+namespace Engine.Graphics.Camera;
public interface ICamera
{
diff --git a/Engine/src/Renderer/Camera/ScreenspaceCamera.cs b/Engine/src/Graphics/Camera/ScreenspaceCamera.cs
similarity index 86%
rename from Engine/src/Renderer/Camera/ScreenspaceCamera.cs
rename to Engine/src/Graphics/Camera/ScreenspaceCamera.cs
index a29fa99..2587f5d 100644
--- a/Engine/src/Renderer/Camera/ScreenspaceCamera.cs
+++ b/Engine/src/Graphics/Camera/ScreenspaceCamera.cs
@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
-namespace Engine.Renderer.Camera;
+namespace Engine.Graphics.Camera;
public class ScreenspaceCamera : ICamera
{
diff --git a/Engine/src/Renderer/Debug.cs b/Engine/src/Graphics/Debug.cs
similarity index 98%
rename from Engine/src/Renderer/Debug.cs
rename to Engine/src/Graphics/Debug.cs
index cbaa41a..9fdb771 100644
--- a/Engine/src/Renderer/Debug.cs
+++ b/Engine/src/Graphics/Debug.cs
@@ -3,7 +3,7 @@ using OpenTK.Graphics.OpenGL;
using Serilog;
using Serilog.Events;
-namespace Engine.Renderer;
+namespace Engine.Graphics;
internal static class Debug
{
diff --git a/Engine/src/Renderer/Framebuffer/Framebuffer.cs b/Engine/src/Graphics/Framebuffer/Framebuffer.cs
similarity index 88%
rename from Engine/src/Renderer/Framebuffer/Framebuffer.cs
rename to Engine/src/Graphics/Framebuffer/Framebuffer.cs
index 9e8d1e2..3fe3a38 100644
--- a/Engine/src/Renderer/Framebuffer/Framebuffer.cs
+++ b/Engine/src/Graphics/Framebuffer/Framebuffer.cs
@@ -1,9 +1,9 @@
using System.Collections.ObjectModel;
-using Engine.Renderer.Pixel;
-using Engine.Renderer.Texture;
+using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
+using Serilog;
-namespace Engine.Renderer.Framebuffer;
+namespace Engine.Graphics.Framebuffer;
public class Framebuffer : OpenGlObject
{
@@ -55,10 +55,10 @@ public class Framebuffer : OpenGlObject
{
switch (attachment.Value.Type)
{
- case IFramebufferAttachment.AttachmentType._texture:
+ case IFramebufferAttachment.AttachmentType.Texture:
GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0);
break;
- case IFramebufferAttachment.AttachmentType._renderbuffer:
+ case IFramebufferAttachment.AttachmentType.Renderbuffer:
GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer,
attachment.Value.Handle);
break;
@@ -102,10 +102,12 @@ public class Framebuffer : OpenGlObject
Width = parWidth;
Height = parHeight;
- foreach (KeyValuePair attachment in _attachments)
+ foreach (var attachment in _attachments)
{
attachment.Value.Resize(parWidth, parHeight);
}
+
+ Log.Debug("Framebuffer {Handle} resized to {Width}x{Height}", Handle, parWidth, parHeight);
}
public override void Bind()
diff --git a/Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs b/Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs
similarity index 96%
rename from Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs
rename to Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs
index e1cac61..a5cf4b6 100644
--- a/Engine/src/Renderer/Framebuffer/FramebufferBuilder.cs
+++ b/Engine/src/Graphics/Framebuffer/FramebufferBuilder.cs
@@ -1,8 +1,8 @@
-using Engine.Renderer.Pixel;
-using Engine.Renderer.Texture;
+using Engine.Graphics.Pixel;
+using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Framebuffer;
+namespace Engine.Graphics.Framebuffer;
public class FramebufferBuilder(int parWidth, int parHeight)
{
diff --git a/Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs b/Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs
similarity index 73%
rename from Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs
rename to Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs
index e95069c..5ae762d 100644
--- a/Engine/src/Renderer/Framebuffer/IFramebufferAttachment.cs
+++ b/Engine/src/Graphics/Framebuffer/IFramebufferAttachment.cs
@@ -1,4 +1,4 @@
-namespace Engine.Renderer.Framebuffer;
+namespace Engine.Graphics.Framebuffer;
public interface IFramebufferAttachment
{
@@ -9,7 +9,7 @@ public interface IFramebufferAttachment
public enum AttachmentType
{
- _texture,
- _renderbuffer
+ Texture,
+ Renderbuffer
}
}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Framebuffer/Renderbuffer.cs b/Engine/src/Graphics/Framebuffer/Renderbuffer.cs
similarity index 93%
rename from Engine/src/Renderer/Framebuffer/Renderbuffer.cs
rename to Engine/src/Graphics/Framebuffer/Renderbuffer.cs
index 98a95d8..301aeda 100644
--- a/Engine/src/Renderer/Framebuffer/Renderbuffer.cs
+++ b/Engine/src/Graphics/Framebuffer/Renderbuffer.cs
@@ -1,13 +1,13 @@
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Framebuffer;
+namespace Engine.Graphics.Framebuffer;
public class Renderbuffer : OpenGlObject, IFramebufferAttachment
{
public int Width { get; private set; }
public int Height { get; private set; }
- public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._renderbuffer;
+ public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType.Renderbuffer;
private readonly RenderbufferStorage _format;
diff --git a/Engine/src/Renderer/IPresenter.cs b/Engine/src/Graphics/IPresenter.cs
similarity index 82%
rename from Engine/src/Renderer/IPresenter.cs
rename to Engine/src/Graphics/IPresenter.cs
index aa435fd..445301b 100644
--- a/Engine/src/Renderer/IPresenter.cs
+++ b/Engine/src/Graphics/IPresenter.cs
@@ -1,8 +1,8 @@
-using Engine.Renderer.Texture;
+using Engine.Graphics.Texture;
using Engine.Scene;
using OpenTK.Windowing.Common;
-namespace Engine.Renderer;
+namespace Engine.Graphics;
public interface IPresenter : IUpdate
{
diff --git a/Engine/src/Renderer/OpenGLObject.cs b/Engine/src/Graphics/OpenGLObject.cs
similarity index 92%
rename from Engine/src/Renderer/OpenGLObject.cs
rename to Engine/src/Graphics/OpenGLObject.cs
index 9593b22..2d69d00 100644
--- a/Engine/src/Renderer/OpenGLObject.cs
+++ b/Engine/src/Graphics/OpenGLObject.cs
@@ -1,6 +1,6 @@
using Serilog;
-namespace Engine.Renderer;
+namespace Engine.Graphics;
public abstract class OpenGlObject
{
@@ -14,8 +14,9 @@ public abstract class OpenGlObject
~OpenGlObject()
{
Destroy();
- Handle = -1;
Log.Debug("OpenGL object {Handle} destroyed", Handle);
+
+ Handle = -1;
}
}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Pixel/IPixel.cs b/Engine/src/Graphics/Pixel/IPixel.cs
similarity index 87%
rename from Engine/src/Renderer/Pixel/IPixel.cs
rename to Engine/src/Graphics/Pixel/IPixel.cs
index 8ad0a8f..afdfa09 100644
--- a/Engine/src/Renderer/Pixel/IPixel.cs
+++ b/Engine/src/Graphics/Pixel/IPixel.cs
@@ -1,6 +1,6 @@
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Pixel;
+namespace Engine.Graphics.Pixel;
public interface IPixel
{
diff --git a/Engine/src/Renderer/Pixel/R8.cs b/Engine/src/Graphics/Pixel/R8.cs
similarity index 92%
rename from Engine/src/Renderer/Pixel/R8.cs
rename to Engine/src/Graphics/Pixel/R8.cs
index 95fc236..751cc38 100644
--- a/Engine/src/Renderer/Pixel/R8.cs
+++ b/Engine/src/Graphics/Pixel/R8.cs
@@ -1,7 +1,7 @@
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Pixel;
+namespace Engine.Graphics.Pixel;
[StructLayout(LayoutKind.Sequential)]
public struct R8 : IPixel
diff --git a/Engine/src/Renderer/Pixel/Rgb8.cs b/Engine/src/Graphics/Pixel/Rgb8.cs
similarity index 83%
rename from Engine/src/Renderer/Pixel/Rgb8.cs
rename to Engine/src/Graphics/Pixel/Rgb8.cs
index 9440901..fd40fa9 100644
--- a/Engine/src/Renderer/Pixel/Rgb8.cs
+++ b/Engine/src/Graphics/Pixel/Rgb8.cs
@@ -1,7 +1,7 @@
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Pixel;
+namespace Engine.Graphics.Pixel;
[StructLayout(LayoutKind.Sequential)]
public struct Rgb8 : IPixel
@@ -15,4 +15,6 @@ public struct Rgb8 : IPixel
public byte R;
public byte G;
public byte B;
+
+ public override string ToString() => $"{R}, {G}, {B}";
}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/InstancedRenderer.cs b/Engine/src/Graphics/Render/InstancedRenderer.cs
new file mode 100644
index 0000000..e0a0e9e
--- /dev/null
+++ b/Engine/src/Graphics/Render/InstancedRenderer.cs
@@ -0,0 +1,79 @@
+using Engine.Graphics.Buffer;
+using Engine.Graphics.Buffer.Vertex;
+using Engine.Graphics.Shader;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render;
+
+public abstract class InstancedRenderer
+ where C : struct, IVertex
+ where I : struct, IVertex
+{
+ protected readonly Renderer _renderer;
+
+ protected readonly IndexBuffer _indexBuffer;
+ protected readonly VertexBuffer _commonVertexBuffer;
+ protected readonly VertexBuffer _instanceVertexBuffer;
+ protected readonly VertexArray _vertexArray;
+
+ protected readonly int _instanceCount;
+ protected int _queuedInstanceCount;
+ protected readonly I[] _instanceVertices;
+
+ private readonly PrimitiveType _primitiveType;
+ private readonly Program _program;
+
+ protected InstancedRenderer(Renderer parRenderer, PrimitiveType parPrimitiveType, int parInstanceCount,
+ uint[] parIndexBuffer, C[] parInstanceBuffer,
+ Program parProgram)
+ {
+ _program = parProgram;
+ _renderer = parRenderer;
+ _instanceCount = parInstanceCount;
+ _queuedInstanceCount = 0;
+ _instanceVertices = new I[parInstanceCount];
+ _primitiveType = parPrimitiveType;
+
+ _indexBuffer = new IndexBuffer(parIndexBuffer);
+ _commonVertexBuffer = new VertexBuffer(parInstanceBuffer);
+ _instanceVertexBuffer = new VertexBuffer(parInstanceCount,
+ BufferStorageFlags.DynamicStorageBit);
+ _vertexArray = new VertexArray();
+
+ _vertexArray.BindIndexBuffer(_indexBuffer);
+ _vertexArray.BindVertexBuffer(_commonVertexBuffer, 0, 0);
+ _vertexArray.BindVertexBuffer(_instanceVertexBuffer, 1, 1);
+ }
+
+ public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
+ {
+ if (_queuedInstanceCount <= 0)
+ {
+ return;
+ }
+
+ _renderer.EnsureRenderThread();
+
+ _instanceVertexBuffer.UploadData(_instanceVertices, _queuedInstanceCount);
+ _vertexArray.Bind();
+
+ _program.Bind();
+ _program.SetUniform("uProjectionMatrix", in parProjectionMatrix);
+ _program.SetUniform("uViewMatrix", in parViewMatrix);
+ SetAdditionalUniforms(_program);
+
+ GL.DrawElementsInstanced(_primitiveType, _indexBuffer.Count * _queuedInstanceCount,
+ DrawElementsType.UnsignedInt, 0,
+ _queuedInstanceCount);
+ }
+
+ public virtual void Reset()
+ {
+ _queuedInstanceCount = 0;
+ }
+
+ protected virtual void SetAdditionalUniforms(Program parProgram)
+ {
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs b/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs
new file mode 100644
index 0000000..49d8a6e
--- /dev/null
+++ b/Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs
@@ -0,0 +1,10 @@
+using Engine.Graphics.Buffer.Vertex;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Mesh;
+
+public struct MeshInstanceVertex : IVertex
+{
+ [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs b/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
new file mode 100644
index 0000000..1b60b0e
--- /dev/null
+++ b/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
@@ -0,0 +1,26 @@
+using Engine.Graphics.Shader;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Mesh;
+
+public class MeshRenderer : InstancedRenderer
+{
+ public MeshRenderer(Renderer parRenderer, int parInstanceCount, Asset.Mesh.Mesh parMesh)
+ : base(parRenderer, PrimitiveType.Triangles, parInstanceCount, parMesh.Indices.ToArray(),
+ parMesh.Vertices.ToArray(),
+ ProgramLoader.LoadFromSource(ShaderResource.Mesh))
+ {
+ }
+
+ public void Commit(Matrix4 parModelMatrix)
+ {
+ if (_queuedInstanceCount >= _instanceCount)
+ {
+ throw new InvalidOperationException("Instance count exceeded");
+ }
+
+ _instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix;
+ _queuedInstanceCount++;
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Quad/QuadCommonVertex.cs b/Engine/src/Graphics/Render/Quad/QuadCommonVertex.cs
new file mode 100644
index 0000000..ca304e7
--- /dev/null
+++ b/Engine/src/Graphics/Render/Quad/QuadCommonVertex.cs
@@ -0,0 +1,11 @@
+using Engine.Graphics.Buffer.Vertex;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Quad;
+
+public struct QuadCommonVertex : IVertex
+{
+ [Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
+ [Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Quad/QuadInstanceVertex.cs b/Engine/src/Graphics/Render/Quad/QuadInstanceVertex.cs
new file mode 100644
index 0000000..248499f
--- /dev/null
+++ b/Engine/src/Graphics/Render/Quad/QuadInstanceVertex.cs
@@ -0,0 +1,12 @@
+using Engine.Graphics.Buffer.Vertex;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Quad;
+
+public struct QuadInstanceVertex : IVertex
+{
+ [Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
+ [Vertex(VertexAttribType.Int)] public int _textureId;
+ [Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Quad/QuadRenderer.cs b/Engine/src/Graphics/Render/Quad/QuadRenderer.cs
new file mode 100644
index 0000000..1657552
--- /dev/null
+++ b/Engine/src/Graphics/Render/Quad/QuadRenderer.cs
@@ -0,0 +1,59 @@
+using Engine.Graphics.Shader;
+using Engine.Graphics.Texture;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Quad;
+
+public class QuadRenderer : InstancedRenderer
+{
+ private readonly TextureUnitMap _textureUnitMap = new(16);
+
+ public QuadRenderer(Renderer parRenderer, int parInstanceCount)
+ : base(parRenderer, PrimitiveType.Triangles, parInstanceCount, [0, 1, 2, 2, 3, 0], [
+ new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 1) },
+ new QuadCommonVertex { _position = new Vector3(0.5f, -0.5f, 0), _uv = new Vector2(1, 1) },
+ new QuadCommonVertex { _position = new Vector3(0.5f, 0.5f, 0), _uv = new Vector2(1, 0) },
+ new QuadCommonVertex { _position = new Vector3(-0.5f, 0.5f, 0), _uv = new Vector2(0, 0) }
+ ],
+ ProgramLoader.LoadFromSource(ShaderResource.Quad))
+ {
+ }
+
+ public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, Texture.Texture? parTexture = null)
+ {
+ if (_queuedInstanceCount >= _instanceCount)
+ {
+ throw new InvalidOperationException("Instance count exceeded");
+ }
+
+ var textureId = -1;
+ if (parTexture != null)
+ {
+ textureId = _textureUnitMap.GetUnit(parTexture);
+ }
+
+ _instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix;
+ _instanceVertices[_queuedInstanceCount]._color = parColor;
+ _instanceVertices[_queuedInstanceCount]._textureId = textureId;
+ _queuedInstanceCount++;
+ }
+
+ protected override void SetAdditionalUniforms(Program parProgram)
+ {
+ var samplers = new int[_textureUnitMap.Size];
+ foreach (var (texture, unit) in _textureUnitMap)
+ {
+ texture.BindUnit(unit);
+ samplers[unit] = unit;
+ }
+
+ parProgram.SetUniform("uTexture", samplers);
+ }
+
+ public override void Reset()
+ {
+ base.Reset();
+ _textureUnitMap.Reset();
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Renderer.cs b/Engine/src/Graphics/Renderer.cs
similarity index 58%
rename from Engine/src/Renderer/Renderer.cs
rename to Engine/src/Graphics/Renderer.cs
index 888c86b..1a53718 100644
--- a/Engine/src/Renderer/Renderer.cs
+++ b/Engine/src/Graphics/Renderer.cs
@@ -1,30 +1,35 @@
-using System.Runtime.InteropServices;
-using Engine.Renderer.Pixel;
-using Engine.Renderer.Shader;
+using Engine.Graphics.Pixel;
+using Engine.Graphics.Render.Quad;
using OpenTK.Graphics.OpenGL;
-using Serilog;
-using Serilog.Events;
-namespace Engine.Renderer;
+namespace Engine.Graphics;
public class Renderer
{
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
+ public readonly QuadRenderer QuadRenderer;
+
private readonly Framebuffer.Framebuffer _framebuffer;
private readonly Queue> _renderActions = new();
+ private readonly Thread _renderThread;
+
public Renderer(int parWidth, int parHeight)
{
- InitializeOpenGl();
+ InitializeOpenGl(parWidth, parHeight);
+
+ _renderThread = Thread.CurrentThread;
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
.AddColorAttachment()
.AddDepthAttachment()
.Build();
+
+ QuadRenderer = new QuadRenderer(this, 1024 * 8);
}
- private void InitializeOpenGl()
+ private void InitializeOpenGl(int parWidth, int parHeight)
{
#if DEBUG
Debug.Setup();
@@ -36,6 +41,18 @@ public class Renderer
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.Blend);
+
+ GL.Viewport(0, 0, parWidth, parHeight);
+ }
+
+ public void EnsureRenderThread()
+ {
+ if (Thread.CurrentThread == _renderThread)
+ {
+ return;
+ }
+
+ throw new InvalidOperationException("Renderer is not on render thread");
}
internal void Commit(Action parRenderAction)
@@ -45,8 +62,10 @@ public class Renderer
internal void Render()
{
+ EnsureRenderThread();
+
_framebuffer.Bind();
- while (_renderActions.TryDequeue(out Action? renderAction))
+ while (_renderActions.TryDequeue(out var renderAction))
{
renderAction(this);
}
@@ -57,5 +76,6 @@ public class Renderer
internal void Resize(int parWidth, int parHeight)
{
_framebuffer.Resize(parWidth, parHeight);
+ GL.Viewport(0, 0, parWidth, parHeight);
}
}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Shader/ShaderProgram.cs b/Engine/src/Graphics/Shader/Program.cs
similarity index 78%
rename from Engine/src/Renderer/Shader/ShaderProgram.cs
rename to Engine/src/Graphics/Shader/Program.cs
index 1f428b7..457511b 100644
--- a/Engine/src/Renderer/Shader/ShaderProgram.cs
+++ b/Engine/src/Graphics/Shader/Program.cs
@@ -1,48 +1,14 @@
-using System.Runtime.CompilerServices;
-using System.Text;
-using OpenTK.Graphics.OpenGL;
+using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using Serilog;
-namespace Engine.Renderer.Shader;
+namespace Engine.Graphics.Shader;
-public class ShaderProgram : OpenGlObject
+public class Program : OpenGlObject
{
private readonly Dictionary _uniforms = new();
- public static ShaderProgram CreateFromSource(string parSource)
- {
- var vertexSource = new StringBuilder();
- var fragmentSource = new StringBuilder();
- var inFragment = false;
- var inVertex = false;
-
- foreach (var line in parSource.Split('\n'))
- {
- if (line.StartsWith("#shader vertex"))
- {
- inVertex = true;
- inFragment = false;
- }
- else if (line.StartsWith("#shader fragment"))
- {
- inVertex = false;
- inFragment = true;
- }
- else if (inVertex)
- {
- vertexSource.AppendLine(line);
- }
- else if (inFragment)
- {
- fragmentSource.AppendLine(line);
- }
- }
-
- return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString());
- }
-
- public ShaderProgram(string parVertexSource, string parFragmentSource)
+ public Program(string parVertexSource, string parFragmentSource)
{
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
@@ -50,7 +16,7 @@ public class ShaderProgram : OpenGlObject
Handle = LinkProgram(vertexShader, fragmentShader);
}
- public void SetUniform(string parName, T parValue)
+ public void SetUniform(string parName, in T parValue)
{
try
{
@@ -79,6 +45,12 @@ public class ShaderProgram : OpenGlObject
case Matrix4 matrix:
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
break;
+ case int[] intArray:
+ GL.ProgramUniform1(Handle, location, intArray.Length, intArray);
+ break;
+ case float[] floatArray:
+ GL.ProgramUniform1(Handle, location, floatArray.Length, floatArray);
+ break;
default:
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
}
diff --git a/Engine/src/Graphics/Shader/ProgramLoader.cs b/Engine/src/Graphics/Shader/ProgramLoader.cs
new file mode 100644
index 0000000..212dffb
--- /dev/null
+++ b/Engine/src/Graphics/Shader/ProgramLoader.cs
@@ -0,0 +1,38 @@
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Engine.Graphics.Shader;
+
+public static partial class ProgramLoader
+{
+ [GeneratedRegex(@"^//\s+#type\s+(?[a-z]+)$", RegexOptions.Compiled)]
+ private static partial Regex TypeRegex();
+
+ public static Program LoadFromSource(string parSource)
+ {
+ var vertexSource = new StringBuilder();
+ var fragmentSource = new StringBuilder();
+ var inFragment = false;
+ var inVertex = false;
+
+ foreach (var line in parSource.Split('\n').Select(parLine => parLine.TrimEnd()))
+ {
+ var match = TypeRegex().Match(line);
+ if (match.Success)
+ {
+ inVertex = match.Groups["type"].Value == "vertex";
+ inFragment = match.Groups["type"].Value == "fragment";
+ }
+ else if (inVertex)
+ {
+ vertexSource.AppendLine(line);
+ }
+ else if (inFragment)
+ {
+ fragmentSource.AppendLine(line);
+ }
+ }
+
+ return new Program(vertexSource.ToString(), fragmentSource.ToString());
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Texture/DynamicTexture.cs b/Engine/src/Graphics/Texture/DynamicTexture.cs
similarity index 84%
rename from Engine/src/Renderer/Texture/DynamicTexture.cs
rename to Engine/src/Graphics/Texture/DynamicTexture.cs
index 666c51a..7ba4e03 100644
--- a/Engine/src/Renderer/Texture/DynamicTexture.cs
+++ b/Engine/src/Graphics/Texture/DynamicTexture.cs
@@ -1,12 +1,13 @@
-using Engine.Renderer.Framebuffer;
-using Engine.Renderer.Pixel;
+using Engine.Graphics.Framebuffer;
+using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL;
+using Serilog;
-namespace Engine.Renderer.Texture;
+namespace Engine.Graphics.Texture;
public class DynamicTexture : Texture, IFramebufferAttachment
{
- public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._texture;
+ public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType.Texture;
private readonly PixelFormat _format;
private readonly PixelType _type;
@@ -45,5 +46,7 @@ public class DynamicTexture : Texture, IFramebufferAttachment
Bind();
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
IntPtr.Zero);
+
+ Log.Debug("Texture {Handle} resized to {Width}x{Height}", Handle, Width, Height);
}
}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Texture/IConstTexture.cs b/Engine/src/Graphics/Texture/IConstTexture.cs
similarity index 91%
rename from Engine/src/Renderer/Texture/IConstTexture.cs
rename to Engine/src/Graphics/Texture/IConstTexture.cs
index 87ec8b0..0317b91 100644
--- a/Engine/src/Renderer/Texture/IConstTexture.cs
+++ b/Engine/src/Graphics/Texture/IConstTexture.cs
@@ -1,10 +1,12 @@
using Engine.Asset;
-using Engine.Renderer.Pixel;
+using Engine.Graphics.Pixel;
+using OpenTK.Mathematics;
-namespace Engine.Renderer.Texture;
+namespace Engine.Graphics.Texture;
public interface IConstTexture
{
+ public Vector2i Size { get; }
public int Width { get; }
public int Height { get; }
diff --git a/Engine/src/Renderer/Texture/ITexture.cs b/Engine/src/Graphics/Texture/ITexture.cs
similarity index 92%
rename from Engine/src/Renderer/Texture/ITexture.cs
rename to Engine/src/Graphics/Texture/ITexture.cs
index 124e554..0d3547c 100644
--- a/Engine/src/Renderer/Texture/ITexture.cs
+++ b/Engine/src/Graphics/Texture/ITexture.cs
@@ -1,7 +1,7 @@
using Engine.Asset;
-using Engine.Renderer.Pixel;
+using Engine.Graphics.Pixel;
-namespace Engine.Renderer.Texture;
+namespace Engine.Graphics.Texture;
public interface ITexture : IConstTexture
{
diff --git a/Engine/src/Renderer/Texture/StaticTexture.cs b/Engine/src/Graphics/Texture/StaticTexture.cs
similarity index 87%
rename from Engine/src/Renderer/Texture/StaticTexture.cs
rename to Engine/src/Graphics/Texture/StaticTexture.cs
index f180d44..60db787 100644
--- a/Engine/src/Renderer/Texture/StaticTexture.cs
+++ b/Engine/src/Graphics/Texture/StaticTexture.cs
@@ -1,7 +1,7 @@
-using Engine.Renderer.Pixel;
+using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL;
-namespace Engine.Renderer.Texture;
+namespace Engine.Graphics.Texture;
public class StaticTexture : Texture
{
diff --git a/Engine/src/Renderer/Texture/Texture.cs b/Engine/src/Graphics/Texture/Texture.cs
similarity index 77%
rename from Engine/src/Renderer/Texture/Texture.cs
rename to Engine/src/Graphics/Texture/Texture.cs
index 7324103..c5e49ce 100644
--- a/Engine/src/Renderer/Texture/Texture.cs
+++ b/Engine/src/Graphics/Texture/Texture.cs
@@ -1,11 +1,15 @@
using System.Runtime.InteropServices;
-using Engine.Renderer.Pixel;
+using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+using Serilog;
-namespace Engine.Renderer.Texture;
+namespace Engine.Graphics.Texture;
public abstract class Texture : OpenGlObject, ITexture
{
+ public Vector2i Size => new(Width, Height);
+
public int Width
{
get => _width;
@@ -44,6 +48,13 @@ public abstract class Texture : OpenGlObject, ITexture
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
Handle = handle;
+
+ GL.TextureParameter(Handle, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+ GL.TextureParameter(Handle, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+ GL.TextureParameter(Handle, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
+ GL.TextureParameter(Handle, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
+
+ Log.Debug("Texture {Handle} created with {Width}x{Height}", Handle, Width, Height);
}
public void UploadPixels(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
@@ -101,6 +112,10 @@ public abstract class Texture : OpenGlObject, ITexture
var format = default(T).Format;
var type = default(T).Type;
+ // Set alignment to avoid row padding issues
+ GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
+ GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
+
GL.GetTextureSubImage(Handle, 0, parX, parY, 0, parWidth, parHeight, 1, format, type,
parPixels.Length * Marshal.SizeOf(),
parPixels);
@@ -108,6 +123,7 @@ public abstract class Texture : OpenGlObject, ITexture
public void BindUnit(int parUnit = 0)
{
+ GL.ActiveTexture(TextureUnit.Texture0 + parUnit);
GL.BindTextureUnit(parUnit, Handle);
}
diff --git a/Engine/src/Graphics/Texture/TextureUnitMap.cs b/Engine/src/Graphics/Texture/TextureUnitMap.cs
new file mode 100644
index 0000000..eda650a
--- /dev/null
+++ b/Engine/src/Graphics/Texture/TextureUnitMap.cs
@@ -0,0 +1,50 @@
+using System.Collections;
+
+namespace Engine.Graphics.Texture;
+
+public class TextureUnitMap : IEnumerable>
+{
+ public int Size => _textures.Count;
+ public int Capacity => _capacity;
+
+ private readonly Dictionary _textures = new();
+ private readonly int _capacity;
+
+ public TextureUnitMap(int parCapacity)
+ {
+ _capacity = parCapacity;
+ }
+
+ public int GetUnit(Texture parTexture)
+ {
+ if (_textures.TryGetValue(parTexture, out var unit))
+ {
+ return unit;
+ }
+
+ if (_textures.Count >= _capacity)
+ {
+ throw new InvalidOperationException("Texture unit map is full");
+ }
+
+ unit = _textures.Count;
+ _textures.Add(parTexture, unit);
+
+ return unit;
+ }
+
+ public void Reset()
+ {
+ _textures.Clear();
+ }
+
+ public IEnumerator> GetEnumerator()
+ {
+ return _textures.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Input/MouseButton.cs b/Engine/src/Input/MouseButton.cs
index aedd735..41712a4 100644
--- a/Engine/src/Input/MouseButton.cs
+++ b/Engine/src/Input/MouseButton.cs
@@ -2,12 +2,12 @@
public enum MouseButton
{
- _left,
- _right,
- _middle,
+ Left,
+ Right,
+ Middle,
- _button4,
- _button5,
- _button6,
- _button7
+ Button4,
+ Button5,
+ Button6,
+ Button7
}
\ No newline at end of file
diff --git a/Engine/src/Renderer/Buffer/VertexBuffer.cs b/Engine/src/Renderer/Buffer/VertexBuffer.cs
deleted file mode 100644
index 65100a5..0000000
--- a/Engine/src/Renderer/Buffer/VertexBuffer.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System.Runtime.InteropServices;
-using Engine.Renderer.Buffer.Vertex;
-using OpenTK.Graphics.OpenGL;
-using Serilog;
-
-namespace Engine.Renderer.Buffer;
-
-public class VertexBuffer : OpenGlObject
- where T : struct, IVertex
-{
- internal int Count { get; }
-
- private readonly int _stride = Marshal.SizeOf();
-
- public VertexBuffer(int parCount, BufferStorageFlags parFlags)
- {
- if (!IVertex.IsValid(typeof(T)))
- {
- throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
- }
-
- if (parCount <= 0)
- {
- throw new ArgumentException("Count must be greater than 0");
- }
-
- Count = parCount;
-
- GL.CreateBuffers(1, out int handle);
- Handle = handle;
-
- GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, parFlags);
-
- Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
- }
-
- public void UploadData(T[] parData)
- {
- UploadData(0, parData);
- }
-
- public void UploadData(int parOffset, T[] parData)
- {
- if (parOffset < 0)
- {
- throw new ArgumentException("Offset must be greater than 0");
- }
-
- if (parData.Length + parOffset > Count)
- {
- throw new ArgumentException("Data array is too large");
- }
-
- GL.NamedBufferSubData(Handle, parOffset * _stride, parData.Length * _stride, parData);
- }
-
- public override void Bind()
- {
- GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
- }
-
- public override void Unbind()
- {
- GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
- }
-
- protected override void Destroy()
- {
- GL.DeleteBuffer(Handle);
- }
-}
\ No newline at end of file
diff --git a/Engine/src/Resource/ShaderResource.Designer.cs b/Engine/src/Resource/ShaderResource.Designer.cs
new file mode 100644
index 0000000..a13d62f
--- /dev/null
+++ b/Engine/src/Resource/ShaderResource.Designer.cs
@@ -0,0 +1,101 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Engine {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class ShaderResource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal ShaderResource() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Engine.src.Resource.ShaderResource", typeof(ShaderResource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to #shader vertex
+ ///#version 330 core
+ ///
+ ///uniform mat4 uViewMatrix;
+ ///uniform mat4 uProjectionMatrix;
+ ///
+ ///layout(location = 0) in vec3 aPos;
+ ///layout(location = 1) in vec3 aNormal;
+ ///layout(location = 2) in vec2 aTexCoords;
+ ///layout(location = 3) in mat4 aModelMatrix;
+ ///
+ ///out vec2 outTexCoords;
+ ///
+ ///void main()
+ ///{
+ /// gl_Position = uProjectionMatrix * uViewMatrix * aModelMatrix * vec4(aPos, 1.0);
+ /// outTexCoords = aTexCoords;
+ ///}
+ ///
+ ///#shader fragment
+ ///#version 330 core
+ ///
+ ///uniform sampler2D uTexture;
+ ///
+ ///layout(location [rest of string was truncated]";.
+ ///
+ internal static string Mesh {
+ get {
+ return ResourceManager.GetString("Mesh", resourceCulture);
+ }
+ }
+
+ internal static string Quad {
+ get {
+ return ResourceManager.GetString("Quad", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Engine/src/Resource/ShaderResource.resx b/Engine/src/Resource/ShaderResource.resx
new file mode 100644
index 0000000..a854c31
--- /dev/null
+++ b/Engine/src/Resource/ShaderResource.resx
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ../../assets/shader/mesh.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8
+
+
+
+ ../../assets/shader/quad.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8
+
+
\ No newline at end of file
diff --git a/Engine/src/Scene/Component/BuiltIn/Camera.cs b/Engine/src/Scene/Component/BuiltIn/Camera.cs
index 1d9fc9f..94072d2 100644
--- a/Engine/src/Scene/Component/BuiltIn/Camera.cs
+++ b/Engine/src/Scene/Component/BuiltIn/Camera.cs
@@ -1,4 +1,4 @@
-using Engine.Renderer.Camera;
+using Engine.Graphics.Camera;
using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
diff --git a/Engine/src/Scene/Scene.cs b/Engine/src/Scene/Scene.cs
index d5c2119..1eef787 100644
--- a/Engine/src/Scene/Scene.cs
+++ b/Engine/src/Scene/Scene.cs
@@ -1,4 +1,4 @@
-using Engine.Renderer.Camera;
+using Engine.Graphics.Camera;
using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
diff --git a/Engine/src/Window.cs b/Engine/src/Window.cs
index 947fe97..93e586e 100644
--- a/Engine/src/Window.cs
+++ b/Engine/src/Window.cs
@@ -1,13 +1,21 @@
-using OpenTK.Windowing.Desktop;
+using Engine.Asset;
+using Engine.Graphics;
+using Engine.Graphics.Pixel;
+using Engine.Graphics.Texture;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+using OpenTK.Windowing.Common;
+using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Engine;
-public class Window
+public class Window : IPresenter
{
public bool IsExiting => _window.IsExiting;
public int Width { get; private set; }
public int Height { get; private set; }
+ public event Action? Resize;
private readonly Engine _engine;
private readonly NativeWindow _window;
@@ -26,16 +34,32 @@ public class Window
{
Width = parArgs.Width;
Height = parArgs.Height;
+ Resize?.Invoke(parArgs);
};
}
- public void Update()
+ public void Update(double parDeltaTime)
{
- if (!_headless)
+ if (_headless)
{
- NativeWindow.ProcessWindowEvents(false);
- _window.SwapBuffers();
+ return;
}
+
+ NativeWindow.ProcessWindowEvents(false);
+ _window.SwapBuffers();
+ }
+
+ public void Present(IConstTexture parTexture)
+ {
+ GL.Viewport(0, 0, Width, Height);
+
+ GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ var texture = (Texture)parTexture;
+ _engine.Renderer.QuadRenderer.Commit(Matrix4.CreateScale(2f), Vector4.One, texture);
+ _engine.Renderer.QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity);
+ _engine.Renderer.QuadRenderer.Reset();
}
}
diff --git a/PresenterConsole/PresenterConsole.csproj b/PresenterConsole/PresenterConsole.csproj
index 20ec187..426a52e 100644
--- a/PresenterConsole/PresenterConsole.csproj
+++ b/PresenterConsole/PresenterConsole.csproj
@@ -5,20 +5,30 @@
net8.0
enable
enable
- DoomDeathmatchConsole
-
+
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+ True
+ True
+ ShaderResource.resx
+
+
+ True
+ True
+ ShaderResource.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ ShaderResource.Designer.cs
+
-
-
diff --git a/PresenterConsole/assets/shader/ascii.shader b/PresenterConsole/assets/shader/ascii.shader
new file mode 100644
index 0000000..d0ba834
--- /dev/null
+++ b/PresenterConsole/assets/shader/ascii.shader
@@ -0,0 +1,84 @@
+// #type vertex
+#version 460 core
+
+layout (location = 0) in vec2 aPos;
+layout (location = 1) in vec2 aUV;
+
+layout (location = 0) out vec2 oUV;
+
+void main()
+{
+ oUV = aUV;
+ gl_Position = vec4(aPos, 0.0, 1.0);
+}
+
+// #type fragment
+#version 460 core
+
+precision highp float;
+
+// All components are in the range [0…1], including hue.
+vec3 rgb2hsv(vec3 c)
+{
+ vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
+
+ float d = q.x - min(q.w, q.y);
+ float e = 1.0e-10;
+ return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
+}
+
+// 16 Windows color palette (approximated)
+const vec3 windowsPalette[7] = vec3[](
+vec3(rgb2hsv(vec3(0.0, 0.0, 1.0)).rg, 0.0), // Blue (FOREGROUND_BLUE)
+vec3(rgb2hsv(vec3(0.0, 1.0, 0.0)).rg, 0.0), // Green (FOREGROUND_GREEN)
+vec3(rgb2hsv(vec3(0.0, 1.0, 1.0)).rg, 0.0), // Cyan (FOREGROUND_GREEN | FOREGROUND_BLUE)
+vec3(rgb2hsv(vec3(1.0, 0.0, 0.0)).rg, 0.0), // Red (FOREGROUND_RED)
+vec3(rgb2hsv(vec3(1.0, 0.0, 1.0)).rg, 0.0), // Magenta (FOREGROUND_RED | FOREGROUND_BLUE)
+vec3(rgb2hsv(vec3(1.0, 1.0, 0.0)).rg, 0.0), // Yellow (FOREGROUND_RED | FOREGROUND_GREEN)
+vec3(rgb2hsv(vec3(1.0, 1.0, 1.0)).rg, 0.0) // White (all colors set)
+);
+
+// Find the closest color in the Windows palette
+int findClosestColor(vec3 color) {
+ int closestIndex = 0;
+ float minDistance = distance(color, windowsPalette[0]);
+
+ for (int i = 1; i < 7; i++) {
+ float dist = distance(color, windowsPalette[i]);
+ if (dist < minDistance) {
+ minDistance = dist;
+ closestIndex = i;
+ }
+ }
+
+ return closestIndex + 1;
+}
+
+float calculateLuminosity(vec3 color) {
+ // Standard luminosity calculation
+ float luminosity = dot(color, vec3(0.299, 0.587, 0.114));
+ return clamp(luminosity, 0.0, 1.0);
+}
+
+uniform sampler2D uInputTexture;
+
+layout (location = 0) in vec2 iUV;
+layout (location = 0) out vec4 FragColor;
+
+void main() {
+ vec3 pixelColor = texture(uInputTexture, iUV).rgb;
+ vec3 pixelColorHsv = rgb2hsv(pixelColor);
+
+ // Find closest color index (4 bits)
+ int colorIndex = findClosestColor(vec3(pixelColorHsv.rg, 0));
+
+ // Calculate luminosity (4 bits)
+ float luminosity = calculateLuminosity(pixelColor);
+
+ // Combine into a single byte-like value
+ // High 4 bits: color index
+ // Low 4 bits: luminosity
+ FragColor = vec4(luminosity, colorIndex / 255.0, 0, 1);
+}
\ No newline at end of file
diff --git a/PresenterConsole/src/AsciiPixel.cs b/PresenterConsole/src/AsciiPixel.cs
new file mode 100644
index 0000000..09a058a
--- /dev/null
+++ b/PresenterConsole/src/AsciiPixel.cs
@@ -0,0 +1,21 @@
+using Engine.Graphics.Pixel;
+using OpenTK.Graphics.OpenGL;
+
+namespace PresenterConsole;
+
+public struct AsciiPixel : IPixel
+{
+ public PixelFormat Format => PixelFormat.Rg;
+ public PixelType Type => PixelType.UnsignedByte;
+
+ public PixelInternalFormat InternalFormat => PixelInternalFormat.Rg8;
+ public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rg8;
+
+ public byte LightnessIndex => R;
+ public byte ColorIndex => G;
+
+ public byte R;
+ public byte G;
+
+ public override string ToString() => $"{R}, {G}";
+}
\ No newline at end of file
diff --git a/PresenterConsole/src/AsciiVertex.cs b/PresenterConsole/src/AsciiVertex.cs
new file mode 100644
index 0000000..e7a106b
--- /dev/null
+++ b/PresenterConsole/src/AsciiVertex.cs
@@ -0,0 +1,11 @@
+using Engine.Graphics.Buffer.Vertex;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
+
+namespace PresenterConsole;
+
+public struct AsciiVertex : IVertex
+{
+ [Vertex(VertexAttribType.Float, 2)] public Vector2 _position;
+ [Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
+}
\ No newline at end of file
diff --git a/PresenterConsole/src/ConsoleFastOutput.cs b/PresenterConsole/src/ConsoleFastOutput.cs
new file mode 100644
index 0000000..ca340d0
--- /dev/null
+++ b/PresenterConsole/src/ConsoleFastOutput.cs
@@ -0,0 +1,158 @@
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace PresenterConsole;
+
+public class ConsoleFastOutput
+{
+ [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
+ static extern SafeFileHandle CreateFile(
+ string fileName,
+ [MarshalAs(UnmanagedType.U4)] uint fileAccess,
+ [MarshalAs(UnmanagedType.U4)] uint fileShare,
+ IntPtr securityAttributes,
+ [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
+ [MarshalAs(UnmanagedType.U4)] int flags,
+ IntPtr template);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern bool WriteConsoleOutput(
+ SafeFileHandle hConsoleOutput,
+ CharInfo[] lpBuffer,
+ Coord dwBufferSize,
+ Coord dwBufferCoord,
+ ref SmallRect lpWriteRegion);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Coord
+ {
+ public short X;
+ public short Y;
+
+ public Coord(short X, short Y)
+ {
+ this.X = X;
+ this.Y = Y;
+ }
+ };
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct CharUnion
+ {
+ [FieldOffset(0)] public char UnicodeChar;
+ [FieldOffset(0)] public byte AsciiChar;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct CharInfo
+ {
+ [FieldOffset(0)] public CharUnion Char;
+ [FieldOffset(2)] public short Attributes;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct SmallRect
+ {
+ public short Left;
+ public short Top;
+ public short Right;
+ public short Bottom;
+ }
+
+ private readonly SafeFileHandle _handle;
+ private CharInfo[] _buffer;
+ private SmallRect _region;
+ private int _width;
+ private int _height;
+
+ public ConsoleFastOutput(int parWidth, int parHeight)
+ {
+ _width = parWidth;
+ _height = parHeight;
+ _handle = CreateFile("CONOUT$", 0x40000000, 2, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero);
+ if (_handle.IsInvalid)
+ {
+ throw new Exception("Failed to open console");
+ }
+
+ _buffer = new CharInfo[parWidth * parHeight];
+ _region = new SmallRect { Left = 0, Top = 0, Right = (short)_width, Bottom = (short)_height };
+ }
+
+ public void WriteChar(char parChar, short parX, short parY, ushort parForeground, ushort parBackground)
+ {
+ var index = parX + parY * _width;
+ if (index >= _buffer.Length)
+ return;
+
+ _buffer[parX + parY * _width].Char.UnicodeChar = parChar;
+ _buffer[parX + parY * _width].Attributes = (short)(parForeground | (parBackground << 4));
+ }
+
+ public void Flush()
+ {
+ var regions = SplitRegion(_region, 1, 1);
+ for (var i = 0; i < regions.Length; i++)
+ {
+ var region = regions[i];
+ var bufferSize = new Coord(region.Right, region.Bottom);
+ var bufferCoord = new Coord(region.Left, region.Top);
+ WriteConsoleOutput(_handle, _buffer, bufferSize, bufferCoord, ref region);
+ }
+
+ // var bufferSize = new Coord(_region.Right, _region.Bottom);
+ // var bufferCoord = new Coord(0, 0);
+ // WriteConsoleOutput(_handle, _buffer, bufferSize, bufferCoord, ref _region);
+ //
+ // _region.Left = 0;
+ // _region.Top = 0;
+ // _region.Right = (short)_width;
+ // _region.Bottom = (short)_height;
+
+ // run in parallel
+ // Parallel.ForEach(regions, region =>
+ // {
+ // var bufferSize = new Coord(_region.Right, _region.Bottom);
+ // var bufferCoord = new Coord(region.Left, region.Top);
+ // WriteConsoleOutput(_handle, _buffer, bufferSize, bufferCoord, ref region);
+ // });
+ }
+
+ private SmallRect[] SplitRegion(SmallRect parRegion, int countX, int countY)
+ {
+ var regions = new SmallRect[countX * countY];
+ for (var y = 0; y < countY; y++)
+ {
+ for (var x = 0; x < countX; x++)
+ {
+ var region = parRegion;
+ region.Left = (short)(parRegion.Left + x * parRegion.Right / countX);
+ region.Right = (short)(parRegion.Left + (x + 1) * parRegion.Right / countX);
+ region.Top = (short)(parRegion.Top + y * parRegion.Bottom / countY);
+ region.Bottom = (short)(parRegion.Top + (y + 1) * parRegion.Bottom / countY);
+ regions[y * countX + x] = region;
+ }
+ }
+
+ return regions;
+ }
+
+ public void Resize(int parWidth, int parHeight)
+ {
+ if (parWidth <= 0 || parHeight <= 0)
+ {
+ throw new ArgumentException("Width and height must be greater than 0");
+ }
+
+ if (parWidth == _width && parHeight == _height)
+ {
+ return;
+ }
+
+ _width = parWidth;
+ _height = parHeight;
+
+ _buffer = new CharInfo[parWidth * parHeight];
+ _region = new SmallRect { Left = 0, Top = 0, Right = (short)parWidth, Bottom = (short)parHeight };
+ }
+}
\ No newline at end of file
diff --git a/PresenterConsole/src/ConsoleInputHandler.cs b/PresenterConsole/src/ConsoleInputHandler.cs
index 111fd3e..ed57f26 100644
--- a/PresenterConsole/src/ConsoleInputHandler.cs
+++ b/PresenterConsole/src/ConsoleInputHandler.cs
@@ -4,126 +4,126 @@ namespace PresenterConsole;
public class ConsoleInputHandler : IInputHandler
{
- private readonly HashSet _currentKeys = [];
- private readonly HashSet _previousKeys = [];
- private ConsoleModifiers _currentModifiers;
- private ConsoleModifiers _previousModifiers;
+ private readonly HashSet _currentKeys = [];
+ private readonly HashSet _previousKeys = [];
+ private ConsoleModifiers _currentModifiers;
+ private ConsoleModifiers _previousModifiers;
- public void Update(double deltaTime)
+ public void Update(double parDeltaTime)
+ {
+ // Save previous state
+ _previousKeys.Clear();
+ foreach (var key in _currentKeys)
{
- // Save previous state
- _previousKeys.Clear();
- foreach (var key in _currentKeys)
- {
- _previousKeys.Add(key);
- }
-
- _previousModifiers = _currentModifiers;
-
- // Clear current state
- _currentKeys.Clear();
- _currentModifiers = 0;
-
- // Read keys
- while (Console.KeyAvailable)
- {
- var keyInfo = Console.ReadKey(intercept: true);
- _currentKeys.Add(keyInfo.Key);
- _currentModifiers |= keyInfo.Modifiers;
- }
+ _previousKeys.Add(key);
}
- public bool IsKeyPressed(KeyCode keyCode)
- {
- if (IsModifierKey(keyCode))
- return IsModifierActive(keyCode);
+ _previousModifiers = _currentModifiers;
- return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
+ // Clear current state
+ _currentKeys.Clear();
+ _currentModifiers = 0;
+
+ // Read keys
+ while (Console.KeyAvailable)
+ {
+ var keyInfo = Console.ReadKey(intercept: true);
+ _currentKeys.Add(keyInfo.Key);
+ _currentModifiers |= keyInfo.Modifiers;
}
+ }
- public bool IsKeyJustPressed(KeyCode keyCode)
- {
- if (IsModifierKey(keyCode))
- return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
+ public bool IsKeyPressed(KeyCode keyCode)
+ {
+ if (IsModifierKey(keyCode))
+ return IsModifierActive(keyCode);
- var consoleKey = ConvertToConsoleKey(keyCode);
- return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
- }
+ return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
+ }
- public bool IsKeyRepeat(KeyCode keyCode)
- {
- if (IsModifierKey(keyCode))
- return IsModifierActive(keyCode) && WasModifierActive(keyCode);
+ public bool IsKeyJustPressed(KeyCode keyCode)
+ {
+ if (IsModifierKey(keyCode))
+ return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
- var consoleKey = ConvertToConsoleKey(keyCode);
- return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
- }
+ var consoleKey = ConvertToConsoleKey(keyCode);
+ return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
+ }
- public bool IsMouseButtonPressed(MouseButton button)
- {
- return false;
- }
+ public bool IsKeyRepeat(KeyCode keyCode)
+ {
+ if (IsModifierKey(keyCode))
+ return IsModifierActive(keyCode) && WasModifierActive(keyCode);
- public bool IsMouseButtonJustPressed(MouseButton button)
- {
- return false;
- }
+ var consoleKey = ConvertToConsoleKey(keyCode);
+ return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
+ }
- private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
+ public bool IsMouseButtonPressed(MouseButton button)
+ {
+ return false;
+ }
- private bool IsModifierActive(KeyCode keyCode) => keyCode switch
- {
- KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
- KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
- KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
- _ => false
- };
+ public bool IsMouseButtonJustPressed(MouseButton button)
+ {
+ return false;
+ }
- private bool WasModifierActive(KeyCode keyCode) => keyCode switch
- {
- KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
- KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
- KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
- _ => false
- };
+ private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
- private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
- {
- KeyCode.A => ConsoleKey.A,
- KeyCode.B => ConsoleKey.B,
- KeyCode.C => ConsoleKey.C,
- KeyCode.D => ConsoleKey.D,
- KeyCode.E => ConsoleKey.E,
- KeyCode.F => ConsoleKey.F,
- KeyCode.G => ConsoleKey.G,
- KeyCode.H => ConsoleKey.H,
- KeyCode.I => ConsoleKey.I,
- KeyCode.J => ConsoleKey.J,
- KeyCode.K => ConsoleKey.K,
- KeyCode.L => ConsoleKey.L,
- KeyCode.M => ConsoleKey.M,
- KeyCode.N => ConsoleKey.N,
- KeyCode.O => ConsoleKey.O,
- KeyCode.P => ConsoleKey.P,
- KeyCode.Q => ConsoleKey.Q,
- KeyCode.R => ConsoleKey.R,
- KeyCode.S => ConsoleKey.S,
- KeyCode.T => ConsoleKey.T,
- KeyCode.U => ConsoleKey.U,
- KeyCode.V => ConsoleKey.V,
- KeyCode.W => ConsoleKey.W,
- KeyCode.X => ConsoleKey.X,
- KeyCode.Y => ConsoleKey.Y,
- KeyCode.Z => ConsoleKey.Z,
- KeyCode.Enter => ConsoleKey.Enter,
- KeyCode.Escape => ConsoleKey.Escape,
- KeyCode.Space => ConsoleKey.Spacebar,
- KeyCode.Tab => ConsoleKey.Tab,
- KeyCode.Backspace => ConsoleKey.Backspace,
- KeyCode.Up => ConsoleKey.UpArrow,
- KeyCode.Down => ConsoleKey.DownArrow,
- KeyCode.Left => ConsoleKey.LeftArrow,
- KeyCode.Right => ConsoleKey.RightArrow,
- _ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
- };
+ private bool IsModifierActive(KeyCode keyCode) => keyCode switch
+ {
+ KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
+ KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
+ KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
+ _ => false
+ };
+
+ private bool WasModifierActive(KeyCode keyCode) => keyCode switch
+ {
+ KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
+ KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
+ KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
+ _ => false
+ };
+
+ private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
+ {
+ KeyCode.A => ConsoleKey.A,
+ KeyCode.B => ConsoleKey.B,
+ KeyCode.C => ConsoleKey.C,
+ KeyCode.D => ConsoleKey.D,
+ KeyCode.E => ConsoleKey.E,
+ KeyCode.F => ConsoleKey.F,
+ KeyCode.G => ConsoleKey.G,
+ KeyCode.H => ConsoleKey.H,
+ KeyCode.I => ConsoleKey.I,
+ KeyCode.J => ConsoleKey.J,
+ KeyCode.K => ConsoleKey.K,
+ KeyCode.L => ConsoleKey.L,
+ KeyCode.M => ConsoleKey.M,
+ KeyCode.N => ConsoleKey.N,
+ KeyCode.O => ConsoleKey.O,
+ KeyCode.P => ConsoleKey.P,
+ KeyCode.Q => ConsoleKey.Q,
+ KeyCode.R => ConsoleKey.R,
+ KeyCode.S => ConsoleKey.S,
+ KeyCode.T => ConsoleKey.T,
+ KeyCode.U => ConsoleKey.U,
+ KeyCode.V => ConsoleKey.V,
+ KeyCode.W => ConsoleKey.W,
+ KeyCode.X => ConsoleKey.X,
+ KeyCode.Y => ConsoleKey.Y,
+ KeyCode.Z => ConsoleKey.Z,
+ KeyCode.Enter => ConsoleKey.Enter,
+ KeyCode.Escape => ConsoleKey.Escape,
+ KeyCode.Space => ConsoleKey.Spacebar,
+ KeyCode.Tab => ConsoleKey.Tab,
+ KeyCode.Backspace => ConsoleKey.Backspace,
+ KeyCode.Up => ConsoleKey.UpArrow,
+ KeyCode.Down => ConsoleKey.DownArrow,
+ KeyCode.Left => ConsoleKey.LeftArrow,
+ KeyCode.Right => ConsoleKey.RightArrow,
+ _ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
+ };
}
\ No newline at end of file
diff --git a/PresenterConsole/src/ConsolePresenter.cs b/PresenterConsole/src/ConsolePresenter.cs
index 6a4a194..2ff51a7 100644
--- a/PresenterConsole/src/ConsolePresenter.cs
+++ b/PresenterConsole/src/ConsolePresenter.cs
@@ -1,51 +1,117 @@
using Engine.Asset;
-using Engine.Renderer;
-using Engine.Renderer.Framebuffer;
-using Engine.Renderer.Texture;
+using Engine.Graphics;
+using Engine.Graphics.Buffer;
+using Engine.Graphics.Framebuffer;
+using Engine.Graphics.Pixel;
+using Engine.Graphics.Shader;
+using Engine.Graphics.Texture;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
namespace PresenterConsole;
public class ConsolePresenter : IPresenter
{
- public bool IsExiting => false;
- public int Width => Console.WindowWidth;
- public int Height => Console.WindowHeight;
- public event Action? Resize;
+ public bool IsExiting => false;
+ public int Width => Console.WindowWidth;
+ public int Height => Console.WindowHeight;
+ public event Action? Resize;
- private readonly Framebuffer _framebuffer;
- private Image _asciiImage;
+ private int _cachedWidth;
+ private int _cachedHeight;
- public ConsolePresenter(int width, int height)
+ private readonly Engine.Engine _engine;
+ private readonly Framebuffer _framebuffer;
+ private readonly Engine.Graphics.Shader.Program _asciiProgram;
+ private Image? _asciiImage;
+
+ private readonly IndexBuffer _indexBuffer;
+ private readonly VertexBuffer _vertexBuffer;
+ private readonly VertexArray _vertexArray;
+
+ private readonly ConsoleFastOutput _consoleOutput;
+
+ public ConsolePresenter(Engine.Engine parEngine)
+ {
+ _engine = parEngine;
+ _asciiProgram = ProgramLoader.LoadFromSource(Resource.ShaderResource.Ascii);
+
+ _framebuffer = Framebuffer.Builder(Width, Height)
+ .AddColorAttachment()
+ .Build();
+
+ _indexBuffer = new IndexBuffer([0, 1, 2, 2, 3, 0], BufferStorageFlags.MapReadBit);
+ _vertexBuffer = new VertexBuffer([
+ new AsciiVertex { _position = new Vector2(-1.0f, -1.0f), _uv = new Vector2(0.0f, 0.0f) },
+ new AsciiVertex { _position = new Vector2(1.0f, -1.0f), _uv = new Vector2(1.0f, 0.0f) },
+ new AsciiVertex { _position = new Vector2(1.0f, 1.0f), _uv = new Vector2(1.0f, 1.0f) },
+ new AsciiVertex { _position = new Vector2(-1.0f, 1.0f), _uv = new Vector2(0.0f, 1.0f) }
+ ], BufferStorageFlags.MapReadBit);
+ _vertexArray = new VertexArray();
+ _vertexArray.BindIndexBuffer(_indexBuffer);
+ _vertexArray.BindVertexBuffer(_vertexBuffer);
+
+ _consoleOutput = new ConsoleFastOutput(Width, Height);
+ }
+
+ public void Present(IConstTexture parTexture)
+ {
+ var openglTexture = (Texture)parTexture;
+
+ _framebuffer.Bind();
+
+ GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ openglTexture.BindUnit();
+
+ _asciiProgram.Bind();
+ _asciiProgram.SetUniform("uInputTexture", 0);
+
+ _vertexArray.Bind();
+ GL.DrawElements(PrimitiveType.Triangles, _indexBuffer.Count, DrawElementsType.UnsignedInt, 0);
+
+ _framebuffer.Unbind();
+
+ var asciiTexture = _framebuffer.TextureInternal;
+ if (asciiTexture == null)
+ throw new InvalidOperationException("Framebuffer texture is null");
+
+ if (_asciiImage == null || asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
+ _asciiImage = new Image(asciiTexture.Width, asciiTexture.Height);
+
+ asciiTexture.ReadPixels(_asciiImage);
+ Output(_asciiImage);
+ }
+
+ private void Output(Image parImage)
+ {
+ var lightmap = " .-:=+*#%@".Reverse().ToArray();
+ for (var y = 0; y < parImage.Height; y++)
{
- _framebuffer = Framebuffer.Builder(width, height)
- .AddColorAttachment()
- .Build();
+ for (var x = 0; x < parImage.Width; x++)
+ {
+ var pixel = parImage[y, x];
+ var lightnessIndex = (int)(pixel.LightnessIndex / 255.0f * (lightmap.Length - 1));
+ _consoleOutput.WriteChar(lightmap[lightnessIndex], (short)x, (short)y, 0,
+ pixel.ColorIndex);
+ }
}
- public void Present(IConstTexture texture)
+ _consoleOutput.Flush();
+ }
+
+ public void Update(double parDeltaTime)
+ {
+ if (_cachedWidth != Width || _cachedHeight != Height)
{
- var openglTexture = (Texture)texture;
+ _cachedWidth = Width;
+ _cachedHeight = Height;
- _framebuffer.Bind();
- openglTexture.BindUnit();
-
- // TODO: render with ascii shader
-
- _framebuffer.Unbind();
-
- var asciiTexture = _framebuffer.TextureInternal;
- if (asciiTexture == null)
- throw new InvalidOperationException("Framebuffer texture is null");
-
- if (asciiTexture.Width != _asciiImage.Width || asciiTexture.Height != _asciiImage.Height)
- _asciiImage = new Image(asciiTexture.Width, asciiTexture.Height);
-
- asciiTexture.ReadPixels(_asciiImage);
- }
-
- public void Update(double deltaTime)
- {
- throw new NotImplementedException();
+ _framebuffer.Resize(Width, Height);
+ _engine.Renderer.Resize(Width, Height);
+ _consoleOutput.Resize(Width, Height);
}
+ }
}
\ No newline at end of file
diff --git a/PresenterConsole/src/Program.cs b/PresenterConsole/src/Program.cs
new file mode 100644
index 0000000..8d99222
--- /dev/null
+++ b/PresenterConsole/src/Program.cs
@@ -0,0 +1,12 @@
+// See https://aka.ms/new-console-template for more information
+
+using PresenterConsole;
+
+internal class Program
+{
+ public static void Main(string[] args)
+ {
+ var engine = new Engine.Engine(240, 135, (parEngine) => new ConsolePresenter(parEngine));
+ engine.Run();
+ }
+}
\ No newline at end of file
diff --git a/PresenterConsole/src/Resource/ShaderResource.Designer.cs b/PresenterConsole/src/Resource/ShaderResource.Designer.cs
new file mode 100644
index 0000000..ca6e474
--- /dev/null
+++ b/PresenterConsole/src/Resource/ShaderResource.Designer.cs
@@ -0,0 +1,95 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PresenterConsole.Resource {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class ShaderResource {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal ShaderResource() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PresenterConsole.src.Resource.ShaderResource", typeof(ShaderResource).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to // #type vertex
+ ///#version 460 core
+ ///
+ ///layout (location = 0) in vec2 aPos;
+ ///layout (location = 1) in vec2 aUV;
+ ///
+ ///layout (location = 0) out vec2 oUV;
+ ///
+ ///void main()
+ ///{
+ /// oUV = aUV;
+ /// gl_Position = vec4(aPos, 0.0, 1.0);
+ ///}
+ ///
+ /// // #type fragment
+ ///#version 460 core
+ ///
+ ///precision highp float;
+ ///
+ /// // All components are in the range [0…1], including hue.
+ ///vec3 rgb2hsv(vec3 c)
+ ///{
+ /// vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
+ /// vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
+ /// vec4 [rest of string was truncated]";.
+ ///
+ internal static string Ascii {
+ get {
+ return ResourceManager.GetString("Ascii", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/PresenterConsole/src/Resource/ShaderResource.resx b/PresenterConsole/src/Resource/ShaderResource.resx
new file mode 100644
index 0000000..4d2ff4c
--- /dev/null
+++ b/PresenterConsole/src/Resource/ShaderResource.resx
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
+ PublicKeyToken=b77a5c561934e089
+
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
+ PublicKeyToken=b77a5c561934e089
+
+
+
+
+ ../../assets/shader/ascii.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8
+
+
\ No newline at end of file
diff --git a/PresenterWPF/PresenterWPF.csproj b/PresenterWPF/PresenterWPF.csproj
deleted file mode 100644
index 4147e0f..0000000
--- a/PresenterWPF/PresenterWPF.csproj
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- WinExe
- net8.0-windows
- enable
- enable
- true
- DoomDeathmatchWPF
-
-
-
-
-
-
-
-
- MSBuild:Compile
- Wpf
- Designer
-
-
-
-
-
- MSBuild:Compile
- Wpf
- Designer
-
-
-
-
diff --git a/PresenterWpf/PresenterWpf.csproj b/PresenterWpf/PresenterWpf.csproj
new file mode 100644
index 0000000..9588090
--- /dev/null
+++ b/PresenterWpf/PresenterWpf.csproj
@@ -0,0 +1,31 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Wpf
+ Designer
+
+
+
+
+
+ MSBuild:Compile
+ Wpf
+ Designer
+
+
+
+
diff --git a/PresenterWpf/src/App.xaml b/PresenterWpf/src/App.xaml
new file mode 100644
index 0000000..f841d25
--- /dev/null
+++ b/PresenterWpf/src/App.xaml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/PresenterWpf/src/App.xaml.cs b/PresenterWpf/src/App.xaml.cs
new file mode 100644
index 0000000..c8c74ac
--- /dev/null
+++ b/PresenterWpf/src/App.xaml.cs
@@ -0,0 +1,83 @@
+using System.Configuration;
+using System.Data;
+using System.Windows;
+using Engine.Graphics;
+using Engine.Graphics.Texture;
+using OpenTK.Windowing.Common;
+
+namespace PresenterWpf;
+
+///
+/// Interaction logic for App.xaml
+///
+public partial class App : Application
+{
+ // Hijack the default startup event to start the engine
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ var presenter = new PresenterWrapper();
+ var engine = new Engine.Engine(1, 1, _ => presenter);
+
+ // Since engine claims current thread for rendering, we need to create a new thread to run WPF
+ var thread = new Thread(() =>
+ {
+ var window = new MainWindow(engine);
+ presenter.Presenter = window;
+ window.Show();
+ System.Windows.Threading.Dispatcher.Run();
+ });
+
+ thread.SetApartmentState(ApartmentState.STA);
+ thread.IsBackground = true;
+ thread.Start();
+
+ engine.Run();
+
+ // Shutdown WPF
+ Shutdown();
+ }
+
+ private class PresenterWrapper : IPresenter
+ {
+ private IPresenter? _presenter;
+
+ public IPresenter? Presenter
+ {
+ get => _presenter;
+ set
+ {
+ if (_presenter != null)
+ {
+ _presenter.Resize -= PresenterResize;
+ }
+
+ if (value != null)
+ {
+ value.Resize += PresenterResize;
+ }
+
+ _presenter = value;
+ }
+ }
+
+ public bool IsExiting => Presenter?.IsExiting ?? false;
+ public int Width => Presenter?.Width ?? 0;
+ public int Height => Presenter?.Height ?? 0;
+ public event Action? Resize;
+
+ public void Present(IConstTexture parTexture)
+ {
+ Presenter?.Present(parTexture);
+ }
+
+ public void Update(double parDeltaTime)
+ {
+ Presenter?.Update(parDeltaTime);
+ }
+
+ private void PresenterResize(ResizeEventArgs e)
+ {
+ Resize?.Invoke(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PresenterWpf/src/AssemblyInfo.cs b/PresenterWpf/src/AssemblyInfo.cs
new file mode 100644
index 0000000..d49fd6c
--- /dev/null
+++ b/PresenterWpf/src/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
\ No newline at end of file
diff --git a/PresenterWpf/src/MainWindow.xaml b/PresenterWpf/src/MainWindow.xaml
new file mode 100644
index 0000000..91633ef
--- /dev/null
+++ b/PresenterWpf/src/MainWindow.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/PresenterWpf/src/MainWindow.xaml.cs b/PresenterWpf/src/MainWindow.xaml.cs
new file mode 100644
index 0000000..f8e8420
--- /dev/null
+++ b/PresenterWpf/src/MainWindow.xaml.cs
@@ -0,0 +1,101 @@
+using System.ComponentModel;
+using System.IO;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+using Engine.Asset;
+using Engine.Graphics;
+using Engine.Graphics.Pixel;
+using Engine.Graphics.Texture;
+using OpenTK.Windowing.Common;
+
+namespace PresenterWpf;
+
+///
+/// Interaction logic for MainWindow.xaml
+///
+public partial class MainWindow : Window, IPresenter
+{
+ public bool IsExiting { get; set; }
+ public int Width => (int)_cachedWidth;
+ public int Height => (int)_cachedHeight;
+ public event Action? Resize;
+
+
+ private Engine.Engine _engine;
+ private Image? _image;
+ private WriteableBitmap? _bitmap;
+
+ private int _cachedWidth;
+ private int _cachedHeight;
+
+ public MainWindow(Engine.Engine parEngine)
+ {
+ InitializeComponent();
+
+ _engine = parEngine;
+ }
+
+ public void Update(double parDeltaTime)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ if ((int)Image.Width != _cachedWidth || (int)Image.Height != _cachedHeight)
+ {
+ _cachedWidth = (int)Image.Width;
+ _cachedHeight = (int)Image.Height;
+ }
+ });
+
+ // Resizes are lazy so resizing only happens when the window's size actually changes
+ if (Width != 0 && Height != 0)
+ _engine.Renderer.Resize(Width, Height);
+ }
+
+ public void Present(IConstTexture parTexture)
+ {
+ if (_image == null || parTexture.Width != _image.Width || parTexture.Height != _image.Height)
+ {
+ _image = new Image(parTexture.Width, parTexture.Height);
+ }
+
+ parTexture.ReadPixels(_image);
+ Dispatcher.Invoke(() =>
+ {
+ if (_bitmap == null || _bitmap.PixelWidth != _image.Width || _bitmap.PixelHeight != _image.Height)
+ {
+ _bitmap = new WriteableBitmap(_image.Width, _image.Height, 96, 96, PixelFormats.Rgb24, null);
+ }
+
+ DrawImage(_image);
+ Image.Source = _bitmap;
+ });
+ }
+
+ private void DrawImage(Image parImage)
+ {
+ try
+ {
+ _bitmap!.Lock();
+ _bitmap.WritePixels(new Int32Rect(0, 0, parImage.Width, parImage.Height), parImage.Pixels, parImage.Width * 3, 0,
+ 0);
+ }
+ finally
+ {
+ _bitmap!.Unlock();
+ }
+ }
+
+ private void MainWindow_OnClosing(object? parSender, CancelEventArgs parE)
+ {
+ IsExiting = true;
+ }
+}
\ No newline at end of file
diff --git a/PresenterWpf/src/Program.cs b/PresenterWpf/src/Program.cs
new file mode 100644
index 0000000..bd8481d
--- /dev/null
+++ b/PresenterWpf/src/Program.cs
@@ -0,0 +1,5 @@
+namespace PresenterWpf;
+
+internal class Program
+{
+}
\ No newline at end of file