This commit is contained in:
2024-12-05 03:19:18 +03:00
parent 3f1740f41f
commit bd156ad028
48 changed files with 1314 additions and 176 deletions

View File

@@ -7,14 +7,16 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<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" />
<InternalsVisibleTo Include="EngineTests" />
<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>

View File

@@ -1,8 +0,0 @@
namespace Engine.Scene.Component;
public abstract class Component(GameObject gameObject)
{
public Guid Id { get; } = Guid.NewGuid();
public GameObject GameObject { get; } = gameObject;
}

View File

@@ -1,73 +0,0 @@
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();
}
}

View File

@@ -1,14 +1,8 @@
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.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;
@@ -59,7 +53,7 @@ public sealed class Engine
while (!_window.IsExiting)
{
_window.Update();
}
}
}

View File

@@ -15,7 +15,7 @@ public class IndexBuffer : OpenGlObject
Handle = handle;
GL.NamedBufferStorage(Handle, Count * sizeof(uint), IntPtr.Zero, flags);
Log.Debug("Index buffer {Handle} created with {Count} elements", Handle, Count);
}

View File

@@ -10,7 +10,7 @@ public class VertexArray : OpenGlObject
{
// private IndexBuffer? _boundIndexBuffer;
// private readonly Dictionary<int, VertexBuffer<IVertex>> _boundVertexBuffers = new();
public VertexArray()
{
GL.CreateVertexArrays(1, out int handle);
@@ -20,7 +20,7 @@ public class VertexArray : OpenGlObject
public void BindIndexBuffer(IndexBuffer buffer)
{
GL.VertexArrayElementBuffer(Handle, buffer.Handle);
Log.Debug("Vertex array {Handle} bound to index buffer {Buffer}", Handle, buffer.Handle);
}

View File

@@ -26,7 +26,7 @@ public class VertexBuffer<T> : OpenGlObject
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);
}

View File

@@ -6,4 +6,5 @@ public interface ICamera
{
public Matrix4 View { get; }
public Matrix4 Projection { get; }
public Vector2i ScreenSize { get; internal set; }
}

View File

@@ -6,4 +6,5 @@ public class ScreenspaceCamera : ICamera
{
public Matrix4 View => Matrix4.Identity;
public Matrix4 Projection => Matrix4.Identity;
public Vector2i ScreenSize { get; set; }
}

View File

@@ -1,38 +1,18 @@
using System.Runtime.InteropServices;
using Engine.Renderer.Pixel;
using OpenTK.Graphics.OpenGL;
using Serilog;
using Serilog.Events;
namespace Engine.Renderer;
public class Renderer
internal static class Debug
{
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()
public static void Setup()
{
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)
{
@@ -53,7 +33,7 @@ public class Renderer
DebugType.DebugTypeOther => "Info",
_ => "Unknown"
};
var sourcePrefix = source switch
{
DebugSource.DebugSourceApi => "API",
@@ -65,8 +45,7 @@ public class Renderer
_ => "Unknown"
};
logger.Write(
GetLogLevel(severity),
logger.Write(GetLogLevel(severity),
"[OpenGL {TypePrefix}] [{Source}] {Message} (ID: 0x{Id:X8})",
typePrefix,
sourcePrefix,
@@ -86,22 +65,4 @@ public class Renderer
_ => 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);
}
}

View File

@@ -29,7 +29,7 @@ public class Framebuffer : OpenGlObject
_height = value;
}
}
public IConstTexture<Rgb8> Texture => _texture;
internal Texture.Texture<Rgb8> TextureInternal => _texture;

View File

@@ -15,7 +15,7 @@ public abstract class OpenGlObject
{
Destroy();
Handle = -1;
Log.Debug("OpenGL object {Handle} destroyed", Handle);
}
}

View File

@@ -0,0 +1,55 @@
using System.Runtime.InteropServices;
using Engine.Renderer.Pixel;
using Engine.Renderer.Shader;
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()
{
#if DEBUG
Debug.Setup();
#endif
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.FramebufferSrgb);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.Blend);
}
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);
}
}

View File

@@ -42,7 +42,7 @@ public abstract class Texture<T> : OpenGlObject, ITexture<T> where T : struct, I
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)

View File

