.
This commit is contained in:
@@ -19,7 +19,7 @@ insert_final_newline = false
|
|||||||
|
|
||||||
# Organize usings
|
# Organize usings
|
||||||
dotnet_separate_import_directive_groups = false
|
dotnet_separate_import_directive_groups = false
|
||||||
dotnet_sort_system_directives_first = false
|
dotnet_sort_system_directives_first = true
|
||||||
file_header_template = unset
|
file_header_template = unset
|
||||||
|
|
||||||
# this. and Me. preferences
|
# this. and Me. preferences
|
||||||
@@ -101,7 +101,7 @@ csharp_style_conditional_delegate_call = true
|
|||||||
|
|
||||||
# Modifier preferences
|
# Modifier preferences
|
||||||
csharp_prefer_static_local_function = true
|
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
|
# Code-block preferences
|
||||||
csharp_prefer_braces = true
|
csharp_prefer_braces = true
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ internal abstract class Program
|
|||||||
|
|
||||||
public static void Main(string[] args)
|
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();
|
engine.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Engine.Renderer.Buffer.Vertex;
|
using Engine.Graphics.Buffer.Vertex;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using Half = System.Half;
|
using Half = System.Half;
|
||||||
|
|||||||
@@ -11,12 +11,30 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InternalsVisibleTo Include="EngineTests" />
|
<InternalsVisibleTo Include="EngineTests"/>
|
||||||
|
<InternalsVisibleTo Include="PresenterConsole"/>
|
||||||
|
<InternalsVisibleTo Include="PresenterWpf"/>
|
||||||
|
|
||||||
<PackageReference Include="OpenTK" Version="4.8.2"/>
|
<PackageReference Include="OpenTK" Version="4.8.2"/>
|
||||||
<PackageReference Include="Serilog" Version="4.1.0"/>
|
<PackageReference Include="Serilog" Version="4.1.0"/>
|
||||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0"/>
|
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0"/>
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="src\Resource\ShaderResource.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Test.Designer.cs</LastGenOutput>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="src\Resource\ShaderResource.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>ShaderResource.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
34
Engine/assets/shader/mesh.shader
Normal file
34
Engine/assets/shader/mesh.shader
Normal file
@@ -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;
|
||||||
|
}
|
||||||
46
Engine/assets/shader/quad.shader
Normal file
46
Engine/assets/shader/quad.shader
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Pixel;
|
||||||
using Engine.Renderer.Texture;
|
using Engine.Graphics.Texture;
|
||||||
|
|
||||||
namespace Engine.Asset;
|
namespace Engine.Asset;
|
||||||
|
|
||||||
public class Image<T>(T[,] parPixels)
|
public class Image<T>(T[,] parPixels)
|
||||||
where T : struct, IPixel
|
where T : struct, IPixel
|
||||||
{
|
{
|
||||||
public int Width { get; } = parPixels.GetLength(0);
|
public int Width { get; } = parPixels.GetLength(1);
|
||||||
public int Height { get; } = parPixels.GetLength(1);
|
public int Height { get; } = parPixels.GetLength(0);
|
||||||
public T[,] Pixels { get; } = parPixels;
|
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])
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Engine.Renderer.Buffer.Vertex;
|
using Engine.Graphics.Buffer.Vertex;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
@@ -15,6 +15,11 @@ public class Mesh
|
|||||||
private readonly List<Vertex> _vertices = [];
|
private readonly List<Vertex> _vertices = [];
|
||||||
private readonly List<uint> _indices = [];
|
private readonly List<uint> _indices = [];
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Indices, Vertices);
|
||||||
|
}
|
||||||
|
|
||||||
public record struct Vertex : IVertex
|
public record struct Vertex : IVertex
|
||||||
{
|
{
|
||||||
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
|
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Engine.Graphics;
|
||||||
using Engine.Input;
|
using Engine.Input;
|
||||||
using Engine.Scene;
|
using Engine.Scene;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
@@ -12,14 +13,15 @@ namespace Engine;
|
|||||||
|
|
||||||
public sealed class Engine
|
public sealed class Engine
|
||||||
{
|
{
|
||||||
public Renderer.Renderer Renderer => _renderer;
|
public Renderer Renderer => _renderer;
|
||||||
public SceneManager SceneManager => _sceneManager;
|
public SceneManager SceneManager => _sceneManager;
|
||||||
|
|
||||||
private readonly Window _window;
|
private readonly Window _window;
|
||||||
private readonly Renderer.Renderer _renderer;
|
private readonly Renderer _renderer;
|
||||||
private readonly SceneManager _sceneManager = new();
|
private readonly SceneManager _sceneManager = new();
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IInputHandler _inputHandler;
|
private readonly IInputHandler _inputHandler;
|
||||||
|
private readonly IPresenter _presenter;
|
||||||
|
|
||||||
private Thread? _updateThread;
|
private Thread? _updateThread;
|
||||||
|
|
||||||
@@ -30,12 +32,20 @@ public sealed class Engine
|
|||||||
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
|
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
|
||||||
Title = parTitle,
|
Title = parTitle,
|
||||||
StartVisible = !parHeadless,
|
StartVisible = !parHeadless,
|
||||||
APIVersion = new Version(4, 5),
|
APIVersion = new Version(4, 6),
|
||||||
Profile = ContextProfile.Compatability
|
Profile = ContextProfile.Compatability
|
||||||
};
|
};
|
||||||
|
|
||||||
_window = new Window(this, new NativeWindow(settings), parHeadless);
|
_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";
|
Thread.CurrentThread.Name = "RendererThread";
|
||||||
|
|
||||||
@@ -53,18 +63,36 @@ public sealed class Engine
|
|||||||
_logger = Log.ForContext<Engine>();
|
_logger = Log.ForContext<Engine>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Engine(int parWidth, int parHeight, Func<Engine, IPresenter> parPresenter) : this(parWidth, parHeight, true)
|
||||||
|
{
|
||||||
|
_presenter = parPresenter(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_updateThread = new Thread(RunUpdate);
|
_updateThread = new Thread(RunUpdate);
|
||||||
_updateThread.Start();
|
_updateThread.Start();
|
||||||
|
|
||||||
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
|
var timer = Stopwatch.StartNew();
|
||||||
|
var deltaTime = 0.0;
|
||||||
GL.Viewport(0, 0, _window.Width, _window.Height);
|
while (!_presenter.IsExiting)
|
||||||
|
|
||||||
while (!_window.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();
|
_updateThread.Join();
|
||||||
@@ -73,11 +101,12 @@ public sealed class Engine
|
|||||||
private void RunUpdate()
|
private void RunUpdate()
|
||||||
{
|
{
|
||||||
var timer = Stopwatch.StartNew();
|
var timer = Stopwatch.StartNew();
|
||||||
while (!_window.IsExiting)
|
var deltaTime = 0.0;
|
||||||
|
while (!_presenter.IsExiting)
|
||||||
{
|
{
|
||||||
_window.Update();
|
_sceneManager.Update(deltaTime);
|
||||||
_sceneManager.Update(timer.Elapsed.TotalSeconds);
|
|
||||||
timer.Restart();
|
timer.Restart();
|
||||||
|
deltaTime = timer.Elapsed.TotalSeconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer.Buffer;
|
namespace Engine.Graphics.Buffer;
|
||||||
|
|
||||||
public class IndexBuffer : OpenGlObject
|
public class IndexBuffer : OpenGlObject
|
||||||
{
|
{
|
||||||
internal int Count { get; }
|
internal int Count { get; }
|
||||||
|
|
||||||
public IndexBuffer(int parCount, BufferStorageFlags parFlags)
|
public IndexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
|
||||||
{
|
{
|
||||||
Count = parCount;
|
Count = parCount;
|
||||||
|
|
||||||
@@ -19,6 +19,19 @@ public class IndexBuffer : OpenGlObject
|
|||||||
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
|
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)
|
public void UploadData(uint[] parData)
|
||||||
{
|
{
|
||||||
UploadData(0, parData);
|
UploadData(0, parData);
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Buffer.Vertex;
|
namespace Engine.Graphics.Buffer.Vertex;
|
||||||
|
|
||||||
public interface IVertex
|
public interface IVertex
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,7 @@ public interface IVertex
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IOrderedEnumerable<FieldInfo>? fields = GetFields(parType);
|
var fields = GetFields(parType);
|
||||||
var totalSize = 0;
|
var totalSize = 0;
|
||||||
foreach (var field in fields)
|
foreach (var field in fields)
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Buffer.Vertex;
|
namespace Engine.Graphics.Buffer.Vertex;
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
public class VertexAttribute : Attribute
|
public class VertexAttribute : Attribute
|
||||||
@@ -10,8 +10,9 @@ public class VertexAttribute : Attribute
|
|||||||
public bool Normalized { get; }
|
public bool Normalized { get; }
|
||||||
public int RepeatCount { get; }
|
public int RepeatCount { get; }
|
||||||
|
|
||||||
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, bool parNormalized = false,
|
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, int parRepeatCount = 1,
|
||||||
int parRepeatCount = 1)
|
bool parNormalized = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (parComponentCount <= 0)
|
if (parComponentCount <= 0)
|
||||||
{
|
{
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Engine.Renderer.Buffer.Vertex;
|
using Engine.Graphics.Buffer.Vertex;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer.Buffer;
|
namespace Engine.Graphics.Buffer;
|
||||||
|
|
||||||
public class VertexArray : OpenGlObject
|
public class VertexArray : OpenGlObject
|
||||||
{
|
{
|
||||||
// private IndexBuffer? _boundIndexBuffer;
|
// private IndexBuffer? _boundIndexBuffer;
|
||||||
// private readonly Dictionary<int, VertexBuffer<IVertex>> _boundVertexBuffers = new();
|
// private readonly Dictionary<int, VertexBuffer<IVertex>> _boundVertexBuffers = new();
|
||||||
|
private int _enabledAttribs = 0;
|
||||||
|
|
||||||
public VertexArray()
|
public VertexArray()
|
||||||
{
|
{
|
||||||
@@ -42,15 +43,14 @@ public class VertexArray : OpenGlObject
|
|||||||
|
|
||||||
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
|
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
|
||||||
|
|
||||||
var location = 0;
|
|
||||||
foreach (var field in fields)
|
foreach (var field in fields)
|
||||||
{
|
{
|
||||||
var attribute = field.GetCustomAttribute<VertexAttribute>()!;
|
var attribute = field.GetCustomAttribute<VertexAttribute>()!;
|
||||||
|
|
||||||
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
|
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
|
||||||
SetupAttribute(attribute, location, offset, parBindingIndex);
|
SetupAttribute(attribute, _enabledAttribs, offset, parBindingIndex);
|
||||||
|
|
||||||
location += attribute.RepeatCount;
|
_enabledAttribs += attribute.RepeatCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor);
|
GL.VertexArrayBindingDivisor(Handle, parBindingIndex, parDivisor);
|
||||||
108
Engine/src/Graphics/Buffer/VertexBuffer.cs
Normal file
108
Engine/src/Graphics/Buffer/VertexBuffer.cs
Normal file
@@ -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<T> : OpenGlObject
|
||||||
|
where T : struct, IVertex
|
||||||
|
{
|
||||||
|
internal int Count { get; }
|
||||||
|
|
||||||
|
private readonly int _stride = Marshal.SizeOf<T>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Engine.Renderer.Camera;
|
namespace Engine.Graphics.Camera;
|
||||||
|
|
||||||
public interface ICamera
|
public interface ICamera
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Engine.Renderer.Camera;
|
namespace Engine.Graphics.Camera;
|
||||||
|
|
||||||
public class ScreenspaceCamera : ICamera
|
public class ScreenspaceCamera : ICamera
|
||||||
{
|
{
|
||||||
@@ -3,7 +3,7 @@ using OpenTK.Graphics.OpenGL;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Engine.Renderer;
|
namespace Engine.Graphics;
|
||||||
|
|
||||||
internal static class Debug
|
internal static class Debug
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Texture;
|
||||||
using Engine.Renderer.Texture;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer.Framebuffer;
|
namespace Engine.Graphics.Framebuffer;
|
||||||
|
|
||||||
public class Framebuffer : OpenGlObject
|
public class Framebuffer : OpenGlObject
|
||||||
{
|
{
|
||||||
@@ -55,10 +55,10 @@ public class Framebuffer : OpenGlObject
|
|||||||
{
|
{
|
||||||
switch (attachment.Value.Type)
|
switch (attachment.Value.Type)
|
||||||
{
|
{
|
||||||
case IFramebufferAttachment.AttachmentType._texture:
|
case IFramebufferAttachment.AttachmentType.Texture:
|
||||||
GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0);
|
GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0);
|
||||||
break;
|
break;
|
||||||
case IFramebufferAttachment.AttachmentType._renderbuffer:
|
case IFramebufferAttachment.AttachmentType.Renderbuffer:
|
||||||
GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer,
|
GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer,
|
||||||
attachment.Value.Handle);
|
attachment.Value.Handle);
|
||||||
break;
|
break;
|
||||||
@@ -102,10 +102,12 @@ public class Framebuffer : OpenGlObject
|
|||||||
Width = parWidth;
|
Width = parWidth;
|
||||||
Height = parHeight;
|
Height = parHeight;
|
||||||
|
|
||||||
foreach (KeyValuePair<FramebufferAttachment, IFramebufferAttachment> attachment in _attachments)
|
foreach (var attachment in _attachments)
|
||||||
{
|
{
|
||||||
attachment.Value.Resize(parWidth, parHeight);
|
attachment.Value.Resize(parWidth, parHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.Debug("Framebuffer {Handle} resized to {Width}x{Height}", Handle, parWidth, parHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Bind()
|
public override void Bind()
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Pixel;
|
||||||
using Engine.Renderer.Texture;
|
using Engine.Graphics.Texture;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Framebuffer;
|
namespace Engine.Graphics.Framebuffer;
|
||||||
|
|
||||||
public class FramebufferBuilder(int parWidth, int parHeight)
|
public class FramebufferBuilder(int parWidth, int parHeight)
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Engine.Renderer.Framebuffer;
|
namespace Engine.Graphics.Framebuffer;
|
||||||
|
|
||||||
public interface IFramebufferAttachment
|
public interface IFramebufferAttachment
|
||||||
{
|
{
|
||||||
@@ -9,7 +9,7 @@ public interface IFramebufferAttachment
|
|||||||
|
|
||||||
public enum AttachmentType
|
public enum AttachmentType
|
||||||
{
|
{
|
||||||
_texture,
|
Texture,
|
||||||
_renderbuffer
|
Renderbuffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Framebuffer;
|
namespace Engine.Graphics.Framebuffer;
|
||||||
|
|
||||||
public class Renderbuffer : OpenGlObject, IFramebufferAttachment
|
public class Renderbuffer : OpenGlObject, IFramebufferAttachment
|
||||||
{
|
{
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { 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;
|
private readonly RenderbufferStorage _format;
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using Engine.Renderer.Texture;
|
using Engine.Graphics.Texture;
|
||||||
using Engine.Scene;
|
using Engine.Scene;
|
||||||
using OpenTK.Windowing.Common;
|
using OpenTK.Windowing.Common;
|
||||||
|
|
||||||
namespace Engine.Renderer;
|
namespace Engine.Graphics;
|
||||||
|
|
||||||
public interface IPresenter : IUpdate
|
public interface IPresenter : IUpdate
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer;
|
namespace Engine.Graphics;
|
||||||
|
|
||||||
public abstract class OpenGlObject
|
public abstract class OpenGlObject
|
||||||
{
|
{
|
||||||
@@ -14,8 +14,9 @@ public abstract class OpenGlObject
|
|||||||
~OpenGlObject()
|
~OpenGlObject()
|
||||||
{
|
{
|
||||||
Destroy();
|
Destroy();
|
||||||
Handle = -1;
|
|
||||||
|
|
||||||
Log.Debug("OpenGL object {Handle} destroyed", Handle);
|
Log.Debug("OpenGL object {Handle} destroyed", Handle);
|
||||||
|
|
||||||
|
Handle = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Pixel;
|
namespace Engine.Graphics.Pixel;
|
||||||
|
|
||||||
public interface IPixel
|
public interface IPixel
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Pixel;
|
namespace Engine.Graphics.Pixel;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct R8 : IPixel
|
public struct R8 : IPixel
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Pixel;
|
namespace Engine.Graphics.Pixel;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Rgb8 : IPixel
|
public struct Rgb8 : IPixel
|
||||||
@@ -15,4 +15,6 @@ public struct Rgb8 : IPixel
|
|||||||
public byte R;
|
public byte R;
|
||||||
public byte G;
|
public byte G;
|
||||||
public byte B;
|
public byte B;
|
||||||
|
|
||||||
|
public override string ToString() => $"{R}, {G}, {B}";
|
||||||
}
|
}
|
||||||
79
Engine/src/Graphics/Render/InstancedRenderer.cs
Normal file
79
Engine/src/Graphics/Render/InstancedRenderer.cs
Normal file
@@ -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<C, I>
|
||||||
|
where C : struct, IVertex
|
||||||
|
where I : struct, IVertex
|
||||||
|
{
|
||||||
|
protected readonly Renderer _renderer;
|
||||||
|
|
||||||
|
protected readonly IndexBuffer _indexBuffer;
|
||||||
|
protected readonly VertexBuffer<C> _commonVertexBuffer;
|
||||||
|
protected readonly VertexBuffer<I> _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<C>(parInstanceBuffer);
|
||||||
|
_instanceVertexBuffer = new VertexBuffer<I>(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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs
Normal file
10
Engine/src/Graphics/Render/Mesh/MeshInstanceVertex.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
26
Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
Normal file
26
Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Engine.Graphics.Shader;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Graphics.Render.Mesh;
|
||||||
|
|
||||||
|
public class MeshRenderer : InstancedRenderer<Asset.Mesh.Mesh.Vertex, MeshInstanceVertex>
|
||||||
|
{
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Engine/src/Graphics/Render/Quad/QuadCommonVertex.cs
Normal file
11
Engine/src/Graphics/Render/Quad/QuadCommonVertex.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
12
Engine/src/Graphics/Render/Quad/QuadInstanceVertex.cs
Normal file
12
Engine/src/Graphics/Render/Quad/QuadInstanceVertex.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
59
Engine/src/Graphics/Render/Quad/QuadRenderer.cs
Normal file
59
Engine/src/Graphics/Render/Quad/QuadRenderer.cs
Normal file
@@ -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<QuadCommonVertex, QuadInstanceVertex>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,35 @@
|
|||||||
using System.Runtime.InteropServices;
|
using Engine.Graphics.Pixel;
|
||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Render.Quad;
|
||||||
using Engine.Renderer.Shader;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Serilog;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace Engine.Renderer;
|
namespace Engine.Graphics;
|
||||||
|
|
||||||
public class Renderer
|
public class Renderer
|
||||||
{
|
{
|
||||||
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
|
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
|
||||||
|
|
||||||
|
public readonly QuadRenderer QuadRenderer;
|
||||||
|
|
||||||
private readonly Framebuffer.Framebuffer _framebuffer;
|
private readonly Framebuffer.Framebuffer _framebuffer;
|
||||||
private readonly Queue<Action<Renderer>> _renderActions = new();
|
private readonly Queue<Action<Renderer>> _renderActions = new();
|
||||||
|
|
||||||
|
private readonly Thread _renderThread;
|
||||||
|
|
||||||
public Renderer(int parWidth, int parHeight)
|
public Renderer(int parWidth, int parHeight)
|
||||||
{
|
{
|
||||||
InitializeOpenGl();
|
InitializeOpenGl(parWidth, parHeight);
|
||||||
|
|
||||||
|
_renderThread = Thread.CurrentThread;
|
||||||
|
|
||||||
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
|
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
|
||||||
.AddColorAttachment<Rgb8>()
|
.AddColorAttachment<Rgb8>()
|
||||||
.AddDepthAttachment()
|
.AddDepthAttachment()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
QuadRenderer = new QuadRenderer(this, 1024 * 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeOpenGl()
|
private void InitializeOpenGl(int parWidth, int parHeight)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Debug.Setup();
|
Debug.Setup();
|
||||||
@@ -36,6 +41,18 @@ public class Renderer
|
|||||||
|
|
||||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||||
GL.Enable(EnableCap.Blend);
|
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<Renderer> parRenderAction)
|
internal void Commit(Action<Renderer> parRenderAction)
|
||||||
@@ -45,8 +62,10 @@ public class Renderer
|
|||||||
|
|
||||||
internal void Render()
|
internal void Render()
|
||||||
{
|
{
|
||||||
|
EnsureRenderThread();
|
||||||
|
|
||||||
_framebuffer.Bind();
|
_framebuffer.Bind();
|
||||||
while (_renderActions.TryDequeue(out Action<Renderer>? renderAction))
|
while (_renderActions.TryDequeue(out var renderAction))
|
||||||
{
|
{
|
||||||
renderAction(this);
|
renderAction(this);
|
||||||
}
|
}
|
||||||
@@ -57,5 +76,6 @@ public class Renderer
|
|||||||
internal void Resize(int parWidth, int parHeight)
|
internal void Resize(int parWidth, int parHeight)
|
||||||
{
|
{
|
||||||
_framebuffer.Resize(parWidth, parHeight);
|
_framebuffer.Resize(parWidth, parHeight);
|
||||||
|
GL.Viewport(0, 0, parWidth, parHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,48 +1,14 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using System.Text;
|
|
||||||
using OpenTK.Graphics.OpenGL;
|
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer.Shader;
|
namespace Engine.Graphics.Shader;
|
||||||
|
|
||||||
public class ShaderProgram : OpenGlObject
|
public class Program : OpenGlObject
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, int> _uniforms = new();
|
private readonly Dictionary<string, int> _uniforms = new();
|
||||||
|
|
||||||
public static ShaderProgram CreateFromSource(string parSource)
|
public Program(string parVertexSource, string parFragmentSource)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
|
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
|
||||||
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
|
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
|
||||||
@@ -50,7 +16,7 @@ public class ShaderProgram : OpenGlObject
|
|||||||
Handle = LinkProgram(vertexShader, fragmentShader);
|
Handle = LinkProgram(vertexShader, fragmentShader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetUniform<T>(string parName, T parValue)
|
public void SetUniform<T>(string parName, in T parValue)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -79,6 +45,12 @@ public class ShaderProgram : OpenGlObject
|
|||||||
case Matrix4 matrix:
|
case Matrix4 matrix:
|
||||||
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
|
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
|
||||||
break;
|
break;
|
||||||
|
case int[] intArray:
|
||||||
|
GL.ProgramUniform1(Handle, location, intArray.Length, intArray);
|
||||||
|
break;
|
||||||
|
case float[] floatArray:
|
||||||
|
GL.ProgramUniform1(Handle, location, floatArray.Length, floatArray);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
|
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
|
||||||
}
|
}
|
||||||
38
Engine/src/Graphics/Shader/ProgramLoader.cs
Normal file
38
Engine/src/Graphics/Shader/ProgramLoader.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Engine.Graphics.Shader;
|
||||||
|
|
||||||
|
public static partial class ProgramLoader
|
||||||
|
{
|
||||||
|
[GeneratedRegex(@"^//\s+#type\s+(?<type>[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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
using Engine.Renderer.Framebuffer;
|
using Engine.Graphics.Framebuffer;
|
||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Pixel;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer.Texture;
|
namespace Engine.Graphics.Texture;
|
||||||
|
|
||||||
public class DynamicTexture : Texture, IFramebufferAttachment
|
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 PixelFormat _format;
|
||||||
private readonly PixelType _type;
|
private readonly PixelType _type;
|
||||||
@@ -45,5 +46,7 @@ public class DynamicTexture : Texture, IFramebufferAttachment
|
|||||||
Bind();
|
Bind();
|
||||||
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
|
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
|
||||||
IntPtr.Zero);
|
IntPtr.Zero);
|
||||||
|
|
||||||
|
Log.Debug("Texture {Handle} resized to {Width}x{Height}", Handle, Width, Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using Engine.Asset;
|
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 interface IConstTexture
|
||||||
{
|
{
|
||||||
|
public Vector2i Size { get; }
|
||||||
public int Width { get; }
|
public int Width { get; }
|
||||||
public int Height { get; }
|
public int Height { get; }
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Engine.Asset;
|
using Engine.Asset;
|
||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Pixel;
|
||||||
|
|
||||||
namespace Engine.Renderer.Texture;
|
namespace Engine.Graphics.Texture;
|
||||||
|
|
||||||
public interface ITexture : IConstTexture
|
public interface ITexture : IConstTexture
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Pixel;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
namespace Engine.Renderer.Texture;
|
namespace Engine.Graphics.Texture;
|
||||||
|
|
||||||
public class StaticTexture : Texture
|
public class StaticTexture : Texture
|
||||||
{
|
{
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Engine.Renderer.Pixel;
|
using Engine.Graphics.Pixel;
|
||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Engine.Renderer.Texture;
|
namespace Engine.Graphics.Texture;
|
||||||
|
|
||||||
public abstract class Texture : OpenGlObject, ITexture
|
public abstract class Texture : OpenGlObject, ITexture
|
||||||
{
|
{
|
||||||
|
public Vector2i Size => new(Width, Height);
|
||||||
|
|
||||||
public int Width
|
public int Width
|
||||||
{
|
{
|
||||||
get => _width;
|
get => _width;
|
||||||
@@ -44,6 +48,13 @@ public abstract class Texture : OpenGlObject, ITexture
|
|||||||
|
|
||||||
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
|
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
|
||||||
Handle = 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<T>(int parX, int parY, int parWidth, int parHeight, T[,] parPixels)
|
public void UploadPixels<T>(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 format = default(T).Format;
|
||||||
var type = default(T).Type;
|
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,
|
GL.GetTextureSubImage(Handle, 0, parX, parY, 0, parWidth, parHeight, 1, format, type,
|
||||||
parPixels.Length * Marshal.SizeOf<T>(),
|
parPixels.Length * Marshal.SizeOf<T>(),
|
||||||
parPixels);
|
parPixels);
|
||||||
@@ -108,6 +123,7 @@ public abstract class Texture : OpenGlObject, ITexture
|
|||||||
|
|
||||||
public void BindUnit(int parUnit = 0)
|
public void BindUnit(int parUnit = 0)
|
||||||
{
|
{
|
||||||
|
GL.ActiveTexture(TextureUnit.Texture0 + parUnit);
|
||||||
GL.BindTextureUnit(parUnit, Handle);
|
GL.BindTextureUnit(parUnit, Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
50
Engine/src/Graphics/Texture/TextureUnitMap.cs
Normal file
50
Engine/src/Graphics/Texture/TextureUnitMap.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Engine.Graphics.Texture;
|
||||||
|
|
||||||
|
public class TextureUnitMap : IEnumerable<KeyValuePair<Texture, int>>
|
||||||
|
{
|
||||||
|
public int Size => _textures.Count;
|
||||||
|
public int Capacity => _capacity;
|
||||||
|
|
||||||
|
private readonly Dictionary<Texture, int> _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<KeyValuePair<Texture, int>> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _textures.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
public enum MouseButton
|
public enum MouseButton
|
||||||
{
|
{
|
||||||
_left,
|
Left,
|
||||||
_right,
|
Right,
|
||||||
_middle,
|
Middle,
|
||||||
|
|
||||||
_button4,
|
Button4,
|
||||||
_button5,
|
Button5,
|
||||||
_button6,
|
Button6,
|
||||||
_button7
|
Button7
|
||||||
}
|
}
|
||||||
@@ -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<T> : OpenGlObject
|
|
||||||
where T : struct, IVertex
|
|
||||||
{
|
|
||||||
internal int Count { get; }
|
|
||||||
|
|
||||||
private readonly int _stride = Marshal.SizeOf<T>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
101
Engine/src/Resource/ShaderResource.Designer.cs
generated
Normal file
101
Engine/src/Resource/ShaderResource.Designer.cs
generated
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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]";.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Mesh {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Mesh", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string Quad {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Quad", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Engine/src/Resource/ShaderResource.resx
Normal file
29
Engine/src/Resource/ShaderResource.resx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>1.3</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="Mesh" type="System.Resources.ResXFileRef" xml:space="preserve">
|
||||||
|
<value>../../assets/shader/mesh.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<data name="Quad" type="System.Resources.ResXFileRef" xml:space="preserve">
|
||||||
|
<value>../../assets/shader/quad.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Engine.Renderer.Camera;
|
using Engine.Graphics.Camera;
|
||||||
using OpenTK.Mathematics;
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
namespace Engine.Scene.Component.BuiltIn;
|
namespace Engine.Scene.Component.BuiltIn;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Engine.Renderer.Camera;
|
using Engine.Graphics.Camera;
|
||||||
using Engine.Scene.Component.BuiltIn;
|
using Engine.Scene.Component.BuiltIn;
|
||||||
|
|
||||||
namespace Engine.Scene;
|
namespace Engine.Scene;
|
||||||
|
|||||||
@@ -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;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
|
||||||
namespace Engine;
|
namespace Engine;
|
||||||
|
|
||||||
public class Window
|
public class Window : IPresenter
|
||||||
{
|
{
|
||||||
public bool IsExiting => _window.IsExiting;
|
public bool IsExiting => _window.IsExiting;
|
||||||
public int Width { get; private set; }
|
public int Width { get; private set; }
|
||||||
public int Height { get; private set; }
|
public int Height { get; private set; }
|
||||||
|
public event Action<ResizeEventArgs>? Resize;
|
||||||
|
|
||||||
private readonly Engine _engine;
|
private readonly Engine _engine;
|
||||||
private readonly NativeWindow _window;
|
private readonly NativeWindow _window;
|
||||||
@@ -26,16 +34,32 @@ public class Window
|
|||||||
{
|
{
|
||||||
Width = parArgs.Width;
|
Width = parArgs.Width;
|
||||||
Height = parArgs.Height;
|
Height = parArgs.Height;
|
||||||
|
Resize?.Invoke(parArgs);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update(double parDeltaTime)
|
||||||
{
|
{
|
||||||
if (!_headless)
|
if (_headless)
|
||||||
{
|
{
|
||||||
NativeWindow.ProcessWindowEvents(false);
|
return;
|
||||||
_window.SwapBuffers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,30 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>DoomDeathmatchConsole</RootNamespace>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DoomDeathmatch\DoomDeathmatch.csproj" />
|
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Sylvan.BuildTools.Resources" Version="0.6.2">
|
<Compile Update="src\Resource\ShaderResource.Designer.cs">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<DesignTime>True</DesignTime>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<AutoGen>True</AutoGen>
|
||||||
</PackageReference>
|
<DependentUpon>ShaderResource.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="src\Resource\ShaderResource.Designer.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>ShaderResource.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Update="src\Resource\ShaderResource.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>ShaderResource.Designer.cs</LastGenOutput>
|
||||||
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup><StaticResourceFolder Include="assets"/></ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
84
PresenterConsole/assets/shader/ascii.shader
Normal file
84
PresenterConsole/assets/shader/ascii.shader
Normal file
@@ -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);
|
||||||
|
}
|
||||||
21
PresenterConsole/src/AsciiPixel.cs
Normal file
21
PresenterConsole/src/AsciiPixel.cs
Normal file
@@ -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}";
|
||||||
|
}
|
||||||
11
PresenterConsole/src/AsciiVertex.cs
Normal file
11
PresenterConsole/src/AsciiVertex.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
158
PresenterConsole/src/ConsoleFastOutput.cs
Normal file
158
PresenterConsole/src/ConsoleFastOutput.cs
Normal file
@@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,126 +4,126 @@ namespace PresenterConsole;
|
|||||||
|
|
||||||
public class ConsoleInputHandler : IInputHandler
|
public class ConsoleInputHandler : IInputHandler
|
||||||
{
|
{
|
||||||
private readonly HashSet<ConsoleKey> _currentKeys = [];
|
private readonly HashSet<ConsoleKey> _currentKeys = [];
|
||||||
private readonly HashSet<ConsoleKey> _previousKeys = [];
|
private readonly HashSet<ConsoleKey> _previousKeys = [];
|
||||||
private ConsoleModifiers _currentModifiers;
|
private ConsoleModifiers _currentModifiers;
|
||||||
private ConsoleModifiers _previousModifiers;
|
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.Add(key);
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsKeyPressed(KeyCode keyCode)
|
_previousModifiers = _currentModifiers;
|
||||||
{
|
|
||||||
if (IsModifierKey(keyCode))
|
|
||||||
return IsModifierActive(keyCode);
|
|
||||||
|
|
||||||
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)
|
public bool IsKeyPressed(KeyCode keyCode)
|
||||||
{
|
{
|
||||||
if (IsModifierKey(keyCode))
|
if (IsModifierKey(keyCode))
|
||||||
return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
|
return IsModifierActive(keyCode);
|
||||||
|
|
||||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
return _currentKeys.Contains(ConvertToConsoleKey(keyCode));
|
||||||
return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsKeyRepeat(KeyCode keyCode)
|
public bool IsKeyJustPressed(KeyCode keyCode)
|
||||||
{
|
{
|
||||||
if (IsModifierKey(keyCode))
|
if (IsModifierKey(keyCode))
|
||||||
return IsModifierActive(keyCode) && WasModifierActive(keyCode);
|
return IsModifierActive(keyCode) && !WasModifierActive(keyCode);
|
||||||
|
|
||||||
var consoleKey = ConvertToConsoleKey(keyCode);
|
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||||
return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
|
return _currentKeys.Contains(consoleKey) && !_previousKeys.Contains(consoleKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsMouseButtonPressed(MouseButton button)
|
public bool IsKeyRepeat(KeyCode keyCode)
|
||||||
{
|
{
|
||||||
return false;
|
if (IsModifierKey(keyCode))
|
||||||
}
|
return IsModifierActive(keyCode) && WasModifierActive(keyCode);
|
||||||
|
|
||||||
public bool IsMouseButtonJustPressed(MouseButton button)
|
var consoleKey = ConvertToConsoleKey(keyCode);
|
||||||
{
|
return _currentKeys.Contains(consoleKey) && _previousKeys.Contains(consoleKey);
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
public bool IsMouseButtonJustPressed(MouseButton button)
|
||||||
{
|
{
|
||||||
KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
|
return false;
|
||||||
KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
|
}
|
||||||
KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
|
|
||||||
_ => false
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool WasModifierActive(KeyCode keyCode) => keyCode switch
|
private static bool IsModifierKey(KeyCode keyCode) => keyCode is KeyCode.Ctrl or KeyCode.Backspace or KeyCode.Shift;
|
||||||
{
|
|
||||||
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
|
private bool IsModifierActive(KeyCode keyCode) => keyCode switch
|
||||||
{
|
{
|
||||||
KeyCode.A => ConsoleKey.A,
|
KeyCode.Ctrl => (_currentModifiers & ConsoleModifiers.Control) != 0,
|
||||||
KeyCode.B => ConsoleKey.B,
|
KeyCode.Alt => (_currentModifiers & ConsoleModifiers.Alt) != 0,
|
||||||
KeyCode.C => ConsoleKey.C,
|
KeyCode.Shift => (_currentModifiers & ConsoleModifiers.Shift) != 0,
|
||||||
KeyCode.D => ConsoleKey.D,
|
_ => false
|
||||||
KeyCode.E => ConsoleKey.E,
|
};
|
||||||
KeyCode.F => ConsoleKey.F,
|
|
||||||
KeyCode.G => ConsoleKey.G,
|
private bool WasModifierActive(KeyCode keyCode) => keyCode switch
|
||||||
KeyCode.H => ConsoleKey.H,
|
{
|
||||||
KeyCode.I => ConsoleKey.I,
|
KeyCode.Ctrl => (_previousModifiers & ConsoleModifiers.Control) != 0,
|
||||||
KeyCode.J => ConsoleKey.J,
|
KeyCode.Alt => (_previousModifiers & ConsoleModifiers.Alt) != 0,
|
||||||
KeyCode.K => ConsoleKey.K,
|
KeyCode.Shift => (_previousModifiers & ConsoleModifiers.Shift) != 0,
|
||||||
KeyCode.L => ConsoleKey.L,
|
_ => false
|
||||||
KeyCode.M => ConsoleKey.M,
|
};
|
||||||
KeyCode.N => ConsoleKey.N,
|
|
||||||
KeyCode.O => ConsoleKey.O,
|
private static ConsoleKey ConvertToConsoleKey(KeyCode keyCode) => keyCode switch
|
||||||
KeyCode.P => ConsoleKey.P,
|
{
|
||||||
KeyCode.Q => ConsoleKey.Q,
|
KeyCode.A => ConsoleKey.A,
|
||||||
KeyCode.R => ConsoleKey.R,
|
KeyCode.B => ConsoleKey.B,
|
||||||
KeyCode.S => ConsoleKey.S,
|
KeyCode.C => ConsoleKey.C,
|
||||||
KeyCode.T => ConsoleKey.T,
|
KeyCode.D => ConsoleKey.D,
|
||||||
KeyCode.U => ConsoleKey.U,
|
KeyCode.E => ConsoleKey.E,
|
||||||
KeyCode.V => ConsoleKey.V,
|
KeyCode.F => ConsoleKey.F,
|
||||||
KeyCode.W => ConsoleKey.W,
|
KeyCode.G => ConsoleKey.G,
|
||||||
KeyCode.X => ConsoleKey.X,
|
KeyCode.H => ConsoleKey.H,
|
||||||
KeyCode.Y => ConsoleKey.Y,
|
KeyCode.I => ConsoleKey.I,
|
||||||
KeyCode.Z => ConsoleKey.Z,
|
KeyCode.J => ConsoleKey.J,
|
||||||
KeyCode.Enter => ConsoleKey.Enter,
|
KeyCode.K => ConsoleKey.K,
|
||||||
KeyCode.Escape => ConsoleKey.Escape,
|
KeyCode.L => ConsoleKey.L,
|
||||||
KeyCode.Space => ConsoleKey.Spacebar,
|
KeyCode.M => ConsoleKey.M,
|
||||||
KeyCode.Tab => ConsoleKey.Tab,
|
KeyCode.N => ConsoleKey.N,
|
||||||
KeyCode.Backspace => ConsoleKey.Backspace,
|
KeyCode.O => ConsoleKey.O,
|
||||||
KeyCode.Up => ConsoleKey.UpArrow,
|
KeyCode.P => ConsoleKey.P,
|
||||||
KeyCode.Down => ConsoleKey.DownArrow,
|
KeyCode.Q => ConsoleKey.Q,
|
||||||
KeyCode.Left => ConsoleKey.LeftArrow,
|
KeyCode.R => ConsoleKey.R,
|
||||||
KeyCode.Right => ConsoleKey.RightArrow,
|
KeyCode.S => ConsoleKey.S,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(keyCode), $"No mapping defined for {keyCode}")
|
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}")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,117 @@
|
|||||||
using Engine.Asset;
|
using Engine.Asset;
|
||||||
using Engine.Renderer;
|
using Engine.Graphics;
|
||||||
using Engine.Renderer.Framebuffer;
|
using Engine.Graphics.Buffer;
|
||||||
using Engine.Renderer.Texture;
|
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;
|
using OpenTK.Windowing.Common;
|
||||||
|
|
||||||
namespace PresenterConsole;
|
namespace PresenterConsole;
|
||||||
|
|
||||||
public class ConsolePresenter : IPresenter
|
public class ConsolePresenter : IPresenter
|
||||||
{
|
{
|
||||||
public bool IsExiting => false;
|
public bool IsExiting => false;
|
||||||
public int Width => Console.WindowWidth;
|
public int Width => Console.WindowWidth;
|
||||||
public int Height => Console.WindowHeight;
|
public int Height => Console.WindowHeight;
|
||||||
public event Action<ResizeEventArgs>? Resize;
|
public event Action<ResizeEventArgs>? Resize;
|
||||||
|
|
||||||
private readonly Framebuffer _framebuffer;
|
private int _cachedWidth;
|
||||||
private Image<AsciiPixel> _asciiImage;
|
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<AsciiPixel>? _asciiImage;
|
||||||
|
|
||||||
|
private readonly IndexBuffer _indexBuffer;
|
||||||
|
private readonly VertexBuffer<AsciiVertex> _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<AsciiPixel>()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_indexBuffer = new IndexBuffer([0, 1, 2, 2, 3, 0], BufferStorageFlags.MapReadBit);
|
||||||
|
_vertexBuffer = new VertexBuffer<AsciiVertex>([
|
||||||
|
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<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
|
||||||
|
|
||||||
|
asciiTexture.ReadPixels(_asciiImage);
|
||||||
|
Output(_asciiImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Output(Image<AsciiPixel> parImage)
|
||||||
|
{
|
||||||
|
var lightmap = " .-:=+*#%@".Reverse().ToArray();
|
||||||
|
for (var y = 0; y < parImage.Height; y++)
|
||||||
{
|
{
|
||||||
_framebuffer = Framebuffer.Builder(width, height)
|
for (var x = 0; x < parImage.Width; x++)
|
||||||
.AddColorAttachment<AsciiPixel>()
|
{
|
||||||
.Build();
|
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();
|
_framebuffer.Resize(Width, Height);
|
||||||
openglTexture.BindUnit();
|
_engine.Renderer.Resize(Width, Height);
|
||||||
|
_consoleOutput.Resize(Width, Height);
|
||||||
// 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<AsciiPixel>(asciiTexture.Width, asciiTexture.Height);
|
|
||||||
|
|
||||||
asciiTexture.ReadPixels(_asciiImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(double deltaTime)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
12
PresenterConsole/src/Program.cs
Normal file
12
PresenterConsole/src/Program.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
95
PresenterConsole/src/Resource/ShaderResource.Designer.cs
generated
Normal file
95
PresenterConsole/src/Resource/ShaderResource.Designer.cs
generated
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace PresenterConsole.Resource {
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
|
/// </summary>
|
||||||
|
// 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
|
/// </summary>
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
|
/// resource lookups using this strongly typed resource class.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
|
get {
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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]";.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Ascii {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Ascii", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
PresenterConsole/src/Resource/ShaderResource.resx
Normal file
30
PresenterConsole/src/Resource/ShaderResource.resx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>1.3</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||||
|
PublicKeyToken=b77a5c561934e089
|
||||||
|
</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||||
|
PublicKeyToken=b77a5c561934e089
|
||||||
|
</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="Ascii" type="System.Resources.ResXFileRef" xml:space="preserve">
|
||||||
|
<value>../../assets/shader/ascii.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<UseWPF>true</UseWPF>
|
|
||||||
<RootNamespace>DoomDeathmatchWPF</RootNamespace>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\DoomDeathmatch\DoomDeathmatch.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ApplicationDefinition Include="src\App.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
<XamlRuntime>Wpf</XamlRuntime>
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
</ApplicationDefinition>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="src\MainWindow.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
<XamlRuntime>Wpf</XamlRuntime>
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
31
PresenterWpf/PresenterWpf.csproj
Normal file
31
PresenterWpf/PresenterWpf.csproj
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Engine\Engine.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ApplicationDefinition Include="src\App.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<XamlRuntime>Wpf</XamlRuntime>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</ApplicationDefinition>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="src\MainWindow.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<XamlRuntime>Wpf</XamlRuntime>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
5
PresenterWpf/src/App.xaml
Normal file
5
PresenterWpf/src/App.xaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<Application x:Class="PresenterWpf.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:PresenterWpf">
|
||||||
|
</Application>
|
||||||
83
PresenterWpf/src/App.xaml.cs
Normal file
83
PresenterWpf/src/App.xaml.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
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<ResizeEventArgs>? 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
PresenterWpf/src/AssemblyInfo.cs
Normal file
10
PresenterWpf/src/AssemblyInfo.cs
Normal file
@@ -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)
|
||||||
|
)]
|
||||||
13
PresenterWpf/src/MainWindow.xaml
Normal file
13
PresenterWpf/src/MainWindow.xaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Window x:Class="PresenterWpf.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:PresenterWpf"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="MainWindow" Height="450" Width="800" Closing="MainWindow_OnClosing">
|
||||||
|
<Grid>
|
||||||
|
<Image x:Name="Image" Width="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ActualWidth}"
|
||||||
|
Height="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=ActualHeight}" />
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
101
PresenterWpf/src/MainWindow.xaml.cs
Normal file
101
PresenterWpf/src/MainWindow.xaml.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window, IPresenter
|
||||||
|
{
|
||||||
|
public bool IsExiting { get; set; }
|
||||||
|
public int Width => (int)_cachedWidth;
|
||||||
|
public int Height => (int)_cachedHeight;
|
||||||
|
public event Action<ResizeEventArgs>? Resize;
|
||||||
|
|
||||||
|
|
||||||
|
private Engine.Engine _engine;
|
||||||
|
private Image<Rgb8>? _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<Rgb8>(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<Rgb8> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
PresenterWpf/src/Program.cs
Normal file
5
PresenterWpf/src/Program.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace PresenterWpf;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user