-
Hi, I've got an app that shows video frames in a Silk.Net window. Each video frame is drawn as a texture as per the Silk.Net examples. What I can't for the life of me work out is how to get the texture to fill the screen as I resize the window? Can anyone give me some pointers? public class RotatingDiscDisplay : OpenGLVideoDisplay
{
public float _angle = 0.0f;
private string EmptyImagePath = "silk.png";
protected override void OnUpdatingTexture()
{
base.OnUpdatingTexture();
_angle += 1;
using var image = Image.Load<Bgra32>(EmptyImagePath);
var newWidth = image.Width * 2;
var newHeight = image.Height * 2;
image.Mutate(x => x.Resize(image.Width * 2, image.Height * 2).Rotate(_angle));
//IPath yourPolygon = new Star(x: 100.0f, y: 100.0f, prongs: 5, innerRadii: 20.0f, outerRadii: 30.0f);
//image.Mutate(x => x.Fill(Color.Red, yourPolygon)); // fill the star with red
DrawingOptions options = new()
{
GraphicsOptions = new()
{
ColorBlendingMode = PixelColorBlendingMode.Multiply
}
};
IBrush brush = Brushes.Horizontal(Color.Red, Color.Blue);
IPen pen = Pens.DashDot(Color.Green, 5);
IPath yourPolygon = new Star(x: 100.0f, y: 100.0f, prongs: 5, innerRadii: 20.0f, outerRadii: 30.0f);
image.Mutate(x => x.Fill(options, brush, yourPolygon)
.Draw(options, pen, yourPolygon));
var yourPolygon2 = new SixLabors.ImageSharp.Rectangle(0, 0, 100, 100);
image.Mutate(x => x.Fill(options, brush, yourPolygon)
.Draw(options, pen, yourPolygon));
// Draws a star with horizontal red and blue hatching with a dash dot pattern outline.
image.Mutate(x => x.Fill(Color.Green, yourPolygon2));
https://stackoverflow.com/questions/50025908/convert-imagergba32-to-byte-using-imagesharp
var _IMemoryGroup = image.GetPixelMemoryGroup();
var _MemoryGroup = _IMemoryGroup.ToArray()[0];
var PixelData = MemoryMarshal.AsBytes(_MemoryGroup.Span).ToArray();
Texture.Update(PixelData, (uint)image.Width, (uint)image.Height);
//Look at how you could manipulate pixels
//https://docs.sixlabors.com/articles/imagesharp/pixelbuffers.html
//https://github.com/SixLabors/Samples/blob/main/ImageSharp/DrawWaterMarkOnImage/Program.cs
//Appears you can overlay images
//image1.Mutate(i => i.Resize(800, 600, KnownResamplers.Lanczos3));
//image.Mutate(i => i.Resize(1150, 600, KnownResamplers.Lanczos3).BokehBlur().DrawImage(image1, new Point(180, 0), opacity: 1f).Brightness(1.0f));
}
protected override void OnInitializingTexture()
{
base.OnInitializingTexture();
//newTexture = new Texture(Gl, EmptyImagePath);
//OR
//We need it ot be 32 bit ARGB
//https://docs.sixlabors.com/
//using var image = Image.Load<Argb32>(EmptyImagePath);
using var image = Image.Load<Bgra32>(EmptyImagePath);
var newWidth = image.Width * 2;
var newHeight = image.Height * 2;
image.Mutate(x => x.Resize(image.Width * 2, image.Height * 2).Rotate(20));
https://stackoverflow.com/questions/50025908/convert-imagergba32-to-byte-using-imagesharp
var _IMemoryGroup = image.GetPixelMemoryGroup();
var _MemoryGroup = _IMemoryGroup.ToArray()[0];
var PixelData = MemoryMarshal.AsBytes(_MemoryGroup.Span).ToArray();
var newTexture = new Texture(GL, PixelData, (uint)image.Width, (uint)image.Height);
if (Texture != newTexture)
{
var oldTexture = Texture;
Texture = newTexture;
if (oldTexture != null)
oldTexture.Dispose();
}
}
}
public class OpenGLVideoDisplay
{
private IWindow _window;
private GL _gl;
protected GL GL { get => _gl; }
private BufferObject<float> _vbo;
private BufferObject<uint> _ebo;
private VertexArrayObject<float, uint> _vao;
//Create a texture object.
private Texture _texture;
private Shader _shader;
protected IWindow Window { get => _window; }
protected Texture Texture
{
get => _texture;
set
{
_texture = value;
}
}
// OpenGL has image origin in the bottom-left corner.
private readonly float[] Vertices =
{
//X Y Z U V
1.0f, 1.0f, 0.0f, 1f, 0f,
1.0f, -1.0f, 0.0f, 1f, 1f,
-1.0f, -1.0f, 0.0f, 0f, 1f,
-1.0f, 1.0f, 1.0f, 0f, 0f
};
private readonly uint[] Indices =
{
0, 1, 3,
1, 2, 3
};
public void Start()
{
var options = WindowOptions.Default;
options.Size = new Vector2D<int>(1920, 1080);
options.Title = "";
options.WindowBorder = WindowBorder.Fixed;
_window = global::Silk.NET.Windowing.Window.Create(options);
_window.Load += OnLoad;
_window.Render += OnRender;
_window.Resize += _window_Resize;
_window.Closing += OnClose;
_window.Run();
}
private void _window_Resize(Vector2D<int> obj)
{
}
private unsafe void OnLoad()
{
IInputContext input = _window.CreateInput();
for (int i = 0; i < input.Keyboards.Count; i++)
{
input.Keyboards[i].KeyDown += KeyDown;
}
_gl = GL.GetApi(_window);
_ebo = new BufferObject<uint>(_gl, Indices, BufferTargetARB.ElementArrayBuffer);
_vbo = new BufferObject<float>(_gl, Vertices, BufferTargetARB.ArrayBuffer);
_vao = new VertexArrayObject<float, uint>(_gl, _vbo, _ebo);
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 5, 0);
_vao.VertexAttributePointer(1, 2, VertexAttribPointerType.Float, 5, 3);
_shader = new Shader(_gl, "shader.vert", "shader.frag");
InitializeTexture();
_hasLoaded = true;
}
protected virtual void OnUpdatingTexture()
{
}
private void UpdateTexture()
{
OnUpdatingTexture();
}
private void InitializeTexture()
{
OnInitializingTexture();
}
protected virtual void OnInitializingTexture()
{
}
private bool _hasLoaded;
private long _lastRender = 0;
public List<double> _lastFramesMs = new List<double>();
private unsafe void OnRender(double obj)
{
var now = DateTime.Now.Ticks;
var ticks = now - _lastRender;
var ticksPerSeconds = 10_000_000;
_lastRender = now;
var msSinceLast = 1000.0 * ticks / (double)ticksPerSeconds;
_lastFramesMs.Add(msSinceLast);
if (_lastFramesMs.Count == 50)
{
var avgMSPerFrame = _lastFramesMs.Average();
Console.WriteLine("Avg frame gap: " + msSinceLast);
var fps = 1.0 / (avgMSPerFrame / 1000.0);
Console.WriteLine("Avg fps: " + fps);
_lastFramesMs.Clear();
}
UpdateTexture();
_gl.Clear((uint)ClearBufferMask.ColorBufferBit);
_vao.Bind();
_shader.Use();
//Bind a texture and and set the uTexture0 to use texture0.
_texture.Bind(TextureUnit.Texture0);
_shader.SetUniform("uTexture0", 0);
_gl.DrawElements(PrimitiveType.Triangles, (uint)Indices.Length, DrawElementsType.UnsignedInt, null);
}
private void OnClose()
{
OnClosing();
_vbo.Dispose();
_ebo.Dispose();
_vao.Dispose();
_shader.Dispose();
//Remember to dispose the texture.
_texture.Dispose();
}
protected virtual void OnClosing()
{
}
protected virtual void OnKeyDown(Key key)
{
if (key == Key.Escape)
{
_window.Close();
}
}
private void KeyDown(IKeyboard arg1, Key arg2, int arg3)
{
OnKeyDown(arg2);
}
}
public class Texture : IDisposable
{
private uint _handle;
private GL _gl;
public unsafe Texture(GL gl, Image<Rgba32> img)
{
_gl = gl;
_handle = _gl.GenTexture();
Bind();
//Reserve enough memory from the gpu for the whole image
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)img.Width, (uint)img.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, null);
img.ProcessPixelRows(accessor =>
{
//ImageSharp 2 does not store images in contiguous memory by default, so we must send the image row by row
for (int y = 0; y < accessor.Height; y++)
{
fixed (void* data = accessor.GetRowSpan(y))
{
//Loading the actual image.
gl.TexSubImage2D(TextureTarget.Texture2D, 0, 0, y, (uint)accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, data);
}
}
});
SetParameters();
}
public unsafe Texture(GL gl, string path)
{
_gl = gl;
_handle = _gl.GenTexture();
Bind();
//Loading an image using imagesharp.
using (var img = Image.Load<Rgba32>(path))
{
//Reserve enough memory from the gpu for the whole image
gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba8, (uint)img.Width, (uint)img.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, null);
img.ProcessPixelRows(accessor =>
{
//ImageSharp 2 does not store images in contiguous memory by default, so we must send the image row by row
for (int y = 0; y < accessor.Height; y++)
{
fixed (void* data = accessor.GetRowSpan(y))
{
//Loading the actual image.
gl.TexSubImage2D(TextureTarget.Texture2D, 0, 0, y, (uint)accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, data);
}
}
});
}
SetParameters();
}
public unsafe Texture(GL gl, Span<byte> data, uint width, uint height)
{
//Saving the gl instance.
_gl = gl;
//Generating the opengl handle;
_handle = _gl.GenTexture();
Bind();
//We want the ability to create a texture using data generated from code aswell.
fixed (void* d = &data[0])
{
//Setting the data of a texture.
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgb32f, width, height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, d);
SetParameters();
}
}
public unsafe void Update(Image<Rgba32> image, uint width, uint height)
{
//See https://stackoverflow.com/questions/50025908/convert-imagergba32-to-byte-using-imagesharp
//var memoryGroup = image.GetPixelMemoryGroup();
//var mg = memoryGroup.ToArray()[0];
//var pixelData = MemoryMarshal.AsBytes(mg.Span).ToArray();
byte[] pixelBytes = new byte[image.Width * image.Height * Unsafe.SizeOf<Rgba32>()];
image.CopyPixelDataTo(pixelBytes);
Debug.Assert(pixelBytes.Length == image.Width * image.Height * Unsafe.SizeOf<Rgba32>());
fixed (void* d = &pixelBytes[0])
{
//Setting the data of a texture.
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgb32f, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d);
SetParameters();
}
}
public unsafe void Update(Span<byte> data, uint width, uint height)
{
fixed (void* d = &data[0])
{
//Setting the data of a texture.
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgb32f, width, height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, d);
SetParameters();
}
}
private void SetParameters()
{
//Setting some texture perameters so the texture behaves as expected.
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)GLEnum.ClampToEdge);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)GLEnum.ClampToEdge);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)GLEnum.LinearMipmapLinear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)GLEnum.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 8);
//Generating mipmaps.
_gl.GenerateMipmap(TextureTarget.Texture2D);
}
public void Bind(TextureUnit textureSlot = TextureUnit.Texture0)
{
//When we bind a texture we can choose which textureslot we can bind it to.
_gl.ActiveTexture(textureSlot);
_gl.BindTexture(TextureTarget.Texture2D, _handle);
}
public void Dispose()
{
//In order to dispose we need to delete the opengl handle for the texure.
_gl.DeleteTexture(_handle);
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
You need to use glViewport in your FramebufferResize callback to tell OpenGL to resize your framebuffer when the window size changes. |
Beta Was this translation helpful? Give feedback.
-
Thanks @Perksey for the super-fast response. You got it. It was as simple as adding the following code to my
|
Beta Was this translation helpful? Give feedback.
You need to use glViewport in your FramebufferResize callback to tell OpenGL to resize your framebuffer when the window size changes.