Skip to content

Instantly share code, notes, and snippets.

@flibitijibibo
Created February 8, 2022 17:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save flibitijibibo/6790b93ba8a004061f95d2b4370e00a5 to your computer and use it in GitHub Desktop.
Save flibitijibibo/6790b93ba8a004061f95d2b4370e00a5 to your computer and use it in GitHub Desktop.
FNA with Multiple Windows
/* FNA MultiWindow Example
* Written by Ethan "flibitijibibo" Lee
* https://www.flibitijibibo.com/
*
* Released under public domain.
* No warranty implied; use at your own risk.
*/
using System;
using SDL2;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
class Program : Game
{
static void Main(string[] args)
{
using (Program p = new Program())
{
p.Run();
}
}
// XNA stuff
SpriteBatch batch;
Texture2D tex;
// SDL_Window* stuff
IntPtr extraWindow;
uint extraWindowID;
// Event handling
SDL.SDL_EventFilter eventFilter;
SDL.SDL_EventFilter prevEventFilter;
Program()
{
/* Believe it or not you don't really need this in multiwindow,
* it's just so we can set the main window size
*/
GraphicsDeviceManager gdm = new GraphicsDeviceManager(this);
gdm.PreferredBackBufferWidth = 1280;
gdm.PreferredBackBufferHeight = 600;
// Use a filter to get SDL events for your extra window
IntPtr prevUserData;
SDL.SDL_GetEventFilter(
out prevEventFilter,
out prevUserData
);
eventFilter = EventFilter;
SDL.SDL_SetEventFilter(
eventFilter,
prevUserData
);
}
private unsafe int EventFilter(IntPtr userdata, IntPtr evtPtr)
{
SDL.SDL_Event* evt = (SDL.SDL_Event*) evtPtr;
if (evt->type == SDL.SDL_EventType.SDL_WINDOWEVENT)
{
if (evt->window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE)
{
// Lazy hack, just exit when any window is closed
Exit();
return 0;
}
else if (evt->window.windowID == extraWindowID)
{
// Filter these out so Game doesn't get weird
return 0;
}
}
if (prevEventFilter != null)
{
return prevEventFilter(userdata, evtPtr);
}
return 1;
}
protected override void LoadContent()
{
// SDL_WINDOW_VULKAN just loads libvulkan, so we can always set it
extraWindow = SDL.SDL_CreateWindow(
"Extra Window",
SDL.SDL_WINDOWPOS_CENTERED,
SDL.SDL_WINDOWPOS_CENTERED,
800,
720,
(
SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN |
SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN
// Set borderless if shoving into a Form!
)
);
extraWindowID = SDL.SDL_GetWindowID(extraWindow);
/* Use info.win.win.window if shoving into a Form!
SDL.SDL_SysWMInfo info;
SDL.SDL_GetWindowWMInfo(extraWindow, out info);
*/
// Just throw any image next to the exe...
tex = Content.Load<Texture2D>("tex");
batch = new SpriteBatch(GraphicsDevice);
}
protected override void UnloadContent()
{
if (batch != null)
{
batch.Dispose();
batch = null;
}
if (tex != null)
{
tex.Dispose();
tex = null;
}
if (extraWindow != IntPtr.Zero)
{
SDL.SDL_DestroyWindow(extraWindow);
}
}
private void ValidateBackBuffer()
{
// Get the largest width/height of all windows, use as backbuffer size
Rectangle bounds = Window.ClientBounds;
int wx, wy;
SDL.SDL_GetWindowSize(extraWindow, out wx, out wy);
int maxW = Math.Max(bounds.Width, wx);
int maxH = Math.Max(bounds.Height, wy);
/* Note two details:
*
* 1. Do NOT call ApplyChanges, that triggers a window resize!
* 2. Do NOT pass a window handle here, it may cause a swapchain resize!
*/
PresentationParameters pp = GraphicsDevice.PresentationParameters;
if (pp.BackBufferWidth != maxW || pp.BackBufferHeight != maxH)
{
pp.BackBufferWidth = maxW;
pp.BackBufferHeight = maxH;
pp.DeviceWindowHandle = IntPtr.Zero;
GraphicsDevice.Reset(pp);
Console.WriteLine("GraphicsDevice reset!");
}
}
protected override void Draw(GameTime gameTime)
{
ValidateBackBuffer();
// For each window: Get the size, set the viewport, draw, then present
int wx, wy;
SDL.SDL_GetWindowSize(extraWindow, out wx, out wy);
GraphicsDevice.Clear(Color.Red);
GraphicsDevice.Viewport = new Viewport(0, 0, wx, wy);
batch.Begin();
batch.Draw(tex, Vector2.Zero, Color.White);
batch.End();
GraphicsDevice.Present(
new Rectangle(0, 0, wx, wy),
null,
extraWindow
);
// Promise you won't use ClientBounds for anything but this...
Rectangle bounds = Window.ClientBounds;
GraphicsDevice.Clear(Color.Blue);
GraphicsDevice.Viewport = new Viewport(0, 0, bounds.Width, bounds.Height);
batch.Begin();
batch.Draw(tex, Vector2.Zero, Color.White);
batch.End();
}
// Override this so that we can present with subrectangle
protected override void EndDraw()
{
Rectangle bounds = Window.ClientBounds;
GraphicsDevice.Present(
new Rectangle(0, 0, bounds.Width, bounds.Height),
null,
Window.Handle
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment