This commit is contained in:
2024-12-13 15:15:23 +03:00
parent 44bb459c1b
commit fa7b12c88c
66 changed files with 1732 additions and 389 deletions

View File

@@ -11,12 +11,30 @@
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="EngineTests" />
<InternalsVisibleTo Include="EngineTests"/>
<InternalsVisibleTo Include="PresenterConsole"/>
<InternalsVisibleTo Include="PresenterWpf"/>
<PackageReference Include="OpenTK" Version="4.8.2"/>
<PackageReference Include="Serilog" Version="4.1.0"/>
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
</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>

View 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;
}

View 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;
}

View File

@@ -1,18 +1,18 @@
using Engine.Renderer.Pixel;
using Engine.Renderer.Texture;
using Engine.Graphics.Pixel;
using Engine.Graphics.Texture;
namespace Engine.Asset;
public class Image<T>(T[,] parPixels)
where T : struct, IPixel
{
public int Width { get; } = parPixels.GetLength(0);
public int Height { get; } = parPixels.GetLength(1);
public int Width { get; } = parPixels.GetLength(1);
public int Height { get; } = parPixels.GetLength(0);
public T[,] Pixels { get; } = parPixels;
public T this[int parX, int parY] => Pixels[parX, parY];
public T this[int parY, int parX] => Pixels[parY, parX];
public Image(int parWidth, int parHeight) : this(new T[parWidth, parHeight])
public Image(int parWidth, int parHeight) : this(new T[parHeight, parWidth])
{
}

View File

@@ -1,4 +1,4 @@
using Engine.Renderer.Buffer.Vertex;
using Engine.Graphics.Buffer.Vertex;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
@@ -15,6 +15,11 @@ public class Mesh
private readonly List<Vertex> _vertices = [];
private readonly List<uint> _indices = [];
public override int GetHashCode()
{
return HashCode.Combine(Indices, Vertices);
}
public record struct Vertex : IVertex
{
[Vertex(VertexAttribType.Float, 3)] public Vector3 _position;

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using Engine.Graphics;
using Engine.Input;
using Engine.Scene;
using OpenTK.Graphics.OpenGL;
@@ -12,14 +13,15 @@ namespace Engine;
public sealed class Engine
{
public Renderer.Renderer Renderer => _renderer;
public Renderer Renderer => _renderer;
public SceneManager SceneManager => _sceneManager;
private readonly Window _window;
private readonly Renderer.Renderer _renderer;
private readonly Renderer _renderer;
private readonly SceneManager _sceneManager = new();
private readonly ILogger _logger;
private readonly IInputHandler _inputHandler;
private readonly IPresenter _presenter;
private Thread? _updateThread;
@@ -30,12 +32,20 @@ public sealed class Engine
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
Title = parTitle,
StartVisible = !parHeadless,
APIVersion = new Version(4, 5),
APIVersion = new Version(4, 6),
Profile = ContextProfile.Compatability
};
_window = new Window(this, new NativeWindow(settings), parHeadless);
_renderer = new Renderer.Renderer(parWidth, parHeight);
_renderer = new Renderer(parWidth, parHeight);
if (!parHeadless)
{
_window.Resize += parArgs =>
{
_renderer.Resize(parArgs.Width, parArgs.Height);
};
_presenter = _window;
}
Thread.CurrentThread.Name = "RendererThread";
@@ -53,18 +63,36 @@ public sealed class Engine
_logger = Log.ForContext<Engine>();
}
public Engine(int parWidth, int parHeight, Func<Engine, IPresenter> parPresenter) : this(parWidth, parHeight, true)
{
_presenter = parPresenter(this);
}
public void Run()
{
_updateThread = new Thread(RunUpdate);
_updateThread.Start();
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
GL.Viewport(0, 0, _window.Width, _window.Height);
while (!_window.IsExiting)
var timer = Stopwatch.StartNew();
var deltaTime = 0.0;
while (!_presenter.IsExiting)
{
_window.Update();
var time = deltaTime;
_renderer.Commit(_ => GL.ClearColor(0.6f, 0.0f, 0.6f, 1.0f));
_renderer.Commit(_ => GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit));
_renderer.Commit(parRenderer =>
{
parRenderer.QuadRenderer.Commit(
Matrix4.CreateScale(MathF.Sin((float)time) * 2) * Matrix4.CreateRotationZ(MathF.Sin((float)time)),
new Vector4((MathF.Sin((float)time * 3) + 1.0f) / 2, (MathF.Sin((float)time) + 1.0f) / 2, (MathF.Sin((float)time * 5) + 1.0f) / 2, 1.0f));
parRenderer.QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity);
parRenderer.QuadRenderer.Reset();
});
_renderer.Render();
_presenter.Present(_renderer.TextureInternal);
_presenter.Update(deltaTime);
deltaTime = timer.Elapsed.TotalSeconds;
}
_updateThread.Join();
@@ -73,11 +101,12 @@ public sealed class Engine
private void RunUpdate()
{
var timer = Stopwatch.StartNew();
while (!_window.IsExiting)
var deltaTime = 0.0;
while (!_presenter.IsExiting)
{
_window.Update();
_sceneManager.Update(timer.Elapsed.TotalSeconds);
_sceneManager.Update(deltaTime);
timer.Restart();
deltaTime = timer.Elapsed.TotalSeconds;
}
}
}

