How To: Multi-Screen and Sub-Viewports
SubViewGameControl is an Avalonia control that renders a secondary region of the game world using the same GL context and game tick as the primary MonoGameControl. Use it for minimaps, secondary cameras, picture-in-picture panels, or per-screen viewports in a spanning window.
Reference files:
Advanced/SampleGame.MultiScreen.cs ·
Advanced/MainWindow.axaml ·
Advanced/MainWindow.axaml.cs
SubViewGameControl
Declare it in XAML alongside the primary MonoGameControl:
<Panel>
<mg:MonoGameControl x:Name="Host" Game="{Binding Game}" />
<Canvas x:Name="SubViewCanvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<mg:SubViewGameControl x:Name="SubView"
Width="260" Height="180" />
</Canvas>
</Panel>
Wire it in code-behind after InitializeComponent():
_subView.HostControl = _host; // links to the MonoGameControl's GL context
_subView.Draw = _vm.Game.DrawSubView; // delegate called each frame
HostControl registers the sub-view with the MonoGameControl so it receives a
draw callback after each game.Tick().
DrawCallback delegate
The Draw property accepts any Action<SubViewportDrawContext>:
public void DrawSubView(SubViewportDrawContext ctx)
{
if (_spriteBatch is null || _pixel is null) return;
var vp = ctx.Viewport; // Rectangle in framebuffer coordinates
_spriteBatch.Begin();
_spriteBatch.Draw(_pixel, new Rectangle(vp.X, vp.Y, vp.Width, vp.Height),
new Color(20, 20, 60));
// ... draw minimap content ...
_spriteBatch.End();
}
SubViewportDrawContext
| Property | Type | Description |
|---|---|---|
Viewport |
Rectangle |
The sub-view's region in framebuffer coordinates |
Use ctx.Viewport to confine all drawing to the sub-view's region. The sample uses
GraphicsDevice.ScissorRectangle with ScissorTestEnable = true to clip any stray
draws at the GPU level:
GraphicsDevice.ScissorRectangle = ctx.Viewport;
_spriteBatch.Begin(rasterizerState: _scissorRasterizer); // ScissorTestEnable = true
Per-screen spanning
In a borderless window that spans multiple physical displays, create one SubViewGameControl per screen, sized and positioned to cover each display's portion of the canvas:
// For each active screen (sorted left-to-right):
var sv = new SubViewGameControl
{
Width = screenWidthInDip,
Height = screenHeightInDip,
HostControl = _host,
};
// Route rendering:
sv.Draw = isPrimary
? ctx => game.DrawPrimaryView(ctx) // shares main animation state
: ctx => game.DrawScreenViewport(rank, total, ctx); // independent per-screen
Canvas.SetLeft(sv, canvasLeft);
Canvas.SetTop(sv, canvasTop);
_subViewCanvas.Children.Insert(rank, sv); // insert at back to keep overlay on top
See UpdatePerScreenViews() in
MainWindow.axaml.cs
for the complete implementation.
SuppressMainDraw
When per-screen sub-viewport delegates own all rendering, the main Draw() pass
would clear the framebuffer and overwrite content already rendered by those delegates.
Set SuppressMainDraw = true on the game to skip the main draw pass:
// Enable per-screen mode:
game.SuppressMainDraw = true;
// Disable (back to single-screen):
game.SuppressMainDraw = false;
Implement the guard in the game's Draw() method:
protected override void Draw(GameTime gameTime)
{
// FPS tracking runs regardless so the counter stays live.
UpdateFps(gameTime);
if (SuppressMainDraw)
{
GraphicsDevice.Clear(Color.Black);
return;
}
// Normal single-screen draw pass:
GraphicsDevice.Clear(new Color(8, 12, 35));
DrawMainContent(...);
base.Draw(gameTime);
}
DrawPrimaryView — seamless single↔multi transition
To avoid a visible discontinuity when switching between single-screen and multi-screen mode, route the primary screen's sub-viewport delegate to a method that shares the main-view animation state:
// Primary screen uses the same bouncer list as the main draw pass:
public void DrawPrimaryView(SubViewportDrawContext ctx)
{
var dt = (float)(_currentGameTime?.ElapsedGameTime.TotalSeconds ?? 0);
DrawMainContent(ctx.Viewport, dt, viewportFillOnly: true);
}
Non-primary screens each receive an independent animation via DrawScreenViewport.
Unregistering sub-views
Set HostControl = null to unregister a SubViewGameControl before removing it from
the layout tree:
foreach (var sv in _perScreenSubViews)
sv.HostControl = null;
_subViewCanvas.Children.RemoveAll(_perScreenSubViews);
_perScreenSubViews.Clear();