@@ -0,0 +1,29 @@
using Engine.Renderer.Camera;
using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public abstract class Camera(
float nearPlane,
float farPlane
) : Component, ICamera
{
public float AspectRatio { get; private set; } = 1;
public float NearPlane { get; set; } = nearPlane;
public float FarPlane { get; set; } = farPlane;
private Vector2i _screenSize = new(1, 1);
public abstract Matrix4 View { get; }
public abstract Matrix4 Projection { get; }
public Vector2i ScreenSize
{
get => _screenSize;
set
{
_screenSize = value;
AspectRatio = (float)value.X / value.Y;
}
}
}

View File

@@ -0,0 +1,27 @@
using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn;
public class OrthographicCamera(
float nearPlane = 0.1f,
float farPlane = 1000f,
float size = 10f,
OrthographicCamera.Axis axis = OrthographicCamera.Axis.Y
)
: Camera(nearPlane, farPlane)
{
public Axis FixedAxis { get; set; } = axis;
public float Size { get; set; } = size;
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 enum Axis
{
X,
Y,
}
}

View File

@@ -1,23 +1,18 @@
using Engine.Renderer.Camera;
using Engine.Util;
using Engine.Util;
using OpenTK.Mathematics;
namespace Engine.Scene.Component;
namespace Engine.Scene.Component.BuiltIn;
public class PerspectiveCamera(
GameObject gameObject,
float aspectRatio,
float fieldOfView,
float nearPlane,
float farPlane)
: Component(gameObject), ICamera
float fieldOfView = 90.0f,
float nearPlane = 0.1f,
float farPlane = 1000f
)
: Camera(nearPlane, farPlane)
{
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
public override Matrix4 View
{
get
{
@@ -30,5 +25,6 @@ public class PerspectiveCamera(
}
}
public Matrix4 Projection => Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
public override Matrix4 Projection =>
Matrix4.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane);
}

View File

@@ -1,8 +1,8 @@
using OpenTK.Mathematics;
namespace Engine.Scene.Component;
namespace Engine.Scene.Component.BuiltIn;
public class Transform(GameObject gameObject) : Component(gameObject)
public class Transform : Component
{
public Vector3 Position { get; set; } = Vector3.Zero;
public Quaternion Rotation { get; set; } = Quaternion.Identity;
@@ -15,5 +15,25 @@ public class Transform(GameObject gameObject) : Component(gameObject)
public Matrix4 TransformMatrix => LocalTransformMatrix * ParentTransformMatrix;
private Matrix4 ParentTransformMatrix => Matrix4.Identity;
private Matrix4 ParentTransformMatrix
{
get
{
var parent = GameObject.Scene?.Hierarchy.GetParent(GameObject);
return parent == null ? Matrix4.Identity : parent.Transform.TransformMatrix;
}
}
public Transform Clone()
{
var clone = new Transform
{
Position = Position,
Rotation = Rotation,
Scale = Scale,
LocalScale = LocalScale
};
return clone;
}
}

View File

@@ -0,0 +1,45 @@
namespace Engine.Scene.Component;
public abstract class Component
{
public Guid Id { get; } = Guid.NewGuid();
public GameObject GameObject { get; internal set; }
internal virtual void Awake()
{
}
internal virtual void Start()
{
}
internal virtual void Update()
{
}
internal virtual void Render()
{
}
internal virtual void Destroy()
{
}
}
public static class ComponentTypeExtensions
{
internal static Type GetComponentBaseType(this Type type)
{
var baseType = type.BaseType;
if (baseType == null || baseType == typeof(Component))
return type;
while (baseType.BaseType != null && baseType.BaseType != typeof(Component))
{
baseType = baseType.BaseType;
}
return baseType;
}
}

View File