View File

@@ -1,13 +1,13 @@
using OpenTK.Graphics.OpenGL;
using Serilog;
namespace Engine.Renderer.Buffer;
namespace Engine.Graphics.Buffer;
public class IndexBuffer : OpenGlObject
{
internal int Count { get; }
public IndexBuffer(int parCount, BufferStorageFlags parFlags)
public IndexBuffer(int parCount, BufferStorageFlags parFlags = BufferStorageFlags.None)
{
Count = parCount;
@@ -19,6 +19,19 @@ public class IndexBuffer : OpenGlObject
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
}
public IndexBuffer(uint[] parData, BufferStorageFlags parFlags = BufferStorageFlags.None)
{
Count = parData.Length;
GL.CreateBuffers(1, out int handle);
Handle = handle;
GL.NamedBufferStorage(Handle, Count * sizeof(uint), parData, parFlags);
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
}
public void UploadData(uint[] parData)
{
UploadData(0, parData);

View File

@@ -2,7 +2,7 @@
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Buffer.Vertex;
namespace Engine.Graphics.Buffer.Vertex;
public interface IVertex
{
@@ -23,7 +23,7 @@ public interface IVertex
return false;
}
IOrderedEnumerable<FieldInfo>? fields = GetFields(parType);
var fields = GetFields(parType);
var totalSize = 0;
foreach (var field in fields)
{

View File

@@ -1,6 +1,6 @@
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Buffer.Vertex;
namespace Engine.Graphics.Buffer.Vertex;
[AttributeUsage(AttributeTargets.Field)]
public class VertexAttribute : Attribute
@@ -10,8 +10,9 @@ public class VertexAttribute : Attribute
public bool Normalized { get; }
public int RepeatCount { get; }
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, bool parNormalized = false,
int parRepeatCount = 1)
public VertexAttribute(VertexAttribType parType, int parComponentCount = 1, int parRepeatCount = 1,
bool parNormalized = false
)
{
if (parComponentCount <= 0)
{

View File

@@ -1,15 +1,16 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Engine.Renderer.Buffer.Vertex;
using Engine.Graphics.Buffer.Vertex;
using OpenTK.Graphics.OpenGL;
using Serilog;
namespace Engine.Renderer.Buffer;
namespace Engine.Graphics.Buffer;
public class VertexArray : OpenGlObject
{
// private IndexBuffer? _boundIndexBuffer;
// private readonly Dictionary<int, VertexBuffer<IVertex>> _boundVertexBuffers = new();
private int _enabledAttribs = 0;
public VertexArray()
{
@@ -42,15 +43,14 @@ public class VertexArray : OpenGlObject
GL.VertexArrayVertexBuffer(Handle, parBindingIndex, parBuffer.Handle, 0, stride);
var location = 0;
foreach (var field in fields)
{
var attribute = field.GetCustomAttribute<VertexAttribute>()!;
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);

View 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);
}
}

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace Engine.Renderer.Camera;
namespace Engine.Graphics.Camera;
public interface ICamera
{

View File

@@ -1,6 +1,6 @@
using OpenTK.Mathematics;
namespace Engine.Renderer.Camera;
namespace Engine.Graphics.Camera;
public class ScreenspaceCamera : ICamera
{

View File

@@ -3,7 +3,7 @@ using OpenTK.Graphics.OpenGL;
using Serilog;
using Serilog.Events;
namespace Engine.Renderer;
namespace Engine.Graphics;
internal static class Debug
{

View File

@@ -1,9 +1,9 @@
using System.Collections.ObjectModel;
using Engine.Renderer.Pixel;
using Engine.Renderer.Texture;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
using Serilog;
namespace Engine.Renderer.Framebuffer;
namespace Engine.Graphics.Framebuffer;
public class Framebuffer : OpenGlObject
{
@@ -55,10 +55,10 @@ public class Framebuffer : OpenGlObject
{
switch (attachment.Value.Type)
{
case IFramebufferAttachment.AttachmentType._texture:
case IFramebufferAttachment.AttachmentType.Texture:
GL.NamedFramebufferTexture(Handle, attachment.Key, attachment.Value.Handle, 0);
break;
case IFramebufferAttachment.AttachmentType._renderbuffer:
case IFramebufferAttachment.AttachmentType.Renderbuffer:
GL.NamedFramebufferRenderbuffer(Handle, attachment.Key, RenderbufferTarget.Renderbuffer,
attachment.Value.Handle);
break;
@@ -102,10 +102,12 @@ public class Framebuffer : OpenGlObject
Width = parWidth;
Height = parHeight;
foreach (KeyValuePair<FramebufferAttachment, IFramebufferAttachment> attachment in _attachments)
foreach (var attachment in _attachments)
{
attachment.Value.Resize(parWidth, parHeight);
}
Log.Debug("Framebuffer {Handle} resized to {Width}x{Height}", Handle, parWidth, parHeight);
}
public override void Bind()

View File

@@ -1,8 +1,8 @@
using Engine.Renderer.Pixel;
using Engine.Renderer.Texture;
using Engine.Graphics.Pixel;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Framebuffer;
namespace Engine.Graphics.Framebuffer;
public class FramebufferBuilder(int parWidth, int parHeight)
{

View File

@@ -1,4 +1,4 @@
namespace Engine.Renderer.Framebuffer;
namespace Engine.Graphics.Framebuffer;
public interface IFramebufferAttachment
{
@@ -9,7 +9,7 @@ public interface IFramebufferAttachment
public enum AttachmentType
{
_texture,
_renderbuffer
Texture,
Renderbuffer
}
}

View File

@@ -1,13 +1,13 @@
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Framebuffer;
namespace Engine.Graphics.Framebuffer;
public class Renderbuffer : OpenGlObject, IFramebufferAttachment
{
public int Width { get; private set; }
public int Height { get; private set; }
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._renderbuffer;
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType.Renderbuffer;
private readonly RenderbufferStorage _format;

View File

@@ -1,8 +1,8 @@
using Engine.Renderer.Texture;
using Engine.Graphics.Texture;
using Engine.Scene;
using OpenTK.Windowing.Common;
namespace Engine.Renderer;
namespace Engine.Graphics;
public interface IPresenter : IUpdate
{

View File

@@ -1,6 +1,6 @@
using Serilog;
namespace Engine.Renderer;
namespace Engine.Graphics;
public abstract class OpenGlObject
{
@@ -14,8 +14,9 @@ public abstract class OpenGlObject
~OpenGlObject()
{
Destroy();
Handle = -1;
Log.Debug("OpenGL object {Handle} destroyed", Handle);
Handle = -1;
}
}

View File

@@ -1,6 +1,6 @@
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Pixel;
namespace Engine.Graphics.Pixel;
public interface IPixel
{

View File

@@ -1,7 +1,7 @@
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Pixel;
namespace Engine.Graphics.Pixel;
[StructLayout(LayoutKind.Sequential)]
public struct R8 : IPixel

View File

@@ -1,7 +1,7 @@
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Pixel;
namespace Engine.Graphics.Pixel;
[StructLayout(LayoutKind.Sequential)]
public struct Rgb8 : IPixel
@@ -15,4 +15,6 @@ public struct Rgb8 : IPixel
public byte R;
public byte G;
public byte B;
public override string ToString() => $"{R}, {G}, {B}";
}

View 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)
{
}
}

View 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;
}

View 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++;
}
}

View 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;
}

View 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;
}

View 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();
}
}

View File

@@ -1,30 +1,35 @@
using System.Runtime.InteropServices;
using Engine.Renderer.Pixel;
using Engine.Renderer.Shader;
using Engine.Graphics.Pixel;
using Engine.Graphics.Render.Quad;
using OpenTK.Graphics.OpenGL;
using Serilog;
using Serilog.Events;
namespace Engine.Renderer;
namespace Engine.Graphics;
public class Renderer
{
internal Texture.Texture TextureInternal => _framebuffer.TextureInternal!;
public readonly QuadRenderer QuadRenderer;
private readonly Framebuffer.Framebuffer _framebuffer;
private readonly Queue<Action<Renderer>> _renderActions = new();
private readonly Thread _renderThread;
public Renderer(int parWidth, int parHeight)
{
InitializeOpenGl();
InitializeOpenGl(parWidth, parHeight);
_renderThread = Thread.CurrentThread;
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
.AddColorAttachment<Rgb8>()
.AddDepthAttachment()
.Build();
QuadRenderer = new QuadRenderer(this, 1024 * 8);
}
private void InitializeOpenGl()
private void InitializeOpenGl(int parWidth, int parHeight)
{
#if DEBUG
Debug.Setup();
@@ -36,6 +41,18 @@ public class Renderer
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.Blend);
GL.Viewport(0, 0, parWidth, parHeight);
}
public void EnsureRenderThread()
{
if (Thread.CurrentThread == _renderThread)
{
return;
}
throw new InvalidOperationException("Renderer is not on render thread");
}
internal void Commit(Action<Renderer> parRenderAction)
@@ -45,8 +62,10 @@ public class Renderer
internal void Render()
{
EnsureRenderThread();
_framebuffer.Bind();
while (_renderActions.TryDequeue(out Action<Renderer>? renderAction))
while (_renderActions.TryDequeue(out var renderAction))
{
renderAction(this);
}
@@ -57,5 +76,6 @@ public class Renderer
internal void Resize(int parWidth, int parHeight)
{
_framebuffer.Resize(parWidth, parHeight);
GL.Viewport(0, 0, parWidth, parHeight);
}
}

View File

@@ -1,48 +1,14 @@
using System.Runtime.CompilerServices;
using System.Text;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using Serilog;
namespace Engine.Renderer.Shader;
namespace Engine.Graphics.Shader;
public class ShaderProgram : OpenGlObject
public class Program : OpenGlObject
{
private readonly Dictionary<string, int> _uniforms = new();
public static ShaderProgram CreateFromSource(string parSource)
{
var vertexSource = new StringBuilder();
var fragmentSource = new StringBuilder();
var inFragment = false;
var inVertex = false;
foreach (var line in parSource.Split('\n'))
{
if (line.StartsWith("#shader vertex"))
{
inVertex = true;
inFragment = false;
}
else if (line.StartsWith("#shader fragment"))
{
inVertex = false;
inFragment = true;
}
else if (inVertex)
{
vertexSource.AppendLine(line);
}
else if (inFragment)
{
fragmentSource.AppendLine(line);
}
}
return new ShaderProgram(vertexSource.ToString(), fragmentSource.ToString());
}
public ShaderProgram(string parVertexSource, string parFragmentSource)
public Program(string parVertexSource, string parFragmentSource)
{
var vertexShader = CompileSource(parVertexSource, ShaderType.VertexShader);
var fragmentShader = CompileSource(parFragmentSource, ShaderType.FragmentShader);
@@ -50,7 +16,7 @@ public class ShaderProgram : OpenGlObject
Handle = LinkProgram(vertexShader, fragmentShader);
}
public void SetUniform<T>(string parName, T parValue)
public void SetUniform<T>(string parName, in T parValue)
{
try
{
@@ -79,6 +45,12 @@ public class ShaderProgram : OpenGlObject
case Matrix4 matrix:
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
break;
case int[] intArray:
GL.ProgramUniform1(Handle, location, intArray.Length, intArray);
break;
case float[] floatArray:
GL.ProgramUniform1(Handle, location, floatArray.Length, floatArray);
break;
default:
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
}

View 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());
}
}

View File

@@ -1,12 +1,13 @@
using Engine.Renderer.Framebuffer;
using Engine.Renderer.Pixel;
using Engine.Graphics.Framebuffer;
using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL;
using Serilog;
namespace Engine.Renderer.Texture;
namespace Engine.Graphics.Texture;
public class DynamicTexture : Texture, IFramebufferAttachment
{
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType._texture;
public IFramebufferAttachment.AttachmentType Type => IFramebufferAttachment.AttachmentType.Texture;
private readonly PixelFormat _format;
private readonly PixelType _type;
@@ -45,5 +46,7 @@ public class DynamicTexture : Texture, IFramebufferAttachment
Bind();
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
IntPtr.Zero);
Log.Debug("Texture {Handle} resized to {Width}x{Height}", Handle, Width, Height);
}
}

View File

@@ -1,10 +1,12 @@
using Engine.Asset;
using Engine.Renderer.Pixel;
using Engine.Graphics.Pixel;
using OpenTK.Mathematics;
namespace Engine.Renderer.Texture;
namespace Engine.Graphics.Texture;
public interface IConstTexture
{
public Vector2i Size { get; }
public int Width { get; }
public int Height { get; }

View File

@@ -1,7 +1,7 @@
using Engine.Asset;
using Engine.Renderer.Pixel;
using Engine.Graphics.Pixel;
namespace Engine.Renderer.Texture;
namespace Engine.Graphics.Texture;
public interface ITexture : IConstTexture
{

View File

@@ -1,7 +1,7 @@
using Engine.Renderer.Pixel;
using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL;
namespace Engine.Renderer.Texture;
namespace Engine.Graphics.Texture;
public class StaticTexture : Texture
{

View File

@@ -1,11 +1,15 @@
using System.Runtime.InteropServices;
using Engine.Renderer.Pixel;
using Engine.Graphics.Pixel;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using Serilog;
namespace Engine.Renderer.Texture;
namespace Engine.Graphics.Texture;
public abstract class Texture : OpenGlObject, ITexture
{
public Vector2i Size => new(Width, Height);
public int Width
{
get => _width;
@@ -44,6 +48,13 @@ public abstract class Texture : OpenGlObject, ITexture
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
Handle = handle;
GL.TextureParameter(Handle, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TextureParameter(Handle, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TextureParameter(Handle, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
GL.TextureParameter(Handle, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
Log.Debug("Texture {Handle} created with {Width}x{Height}", Handle, Width, Height);
}
public void UploadPixels<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 type = default(T).Type;
// Set alignment to avoid row padding issues
GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
GL.GetTextureSubImage(Handle, 0, parX, parY, 0, parWidth, parHeight, 1, format, type,
parPixels.Length * Marshal.SizeOf<T>(),
parPixels);
@@ -108,6 +123,7 @@ public abstract class Texture : OpenGlObject, ITexture
public void BindUnit(int parUnit = 0)
{
GL.ActiveTexture(TextureUnit.Texture0 + parUnit);
GL.BindTextureUnit(parUnit, Handle);
}

View 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();
}
}

View File

@@ -2,12 +2,12 @@
public enum MouseButton
{
_left,
_right,
_middle,
Left,
Right,
Middle,
_button4,
_button5,
_button6,
_button7
Button4,
Button5,
Button6,
Button7
}

View File

@@ -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);
}
}

View 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]&quot;;.
/// </summary>
internal static string Mesh {
get {
return ResourceManager.GetString("Mesh", resourceCulture);
}
}
internal static string Quad {
get {
return ResourceManager.GetString("Quad", resourceCulture);
}
}
}
}

