/* 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 ); } }