Skip to content

Instantly share code, notes, and snippets.

@westonsoftware
Last active September 3, 2026 12:53
Show Gist options
  • Select an option

  • Save westonsoftware/a3fa982397fe1817ece4a27d3cbc5a89 to your computer and use it in GitHub Desktop.

Select an option

Save westonsoftware/a3fa982397fe1817ece4a27d3cbc5a89 to your computer and use it in GitHub Desktop.
Stride 3D rendered into Avalonia
using System;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Rendering;
using Stride.Graphics;
using System.IO;
namespace InfinityGame
{
public class StartScript : SyncScript
{
private Random random = new Random();
public override void Start()
{
var model = Content.Load<Model>("Sphere");
var size = 100;
var count = 1000;
for (int i = 0; i < count; i++)
{
var modelComponent = new ModelComponent(model);
var randomPosition = new Vector3(random.Next(-size, size), random.Next(0, size), random.Next(-size, size));
var entity = new Entity(randomPosition, "NEW");
Entity.Scene.Entities.Add(entity);
entity.Add(modelComponent);
}
Game.Window.Position = new Int2(0, 0);
}
public override void Update()
{
var sprite = Entity.Get<SpriteComponent>();
var texture = sprite.CurrentSprite.Texture;
using (var image = texture.GetDataAsImage(Game.GraphicsContext.CommandList))
{
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFileType.Bmp);
GlobalEvents.FrameReadyEventKey.Broadcast(stream.ToArray());
}
}
}
}
}
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using InfinityGame;
using Stride.Engine.Events;
using System;
using System.IO;
using System.Windows.Threading;
namespace AvaloniaApplication1.Views
{
public class UserControl1 : UserControl
{
private readonly DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
public UserControl1()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
var strideImage = this.Find<Image>("StrideView");
var frameReady = new EventReceiver<byte[]>(GlobalEvents.FrameReadyEventKey);
timer.Tick += (s,e) =>
{
if (frameReady.TryReceive(out byte[] bmp))
{
using var stream = new MemoryStream(bmp);
var bitmap = new Avalonia.Media.Imaging.Bitmap(stream);
strideImage.Source = bitmap;
}
};
timer.Start();
}
}
}
@tebjan

tebjan commented Mar 7, 2024

Copy link
Copy Markdown

If anyone is looking for the GlobalEvents.FrameReadyEventKey you simply add it to your global events file: https://doc.stride3d.net/4.2/en/manual/scripts/events.html

For example:

public static class GlobalEvents
{
    public static EventKey<byte[]> FrameReadyEventKey = new EventKey<byte[]>("Global", "Frame Ready");
}

Also, note that downloading a texture from the GPU is a slow and blocking operation. Stride has a better TextureReadback class that at least doesn't block. However, the correct solution would be to create AvaloniaUI with a GPU context, if possible, and share a texture via a shared handle. This would be possible with Angle: AvaloniaUI/Avalonia#5432

@luca-domenichini

luca-domenichini commented Jan 27, 2025

Copy link
Copy Markdown

Hi @westonsoftware @tebjan
I found this gist in Stride community resources, looking for AvaloniaUI integration.

I would like to run a Stride game inside an Avalonia app.
It seems to me that this gist just displays a texture inside an Avalonia image. Is that correct?
Is there a way to render a whole game, like it is documented for WPF?

@kurilovigor

Copy link
Copy Markdown

Hi @luca-domenichini
You can try the same approach as in the example for WPF. Example:

StrideGameHost.cs

`using Avalonia.Controls;
using Avalonia.Platform;
using Stride.CommunityToolkit.Engine;
using Stride.Core.Diagnostics;
using Stride.Engine;
using Stride.Games;
using Stride.CommunityToolkit.Bepu;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Stride.Avalonia.Controls
{
public class StrideGameHost : NativeControlHost, IDisposable
{
private Thread? gameThread;
private readonly TaskCompletionSource gameStartedTaskSource = new TaskCompletionSource();
private IntPtr windowHandle;
private Game? game;
private bool gameStarted = false;

    public StrideGameHost() : base()
    {
        if(Design.IsDesignMode)
        {
            return;
        }

        this.gameThread = new Thread(SafeAction.Wrap(GameRunThread))
        {
            IsBackground = true,
            Name = "Game Thread"
        };

        if(OperatingSystem.IsWindows())
        {
            this.gameThread.SetApartmentState(ApartmentState.STA);
        }

        this.SizeChanged += (s, e) =>
        {
            if (this.game != null && this.game.Window != null)
            {
                this.game.Window
                    .SetSize(new Core.Mathematics.Int2((int)this.Bounds.Width, (int)this.Bounds.Height));
            }
        };
    }

    protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
    {
        if (OperatingSystem.IsWindows() && this.gameThread != null)
        {

            this.gameThread.Start();
            this.gameStartedTaskSource.Task
                .GetAwaiter()
                .GetResult();
            return new PlatformHandle(this.windowHandle, "HWND");
        }

        return base.CreateNativeControlCore(parent);
    }

    public void Dispose()
    {
        if (this.game != null)
        {
            this.game.Dispose();
            this.game = null;
        }
    }

    protected virtual void StartGame(Scene scene)
    {
        if (this.game == null)
        {
            return;
        }

        this.game.Window.IsBorderLess = true;
        this.game.SetupBase3DScene();
    }

    protected virtual void UpdateGame(Scene scene, GameTime gameTime)
    {
        if (this.game == null)
        {
            return;
        }

        if(!this.gameStarted)
        {
            this.gameStartedTaskSource.SetResult(true);
            this.gameStarted = true;
            this.windowHandle = this.game.Window.NativeWindow.Handle;
        }
    }


    private void GameRunThread()
    {
        this.game = new Game();
        this.game.Run(
            StartGame,
            UpdateGame);
    }
}

}
`

MainView.axml

<UserControl 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" xmlns:vm="clr-namespace:AvaloniaStrideEmbedding.ViewModels" xmlns:st="clr-namespace:Stride.Avalonia.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="AvaloniaStrideEmbedding.Views.MainView" x:DataType="vm:MainViewModel" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"> <Design.DataContext> <!-- This only sets the DataContext for the previewer in an IDE, to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) --> <vm:MainViewModel /> </Design.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center" TextWrapping="Wrap"/> <st:StrideGameHost Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" /> </Grid> </UserControl>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment