我们使用到了Frame3D,在前面写好的。这个效果很酷炫,可以在任何一个面实现视频播放。

VideoIn3D.axaml代码

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Width="426" Height="354"
        x:Class="AvaloniaUI.VideoIn3D"
        xmlns:local="using:AvaloniaUI"
        Title="VideoIn3D">
    <Grid Margin="5" RowDefinitions="*">
        <Border BorderBrush="Yellow" BorderThickness="1">
            <local:VideoCubeView x:Name="view" Loop="True"/>
        </Border>
    </Grid>
</Window>

VideoIn3D.axaml.cs代码

using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Controls;
using AvaloniaUI.Demos.Book._23.Tools;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using System;
using System.Diagnostics;
using System.Threading;
using Buffer = System.Buffer;
using PixelFormat = OpenTK.Graphics.OpenGL.PixelFormat;

namespace AvaloniaUI;

public sealed class VideoCubeView : OpenGlControlBase
{
    public static readonly StyledProperty<Uri?> VideoSourceProperty =
        AvaloniaProperty.Register<VideoCubeView, Uri?>(nameof(VideoSource), defaultValue: null);

    public Uri? VideoSource
    {
        get => GetValue(VideoSourceProperty);
        set => SetValue(VideoSourceProperty, value);
    }

    public static readonly StyledProperty<bool> LoopProperty =
        AvaloniaProperty.Register<VideoCubeView, bool>(nameof(Loop), defaultValue: false);

    public bool Loop
    {
        get => GetValue(LoopProperty);
        set => SetValue(LoopProperty, value);
    }

    public static readonly StyledProperty<Color> CubeColorProperty =
        AvaloniaProperty.Register<VideoCubeView, Color>(nameof(CubeColor), Colors.White);

    public Color CubeColor
    {
        get => GetValue(CubeColorProperty);
        set => SetValue(CubeColorProperty, value);
    }

    private int program;
    private int vao;
    private int vbo;
    private int ebo;
    private int indexCount;

    private int videoTexture;
    private int texWidth;
    private int texHeight;

    private float time;//保留用作动画
    private float rotY;
    private long lastTick;

    private Frame3D? frame3d;

    private byte[] frontRgba = [];
    private byte[] backRgba = [];
    private int backWidth;
    private int backHeight;
    private int backStride;
    private long backFrameId;
    private int newFrameFlag;

    static VideoCubeView()
    {
        VideoSourceProperty.Changed.AddClassHandler<VideoCubeView>((v, e) => v.RestartVideo());
        LoopProperty.Changed.AddClassHandler<VideoCubeView>((v, e) => v.ApplyLoop());
    }

    protected override void OnOpenGlInit(GlInterface gl)
    {
        GL.LoadBindings(new BindingsContext(gl));

        GL.Enable(EnableCap.DepthTest);
        GL.DepthFunc(DepthFunction.Lequal);

        GL.Enable(EnableCap.CullFace);
        GL.CullFace(TriangleFace.Back);

        CreateProgram();
        CreateCubeBuffers();

        RestartVideo();
    }

    protected override void OnOpenGlDeinit(GlInterface gl)
    {
        if (frame3d is not null)
        {
            frame3d.Ready -= OnFrameReady;
            frame3d.Dispose();
            frame3d = null;
        }

        if (videoTexture != 0) GL.DeleteTexture(videoTexture);
        if (program != 0) GL.DeleteProgram(program);
        if (ebo != 0) GL.DeleteBuffer(ebo);
        if (vbo != 0) GL.DeleteBuffer(vbo);
        if (vao != 0) GL.DeleteVertexArray(vao);

        videoTexture = 0;
        program = 0;
        ebo = 0;
        vbo = 0;
        vao = 0;
    }

