Browse Source
* OpenGL via API lease example * Hide glClearDepth internals * Missing filepull/15722/head
committed by
GitHub
15 changed files with 875 additions and 426 deletions
@ -0,0 +1,30 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.Pages.OpenGl.GlPageKnobs" |
||||
|
xmlns:pages="using:ControlCatalog.Pages" |
||||
|
xmlns:gl="using:ControlCatalog.Pages.OpenGl" |
||||
|
> |
||||
|
<Grid> |
||||
|
<StackPanel> |
||||
|
<TextBlock Text="{Binding $parent[gl:GlPageKnobs].Info}"/> |
||||
|
</StackPanel> |
||||
|
<Grid ColumnDefinitions="*,Auto" Margin="20"> |
||||
|
<StackPanel Grid.Column="1" MinWidth="300"> |
||||
|
<TextBlock>Yaw</TextBlock> |
||||
|
<Slider Value="{Binding $parent[gl:GlPageKnobs].Yaw, Mode=TwoWay}" Maximum="10"/> |
||||
|
<TextBlock>Pitch</TextBlock> |
||||
|
<Slider Value="{Binding $parent[gl:GlPageKnobs].Pitch, Mode=TwoWay}" Maximum="10"/> |
||||
|
<TextBlock>Roll</TextBlock> |
||||
|
<Slider Value="{Binding $parent[gl:GlPageKnobs].Roll, Mode=TwoWay}" Maximum="10"/> |
||||
|
<StackPanel Orientation="Horizontal"> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock> |
||||
|
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock> |
||||
|
</StackPanel> |
||||
|
<Slider Value="{Binding $parent[gl:GlPageKnobs].Disco, Mode=TwoWay}" Maximum="1"/> |
||||
|
</StackPanel> |
||||
|
</Grid> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,70 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace ControlCatalog.Pages.OpenGl; |
||||
|
|
||||
|
public partial class GlPageKnobs : UserControl |
||||
|
{ |
||||
|
public GlPageKnobs() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private float _yaw; |
||||
|
|
||||
|
public static readonly DirectProperty<GlPageKnobs, float> YawProperty = |
||||
|
AvaloniaProperty.RegisterDirect<GlPageKnobs, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v); |
||||
|
|
||||
|
public float Yaw |
||||
|
{ |
||||
|
get => _yaw; |
||||
|
set => SetAndRaise(YawProperty, ref _yaw, value); |
||||
|
} |
||||
|
|
||||
|
private float _pitch; |
||||
|
|
||||
|
public static readonly DirectProperty<GlPageKnobs, float> PitchProperty = |
||||
|
AvaloniaProperty.RegisterDirect<GlPageKnobs, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v); |
||||
|
|
||||
|
public float Pitch |
||||
|
{ |
||||
|
get => _pitch; |
||||
|
set => SetAndRaise(PitchProperty, ref _pitch, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private float _roll; |
||||
|
|
||||
|
public static readonly DirectProperty<GlPageKnobs, float> RollProperty = |
||||
|
AvaloniaProperty.RegisterDirect<GlPageKnobs, float>("Roll", o => o.Roll, (o, v) => o.Roll = v); |
||||
|
|
||||
|
public float Roll |
||||
|
{ |
||||
|
get => _roll; |
||||
|
set => SetAndRaise(RollProperty, ref _roll, value); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private float _disco; |
||||
|
|
||||
|
public static readonly DirectProperty<GlPageKnobs, float> DiscoProperty = |
||||
|
AvaloniaProperty.RegisterDirect<GlPageKnobs, float>("Disco", o => o.Disco, (o, v) => o.Disco = v); |
||||
|
|
||||
|
public float Disco |
||||
|
{ |
||||
|
get => _disco; |
||||
|
set => SetAndRaise(DiscoProperty, ref _disco, value); |
||||
|
} |
||||
|
|
||||
|
private string _info = string.Empty; |
||||
|
|
||||
|
public static readonly DirectProperty<GlPageKnobs, string> InfoProperty = |
||||
|
AvaloniaProperty.RegisterDirect<GlPageKnobs, string>("Info", o => o.Info, (o, v) => o.Info = v); |
||||
|
|
||||
|
public string Info |
||||
|
{ |
||||
|
get => _info; |
||||
|
private set => SetAndRaise(InfoProperty, ref _info, value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,311 @@ |
|||||
|
using System; |
||||
|
using System.Diagnostics; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia; |
||||
|
using Avalonia.OpenGL; |
||||
|
using static Avalonia.OpenGL.GlConsts; |
||||
|
// ReSharper disable StringLiteralTypo
|
||||
|
|
||||
|
namespace ControlCatalog.Pages.OpenGl; |
||||
|
|
||||
|
internal class OpenGlContent |
||||
|
{ |
||||
|
private int _vertexShader; |
||||
|
private int _fragmentShader; |
||||
|
private int _shaderProgram; |
||||
|
private int _vertexBufferObject; |
||||
|
private int _indexBufferObject; |
||||
|
private int _vertexArrayObject; |
||||
|
private GlVersion GlVersion; |
||||
|
|
||||
|
private string GetShader(bool fragment, string shader) |
||||
|
{ |
||||
|
var version = (GlVersion.Type == GlProfileType.OpenGL |
||||
|
? RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 120 |
||||
|
: 100); |
||||
|
var data = "#version " + version + "\n"; |
||||
|
if (GlVersion.Type == GlProfileType.OpenGLES) |
||||
|
data += "precision mediump float;\n"; |
||||
|
if (version >= 150) |
||||
|
{ |
||||
|
shader = shader.Replace("attribute", "in"); |
||||
|
if (fragment) |
||||
|
shader = shader |
||||
|
.Replace("varying", "in") |
||||
|
.Replace("//DECLAREGLFRAG", "out vec4 outFragColor;") |
||||
|
.Replace("gl_FragColor", "outFragColor"); |
||||
|
else |
||||
|
shader = shader.Replace("varying", "out"); |
||||
|
} |
||||
|
|
||||
|
data += shader; |
||||
|
|
||||
|
return data; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private string VertexShaderSource => GetShader(false, @"
|
||||
|
attribute vec3 aPos; |
||||
|
attribute vec3 aNormal; |
||||
|
uniform mat4 uModel; |
||||
|
uniform mat4 uProjection; |
||||
|
uniform mat4 uView; |
||||
|
|
||||
|
varying vec3 FragPos; |
||||
|
varying vec3 VecPos; |
||||
|
varying vec3 Normal; |
||||
|
uniform float uTime; |
||||
|
uniform float uDisco; |
||||
|
void main() |
||||
|
{ |
||||
|
float discoScale = sin(uTime * 10.0) / 10.0; |
||||
|
float distortionX = 1.0 + uDisco * cos(uTime * 20.0) / 10.0; |
||||
|
|
||||
|
float scale = 1.0 + uDisco * discoScale; |
||||
|
|
||||
|
vec3 scaledPos = aPos; |
||||
|
scaledPos.x = scaledPos.x * distortionX; |
||||
|
|
||||
|
scaledPos *= scale; |
||||
|
gl_Position = uProjection * uView * uModel * vec4(scaledPos, 1.0); |
||||
|
FragPos = vec3(uModel * vec4(aPos, 1.0)); |
||||
|
VecPos = aPos; |
||||
|
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0))); |
||||
|
} |
||||
|
");
|
||||
|
|
||||
|
private string FragmentShaderSource => GetShader(true, @"
|
||||
|
varying vec3 FragPos; |
||||
|
varying vec3 VecPos; |
||||
|
varying vec3 Normal; |
||||
|
uniform float uMaxY; |
||||
|
uniform float uMinY; |
||||
|
uniform float uTime; |
||||
|
uniform float uDisco; |
||||
|
//DECLAREGLFRAG
|
||||
|
|
||||
|
void main() |
||||
|
{ |
||||
|
float y = (VecPos.y - uMinY) / (uMaxY - uMinY); |
||||
|
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + uTime * 40.0 + y * 50.0); |
||||
|
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - uTime * 20.0 - y * 30.0); |
||||
|
|
||||
|
vec3 discoColor = vec3( |
||||
|
0.5 + abs(0.5 - y) * cos(uTime * 10.0), |
||||
|
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)), |
||||
|
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0)))); |
||||
|
|
||||
|
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25); |
||||
|
objectColor = objectColor * (1.0 - uDisco) + discoColor * uDisco; |
||||
|
|
||||
|
float ambientStrength = 0.3; |
||||
|
vec3 lightColor = vec3(1.0, 1.0, 1.0); |
||||
|
vec3 lightPos = vec3(uMaxY * 2.0, uMaxY * 2.0, uMaxY * 2.0); |
||||
|
vec3 ambient = ambientStrength * lightColor; |
||||
|
|
||||
|
|
||||
|
vec3 norm = normalize(Normal); |
||||
|
vec3 lightDir = normalize(lightPos - FragPos); |
||||
|
|
||||
|
float diff = max(dot(norm, lightDir), 0.0); |
||||
|
vec3 diffuse = diff * lightColor; |
||||
|
|
||||
|
vec3 result = (ambient + diffuse) * objectColor; |
||||
|
gl_FragColor = vec4(result, 1.0); |
||||
|
|
||||
|
} |
||||
|
");
|
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)] |
||||
|
private struct Vertex |
||||
|
{ |
||||
|
public Vector3 Position; |
||||
|
public Vector3 Normal; |
||||
|
} |
||||
|
|
||||
|
private readonly Vertex[] _points; |
||||
|
private readonly ushort[] _indices; |
||||
|
private readonly float _minY; |
||||
|
private readonly float _maxY; |
||||
|
|
||||
|
public OpenGlContent() |
||||
|
{ |
||||
|
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin")); |
||||
|
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)!)) |
||||
|
{ |
||||
|
var buf = new byte[sr.ReadInt32()]; |
||||
|
sr.Read(buf, 0, buf.Length); |
||||
|
var points = new float[buf.Length / 4]; |
||||
|
Buffer.BlockCopy(buf, 0, points, 0, buf.Length); |
||||
|
buf = new byte[sr.ReadInt32()]; |
||||
|
sr.Read(buf, 0, buf.Length); |
||||
|
_indices = new ushort[buf.Length / 2]; |
||||
|
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length); |
||||
|
_points = new Vertex[points.Length / 3]; |
||||
|
for (var primitive = 0; primitive < points.Length / 3; primitive++) |
||||
|
{ |
||||
|
var srci = primitive * 3; |
||||
|
_points[primitive] = new Vertex |
||||
|
{ |
||||
|
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2]) |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < _indices.Length; i += 3) |
||||
|
{ |
||||
|
Vector3 a = _points[_indices[i]].Position; |
||||
|
Vector3 b = _points[_indices[i + 1]].Position; |
||||
|
Vector3 c = _points[_indices[i + 2]].Position; |
||||
|
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b)); |
||||
|
|
||||
|
_points[_indices[i]].Normal += normal; |
||||
|
_points[_indices[i + 1]].Normal += normal; |
||||
|
_points[_indices[i + 2]].Normal += normal; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < _points.Length; i++) |
||||
|
{ |
||||
|
_points[i].Normal = Vector3.Normalize(_points[i].Normal); |
||||
|
_maxY = Math.Max(_maxY, _points[i].Position.Y); |
||||
|
_minY = Math.Min(_minY, _points[i].Position.Y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static void CheckError(GlInterface gl) |
||||
|
{ |
||||
|
int err; |
||||
|
while ((err = gl.GetError()) != GL_NO_ERROR) |
||||
|
Console.WriteLine(err); |
||||
|
} |
||||
|
|
||||
|
public string Info { get; private set; } |
||||
|
|
||||
|
public unsafe void Init(GlInterface GL, GlVersion version) |
||||
|
{ |
||||
|
GlVersion = version; |
||||
|
CheckError(GL); |
||||
|
|
||||
|
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}"; |
||||
|
|
||||
|
// Load the source of the vertex shader and compile it.
|
||||
|
_vertexShader = GL.CreateShader(GL_VERTEX_SHADER); |
||||
|
Console.WriteLine(GL.CompileShaderAndGetError(_vertexShader, VertexShaderSource)); |
||||
|
|
||||
|
// Load the source of the fragment shader and compile it.
|
||||
|
_fragmentShader = GL.CreateShader(GL_FRAGMENT_SHADER); |
||||
|
Console.WriteLine(GL.CompileShaderAndGetError(_fragmentShader, FragmentShaderSource)); |
||||
|
|
||||
|
// Create the shader program, attach the vertex and fragment shaders and link the program.
|
||||
|
_shaderProgram = GL.CreateProgram(); |
||||
|
GL.AttachShader(_shaderProgram, _vertexShader); |
||||
|
GL.AttachShader(_shaderProgram, _fragmentShader); |
||||
|
const int positionLocation = 0; |
||||
|
const int normalLocation = 1; |
||||
|
GL.BindAttribLocationString(_shaderProgram, positionLocation, "aPos"); |
||||
|
GL.BindAttribLocationString(_shaderProgram, normalLocation, "aNormal"); |
||||
|
Console.WriteLine(GL.LinkProgramAndGetError(_shaderProgram)); |
||||
|
CheckError(GL); |
||||
|
|
||||
|
// Create the vertex buffer object (VBO) for the vertex data.
|
||||
|
_vertexBufferObject = GL.GenBuffer(); |
||||
|
// Bind the VBO and copy the vertex data into it.
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
||||
|
CheckError(GL); |
||||
|
var vertexSize = Marshal.SizeOf<Vertex>(); |
||||
|
fixed (void* pdata = _points) |
||||
|
GL.BufferData(GL_ARRAY_BUFFER, new IntPtr(_points.Length * vertexSize), |
||||
|
new IntPtr(pdata), GL_STATIC_DRAW); |
||||
|
|
||||
|
_indexBufferObject = GL.GenBuffer(); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
||||
|
CheckError(GL); |
||||
|
fixed (void* pdata = _indices) |
||||
|
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), |
||||
|
GL_STATIC_DRAW); |
||||
|
CheckError(GL); |
||||
|
_vertexArrayObject = GL.GenVertexArray(); |
||||
|
GL.BindVertexArray(_vertexArrayObject); |
||||
|
CheckError(GL); |
||||
|
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT, |
||||
|
0, vertexSize, IntPtr.Zero); |
||||
|
GL.VertexAttribPointer(normalLocation, 3, GL_FLOAT, |
||||
|
0, vertexSize, new IntPtr(12)); |
||||
|
GL.EnableVertexAttribArray(positionLocation); |
||||
|
GL.EnableVertexAttribArray(normalLocation); |
||||
|
CheckError(GL); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Deinit(GlInterface GL) |
||||
|
{ |
||||
|
// Unbind everything
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, 0); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); |
||||
|
GL.BindVertexArray(0); |
||||
|
GL.UseProgram(0); |
||||
|
|
||||
|
// Delete all resources.
|
||||
|
GL.DeleteBuffer(_vertexBufferObject); |
||||
|
GL.DeleteBuffer(_indexBufferObject); |
||||
|
GL.DeleteVertexArray(_vertexArrayObject); |
||||
|
GL.DeleteProgram(_shaderProgram); |
||||
|
GL.DeleteShader(_fragmentShader); |
||||
|
GL.DeleteShader(_vertexShader); |
||||
|
} |
||||
|
|
||||
|
static Stopwatch St = Stopwatch.StartNew(); |
||||
|
|
||||
|
public unsafe void OnOpenGlRender(GlInterface gl, int fb, PixelSize size, |
||||
|
float yaw, float pitch, float roll, float disco) |
||||
|
{ |
||||
|
gl.Viewport(0, 0, size.Width, size.Height); |
||||
|
gl.ClearDepth(1); |
||||
|
gl.Disable(GL_CULL_FACE); |
||||
|
gl.Disable(GL_SCISSOR_TEST); |
||||
|
gl.DepthFunc(GL_LESS); |
||||
|
gl.DepthMask(1); |
||||
|
|
||||
|
gl.ClearColor(0, 0, 0, 0); |
||||
|
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
||||
|
gl.Enable(GL_DEPTH_TEST); |
||||
|
|
||||
|
|
||||
|
var GL = gl; |
||||
|
|
||||
|
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
||||
|
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
||||
|
GL.BindVertexArray(_vertexArrayObject); |
||||
|
GL.UseProgram(_shaderProgram); |
||||
|
CheckError(GL); |
||||
|
var projection = |
||||
|
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(size.Width / size.Height), |
||||
|
0.01f, 1000); |
||||
|
|
||||
|
|
||||
|
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, 1, 0)); |
||||
|
var model = Matrix4x4.CreateFromYawPitchRoll(yaw, pitch, roll); |
||||
|
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); |
||||
|
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); |
||||
|
var projectionLoc = GL.GetUniformLocationString(_shaderProgram, "uProjection"); |
||||
|
var maxYLoc = GL.GetUniformLocationString(_shaderProgram, "uMaxY"); |
||||
|
var minYLoc = GL.GetUniformLocationString(_shaderProgram, "uMinY"); |
||||
|
var timeLoc = GL.GetUniformLocationString(_shaderProgram, "uTime"); |
||||
|
var discoLoc = GL.GetUniformLocationString(_shaderProgram, "uDisco"); |
||||
|
GL.UniformMatrix4fv(modelLoc, 1, false, &model); |
||||
|
GL.UniformMatrix4fv(viewLoc, 1, false, &view); |
||||
|
GL.UniformMatrix4fv(projectionLoc, 1, false, &projection); |
||||
|
GL.Uniform1f(maxYLoc, _maxY); |
||||
|
GL.Uniform1f(minYLoc, _minY); |
||||
|
GL.Uniform1f(timeLoc, (float)St.Elapsed.TotalSeconds); |
||||
|
GL.Uniform1f(discoLoc, disco); |
||||
|
CheckError(GL); |
||||
|
GL.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero); |
||||
|
|
||||
|
CheckError(GL); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Logging; |
||||
|
using Avalonia.OpenGL; |
||||
|
using SkiaSharp; |
||||
|
using static Avalonia.OpenGL.GlConsts; |
||||
|
namespace ControlCatalog.Pages.OpenGl; |
||||
|
|
||||
|
internal class OpenGlFbo : IDisposable |
||||
|
{ |
||||
|
private readonly GRContext _grContext; |
||||
|
private int _fbo; |
||||
|
private int _depthBuffer; |
||||
|
private int _texture; |
||||
|
private PixelSize _size; |
||||
|
public PixelSize Size => _size; |
||||
|
public GlInterface Gl => Context.GlInterface; |
||||
|
public IGlContext Context { get; } |
||||
|
|
||||
|
public OpenGlFbo(IGlContext context, GRContext grContext) |
||||
|
{ |
||||
|
_grContext = grContext; |
||||
|
Context = context; |
||||
|
_fbo = Gl.GenFramebuffer(); |
||||
|
} |
||||
|
|
||||
|
public void Resize(PixelSize size) |
||||
|
{ |
||||
|
if(_size == size) |
||||
|
return; |
||||
|
|
||||
|
if (_texture != 0) |
||||
|
Gl.DeleteTexture(_texture); |
||||
|
_texture = 0; |
||||
|
if(_depthBuffer != 0) |
||||
|
Gl.DeleteRenderbuffer(_depthBuffer); |
||||
|
_depthBuffer = 0; |
||||
|
Gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo); |
||||
|
|
||||
|
_texture = Gl.GenTexture(); |
||||
|
|
||||
|
var textureFormat = Context.Version.Type == GlProfileType.OpenGLES && Context.Version.Major == 2 |
||||
|
? GL_RGBA |
||||
|
: GL_RGBA8; |
||||
|
|
||||
|
Gl.BindTexture(GL_TEXTURE_2D, _texture); |
||||
|
Gl.TexImage2D(GL_TEXTURE_2D, 0, textureFormat, size.Width, size.Height, 0, GL_RGBA, |
||||
|
GL_UNSIGNED_BYTE, IntPtr.Zero); |
||||
|
Gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); |
||||
|
|
||||
|
_depthBuffer = Gl.GenRenderbuffer(); |
||||
|
var depthFormat = Context.Version.Type == GlProfileType.OpenGLES |
||||
|
? GL_DEPTH_COMPONENT16 |
||||
|
: GL_DEPTH_COMPONENT; |
||||
|
Gl.BindRenderbuffer(GL_RENDERBUFFER, _depthBuffer); |
||||
|
Gl.RenderbufferStorage(GL_RENDERBUFFER, depthFormat, size.Width, size.Height); |
||||
|
Gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer); |
||||
|
|
||||
|
var status = Gl.CheckFramebufferStatus(GL_FRAMEBUFFER); |
||||
|
IsValid = (status == GL_FRAMEBUFFER_COMPLETE); |
||||
|
if(!IsValid) |
||||
|
{ |
||||
|
int code = Gl.GetError(); |
||||
|
Console.WriteLine("Unable to configure OpenGL FBO: " + code); |
||||
|
} |
||||
|
|
||||
|
_size = size; |
||||
|
} |
||||
|
|
||||
|
public bool IsValid { get; private set; } |
||||
|
|
||||
|
public int Fbo => _fbo; |
||||
|
|
||||
|
public SKImage? Snapshot() |
||||
|
{ |
||||
|
Gl.Flush(); |
||||
|
_grContext.ResetContext(); |
||||
|
|
||||
|
using var texture = new GRBackendTexture(_size.Width, _size.Height, false, |
||||
|
new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)_texture, SKColorType.Rgba8888.ToGlSizedFormat())); |
||||
|
|
||||
|
var surf = SKSurface.Create(_grContext, texture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); |
||||
|
if (surf == null) |
||||
|
{ |
||||
|
using var unformatted = new GRBackendTexture(_size.Width, _size.Height, false, |
||||
|
new GRGlTextureInfo(GlConsts.GL_TEXTURE_2D, (uint)_texture)); |
||||
|
|
||||
|
surf = SKSurface.Create(_grContext, unformatted, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888); |
||||
|
} |
||||
|
|
||||
|
SKImage? rv; |
||||
|
using (surf) |
||||
|
rv = surf?.Snapshot(); |
||||
|
_grContext.Flush(); |
||||
|
return rv; |
||||
|
/* |
||||
|
var target = new GRBackendRenderTarget(_size.Width, _size.Height, 0, 0, |
||||
|
new GRGlFramebufferInfo((uint)_fbo, SKColorType.Rgba8888.ToGlSizedFormat())); |
||||
|
SKImage rv; |
||||
|
using (var surface = SKSurface.Create(_grContext, target, |
||||
|
GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888, |
||||
|
new SKSurfaceProperties(SKPixelGeometry.RgbHorizontal))) |
||||
|
rv = surface.Snapshot(); |
||||
|
_grContext.Flush(); |
||||
|
return rv;*/ |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if(_fbo != 0) |
||||
|
Gl.DeleteFramebuffer(_fbo); |
||||
|
_fbo = 0; |
||||
|
if (_depthBuffer != 0) |
||||
|
Gl.DeleteRenderbuffer(_depthBuffer); |
||||
|
if(_texture != 0) |
||||
|
Gl.DeleteTexture(_texture); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
<UserControl xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="ControlCatalog.Pages.OpenGlLeasePage" |
||||
|
xmlns:pages="using:ControlCatalog.Pages" |
||||
|
xmlns:openGl="clr-namespace:ControlCatalog.Pages.OpenGl"> |
||||
|
<Grid> |
||||
|
<Control x:Name="Viewport" /> |
||||
|
<openGl:GlPageKnobs x:Name="Knobs" /> |
||||
|
</Grid> |
||||
|
</UserControl> |
||||
@ -0,0 +1,216 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.OpenGL; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering.Composition; |
||||
|
using Avalonia.Skia; |
||||
|
using ControlCatalog.Pages.OpenGl; |
||||
|
using SkiaSharp; |
||||
|
using static Avalonia.OpenGL.GlConsts; |
||||
|
|
||||
|
namespace ControlCatalog.Pages; |
||||
|
|
||||
|
public class OpenGlLeasePage : UserControl |
||||
|
{ |
||||
|
private readonly Control _viewport; |
||||
|
private readonly GlPageKnobs _knobs; |
||||
|
private CompositionCustomVisual? _visual; |
||||
|
|
||||
|
class GlVisual : CompositionCustomVisualHandler |
||||
|
{ |
||||
|
private OpenGlContent _content; |
||||
|
private Parameters _parameters; |
||||
|
private bool _contentInitialized; |
||||
|
private OpenGlFbo? _fbo; |
||||
|
private bool _reRender; |
||||
|
private IGlContext? _gl; |
||||
|
|
||||
|
public GlVisual(OpenGlContent content, Parameters parameters) |
||||
|
{ |
||||
|
_content = content; |
||||
|
_parameters = parameters; |
||||
|
} |
||||
|
|
||||
|
public override void OnRender(ImmediateDrawingContext drawingContext) |
||||
|
{ |
||||
|
if (_parameters.Disco > 0.01f) |
||||
|
RegisterForNextAnimationFrameUpdate(); |
||||
|
var bounds = GetRenderBounds(); |
||||
|
var size = PixelSize.FromSize(bounds.Size, 1); |
||||
|
if (size.Width < 1 || size.Height < 1) |
||||
|
return; |
||||
|
|
||||
|
if(drawingContext.TryGetFeature<ISkiaSharpApiLeaseFeature>(out var skiaFeature)) |
||||
|
{ |
||||
|
using var skiaLease = skiaFeature.Lease(); |
||||
|
var grContext = skiaLease.GrContext; |
||||
|
if (grContext == null) |
||||
|
return; |
||||
|
SKImage? snapshot; |
||||
|
using (var platformApiLease = skiaLease.TryLeasePlatformGraphicsApi()) |
||||
|
{ |
||||
|
if (platformApiLease?.Context is not IGlContext glContext) |
||||
|
return; |
||||
|
|
||||
|
var gl = glContext.GlInterface; |
||||
|
if (_gl != glContext) |
||||
|
{ |
||||
|
// The old context is lost
|
||||
|
_fbo = null; |
||||
|
_contentInitialized = false; |
||||
|
_gl = glContext; |
||||
|
} |
||||
|
|
||||
|
gl.GetIntegerv(GL_FRAMEBUFFER_BINDING, out var oldFb); |
||||
|
|
||||
|
_fbo ??= new OpenGlFbo(glContext, grContext); |
||||
|
if (_fbo.Size != size) |
||||
|
_fbo.Resize(size); |
||||
|
|
||||
|
gl.BindFramebuffer(GL_FRAMEBUFFER, _fbo.Fbo); |
||||
|
|
||||
|
|
||||
|
if (!_contentInitialized) |
||||
|
{ |
||||
|
_content.Init(gl, glContext.Version); |
||||
|
_contentInitialized = true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
_content.OnOpenGlRender(gl, _fbo.Fbo, size, _parameters.Yaw, _parameters.Pitch, |
||||
|
_parameters.Roll, _parameters.Disco); |
||||
|
|
||||
|
snapshot = _fbo.Snapshot(); |
||||
|
gl.BindFramebuffer(GL_FRAMEBUFFER, oldFb); |
||||
|
} |
||||
|
|
||||
|
using(snapshot) |
||||
|
if (snapshot != null) |
||||
|
skiaLease.SkCanvas.DrawImage(snapshot, new SKRect(0, 0, |
||||
|
(float)bounds.Width, (float)bounds.Height)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void OnAnimationFrameUpdate() |
||||
|
{ |
||||
|
if (_reRender || _parameters.Disco > 0.01f) |
||||
|
{ |
||||
|
_reRender = false; |
||||
|
Invalidate(); |
||||
|
} |
||||
|
|
||||
|
base.OnAnimationFrameUpdate(); |
||||
|
} |
||||
|
|
||||
|
public override void OnMessage(object message) |
||||
|
{ |
||||
|
if (message is Parameters p) |
||||
|
{ |
||||
|
_parameters = p; |
||||
|
_reRender = true; |
||||
|
RegisterForNextAnimationFrameUpdate(); |
||||
|
} |
||||
|
else if (message is DisposeMessage) |
||||
|
{ |
||||
|
if (_gl != null) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
if (_fbo != null || _contentInitialized) |
||||
|
{ |
||||
|
using (_gl.MakeCurrent()) |
||||
|
{ |
||||
|
if (_contentInitialized) |
||||
|
_content.Deinit(_gl.GlInterface); |
||||
|
_contentInitialized = false; |
||||
|
_fbo?.Dispose(); |
||||
|
_fbo = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
catch (Exception e) |
||||
|
{ |
||||
|
Console.WriteLine(e.ToString()); |
||||
|
} |
||||
|
|
||||
|
_gl = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
base.OnMessage(message); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class Parameters |
||||
|
{ |
||||
|
public float Yaw; |
||||
|
public float Pitch; |
||||
|
public float Roll; |
||||
|
public float Disco; |
||||
|
} |
||||
|
|
||||
|
public class DisposeMessage |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public OpenGlLeasePage() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
_viewport = this.FindControl<Control>("Viewport")!; |
||||
|
_viewport.AttachedToVisualTree += ViewportAttachedToVisualTree; |
||||
|
_viewport.DetachedFromVisualTree += ViewportDetachedFromVisualTree; |
||||
|
_knobs = this.FindControl<GlPageKnobs>("Knobs")!; |
||||
|
_knobs.PropertyChanged += KnobsPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
private void KnobsPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs change) |
||||
|
{ |
||||
|
if (change.Property == GlPageKnobs.YawProperty |
||||
|
|| change.Property == GlPageKnobs.RollProperty |
||||
|
|| change.Property == GlPageKnobs.PitchProperty |
||||
|
|| change.Property == GlPageKnobs.DiscoProperty) |
||||
|
_visual?.SendHandlerMessage(GetParameters()); |
||||
|
} |
||||
|
|
||||
|
Parameters GetParameters() => new() |
||||
|
{ |
||||
|
Yaw = _knobs!.Yaw, Pitch = _knobs.Pitch, Roll = _knobs.Roll, Disco = _knobs.Disco |
||||
|
}; |
||||
|
|
||||
|
private void ViewportAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
var visual = ElementComposition.GetElementVisual(_viewport!); |
||||
|
if(visual == null) |
||||
|
return; |
||||
|
_visual = visual.Compositor.CreateCustomVisual(new GlVisual(new OpenGlContent(), GetParameters())); |
||||
|
ElementComposition.SetElementChildVisual(_viewport, _visual); |
||||
|
UpdateSize(Bounds.Size); |
||||
|
} |
||||
|
|
||||
|
private void UpdateSize(Size size) |
||||
|
{ |
||||
|
if (_visual != null) |
||||
|
_visual.Size = new Vector(size.Width, size.Height); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
var size = base.ArrangeOverride(finalSize); |
||||
|
UpdateSize(size); |
||||
|
return size; |
||||
|
} |
||||
|
|
||||
|
private void ViewportDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
_visual?.SendHandlerMessage(new DisposeMessage()); |
||||
|
_visual = null; |
||||
|
ElementComposition.SetElementChildVisual(_viewport, null); |
||||
|
base.OnDetachedFromVisualTree(e); |
||||
|
} |
||||
|
} |
||||
@ -1,29 +1,10 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" |
<UserControl xmlns="https://github.com/avaloniaui" |
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
x:Class="ControlCatalog.Pages.OpenGlPage" |
x:Class="ControlCatalog.Pages.OpenGlPage" |
||||
xmlns:pages="using:ControlCatalog.Pages"> |
xmlns:pages="using:ControlCatalog.Pages" |
||||
|
xmlns:openGl="clr-namespace:ControlCatalog.Pages.OpenGl"> |
||||
<Grid> |
<Grid> |
||||
<pages:OpenGlPageControl x:Name="GL"/> |
<pages:OpenGlPageControl x:Name="GL"/> |
||||
<StackPanel> |
<openGl:GlPageKnobs x:Name="Knobs" /> |
||||
<TextBlock Text="{Binding #GL.Info}"/> |
|
||||
</StackPanel> |
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="20"> |
|
||||
<StackPanel Grid.Column="1" MinWidth="300"> |
|
||||
<TextBlock>Yaw</TextBlock> |
|
||||
<Slider Value="{Binding Yaw, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
|
||||
<TextBlock>Pitch</TextBlock> |
|
||||
<Slider Value="{Binding Pitch, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
|
||||
<TextBlock>Roll</TextBlock> |
|
||||
<Slider Value="{Binding Roll, Mode=TwoWay, ElementName=GL}" Maximum="10"/> |
|
||||
<StackPanel Orientation="Horizontal"> |
|
||||
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock> |
|
||||
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock> |
|
||||
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock> |
|
||||
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock> |
|
||||
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock> |
|
||||
</StackPanel> |
|
||||
<Slider Value="{Binding Disco, Mode=TwoWay, ElementName=GL}" Maximum="1"/> |
|
||||
</StackPanel> |
|
||||
</Grid> |
|
||||
</Grid> |
</Grid> |
||||
</UserControl> |
</UserControl> |
||||
|
|||||
@ -1,375 +1,56 @@ |
|||||
using System; |
|
||||
using System.Diagnostics; |
|
||||
using System.IO; |
|
||||
using System.Linq; |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.InteropServices; |
|
||||
using Avalonia; |
using Avalonia; |
||||
using Avalonia.Controls; |
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
using Avalonia.OpenGL; |
using Avalonia.OpenGL; |
||||
using Avalonia.OpenGL.Controls; |
using Avalonia.OpenGL.Controls; |
||||
using Avalonia.Platform.Interop; |
using ControlCatalog.Pages.OpenGl; |
||||
using Avalonia.Threading; |
|
||||
using static Avalonia.OpenGL.GlConsts; |
|
||||
// ReSharper disable StringLiteralTypo
|
// ReSharper disable StringLiteralTypo
|
||||
|
|
||||
namespace ControlCatalog.Pages |
namespace ControlCatalog.Pages |
||||
{ |
{ |
||||
public class OpenGlPage : UserControl |
public class OpenGlPage : UserControl |
||||
{ |
{ |
||||
|
public OpenGlPage() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
this.FindControl<OpenGlPageControl>("GL") |
||||
|
!.Init(this.FindControl<GlPageKnobs>("Knobs")!); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
public class OpenGlPageControl : OpenGlControlBase |
public class OpenGlPageControl : OpenGlControlBase |
||||
{ |
{ |
||||
private float _yaw; |
private OpenGlContent _content = new(); |
||||
|
private GlPageKnobs? _knobs; |
||||
public static readonly DirectProperty<OpenGlPageControl, float> YawProperty = |
|
||||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v); |
|
||||
|
|
||||
public float Yaw |
public void Init(GlPageKnobs knobs) |
||||
{ |
{ |
||||
get => _yaw; |
_knobs = knobs; |
||||
set => SetAndRaise(YawProperty, ref _yaw, value); |
_knobs.PropertyChanged += KnobsPropertyChanged; |
||||
} |
} |
||||
|
|
||||
private float _pitch; |
private void KnobsPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs change) |
||||
|
|
||||
public static readonly DirectProperty<OpenGlPageControl, float> PitchProperty = |
|
||||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v); |
|
||||
|
|
||||
public float Pitch |
|
||||
{ |
{ |
||||
get => _pitch; |
if (change.Property == GlPageKnobs.YawProperty |
||||
set => SetAndRaise(PitchProperty, ref _pitch, value); |
|| change.Property == GlPageKnobs.RollProperty |
||||
} |
|| change.Property == GlPageKnobs.PitchProperty |
||||
|
|| change.Property == GlPageKnobs.DiscoProperty) |
||||
|
RequestNextFrameRendering(); |
||||
private float _roll; |
|
||||
|
|
||||
public static readonly DirectProperty<OpenGlPageControl, float> RollProperty = |
|
||||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Roll", o => o.Roll, (o, v) => o.Roll = v); |
|
||||
|
|
||||
public float Roll |
|
||||
{ |
|
||||
get => _roll; |
|
||||
set => SetAndRaise(RollProperty, ref _roll, value); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private float _disco; |
|
||||
|
|
||||
public static readonly DirectProperty<OpenGlPageControl, float> DiscoProperty = |
|
||||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Disco", o => o.Disco, (o, v) => o.Disco = v); |
|
||||
|
|
||||
public float Disco |
|
||||
{ |
|
||||
get => _disco; |
|
||||
set => SetAndRaise(DiscoProperty, ref _disco, value); |
|
||||
} |
|
||||
|
|
||||
private string _info = string.Empty; |
|
||||
|
|
||||
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty = |
|
||||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v); |
|
||||
|
|
||||
public string Info |
|
||||
{ |
|
||||
get => _info; |
|
||||
private set => SetAndRaise(InfoProperty, ref _info, value); |
|
||||
} |
} |
||||
|
|
||||
private int _vertexShader; |
protected override unsafe void OnOpenGlInit(GlInterface GL) => _content.Init(GL, GlVersion); |
||||
private int _fragmentShader; |
|
||||
private int _shaderProgram; |
|
||||
private int _vertexBufferObject; |
|
||||
private int _indexBufferObject; |
|
||||
private int _vertexArrayObject; |
|
||||
|
|
||||
private string GetShader(bool fragment, string shader) |
|
||||
{ |
|
||||
var version = (GlVersion.Type == GlProfileType.OpenGL ? |
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 120 : |
|
||||
100); |
|
||||
var data = "#version " + version + "\n"; |
|
||||
if (GlVersion.Type == GlProfileType.OpenGLES) |
|
||||
data += "precision mediump float;\n"; |
|
||||
if (version >= 150) |
|
||||
{ |
|
||||
shader = shader.Replace("attribute", "in"); |
|
||||
if (fragment) |
|
||||
shader = shader |
|
||||
.Replace("varying", "in") |
|
||||
.Replace("//DECLAREGLFRAG", "out vec4 outFragColor;") |
|
||||
.Replace("gl_FragColor", "outFragColor"); |
|
||||
else |
|
||||
shader = shader.Replace("varying", "out"); |
|
||||
} |
|
||||
|
|
||||
data += shader; |
|
||||
|
|
||||
return data; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private string VertexShaderSource => GetShader(false, @"
|
|
||||
attribute vec3 aPos; |
|
||||
attribute vec3 aNormal; |
|
||||
uniform mat4 uModel; |
|
||||
uniform mat4 uProjection; |
|
||||
uniform mat4 uView; |
|
||||
|
|
||||
varying vec3 FragPos; |
|
||||
varying vec3 VecPos; |
|
||||
varying vec3 Normal; |
|
||||
uniform float uTime; |
|
||||
uniform float uDisco; |
|
||||
void main() |
|
||||
{ |
|
||||
float discoScale = sin(uTime * 10.0) / 10.0; |
|
||||
float distortionX = 1.0 + uDisco * cos(uTime * 20.0) / 10.0; |
|
||||
|
|
||||
float scale = 1.0 + uDisco * discoScale; |
|
||||
|
|
||||
vec3 scaledPos = aPos; |
|
||||
scaledPos.x = scaledPos.x * distortionX; |
|
||||
|
|
||||
scaledPos *= scale; |
|
||||
gl_Position = uProjection * uView * uModel * vec4(scaledPos, 1.0); |
|
||||
FragPos = vec3(uModel * vec4(aPos, 1.0)); |
|
||||
VecPos = aPos; |
|
||||
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0))); |
|
||||
} |
|
||||
");
|
|
||||
|
|
||||
private string FragmentShaderSource => GetShader(true, @"
|
|
||||
varying vec3 FragPos; |
|
||||
varying vec3 VecPos; |
|
||||
varying vec3 Normal; |
|
||||
uniform float uMaxY; |
|
||||
uniform float uMinY; |
|
||||
uniform float uTime; |
|
||||
uniform float uDisco; |
|
||||
//DECLAREGLFRAG
|
|
||||
|
|
||||
void main() |
|
||||
{ |
|
||||
float y = (VecPos.y - uMinY) / (uMaxY - uMinY); |
|
||||
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + uTime * 40.0 + y * 50.0); |
|
||||
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - uTime * 20.0 - y * 30.0); |
|
||||
|
|
||||
vec3 discoColor = vec3( |
|
||||
0.5 + abs(0.5 - y) * cos(uTime * 10.0), |
|
||||
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)), |
|
||||
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0)))); |
|
||||
|
|
||||
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25); |
|
||||
objectColor = objectColor * (1.0 - uDisco) + discoColor * uDisco; |
|
||||
|
|
||||
float ambientStrength = 0.3; |
|
||||
vec3 lightColor = vec3(1.0, 1.0, 1.0); |
|
||||
vec3 lightPos = vec3(uMaxY * 2.0, uMaxY * 2.0, uMaxY * 2.0); |
|
||||
vec3 ambient = ambientStrength * lightColor; |
|
||||
|
|
||||
|
protected override void OnOpenGlDeinit(GlInterface GL) => _content.Deinit(GL); |
||||
|
|
||||
vec3 norm = normalize(Normal); |
|
||||
vec3 lightDir = normalize(lightPos - FragPos); |
|
||||
|
|
||||
float diff = max(dot(norm, lightDir), 0.0); |
|
||||
vec3 diffuse = diff * lightColor; |
|
||||
|
|
||||
vec3 result = (ambient + diffuse) * objectColor; |
|
||||
gl_FragColor = vec4(result, 1.0); |
|
||||
|
|
||||
} |
|
||||
");
|
|
||||
|
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)] |
|
||||
private struct Vertex |
|
||||
{ |
|
||||
public Vector3 Position; |
|
||||
public Vector3 Normal; |
|
||||
} |
|
||||
|
|
||||
private readonly Vertex[] _points; |
|
||||
private readonly ushort[] _indices; |
|
||||
private readonly float _minY; |
|
||||
private readonly float _maxY; |
|
||||
|
|
||||
|
|
||||
public OpenGlPageControl() |
|
||||
{ |
|
||||
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin")); |
|
||||
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)!)) |
|
||||
{ |
|
||||
var buf = new byte[sr.ReadInt32()]; |
|
||||
sr.Read(buf, 0, buf.Length); |
|
||||
var points = new float[buf.Length / 4]; |
|
||||
Buffer.BlockCopy(buf, 0, points, 0, buf.Length); |
|
||||
buf = new byte[sr.ReadInt32()]; |
|
||||
sr.Read(buf, 0, buf.Length); |
|
||||
_indices = new ushort[buf.Length / 2]; |
|
||||
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length); |
|
||||
_points = new Vertex[points.Length / 3]; |
|
||||
for (var primitive = 0; primitive < points.Length / 3; primitive++) |
|
||||
{ |
|
||||
var srci = primitive * 3; |
|
||||
_points[primitive] = new Vertex |
|
||||
{ |
|
||||
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2]) |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
for (int i = 0; i < _indices.Length; i += 3) |
|
||||
{ |
|
||||
Vector3 a = _points[_indices[i]].Position; |
|
||||
Vector3 b = _points[_indices[i + 1]].Position; |
|
||||
Vector3 c = _points[_indices[i + 2]].Position; |
|
||||
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b)); |
|
||||
|
|
||||
_points[_indices[i]].Normal += normal; |
|
||||
_points[_indices[i + 1]].Normal += normal; |
|
||||
_points[_indices[i + 2]].Normal += normal; |
|
||||
} |
|
||||
|
|
||||
for (int i = 0; i < _points.Length; i++) |
|
||||
{ |
|
||||
_points[i].Normal = Vector3.Normalize(_points[i].Normal); |
|
||||
_maxY = Math.Max(_maxY, _points[i].Position.Y); |
|
||||
_minY = Math.Min(_minY, _points[i].Position.Y); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
private static void CheckError(GlInterface gl) |
|
||||
{ |
|
||||
int err; |
|
||||
while ((err = gl.GetError()) != GL_NO_ERROR) |
|
||||
Console.WriteLine(err); |
|
||||
} |
|
||||
|
|
||||
protected override unsafe void OnOpenGlInit(GlInterface GL) |
|
||||
{ |
|
||||
CheckError(GL); |
|
||||
|
|
||||
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}"; |
|
||||
|
|
||||
// Load the source of the vertex shader and compile it.
|
|
||||
_vertexShader = GL.CreateShader(GL_VERTEX_SHADER); |
|
||||
Console.WriteLine(GL.CompileShaderAndGetError(_vertexShader, VertexShaderSource)); |
|
||||
|
|
||||
// Load the source of the fragment shader and compile it.
|
|
||||
_fragmentShader = GL.CreateShader(GL_FRAGMENT_SHADER); |
|
||||
Console.WriteLine(GL.CompileShaderAndGetError(_fragmentShader, FragmentShaderSource)); |
|
||||
|
|
||||
// Create the shader program, attach the vertex and fragment shaders and link the program.
|
|
||||
_shaderProgram = GL.CreateProgram(); |
|
||||
GL.AttachShader(_shaderProgram, _vertexShader); |
|
||||
GL.AttachShader(_shaderProgram, _fragmentShader); |
|
||||
const int positionLocation = 0; |
|
||||
const int normalLocation = 1; |
|
||||
GL.BindAttribLocationString(_shaderProgram, positionLocation, "aPos"); |
|
||||
GL.BindAttribLocationString(_shaderProgram, normalLocation, "aNormal"); |
|
||||
Console.WriteLine(GL.LinkProgramAndGetError(_shaderProgram)); |
|
||||
CheckError(GL); |
|
||||
|
|
||||
// Create the vertex buffer object (VBO) for the vertex data.
|
|
||||
_vertexBufferObject = GL.GenBuffer(); |
|
||||
// Bind the VBO and copy the vertex data into it.
|
|
||||
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
|
||||
CheckError(GL); |
|
||||
var vertexSize = Marshal.SizeOf<Vertex>(); |
|
||||
fixed (void* pdata = _points) |
|
||||
GL.BufferData(GL_ARRAY_BUFFER, new IntPtr(_points.Length * vertexSize), |
|
||||
new IntPtr(pdata), GL_STATIC_DRAW); |
|
||||
|
|
||||
_indexBufferObject = GL.GenBuffer(); |
|
||||
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
|
||||
CheckError(GL); |
|
||||
fixed (void* pdata = _indices) |
|
||||
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), |
|
||||
GL_STATIC_DRAW); |
|
||||
CheckError(GL); |
|
||||
_vertexArrayObject = GL.GenVertexArray(); |
|
||||
GL.BindVertexArray(_vertexArrayObject); |
|
||||
CheckError(GL); |
|
||||
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT, |
|
||||
0, vertexSize, IntPtr.Zero); |
|
||||
GL.VertexAttribPointer(normalLocation, 3, GL_FLOAT, |
|
||||
0, vertexSize, new IntPtr(12)); |
|
||||
GL.EnableVertexAttribArray(positionLocation); |
|
||||
GL.EnableVertexAttribArray(normalLocation); |
|
||||
CheckError(GL); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
protected override void OnOpenGlDeinit(GlInterface GL) |
|
||||
{ |
|
||||
// Unbind everything
|
|
||||
GL.BindBuffer(GL_ARRAY_BUFFER, 0); |
|
||||
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); |
|
||||
GL.BindVertexArray(0); |
|
||||
GL.UseProgram(0); |
|
||||
|
|
||||
// Delete all resources.
|
|
||||
GL.DeleteBuffer(_vertexBufferObject); |
|
||||
GL.DeleteBuffer(_indexBufferObject); |
|
||||
GL.DeleteVertexArray(_vertexArrayObject); |
|
||||
GL.DeleteProgram(_shaderProgram); |
|
||||
GL.DeleteShader(_fragmentShader); |
|
||||
GL.DeleteShader(_vertexShader); |
|
||||
} |
|
||||
|
|
||||
static Stopwatch St = Stopwatch.StartNew(); |
|
||||
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb) |
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb) |
||||
{ |
{ |
||||
gl.ClearColor(0, 0, 0, 0); |
if (_knobs == null) |
||||
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
return; |
||||
gl.Enable(GL_DEPTH_TEST); |
_content.OnOpenGlRender(gl, fb, new PixelSize((int)Bounds.Width, (int)Bounds.Height), |
||||
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height); |
_knobs.Yaw, _knobs.Pitch, _knobs.Roll, _knobs.Disco); |
||||
var GL = gl; |
if (_knobs.Disco > 0.01) |
||||
|
|
||||
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
|
||||
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
|
||||
GL.BindVertexArray(_vertexArrayObject); |
|
||||
GL.UseProgram(_shaderProgram); |
|
||||
CheckError(GL); |
|
||||
var projection = |
|
||||
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(Bounds.Width / Bounds.Height), |
|
||||
0.01f, 1000); |
|
||||
|
|
||||
|
|
||||
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, 1, 0)); |
|
||||
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll); |
|
||||
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); |
|
||||
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); |
|
||||
var projectionLoc = GL.GetUniformLocationString(_shaderProgram, "uProjection"); |
|
||||
var maxYLoc = GL.GetUniformLocationString(_shaderProgram, "uMaxY"); |
|
||||
var minYLoc = GL.GetUniformLocationString(_shaderProgram, "uMinY"); |
|
||||
var timeLoc = GL.GetUniformLocationString(_shaderProgram, "uTime"); |
|
||||
var discoLoc = GL.GetUniformLocationString(_shaderProgram, "uDisco"); |
|
||||
GL.UniformMatrix4fv(modelLoc, 1, false, &model); |
|
||||
GL.UniformMatrix4fv(viewLoc, 1, false, &view); |
|
||||
GL.UniformMatrix4fv(projectionLoc, 1, false, &projection); |
|
||||
GL.Uniform1f(maxYLoc, _maxY); |
|
||||
GL.Uniform1f(minYLoc, _minY); |
|
||||
GL.Uniform1f(timeLoc, (float)St.Elapsed.TotalSeconds); |
|
||||
GL.Uniform1f(discoLoc, _disco); |
|
||||
CheckError(GL); |
|
||||
GL.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero); |
|
||||
|
|
||||
CheckError(GL); |
|
||||
if (_disco > 0.01) |
|
||||
RequestNextFrameRendering(); |
|
||||
} |
|
||||
|
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|
||||
{ |
|
||||
if (change.Property == YawProperty || change.Property == RollProperty || change.Property == PitchProperty || |
|
||||
change.Property == DiscoProperty) |
|
||||
RequestNextFrameRendering(); |
RequestNextFrameRendering(); |
||||
base.OnPropertyChanged(change); |
|
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,41 +1,11 @@ |
|||||
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage"> |
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage"> |
||||
<StackPanel Orientation="Vertical" Spacing="4"> |
<StackPanel Orientation="Vertical" Spacing="4"> |
||||
<TextBlock Classes="h2">A progress bar control</TextBlock> |
|
||||
<StackPanel Spacing="5"> |
|
||||
<StackPanel Orientation="Horizontal" Spacing="5"> |
|
||||
<TextBlock VerticalAlignment="Center">Maximum</TextBlock> |
|
||||
<NumericUpDown x:Name="maximum" Value="100" VerticalAlignment="Center"/> |
|
||||
</StackPanel> |
|
||||
<StackPanel Orientation="Horizontal" Spacing="5"> |
|
||||
<TextBlock VerticalAlignment="Center">Minimum</TextBlock> |
|
||||
<NumericUpDown x:Name="minimum" Value="0" VerticalAlignment="Center"/> |
|
||||
</StackPanel> |
|
||||
<StackPanel Orientation="Horizontal" Spacing="5"> |
|
||||
<TextBlock VerticalAlignment="Center">Progress Text Format</TextBlock> |
|
||||
<TextBox x:Name="stringFormat" Text="{}{0:0}%" VerticalAlignment="Center"/> |
|
||||
</StackPanel> |
|
||||
<CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" /> |
|
||||
<CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" /> |
|
||||
<StackPanel Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> |
|
||||
<StackPanel Spacing="16"> |
|
||||
<ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" |
|
||||
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" ProgressTextFormat="{Binding #stringFormat.Text}"/> |
|
||||
</StackPanel> |
|
||||
<ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" |
|
||||
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" ProgressTextFormat="{Binding #stringFormat.Text}"/> |
|
||||
</StackPanel> |
|
||||
<StackPanel Margin="16"> |
|
||||
<Slider Name="hprogress" Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" Value="40" /> |
|
||||
<Slider Name="vprogress" Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}" Value="60" /> |
|
||||
</StackPanel> |
|
||||
|
|
||||
<StackPanel Spacing="10"> |
<StackPanel Spacing="10"> |
||||
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" |
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" /> |
||||
Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}"/> |
|
||||
<ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" /> |
<ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" /> |
||||
<ProgressBar VerticalAlignment="Center" Value="50" /> |
<ProgressBar VerticalAlignment="Center" Value="50" /> |
||||
<ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" /> |
<ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" /> |
||||
</StackPanel> |
</StackPanel> |
||||
</StackPanel> |
</StackPanel> |
||||
</StackPanel> |
|
||||
</UserControl> |
</UserControl> |
||||
|
|||||
Loading…
Reference in new issue