View 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>

View File

@@ -1,4 +1,4 @@
using Engine.Renderer.Camera;
using Engine.Graphics.Camera;
using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;

View File

@@ -1,4 +1,4 @@
using Engine.Renderer.Camera;
using Engine.Graphics.Camera;
using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;

View File

@@ -1,13 +1,21 @@
using OpenTK.Windowing.Desktop;
using Engine.Asset;
using Engine.Graphics;
using Engine.Graphics.Pixel;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Engine;
public class Window
public class Window : IPresenter
{
public bool IsExiting => _window.IsExiting;
public int Width { get; private set; }
public int Height { get; private set; }
public event Action<ResizeEventArgs>? Resize;
private readonly Engine _engine;
private readonly NativeWindow _window;
@@ -26,16 +34,32 @@ public class Window
{
Width = parArgs.Width;
Height = parArgs.Height;
Resize?.Invoke(parArgs);
};
}
public void Update()
public void Update(double parDeltaTime)
{
if (!_headless)
if (_headless)
{
NativeWindow.ProcessWindowEvents(false);
_window.SwapBuffers();
return;
}
NativeWindow.ProcessWindowEvents(false);
_window.SwapBuffers();
}
public void Present(IConstTexture parTexture)
{
GL.Viewport(0, 0, Width, Height);
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
var texture = (Texture)parTexture;
_engine.Renderer.QuadRenderer.Commit(Matrix4.CreateScale(2f), Vector4.One, texture);
_engine.Renderer.QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity);
_engine.Renderer.QuadRenderer.Reset();
}
}