    protected override void OnOpenGlRender(GlInterface gl, int fb)
    {
        RequestNextFrameRendering();

        GL.BindFramebuffer(FramebufferTarget.Framebuffer, fb);

        var width = Math.Max(1, (int)Bounds.Width);
        var height = Math.Max(1, (int)Bounds.Height);
        GL.Viewport(0, 0, width, height);

        UploadFrameIfNeeded();

        GL.ClearColor(0.07f, 0.07f, 0.08f, 1f);
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        var now = Stopwatch.GetTimestamp();
        if (lastTick == 0) lastTick = now;

        var dt = (float)((now - lastTick) / (double)Stopwatch.Frequency);
        lastTick = now;

        if (dt < 0f) dt = 0f;
        if (dt > 0.1f) dt = 0.1f;

        time += dt;
        rotY += 0.6f * dt;

        var aspect = width / (float)height;

        var model =
            Matrix4.CreateTranslation(-5f, -5f, -5f) *
            Matrix4.CreateRotationY(rotY);

        var eye = new Vector3(-20f, 15f, 25f);

        var view =
            Matrix4.LookAt(
                eye,
                Vector3.Zero,
                Vector3.UnitY);

        var proj =
            Matrix4.CreatePerspectiveFieldOfView(
                MathHelper.DegreesToRadians(60f),
                aspect,
                1f,
                100f);

        var mvp = model * view * proj;

        GL.UseProgram(program);

        SetMat4("uMvp", mvp);
        SetInt("uTex", 0);
        SetInt("uHasTex", videoTexture != 0 ? 1 : 0);
        SetVec4("uColor", CubeColor);

        if (videoTexture != 0)
        {
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, videoTexture);
        }

