.
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