.
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0"/>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,7 +11,7 @@ layout (location = 3) in int aTextureId;
|
||||
layout (location = 4) in mat4 aModelMatrix;
|
||||
|
||||
layout (location = 0) out vec2 oUV;
|
||||
layout (location = 1) out int oTextureId;
|
||||
layout (location = 1) flat out int oTextureId;
|
||||
layout (location = 2) out vec3 oNormal;
|
||||
|
||||
void main()
|
||||
@@ -35,7 +35,7 @@ layout (location = 0) out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 lightColor = vec3(iUV, 0);
|
||||
vec3 lightColor = vec3(1);
|
||||
FragColor = vec4(lightColor, 1.0);
|
||||
if (iTextureId >= 0)
|
||||
FragColor *= texture(uTexture[iTextureId], iUV);
|
||||
|
||||
@@ -12,7 +12,7 @@ layout (location = 4) in mat4 aModel;
|
||||
|
||||
layout (location = 0) out vec4 oColor;
|
||||
layout (location = 1) out vec2 oUV;
|
||||
layout (location = 2) out int oTextureId;
|
||||
layout (location = 2) flat out int oTextureId;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
||||
61
Engine/assets/shader/text.shader
Normal file
61
Engine/assets/shader/text.shader
Normal file
@@ -0,0 +1,61 @@
|
||||
// #type vertex
|
||||
#version 460 core
|
||||
|
||||
uniform mat4 u_Projection;
|
||||
uniform mat4 u_View;
|
||||
|
||||
layout (location = 0) in vec4 a_Position;
|
||||
layout (location = 1) in vec2 a_UV;
|
||||
layout (location = 2) in vec4 a_Color;
|
||||
layout (location = 3) in int a_AtlasId;
|
||||
layout (location = 4) in vec2 a_UnitRange;
|
||||
layout (location = 5) in mat4 a_Model;
|
||||
|
||||
layout (location = 0) out vec4 o_Color;
|
||||
layout (location = 1) out vec2 o_UV;
|
||||
layout (location = 2) flat out int o_AtlasId;
|
||||
layout (location = 3) flat out vec2 o_UnitRange;
|
||||
|
||||
void main()
|
||||
{
|
||||
o_Color = a_Color;
|
||||
o_UV = a_UV;
|
||||
o_AtlasId = a_AtlasId;
|
||||
o_UnitRange = a_UnitRange;
|
||||
|
||||
gl_Position = u_Projection * u_View * a_Model * a_Position;
|
||||
}
|
||||
|
||||
// #type fragment
|
||||
#version 460 core
|
||||
|
||||
uniform sampler2D u_Atlas[16];
|
||||
|
||||
layout (location = 0) in vec4 i_Color;
|
||||
layout (location = 1) in vec2 i_UV;
|
||||
layout (location = 2) flat in int i_AtlasId;
|
||||
layout (location = 3) flat in vec2 i_UnitRange;
|
||||
|
||||
layout (location = 0) out vec4 FragColor;
|
||||
|
||||
float median(float r, float g, float b) {
|
||||
return max(min(r, g), min(max(r, g), b));
|
||||
}
|
||||
|
||||
float screenPxRange() {
|
||||
vec2 screenTexSize = vec2(1.0) / fwidth(i_UV);
|
||||
return max(0.5 * dot(i_UnitRange, screenTexSize), 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 msd = texture(u_Atlas[i_AtlasId], i_UV).rgb;
|
||||
float sd = median(msd.r, msd.g, msd.b);
|
||||
float screenPxDistance = screenPxRange() * (sd - 0.5);
|
||||
float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
||||
// this is a temporary solution for TODO: fix depth test self discard on overlap
|
||||
if (opacity == 0.0) {
|
||||
discard;
|
||||
}
|
||||
FragColor = mix(vec4(0), i_Color, opacity);
|
||||
}
|
||||
109
Engine/src/Asset/Font/Font.cs
Normal file
109
Engine/src/Asset/Font/Font.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using Engine.Asset.Font.Metadata;
|
||||
using Engine.Graphics.Texture;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Asset.Font;
|
||||
|
||||
public class Font
|
||||
{
|
||||
public StaticTexture AtlasTexture => _atlasTexture;
|
||||
public Metadata.Metadata Metadata => _metadata;
|
||||
public Vector2 UnitRange => _unitRange;
|
||||
|
||||
private readonly StaticTexture _atlasTexture;
|
||||
private readonly Metadata.Metadata _metadata;
|
||||
|
||||
private readonly Dictionary<int, Glyph> _glyphs = new();
|
||||
private readonly Dictionary<int, GlyphData> _glyphData = new();
|
||||
private readonly Dictionary<(int, int), Kerning> _kernings = new();
|
||||
private readonly Vector2 _unitRange;
|
||||
|
||||
public Font(StaticTexture parAtlasTexture, Metadata.Metadata parMetadata)
|
||||
{
|
||||
_atlasTexture = parAtlasTexture;
|
||||
_metadata = parMetadata;
|
||||
_unitRange = new Vector2(_metadata.Atlas.DistanceRange / _metadata.Atlas.Width,
|
||||
_metadata.Atlas.DistanceRange / _metadata.Atlas.Height);
|
||||
|
||||
LoadGlyphs();
|
||||
LoadKernings();
|
||||
}
|
||||
|
||||
public Glyph? GetGlyph(int parUnicode)
|
||||
{
|
||||
return _glyphs.GetValueOrDefault(parUnicode);
|
||||
}
|
||||
|
||||
public GlyphData? GetGlyphData(int parUnicode)
|
||||
{
|
||||
return _glyphData.GetValueOrDefault(parUnicode);
|
||||
}
|
||||
|
||||
public Kerning? GetKerning(int parUnicode1, int parUnicode2)
|
||||
{
|
||||
return _kernings.GetValueOrDefault((parUnicode1, parUnicode2));
|
||||
}
|
||||
|
||||
public FontIterator Iterator(string parText)
|
||||
{
|
||||
return new FontIterator(this, parText);
|
||||
}
|
||||
|
||||
public Vector2 Measure(string parText)
|
||||
{
|
||||
var fontIterator = Iterator(parText);
|
||||
_ = fontIterator.ToList();
|
||||
|
||||
return new Vector2(fontIterator.MaxWidth, fontIterator.MaxHeight);
|
||||
}
|
||||
|
||||
private void LoadGlyphs()
|
||||
{
|
||||
foreach (var glyph in _metadata.Glyphs)
|
||||
{
|
||||
_glyphs.Add(glyph.Unicode, glyph);
|
||||
|
||||
if (glyph.PlaneBounds == null || glyph.AtlasBounds == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_glyphData.Add(glyph.Unicode, new GlyphData(_metadata, glyph));
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadKernings()
|
||||
{
|
||||
foreach (var kerning in _metadata.Kerning)
|
||||
{
|
||||
_kernings.Add((kerning.Unicode1, kerning.Unicode2), kerning);
|
||||
}
|
||||
}
|
||||
|
||||
public record GlyphData
|
||||
{
|
||||
public Vector2[] Positions { get; }
|
||||
public Vector2[] UVs { get; }
|
||||
|
||||
public GlyphData(in Metadata.Metadata parMetadata, in Glyph parGlyph)
|
||||
{
|
||||
var size = new Vector2(parMetadata.Atlas.Width, parMetadata.Atlas.Height);
|
||||
Positions =
|
||||
[
|
||||
new Vector2(parGlyph.PlaneBounds!.Left, parGlyph.PlaneBounds.Bottom),
|
||||
new Vector2(parGlyph.PlaneBounds.Left, parGlyph.PlaneBounds.Top),
|
||||
new Vector2(parGlyph.PlaneBounds.Right, parGlyph.PlaneBounds.Bottom),
|
||||
new Vector2(parGlyph.PlaneBounds.Right, parGlyph.PlaneBounds.Top),
|
||||
];
|
||||
|
||||
UVs =
|
||||
[
|
||||
new Vector2(parGlyph.AtlasBounds!.Left, parGlyph.AtlasBounds.Bottom) / size,
|
||||
new Vector2(parGlyph.AtlasBounds.Left, parGlyph.AtlasBounds.Top) / size,
|
||||
new Vector2(parGlyph.AtlasBounds.Right, parGlyph.AtlasBounds.Bottom) / size,
|
||||
|
||||
new Vector2(parGlyph.AtlasBounds.Right, parGlyph.AtlasBounds.Top) / size,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Engine/src/Asset/Font/FontIterator.cs
Normal file
119
Engine/src/Asset/Font/FontIterator.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Collections;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Asset.Font;
|
||||
|
||||
public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
|
||||
{
|
||||
public float MaxWidth => _maxWidth;
|
||||
public float MaxHeight => _maxHeight;
|
||||
|
||||
private readonly Font _font;
|
||||
private readonly string _text;
|
||||
|
||||
private int _currentIndex;
|
||||
private int _previousCodepoint = -1;
|
||||
private int _lineCharCount;
|
||||
private Vector2 _cursor = Vector2.Zero;
|
||||
private Vector2 _kerning = Vector2.Zero;
|
||||
|
||||
private float _maxWidth;
|
||||
private float _maxHeight;
|
||||
|
||||
public FontIterator(Font parFont, string parText)
|
||||
{
|
||||
_font = parFont;
|
||||
_text = parText;
|
||||
}
|
||||
|
||||
private static bool IsLineBreak(int parCodepoint)
|
||||
{
|
||||
return parCodepoint == '\n';
|
||||
}
|
||||
|
||||
private static bool IsTab(int parCodepoint)
|
||||
{
|
||||
return parCodepoint == '\t';
|
||||
}
|
||||
|
||||
private void Tab()
|
||||
{
|
||||
var spaceGlyph = _font.GetGlyph(' ');
|
||||
if (spaceGlyph == null)
|
||||
return;
|
||||
|
||||
var missingSpaces = 4 - _lineCharCount % 4;
|
||||
_cursor.X += missingSpaces * spaceGlyph.Advance;
|
||||
}
|
||||
|
||||
private void LineBreak()
|
||||
{
|
||||
_kerning = Vector2.Zero;
|
||||
_cursor.X = 0;
|
||||
_cursor.Y += _font.Metadata.Metrics.LineHeight;
|
||||
_lineCharCount = 0;
|
||||
_previousCodepoint = -1;
|
||||
}
|
||||
|
||||
public IEnumerator<NextGlyphData> GetEnumerator()
|
||||
{
|
||||
while (_currentIndex < _text.Length)
|
||||
{
|
||||
var codepoint = char.ConvertToUtf32(_text, _currentIndex);
|
||||
|
||||
if (IsLineBreak(codepoint))
|
||||
{
|
||||
LineBreak();
|
||||
_currentIndex += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTab(codepoint))
|
||||
{
|
||||
Tab();
|
||||
_currentIndex += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var glyph = _font.GetGlyph(codepoint);
|
||||
if (glyph == null)
|
||||
{
|
||||
glyph = _font.GetGlyph('?');
|
||||
if (glyph == null)
|
||||
{
|
||||
_currentIndex += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (_previousCodepoint != -1)
|
||||
{
|
||||
var kerning = _font.GetKerning(_previousCodepoint, glyph.Unicode);
|
||||
if (kerning != null)
|
||||
{
|
||||
_kerning.X += kerning.Advance;
|
||||
}
|
||||
}
|
||||
|
||||
var glyphData = _font.GetGlyphData(glyph.Unicode);
|
||||
|
||||
_maxWidth = Math.Max(_maxWidth, _cursor.X + glyph.Advance);
|
||||
_maxHeight = Math.Max(_maxHeight, _cursor.Y + _font.Metadata.Metrics.Ascender);
|
||||
|
||||
if (glyphData != null)
|
||||
yield return new NextGlyphData(glyphData.Positions, glyphData.UVs, (_cursor + _kerning) * new Vector2(1, -1));
|
||||
|
||||
_cursor.X += glyph.Advance;
|
||||
_lineCharCount++;
|
||||
_previousCodepoint = glyph.Unicode;
|
||||
_currentIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public record NextGlyphData(Vector2[] Positions, Vector2[] UVs, Vector2 Offset);
|
||||
}
|
||||
28
Engine/src/Asset/Font/Metadata/Atlas.cs
Normal file
28
Engine/src/Asset/Font/Metadata/Atlas.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Engine.Asset.Font.Metadata;
|
||||
|
||||
[JsonSerializable(typeof(Atlas))]
|
||||
[Serializable]
|
||||
public record Atlas
|
||||
{
|
||||
[JsonPropertyName("distanceRange")]
|
||||
[JsonInclude]
|
||||
public float DistanceRange { get; private set; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
[JsonInclude]
|
||||
public float Size { get; private set; }
|
||||
|
||||
[JsonPropertyName("width")]
|
||||
[JsonInclude]
|
||||
public int Width { get; private set; }
|
||||
|
||||
[JsonPropertyName("height")]
|
||||
[JsonInclude]
|
||||
public int Height { get; private set; }
|
||||
|
||||
[JsonPropertyName("yOrigin")]
|
||||
[JsonInclude]
|
||||
public string YOrigin { get; private set; }
|
||||
}
|
||||
24
Engine/src/Asset/Font/Metadata/Bounds.cs
Normal file
24
Engine/src/Asset/Font/Metadata/Bounds.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Engine.Asset.Font.Metadata;
|
||||
|
||||
[JsonSerializable(typeof(Bounds))]
|
||||
[Serializable]
|
||||
public record Bounds
|
||||
{
|
||||
[JsonPropertyName("left")]
|
||||
[JsonInclude]
|
||||
public float Left { get; private set; }
|
||||
|
||||
[JsonPropertyName("bottom")]
|
||||
[JsonInclude]
|
||||
public float Bottom { get; private set; }
|
||||
|
||||
[JsonPropertyName("right")]
|
||||
[JsonInclude]
|
||||
public float Right { get; private set; }
|
||||
|
||||
[JsonPropertyName("top")]
|
||||
[JsonInclude]
|
||||
public float Top { get; private set; }
|
||||
}
|
||||
25
Engine/src/Asset/Font/Metadata/Glyph.cs
Normal file
25
Engine/src/Asset/Font/Metadata/Glyph.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Asset.Font.Metadata;
|
||||
|
||||
[JsonSerializable(typeof(Glyph))]
|
||||
[Serializable]
|
||||
public record Glyph
|
||||
{
|
||||
[JsonPropertyName("unicode")]
|
||||
[JsonInclude]
|
||||
public int Unicode { get; private set; }
|
||||
|
||||
[JsonPropertyName("advance")]
|
||||
[JsonInclude]
|
||||
public float Advance { get; private set; }
|
||||
|
||||
[JsonPropertyName("planeBounds")]
|
||||
[JsonInclude]
|
||||
public Bounds? PlaneBounds { get; private set; }
|
||||
|
||||
[JsonPropertyName("atlasBounds")]
|
||||
[JsonInclude]
|
||||
public Bounds? AtlasBounds { get; private set; }
|
||||
}
|
||||
20
Engine/src/Asset/Font/Metadata/Kerning.cs
Normal file
20
Engine/src/Asset/Font/Metadata/Kerning.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Engine.Asset.Font.Metadata;
|
||||
|
||||
[JsonSerializable(typeof(Kerning))]
|
||||
[Serializable]
|
||||
public record Kerning
|
||||
{
|
||||
[JsonPropertyName("unicode1")]
|
||||
[JsonInclude]
|
||||
public int Unicode1 { get; private set; }
|
||||
|
||||
[JsonPropertyName("unicode2")]
|
||||
[JsonInclude]
|
||||
public int Unicode2 { get; private set; }
|
||||
|
||||
[JsonPropertyName("advance")]
|
||||
[JsonInclude]
|
||||
public float Advance { get; private set; }
|
||||
}
|
||||
24
Engine/src/Asset/Font/Metadata/Metadata.cs
Normal file
24
Engine/src/Asset/Font/Metadata/Metadata.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Engine.Asset.Font.Metadata;
|
||||
|
||||
[JsonSerializable(typeof(Metadata))]
|
||||
[Serializable]
|
||||
public record Metadata
|
||||
{
|
||||
[JsonPropertyName("atlas")]
|
||||
[JsonInclude]
|
||||
public Atlas Atlas { get; private set; }
|
||||
|
||||
[JsonPropertyName("metrics")]
|
||||
[JsonInclude]
|
||||
public Metrics Metrics { get; private set; }
|
||||
|
||||
[JsonPropertyName("glyphs")]
|
||||
[JsonInclude]
|
||||
public Glyph[] Glyphs { get; private set; }
|
||||
|
||||
[JsonPropertyName("kerning")]
|
||||
[JsonInclude]
|
||||
public Kerning[] Kerning { get; private set; }
|
||||
}
|
||||
31
Engine/src/Asset/Font/Metadata/Metrics.cs
Normal file
31
Engine/src/Asset/Font/Metadata/Metrics.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Engine.Asset.Font.Metadata;
|
||||
|
||||
[JsonSerializable(typeof(Metrics))]
|
||||
[Serializable]
|
||||
public record Metrics
|
||||
{
|
||||
[JsonPropertyName("emSize")]
|
||||
[JsonInclude]
|
||||
public float EmSize { get; private set; }
|
||||
|
||||
[JsonPropertyName("lineHeight")]
|
||||
[JsonInclude]
|
||||
public float LineHeight { get; private set; }
|
||||
|
||||
[JsonPropertyName("ascender")]
|
||||
[JsonInclude]
|
||||
public float Ascender { get; private set; }
|
||||
|
||||
[JsonPropertyName("descender")]
|
||||
[JsonInclude]
|
||||
public float Descender { get; private set; }
|
||||
|
||||
[JsonPropertyName("underlineY")]
|
||||
[JsonInclude]
|
||||
public float UnderlineY { get; private set; }
|
||||
|
||||
[JsonPropertyName("underlineThickness")]
|
||||
public float UnderlineThickness { get; private set; }
|
||||
}
|
||||
@@ -3,19 +3,19 @@ using Engine.Graphics.Texture;
|
||||
|
||||
namespace Engine.Asset;
|
||||
|
||||
public class Image<T>(T[,] parPixels)
|
||||
where T : struct, IPixel
|
||||
public class Image<T> where T : struct, IPixel
|
||||
{
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public T[,] Pixels { get; } = parPixels;
|
||||
public T[,] Pixels { get; }
|
||||
|
||||
public T this[int parY, int parX] => Pixels[parY, parX];
|
||||
|
||||
public Image(int parWidth, int parHeight) : this(new T[parHeight, parWidth])
|
||||
public Image(int parWidth, int parHeight)
|
||||
{
|
||||
Width = parWidth;
|
||||
Height = parHeight;
|
||||
Pixels = new T[parHeight, parWidth];
|
||||
}
|
||||
|
||||
public DynamicTexture ToDynamicTexture()
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Engine.Asset;
|
||||
using Engine.Asset.Font;
|
||||
using Engine.Asset.Mesh;
|
||||
using Engine.Graphics;
|
||||
using Engine.Graphics.Pixel;
|
||||
using Engine.Graphics.Shader;
|
||||
using Engine.Graphics.Texture;
|
||||
using Engine.Input;
|
||||
using Engine.Resource;
|
||||
using Engine.Resource.Loader;
|
||||
using Engine.Scene;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
@@ -50,9 +58,15 @@ public sealed class Engine
|
||||
private IInputHandler? _inputHandler;
|
||||
private IPresenter? _presenter;
|
||||
|
||||
internal ResourceManager EngineResourceManager => _engineResourceManager;
|
||||
public IResourceManager AssetResourceManager => _assetResourceManager;
|
||||
private readonly ResourceManager _engineResourceManager;
|
||||
private readonly ResourceManager _assetResourceManager;
|
||||
|
||||
private Thread? _updateThread;
|
||||
|
||||
public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, ILogger parLogger)
|
||||
public Engine(int parWidth, int parHeight, bool parHeadless, string parTitle, string parAssetFolder,
|
||||
ILogger parLogger)
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
@@ -61,20 +75,59 @@ public sealed class Engine
|
||||
|
||||
Instance = this;
|
||||
|
||||
Log.Logger = parLogger;
|
||||
_logger = Log.ForContext<Engine>();
|
||||
|
||||
var settings = new NativeWindowSettings
|
||||
{
|
||||
ClientSize = parHeadless ? new Vector2i(1, 1) : new Vector2i(parWidth, parHeight),
|
||||
Title = parTitle,
|
||||
StartVisible = !parHeadless,
|
||||
APIVersion = new Version(4, 6),
|
||||
Profile = ContextProfile.Core
|
||||
Profile = ContextProfile.Core,
|
||||
DepthBits = 0,
|
||||
StencilBits = 0
|
||||
};
|
||||
|
||||
Renderer = new Renderer(parWidth, parHeight, settings);
|
||||
Window = new Window(this, Renderer.NativeWindow, parHeadless);
|
||||
_engineResourceManager = CreateEngineResourceManager();
|
||||
_assetResourceManager = CreateAssetResourceManager(parAssetFolder);
|
||||
|
||||
Log.Logger = parLogger;
|
||||
_logger = Log.ForContext<Engine>();
|
||||
_logger.Information("Created asset resource manager in {AssetFolder}", parAssetFolder);
|
||||
|
||||
Renderer = new Renderer(this, parWidth, parHeight, settings);
|
||||
Window = new Window(this, Renderer.NativeWindow, parHeadless);
|
||||
}
|
||||
|
||||
private static ResourceManager CreateEngineResourceManager()
|
||||
{
|
||||
var memoryStreamProvider = new MemoryResourceStreamProvider();
|
||||
memoryStreamProvider.AddResource("shader/mesh", Encoding.UTF8.GetBytes(ShaderResource.Mesh));
|
||||
memoryStreamProvider.AddResource("shader/quad", Encoding.UTF8.GetBytes(ShaderResource.Quad));
|
||||
memoryStreamProvider.AddResource("shader/text", Encoding.UTF8.GetBytes(ShaderResource.Text));
|
||||
|
||||
var resourceManager = new ResourceManager(memoryStreamProvider);
|
||||
RegisterDefaultLoaders(resourceManager);
|
||||
|
||||
return resourceManager;
|
||||
}
|
||||
|
||||
private static ResourceManager CreateAssetResourceManager(string parAssetFolder)
|
||||
{
|
||||
var filesystemStreamProvider = new FilesystemResourceStreamProvider(parAssetFolder);
|
||||
|
||||
var resourceManager = new ResourceManager(filesystemStreamProvider);
|
||||
RegisterDefaultLoaders(resourceManager);
|
||||
|
||||
return resourceManager;
|
||||
}
|
||||
|
||||
private static void RegisterDefaultLoaders(ResourceManager parResourceManager)
|
||||
{
|
||||
parResourceManager.RegisterLoader<Program>(new ProgramLoader());
|
||||
parResourceManager.RegisterLoader<Mesh>(new MeshLoader());
|
||||
parResourceManager.RegisterLoader<Image<Rgba8>>(new ImageLoader());
|
||||
parResourceManager.RegisterLoader<Texture>(new TextureLoader());
|
||||
parResourceManager.RegisterLoader<Font>(new FontLoader());
|
||||
}
|
||||
|
||||
private readonly object _sceneLock = new();
|
||||
@@ -89,6 +142,11 @@ public sealed class Engine
|
||||
_updateThread.Join();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Presenter?.Exit();
|
||||
}
|
||||
|
||||
private void RunRender()
|
||||
{
|
||||
while (!Presenter?.IsExiting ?? false)
|
||||
@@ -114,18 +172,11 @@ public sealed class Engine
|
||||
SceneManager.Render();
|
||||
}
|
||||
|
||||
Renderer.QuadRenderer.Render(projection, view);
|
||||
Renderer.QuadRenderer.Reset();
|
||||
|
||||
Renderer.GlobalMeshRenderer.Render(projection, view);
|
||||
Renderer.GlobalMeshRenderer.Reset();
|
||||
|
||||
Renderer.EndFrame(projection, view);
|
||||
|
||||
Presenter!.Present(Renderer.RenderTexture);
|
||||
Presenter!.Render();
|
||||
|
||||
|
||||
#if DEBUG
|
||||
Debug.RenderDocEndFrame();
|
||||
#endif
|
||||
@@ -144,7 +195,14 @@ public sealed class Engine
|
||||
|
||||
lock (_sceneLock)
|
||||
{
|
||||
SceneManager.Update(deltaTime);
|
||||
try
|
||||
{
|
||||
SceneManager.Update(deltaTime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Exception in scene update");
|
||||
}
|
||||
}
|
||||
|
||||
deltaTime = timer.Elapsed.TotalSeconds;
|
||||
|
||||
@@ -13,6 +13,7 @@ public sealed class EngineBuilder
|
||||
private bool _headless;
|
||||
private int _width = 1;
|
||||
private int _height = 1;
|
||||
private string _assetFolder = "./";
|
||||
|
||||
private Func<Engine, IPresenter>? _presenterFunc;
|
||||
private Func<Engine, IInputHandler>? _inputHandlerFunc;
|
||||
@@ -51,6 +52,12 @@ public sealed class EngineBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public EngineBuilder AssetFolder(string parAssetFolder)
|
||||
{
|
||||
_assetFolder = parAssetFolder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EngineBuilder Presenter(Func<Engine, IPresenter> parPresenterFunc)
|
||||
{
|
||||
_presenterFunc = parPresenterFunc;
|
||||
@@ -94,7 +101,7 @@ public sealed class EngineBuilder
|
||||
public Engine Build()
|
||||
{
|
||||
var logger = BuildLogger();
|
||||
var engine = new Engine(_width, _height, _headless, _title, logger);
|
||||
var engine = new Engine(_width, _height, _headless, _title, _assetFolder, logger);
|
||||
|
||||
var presenter = _presenterFunc?.Invoke(engine);
|
||||
if (presenter != null)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Engine.Graphics.Pixel;
|
||||
using Engine.Graphics.Render.Mesh;
|
||||
using Engine.Graphics.Render.Quad;
|
||||
using Engine.Graphics.Render.Text;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
@@ -8,18 +9,23 @@ namespace Engine.Graphics;
|
||||
|
||||
public class GenericRenderer : IRenderer
|
||||
{
|
||||
public QuadRenderer QuadRenderer => _quadRenderer ??= new QuadRenderer(1024 * 8);
|
||||
public GlobalMeshRenderer GlobalMeshRenderer => _globalMeshRenderer ??= new GlobalMeshRenderer(1024);
|
||||
public QuadRenderer QuadRenderer => _quadRenderer ??= new QuadRenderer(_engine, 1024 * 8);
|
||||
public AnyMeshRenderer AnyMeshRenderer => _globalMeshRenderer ??= new AnyMeshRenderer(_engine, 1024);
|
||||
public TextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8);
|
||||
|
||||
private QuadRenderer? _quadRenderer;
|
||||
private GlobalMeshRenderer? _globalMeshRenderer;
|
||||
private AnyMeshRenderer? _globalMeshRenderer;
|
||||
private TextRenderer? _textRenderer;
|
||||
|
||||
private readonly Engine _engine;
|
||||
internal readonly Framebuffer.Framebuffer _framebuffer;
|
||||
|
||||
private bool _frameStarted;
|
||||
|
||||
public GenericRenderer(int parWidth, int parHeight)
|
||||
public GenericRenderer(Engine parEngine, int parWidth, int parHeight)
|
||||
{
|
||||
_engine = parEngine;
|
||||
|
||||
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
|
||||
.AddColorAttachment<Rgba8>()
|
||||
.AddDepthAttachment()
|
||||
@@ -38,14 +44,17 @@ public class GenericRenderer : IRenderer
|
||||
|
||||
_framebuffer.Bind();
|
||||
|
||||
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
QuadRenderer.Render(parProjectionMatrix, parViewMatrix);
|
||||
QuadRenderer.Reset();
|
||||
|
||||
GlobalMeshRenderer.Render(parProjectionMatrix, parViewMatrix);
|
||||
GlobalMeshRenderer.Reset();
|
||||
AnyMeshRenderer.Render(parProjectionMatrix, parViewMatrix);
|
||||
AnyMeshRenderer.Reset();
|
||||
|
||||
TextRenderer.Render(parProjectionMatrix, parViewMatrix);
|
||||
TextRenderer.Reset();
|
||||
|
||||
_framebuffer.Unbind();
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@ public interface IPresenter : IUpdate, IRender
|
||||
event Action<ResizeEventArgs> Resize;
|
||||
|
||||
void Present(IConstTexture parTexture);
|
||||
void Exit();
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
namespace Engine.Graphics.Pipeline;
|
||||
|
||||
public class RenderLayer
|
||||
public class RenderLayer : IComparable<RenderLayer>
|
||||
{
|
||||
public static readonly RenderLayer DEFAULT = new("default");
|
||||
public static readonly RenderLayer HUD = new("hud");
|
||||
public static readonly RenderLayer OVERLAY = new("overlay");
|
||||
public static readonly RenderLayer DEFAULT = new("default", 0);
|
||||
public static readonly RenderLayer OVERLAY = new("overlay", 1);
|
||||
public static readonly RenderLayer HUD = new("hud", 2);
|
||||
|
||||
public static readonly IReadOnlyList<RenderLayer> ALL = new List<RenderLayer> { DEFAULT, HUD, OVERLAY }.AsReadOnly();
|
||||
public static readonly IReadOnlyList<RenderLayer> ALL = new List<RenderLayer> { DEFAULT, OVERLAY, HUD }.AsReadOnly();
|
||||
|
||||
public string Name { get; }
|
||||
private readonly int _order;
|
||||
|
||||
private RenderLayer(string parName)
|
||||
private RenderLayer(string parName, int parOrder)
|
||||
{
|
||||
Name = parName;
|
||||
_order = parOrder;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -20,6 +22,11 @@ public class RenderLayer
|
||||
return Name;
|
||||
}
|
||||
|
||||
public int CompareTo(RenderLayer? parOther)
|
||||
{
|
||||
return _order.CompareTo(parOther?._order);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name.GetHashCode();
|
||||
|
||||
@@ -50,7 +50,8 @@ public abstract class InstancedRenderer<C, I>
|
||||
return;
|
||||
}
|
||||
|
||||
_instanceVertexBuffer.UploadData(_instanceVertices, _queuedInstanceCount);
|
||||
if (DataChanged())
|
||||
_instanceVertexBuffer.UploadData(_instanceVertices, _queuedInstanceCount);
|
||||
_vertexArray.Bind();
|
||||
|
||||
_program.Bind();
|
||||
@@ -71,4 +72,9 @@ public abstract class InstancedRenderer<C, I>
|
||||
protected virtual void SetAdditionalUniforms(Program parProgram)
|
||||
{
|
||||
}
|
||||
|
||||
protected virtual bool DataChanged()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,23 @@ using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Graphics.Render.Mesh;
|
||||
|
||||
public class GlobalMeshRenderer(int parMaxInstanceCount)
|
||||
public class AnyMeshRenderer(Engine parEngine, int parMaxInstanceCount)
|
||||
{
|
||||
private readonly Dictionary<Asset.Mesh.Mesh, MeshRenderer> _meshRenderers = new();
|
||||
private readonly HashSet<Asset.Mesh.Mesh> _frameMeshes = [];
|
||||
|
||||
private readonly Program _program = ProgramLoader.LoadFromSource(ShaderResource.Mesh);
|
||||
private readonly Program _program = parEngine.EngineResourceManager.Load<Program>("shader/mesh");
|
||||
|
||||
public void Commit(Asset.Mesh.Mesh parMesh, Matrix4 parModelMatrix)
|
||||
public void Commit(Asset.Mesh.Mesh parMesh, Matrix4 parModelMatrix, Texture.Texture? parAlbedo = null)
|
||||
{
|
||||
if (_meshRenderers.TryGetValue(parMesh, out var meshRenderer))
|
||||
{
|
||||
meshRenderer.Commit(parModelMatrix);
|
||||
meshRenderer.Commit(parModelMatrix, parAlbedo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newMeshRenderer = new MeshRenderer(parMesh, parMaxInstanceCount, _program);
|
||||
newMeshRenderer.Commit(parModelMatrix);
|
||||
newMeshRenderer.Commit(parModelMatrix, parAlbedo);
|
||||
|
||||
_meshRenderers.Add(parMesh, newMeshRenderer);
|
||||
}
|
||||
@@ -42,11 +42,11 @@ public class GlobalMeshRenderer(int parMaxInstanceCount)
|
||||
meshRenderer.Reset();
|
||||
}
|
||||
|
||||
var meshes = _meshRenderers.Keys;
|
||||
var unusedMeshes = meshes.Where(parMesh => !_frameMeshes.Contains(parMesh));
|
||||
foreach (var unusedMesh in unusedMeshes)
|
||||
var meshes = _meshRenderers.Keys.ToList();
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
_meshRenderers.Remove(unusedMesh);
|
||||
if (!_frameMeshes.Contains(mesh))
|
||||
_meshRenderers.Remove(mesh);
|
||||
}
|
||||
|
||||
_frameMeshes.Clear();
|
||||
@@ -8,4 +8,9 @@ public struct MeshInstanceVertex : IVertex
|
||||
{
|
||||
[Vertex(VertexAttribType.Int)] public int _textureId;
|
||||
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_textureId, _modelMatrix);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program
|
||||
private readonly TextureUnitMap _textureUnitMap = new(16);
|
||||
private readonly int[] _textureUnitIndices = new int[16];
|
||||
|
||||
private int _frameHash;
|
||||
private int _previousHash;
|
||||
|
||||
public void Commit(Matrix4 parModelMatrix, Texture.Texture? parTexture = null)
|
||||
{
|
||||
if (_queuedInstanceCount >= _instanceCount)
|
||||
@@ -32,6 +35,9 @@ public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program
|
||||
|
||||
_instanceVertices[_queuedInstanceCount]._textureId = textureId;
|
||||
_instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix;
|
||||
|
||||
_frameHash = HashCode.Combine(_frameHash, _instanceVertices[_queuedInstanceCount]);
|
||||
|
||||
_queuedInstanceCount++;
|
||||
}
|
||||
|
||||
@@ -45,4 +51,17 @@ public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program
|
||||
|
||||
parProgram.SetUniform("uTexture", _textureUnitIndices);
|
||||
}
|
||||
|
||||
protected override bool DataChanged()
|
||||
{
|
||||
return _frameHash != _previousHash;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
_textureUnitMap.Reset();
|
||||
_previousHash = _frameHash;
|
||||
_frameHash = 0;
|
||||
}
|
||||
}
|
||||
@@ -9,4 +9,9 @@ public struct QuadInstanceVertex : IVertex
|
||||
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
|
||||
[Vertex(VertexAttribType.Int)] public int _textureId;
|
||||
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_color, _textureId, _modelMatrix);
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,20 @@ namespace Engine.Graphics.Render.Quad;
|
||||
public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVertex>
|
||||
{
|
||||
private readonly TextureUnitMap _textureUnitMap = new(16);
|
||||
|
||||
private readonly int[] _textureUnitIndices = new int[16];
|
||||
|
||||
public QuadRenderer(int parInstanceCount)
|
||||
: base(PrimitiveType.Triangles, parInstanceCount, [0, 1, 2, 2, 3, 0], [
|
||||
new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 1) },
|
||||
new QuadCommonVertex { _position = new Vector3(0.5f, -0.5f, 0), _uv = new Vector2(1, 1) },
|
||||
new QuadCommonVertex { _position = new Vector3(0.5f, 0.5f, 0), _uv = new Vector2(1, 0) },
|
||||
new QuadCommonVertex { _position = new Vector3(-0.5f, 0.5f, 0), _uv = new Vector2(0, 0) }
|
||||
private int _frameHash;
|
||||
private int _previousHash;
|
||||
|
||||
public QuadRenderer(Engine parEngine, int parInstanceCount)
|
||||
: base(PrimitiveType.Triangles, parInstanceCount, [0, 2, 1, 2, 3, 1], [
|
||||
new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 0) },
|
||||
new QuadCommonVertex { _position = new Vector3(-0.5f, 0.5f, 0), _uv = new Vector2(0, 1) },
|
||||
new QuadCommonVertex { _position = new Vector3(0.5f, -0.5f, 0), _uv = new Vector2(1, 0) },
|
||||
new QuadCommonVertex { _position = new Vector3(0.5f, 0.5f, 0), _uv = new Vector2(1, 1) },
|
||||
],
|
||||
ProgramLoader.LoadFromSource(ShaderResource.Quad))
|
||||
parEngine.EngineResourceManager.Load<Program>("shader/quad"))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -37,6 +41,9 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
|
||||
_instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix;
|
||||
_instanceVertices[_queuedInstanceCount]._color = parColor;
|
||||
_instanceVertices[_queuedInstanceCount]._textureId = textureId;
|
||||
|
||||
_frameHash = HashCode.Combine(_frameHash, _instanceVertices[_queuedInstanceCount]);
|
||||
|
||||
_queuedInstanceCount++;
|
||||
}
|
||||
|
||||
@@ -51,9 +58,16 @@ public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVert
|
||||
parProgram.SetUniform("uTexture", _textureUnitIndices);
|
||||
}
|
||||
|
||||
protected override bool DataChanged()
|
||||
{
|
||||
return _frameHash != _previousHash;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
_textureUnitMap.Reset();
|
||||
_previousHash = _frameHash;
|
||||
_frameHash = 0;
|
||||
}
|
||||
}
|
||||
13
Engine/src/Graphics/Render/Text/GlyphCommonVertex.cs
Normal file
13
Engine/src/Graphics/Render/Text/GlyphCommonVertex.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Engine.Graphics.Buffer.Vertex;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Graphics.Render.Text;
|
||||
|
||||
public struct GlyphCommonVertex : IVertex
|
||||
{
|
||||
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
|
||||
[Vertex(VertexAttribType.Int)] public int _atlasId;
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _unitRange;
|
||||
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
|
||||
}
|
||||
15
Engine/src/Graphics/Render/Text/GlyphVertex.cs
Normal file
15
Engine/src/Graphics/Render/Text/GlyphVertex.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Engine.Graphics.Buffer.Vertex;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Graphics.Render.Text;
|
||||
|
||||
public struct GlyphVertex : IVertex
|
||||
{
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _position;
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _uv;
|
||||
[Vertex(VertexAttribType.Float, 4)] public Vector4 _color;
|
||||
[Vertex(VertexAttribType.Int)] public int _atlasId;
|
||||
[Vertex(VertexAttribType.Float, 2)] public Vector2 _unitRange;
|
||||
[Vertex(VertexAttribType.Float, 4, 4)] public Matrix4 _modelMatrix;
|
||||
}
|
||||
125
Engine/src/Graphics/Render/Text/TextRenderer.cs
Normal file
125
Engine/src/Graphics/Render/Text/TextRenderer.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Engine.Asset.Font;
|
||||
using Engine.Graphics.Buffer;
|
||||
using Engine.Graphics.Shader;
|
||||
using Engine.Graphics.Texture;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Graphics.Render.Text;
|
||||
|
||||
public class TextRenderer
|
||||
{
|
||||
private readonly IndexBuffer _indexBuffer;
|
||||
private readonly VertexArray _vertexArray;
|
||||
|
||||
private readonly VertexBuffer<GlyphVertex> _glyphVertexBuffer;
|
||||
// private readonly VertexBuffer<GlyphCommonVertex> _glyphCommonVertexBuffer;
|
||||
|
||||
private readonly Program _program;
|
||||
private readonly int _characterCount;
|
||||
private int _queuedCharacterCount;
|
||||
|
||||
private readonly TextureUnitMap _textureUnitMap = new(16);
|
||||
private readonly int[] _textureUnitIndices = new int[16];
|
||||
|
||||
private readonly GlyphVertex[] _glyphVertices;
|
||||
// private readonly GlyphCommonVertex[] _glyphCommonVertices;
|
||||
|
||||
public TextRenderer(Engine parEngine, int parCharacterCount)
|
||||
{
|
||||
_characterCount = parCharacterCount;
|
||||
_glyphVertices = new GlyphVertex[parCharacterCount * 4];
|
||||
// _glyphCommonVertices = new GlyphCommonVertex[parCharacterCount];
|
||||
|
||||
_program = parEngine.EngineResourceManager.Load<Program>("shader/text");
|
||||
|
||||
_indexBuffer = new IndexBuffer(CreateIndices(_characterCount));
|
||||
_glyphVertexBuffer = new VertexBuffer<GlyphVertex>(_characterCount * 4,
|
||||
BufferStorageFlags.DynamicStorageBit);
|
||||
// _glyphCommonVertexBuffer = new VertexBuffer<GlyphCommonVertex>(_characterCount,
|
||||
// BufferStorageFlags.DynamicStorageBit);
|
||||
|
||||
_vertexArray = new VertexArray();
|
||||
|
||||
_vertexArray.BindIndexBuffer(_indexBuffer);
|
||||
_vertexArray.BindVertexBuffer(_glyphVertexBuffer, 0, 0);
|
||||
// _vertexArray.BindVertexBuffer(_glyphCommonVertexBuffer, 1, 1);
|
||||
}
|
||||
|
||||
public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix)
|
||||
{
|
||||
if (_queuedCharacterCount >= _characterCount)
|
||||
{
|
||||
throw new InvalidOperationException("Character count exceeded");
|
||||
}
|
||||
|
||||
var textureId = _textureUnitMap.GetUnit(parFont.AtlasTexture);
|
||||
|
||||
var fontIterator = parFont.Iterator(parText);
|
||||
|
||||
foreach (var glyphData in fontIterator)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
_glyphVertices[_queuedCharacterCount * 4 + i]._position = glyphData.Positions[i] + glyphData.Offset;
|
||||
_glyphVertices[_queuedCharacterCount * 4 + i]._uv = glyphData.UVs[i];
|
||||
_glyphVertices[_queuedCharacterCount * 4 + i]._color = parColor;
|
||||
_glyphVertices[_queuedCharacterCount * 4 + i]._atlasId = textureId;
|
||||
_glyphVertices[_queuedCharacterCount * 4 + i]._unitRange = parFont.UnitRange;
|
||||
_glyphVertices[_queuedCharacterCount * 4 + i]._modelMatrix = parModelMatrix;
|
||||
}
|
||||
|
||||
_queuedCharacterCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
|
||||
{
|
||||
if (_queuedCharacterCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_glyphVertexBuffer.UploadData(_glyphVertices, _queuedCharacterCount * 4);
|
||||
// _glyphCommonVertexBuffer.UploadData(_glyphCommonVertices, _queuedCharacterCount);
|
||||
_vertexArray.Bind();
|
||||
|
||||
_program.Bind();
|
||||
_program.SetUniform("u_Projection", in parProjectionMatrix);
|
||||
_program.SetUniform("u_View", in parViewMatrix);
|
||||
foreach (var (texture, unit) in _textureUnitMap.Textures)
|
||||
{
|
||||
texture.BindUnit(unit);
|
||||
_textureUnitIndices[unit] = unit;
|
||||
}
|
||||
|
||||
_program.SetUniform("u_Atlas", _textureUnitIndices);
|
||||
|
||||
GL.DrawElements(PrimitiveType.Triangles, _queuedCharacterCount * 6, DrawElementsType.UnsignedInt, 0);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_textureUnitMap.Reset();
|
||||
_queuedCharacterCount = 0;
|
||||
}
|
||||
|
||||
private static uint[] CreateIndices(int parCharacterCount)
|
||||
{
|
||||
var indices = new uint[parCharacterCount * 6];
|
||||
|
||||
for (var i = 0; i < parCharacterCount; i++)
|
||||
{
|
||||
var index = i * 6;
|
||||
var offset = i * 4;
|
||||
indices[index + 0] = (uint)offset + 0;
|
||||
indices[index + 1] = (uint)offset + 2;
|
||||
indices[index + 2] = (uint)offset + 1;
|
||||
indices[index + 3] = (uint)offset + 2;
|
||||
indices[index + 4] = (uint)offset + 3;
|
||||
indices[index + 5] = (uint)offset + 1;
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using Engine.Graphics.Pipeline;
|
||||
using System.Collections.Generic;
|
||||
using Engine.Graphics.Pipeline;
|
||||
using Engine.Graphics.Pixel;
|
||||
using Engine.Graphics.Render.Mesh;
|
||||
using Engine.Graphics.Render.Quad;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using Serilog;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
@@ -13,12 +14,22 @@ public class Renderer
|
||||
internal Framebuffer.Framebuffer RenderFramebuffer => _framebuffer;
|
||||
internal Texture.Texture RenderTexture => _framebuffer.TextureInternal!;
|
||||
|
||||
public QuadRenderer QuadRenderer { get; }
|
||||
public GlobalMeshRenderer GlobalMeshRenderer { get; }
|
||||
private QuadRenderer QuadRenderer { get; }
|
||||
public int ViewportWidth => _framebuffer.Width;
|
||||
public int ViewportHeight => _framebuffer.Height;
|
||||
|
||||
private readonly Dictionary<RenderLayer, GenericRenderer> _renderers = new();
|
||||
private readonly SortedDictionary<RenderLayer, GenericRenderer> _renderers = new();
|
||||
|
||||
public GenericRenderer this[RenderLayer parRenderLayer]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_renderers.TryGetValue(parRenderLayer, out var renderer))
|
||||
return renderer;
|
||||
|
||||
throw new InvalidOperationException($"Renderer for layer {parRenderLayer} not found");
|
||||
}
|
||||
}
|
||||
|
||||
internal NativeWindow NativeWindow { get; }
|
||||
|
||||
@@ -27,9 +38,10 @@ public class Renderer
|
||||
|
||||
private readonly Queue<Action> _scheduleActions = new();
|
||||
|
||||
public Renderer(int parWidth, int parHeight, NativeWindowSettings parSettings)
|
||||
public Renderer(Engine parEngine, int parWidth, int parHeight, NativeWindowSettings parSettings)
|
||||
{
|
||||
Thread.CurrentThread.Name = "RendererThread";
|
||||
_renderThread = Thread.CurrentThread;
|
||||
_renderThread.Name = "RendererThread";
|
||||
|
||||
#if DEBUG
|
||||
Debug.InitializeRenderDoc();
|
||||
@@ -39,15 +51,16 @@ public class Renderer
|
||||
|
||||
InitializeOpenGl(parWidth, parHeight);
|
||||
|
||||
_renderThread = Thread.CurrentThread;
|
||||
|
||||
_framebuffer = Framebuffer.Framebuffer.Builder(parWidth, parHeight)
|
||||
.AddColorAttachment<Rgb8>()
|
||||
.AddDepthAttachment()
|
||||
.AddColorAttachment<Rgba8>()
|
||||
.Build();
|
||||
|
||||
QuadRenderer = new QuadRenderer(1024 * 8);
|
||||
GlobalMeshRenderer = new GlobalMeshRenderer(1024);
|
||||
QuadRenderer = new QuadRenderer(parEngine, 1);
|
||||
|
||||
foreach (var layer in RenderLayer.ALL)
|
||||
{
|
||||
_renderers.Add(layer, new GenericRenderer(parEngine, parWidth, parHeight));
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeOpenGl(int parWidth, int parHeight)
|
||||
@@ -57,6 +70,7 @@ public class Renderer
|
||||
#endif
|
||||
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
// GL.Enable(EnableCap.CullFace);
|
||||
|
||||
// GL.Enable(EnableCap.FramebufferSrgb);
|
||||
|
||||
@@ -81,39 +95,63 @@ public class Renderer
|
||||
_scheduleActions.Enqueue(parAction);
|
||||
}
|
||||
|
||||
internal Task<T> Schedule<T>(Func<T> parAction)
|
||||
{
|
||||
var completionSource = new TaskCompletionSource<T>();
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
completionSource.SetResult(parAction());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
completionSource.SetException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
internal void StartFrame()
|
||||
{
|
||||
EnsureRenderThread();
|
||||
|
||||
RunScheduledActions();
|
||||
|
||||
_framebuffer.Bind();
|
||||
|
||||
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
// foreach (var renderer in _renderers.Values)
|
||||
// {
|
||||
// renderer.StartFrame();
|
||||
// }
|
||||
foreach (var renderer in _renderers.Values)
|
||||
{
|
||||
renderer.StartFrame();
|
||||
}
|
||||
}
|
||||
|
||||
internal void EndFrame(in Matrix4 parViewMatrix, in Matrix4 parProjectionMatrix)
|
||||
{
|
||||
EnsureRenderThread();
|
||||
|
||||
// foreach (var renderer in _renderers.Values)
|
||||
// {
|
||||
// renderer.EndFrame(in parViewMatrix, in parProjectionMatrix);
|
||||
// }
|
||||
//
|
||||
// foreach (var renderer in _renderers.Values)
|
||||
// {
|
||||
// QuadRenderer.Commit(Matrix4.CreateScale(2f), Vector4.One, renderer._framebuffer.TextureInternal);
|
||||
// }
|
||||
//
|
||||
// QuadRenderer.Render(in parProjectionMatrix, in parViewMatrix);
|
||||
// QuadRenderer.Reset();
|
||||
foreach (var renderer in _renderers.Values)
|
||||
{
|
||||
renderer.EndFrame(in parViewMatrix, in parProjectionMatrix);
|
||||
}
|
||||
|
||||
_framebuffer.Bind();
|
||||
|
||||
// GL.Disable(EnableCap.DepthTest);
|
||||
// GL.Disable(EnableCap.CullFace);
|
||||
|
||||
GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
foreach (var renderer in _renderers.Values)
|
||||
{
|
||||
QuadRenderer.Commit(Matrix4.CreateScale(2f, -2f, 1f), Vector4.One, renderer._framebuffer.TextureInternal);
|
||||
QuadRenderer.Render(Matrix4.Identity, Matrix4.Identity);
|
||||
QuadRenderer.Reset();
|
||||
}
|
||||
|
||||
// GL.Enable(EnableCap.DepthTest);
|
||||
// GL.Enable(EnableCap.CullFace);
|
||||
|
||||
_framebuffer.Unbind();
|
||||
}
|
||||
@@ -122,6 +160,11 @@ public class Renderer
|
||||
{
|
||||
_framebuffer.Resize(parWidth, parHeight);
|
||||
GL.Viewport(0, 0, parWidth, parHeight);
|
||||
|
||||
foreach (var renderer in _renderers.Values)
|
||||
{
|
||||
renderer.Resize(parWidth, parHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void RunScheduledActions()
|
||||
|
||||
@@ -125,6 +125,8 @@ public class Program : OpenGlObject
|
||||
if (linkStatus == 0)
|
||||
{
|
||||
var log = GL.GetProgramInfoLog(programId);
|
||||
GL.DeleteProgram(programId);
|
||||
|
||||
throw new ShaderLinkException(log);
|
||||
}
|
||||
|
||||
@@ -133,11 +135,15 @@ public class Program : OpenGlObject
|
||||
if (validateStatus == 0)
|
||||
{
|
||||
var log = GL.GetProgramInfoLog(programId);
|
||||
GL.DeleteProgram(programId);
|
||||
|
||||
throw new ShaderValidationException(log);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
GL.DetachShader(programId, parVertexShader);
|
||||
GL.DetachShader(programId, parFragmentShader);
|
||||
GL.DeleteShader(parVertexShader);
|
||||
GL.DeleteShader(parFragmentShader);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,6 @@ public abstract class Texture : OpenGlObject, ITexture
|
||||
|
||||
public void BindUnit(int parUnit = 0)
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0 + parUnit);
|
||||
GL.BindTextureUnit(parUnit, Handle);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using Engine.Scene;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Input;
|
||||
|
||||
public interface IInputHandler : IUpdate
|
||||
{
|
||||
Vector2 MousePosition { get; }
|
||||
|
||||
bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode);
|
||||
bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode);
|
||||
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace Engine.Input;
|
||||
|
||||
public class WindowInputHandler(Window parWindow) : IInputHandler
|
||||
{
|
||||
public Vector2 MousePosition => parWindow.NativeWindow.MouseState.Position;
|
||||
|
||||
private KeyboardState _previousKeyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
|
||||
private KeyboardState _keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
|
||||
|
||||
private MouseState _previousMouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
|
||||
private MouseState _mouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
|
||||
|
||||
public void Update(double parDeltaTime)
|
||||
{
|
||||
_previousKeyboardState = _keyboardState;
|
||||
_keyboardState = parWindow.NativeWindow.KeyboardState.GetSnapshot();
|
||||
|
||||
_previousMouseState = _mouseState;
|
||||
_mouseState = parWindow.NativeWindow.MouseState.GetSnapshot();
|
||||
}
|
||||
|
||||
public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode)
|
||||
@@ -26,12 +35,13 @@ public class WindowInputHandler(Window parWindow) : IInputHandler
|
||||
|
||||
public bool IsMouseButtonPressed(MouseButtonCode parButtonCode)
|
||||
{
|
||||
return parWindow.NativeWindow.MouseState.IsButtonDown(MapMouseButtonCode(parButtonCode));
|
||||
return _mouseState.IsButtonDown(MapMouseButtonCode(parButtonCode));
|
||||
}
|
||||
|
||||
public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode)
|
||||
{
|
||||
return parWindow.NativeWindow.MouseState.IsButtonPressed(MapMouseButtonCode(parButtonCode));
|
||||
return _mouseState.IsButtonDown(MapMouseButtonCode(parButtonCode)) &&
|
||||
!_previousMouseState.IsButtonDown(MapMouseButtonCode(parButtonCode));
|
||||
}
|
||||
|
||||
private static MouseButton MapMouseButtonCode(MouseButtonCode parButton)
|
||||
|
||||
9
Engine/src/Resource/FilesystemResourceStreamProvider.cs
Normal file
9
Engine/src/Resource/FilesystemResourceStreamProvider.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public class FilesystemResourceStreamProvider(string parBasePath) : IResourceStreamProvider
|
||||
{
|
||||
public Stream GetStream(string parPath)
|
||||
{
|
||||
return File.OpenRead(Path.Combine(parBasePath, parPath));
|
||||
}
|
||||
}
|
||||
6
Engine/src/Resource/IResourceLoader.cs
Normal file
6
Engine/src/Resource/IResourceLoader.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public interface IResourceLoader
|
||||
{
|
||||
object Load(string parPath, IResourceStreamProvider parStreamProvider);
|
||||
}
|
||||
6
Engine/src/Resource/IResourceManager.cs
Normal file
6
Engine/src/Resource/IResourceManager.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public interface IResourceManager
|
||||
{
|
||||
T Load<T>(string parPath) where T : class;
|
||||
}
|
||||
6
Engine/src/Resource/IResourceStreamProvider.cs
Normal file
6
Engine/src/Resource/IResourceStreamProvider.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public interface IResourceStreamProvider
|
||||
{
|
||||
Stream GetStream(string parPath);
|
||||
}
|
||||
26
Engine/src/Resource/Loader/FontLoader.cs
Normal file
26
Engine/src/Resource/Loader/FontLoader.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Text.Json;
|
||||
using Engine.Asset.Font;
|
||||
using Engine.Asset.Font.Metadata;
|
||||
|
||||
namespace Engine.Resource.Loader;
|
||||
|
||||
public class FontLoader : IResourceLoader
|
||||
{
|
||||
public object Load(string parPath, IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
var metadataPath = Path.Combine(parPath, "metadata.json");
|
||||
using var metadataStream = parStreamProvider.GetStream(metadataPath);
|
||||
var metadata = JsonSerializer.Deserialize<Metadata>(metadataStream);
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to load metadata from {metadataPath}");
|
||||
}
|
||||
|
||||
var atlasPath = Path.Combine(parPath, "atlas.png");
|
||||
using var atlasStream = parStreamProvider.GetStream(atlasPath);
|
||||
var atlasTexture = ImageLoader.Load(atlasStream).ToStaticTexture();
|
||||
|
||||
return new Font(atlasTexture, metadata);
|
||||
}
|
||||
}
|
||||
35
Engine/src/Resource/Loader/ImageLoader.cs
Normal file
35
Engine/src/Resource/Loader/ImageLoader.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Engine.Graphics.Pixel;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Engine.Resource.Loader;
|
||||
|
||||
public class ImageLoader : IResourceLoader
|
||||
{
|
||||
public object Load(string parPath, IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
using var stream = parStreamProvider.GetStream(parPath);
|
||||
return Load(stream);
|
||||
}
|
||||
|
||||
internal static Asset.Image<Rgba8> Load(Stream parStream)
|
||||
{
|
||||
var sharpImage = SixLabors.ImageSharp.Image.Load<Rgba32>(parStream);
|
||||
if (sharpImage == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to load image from stream");
|
||||
}
|
||||
|
||||
sharpImage.Mutate(parImageContext => parImageContext.Flip(FlipMode.Vertical));
|
||||
|
||||
var image = new Asset.Image<Rgba8>(sharpImage.Width, sharpImage.Height);
|
||||
var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(image.Pixels),
|
||||
image.Pixels.Length * Marshal.SizeOf<Rgba8>());
|
||||
sharpImage.CopyPixelDataTo(span);
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
19
Engine/src/Resource/Loader/MeshLoader.cs
Normal file
19
Engine/src/Resource/Loader/MeshLoader.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Engine.Asset.Mesh.Loader;
|
||||
|
||||
namespace Engine.Resource.Loader;
|
||||
|
||||
public class MeshLoader : IResourceLoader
|
||||
{
|
||||
public object Load(string parPath, IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
var extension = Path.GetExtension(parPath);
|
||||
using var reader = new StreamReader(parStreamProvider.GetStream(parPath));
|
||||
|
||||
return extension switch
|
||||
{
|
||||
".obj" => ObjMeshLoader.Load(reader),
|
||||
".stl" => StlMeshLoader.Load(reader),
|
||||
_ => throw new InvalidOperationException($"Unsupported mesh format: {extension}")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Engine.Graphics.Shader;
|
||||
|
||||
namespace Engine.Graphics.Shader;
|
||||
namespace Engine.Resource.Loader;
|
||||
|
||||
public static partial class ProgramLoader
|
||||
public partial class ProgramLoader : IResourceLoader
|
||||
{
|
||||
[GeneratedRegex(@"^//\s+#type\s+(?<type>[a-z]+)$", RegexOptions.Compiled)]
|
||||
private static partial Regex TypeRegex();
|
||||
|
||||
public static Program LoadFromSource(string parSource)
|
||||
public object Load(string parPath, IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
var textReader = new StreamReader(parStreamProvider.GetStream(parPath));
|
||||
|
||||
var vertexSource = new StringBuilder();
|
||||
var fragmentSource = new StringBuilder();
|
||||
var inFragment = false;
|
||||
var inVertex = false;
|
||||
|
||||
foreach (var line in parSource.Split('\n').Select(parLine => parLine.TrimEnd()))
|
||||
while (textReader.ReadLine() is { } line)
|
||||
{
|
||||
var match = TypeRegex().Match(line);
|
||||
if (match.Success)
|
||||
12
Engine/src/Resource/Loader/TextureLoader.cs
Normal file
12
Engine/src/Resource/Loader/TextureLoader.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Engine.Resource.Loader;
|
||||
|
||||
public class TextureLoader : IResourceLoader
|
||||
{
|
||||
public object Load(string parPath, IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
using var stream = parStreamProvider.GetStream(parPath);
|
||||
var image = ImageLoader.Load(stream);
|
||||
|
||||
return image.ToStaticTexture();
|
||||
}
|
||||
}
|
||||
16
Engine/src/Resource/MemoryResourceStreamProvider.cs
Normal file
16
Engine/src/Resource/MemoryResourceStreamProvider.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public class MemoryResourceStreamProvider : IResourceStreamProvider
|
||||
{
|
||||
private readonly Dictionary<string, byte[]> _resources = new();
|
||||
|
||||
public Stream GetStream(string parPath)
|
||||
{
|
||||
return new MemoryStream(_resources[parPath]);
|
||||
}
|
||||
|
||||
internal void AddResource(string parPath, byte[] parData)
|
||||
{
|
||||
_resources.Add(parPath, parData);
|
||||
}
|
||||
}
|
||||
36
Engine/src/Resource/ResourceHandle.cs
Normal file
36
Engine/src/Resource/ResourceHandle.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public class ResourceHandle<T>
|
||||
{
|
||||
public T? Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isLoaded)
|
||||
{
|
||||
return _value!;
|
||||
}
|
||||
|
||||
if (!_task.IsCompleted)
|
||||
{
|
||||
return _defaultValue;
|
||||
}
|
||||
|
||||
_value = _task.Result;
|
||||
_isLoaded = true;
|
||||
|
||||
return _value!;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly T? _defaultValue;
|
||||
private readonly Task<T> _task;
|
||||
private T? _value;
|
||||
private bool _isLoaded;
|
||||
|
||||
public ResourceHandle(Task<T> parTask, T? parDefaultValue)
|
||||
{
|
||||
_defaultValue = parDefaultValue;
|
||||
_task = parTask;
|
||||
}
|
||||
}
|
||||
71
Engine/src/Resource/ResourceManager.cs
Normal file
71
Engine/src/Resource/ResourceManager.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
namespace Engine.Resource;
|
||||
|
||||
public class ResourceManager : IResourceManager
|
||||
{
|
||||
internal IResourceStreamProvider StreamProvider => _streamProvider;
|
||||
|
||||
private readonly IResourceStreamProvider _streamProvider;
|
||||
private readonly Dictionary<Type, IResourceLoader> _loaders = new();
|
||||
private readonly Dictionary<Type, ResourceStorage> _storages = new();
|
||||
|
||||
public ResourceManager(IResourceStreamProvider parStreamProvider)
|
||||
{
|
||||
_streamProvider = parStreamProvider;
|
||||
}
|
||||
|
||||
internal void RegisterLoader<T>(IResourceLoader parLoader) where T : class
|
||||
{
|
||||
_loaders.Add(typeof(T), parLoader);
|
||||
}
|
||||
|
||||
public T Load<T>(string parPath) where T : class
|
||||
{
|
||||
if (!_storages.TryGetValue(typeof(T), out var storage))
|
||||
{
|
||||
storage = new ResourceStorage();
|
||||
_storages.Add(typeof(T), storage);
|
||||
}
|
||||
|
||||
if (storage.Get<T>(parPath) is { } cachedResource)
|
||||
{
|
||||
return cachedResource;
|
||||
}
|
||||
|
||||
var loader = _loaders.GetValueOrDefault(typeof(T));
|
||||
if (loader == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No loader found for type {typeof(T)}");
|
||||
}
|
||||
|
||||
var resource = loader.Load(parPath, _streamProvider);
|
||||
|
||||
storage.Add(parPath, resource);
|
||||
|
||||
return (T)resource;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
_storages.Clear();
|
||||
}
|
||||
|
||||
private class ResourceStorage
|
||||
{
|
||||
private readonly Dictionary<string, object> _resources = new();
|
||||
|
||||
public void Add<T>(string parPath, T parResource) where T : class
|
||||
{
|
||||
_resources.Add(parPath, parResource);
|
||||
}
|
||||
|
||||
public T? Get<T>(string parPath) where T : class
|
||||
{
|
||||
return _resources.TryGetValue(parPath, out var resource) ? (T)resource : null;
|
||||
}
|
||||
|
||||
public void Remove(string parPath)
|
||||
{
|
||||
_resources.Remove(parPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Engine/src/Resource/ShaderResource.Designer.cs
generated
35
Engine/src/Resource/ShaderResource.Designer.cs
generated
@@ -58,34 +58,7 @@ namespace Engine {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to #shader vertex
|
||||
///#version 330 core
|
||||
///
|
||||
///uniform mat4 uViewMatrix;
|
||||
///uniform mat4 uProjectionMatrix;
|
||||
///
|
||||
///layout(location = 0) in vec3 aPos;
|
||||
///layout(location = 1) in vec3 aNormal;
|
||||
///layout(location = 2) in vec2 aTexCoords;
|
||||
///layout(location = 3) in mat4 aModelMatrix;
|
||||
///
|
||||
///out vec2 outTexCoords;
|
||||
///
|
||||
///void main()
|
||||
///{
|
||||
/// gl_Position = uProjectionMatrix * uViewMatrix * aModelMatrix * vec4(aPos, 1.0);
|
||||
/// outTexCoords = aTexCoords;
|
||||
///}
|
||||
///
|
||||
///#shader fragment
|
||||
///#version 330 core
|
||||
///
|
||||
///uniform sampler2D uTexture;
|
||||
///
|
||||
///layout(location [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
|
||||
internal static string Mesh {
|
||||
get {
|
||||
return ResourceManager.GetString("Mesh", resourceCulture);
|
||||
@@ -97,5 +70,11 @@ namespace Engine {
|
||||
return ResourceManager.GetString("Quad", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Text {
|
||||
get {
|
||||
return ResourceManager.GetString("Text", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,8 @@
|
||||
<data name="Quad" type="System.Resources.ResXFileRef" xml:space="preserve">
|
||||
<value>../../assets/shader/quad.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8</value>
|
||||
</data>
|
||||
|
||||
<data name="Text" type="System.Resources.ResXFileRef" xml:space="preserve">
|
||||
<value>../../assets/shader/text.shader;System.String, mscorlib, Version=4.0.0.0, Culture=neutral;utf-8</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -13,28 +13,62 @@ public class OrthographicCamera(
|
||||
{
|
||||
public Axis FixedAxis { get; set; } = parAxis;
|
||||
public float Size { get; set; } = parSize;
|
||||
public bool UseScreenSize { get; set; } = false;
|
||||
|
||||
public override Matrix4 View => GameObject.Transform.TransformMatrix.Inverted();
|
||||
|
||||
public override Matrix4 Projection => FixedAxis == Axis.X
|
||||
? Matrix4.CreateOrthographic(Size, -Size / AspectRatio, NearPlane, FarPlane)
|
||||
: Matrix4.CreateOrthographic(Size * AspectRatio, -Size, NearPlane, FarPlane);
|
||||
public override Matrix4 Projection => GetProjectionMatrix();
|
||||
|
||||
private Matrix4 GetProjectionMatrix()
|
||||
{
|
||||
var size = GameObject.Transform.Size.Xy;
|
||||
return Matrix4.CreateOrthographic(size.X, size.Y, -NearPlane, -FarPlane);
|
||||
}
|
||||
|
||||
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
|
||||
{
|
||||
var offset = FixedAxis == Axis.X
|
||||
? new Vector2(Size, Size / AspectRatio)
|
||||
: new Vector2(Size * AspectRatio, Size);
|
||||
offset /= 2;
|
||||
var normalized = parScreenPosition / ScreenSize - new Vector2(0.5f);
|
||||
normalized.X *= 2;
|
||||
normalized.Y *= -2;
|
||||
|
||||
return new Vector4(parScreenPosition.X - offset.X, parScreenPosition.Y - offset.Y, 0, 1)
|
||||
return new Vector4(normalized.X, normalized.Y, 0, 1)
|
||||
.MulProject(Projection.Inverted())
|
||||
.MulProject(GameObject.Transform.TransformMatrix)
|
||||
.Xyz;
|
||||
}
|
||||
|
||||
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var normalized = new Vector4(parWorldPosition, 1)
|
||||
.MulProject(GameObject.Transform.TransformMatrix.Inverted())
|
||||
.MulProject(Projection)
|
||||
.Xy;
|
||||
|
||||
normalized.X /= 2;
|
||||
normalized.Y /= -2;
|
||||
|
||||
return (normalized + new Vector2(0.5f)) * ScreenSize;
|
||||
}
|
||||
|
||||
public override void Update(double parDeltaTime)
|
||||
{
|
||||
if (UseScreenSize)
|
||||
{
|
||||
GameObject.Transform.Size.Xy = ScreenSize.ToVector2();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FixedAxis == Axis.X)
|
||||
{
|
||||
GameObject.Transform.Size.X = Size;
|
||||
GameObject.Transform.Size.Y = Size / AspectRatio;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject.Transform.Size.X = Size * AspectRatio;
|
||||
GameObject.Transform.Size.Y = Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Axis
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Engine.Util;
|
||||
using OpenTK.Mathematics;
|
||||
using Math = System.Math;
|
||||
|
||||
namespace Engine.Scene.Component.BuiltIn;
|
||||
|
||||
public class PerspectiveCamera(
|
||||
float parFieldOfView = 90.0f,
|
||||
float parFieldOfView = 60.0f,
|
||||
float parNearPlane = 0.01f,
|
||||
float parFarPlane = 1000f
|
||||
)
|
||||
@@ -18,9 +17,9 @@ public class PerspectiveCamera(
|
||||
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();
|
||||
var forward = new Vector4(0, 1, 0, 1).MulProject(in transformMatrix);
|
||||
var eye = new Vector4(0, 0, 0, 1).MulProject(in transformMatrix);
|
||||
var up = (new Vector4(0, 0, 1, 1).MulProject(in transformMatrix) - eye).Normalized();
|
||||
|
||||
return Matrix4.LookAt(eye.Xyz, forward.Xyz, up.Xyz);
|
||||
}
|
||||
@@ -32,11 +31,26 @@ public class PerspectiveCamera(
|
||||
|
||||
public override Vector3 ScreenToWorld(Vector2 parScreenPosition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var normalized = parScreenPosition / ScreenSize - new Vector2(0.5f);
|
||||
normalized.X *= 2;
|
||||
normalized.Y *= -2;
|
||||
|
||||
return new Vector4(normalized.X, normalized.Y, 0, 1)
|
||||
.MulProject(Projection.Inverted())
|
||||
.MulProject(GameObject.Transform.TransformMatrix)
|
||||
.Xyz;
|
||||
}
|
||||
|
||||
public override Vector2 WorldToScreen(Vector3 parWorldPosition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var normalized = new Vector4(parWorldPosition, 1)
|
||||
.MulProject(GameObject.Transform.TransformMatrix.Inverted())
|
||||
.MulProject(Projection)
|
||||
.Xy;
|
||||
|
||||
normalized.X /= 2;
|
||||
normalized.Y /= -2;
|
||||
|
||||
return (normalized + new Vector2(0.5f)) * ScreenSize;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Engine.Graphics.Texture;
|
||||
using Engine.Graphics.Pipeline;
|
||||
using Engine.Graphics.Texture;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Scene.Component.BuiltIn.Renderer;
|
||||
@@ -7,11 +8,13 @@ public class Box2DRenderer : Component
|
||||
{
|
||||
public ref Vector4 Color => ref _color;
|
||||
public Texture? Texture { get; set; } = null;
|
||||
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
|
||||
|
||||
private Vector4 _color = Vector4.One;
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
Engine.Instance.Renderer.QuadRenderer.Commit(GameObject.Transform.TransformMatrix, Color, Texture);
|
||||
Engine.Instance.Renderer[RenderLayer].QuadRenderer
|
||||
.Commit(GameObject.Transform.FullTransformMatrix, Color, Texture);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
using Engine.Asset.Mesh;
|
||||
using Engine.Graphics.Pipeline;
|
||||
using Engine.Graphics.Texture;
|
||||
|
||||
namespace Engine.Scene.Component.BuiltIn.Renderer;
|
||||
|
||||
public class MeshRenderer : Component
|
||||
{
|
||||
public Mesh Mesh { get; set; } = null!;
|
||||
public Texture? Albedo { get; set; } = null;
|
||||
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
Engine.Instance.Renderer.GlobalMeshRenderer.Commit(Mesh, GameObject.Transform.TransformMatrix);
|
||||
Engine.Instance.Renderer[RenderLayer].AnyMeshRenderer
|
||||
.Commit(Mesh, GameObject.Transform.FullTransformMatrix, Albedo);
|
||||
}
|
||||
}
|
||||
25
Engine/src/Scene/Component/BuiltIn/Renderer/TextRenderer.cs
Normal file
25
Engine/src/Scene/Component/BuiltIn/Renderer/TextRenderer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Engine.Asset.Font;
|
||||
using Engine.Graphics.Pipeline;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Engine.Scene.Component.BuiltIn.Renderer;
|
||||
|
||||
public class TextRenderer : Component
|
||||
{
|
||||
public Font Font { get; set; } = null!;
|
||||
public string? Text { get; set; }
|
||||
public ref Vector4 Color => ref _color;
|
||||
|
||||
public RenderLayer RenderLayer { get; set; } = RenderLayer.DEFAULT;
|
||||
|
||||
private Vector4 _color = Vector4.One;
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
if (Text == null)
|
||||
return;
|
||||
|
||||
Engine.Instance.Renderer[RenderLayer].TextRenderer
|
||||
.Commit(Font, Text, Color, GameObject.Transform.FullTransformMatrix);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@ public class Transform : Component
|
||||
private Vector3 _translation = Vector3.Zero;
|
||||
private Quaternion _rotation = Quaternion.Identity;
|
||||
private Vector3 _scale = Vector3.One;
|
||||
private Vector3 _localScale = Vector3.One;
|
||||
private Vector3 _size = Vector3.One;
|
||||
|
||||
public ref Vector3 Translation => ref _translation;
|
||||
public ref Quaternion Rotation => ref _rotation;
|
||||
public ref Vector3 Scale => ref _scale;
|
||||
public ref Vector3 LocalScale => ref _localScale;
|
||||
public ref Vector3 Size => ref _size;
|
||||
|
||||
public Matrix4 LocalTransformMatrix => Matrix4.CreateScale(Scale) *
|
||||
Matrix4.CreateFromQuaternion(Rotation) *
|
||||
@@ -20,6 +20,8 @@ public class Transform : Component
|
||||
|
||||
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
|
||||
|
||||
public Matrix4 FullTransformMatrix => Matrix4.CreateScale(Size) * TransformMatrix;
|
||||
|
||||
private Matrix4 ParentTransformMatrix
|
||||
{
|
||||
get
|
||||
@@ -32,7 +34,7 @@ public class Transform : Component
|
||||
public Transform Clone()
|
||||
{
|
||||
var clone =
|
||||
new Transform { Translation = Translation, Rotation = Rotation, Scale = Scale, LocalScale = LocalScale };
|
||||
new Transform { Translation = Translation, Rotation = Rotation, Scale = Scale, Size = Size };
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Scene : IUpdate, IRender
|
||||
|
||||
public T? FindFirstComponent<T>() where T : Component.Component
|
||||
{
|
||||
return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>()).FirstOrDefault();
|
||||
return Hierarchy.Objects.Select(parGameObject => parGameObject.GetComponent<T>()).FirstOrDefault(parComponent => parComponent != null);
|
||||
}
|
||||
|
||||
public void Update(double parDeltaTime)
|
||||
@@ -87,10 +87,15 @@ public class Scene : IUpdate, IRender
|
||||
});
|
||||
}
|
||||
|
||||
public void AddChild(GameObject parParent, GameObject parGameObject)
|
||||
public void SetChild(GameObject parParent, GameObject parChild)
|
||||
{
|
||||
Add(parGameObject);
|
||||
Hierarchy.AddChild(parParent, parGameObject);
|
||||
Hierarchy.AddChild(parParent, parChild);
|
||||
}
|
||||
|
||||
public void AddChild(GameObject parParent, GameObject parChild)
|
||||
{
|
||||
Add(parChild);
|
||||
SetChild(parParent, parChild);
|
||||
}
|
||||
|
||||
public void Remove(GameObject parGameObject)
|
||||
@@ -115,14 +120,14 @@ public class Scene : IUpdate, IRender
|
||||
{
|
||||
Hierarchy.ProcessChanges();
|
||||
|
||||
while (_sceneActions.TryDequeue(out var action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
foreach (var gameObject in Hierarchy.Objects)
|
||||
{
|
||||
gameObject.ProcessChanges();
|
||||
}
|
||||
|
||||
while (_sceneActions.TryDequeue(out var action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,8 @@ public class SceneManager : IUpdate, IRender
|
||||
_currentScene.Enter();
|
||||
}
|
||||
|
||||
_currentScene?.Update(parDeltaTime);
|
||||
if (parDeltaTime != 0)
|
||||
_currentScene?.Update(parDeltaTime);
|
||||
}
|
||||
|
||||
public void Render()
|
||||
|
||||
@@ -66,10 +66,15 @@ public class Window : IPresenter
|
||||
GL.BlitNamedFramebuffer(_engine.Renderer.RenderFramebuffer.Handle, 0,
|
||||
0, parTexture.Height, parTexture.Width, 0,
|
||||
0, 0, Width, Height,
|
||||
ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit,
|
||||
ClearBufferMask.ColorBufferBit,
|
||||
BlitFramebufferFilter.Nearest
|
||||
);
|
||||
}
|
||||
|
||||
public void Exit()
|
||||
{
|
||||
_window.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class NativeWindowExtensions
|
||||
|
||||
Reference in New Issue
Block a user