14 changed files with 702 additions and 12 deletions
@ -0,0 +1,18 @@ |
|||
<UserControl xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="ControlCatalog.Pages.OpenGlPage" |
|||
xmlns:pages="clr-namespace:ControlCatalog.Pages"> |
|||
<Grid> |
|||
<pages:OpenGlPageControl x:Name="GL"/> |
|||
<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> |
|||
</Grid> |
|||
</Grid> |
|||
</UserControl> |
|||
@ -0,0 +1,288 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Numerics; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.OpenGL; |
|||
using static Avalonia.OpenGL.GlConsts; |
|||
// ReSharper disable StringLiteralTypo
|
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class OpenGlPage : UserControl |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class OpenGlPageControl : OpenGlControlBase |
|||
{ |
|||
private float _yaw; |
|||
|
|||
public static readonly DirectProperty<OpenGlPageControl, float> YawProperty = |
|||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, 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<OpenGlPageControl, float> PitchProperty = |
|||
AvaloniaProperty.RegisterDirect<OpenGlPageControl, 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<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); |
|||
} |
|||
|
|||
static OpenGlPageControl() |
|||
{ |
|||
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty); |
|||
} |
|||
|
|||
private int _vertexShader; |
|||
private int _fragmentShader; |
|||
private int _shaderProgram; |
|||
private int _vertexBufferObject; |
|||
private int _indexBufferObject; |
|||
private int _vertexArrayObject; |
|||
|
|||
private string WithVersion(string shader) => |
|||
$"#version {(DisplayType == GlDisplayType.OpenGl ? 120 : 100)}\n" + shader; |
|||
|
|||
private string WithVersionAndPrecision(string shader, string precision) => |
|||
WithVersion(DisplayType == GlDisplayType.OpenGLES ? $"precision {precision}\n{shader}" : shader); |
|||
|
|||
private string VertexShaderSource => WithVersion(@"
|
|||
attribute vec3 aPos; |
|||
attribute vec3 aNormal; |
|||
uniform mat4 uModel; |
|||
uniform mat4 uProjection; |
|||
uniform mat4 uView; |
|||
|
|||
varying vec3 FragPos; |
|||
varying vec3 VecPos; |
|||
varying vec3 Normal; |
|||
void main() |
|||
{ |
|||
gl_Position = uProjection * uView * uModel * vec4(aPos, 1.0); |
|||
FragPos = vec3(uModel * vec4(aPos, 1.0)); |
|||
VecPos = aPos; |
|||
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0))); |
|||
} |
|||
");
|
|||
|
|||
private string FragmentShaderSource => WithVersionAndPrecision(@"
|
|||
varying vec3 FragPos; |
|||
varying vec3 VecPos; |
|||
varying vec3 Normal; |
|||
uniform float uMaxY; |
|||
uniform float uMinY; |
|||
void main() |
|||
{ |
|||
float y = (VecPos.y - uMinY) / (uMaxY - uMinY); |
|||
vec3 objectColor = vec3((1 - y), 0.40 + y / 4, y * 0.75+0.25); |
|||
//vec3 objectColor = normalize(FragPos);
|
|||
|
|||
|
|||
float ambientStrength = 0.3; |
|||
vec3 lightColor = vec3(1.0, 1.0, 1.0); |
|||
vec3 lightPos = vec3(uMaxY * 2, uMaxY * 2, uMaxY * 2); |
|||
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); |
|||
|
|||
} |
|||
", "mediump float");
|
|||
|
|||
[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 void CheckError(GlInterface gl) |
|||
{ |
|||
int err; |
|||
while ((err = gl.GetError()) != GL_NO_ERROR) |
|||
Console.WriteLine(err); |
|||
} |
|||
|
|||
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb) |
|||
{ |
|||
// 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); |
|||
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); |
|||
fixed (void* pdata = _indices) |
|||
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata), |
|||
GL_STATIC_DRAW); |
|||
|
|||
_vertexArrayObject = GL.GenVertexArray(); |
|||
GL.BindVertexArray(_vertexArrayObject); |
|||
|
|||
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, int fb) |
|||
{ |
|||
// 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); |
|||
} |
|||
|
|||
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb) |
|||
{ |
|||
gl.ClearColor(0, 0, 0, 0); |
|||
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|||
gl.Enable(GL_DEPTH_TEST); |
|||
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height); |
|||
var GL = gl; |
|||
|
|||
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject); |
|||
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject); |
|||
GL.BindVertexArray(_vertexArrayObject); |
|||
GL.UseProgram(_shaderProgram); |
|||
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"); |
|||
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.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero); |
|||
|
|||
CheckError(GL); |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,149 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Avalonia.OpenGL.Imaging; |
|||
using static Avalonia.OpenGL.GlConsts; |
|||
|
|||
namespace Avalonia.OpenGL |
|||
{ |
|||
public abstract class OpenGlControlBase : Control |
|||
{ |
|||
private IGlContext _context; |
|||
private int _fb, _texture, _renderBuffer; |
|||
private OpenGlTextureBitmap _bitmap; |
|||
private Size _oldSize; |
|||
protected GlDisplayType DisplayType { get; private set; } |
|||
public sealed override void Render(DrawingContext context) |
|||
{ |
|||
if(!EnsureInitialized()) |
|||
return; |
|||
using (_context.MakeCurrent()) |
|||
{ |
|||
using (_bitmap.Lock()) |
|||
{ |
|||
var gl = _context.Display.GlInterface; |
|||
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); |
|||
if (_oldSize != Bounds.Size) |
|||
ResizeTexture(gl); |
|||
|
|||
OnOpenGlRender(gl, _fb); |
|||
gl.Flush(); |
|||
} |
|||
} |
|||
|
|||
context.DrawImage(_bitmap, 1, new Rect(_bitmap.Size), Bounds); |
|||
base.Render(context); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
if (_context != null) |
|||
{ |
|||
using (_context.MakeCurrent()) |
|||
{ |
|||
OnOpenGlDeinit(_context.Display.GlInterface, _fb); |
|||
var gl = _context.Display.GlInterface; |
|||
gl.BindTexture(GL_TEXTURE_2D, 0); |
|||
gl.BindFramebuffer(GL_FRAMEBUFFER, 0); |
|||
gl.DeleteFramebuffers(1, new[] { _fb }); |
|||
using (_bitmap.Lock()) |
|||
{ |
|||
_bitmap.SetTexture(0, 0, new PixelSize(1, 1), 1); |
|||
gl.DeleteTextures(1, new[] { _texture }); |
|||
} |
|||
_bitmap.Dispose(); |
|||
|
|||
_context.Dispose(); |
|||
_context = null; |
|||
} |
|||
} |
|||
base.OnDetachedFromVisualTree(e); |
|||
} |
|||
|
|||
bool EnsureInitialized() |
|||
{ |
|||
if (_context != null) |
|||
return true; |
|||
|
|||
var feature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>(); |
|||
if (feature == null) |
|||
return false; |
|||
_context = feature.CreateContext(); |
|||
DisplayType = feature.Display.Type; |
|||
try |
|||
{ |
|||
_bitmap = new OpenGlTextureBitmap(); |
|||
} |
|||
catch (PlatformNotSupportedException) |
|||
{ |
|||
_context.Dispose(); |
|||
_context = null; |
|||
return false; |
|||
} |
|||
|
|||
using (_context.MakeCurrent()) |
|||
{ |
|||
_oldSize = Bounds.Size; |
|||
var gl = _context.Display.GlInterface; |
|||
var oneArr = new int[1]; |
|||
gl.GenFramebuffers(1, oneArr); |
|||
_fb = oneArr[0]; |
|||
gl.BindFramebuffer(GL_FRAMEBUFFER, _fb); |
|||
|
|||
gl.GenTextures(1, oneArr); |
|||
_texture = oneArr[0]; |
|||
gl.BindTexture(GL_TEXTURE_2D, _texture); |
|||
ResizeTexture(gl); |
|||
|
|||
gl.FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); |
|||
gl.DrawBuffers(1, new[] { GL_COLOR_ATTACHMENT0 }); |
|||
var status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); |
|||
if (status != GL_FRAMEBUFFER_COMPLETE) |
|||
{ |
|||
//TODO: Cleanup
|
|||
return false; |
|||
} |
|||
OnOpenGlInit(_context.Display.GlInterface, _fb); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
void ResizeTexture(GlInterface gl) |
|||
{ |
|||
var size = GetPixelSize(); |
|||
gl.TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, |
|||
size.Width, size.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, IntPtr.Zero); |
|||
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
|||
gl.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
|||
|
|||
//TODO: destroy the previous one
|
|||
var oneArr = new int[1]; |
|||
gl.GenRenderbuffers(1, oneArr); |
|||
_renderBuffer = oneArr[0]; |
|||
gl.BindRenderbuffer(GL_RENDERBUFFER, _renderBuffer); |
|||
gl.RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height); |
|||
gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderBuffer); |
|||
using (_bitmap.Lock()) |
|||
_bitmap.SetTexture(_texture, GL_RGBA8, size, 1); |
|||
} |
|||
|
|||
//TODO: dpi
|
|||
PixelSize GetPixelSize() => |
|||
new PixelSize(Math.Max(1, (int)Bounds.Width), |
|||
Math.Max(1, (int)Bounds.Height)); |
|||
|
|||
|
|||
protected virtual void OnOpenGlInit(GlInterface gl, int fb) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected virtual void OnOpenGlDeinit(GlInterface gl, int fb) |
|||
{ |
|||
|
|||
} |
|||
|
|||
protected abstract void OnOpenGlRender(GlInterface gl, int fb); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue