.
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
|
.idea/
|
||||||
|
*.DotSettings.*
|
||||||
34
DoomDeathmatch.sln
Normal file
34
DoomDeathmatch.sln
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatch", "DoomDeathmatch\DoomDeathmatch.csproj", "{7AE4D009-6590-4E44-9B68-FE37B6650F70}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine", "Engine\Engine.csproj", "{5EE134DE-2275-40C0-8B9D-4EFF22474F63}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatchConsole", "DoomDeathmatchConsole\DoomDeathmatchConsole.csproj", "{B9A652EE-4267-4D6B-B1A6-2447F870A06D}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatchWPF", "DoomDeathmatchWPF\DoomDeathmatchWPF.csproj", "{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{7AE4D009-6590-4E44-9B68-FE37B6650F70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7AE4D009-6590-4E44-9B68-FE37B6650F70}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7AE4D009-6590-4E44-9B68-FE37B6650F70}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7AE4D009-6590-4E44-9B68-FE37B6650F70}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5EE134DE-2275-40C0-8B9D-4EFF22474F63}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B9A652EE-4267-4D6B-B1A6-2447F870A06D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B712A719-5EB3-4869-AA4A-3BFFA3B9C918}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
19
DoomDeathmatch/Assets/test.shader
Normal file
19
DoomDeathmatch/Assets/test.shader
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#shader vertex
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 aPos;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#shader fragment
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
FragColor = vec4(1.0, 1.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
21
DoomDeathmatch/DoomDeathmatch.csproj
Normal file
21
DoomDeathmatch/DoomDeathmatch.csproj
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Engine\Engine.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<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" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
20
DoomDeathmatch/Program.cs
Normal file
20
DoomDeathmatch/Program.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace DoomDeathmatch;
|
||||||
|
|
||||||
|
internal abstract class Program
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
static extern IntPtr GetConsoleWindow();
|
||||||
|
|
||||||
|
[DllImport("User32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr GetDC(IntPtr hwnd);
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var engine = new Engine.Engine(1280, 720, false, "Doom Deathmatch");
|
||||||
|
engine.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
DoomDeathmatch/QuadVertex.cs
Normal file
21
DoomDeathmatch/QuadVertex.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Engine.Renderer.Buffer.Vertex;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using Half = System.Half;
|
||||||
|
|
||||||
|
namespace DoomDeathmatch;
|
||||||
|
|
||||||
|
public struct QuadVertex : IVertex
|
||||||
|
{
|
||||||
|
[Vertex(VertexAttribType.Float, 2)]
|
||||||
|
public Vector2 Position2;
|
||||||
|
|
||||||
|
[Vertex(VertexAttribType.Float, 2)]
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
[Vertex(VertexAttribType.Float, 2)]
|
||||||
|
public Vector2 Position4;
|
||||||
|
|
||||||
|
[Vertex(VertexAttribType.Float, 2)]
|
||||||
|
public Vector2 Position3;
|
||||||
|
}
|
||||||
32
Engine/Asset/Image.cs
Normal file
32
Engine/Asset/Image.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
namespace Engine.Asset;
|
||||||
|
|
||||||
|
public class Image<T>(T[,] pixels)
|
||||||
|
where T : struct, IPixel
|
||||||
|
{
|
||||||
|
public int Width { get; } = pixels.GetLength(0);
|
||||||
|
public int Height { get; } = pixels.GetLength(1);
|
||||||
|
public T[,] Pixels { get; } = pixels;
|
||||||
|
|
||||||
|
public T this[int x, int y] => Pixels[x, y];
|
||||||
|
|
||||||
|
public Image(int width, int height) : this(new T[width, height])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicTexture<T> ToDynamicTexture()
|
||||||
|
{
|
||||||
|
var texture = new DynamicTexture<T>(Width, Height);
|
||||||
|
texture.UploadPixels(this);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StaticTexture<T> ToStaticTexture()
|
||||||
|
{
|
||||||
|
var texture = new StaticTexture<T>(Width, Height);
|
||||||
|
texture.UploadPixels(this);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Engine/Asset/Mesh/Loader/IMeshLoader.cs
Normal file
29
Engine/Asset/Mesh/Loader/IMeshLoader.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Engine.Asset.Mesh.Loader;
|
||||||
|
|
||||||
|
public interface IMeshLoader
|
||||||
|
{
|
||||||
|
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default);
|
||||||
|
|
||||||
|
public static Mesh Optimize(Mesh mesh)
|
||||||
|
{
|
||||||
|
var optimizedMesh = new Mesh();
|
||||||
|
var vertexMap = new Dictionary<Mesh.Vertex, uint>();
|
||||||
|
uint index = 0;
|
||||||
|
|
||||||
|
foreach (var vertex in mesh.Vertices)
|
||||||
|
{
|
||||||
|
if (vertexMap.TryGetValue(vertex, out var existingIndex))
|
||||||
|
optimizedMesh.IndicesInternal.Add(existingIndex);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vertexMap.Add(vertex, index);
|
||||||
|
optimizedMesh.VerticesInternal.Add(vertex);
|
||||||
|
optimizedMesh.IndicesInternal.Add(index);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return optimizedMesh;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Engine/Asset/Mesh/Loader/MeshLoaderParameters.cs
Normal file
12
Engine/Asset/Mesh/Loader/MeshLoaderParameters.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Engine.Asset.Mesh.Loader;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MeshLoaderParameters
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
LoadNormals = 1 << 0,
|
||||||
|
LoadUVs = 1 << 1,
|
||||||
|
Optimize = 1 << 2,
|
||||||
|
|
||||||
|
Default = LoadNormals | LoadUVs | Optimize
|
||||||
|
}
|
||||||
90
Engine/Asset/Mesh/Loader/ObjMeshLoader.cs
Normal file
90
Engine/Asset/Mesh/Loader/ObjMeshLoader.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Asset.Mesh.Loader;
|
||||||
|
|
||||||
|
public class ObjMeshLoader : IMeshLoader
|
||||||
|
{
|
||||||
|
private static readonly ObjMeshLoader Instance = new();
|
||||||
|
|
||||||
|
public static Mesh Load(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||||
|
=> Instance.LoadMesh(path, parameters);
|
||||||
|
|
||||||
|
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||||
|
{
|
||||||
|
var mesh = new Mesh();
|
||||||
|
|
||||||
|
var tempVertices = new List<Vector3>();
|
||||||
|
var tempNormals = new List<Vector3>();
|
||||||
|
var tempUVs = new List<Vector2>();
|
||||||
|
var index = 0u;
|
||||||
|
|
||||||
|
var loadNormals = parameters.HasFlag(MeshLoaderParameters.LoadNormals);
|
||||||
|
var loadUVs = parameters.HasFlag(MeshLoaderParameters.LoadUVs);
|
||||||
|
|
||||||
|
using var reader = new StreamReader(path);
|
||||||
|
while (reader.ReadLine() is { } line)
|
||||||
|
{
|
||||||
|
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
if (parts.Length == 0 || parts[0].StartsWith('#'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (parts[0])
|
||||||
|
{
|
||||||
|
case "v":
|
||||||
|
tempVertices.Add(new Vector3(
|
||||||
|
float.Parse(parts[1], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[2], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[3], CultureInfo.InvariantCulture)
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "vt" when loadUVs:
|
||||||
|
tempUVs.Add(new Vector2(
|
||||||
|
float.Parse(parts[1], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[2], CultureInfo.InvariantCulture)
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "vn" when loadNormals:
|
||||||
|
tempNormals.Add(new Vector3(
|
||||||
|
float.Parse(parts[1], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[2], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[3], CultureInfo.InvariantCulture)
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "f":
|
||||||
|
for (var i = 1; i <= 3; i++)
|
||||||
|
{
|
||||||
|
var faceComponents = parts[i].Split('/');
|
||||||
|
var meshVertex = new Mesh.Vertex
|
||||||
|
{
|
||||||
|
Position = tempVertices[int.Parse(faceComponents[0]) - 1]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loadUVs && faceComponents.Length > 1 && faceComponents[1] != "")
|
||||||
|
{
|
||||||
|
meshVertex.Uv = tempUVs[int.Parse(faceComponents[1]) - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadNormals && faceComponents.Length > 2 && faceComponents[2] != "")
|
||||||
|
{
|
||||||
|
meshVertex.Normal = tempNormals[int.Parse(faceComponents[2]) - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh.VerticesInternal.Add(meshVertex);
|
||||||
|
mesh.IndicesInternal.Add(index++);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.HasFlag(MeshLoaderParameters.Optimize))
|
||||||
|
mesh = IMeshLoader.Optimize(mesh);
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
Engine/Asset/Mesh/Loader/StlMeshLoader.cs
Normal file
66
Engine/Asset/Mesh/Loader/StlMeshLoader.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Asset.Mesh.Loader;
|
||||||
|
|
||||||
|
public class StlMeshLoader : IMeshLoader
|
||||||
|
{
|
||||||
|
private static readonly StlMeshLoader Instance = new();
|
||||||
|
|
||||||
|
public static Mesh Load(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||||
|
=> Instance.LoadMesh(path, parameters);
|
||||||
|
|
||||||
|
public Mesh LoadMesh(string path, MeshLoaderParameters parameters = MeshLoaderParameters.Default)
|
||||||
|
{
|
||||||
|
var mesh = new Mesh();
|
||||||
|
|
||||||
|
var currentNormal = new Vector3();
|
||||||
|
var index = 0u;
|
||||||
|
|
||||||
|
using var reader = new StreamReader(path);
|
||||||
|
while (reader.ReadLine() is { } line)
|
||||||
|
{
|
||||||
|
line = line.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("solid") || line.StartsWith("outer loop") ||
|
||||||
|
line.StartsWith("endloop"))
|
||||||
|
continue;
|
||||||
|
if (line.StartsWith("endsolid"))
|
||||||
|
break;
|
||||||
|
|
||||||
|
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
switch (parts[0])
|
||||||
|
{
|
||||||
|
case "facet" when parts[1] == "normal" && parameters.HasFlag(MeshLoaderParameters.LoadNormals):
|
||||||
|
currentNormal = new Vector3(
|
||||||
|
float.Parse(parts[2], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[3], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[4], CultureInfo.InvariantCulture)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "vertex":
|
||||||
|
{
|
||||||
|
var vertex = new Vector3(
|
||||||
|
float.Parse(parts[1], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[2], CultureInfo.InvariantCulture),
|
||||||
|
float.Parse(parts[3], CultureInfo.InvariantCulture)
|
||||||
|
);
|
||||||
|
|
||||||
|
mesh.VerticesInternal.Add(new Mesh.Vertex
|
||||||
|
{
|
||||||
|
Position = vertex,
|
||||||
|
Normal = currentNormal
|
||||||
|
});
|
||||||
|
mesh.IndicesInternal.Add(index++);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.HasFlag(MeshLoaderParameters.Optimize))
|
||||||
|
mesh = IMeshLoader.Optimize(mesh);
|
||||||
|
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Engine/Asset/Mesh/Mesh.cs
Normal file
22
Engine/Asset/Mesh/Mesh.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Asset.Mesh;
|
||||||
|
|
||||||
|
public class Mesh
|
||||||
|
{
|
||||||
|
public IReadOnlyList<Vertex> Vertices => _vertices;
|
||||||
|
public IReadOnlyList<uint> Indices => _indices;
|
||||||
|
|
||||||
|
internal IList<Vertex> VerticesInternal => _vertices;
|
||||||
|
internal IList<uint> IndicesInternal => _indices;
|
||||||
|
|
||||||
|
private readonly List<Vertex> _vertices = [];
|
||||||
|
private readonly List<uint> _indices = [];
|
||||||
|
|
||||||
|
public record struct Vertex
|
||||||
|
{
|
||||||
|
public Vector3 Position { get; internal set; }
|
||||||
|
public Vector3 Normal { get; internal set; }
|
||||||
|
public Vector2 Uv { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Engine/Engine.cs
Normal file
65
Engine/Engine.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Engine.Renderer.Buffer;
|
||||||
|
using Engine.Renderer.Buffer.Vertex;
|
||||||
|
using Engine.Renderer.Shader;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using OpenTK.Windowing.Common;
|
||||||
|
using OpenTK.Windowing.Desktop;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog.Sinks.SystemConsole.Themes;
|
||||||
|
|
||||||
|
namespace Engine;
|
||||||
|
|
||||||
|
public sealed class Engine
|
||||||
|
{
|
||||||
|
public Renderer.Renderer Renderer => _renderer;
|
||||||
|
|
||||||
|
private readonly Window _window;
|
||||||
|
private readonly Renderer.Renderer _renderer;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public Engine(int width, int height, bool headless = false, string title = "")
|
||||||
|
{
|
||||||
|
var settings = new NativeWindowSettings
|
||||||
|
{
|
||||||
|
ClientSize = headless ? new Vector2i(1, 1) : new Vector2i(width, height),
|
||||||
|
Title = title,
|
||||||
|
StartVisible = !headless,
|
||||||
|
APIVersion = new Version(4, 5),
|
||||||
|
Profile = ContextProfile.Compatability
|
||||||
|
};
|
||||||
|
|
||||||
|
_window = new Window(this, new NativeWindow(settings), headless);
|
||||||
|
_renderer = new Renderer.Renderer(width, height);
|
||||||
|
|
||||||
|
Thread.CurrentThread.Name = "RendererThread";
|
||||||
|
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Verbose()
|
||||||
|
.Enrich.WithThreadName()
|
||||||
|
.Enrich.WithThreadId()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.WriteTo.Console(
|
||||||
|
outputTemplate:
|
||||||
|
"[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level:u3}] [{ThreadName,-15:l}:{ThreadId,-4:d4}] [{SourceContext:l}] {Message:lj}{NewLine}{Exception}",
|
||||||
|
theme: AnsiConsoleTheme.Literate)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
_logger = Log.ForContext<Engine>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
GL.ClearColor(0.2f, 0.3f, 0.3f, 1.0f);
|
||||||
|
|
||||||
|
GL.Viewport(0, 0, _window.Width, _window.Height);
|
||||||
|
|
||||||
|
while (!_window.IsExiting)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Engine/Engine.csproj
Normal file
20
Engine/Engine.csproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
</Project>
|
||||||
11
Engine/Input/IInputHandler.cs
Normal file
11
Engine/Input/IInputHandler.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Engine.Input;
|
||||||
|
|
||||||
|
public interface IInputHandler
|
||||||
|
{
|
||||||
|
bool IsKeyPressed(Key key);
|
||||||
|
bool IsKeyJustPressed(Key key);
|
||||||
|
bool IsKeyRepeat(Key key);
|
||||||
|
|
||||||
|
bool IsMouseButtonPressed(MouseButton button);
|
||||||
|
bool IsMouseButtonJustPressed(MouseButton button);
|
||||||
|
}
|
||||||
52
Engine/Input/Key.cs
Normal file
52
Engine/Input/Key.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
namespace Engine.Input;
|
||||||
|
|
||||||
|
public enum Key
|
||||||
|
{
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
E,
|
||||||
|
F,
|
||||||
|
G,
|
||||||
|
H,
|
||||||
|
I,
|
||||||
|
J,
|
||||||
|
K,
|
||||||
|
L,
|
||||||
|
M,
|
||||||
|
N,
|
||||||
|
O,
|
||||||
|
P,
|
||||||
|
Q,
|
||||||
|
R,
|
||||||
|
S,
|
||||||
|
T,
|
||||||
|
U,
|
||||||
|
V,
|
||||||
|
W,
|
||||||
|
X,
|
||||||
|
Y,
|
||||||
|
Z,
|
||||||
|
|
||||||
|
Ctrl,
|
||||||
|
Alt,
|
||||||
|
Shift,
|
||||||
|
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
|
||||||
|
Escape,
|
||||||
|
Enter,
|
||||||
|
Space,
|
||||||
|
Tab,
|
||||||
|
Backspace,
|
||||||
|
Delete,
|
||||||
|
Insert,
|
||||||
|
Home,
|
||||||
|
End,
|
||||||
|
PageUp,
|
||||||
|
PageDown,
|
||||||
|
}
|
||||||
13
Engine/Input/MouseButton.cs
Normal file
13
Engine/Input/MouseButton.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Engine.Input;
|
||||||
|
|
||||||
|
public enum MouseButton
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Middle,
|
||||||
|
|
||||||
|
Button4,
|
||||||
|
Button5,
|
||||||
|
Button6,
|
||||||
|
Button7,
|
||||||
|
}
|
||||||
52
Engine/Renderer/Buffer/IndexBuffer.cs
Normal file
52
Engine/Renderer/Buffer/IndexBuffer.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Buffer;
|
||||||
|
|
||||||
|
public class IndexBuffer : OpenGlObject
|
||||||
|
{
|
||||||
|
internal int Count { get; }
|
||||||
|
|
||||||
|
public IndexBuffer(int count, BufferStorageFlags flags)
|
||||||
|
{
|
||||||
|
Count = count;
|
||||||
|
|
||||||
|
GL.CreateBuffers(1, out int handle);
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, flags);
|
||||||
|
|
||||||
|
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UploadData(uint[] data)
|
||||||
|
{
|
||||||
|
UploadData(0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UploadData(int offset, uint[] data)
|
||||||
|
{
|
||||||
|
if (offset < 0)
|
||||||
|
throw new ArgumentException("Offset must be greater than 0");
|
||||||
|
|
||||||
|
if (data.Length + offset > Count)
|
||||||
|
throw new ArgumentException("Data array is too large");
|
||||||
|
|
||||||
|
GL.NamedBufferSubData(Handle, offset, data.Length * sizeof(uint), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteBuffer(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Engine/Renderer/Buffer/Vertex/IVertex.cs
Normal file
50
Engine/Renderer/Buffer/Vertex/IVertex.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Buffer.Vertex;
|
||||||
|
|
||||||
|
public interface IVertex
|
||||||
|
{
|
||||||
|
public static IOrderedEnumerable<FieldInfo> GetFields<T>() => GetFields(typeof(T));
|
||||||
|
|
||||||
|
public static IOrderedEnumerable<FieldInfo> GetFields(Type type) =>
|
||||||
|
type.GetFields(BindingFlags.Public | BindingFlags.Instance).OrderBy(f => f.MetadataToken);
|
||||||
|
|
||||||
|
public static bool IsValid(Type type)
|
||||||
|
{
|
||||||
|
if (!type.IsValueType || !type.IsAssignableTo(typeof(IVertex)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var fields = GetFields(type);
|
||||||
|
var totalSize = 0;
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
if (!field.FieldType.IsValueType)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var attribute = field.GetCustomAttribute<VertexAttribute>();
|
||||||
|
if (attribute == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var size = AttributeSize(attribute.Type) * attribute.ComponentCount * attribute.RepeatCount;
|
||||||
|
if (size != Marshal.SizeOf(field.FieldType))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
totalSize += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSize == Marshal.SizeOf(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int AttributeSize(VertexAttribType type) => type switch
|
||||||
|
{
|
||||||
|
VertexAttribType.Byte or VertexAttribType.UnsignedByte => sizeof(byte),
|
||||||
|
VertexAttribType.Short or VertexAttribType.UnsignedShort => sizeof(short),
|
||||||
|
VertexAttribType.Int or VertexAttribType.UnsignedInt => sizeof(int),
|
||||||
|
VertexAttribType.HalfFloat => Marshal.SizeOf<Half>(),
|
||||||
|
VertexAttribType.Float => sizeof(float),
|
||||||
|
VertexAttribType.Double => sizeof(double),
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
26
Engine/Renderer/Buffer/Vertex/VertexAttribute.cs
Normal file
26
Engine/Renderer/Buffer/Vertex/VertexAttribute.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Buffer.Vertex;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class VertexAttribute : Attribute
|
||||||
|
{
|
||||||
|
public VertexAttribType Type { get; }
|
||||||
|
public int ComponentCount { get; }
|
||||||
|
public bool Normalized { get; }
|
||||||
|
public int RepeatCount { get; }
|
||||||
|
|
||||||
|
public VertexAttribute(VertexAttribType type, int componentCount = 1, bool normalized = false, int repeatCount = 1)
|
||||||
|
{
|
||||||
|
if (componentCount <= 0)
|
||||||
|
throw new ArgumentException("Count must be greater than 0");
|
||||||
|
|
||||||
|
if (repeatCount <= 0)
|
||||||
|
throw new ArgumentException("Repeat must be greater than 0");
|
||||||
|
|
||||||
|
Type = type;
|
||||||
|
ComponentCount = componentCount;
|
||||||
|
Normalized = normalized;
|
||||||
|
RepeatCount = repeatCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Engine/Renderer/Buffer/VertexArray.cs
Normal file
89
Engine/Renderer/Buffer/VertexArray.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Engine.Renderer.Buffer.Vertex;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Buffer;
|
||||||
|
|
||||||
|
public class VertexArray : OpenGlObject
|
||||||
|
{
|
||||||
|
// private IndexBuffer? _boundIndexBuffer;
|
||||||
|
// private readonly Dictionary<int, VertexBuffer<IVertex>> _boundVertexBuffers = new();
|
||||||
|
|
||||||
|
public VertexArray()
|
||||||
|
{
|
||||||
|
GL.CreateVertexArrays(1, out int handle);
|
||||||
|
Handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BindIndexBuffer(IndexBuffer buffer)
|
||||||
|
{
|
||||||
|
GL.VertexArrayElementBuffer(Handle, buffer.Handle);
|
||||||
|
|
||||||
|
Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, buffer.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BindVertexBuffer<T>(VertexBuffer<T> buffer, int bindingIndex = 0, int divisor = 0)
|
||||||
|
where T : struct, IVertex
|
||||||
|
{
|
||||||
|
if (bindingIndex < 0)
|
||||||
|
throw new ArgumentException("Binding index must be greater than 0");
|
||||||
|
|
||||||
|
if (divisor < 0)
|
||||||
|
throw new ArgumentException("Divisor must be greater than 0");
|
||||||
|
|
||||||
|
var stride = Marshal.SizeOf<T>();
|
||||||
|
var fields = IVertex.GetFields<T>();
|
||||||
|
|
||||||
|
GL.VertexArrayVertexBuffer(Handle, bindingIndex, buffer.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, bindingIndex);
|
||||||
|
|
||||||
|
location += attribute.RepeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.VertexArrayBindingDivisor(Handle, bindingIndex, divisor);
|
||||||
|
|
||||||
|
Log.Debug(
|
||||||
|
"Vertex array {Handle} bound to vertex buffer {Buffer} at {BindingIndex} binding with {Divisor} divisor",
|
||||||
|
Handle, buffer.Handle, bindingIndex, divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupAttribute(VertexAttribute attribute, int baseLocation, int baseOffset, int bindingIndex)
|
||||||
|
{
|
||||||
|
var size = attribute.ComponentCount * IVertex.AttributeSize(attribute.Type);
|
||||||
|
|
||||||
|
for (var i = 0; i < attribute.RepeatCount; i++)
|
||||||
|
{
|
||||||
|
var location = baseLocation + i;
|
||||||
|
var offset = baseOffset + i * size;
|
||||||
|
|
||||||
|
GL.EnableVertexArrayAttrib(Handle, location);
|
||||||
|
GL.VertexArrayAttribFormat(Handle, location, attribute.ComponentCount, attribute.Type, attribute.Normalized,
|
||||||
|
offset);
|
||||||
|
GL.VertexArrayAttribBinding(Handle, location, bindingIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.BindVertexArray(Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.BindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteVertexArray(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Engine/Renderer/Buffer/VertexBuffer.cs
Normal file
63
Engine/Renderer/Buffer/VertexBuffer.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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 count, BufferStorageFlags flags)
|
||||||
|
{
|
||||||
|
if (!IVertex.IsValid(typeof(T)))
|
||||||
|
throw new ArgumentException($"Type {typeof(T).Name} is not a valid vertex type");
|
||||||
|
|
||||||
|
if (count <= 0)
|
||||||
|
throw new ArgumentException("Count must be greater than 0");
|
||||||
|
|
||||||
|
Count = count;
|
||||||
|
|
||||||
|
GL.CreateBuffers(1, out int handle);
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
GL.NamedBufferStorage(Handle, Count * _stride, IntPtr.Zero, flags);
|
||||||
|
|
||||||
|
Log.Debug("Vertex buffer {Handle} created with {Count} elements of type {Type}", Handle, Count, typeof(T).Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UploadData(T[] data)
|
||||||
|
{
|
||||||
|
UploadData(0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UploadData(int offset, T[] data)
|
||||||
|
{
|
||||||
|
if (offset < 0)
|
||||||
|
throw new ArgumentException("Offset must be greater than 0");
|
||||||
|
|
||||||
|
if (data.Length + offset > Count)
|
||||||
|
throw new ArgumentException("Data array is too large");
|
||||||
|
|
||||||
|
GL.NamedBufferSubData(Handle, offset * _stride, data.Length * _stride, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteBuffer(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Engine/Renderer/Camera/ICamera.cs
Normal file
9
Engine/Renderer/Camera/ICamera.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Camera;
|
||||||
|
|
||||||
|
public interface ICamera
|
||||||
|
{
|
||||||
|
public Matrix4 View { get; }
|
||||||
|
public Matrix4 Projection { get; }
|
||||||
|
}
|
||||||
9
Engine/Renderer/Camera/ScreenspaceCamera.cs
Normal file
9
Engine/Renderer/Camera/ScreenspaceCamera.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Camera;
|
||||||
|
|
||||||
|
public class ScreenspaceCamera : ICamera
|
||||||
|
{
|
||||||
|
public Matrix4 View => Matrix4.Identity;
|
||||||
|
public Matrix4 Projection => Matrix4.Identity;
|
||||||
|
}
|
||||||
88
Engine/Renderer/Framebuffer/Framebuffer.cs
Normal file
88
Engine/Renderer/Framebuffer/Framebuffer.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using Engine.Renderer.Texture;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Framebuffer;
|
||||||
|
|
||||||
|
public class Framebuffer : OpenGlObject
|
||||||
|
{
|
||||||
|
public int Width
|
||||||
|
{
|
||||||
|
get => _width;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
throw new ArgumentException("Width must be greater than 0");
|
||||||
|
|
||||||
|
_width = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Height
|
||||||
|
{
|
||||||
|
get => _height;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
throw new ArgumentException("Height must be greater than 0");
|
||||||
|
|
||||||
|
_height = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConstTexture<Rgb8> Texture => _texture;
|
||||||
|
internal Texture.Texture<Rgb8> TextureInternal => _texture;
|
||||||
|
|
||||||
|
private int _width;
|
||||||
|
private int _height;
|
||||||
|
|
||||||
|
private readonly DynamicTexture<Rgb8> _texture;
|
||||||
|
private readonly Renderbuffer _renderbuffer;
|
||||||
|
|
||||||
|
public Framebuffer(int width, int height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
GL.CreateFramebuffers(1, out int handle);
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
_texture = new DynamicTexture<Rgb8>(width, height);
|
||||||
|
GL.NamedFramebufferTexture(Handle, FramebufferAttachment.ColorAttachment0, _texture.Handle, 0);
|
||||||
|
|
||||||
|
_renderbuffer = new Renderbuffer(width, height, RenderbufferStorage.Depth24Stencil8);
|
||||||
|
GL.NamedFramebufferRenderbuffer(Handle, FramebufferAttachment.DepthStencilAttachment,
|
||||||
|
RenderbufferTarget.Renderbuffer, _renderbuffer.Handle);
|
||||||
|
|
||||||
|
var status = GL.CheckNamedFramebufferStatus(Handle, FramebufferTarget.Framebuffer);
|
||||||
|
if (status != FramebufferStatus.FramebufferComplete)
|
||||||
|
throw new Exception($"Framebuffer is not complete: {status}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resize(int width, int height)
|
||||||
|
{
|
||||||
|
if (Width == width && Height == height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
_texture.Resize(width, height);
|
||||||
|
_renderbuffer.Resize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteFramebuffer(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Engine/Renderer/Framebuffer/Renderbuffer.cs
Normal file
49
Engine/Renderer/Framebuffer/Renderbuffer.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Framebuffer;
|
||||||
|
|
||||||
|
public class Renderbuffer : OpenGlObject
|
||||||
|
{
|
||||||
|
public int Width { get; private set; }
|
||||||
|
public int Height { get; private set; }
|
||||||
|
|
||||||
|
private readonly RenderbufferStorage _format;
|
||||||
|
|
||||||
|
public Renderbuffer(int width, int height, RenderbufferStorage format)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
_format = format;
|
||||||
|
|
||||||
|
GL.CreateRenderbuffers(1, out int handle);
|
||||||
|
Handle = handle;
|
||||||
|
|
||||||
|
GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resize(int width, int height)
|
||||||
|
{
|
||||||
|
if (Width == width && Height == height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
GL.NamedRenderbufferStorage(Handle, _format, Width, Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteRenderbuffer(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Engine/Renderer/IPresenter.cs
Normal file
9
Engine/Renderer/IPresenter.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
namespace Engine.Renderer;
|
||||||
|
|
||||||
|
public interface IPresenter<T> where T : struct, IPixel
|
||||||
|
{
|
||||||
|
public void Present(IConstTexture<T> texture);
|
||||||
|
}
|
||||||
21
Engine/Renderer/OpenGLObject.cs
Normal file
21
Engine/Renderer/OpenGLObject.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Engine.Renderer;
|
||||||
|
|
||||||
|
public abstract class OpenGlObject
|
||||||
|
{
|
||||||
|
public int Handle { get; protected set; } = -1;
|
||||||
|
|
||||||
|
internal abstract void Bind();
|
||||||
|
internal abstract void Unbind();
|
||||||
|
|
||||||
|
protected abstract void Destroy();
|
||||||
|
|
||||||
|
~OpenGlObject()
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
Handle = -1;
|
||||||
|
|
||||||
|
Log.Debug("OpenGL object {Handle} destroyed", Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Engine/Renderer/Pixel/IPixel.cs
Normal file
12
Engine/Renderer/Pixel/IPixel.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Pixel;
|
||||||
|
|
||||||
|
public interface IPixel
|
||||||
|
{
|
||||||
|
public PixelFormat Format { get; }
|
||||||
|
public PixelType Type { get; }
|
||||||
|
|
||||||
|
public PixelInternalFormat InternalFormat { get; }
|
||||||
|
public SizedInternalFormat SizedInternalFormat { get; }
|
||||||
|
}
|
||||||
16
Engine/Renderer/Pixel/Rgb8.cs
Normal file
16
Engine/Renderer/Pixel/Rgb8.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Pixel;
|
||||||
|
|
||||||
|
public struct Rgb8 : IPixel
|
||||||
|
{
|
||||||
|
public PixelFormat Format => PixelFormat.Rgb;
|
||||||
|
public PixelType Type => PixelType.UnsignedByte;
|
||||||
|
|
||||||
|
public PixelInternalFormat InternalFormat => PixelInternalFormat.Rgb8;
|
||||||
|
public SizedInternalFormat SizedInternalFormat => SizedInternalFormat.Rgb8;
|
||||||
|
|
||||||
|
public byte R;
|
||||||
|
public byte G;
|
||||||
|
public byte B;
|
||||||
|
}
|
||||||
107
Engine/Renderer/Renderer.cs
Normal file
107
Engine/Renderer/Renderer.cs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace Engine.Renderer;
|
||||||
|
|
||||||
|
public class Renderer
|
||||||
|
{
|
||||||
|
internal Texture.Texture<Rgb8> TextureInternal => _framebuffer.TextureInternal;
|
||||||
|
|
||||||
|
private readonly Framebuffer.Framebuffer _framebuffer;
|
||||||
|
private readonly Queue<Action<Renderer>> _renderActions = new();
|
||||||
|
|
||||||
|
public Renderer(int width, int height)
|
||||||
|
{
|
||||||
|
InitializeOpenGl();
|
||||||
|
|
||||||
|
_framebuffer = new Framebuffer.Framebuffer(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeOpenGl()
|
||||||
|
{
|
||||||
|
GL.Enable(EnableCap.DebugOutput);
|
||||||
|
GL.DebugMessageCallback(DebugCallback, IntPtr.Zero);
|
||||||
|
|
||||||
|
GL.Enable(EnableCap.DepthTest);
|
||||||
|
|
||||||
|
GL.Enable(EnableCap.FramebufferSrgb);
|
||||||
|
|
||||||
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||||
|
GL.Enable(EnableCap.Blend);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length,
|
||||||
|
IntPtr message, IntPtr userParam)
|
||||||
|
{
|
||||||
|
var logger = Log.ForContext<Engine>();
|
||||||
|
|
||||||
|
var messageText = Marshal.PtrToStringAnsi(message, length) ?? "Unknown OpenGL Error";
|
||||||
|
|
||||||
|
var typePrefix = type switch
|
||||||
|
{
|
||||||
|
DebugType.DebugTypeError => "Error",
|
||||||
|
DebugType.DebugTypeDeprecatedBehavior => "Deprecated",
|
||||||
|
DebugType.DebugTypeUndefinedBehavior => "Undefined",
|
||||||
|
DebugType.DebugTypePortability => "Portability",
|
||||||
|
DebugType.DebugTypePerformance => "Performance",
|
||||||
|
DebugType.DebugTypeMarker => "Marker",
|
||||||
|
DebugType.DebugTypePushGroup => "PushGroup",
|
||||||
|
DebugType.DebugTypePopGroup => "PopGroup",
|
||||||
|
DebugType.DebugTypeOther => "Info",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
var sourcePrefix = source switch
|
||||||
|
{
|
||||||
|
DebugSource.DebugSourceApi => "API",
|
||||||
|
DebugSource.DebugSourceWindowSystem => "Window",
|
||||||
|
DebugSource.DebugSourceShaderCompiler => "Shader",
|
||||||
|
DebugSource.DebugSourceThirdParty => "ThirdParty",
|
||||||
|
DebugSource.DebugSourceApplication => "Application",
|
||||||
|
DebugSource.DebugSourceOther => "Other",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.Write(
|
||||||
|
GetLogLevel(severity),
|
||||||
|
"[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})",
|
||||||
|
typePrefix,
|
||||||
|
sourcePrefix,
|
||||||
|
messageText,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogEventLevel GetLogLevel(DebugSeverity severity)
|
||||||
|
{
|
||||||
|
return severity switch
|
||||||
|
{
|
||||||
|
DebugSeverity.DebugSeverityNotification => LogEventLevel.Information,
|
||||||
|
DebugSeverity.DebugSeverityHigh => LogEventLevel.Error,
|
||||||
|
DebugSeverity.DebugSeverityMedium => LogEventLevel.Warning,
|
||||||
|
DebugSeverity.DebugSeverityLow => LogEventLevel.Debug,
|
||||||
|
_ => LogEventLevel.Debug
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Commit(Action<Renderer> renderAction)
|
||||||
|
{
|
||||||
|
_renderActions.Enqueue(renderAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Render()
|
||||||
|
{
|
||||||
|
_framebuffer.Bind();
|
||||||
|
while (_renderActions.TryDequeue(out var renderAction))
|
||||||
|
renderAction(this);
|
||||||
|
_framebuffer.Unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Resize(int width, int height)
|
||||||
|
{
|
||||||
|
_framebuffer.Resize(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
181
Engine/Renderer/Shader/ShaderProgram.cs
Normal file
181
Engine/Renderer/Shader/ShaderProgram.cs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Shader;
|
||||||
|
|
||||||
|
public class ShaderProgram : OpenGlObject
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, int> _uniforms = new();
|
||||||
|
|
||||||
|
public static ShaderProgram CreateFromSource(string source)
|
||||||
|
{
|
||||||
|
var vertexSource = new StringBuilder();
|
||||||
|
var fragmentSource = new StringBuilder();
|
||||||
|
var inFragment = false;
|
||||||
|
var inVertex = false;
|
||||||
|
|
||||||
|
foreach (var line in source.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 vertexSource, string fragmentSource)
|
||||||
|
{
|
||||||
|
var vertexShader = CompileSource(vertexSource, ShaderType.VertexShader);
|
||||||
|
var fragmentShader = CompileSource(fragmentSource, ShaderType.FragmentShader);
|
||||||
|
|
||||||
|
Handle = LinkProgram(vertexShader, fragmentShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetUniform<T>(string name, T value, [CallerMemberName] string caller = "")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var location = GetUniformLocation(name);
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case bool boolValue:
|
||||||
|
GL.ProgramUniform1(Handle, location, boolValue ? 1 : 0);
|
||||||
|
break;
|
||||||
|
case int intValue:
|
||||||
|
GL.ProgramUniform1(Handle, location, intValue);
|
||||||
|
break;
|
||||||
|
case float floatValue:
|
||||||
|
GL.ProgramUniform1(Handle, location, floatValue);
|
||||||
|
break;
|
||||||
|
case Vector2 vec2:
|
||||||
|
GL.ProgramUniform2(Handle, location, vec2);
|
||||||
|
break;
|
||||||
|
case Vector3 vec3:
|
||||||
|
GL.ProgramUniform3(Handle, location, vec3);
|
||||||
|
break;
|
||||||
|
case Vector4 vec4:
|
||||||
|
GL.ProgramUniform4(Handle, location, vec4);
|
||||||
|
break;
|
||||||
|
case Matrix4 matrix:
|
||||||
|
GL.ProgramUniformMatrix4(Handle, location, false, ref matrix);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException($"Unsupported uniform type: {typeof(T).Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Failed to set uniform {UniformName} from {Caller}", name, caller);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.UseProgram(Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.UseProgram(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteProgram(Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetUniformLocation(string name)
|
||||||
|
{
|
||||||
|
if (_uniforms.TryGetValue(name, out var location))
|
||||||
|
return location;
|
||||||
|
|
||||||
|
location = GL.GetUniformLocation(Handle, name);
|
||||||
|
if (location < 0)
|
||||||
|
throw new ArgumentException($"Uniform '{name}' not found in shader program");
|
||||||
|
|
||||||
|
_uniforms.Add(name, location);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CompileSource(string source, ShaderType type)
|
||||||
|
{
|
||||||
|
var shaderId = GL.CreateShader(type);
|
||||||
|
GL.ShaderSource(shaderId, source);
|
||||||
|
GL.CompileShader(shaderId);
|
||||||
|
|
||||||
|
GL.GetShader(shaderId, ShaderParameter.CompileStatus, out var status);
|
||||||
|
if (status == 0)
|
||||||
|
{
|
||||||
|
var log = GL.GetShaderInfoLog(shaderId);
|
||||||
|
GL.DeleteShader(shaderId);
|
||||||
|
|
||||||
|
throw new ShaderCompilationException(type, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shaderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int LinkProgram(int vertexShader, int fragmentShader)
|
||||||
|
{
|
||||||
|
var programId = GL.CreateProgram();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GL.AttachShader(programId, vertexShader);
|
||||||
|
GL.AttachShader(programId, fragmentShader);
|
||||||
|
GL.LinkProgram(programId);
|
||||||
|
|
||||||
|
GL.GetProgram(programId, GetProgramParameterName.LinkStatus, out var linkStatus);
|
||||||
|
if (linkStatus == 0)
|
||||||
|
{
|
||||||
|
var log = GL.GetProgramInfoLog(programId);
|
||||||
|
throw new ShaderLinkException(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
GL.ValidateProgram(programId);
|
||||||
|
GL.GetProgram(programId, GetProgramParameterName.ValidateStatus, out var validateStatus);
|
||||||
|
if (validateStatus == 0)
|
||||||
|
{
|
||||||
|
var log = GL.GetProgramInfoLog(programId);
|
||||||
|
throw new ShaderValidationException(log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
GL.DeleteShader(vertexShader);
|
||||||
|
GL.DeleteShader(fragmentShader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return programId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShaderCompilationException(ShaderType type, string message)
|
||||||
|
: Exception($"Failed to compile {type} shader: {message}")
|
||||||
|
{
|
||||||
|
public ShaderType ShaderType { get; } = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ShaderLinkException(string message) : Exception($"Failed to link shader program: {message}");
|
||||||
|
|
||||||
|
public class ShaderValidationException(string message) : Exception($"Failed to validate shader program: {message}");
|
||||||
36
Engine/Renderer/Texture/DynamicTexture.cs
Normal file
36
Engine/Renderer/Texture/DynamicTexture.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
public class DynamicTexture<T> : Texture<T> where T : struct, IPixel
|
||||||
|
{
|
||||||
|
private readonly PixelFormat _format;
|
||||||
|
private readonly PixelType _type;
|
||||||
|
private readonly PixelInternalFormat _internalFormat;
|
||||||
|
|
||||||
|
public DynamicTexture(int width, int height) : base(width, height)
|
||||||
|
{
|
||||||
|
var pixel = default(T);
|
||||||
|
_format = pixel.Format;
|
||||||
|
_type = pixel.Type;
|
||||||
|
_internalFormat = pixel.InternalFormat;
|
||||||
|
|
||||||
|
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
||||||
|
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
|
||||||
|
IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Resize(int width, int height)
|
||||||
|
{
|
||||||
|
if (Width == width && Height == height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
Bind();
|
||||||
|
GL.TexImage2D(TextureTarget.Texture2D, 0, _internalFormat, Width, Height, 0, _format, _type,
|
||||||
|
IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Engine/Renderer/Texture/IConstTexture.cs
Normal file
37
Engine/Renderer/Texture/IConstTexture.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Engine.Asset;
|
||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
public interface IConstTexture<T> where T : struct, IPixel
|
||||||
|
{
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
|
||||||
|
public void ReadPixels(int x, int y, int width, int height, T[,] pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConstTextureExtensions
|
||||||
|
{
|
||||||
|
public static T[,] ReadPixels<T>(this IConstTexture<T> texture) where T : struct, IPixel
|
||||||
|
=> texture.ReadPixels(0, 0, texture.Width, texture.Height);
|
||||||
|
|
||||||
|
public static T[,] ReadPixels<T>(this IConstTexture<T> texture, int x, int y, int width, int height)
|
||||||
|
where T : struct, IPixel
|
||||||
|
{
|
||||||
|
var pixels = new T[width, height];
|
||||||
|
texture.ReadPixels(x, y, width, height, pixels);
|
||||||
|
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReadPixels<T>(this IConstTexture<T> texture, Image<T> image) where T : struct, IPixel
|
||||||
|
=> texture.ReadPixels(0, 0, image);
|
||||||
|
|
||||||
|
public static void ReadPixels<T>(this IConstTexture<T> texture, int x, int y, Image<T> image)
|
||||||
|
where T : struct, IPixel =>
|
||||||
|
texture.ReadPixels(x, y, image.Width, image.Height, image.Pixels);
|
||||||
|
|
||||||
|
public static void ReadPixels<T>(this IConstTexture<T> texture, T[,] pixels) where T : struct, IPixel
|
||||||
|
=> texture.ReadPixels(0, 0, texture.Width, texture.Height, pixels);
|
||||||
|
}
|
||||||
22
Engine/Renderer/Texture/ITexture.cs
Normal file
22
Engine/Renderer/Texture/ITexture.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Engine.Asset;
|
||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
public interface ITexture<T> : IConstTexture<T> where T : struct, IPixel
|
||||||
|
{
|
||||||
|
public void UploadPixels(int x, int y, int width, int height, T[,] pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TextureExtensions
|
||||||
|
{
|
||||||
|
public static void UploadPixels<T>(this ITexture<T> texture, Image<T> image) where T : struct, IPixel
|
||||||
|
=> texture.UploadPixels(0, 0, image);
|
||||||
|
|
||||||
|
public static void UploadPixels<T>(this ITexture<T> texture, int x, int y, Image<T> image)
|
||||||
|
where T : struct, IPixel =>
|
||||||
|
texture.UploadPixels(x, y, image.Width, image.Height, image.Pixels);
|
||||||
|
|
||||||
|
public static void UploadPixels<T>(this ITexture<T> texture, T[,] pixels) where T : struct, IPixel
|
||||||
|
=> texture.UploadPixels(0, 0, texture.Width, texture.Height, pixels);
|
||||||
|
}
|
||||||
13
Engine/Renderer/Texture/StaticTexture.cs
Normal file
13
Engine/Renderer/Texture/StaticTexture.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
public class StaticTexture<T> : Texture<T> where T : struct, IPixel
|
||||||
|
{
|
||||||
|
public StaticTexture(int width, int height) : base(width, height)
|
||||||
|
{
|
||||||
|
var format = default(T).SizedInternalFormat;
|
||||||
|
GL.TextureStorage2D(Handle, 1, format, Width, Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Engine/Renderer/Texture/Texture.cs
Normal file
106
Engine/Renderer/Texture/Texture.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Engine.Asset;
|
||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
|
||||||
|
namespace Engine.Renderer.Texture;
|
||||||
|
|
||||||
|
public abstract class Texture<T> : OpenGlObject, ITexture<T> where T : struct, IPixel
|
||||||
|
{
|
||||||
|
public int Width
|
||||||
|
{
|
||||||
|
get => _width;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
throw new ArgumentException("Width must be greater than 0");
|
||||||
|
|
||||||
|
_width = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Height
|
||||||
|
{
|
||||||
|
get => _height;
|
||||||
|
protected set
|
||||||
|
{
|
||||||
|
if (value <= 0)
|
||||||
|
throw new ArgumentException("Height must be greater than 0");
|
||||||
|
|
||||||
|
_height = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _width;
|
||||||
|
private int _height;
|
||||||
|
|
||||||
|
protected Texture(int width, int height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
|
||||||
|
GL.CreateTextures(TextureTarget.Texture2D, 1, out int handle);
|
||||||
|
Handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UploadPixels(int x, int y, int width, int height, T[,] pixels)
|
||||||
|
{
|
||||||
|
if (x < 0 || y < 0)
|
||||||
|
throw new ArgumentException("x and y must be greater than 0");
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
throw new ArgumentException("Width and height must be greater than 0");
|
||||||
|
|
||||||
|
if (x + width > Width || y + height > Height)
|
||||||
|
throw new ArgumentException("x + width and y + height must be less than width and height");
|
||||||
|
|
||||||
|
if (pixels.Length != width * height)
|
||||||
|
throw new ArgumentException("Pixels array must be of size width * height");
|
||||||
|
|
||||||
|
var format = pixels[0, 0].Format;
|
||||||
|
var type = pixels[0, 0].Type;
|
||||||
|
|
||||||
|
GL.TextureSubImage2D(Handle, 0, x, y, width, height, format, type, pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadPixels(int x, int y, int width, int height, T[,] pixels)
|
||||||
|
{
|
||||||
|
if (x < 0 || y < 0)
|
||||||
|
throw new ArgumentException("x and y must be greater than 0");
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
throw new ArgumentException("Width and height must be greater than 0");
|
||||||
|
|
||||||
|
if (x + width > Width || y + height > Height)
|
||||||
|
throw new ArgumentException("x + width and y + height must be less than width and height");
|
||||||
|
|
||||||
|
if (pixels.Length != width * height)
|
||||||
|
throw new ArgumentException("Pixels array must be of size width * height");
|
||||||
|
|
||||||
|
var format = default(T).Format;
|
||||||
|
var type = default(T).Type;
|
||||||
|
|
||||||
|
GL.GetTextureSubImage(Handle, 0, x, y, 0, width, height, 1, format, type, pixels.Length * Marshal.SizeOf<T>(),
|
||||||
|
pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void BindUnit(int unit)
|
||||||
|
{
|
||||||
|
GL.BindTextureUnit(unit, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Bind()
|
||||||
|
{
|
||||||
|
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal override void Unbind()
|
||||||
|
{
|
||||||
|
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Destroy()
|
||||||
|
{
|
||||||
|
GL.DeleteTexture(Handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Engine/Scene/Component/Component.cs
Normal file
8
Engine/Scene/Component/Component.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Engine.Scene.Component;
|
||||||
|
|
||||||
|
public abstract class Component(GameObject gameObject)
|
||||||
|
{
|
||||||
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public GameObject GameObject { get; } = gameObject;
|
||||||
|
}
|
||||||
34
Engine/Scene/Component/PerspectiveCamera.cs
Normal file
34
Engine/Scene/Component/PerspectiveCamera.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Engine.Renderer.Camera;
|
||||||
|
using Engine.Util;
|
||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Scene.Component;
|
||||||
|
|
||||||
|
public class PerspectiveCamera(
|
||||||
|
GameObject gameObject,
|
||||||
|
float aspectRatio,
|
||||||
|
float fieldOfView,
|
||||||
|
float nearPlane,
|
||||||
|
float farPlane)
|
||||||
|
: Component(gameObject), ICamera
|
||||||
|
{
|
||||||
|
public float AspectRatio { get; set; } = aspectRatio;
|
||||||
|
public float FieldOfView { get; set; } = fieldOfView;
|
||||||
|
public float NearPlane { get; set; } = nearPlane;
|
||||||
|
public float FarPlane { get; set; } = farPlane;
|
||||||
|
|
||||||
|
public Matrix4 View
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var transformMatrix = GameObject.Transform.TransformMatrix;
|
||||||
|
var forward = new Vector4(0, 0, 1, 1).MulProject(transformMatrix);
|
||||||
|
var eye = new Vector4(0, 0, 0, 1).MulProject(transformMatrix);
|
||||||
|
var up = (new Vector4(0, 1, 0, 1).MulProject(transformMatrix) - eye).Normalized();
|
||||||
|
|
||||||
|
return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix4 Projection => Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
|
||||||
|
}
|
||||||
19
Engine/Scene/Component/Transform.cs
Normal file
19
Engine/Scene/Component/Transform.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Scene.Component;
|
||||||
|
|
||||||
|
public class Transform(GameObject gameObject) : Component(gameObject)
|
||||||
|
{
|
||||||
|
public Vector3 Position { get; set; } = Vector3.Zero;
|
||||||
|
public Quaternion Rotation { get; set; } = Quaternion.Identity;
|
||||||
|
public Vector3 Scale { get; set; } = Vector3.One;
|
||||||
|
public Vector3 LocalScale { get; set; } = Vector3.One;
|
||||||
|
|
||||||
|
public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) *
|
||||||
|
Matrix4.CreateFromQuaternion(Rotation) *
|
||||||
|
Matrix4.CreateTranslation(Position);
|
||||||
|
|
||||||
|
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
|
||||||
|
|
||||||
|
private Matrix4 ParentTransformMatrix => Matrix4.Identity;
|
||||||
|
}
|
||||||
73
Engine/Scene/GameObject.cs
Normal file
73
Engine/Scene/GameObject.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using Engine.Scene.Component;
|
||||||
|
|
||||||
|
namespace Engine.Scene;
|
||||||
|
|
||||||
|
public sealed class GameObject
|
||||||
|
{
|
||||||
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
|
public Transform Transform { get; }
|
||||||
|
|
||||||
|
private readonly Queue<Action> _componentActions = new();
|
||||||
|
|
||||||
|
private readonly List<Component.Component> _components = new();
|
||||||
|
private readonly ISet<Type> _addedComponentTypes = new HashSet<Type>();
|
||||||
|
|
||||||
|
public GameObject()
|
||||||
|
{
|
||||||
|
AddComponent<Transform>();
|
||||||
|
UpdateComponents();
|
||||||
|
|
||||||
|
Transform = GetComponent<Transform>()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? GetComponent<T>() where T : Component.Component
|
||||||
|
{
|
||||||
|
if (!_addedComponentTypes.Contains(typeof(T)))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return _components.OfType<T>().FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddComponent<T>(params object?[] args) where T : Component.Component
|
||||||
|
{
|
||||||
|
if (_addedComponentTypes.Contains(typeof(T)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var newArgs = new object?[args.Length + 1];
|
||||||
|
newArgs[0] = this;
|
||||||
|
for (var i = 0; i < args.Length; i++)
|
||||||
|
newArgs[i + 1] = args[i];
|
||||||
|
|
||||||
|
var component = (T?)Activator.CreateInstance(typeof(T), newArgs);
|
||||||
|
if (component == null)
|
||||||
|
throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
|
||||||
|
|
||||||
|
_componentActions.Enqueue(() =>
|
||||||
|
{
|
||||||
|
_components.Add(component);
|
||||||
|
_addedComponentTypes.Add(typeof(T));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveComponent<T>() where T : Component.Component
|
||||||
|
{
|
||||||
|
if (!_addedComponentTypes.Contains(typeof(T)) || typeof(T) == typeof(Transform))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var component = GetComponent<T>();
|
||||||
|
if (component == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_componentActions.Enqueue(() =>
|
||||||
|
{
|
||||||
|
_components.Remove(component);
|
||||||
|
_addedComponentTypes.Remove(typeof(T));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateComponents()
|
||||||
|
{
|
||||||
|
while (_componentActions.TryDequeue(out var action))
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Engine/Util/Math.cs
Normal file
15
Engine/Util/Math.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
|
||||||
|
namespace Engine.Util;
|
||||||
|
|
||||||
|
public static class Math
|
||||||
|
{
|
||||||
|
public static Vector4 MulProject(this Vector4 a, in Matrix4 m)
|
||||||
|
{
|
||||||
|
var result = a * m;
|
||||||
|
if (result.W != 0.0)
|
||||||
|
result /= result.W;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Engine/Window.cs
Normal file
79
Engine/Window.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Engine.Renderer;
|
||||||
|
using Engine.Renderer.Pixel;
|
||||||
|
using Engine.Renderer.Texture;
|
||||||
|
using OpenTK.Graphics.OpenGL;
|
||||||
|
using OpenTK.Windowing.Desktop;
|
||||||
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
|
||||||
|
namespace Engine;
|
||||||
|
|
||||||
|
public class Window : IPresenter<Rgb8>
|
||||||
|
{
|
||||||
|
public bool IsExiting => _window.IsExiting;
|
||||||
|
public int Width => _window.ClientSize.X;
|
||||||
|
public int Height => _window.ClientSize.Y;
|
||||||
|
|
||||||
|
private readonly Engine _engine;
|
||||||
|
private readonly NativeWindow _window;
|
||||||
|
private readonly bool _headless;
|
||||||
|
|
||||||
|
public Window(Engine engine, NativeWindow window, bool headless)
|
||||||
|
{
|
||||||
|
_engine = engine;
|
||||||
|
_window = window;
|
||||||
|
_headless = headless;
|
||||||
|
|
||||||
|
_window.MakeCurrent();
|
||||||
|
_window.Resize += args => GL.Viewport(0, 0, args.Width, args.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (!_headless)
|
||||||
|
{
|
||||||
|
NativeWindow.ProcessWindowEvents(false);
|
||||||
|
_window.SwapBuffers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Present(IConstTexture<Rgb8> texture)
|
||||||
|
{
|
||||||
|
if (_headless)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||||
|
|
||||||
|
_engine.Renderer.TextureInternal.Bind();
|
||||||
|
|
||||||
|
GL.Enable(EnableCap.Texture2D);
|
||||||
|
GL.Begin(PrimitiveType.Quads);
|
||||||
|
GL.Color3(1, 1, 1);
|
||||||
|
|
||||||
|
GL.TexCoord2(0, 0);
|
||||||
|
GL.Vertex2(0, 0);
|
||||||
|
|
||||||
|
GL.TexCoord2(1, 0);
|
||||||
|
GL.Vertex2(1, 0);
|
||||||
|
|
||||||
|
GL.TexCoord2(1, 1);
|
||||||
|
GL.Vertex2(1, 1);
|
||||||
|
|
||||||
|
GL.TexCoord2(0, 1);
|
||||||
|
GL.Vertex2(0, 1);
|
||||||
|
|
||||||
|
GL.End();
|
||||||
|
|
||||||
|
GL.Disable(EnableCap.Texture2D);
|
||||||
|
GL.Flush();
|
||||||
|
|
||||||
|
_engine.Renderer.TextureInternal.Unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NativeWindowExtensions
|
||||||
|
{
|
||||||
|
public static unsafe void SwapBuffers(this NativeWindow window)
|
||||||
|
{
|
||||||
|
GLFW.SwapBuffers(window.WindowPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user