@@ -0,0 +1,136 @@
using System.Reflection;
using Engine.Scene.Component;
using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
public sealed class GameObject
{
public Guid Id { get; } = Guid.NewGuid();
public Transform Transform { get; }
internal Scene? Scene { get; set; }
private readonly Queue<Action> _componentActions = new();
private readonly IList<Component.Component> _components = new List<Component.Component>();
private readonly ISet<Type> _addedComponentTypes = new HashSet<Type>();
public GameObject()
{
AddComponent<Transform>();
ProcessChanges();
Transform = GetComponent<Transform>()!;
}
public GameObject(Transform transform)
{
AddComponent(transform.Clone());
ProcessChanges();
Transform = GetComponent<Transform>()!;
}
public void Awake()
{
foreach (var component in _components)
component.Awake();
}
public void Start()
{
foreach (var component in _components)
component.Start();
}
public void Update()
{
foreach (var component in _components)
component.Update();
}
public void Render()
{
foreach (var component in _components)
component.Render();
}
public void Destroy()
{
foreach (var component in _components)
component.Destroy();
}
public T? GetComponent<T>() where T : Component.Component
{
return !HasComponent<T>() ? null : _components.OfType<T>().First();
}
public void AddComponent<T>() where T : Component.Component, new()
{
var component = new T();
AddComponent(component);
}
public void AddComponent<T>(params object?[] args) where T : Component.Component
{
var component = (T?)Activator.CreateInstance(
typeof(T),
BindingFlags.Instance | BindingFlags.Public | BindingFlags.CreateInstance |
BindingFlags.OptionalParamBinding,
null,
args,
null
);
if (component == null)
throw new InvalidOperationException($"Failed to create component of type {typeof(T)}");
AddComponent(component);
}
public void AddComponent<T>(T component) where T : Component.Component
{
_componentActions.Enqueue(() =>
{
if (HasComponent<T>())
throw new ArgumentException($"GameObject already has component of type {typeof(T)}");
component.GameObject = this;
_components.Add(component);
_addedComponentTypes.Add(typeof(T).GetComponentBaseType());
});
}
public void RemoveComponent<T>() where T : Component.Component
{
if (typeof(T) == typeof(Transform))
throw new ArgumentException("GameObject cannot remove Transform component");
_componentActions.Enqueue(() =>
{
if (!HasComponent<T>())
throw new ArgumentException($"GameObject does not have component of type {typeof(T)}");
var component = GetComponent<T>();
if (component == null)
return;
_components.Remove(component);
_addedComponentTypes.Remove(typeof(T));
});
}
public bool HasComponent<T>() where T : Component.Component
{
var baseType = typeof(T).GetComponentBaseType();
return _addedComponentTypes.Contains(baseType);
}
internal void ProcessChanges()
{
while (_componentActions.TryDequeue(out var action))
action();
}
}

View File

