using System; using System.Diagnostics; using System.Numerics; using System.Runtime.InteropServices; using gaemstone.ECS; using gaemstone.Flecs; using gaemstone.Utility; using Silk.NET.OpenGL; using Silk.NET.Windowing; using static gaemstone.Client.Components.CameraComponents; using static gaemstone.Client.Components.RenderingComponents; using static gaemstone.Client.Systems.Windowing; using static gaemstone.Components.TransformComponents; namespace gaemstone.Client.Systems; [Module] [DependsOn] [DependsOn] [DependsOn] [DependsOn] public class Renderer { private uint _program; private int _cameraMatrixUniform; private int _modelMatrixUniform; private Rule? _renderEntityRule; [Observer] public void OnCanvasSet(Canvas canvas) { var GL = canvas.GL; GL.Enable(EnableCap.DebugOutputSynchronous); GL.DebugMessageCallback(DebugCallback, 0); GL.Enable(EnableCap.CullFace); GL.CullFace(CullFaceMode.Back); GL.Enable(EnableCap.DepthTest); GL.DepthFunc(DepthFunction.Less); GL.Enable(EnableCap.Blend); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); var vertexShaderSource = Resources.GetString("/gaemstone.Client/Resources/default.vs.glsl"); var fragmentShaderSource = Resources.GetString("/gaemstone.Client/Resources/default.fs.glsl"); var vertexShader = GL.CreateAndCompileShader(ShaderType.VertexShader , "vertex" , vertexShaderSource); var fragmentShader = GL.CreateAndCompileShader(ShaderType.FragmentShader, "fragment", fragmentShaderSource); _program = GL.CreateAndLinkProgram("program", vertexShader, fragmentShader); _cameraMatrixUniform = GL.GetUniformLocation(_program, "cameraMatrix"); _modelMatrixUniform = GL.GetUniformLocation(_program, "modelMatrix"); } [System] public void Clear(Canvas canvas) { var GL = canvas.GL; GL.UseProgram(_program); GL.Viewport(canvas.Size); GL.ClearColor(canvas.BackgroundColor); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); } [System] public void Render(Universe universe, [Game] Canvas canvas, in GlobalTransform cameraTransform, in Camera camera, CameraViewport? viewport) { var color = viewport?.ClearColor ?? Color.FromRGB(0.3f, 0.0f, 0.5f); var bounds = viewport?.Viewport ?? new(default, canvas.Size); var GL = canvas.GL; GL.Enable(EnableCap.ScissorTest); GL.Viewport(bounds); GL.Scissor(bounds.Left, bounds.Top, (uint)bounds.Width, (uint)bounds.Height); GL.ClearColor(color); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Disable(EnableCap.ScissorTest); // Create the camera's projection matrix. var cameraProjection = camera.IsOrthographic ? Matrix4x4.CreateOrthographic( bounds.Width, -bounds.Height, camera.NearPlane, camera.FarPlane) : Matrix4x4.CreatePerspectiveFieldOfView( camera.FieldOfView * MathF.PI / 180, // Degrees => Radians (float)bounds.Width / bounds.Height, // Aspect Ratio camera.NearPlane, camera.FarPlane); // Get the camera's transform matrix and invert it. Matrix4x4.Invert(cameraTransform, out var invertedTransform); // Set the uniform to the combined transform and projection. var cameraMatrix = invertedTransform * cameraProjection; GL.UniformMatrix4(_cameraMatrixUniform, 1, false, in cameraMatrix.M11); _renderEntityRule ??= new(universe, new(""" GlobalTransform, (Mesh, $mesh), MeshHandle($mesh), ?(Texture, $tex), ?TextureHandle($tex) """)); foreach (var iter in _renderEntityRule.Iter()) { var transforms = iter.Field(1); var meshes = iter.Field(3); // var texPairs = iter.FieldOrEmpty(4); var textures = iter.FieldOrEmpty(5); for (var i = 0; i < iter.Count; i++) { var rTransform = transforms[i]; var mesh = meshes[i]; // var hasTexture = (texPairs.Length > 0); var texture = textures.GetOrNull(i); // If entity has Texture, bind it now. if (texture.HasValue) GL.BindTexture(texture.Value.Target, texture.Value.Handle); // Draw the mesh. GL.UniformMatrix4(_modelMatrixUniform, 1, false, in rTransform.Value.M11); GL.BindVertexArray(mesh.Handle); if (!mesh.IsIndexed) GL.DrawArrays(PrimitiveType.Triangles, 0, (uint)mesh.Count); else unsafe { GL.DrawElements(PrimitiveType.Triangles, (uint)mesh.Count, DrawElementsType.UnsignedShort, null); } // If entity has Texture, unbind it after it has been rendered. if (texture.HasValue) GL.BindTexture(texture.Value.Target, 0); } } } [System] public static void SwapBuffers(GameWindow window) => window.Handle.SwapBuffers(); [DebuggerStepThrough] private static void DebugCallback(GLEnum source, GLEnum _type, int id, GLEnum _severity, int length, nint _message, nint userParam) { var type = (DebugType)_type; var severity = (DebugSeverity)_severity; var message = Marshal.PtrToStringAnsi(_message, length); Console.WriteLine($"[GLDebug] [{severity}] {type}/{id}: {message}"); if (type == DebugType.DebugTypeError) throw new Exception(message); } }