diff --git a/Quik.Media.Defaults/QImageStbi.cs b/Quik.Media.Defaults/QImageStbi.cs index 06207d4..55b1dce 100644 --- a/Quik.Media.Defaults/QImageStbi.cs +++ b/Quik.Media.Defaults/QImageStbi.cs @@ -11,12 +11,14 @@ namespace Quik.Media.Defaults { private readonly StbImage image; private QImageBuffer buffer; + private bool isSdf = false; public override int Width => image.Width; public override int Height => image.Height; public override int Depth => 1; + public override bool IsSdf => isSdf; public override QImageFormat InternalFormat => Stb2QImageFormat(image.Format); public QImageStbi(Stream source) @@ -79,6 +81,11 @@ namespace Quik.Media.Defaults buffer.UnlockBits(); } + public void SdfHint(bool value = true) + { + isSdf = value; + } + protected override void Dispose(bool disposing) { base.Dispose(disposing); diff --git a/Quik/Media/QImage.cs b/Quik/Media/QImage.cs index 8732f27..fc3975f 100644 --- a/Quik/Media/QImage.cs +++ b/Quik/Media/QImage.cs @@ -10,6 +10,7 @@ namespace Quik.Media public abstract QImageFormat InternalFormat { get; } public virtual int MipMapLevels => 0; public virtual bool Premultiplied => false; + public virtual bool IsSdf => false; public abstract void LockBits2d(out QImageLock imageLock, QImageLockOptions options); public abstract void LockBits3d(out QImageLock imageLock, QImageLockOptions options); diff --git a/Quik/OpenGL/GL21Driver.cs b/Quik/OpenGL/GL21Driver.cs index 5d3cd81..9dc671f 100644 --- a/Quik/OpenGL/GL21Driver.cs +++ b/Quik/OpenGL/GL21Driver.cs @@ -3,6 +3,8 @@ using System.IO; using System.Collections.Generic; using Quik.VertexGenerator; using static Quik.OpenGL.GLEnum; +using Quik.Media; +using System.Linq; namespace Quik.OpenGL { @@ -10,18 +12,23 @@ namespace Quik.OpenGL { private int program; private int v2Position; - private int iZIndex; + private int fZIndex; private int v2TexPos; + private int fTexLayer; private int v4Color; private int m4Transforms; - private int iMaxZ; + private int fMaxZ; private int iEnableSdf; private int iEnableTexture; private int iAlphaDiscard; private int fSdfThreshold; - private int txTexture; + private int tx2d; + private int tx2darray; + private bool isDiposed; private readonly Dictionary data = new Dictionary(); + private readonly TextureManager textures = new TextureManager(); + public bool IsInit { get; private set; } = false; public event Action OnGCDispose; @@ -57,16 +64,19 @@ namespace Quik.OpenGL GL.DeleteShader(fs); v2Position = GL.GetAttribLocation(program, nameof(v2Position)); - iZIndex = GL.GetAttribLocation(program, nameof(iZIndex)); + fZIndex = GL.GetAttribLocation(program, nameof(fZIndex)); v2TexPos = GL.GetAttribLocation(program, nameof(v2TexPos)); + fTexLayer = GL.GetAttribLocation(program, nameof(fTexLayer)); v4Color = GL.GetAttribLocation(program, nameof(v4Color)); m4Transforms = GL.GetUniformLocation(program, nameof(m4Transforms)); - iMaxZ = GL.GetUniformLocation(program, nameof(iMaxZ)); + fMaxZ = GL.GetUniformLocation(program, nameof(fMaxZ)); + fSdfThreshold = GL.GetUniformLocation(program, nameof(fSdfThreshold)); iEnableSdf = GL.GetUniformLocation(program, nameof(iEnableSdf)); - iEnableTexture = GL.GetUniformLocation(program, nameof(iEnableSdf)); + iEnableTexture = GL.GetUniformLocation(program, nameof(iEnableTexture)); iAlphaDiscard = GL.GetUniformLocation(program, nameof(iAlphaDiscard)); - txTexture = GL.GetUniformLocation(program, nameof(txTexture)); + tx2d = GL.GetUniformLocation(program, nameof(tx2d)); + tx2darray = GL.GetUniformLocation(program, nameof(tx2darray)); IsInit = true; } @@ -93,23 +103,39 @@ namespace Quik.OpenGL QMat4.Orthographic(out QMat4 matrix, view); GL.UseProgram(program); - GL.Uniform1(iMaxZ, queue.ZDepth+1); + GL.Uniform1(fMaxZ, (float)(queue.ZDepth+1)); GL.UniformMatrix4(m4Transforms, false, in matrix); GL.Uniform1(fSdfThreshold, 0.0f); - GL.Uniform1(txTexture, 0); + GL.Uniform1(tx2d, 0); GL.Enable(GL_BLEND); GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); foreach (DrawCall call in queue) { + GL.Viewport( + (int)call.Bounds.Left, + (int)(view.Bottom - call.Bounds.Bottom), + (int)call.Bounds.Right, + (int)(view.Bottom - call.Bounds.Top)); GL.ActiveTexture(GL_TEXTURE0); GL.BindTexture(GL_TEXTURE_2D, 0); if (call.Texture != null) { - // TODO: actually use textures? - GL.Uniform1(iEnableTexture, 1); - GL.Uniform1(iEnableSdf, call.Texture.SignedDistanceField ? 1 : 0); + GL.Uniform1(iEnableSdf, call.Texture.IsSdf ? 1 : 0); GL.Uniform1(iAlphaDiscard, 1); + + if (call.Texture.Depth > 1) + { + GL.Uniform1(iEnableTexture, 3); + GL.ActiveTexture(GL_TEXTURE1); + GL.BindTexture(GL_TEXTURE_2D_ARRAY, textures.GetTexture(call.Texture)); + } + else + { + GL.Uniform1(iEnableTexture, 2); + GL.ActiveTexture(GL_TEXTURE0); + GL.BindTexture(GL_TEXTURE_2D, textures.GetTexture(call.Texture)); + } } else { @@ -254,13 +280,15 @@ namespace Quik.OpenGL GL.BufferData(GL_ARRAY_BUFFER, QuikVertex.Stride * Queue.VertexCount, Queue.VertexArray, GL_STREAM_DRAW); GL.VertexAttribPointer(driver.v2Position, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.PositionOffset); - GL.VertexAttribIPointer(driver.iZIndex, 1, GL_UNSIGNED_INT, QuikVertex.Stride, QuikVertex.ZIndexOffset); + GL.VertexAttribPointer(driver.fZIndex, 1, GL_UNSIGNED_INT, false, QuikVertex.Stride, QuikVertex.ZIndexOffset); GL.VertexAttribPointer(driver.v2TexPos, 2, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureCoordinatesOffset); + GL.VertexAttribPointer(driver.fTexLayer, 1, GL_FLOAT, false, QuikVertex.Stride, QuikVertex.TextureLayerOffset); GL.VertexAttribPointer(driver.v4Color, 4, GL_UNSIGNED_BYTE, true, QuikVertex.Stride, QuikVertex.ColorOffset); GL.EnableVertexAttribArray(driver.v2Position); - GL.EnableVertexAttribArray(driver.iZIndex); + GL.EnableVertexAttribArray(driver.fZIndex); GL.EnableVertexAttribArray(driver.v2TexPos); GL.EnableVertexAttribArray(driver.v4Color); + GL.EnableVertexAttribArray(driver.fTexLayer); GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, Queue.ElementCount * sizeof(int), Queue.ElementArray, GL_STREAM_DRAW); @@ -287,4 +315,115 @@ namespace Quik.OpenGL } } } + + internal class TextureManager : IDisposable + { + private readonly Dictionary textures = new Dictionary(); + private readonly HashSet imagesNotUsed = new HashSet(); + private bool isDisposed = false; + + public void BeginFrame() + { + if (imagesNotUsed.Count > 0) + { + foreach (QImage image in imagesNotUsed) + { + GL.DeleteTexture(textures[image]); + } + + imagesNotUsed.Clear(); + } + + foreach (QImage image in textures.Keys) + { + imagesNotUsed.Add(image); + } + } + + public int GetTexture(QImage image) + { + if (textures.TryGetValue(image, out int texture)) + { + return texture; + } + + if (image.Depth > 1) + { + texture = UploadTexture3d(image); + } + else + { + texture = UploadTexture2d(image); + } + + return textures[image] = texture; + } + + public int UploadTexture3d(QImage image3d) + { + int texture = GL.GenTexture(); + + GL.BindTexture(GL_TEXTURE_2D_ARRAY, texture); + + image3d.LockBits3d(out QImageLock lck, QImageLockOptions.Default); + GL.TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, lck.Width, lck.Height, lck.Depth, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr); + image3d.UnlockBits(); + + GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + return texture; + } + + public int UploadTexture2d(QImage image2d) + { + int texture = GL.GenTexture(); + + GL.BindTexture(GL_TEXTURE_2D, texture); + + image2d.LockBits2d(out QImageLock lck, QImageLockOptions.Default); + GL.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lck.Width, lck.Height, 0, s_InternalFormat[lck.Format], s_PixelType[lck.Format], lck.ImagePtr); + image2d.UnlockBits(); + + GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + GL.TexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + return texture; + } + + public void Dispose() + { + if (isDisposed) + return; + + isDisposed = true; + + int[] ids = textures.Values.ToArray(); + GL.DeleteTextures(ids); + } + + private static readonly Dictionary s_InternalFormat = new Dictionary() + { + [QImageFormat.AlphaF] = GL_ALPHA, + [QImageFormat.AlphaU8] = GL_ALPHA, + [QImageFormat.RedF] = GL_RED, + [QImageFormat.RedU8] = GL_RED, + [QImageFormat.RgbF] = GL_RGB, + [QImageFormat.RgbU8] = GL_RGB, + [QImageFormat.RgbaU8] = GL_RGBA, + [QImageFormat.RgbaF] = GL_RGBA, + }; + + private static readonly Dictionary s_PixelType = new Dictionary() + { + [QImageFormat.AlphaF] = GL_FLOAT, + [QImageFormat.RedF] = GL_FLOAT, + [QImageFormat.RgbF] = GL_FLOAT, + [QImageFormat.RgbaF] = GL_FLOAT, + [QImageFormat.AlphaU8] = GL_UNSIGNED_BYTE, + [QImageFormat.RedU8] = GL_UNSIGNED_BYTE, + [QImageFormat.RgbU8] = GL_UNSIGNED_BYTE, + [QImageFormat.RgbaU8] = GL_UNSIGNED_BYTE, + }; + } } \ No newline at end of file diff --git a/Quik/res/gl21.frag b/Quik/res/gl21.frag index d110c47..d8507f3 100644 --- a/Quik/res/gl21.frag +++ b/Quik/res/gl21.frag @@ -2,16 +2,34 @@ * QUIK: User Interface Kit * Copyright (C) 2023 Halit Utku Maden, et al. */ -#version 120 +#version 130 -varying vec2 fv2TexPos; -varying vec4 fv4Color; +in vec2 fv2TexPos; +in vec4 fv4Color; +in float ffTexLayer; -uniform int iEnableSdf; -uniform int iEnableTexture; -uniform int iAlphaDiscard; -uniform float fSdfThreshold; -uniform sampler2D txTexture; +out vec4 fragColor; + +uniform int iEnableSdf; +uniform int iEnableTexture; +uniform int iAlphaDiscard; +uniform float fSdfThreshold; +uniform sampler2D tx2d; +uniform sampler2DArray tx2dArray; + +const float fAlphaThreshold = 0.01; + +vec4 getTexture() +{ + if (iEnableTexture == 3) + { + return texture(tx2dArray, vec3(fv2TexPos, ffTexLayer)); + } + else + { + return texture(tx2d, fv2TexPos); + } +} void main(void) { @@ -19,16 +37,19 @@ void main(void) if (iEnableTexture != 0) { - vec4 value = texture2D(txTexture, fv2TexPos); + vec4 value = getTexture(); if (iEnableSdf != 0) { float a = max(value.r, value.a); - value = (a >= fSdfThreshold) ? vec4(1,1,1,1) : vec4(0,0,0,0); + value = + (a >= fSdfThreshold) ? + vec4(1.0,1.0,1.0,1.0) : + vec4(0.0,0.0,0.0,0.0); } - if (iAlphaDiscard != 0 && value.a == 0.0) + if (iAlphaDiscard != 0 && value.a <= fAlphaThreshold) { discard; } @@ -36,5 +57,5 @@ void main(void) albedo = albedo * value; } - gl_FragColor = albedo; + fragColor = albedo; } \ No newline at end of file diff --git a/Quik/res/gl21.vert b/Quik/res/gl21.vert index 53f1532..f738890 100644 --- a/Quik/res/gl21.vert +++ b/Quik/res/gl21.vert @@ -2,24 +2,34 @@ * QUIK: User Interface Kit * Copyright (C) 2023 Halit Utku Maden, et al. */ -#version 120 +#version 130 -attribute vec2 v2Position; /**< The vertex position.*/ -attribute int iZIndex; /**< The z index. */ -attribute vec2 v2TexPos; /**< The texture coorindates. */ -attribute vec4 v4Color; /**< The vertex color. */ +in vec2 v2Position; /**< The vertex position.*/ +in float fZIndex; /**< The z index. */ +in vec2 v2TexPos; /**< The texture coorindates. */ +in float fTexLayer; /**< The texture layer for 3D textures. */ +in vec4 v4Color; /**< The vertex color. */ -varying vec2 fv2TexPos; -varying vec4 fv4Color; +out vec2 fv2TexPos; +out float ffTexLayer; +out vec4 fv4Color; uniform mat4 m4Transforms; /**< The view matrix. */ -uniform int iMaxZ; /**< Highest Z coordinate. */ +uniform float fMaxZ; /**< Highest Z coordinate. */ + +const mat4 m4BaseTransforms = mat4( + vec4( 2.0, 0.0, 0.0, 0.0), + vec4( 0.0, -2.0, 0.0, 0.0), + vec4( 0.0, 0.0, 1.0, 0.0), + vec4(-1.0, 1.0, 0.0, 1.0) +); void main(void) { - vec4 v = vec4(v2Position, float(iZIndex)/float(iMaxZ), 1); - gl_Position = v * m4Transforms; + vec4 v = vec4(v2Position, fZIndex/fMaxZ, 1); + gl_Position = m4Transforms * v; fv2TexPos = v2TexPos; fv4Color = v4Color; -} \ No newline at end of file + ffTexLayer = fTexLayer; +}