@@ -0,0 +1,140 @@
using System.Collections;
using System.Collections.Concurrent;
using Engine.Util;
namespace Engine.Scene;
public class Hierarchy<T> : IEnumerable<T>
where T : class
{
private readonly Dictionary<NullableObject<T>, IList<T>> _childrenLookup = new();
private readonly Dictionary<T, T?> _parentLookup = new();
private readonly ConcurrentQueue<Action> _hierarchyActions = new();
public Hierarchy()
{
_childrenLookup.Add(new NullableObject<T>(), new List<T>());
}
internal void ProcessChanges()
{
while (_hierarchyActions.TryDequeue(out var action))
action();
}
public void Add(T obj)
{
_hierarchyActions.Enqueue(() =>
{
if (_parentLookup.ContainsKey(obj))
throw new ArgumentException("Object is already added to hierarchy");
_childrenLookup.Add(obj, new List<T>());
_parentLookup.Add(obj, null);
_childrenLookup[null].Add(obj);
});
}
public void Remove(T obj)
{
foreach (var child in GetChildren(obj))
Remove(child);
_hierarchyActions.Enqueue(() =>
{
var parent = GetParent(obj);
_childrenLookup[parent].Remove(obj);
_parentLookup.Remove(obj);
_childrenLookup.Remove(obj);
});
}
public void AddChild(T parent, T child)
{
SetParent(child, parent);
}
private void SetParent(T child, T? parent)
{
if (child.Equals(parent))
throw new InvalidOperationException("Child cannot be parent");
_hierarchyActions.Enqueue(() =>
{
if (IsInHierarchy(child, parent))
throw new InvalidOperationException("Parent is a child of child");
var oldParent = GetParent(child);
_childrenLookup[oldParent].Remove(child);
_childrenLookup[parent].Add(child);
_parentLookup[child] = parent;
});
}
public bool Contains(T obj)
{
return _parentLookup.ContainsKey(obj) && _childrenLookup.ContainsKey(obj);
}
public T? GetParent(T child)
{
return _parentLookup.TryGetValue(child, out var parent)
? parent
: throw new InvalidOperationException($"Child {child} is not in hierarchy");
}
public IEnumerable<T> GetChildren(T? obj = null)
{
return _childrenLookup.TryGetValue(obj, out var children) ? children : Enumerable.Empty<T>();
}
public bool IsInHierarchy(T? ancestor, T? child)
{
if (child == null) // if child is null (root), then it is not in hierarchy, as root can not have a parent
return false;
if (ancestor == null) // if ancestor is null (root), then child is not in hierarchy, as root is not a parent
return false;
if (ancestor.Equals(child))
return true;
var parent = GetParent(child);
if (parent == null)
return false;
if (ancestor.Equals(parent))
return true;
return IsInHierarchy(ancestor, parent);
}
public IEnumerable<T> GetAllChildren(T? obj = null)
{
var children = GetChildren(obj);
foreach (var child in children)
{
yield return child;
foreach (var descendant in GetAllChildren(child))
{
yield return descendant;
}
}
}
public IEnumerator<T> GetEnumerator()
{
return _parentLookup.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

108
Engine/src/Scene/Scene.cs Normal file
View File

@@ -0,0 +1,108 @@
using Engine.Renderer.Camera;
using Engine.Scene.Component.BuiltIn;
namespace Engine.Scene;
public class Scene
{
public bool IsPlaying { get; private set; }
public ICamera? Camera { get; private set; }
internal Hierarchy<GameObject> Hierarchy { get; } = [];
private readonly Queue<Action> _sceneActions = [];
internal void Enter()
{
if (IsPlaying)
throw new InvalidOperationException("Scene is already playing");
ProcessChanges();
Camera = FindFirstComponent<Camera>();
IsPlaying = true;
}
public T? FindFirstComponent<T>() where T : Component.Component
{
return Hierarchy.Select(gameObject => gameObject.GetComponent<T>()).OfType<T>().FirstOrDefault();
}
internal void Update()
{
if (!IsPlaying)
throw new InvalidOperationException("Scene is not playing");
ProcessChanges();
foreach (var gameObject in Hierarchy)
gameObject.Update();
}
internal void Render()
{
if (!IsPlaying)
throw new InvalidOperationException("Scene is not playing");
foreach (var gameObject in Hierarchy)
{
gameObject.Render();
}
}
internal void Exit()
{
if (!IsPlaying)
throw new InvalidOperationException("Scene is not playing");
foreach (var gameObject in Hierarchy)
{
gameObject.Destroy();
}
IsPlaying = false;
}
public void Add(GameObject gameObject)
{
Hierarchy.Add(gameObject);
_sceneActions.Enqueue(() =>
{
gameObject.Scene = this;
gameObject.Awake();
gameObject.Start();
});
}
public void Remove(GameObject gameObject)
{
Hierarchy.Remove(gameObject);
_sceneActions.Enqueue(() =>
{
foreach (var child in Hierarchy.GetAllChildren(gameObject))
{
child.Destroy();
child.Scene = null;
}
gameObject.Destroy();
gameObject.Scene = null;
});
}
private void ProcessChanges()
{
Hierarchy.ProcessChanges();
while (_sceneActions.TryDequeue(out var action))
action();
foreach (var gameObject in Hierarchy)
gameObject.ProcessChanges();
}
}

View File

@@ -0,0 +1,55 @@
namespace Engine.Util;
public readonly struct NullableObject<T>
where T : class
{
public bool IsNull => _value == null;
public T? Value => _value;
private readonly T? _value;
public NullableObject()
{
_value = null;
}
public NullableObject(T? value)
{
_value = value;
}
public static implicit operator T?(NullableObject<T> nullableObject) => nullableObject.Value;
public static implicit operator NullableObject<T>(T? value) => new(value);
public override string ToString()
{
return _value?.ToString() ?? "null";
}
public override bool Equals(object? obj)
{
if (obj == null)
return IsNull;
if (obj is not NullableObject<T> other)
return false;
if (IsNull && other.IsNull)
return true;
return _value!.Equals(other._value);
}
public override int GetHashCode()
{
if (IsNull)
return 0;
var hashCode = _value!.GetHashCode();
if (hashCode >= 0)
hashCode += 1;
return hashCode;
}
}

View File

@@ -10,9 +10,9 @@ namespace Engine;
public class Window : IPresenter<Rgb8>
{
public bool IsExiting => _window.IsExiting;
public int Width => _window.ClientSize.X;
public int Height => _window.ClientSize.Y;
public int Width { get; private set; }
public int Height { get; private set; }
private readonly Engine _engine;
private readonly NativeWindow _window;
private readonly bool _headless;
@@ -22,9 +22,15 @@ public class Window : IPresenter<Rgb8>
_engine = engine;
_window = window;
_headless = headless;
(Width, Height) = _window.ClientSize;
_window.MakeCurrent();
_window.Resize += args => GL.Viewport(0, 0, args.Width, args.Height);
_window.Resize += args =>
{
Width = args.Width;
Height = args.Height;
};
}
public void Update()
@@ -42,30 +48,30 @@ public class Window : IPresenter<Rgb8>
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();
}
}