Created
February 8, 2022 17:38
-
-
Save flibitijibibo/6790b93ba8a004061f95d2b4370e00a5 to your computer and use it in GitHub Desktop.
FNA with Multiple Windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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