internal unsafe void Launch()
        {
            Console.WriteLine("Draw Texture...");

            var image      = new RasterImage();
            var namePrefix = typeof(DrawTextureLauncher).Namespace + ".Resources.";

            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(namePrefix + "kitten.bmp"))
            {
                image.LoadBmp(stream ?? throw new InvalidOperationException());
            }

            using var windowServer     = new WindowServer();
            using var renderingContext = new RenderingContext();
            using var window           = windowServer.CreateWindow(renderingContext);

            window.BorderStyle             = WindowBorderStyle.Sizable;
            renderingContext.CurrentWindow = window;
            window.MinimumSize             = (400, 300);
            window.MaximumSize             = (600, 500);
            window.Size    = (450, 350);
            window.Opacity = 0.8;

            var gl = new GraphicsLibrary(renderingContext);

            gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);

            var program      = gl.CreateProgram();
            var vertexShader = OpenGLHelper.LoadShader(gl, ShaderType.Vertex, @"
                    in vec2 position;
                    in vec3 color;
                    in vec2 texCoord;
                    out vec3 fColor;
                    out vec2 fTexCoord;
                    void main() {
                        gl_Position = vec4(position, 0.0, 1.0);
                        fColor = color;
                        fTexCoord = texCoord;
                    }
                ");

            gl.AttachShader(program, vertexShader);
            var fragmentShader = OpenGLHelper.LoadShader(gl, ShaderType.Fragment, @"
                    in vec3 fColor;
                    in vec2 fTexCoord;
                    out vec4 outColor;
                    uniform sampler2D tex;
                    void main() {
                        outColor = texture(tex, fTexCoord) * vec4(fColor, 1.0);
                    }
                ");

            gl.AttachShader(program, fragmentShader);

            var position = 0u;

            OpenGLHelper.BindAttribLocation(gl, program, position, "position");
            var color = 1u;

            OpenGLHelper.BindAttribLocation(gl, program, color, "color");
            var texCoord = 2u;

            OpenGLHelper.BindAttribLocation(gl, program, texCoord, "texCoord");

            OpenGLHelper.LinkProgram(gl, program);

            uint h;

            gl.GenVertexArrays(1, &h);
            uint vertexArray = h;

            gl.BindVertexArray(vertexArray);

            float[] vertices = new float[] {
                -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,     // Top-left
                0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,      // Top-right
                0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,     // Bottom-right
                -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f     // Bottom-left
            };
            gl.GenBuffers(1, &h);
            uint vertexBuffer = h;

            gl.BindBuffer(BufferTarget.Array, vertexBuffer);
            OpenGLHelper.BufferData(gl, BufferTarget.Array, vertices, BufferUsage.StaticDraw);

            var elements = new ushort[] {
                0, 1, 2,
                2, 3, 0
            };

            gl.GenBuffers(1, &h);
            uint elementBuffer = h;

            gl.BindBuffer(BufferTarget.ElementArray, elementBuffer);
            OpenGLHelper.BufferData(gl, BufferTarget.ElementArray, elements, BufferUsage.StaticDraw);

            gl.UseProgram(program);
            var stride = sizeof(float) * 7;

            gl.EnableVertexAttribArray(position);
            gl.VertexAttribPointer(position, 2, AttributeElementType.Float, Boolean.False, stride, IntPtr.Zero);
            gl.EnableVertexAttribArray(color);
            gl.VertexAttribPointer(color, 3, AttributeElementType.Float, Boolean.False, stride, IntPtr.Zero + 2 * sizeof(float));
            gl.EnableVertexAttribArray(texCoord);
            gl.VertexAttribPointer(texCoord, 2, AttributeElementType.Float, Boolean.False, stride, IntPtr.Zero + 5 * sizeof(float));

            gl.GenTextures(1, &h);
            var texture = h;

            gl.ActiveTexture(TextureUnit.Texture0);
            gl.BindTexture(TextureTarget.Texture2d, texture);
            OpenGLHelper.TexImage2D(gl, image, TextureTarget2D.Texture2d, 0);

            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureWrapS, (int)TextureParameterValue.ClampToEdge);
            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureWrapT, (int)TextureParameterValue.ClampToEdge);
            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureMinFilter, (int)TextureParameterValue.Linear);
            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureMagFilter, (int)TextureParameterValue.Linear);

            var tex = OpenGLHelper.GetUniformLocation(gl, program, "tex");

            gl.Uniform1i(tex, 0);

            window.Closed += (sender, e) => {
                uint t = texture;
                gl.DeleteTextures(1, &t);
                gl.DeleteProgram(program);
                gl.DeleteShader(vertexShader);
                gl.DeleteShader(fragmentShader);
                t = vertexBuffer;
                gl.DeleteBuffers(1, &t);
                t = elementBuffer;
                gl.DeleteBuffers(1, &t);
                t = vertexArray;
                gl.DeleteVertexArrays(1, &t);
            };

            void draw()
            {
                var(width, height) = window.ViewportSize;
                gl.Viewport(0, 0, (int)width, (int)height);
                gl.Clear(Buffers.Color);
                gl.DrawElements(DrawMode.Triangles, 6, DrawIndexType.UnsignedShort, IntPtr.Zero);
                renderingContext.SwapBuffers(window);
            }

            window.Resize += (sender, e) => draw();

            window.Visible = true;
            while (windowServer.Windows.Count > 0)
            {
                draw();

                windowServer.ProcessEvents(0.02);
            }

            Console.WriteLine("Draw Texture done.");
        }
        internal unsafe void Launch()
        {
            Console.WriteLine("Rotate Cube...");

            var image      = new RasterImage();
            var namePrefix = typeof(DrawTextureLauncher).Namespace + ".Resources.";

            using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(namePrefix + "kitten.png"))
            {
                image.LoadPng(stream ?? throw new InvalidOperationException());
            }

            using var windowServer     = new WindowServer();
            using var renderingContext = new RenderingContext();
            using var window           = windowServer.CreateWindow(renderingContext);

            window.BorderStyle             = WindowBorderStyle.Sizable;
            window.Title                   = "Rotate Cube...";
            renderingContext.CurrentWindow = window;

            var gl = new GraphicsLibrary(renderingContext);

            gl.ClearColor(0, 0, 0, 1);

            var program      = gl.CreateProgram();
            var vertexShader = OpenGLHelper.LoadShader(gl, ShaderType.Vertex, @"
                    in vec3 position;
                    in vec3 color;
                    in vec2 texCoord;
                    out vec3 fColor;
                    out vec2 fTexCoord;
                    uniform mat4 model;
                    uniform mat4 view;
                    uniform mat4 proj;
                    void main() {
                        gl_Position = proj * view * model * vec4(position, 1.0);
                        fColor = color;
                        fTexCoord = texCoord;
                    }
                ");

            gl.AttachShader(program, vertexShader);
            var fragmentShader = OpenGLHelper.LoadShader(gl, ShaderType.Fragment, @"
                    in vec3 fColor;
                    in vec2 fTexCoord;
                    out vec4 outColor;
                    uniform sampler2D tex;
                    void main() {
                        outColor = texture(tex, fTexCoord) * vec4(fColor, 1.0);
                    }
                ");

            gl.AttachShader(program, fragmentShader);

            var position = 0u;

            OpenGLHelper.BindAttribLocation(gl, program, position, "position");
            var color = 1u;

            OpenGLHelper.BindAttribLocation(gl, program, color, "color");
            var texCoord = 2u;

            OpenGLHelper.BindAttribLocation(gl, program, texCoord, "texCoord");

            OpenGLHelper.LinkProgram(gl, program);

            uint h;

            gl.GenVertexArrays(1, &h);
            uint vertexArray = h;

            gl.BindVertexArray(vertexArray);

            var vertices = new float[] {
                -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,

                -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,

                -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,

                0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,

                -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,

                -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
                0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
                0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
                -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
                -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
            };

            gl.GenBuffers(1, &h);
            uint vertexBuffer = h;

            gl.BindBuffer(BufferTarget.Array, vertexBuffer);
            OpenGLHelper.BufferData(gl, BufferTarget.Array, vertices, BufferUsage.StaticDraw);

            var stride = sizeof(float) * 8;

            gl.EnableVertexAttribArray(position);
            gl.VertexAttribPointer(position, 3, AttributeElementType.Float, Boolean.False, stride, IntPtr.Zero);
            gl.EnableVertexAttribArray(color);
            gl.VertexAttribPointer(color, 3, AttributeElementType.Float, Boolean.False, stride, IntPtr.Zero + 3 * sizeof(float));
            gl.EnableVertexAttribArray(texCoord);
            gl.VertexAttribPointer(texCoord, 2, AttributeElementType.Float, Boolean.False, stride, IntPtr.Zero + 6 * sizeof(float));

            gl.GenTextures(1, &h);
            var texture = h;

            gl.ActiveTexture(TextureUnit.Texture0);
            gl.BindTexture(TextureTarget.Texture2d, texture);
            OpenGLHelper.TexImage2D(gl, image, TextureTarget2D.Texture2d, 0);

            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureWrapS, (int)TextureParameterValue.ClampToEdge);
            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureWrapT, (int)TextureParameterValue.ClampToEdge);
            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureMinFilter, (int)TextureParameterValue.Linear);
            gl.TexParameteri(TextureTarget.Texture2d, TextureParameteri.TextureMagFilter, (int)TextureParameterValue.Linear);

            gl.UseProgram(program);

            var tex = OpenGLHelper.GetUniformLocation(gl, program, "tex");

            gl.Uniform1i(tex, 0);

            var view  = OpenGLHelper.GetUniformLocation(gl, program, "view");
            var proj  = OpenGLHelper.GetUniformLocation(gl, program, "proj");
            var model = OpenGLHelper.GetUniformLocation(gl, program, "model");

            var mat = Matrix4x4.CreateLookAt(new Vector3(1.5f, 1.5f, 1.5f), new Vector3(0f, 0f, 0f), new Vector3(0f, 0f, 1f));

            OpenGLHelper.UniformMatrix(gl, view, mat);

            mat = Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), 1.3f, 1f, 10f);
            OpenGLHelper.UniformMatrix(gl, proj, mat);

            var viewportSize = window.ViewportSize;
            var windowSize   = window.Size;

            renderingContext.CurrentWindow = null;


            bool  done     = false;
            float angle    = 0f;
            var   drawLock = new object();

            void draw()
            {
                var stopWatch = new Stopwatch();

                stopWatch.Start();
                var lastTime  = stopWatch.ElapsedMilliseconds / 1e3f;
                var deltaTime = 0.0f;

                //renderingContext.SyncWithVerticalBlank(window, false);
                renderingContext.CurrentWindow = window;
                while (!done)
                {
                    var currentTime = stopWatch.ElapsedMilliseconds / 1e3f;
                    deltaTime = currentTime - lastTime;
                    lastTime  = currentTime;

                    gl.Viewport(0, 0, (int)viewportSize.width, (int)viewportSize.height);
                    gl.UseProgram(program);
                    angle += deltaTime;
                    mat    = Matrix4x4.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), angle);
                    OpenGLHelper.UniformMatrix(gl, model, mat);

                    gl.Enable(Capability.DepthTest);
                    gl.Clear(Buffers.Color | Buffers.Depth);
                    gl.BindVertexArray(vertexArray);
                    gl.BindBuffer(BufferTarget.Array, vertexBuffer);
                    gl.ActiveTexture(TextureUnit.Texture0);
                    gl.BindTexture(TextureTarget.Texture2d, texture);
                    gl.DrawArrays(DrawMode.Triangles, 0, 36);
                    gl.Disable(Capability.DepthTest);

                    renderingContext.SwapBuffers(window);
                    lock (drawLock)
                    {
                        Monitor.Wait(drawLock, 15);
                    }
                }
                renderingContext.CurrentWindow = null;
            }

            var drawThread = new Thread(draw);

            window.Closed += (sender, e) => {
                done = true;
                drawThread.Join();
                renderingContext.CurrentWindow = window;
                uint t = texture;
                gl.DeleteTextures(1, &t);
                gl.DeleteProgram(program);
                gl.DeleteShader(vertexShader);
                gl.DeleteShader(fragmentShader);
                t = vertexBuffer;
                gl.DeleteBuffers(1, &t);
                t = vertexArray;
                gl.DeleteVertexArrays(1, &t);
            };

            window.Resize += (sender, e) =>
            {
                viewportSize = window.ViewportSize;
                windowSize   = window.Size;
                lock (drawLock)
                {
                    Monitor.Pulse(drawLock);
                }
            };

            window.FramebufferResize += (sender, e) =>
            {
                viewportSize = window.ViewportSize;
                lock (drawLock)
                {
                    Monitor.Pulse(drawLock);
                }
            };

            drawThread.Start();

            window.Visible = true;
            while (windowServer.Windows.Count > 0)
            {
                windowServer.ProcessEvents(-1);
            }

            Console.WriteLine("Rotate Cube done.");
        }