        GL.BindVertexArray(vao);
        GL.DrawElements(PrimitiveType.Triangles, indexCount, DrawElementsType.UnsignedShort, 0);
        GL.BindVertexArray(0);
    }

    private void RestartVideo()
    {
        if (frame3d is not null)
        {
            frame3d.Ready -= OnFrameReady;
            frame3d.Dispose();
            frame3d = null;
        }

        newFrameFlag = 0;
        backFrameId = 0;

        if (VideoSource is null)
            return;

        frame3d = new Frame3D
        {
            Source = VideoSource,
            FlipY = true,
            Loop = Loop
        };

        frame3d.Ready += OnFrameReady;
        frame3d.Play();
    }

    private void ApplyLoop()
    {
        if (frame3d is null)
            return;

        frame3d.Loop = Loop;

        if (VideoSource is not null)
            RestartVideo();
    }

    private void OnFrameReady(object? sender, FrameReadyEventArgs e)
    {
        EnsureBuffers(e.Width, e.Height);

        Buffer.BlockCopy(e.Rgba, 0, backRgba, 0, e.Width * e.Height * 4);

        backWidth = e.Width;
        backHeight = e.Height;
        backStride = e.Stride;
        backFrameId = e.FrameId;

        Interlocked.Exchange(ref newFrameFlag, 1);
    }

    private void EnsureBuffers(int w, int h)
    {
        var size = w * h * 4;

        if (backRgba.Length != size)
            backRgba = new byte[size];

        if (frontRgba.Length != size)
            frontRgba = new byte[size];
    }

    private void UploadFrameIfNeeded()
    {
        if (Interlocked.Exchange(ref newFrameFlag, 0) == 0)
            return;

        (backRgba, frontRgba) = (frontRgba, backRgba);
        var w = backWidth;
        var h = backHeight;

        if (w <= 0 || h <= 0)
            return;

        if (frontRgba.Length != w * h * 4)
            return;

        if (videoTexture == 0)
        {
            videoTexture = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, videoTexture);

            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

            GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);

            GL.TexImage2D(
                TextureTarget.Texture2D,
                0,
                PixelInternalFormat.Rgba8,
                w,
                h,
                0,
                PixelFormat.Rgba,
                PixelType.UnsignedByte,
                frontRgba);

            GL.BindTexture(TextureTarget.Texture2D, 0);

            texWidth = w;
            texHeight = h;
            return;
        }

        if (texWidth != w || texHeight != h)
        {
            GL.BindTexture(TextureTarget.Texture2D, videoTexture);

            GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);

            GL.TexImage2D(
                TextureTarget.Texture2D,
                0,
                PixelInternalFormat.Rgba8,
                w,
                h,
                0,
                PixelFormat.Rgba,
                PixelType.UnsignedByte,
                frontRgba);

            GL.BindTexture(TextureTarget.Texture2D, 0);

            texWidth = w;
            texHeight = h;
            return;
        }

        GL.BindTexture(TextureTarget.Texture2D, videoTexture);

        GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);

        GL.TexSubImage2D(
            TextureTarget.Texture2D,
            0,
            0,
            0,
            w,
            h,
            PixelFormat.Rgba,
            PixelType.UnsignedByte,
            frontRgba);

        GL.BindTexture(TextureTarget.Texture2D, 0);
    }

    private void CreateCubeBuffers()
    {
        var mesh = Mesh.CreateCube(10f);
        indexCount = mesh.indexCount;

        vao = GL.GenVertexArray();
        vbo = GL.GenBuffer();
        ebo = GL.GenBuffer();

        GL.BindVertexArray(vao);

        GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
        GL.BufferData(BufferTarget.ArrayBuffer, mesh.vertices.Length * sizeof(float), mesh.vertices, BufferUsageHint.StaticDraw);

        GL.BindBuffer(BufferTarget.ElementArrayBuffer, ebo);
        GL.BufferData(BufferTarget.ElementArrayBuffer, mesh.indices.Length * sizeof(ushort), mesh.indices, BufferUsageHint.StaticDraw);

        var stride = 8 * sizeof(float);

        GL.EnableVertexAttribArray(0);
        GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, 0);

        GL.EnableVertexAttribArray(2);
        GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, stride, 6 * sizeof(float));

        GL.BindVertexArray(0);
    }

    private void CreateProgram()
    {
        var vs = CompileShader(ShaderType.VertexShader, VertexShaderSource);
        var fs = CompileShader(ShaderType.FragmentShader, FragmentShaderSource);

        program = GL.CreateProgram();
        GL.AttachShader(program, vs);
        GL.AttachShader(program, fs);
        GL.LinkProgram(program);

        GL.GetProgram(program, GetProgramParameterName.LinkStatus, out var linked);
        if (linked == 0)
            throw new InvalidOperationException(GL.GetProgramInfoLog(program));

        GL.DeleteShader(vs);
        GL.DeleteShader(fs);
    }

    private static int CompileShader(ShaderType type, string source)
    {
        var shader = GL.CreateShader(type);
        GL.ShaderSource(shader, source);
        GL.CompileShader(shader);

        GL.GetShader(shader, ShaderParameter.CompileStatus, out var ok);
        if (ok == 0)
            throw new InvalidOperationException(GL.GetShaderInfoLog(shader));

        return shader;
    }

    private void SetInt(string name, int v)
    {
        var loc = GL.GetUniformLocation(program, name);
        if (loc != -1) GL.Uniform1(loc, v);
    }

    private void SetMat4(string name, Matrix4 m)
    {
        var loc = GL.GetUniformLocation(program, name);
        if (loc != -1) GL.UniformMatrix4(loc, false, ref m);
    }

    private void SetVec4(string name, Color c)
    {
        var loc = GL.GetUniformLocation(program, name);
        if (loc == -1)
            return;

        GL.Uniform4(
            loc,
            c.R / 255f,
            c.G / 255f,
            c.B / 255f,
            c.A / 255f);
    }

    private sealed class BindingsContext(GlInterface gl) : IBindingsContext
    {
        public IntPtr GetProcAddress(string procName) => gl.GetProcAddress(procName);
    }

    private const string VertexShaderSource = """
#version 300 es
precision highp float;

layout(location=0) in vec3 aPos;
layout(location=2) in vec2 aUv;

uniform mat4 uMvp;

out vec2 vUv;

void main()
{
    vUv = aUv;
    gl_Position = uMvp * vec4(aPos, 1.0);
}
""";

    private const string FragmentShaderSource = """
#version 300 es
precision mediump float;

in vec2 vUv;
out vec4 FragColor;

uniform int uHasTex;
uniform sampler2D uTex;
uniform vec4 uColor;

void main()
{
    if (uHasTex == 0)
    {
        FragColor = uColor;
        return;
    }

    vec3 tex = texture(uTex, vUv).rgb;
    FragColor = vec4(tex * uColor.rgb, 1.0);
}
""";
}
public partial class VideoIn3D : Window
{
    public VideoIn3D()
    {
        InitializeComponent();
        view.VideoSource = new Uri("avares://AvaloniaUI/Resources/Sounds/test.mpg");
    }
    public static void Test()
    {
        var uri = new Uri("avares://AvaloniaUI/Resources/Sounds/test.mpg");

        using var frame = new Frame3D
        {
            Source = uri,
            FlipY = true
        };

        using var got = new ManualResetEventSlim(false);

        frame.Ready += (_, e) =>
        {
            Console.WriteLine($"Frame: {e.Width}x{e.Height} stride={e.Stride} id={e.FrameId}");

            if (e.Stride != e.Width * 4)
                throw new Exception("Stride mismatch.");

            if (e.Rgba.Length != e.Width * e.Height * 4)
                throw new Exception("RGBA length mismatch.");

            got.Set();
        };

        if (!got.Wait(TimeSpan.FromSeconds(5)))
            throw new Exception("No frame received in 5 seconds.");

        Console.WriteLine("Frame3D test passed.");
        frame.Stop();
    }
}

运行效果

image

 

posted on 2026-08-19 11:36  dalgleish  阅读(5)  评论(0)    收藏  举报