diff --git a/Quik.Media.Stb/QFontStbtt.cs b/Quik.Media.Stb/QFontStbtt.cs index fa0e666..62b5557 100644 --- a/Quik.Media.Stb/QFontStbtt.cs +++ b/Quik.Media.Stb/QFontStbtt.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using static StbTrueTypeSharp.StbTrueType; using Quik.Media.Color; namespace Quik.Media.Stb diff --git a/Quik.OpenTK/GL30Driver.cs b/Quik.OpenTK/GL30Driver.cs deleted file mode 100644 index e919086..0000000 --- a/Quik.OpenTK/GL30Driver.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using Quik.OpenGL; -using Quik.VertexGenerator; -using Matrix4 = OpenTK.Mathematics.Matrix4; -using static Quik.OpenGL.GLEnum; - -namespace Quik.OpenTK -{ - public class GL30Driver : IDisposable - { - public GL30Driver() - { - Assembly asm = typeof(GL30Driver).Assembly; - - using (StreamReader vert = new StreamReader(asm.GetManifestResourceStream("Quik.OpenTK.glsl.glsl130.vert"))) - using (StreamReader frag = new StreamReader(asm.GetManifestResourceStream("Quik.OpenTK.glsl.glsl130.frag"))) - { - int vs; - int fs; - - vs = GL.CreateShader(GL_VERTEX_SHADER); - fs = GL.CreateShader(GL_FRAGMENT_SHADER); - - _sp = GL.CreateProgram(); - - GL.ShaderSource(vs, vert.ReadToEnd()); - GL.ShaderSource(fs, frag.ReadToEnd()); - - GL.CompileShader(vs); - GL.CompileShader(fs); - - #if DEBUG - int status; - - GL.GetShader(vs, GL_COMPILE_STATUS, out status); - if (status == 0) - { - throw new Exception(GL.GetShaderInfoLog(vs)); - } - - GL.GetShader(fs, GL_COMPILE_STATUS, out status); - if (status == 0) - { - throw new Exception(GL.GetShaderInfoLog(fs)); - } - #endif - - GL.AttachShader(_sp, vs); - GL.AttachShader(_sp, fs); - - GL.LinkProgram(_sp); - - #if DEBUG - GL.GetProgram(_sp, GL_LINK_STATUS, out status); - if (status == 0) - { - throw new Exception(GL.GetProgramInfoLog(_sp)); - } - #endif - - GL.DetachShader(_sp, vs); - GL.DetachShader(_sp, fs); - GL.DeleteShader(vs); - GL.DeleteShader(fs); - } - - LoadUniform(_nameM4View, out _locM4View); - LoadUniform(_nameM4Model, out _locM4Model); - LoadUniform(_nameIFlags, out _locIFlags); - LoadUniform(_nameFSdfThreshold, out _locFSdfThreshold); - LoadUniform(_nameFSdfAuxilliaryThreshold, out _locFSdfAuxilliaryThreshold); - LoadUniform(_nameV4SdfAuxilliaryColor, out _locV4SdfAuxilliaryColor); - LoadUniform(_nameX2Texture, out _locX2Texture); - LoadUniform(_nameV2TextureOffset, out _locV2TextureOffset); - - LoadAttribute(_nameV2Postion, out _locV2Position); - LoadAttribute(_nameV2Texture, out _locV2Texture); - LoadAttribute(_nameV4Color, out _locV4Color); - - void LoadUniform(string name, out int location) - { - location = GL.GetUniformLocation(_sp, name); - } - - void LoadAttribute(string name, out int location) - { - location = GL.GetAttribLocation(_sp, name); - } - - _vbo = GL.GenBuffer(); - _ebo = GL.GenBuffer(); - _vao = GL.GenVertexArray(); - } - - private readonly int _sp; - private readonly int _vao; - private readonly int _vbo; - private readonly int _ebo; - - private const string _nameM4View = "m4View"; - private readonly int _locM4View; - private const string _nameM4Model = "m4Model"; - private readonly int _locM4Model; - private const string _nameV2Postion = "v2Position"; - private readonly int _locV2Position; - private const string _nameV2Texture = "v2Texture"; - private readonly int _locV2Texture; - private const string _nameV4Color = "v4Color"; - private readonly int _locV4Color; - private const string _nameIFlags = "iFlags"; - private readonly int _locIFlags; - private const string _nameFSdfThreshold = "fSdfThreshold"; - private readonly int _locFSdfThreshold; - private const string _nameFSdfAuxilliaryThreshold = "fSdfAuxilliaryThreshold"; - private readonly int _locFSdfAuxilliaryThreshold; - private const string _nameV4SdfAuxilliaryColor = "v4SdfAuxilliaryColor"; - private readonly int _locV4SdfAuxilliaryColor; - private const string _nameX2Texture = "x2Texture"; - private readonly int _locX2Texture; - private const string _nameV2TextureOffset = "v2TextureOffset"; - private readonly int _locV2TextureOffset; - - [Flags] - private enum Flags : int - { - Texture = 1 << 0, - DiscardEnable = 1 << 1, - Sdf = 1 << 2, - SdfAuxEnable = 1 << 3, - } - - public void Draw(DrawQueue queue) - { - GL.UseProgram(_sp); - GL.BindVertexArray(_vao); - - GL.BindBuffer(GL_ARRAY_BUFFER, _vbo); - GL.BufferData(GL_ARRAY_BUFFER, queue.VertexCount * QuikVertex.Stride, queue.VertexArray, GL_STREAM_DRAW); - GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo); - GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, queue.ElementCount * sizeof(int), queue.ElementArray, GL_STREAM_DRAW); - - GL.VertexAttribPointer(_locV2Position, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.PositionOffset); - GL.VertexAttribPointer(_locV2Texture, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureCoordinatesOffset); - GL.VertexAttribPointer(_locV4Color, 4, GL_UNSIGNED_BYTE, false, QuikVertex.Stride, QuikVertex.ColorOffset); - GL.EnableVertexAttribArray(_locV2Position); - GL.EnableVertexAttribArray(_locV2Texture); - GL.EnableVertexAttribArray(_locV4Color); - - GL.Enable(GL_BLEND); - GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - Matrix4 m = Matrix4.Identity; - GL.UniformMatrix4(_locM4Model, false, ref m.Row0.X); - - foreach (DrawCall call in queue) - { - GL.BindTexture(GL_TEXTURE_2D, 0); - m = Matrix4.CreateOrthographicOffCenter(0, call.Bounds.Right, 0, call.Bounds.Top, 1.0f, -1.0f); - GL.UniformMatrix4(_locM4View, false, ref m.Row0.X); - GL.DrawElements(GL_TRIANGLES, call.Count, GL_UNSIGNED_INT, call.Start); - } - } - - private bool _isDisposed = false; - private void Dispose(bool disposing) - { - if (_isDisposed) return; - - if (disposing) - { - GL.DeleteProgram(_sp); - } - else - { - throw new Exception("OpenGL resource is leaked. Dispose unreferenced OpenGL objects in the context thread."); - } - - _isDisposed = true; - } - - public void Dispose() => Dispose(true); - ~GL30Driver() - { - Dispose(false); - } - } -} \ No newline at end of file diff --git a/Quik.OpenTK/OpenTKPlatform.cs b/Quik.OpenTK/OpenTKPlatform.cs new file mode 100644 index 0000000..468bdbc --- /dev/null +++ b/Quik.OpenTK/OpenTKPlatform.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using Quik.Media; +using Quik.OpenGL; +using Quik.PAL; + +namespace Quik.OpenTK +{ + public class OpenTKPlatform : IQuikPlatform + { + private readonly List _ports = new List(); + + // These shall remain a sad nop for now. + public string Title { get; set; } + public QImage Icon { get; set; } + + public event EventHandler EventRaised; + + public NativeWindowSettings DefaultSettings { get; set; } = NativeWindowSettings.Default; + + public IReadOnlyList Ports => _ports; + + private bool IsGLInitialized = false; + + public IQuikPort CreatePort() + { + NativeWindow window = new NativeWindow(DefaultSettings); + OpenTKPort port = new OpenTKPort(window); + _ports.Add(port); + + if (!IsGLInitialized) + { + window.Context.MakeCurrent(); + GL.LoadBindings((string proc) => GLFW.GetProcAddress(proc)); + IsGLInitialized = true; + } + + return port; + } + + public void Dispose() + { + // FIXME: dispose pattern here! + + // Copy the array to prevent collection modification exceptions. + foreach (OpenTKPort port in _ports.ToArray()) + { + port.Dispose(); + } + } + + public void ProcessEvents(bool block) + { + NativeWindow.ProcessWindowEvents(block); + } + } +} \ No newline at end of file diff --git a/Quik.OpenTK/OpenTKPort.cs b/Quik.OpenTK/OpenTKPort.cs new file mode 100644 index 0000000..2c13d2b --- /dev/null +++ b/Quik.OpenTK/OpenTKPort.cs @@ -0,0 +1,105 @@ +using System; +using OpenTK.Mathematics; +using OpenTK.Windowing.Desktop; +using Quik.OpenGL; +using Quik.CommandMachine; +using Quik.PAL; +using Quik.VertexGenerator; + +namespace Quik.OpenTK +{ + public class OpenTKPort : IQuikPort + { + private readonly NativeWindow _window; + private readonly GL21Driver _glDriver; + private readonly VertexGeneratorEngine _vertexEngine; + + public string Title + { + get => _window.Title; + set => _window.Title = value; + } + + public QVec2 Size + { + get + { + Vector2i size = _window.ClientSize; + return new QVec2(size.X, size.Y); + } + set + { + // OpenTK being OpenTK as usual, you can't set the client size. + Vector2i extents = _window.Size - _window.ClientSize; + Vector2i size = extents + new Vector2i((int)value.X, (int)value.Y); + _window.Size = size; + } + } + public QVec2 Position + { + get + { + Vector2i location = _window.Location; + return new QVec2(location.X, location.Y); + } + set + { + Vector2i location = new Vector2i((int)value.X, (int)value.Y); + _window.Location = location; + } + } + + public bool IsValid => !isDisposed; + + public event EventHandler EventRaised; + + public OpenTKPort(NativeWindow window) + { + _window = window; + _glDriver = new GL21Driver(); + _vertexEngine = new VertexGeneratorEngine(); + } + + public void Focus() + { + _window.Focus(); + } + + public void Paint(CommandQueue queue) + { + QRectangle view = new QRectangle(Size, new QVec2(0, 0)); + + _vertexEngine.Reset(); + _vertexEngine.ProcessCommands(new QRectangle(), queue); + + _window.Context.MakeCurrent(); + + if (!_glDriver.IsInit) + _glDriver.Init(); + + _glDriver.Draw(_vertexEngine.DrawQueue, view); + + _window.Context.SwapBuffers(); + } + + public void Show(bool shown = true) + { + _window.IsVisible = shown; + } + + private bool isDisposed; + private void Dispose(bool disposing) + { + if (isDisposed) return; + + if (disposing) + { + _window?.Dispose(); + GC.SuppressFinalize(this); + } + + isDisposed = true; + } + public void Dispose() => Dispose(true); + } +} \ No newline at end of file diff --git a/Quik.OpenTK/glsl/glsl130.frag b/Quik.OpenTK/glsl/glsl130.frag deleted file mode 100644 index 4763770..0000000 --- a/Quik.OpenTK/glsl/glsl130.frag +++ /dev/null @@ -1,61 +0,0 @@ -#version 130 - -#define F_TEXTURE (1 << 0) -#define F_DISCARD_EN (1 << 1) -#define F_SDF (1 << 2) -#define F_SDF_AUX_EN (1 << 3) - -uniform int iFlags; -uniform float fSdfThreshold; -uniform float fSdfAuxilliaryThreshold; -uniform vec4 v4SdfAuxilliaryColor; -uniform sampler2D x2Texture; -uniform vec2 v2TextureOffset; - -in vec2 fv2Texture; -out vec4 fv4Color; - -vec4 v4Color() -{ - vec4 color = fv4Color; - - if ((iFlags & F_TEXTURE) != 0) - { - if ((iFlags & F_SDF) != 0) - { - float a = texture(x2Texture, fv2Texture + v2TextureOffset).a; - - if ((iFlags & F_SDF_AUX_EN) != 0) - { - color = - (a > fSdfThreshold) - ? color - : (a > fSdfAuxilliaryThreshold) - ? v4SdfAuxilliaryColor - : vec4(0, 0, 0, 0); - } - else if (a < fSdfThreshold) - { - color = vec4(0, 0, 0, 0); - } - } - else - { - color *= texture(x2Texture, fv2Texture + v2TextureOffset); - } - } - - return color; -} - -void main() -{ - vec4 color = v4Color(); - - if ((iFlags & F_DISCARD_EN) != 0 && color.a <= 0) - { - discard; - } - - fv4Color = color; -} diff --git a/Quik.OpenTK/glsl/glsl130.vert b/Quik.OpenTK/glsl/glsl130.vert deleted file mode 100644 index d4e128d..0000000 --- a/Quik.OpenTK/glsl/glsl130.vert +++ /dev/null @@ -1,19 +0,0 @@ -#version 130 - -uniform mat4 m4View; -uniform mat4 m4Model; - -in vec2 v2Position; -in vec2 v2Texture; -in vec4 v4Color; - -out vec2 fv2Texture; -out vec4 fv4Color; - -void main() -{ - fv4Color = v4Color; - fv2Texture = v2Texture; - - gl_Position = m4View * m4Model * vec4(v2Position.xy, 1, 1); -} diff --git a/Quik.sln b/Quik.sln index 604b4c1..75e58d5 100644 --- a/Quik.sln +++ b/Quik.sln @@ -15,6 +15,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AE05ADE5 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Quik.Stb.Tests", "tests\Quik.Stb.Tests\Quik.Stb.Tests.csproj", "{BC7D3002-B79B-4141-B6CC-74FB2175B474}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuikDemo", "tests\QuikDemo\QuikDemo.csproj", "{79CBF97F-4884-4692-94FB-75DDEB61E26F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -97,8 +99,21 @@ Global {BC7D3002-B79B-4141-B6CC-74FB2175B474}.Release|x64.Build.0 = Release|Any CPU {BC7D3002-B79B-4141-B6CC-74FB2175B474}.Release|x86.ActiveCfg = Release|Any CPU {BC7D3002-B79B-4141-B6CC-74FB2175B474}.Release|x86.Build.0 = Release|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Debug|x64.ActiveCfg = Debug|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Debug|x64.Build.0 = Debug|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Debug|x86.ActiveCfg = Debug|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Debug|x86.Build.0 = Debug|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Release|Any CPU.Build.0 = Release|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Release|x64.ActiveCfg = Release|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Release|x64.Build.0 = Release|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Release|x86.ActiveCfg = Release|Any CPU + {79CBF97F-4884-4692-94FB-75DDEB61E26F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {BC7D3002-B79B-4141-B6CC-74FB2175B474} = {AE05ADE5-A809-479F-97D5-BEAFE7604285} + {79CBF97F-4884-4692-94FB-75DDEB61E26F} = {AE05ADE5-A809-479F-97D5-BEAFE7604285} EndGlobalSection EndGlobal diff --git a/Quik/OpenGL/GL21Driver.cs b/Quik/OpenGL/GL21Driver.cs index f26334a..c0bb24c 100644 --- a/Quik/OpenGL/GL21Driver.cs +++ b/Quik/OpenGL/GL21Driver.cs @@ -80,9 +80,10 @@ namespace Quik.OpenGL { AssertInit(); - if (data.TryGetValue(queue, out DrawData draw)) + if (!data.TryGetValue(queue, out DrawData draw)) { draw = new DrawData(this, queue); + data.Add(queue, draw); } // This already binds the vertex array for me. @@ -206,12 +207,18 @@ namespace Quik.OpenGL GL.DeleteProgram(program); + foreach (DrawData datum in data.Values) + { + datum.Dispose(); + } + isDiposed = true; + GC.SuppressFinalize(this); } public void Dispose() => Dispose(true); - private struct DrawData : IDisposable + private class DrawData : IDisposable { public DrawQueue Queue { get; } public int VertexArray { get; } @@ -240,6 +247,9 @@ namespace Quik.OpenGL vbo = Swap(ref vbo1, ref vbo2); ebo = Swap(ref ebo1, ref ebo2); + if (Queue.VertexCount == 0 || Queue.ElementCount == 0) + return; + GL.BindVertexArray(VertexArray); GL.BindBuffer(GL_ARRAY_BUFFER, vbo); GL.BufferData(GL_ARRAY_BUFFER, QuikVertex.Stride * Queue.VertexCount, Queue.VertexArray, GL_STREAM_DRAW); diff --git a/Quik/PAL/IQuikHost.cs b/Quik/PAL/IQuikHost.cs new file mode 100644 index 0000000..571fb1f --- /dev/null +++ b/Quik/PAL/IQuikHost.cs @@ -0,0 +1,38 @@ +using System; +using Quik.Media; + +namespace Quik.PAL +{ + /// + /// The primary primary platform abstraction interface for Quik hosts. + /// + public interface IQuikPlatform : IDisposable + { + /// + /// The title of the application. + /// + string Title { get; set; } + + /// + /// The default icon for the application. + /// + QImage Icon { get; set; } + + /// + /// The event raised when an event is received. + /// + event EventHandler EventRaised; + + /// + /// Create a window. + /// + /// The window instance. + IQuikPort CreatePort(); + + /// + /// Raise the events that have been enqueued. + /// + /// True to block until a new event arrives. + void ProcessEvents(bool block); + } +} \ No newline at end of file diff --git a/Quik/PAL/IQuikPort.cs b/Quik/PAL/IQuikPort.cs new file mode 100644 index 0000000..75c0099 --- /dev/null +++ b/Quik/PAL/IQuikPort.cs @@ -0,0 +1,49 @@ +using System; +using Quik.CommandMachine; + +namespace Quik.PAL +{ + /// + /// An abstraction over the a window or a rendering context. + /// + public interface IQuikPort : IDisposable + { + /// + /// Title of the window, if applicable. + /// + string Title { get; set; } + + /// + /// Size of the window. + /// + QVec2 Size { get; set; } + + /// + /// Position of the window in the coordinate system. + /// + QVec2 Position { get; set; } + bool IsValid { get; } + + /// + /// Called when an event for this port is raised. + /// + event EventHandler EventRaised; + + /// + /// Focus the window, bringing it to the front. + /// + void Focus(); + + /// + /// Show or hide the window. + /// + /// True to show the window, false to hide. + void Show(bool shown = true); + + /// + /// Paint the given command queue onto the port. + /// + /// The command queue to paint. + void Paint(CommandQueue queue); + } +} \ No newline at end of file diff --git a/Quik/Quik.csproj b/Quik/Quik.csproj index deccd25..28891a9 100644 --- a/Quik/Quik.csproj +++ b/Quik/Quik.csproj @@ -7,4 +7,8 @@ True + + + + diff --git a/Quik/QuikApplication.cs b/Quik/QuikApplication.cs index e630b8f..804ff7f 100644 --- a/Quik/QuikApplication.cs +++ b/Quik/QuikApplication.cs @@ -1,10 +1,70 @@ using System; using System.Collections.Generic; +using Quik.CommandMachine; +using Quik.Controls; +using Quik.Media; +using Quik.PAL; namespace Quik { + /// + /// Main class for Quik applications. + /// public class QuikApplication { + /// + /// The application platform driver. + /// + public IQuikPlatform Platform { get; } + /// + /// Title of the application. + /// + public string Title + { + get => Platform.Title; + set => Platform.Title = value; + } + + /// + /// Application icon. + /// + public QImage Icon + { + get => Platform.Icon; + set => Platform.Icon = value; + } + + public View MainView { get; private set; } = null; + + /// + /// List of media loaders, drivers that load media such as images and fonts. + /// + public List MediaLoaders { get; } = new List(); + + public QuikApplication(IQuikPlatform platform) + { + Platform = platform; + } + + public void Run(View mainView) + { + IQuikPort port = Platform.CreatePort(); + CommandQueue cmd = new CommandQueue(); + + MainView = mainView; + + port.EventRaised += (sender, ea) => mainView.NotifyEvent(sender, ea); + + while (port.IsValid) + { + Platform.ProcessEvents(false); + if (port.IsValid) + { + MainView.Paint(cmd); + port.Paint(cmd); + } + } + } } } \ No newline at end of file diff --git a/tests/QuikDemo/Program.cs b/tests/QuikDemo/Program.cs new file mode 100644 index 0000000..2f10163 --- /dev/null +++ b/tests/QuikDemo/Program.cs @@ -0,0 +1,15 @@ +using Quik; +using Quik.OpenTK; + +namespace QuikDemo +{ + public static class Program + { + public static readonly QuikApplication Application = new QuikApplication(new OpenTKPlatform()); + + public static void Main(string[] args) + { + Application.Run(new Quik.Controls.View()); + } + } +} \ No newline at end of file diff --git a/tests/QuikDemo/QuikDemo.csproj b/tests/QuikDemo/QuikDemo.csproj new file mode 100644 index 0000000..9ae25ef --- /dev/null +++ b/tests/QuikDemo/QuikDemo.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + Exe + net6.0 + disable + enable + + +