#region License␊ |
/* FNA - XNA4 Reimplementation for Desktop Platforms␊ |
* Copyright 2009-2016 Ethan Lee and the MonoGame Team␊ |
*␊ |
* Released under the Microsoft Public License.␊ |
* See LICENSE for details.␊ |
*/␊ |
#endregion␊ |
␊ |
#region USE_SCANCODES Option␊ |
// #define USE_SCANCODES␊ |
/* XNA Keys are based on keycodes, rather than scancodes.␊ |
*␊ |
* With SDL2 you can actually pick between SDL_Keycode and SDL_Scancode, but␊ |
* scancodes will not be accurate to XNA4. The benefit is that scancodes will␊ |
* essentially ignore "foreign" keyboard layouts, making default keyboard␊ |
* layouts work out of the box everywhere (unless the actual symbol for the keys␊ |
* matters in your game).␊ |
*␊ |
* At the same time, the TextInputEXT extension will still read the actual chars␊ |
* correctly, so you can (mostly) have your cake and eat it too if you don't␊ |
* care about your bindings menu not making a lot of sense on foreign layouts.␊ |
* -flibit␊ |
*/␊ |
#endregion␊ |
␊ |
#region Using Statements␊ |
using System;␊ |
using System.IO;␊ |
using System.Collections.Generic;␊ |
using System.Runtime.InteropServices;␊ |
␊ |
using SDL2;␊ |
␊ |
using Microsoft.Xna.Framework.Graphics;␊ |
using Microsoft.Xna.Framework.Input;␊ |
#endregion␊ |
␊ |
namespace Microsoft.Xna.Framework␊ |
{␊ |
␉class SDL2_GamePlatform : GamePlatform␊ |
␉{␊ |
␉␉#region Public Constructor␊ |
␊ |
␉␉public SDL2_GamePlatform(Game game) : base(game, SDL.SDL_GetPlatform())␊ |
␉␉{␊ |
␉␉␉/* SDL2 might complain if an OS that uses SDL_main has not actually␊ |
␉␉␉ * used SDL_main by the time you initialize SDL2.␊ |
␉␉␉ * The only platform that is affected is Windows, but we can skip␊ |
␉␉␉ * their WinMain. This was only added to prevent iOS from exploding.␊ |
␉␉␉ * -flibit␊ |
␉␉␉ */␊ |
␉␉␉SDL.SDL_SetMainReady();␊ |
␊ |
␉␉␉// This _should_ be the first real SDL call we make...␊ |
␉␉␉SDL.SDL_Init(␊ |
␉␉␉␉SDL.SDL_INIT_VIDEO |␊ |
␉␉␉␉SDL.SDL_INIT_JOYSTICK |␊ |
␉␉␉␉SDL.SDL_INIT_GAMECONTROLLER |␊ |
␉␉␉␉SDL.SDL_INIT_HAPTIC␊ |
␉␉␉);␊ |
␊ |
␉␉␉// Set any hints to match XNA4 behavior...␊ |
␉␉␉string hint = SDL.SDL_GetHint(SDL.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS);␊ |
␉␉␉if (String.IsNullOrEmpty(hint))␊ |
␉␉␉{␊ |
␉␉␉␉SDL.SDL_SetHint(␊ |
␉␉␉␉␉SDL.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,␊ |
␉␉␉␉␉"1"␊ |
␉␉␉␉);␊ |
␉␉␉}␊ |
␊ |
␉␉␉// If available, load the SDL_GameControllerDB␊ |
␉␉␉string mappingsDB = Path.Combine(␊ |
␉␉␉␉TitleContainer.Location,␊ |
␉␉␉␉"gamecontrollerdb.txt"␊ |
␉␉␉);␊ |
␉␉␉if (File.Exists(mappingsDB))␊ |
␉␉␉{␊ |
␉␉␉␉SDL.SDL_GameControllerAddMappingsFromFile(␊ |
␉␉␉␉␉mappingsDB␊ |
␉␉␉␉);␊ |
␉␉␉}␊ |
␊ |
␉␉␉// Set and initialize the SDL2 window␊ |
␉␉␉bool forceES2 = Environment.GetEnvironmentVariable(␊ |
␉␉␉␉"FNA_OPENGL_FORCE_ES2"␊ |
␉␉␉) == "1";␊ |
␉␉␉bool forceCoreProfile = Environment.GetEnvironmentVariable(␊ |
␉␉␉␉"FNA_OPENGL_FORCE_CORE_PROFILE"␊ |
␉␉␉) == "1";␊ |
␉␉␉game.Window = new SDL2_GameWindow(␊ |
␉␉␉␉forceES2 ||␊ |
␉␉␉␉OSVersion.Equals("Emscripten") ||␊ |
␉␉␉␉OSVersion.Equals("Android") ||␊ |
␉␉␉␉OSVersion.Equals("iOS"),␊ |
␉␉␉␉forceCoreProfile␊ |
␉␉␉);␊ |
␊ |
␉␉␉// Disable the screensaver.␊ |
␉␉␉SDL.SDL_DisableScreenSaver();␊ |
␊ |
␉␉␉// We hide the mouse cursor by default.␊ |
␉␉␉SDL.SDL_ShowCursor(0);␊ |
␉␉}␊ |
␊ |
␉␉#endregion␊ |
␊ |
␉␉#region Public GamePlatform Methods␊ |
␊ |
␉␉public override void RunLoop()␊ |
␉␉{␊ |
␉␉␉SDL.SDL_ShowWindow(Game.Window.Handle);␊ |
␊ |
␉␉␉// Which display did we end up on?␊ |
␉␉␉int displayIndex = SDL.SDL_GetWindowDisplayIndex(␊ |
␉␉␉␉Game.Window.Handle␊ |
␉␉␉);␊ |
␊ |
␉␉␉// OSX has some fancy fullscreen features, let's use them!␊ |
␉␉␉bool osxUseSpaces;␊ |
␉␉␉if (OSVersion.Equals("Mac OS X"))␊ |
␉␉␉{␊ |
␉␉␉␉string hint = SDL.SDL_GetHint(SDL.SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES);␊ |
␉␉␉␉osxUseSpaces = (String.IsNullOrEmpty(hint) || hint.Equals("1"));␊ |
␉␉␉}␊ |
␉␉␉else␊ |
␉␉␉{␊ |
␉␉␉␉osxUseSpaces = false;␊ |
␉␉␉}␊ |
␊ |
␉␉␉// Active Key List␊ |
␉␉␉List<Keys> keys = new List<Keys>();␊ |
␊ |
␉␉␉/* Setup Text Input Control Character Arrays␊ |
␉␉␉ * (Only 4 control keys supported at this time)␊ |
␉␉␉ */␊ |
␉␉␉bool[] INTERNAL_TextInputControlDown = new bool[4];␊ |
␉␉␉int[] INTERNAL_TextInputControlRepeat = new int[4];␊ |
␉␉␉bool INTERNAL_TextInputSuppress = false;␊ |
␊ |
␉␉␉SDL.SDL_Event evt;␊ |
␊ |
␉␉␉while (Game.RunApplication)␊ |
␉␉␉{␊ |
␉␉␉␉while (SDL.SDL_PollEvent(out evt) == 1)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉// Keyboard␊ |
␉␉␉␉␉if (evt.type == SDL.SDL_EventType.SDL_KEYDOWN)␊ |
␉␉␉␉␉{␊ |
#if USE_SCANCODES␊ |
␉␉␉␉␉␉Keys key = SDL2_KeyboardUtil.ToXNA(evt.key.keysym.scancode);␊ |
#else␊ |
␉␉␉␉␉␉Keys key = SDL2_KeyboardUtil.ToXNA(evt.key.keysym.sym);␊ |
#endif␊ |
␉␉␉␉␉␉if (!keys.Contains(key))␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉keys.Add(key);␊ |
␉␉␉␉␉␉␉if (key == Keys.Back)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[0] = true;␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlRepeat[0] = Environment.TickCount + 400;␊ |
␉␉␉␉␉␉␉␉TextInputEXT.OnTextInput((char) 8); // Backspace␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else if (key == Keys.Tab)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[1] = true;␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlRepeat[1] = Environment.TickCount + 400;␊ |
␉␉␉␉␉␉␉␉TextInputEXT.OnTextInput((char) 9); // Tab␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else if (key == Keys.Enter)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[2] = true;␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlRepeat[2] = Environment.TickCount + 400;␊ |
␉␉␉␉␉␉␉␉TextInputEXT.OnTextInput((char) 13); // Enter␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else if (keys.Contains(Keys.LeftControl) && key == Keys.V)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[3] = true;␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlRepeat[3] = Environment.TickCount + 400;␊ |
␉␉␉␉␉␉␉␉TextInputEXT.OnTextInput((char) 22); // Control-V (Paste)␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputSuppress = true;␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_KEYUP)␊ |
␉␉␉␉␉{␊ |
#if USE_SCANCODES␊ |
␉␉␉␉␉␉Keys key = SDL2_KeyboardUtil.ToXNA(evt.key.keysym.scancode);␊ |
#else␊ |
␉␉␉␉␉␉Keys key = SDL2_KeyboardUtil.ToXNA(evt.key.keysym.sym);␊ |
#endif␊ |
␉␉␉␉␉␉if (keys.Remove(key))␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉if (key == Keys.Back)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[0] = false;␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else if (key == Keys.Tab)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[1] = false;␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else if (key == Keys.Enter)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[2] = false;␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else if ((!keys.Contains(Keys.LeftControl) && INTERNAL_TextInputControlDown[3]) || key == Keys.V)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputControlDown[3] = false;␊ |
␉␉␉␉␉␉␉␉INTERNAL_TextInputSuppress = false;␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉// Mouse Input␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_MOUSEMOTION)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉Mouse.INTERNAL_IsWarped = false;␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_MOUSEWHEEL)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉// 120 units per notch. Because reasons.␊ |
␉␉␉␉␉␉Mouse.INTERNAL_MouseWheel += evt.wheel.y * 120;␊ |
␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉// Various Window Events...␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_WINDOWEVENT)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉// Window Focus␊ |
␉␉␉␉␉␉if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉Game.IsActive = true;␊ |
␊ |
␉␉␉␉␉␉␉if (!osxUseSpaces)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉// If we alt-tab away, we lose the 'fullscreen desktop' flag on some WMs␊ |
␉␉␉␉␉␉␉␉SDL.SDL_SetWindowFullscreen(␊ |
␉␉␉␉␉␉␉␉␉Game.Window.Handle,␊ |
␉␉␉␉␉␉␉␉␉Game.GraphicsDevice.PresentationParameters.IsFullScreen ?␊ |
␉␉␉␉␉␉␉␉␉␉(uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP :␊ |
␉␉␉␉␉␉␉␉␉␉0␊ |
␉␉␉␉␉␉␉␉);␊ |
␉␉␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉␉␉// Disable the screensaver when we're back.␊ |
␉␉␉␉␉␉␉SDL.SDL_DisableScreenSaver();␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉Game.IsActive = false;␊ |
␊ |
␉␉␉␉␉␉␉if (!osxUseSpaces)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉SDL.SDL_SetWindowFullscreen(Game.Window.Handle, 0);␊ |
␉␉␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉␉␉// Give the screensaver back, we're not that important now.␊ |
␉␉␉␉␉␉␉SDL.SDL_EnableScreenSaver();␊ |
␉␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉␉// Window Resize␊ |
␉␉␉␉␉␉else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉Mouse.INTERNAL_WindowWidth = evt.window.data1;␊ |
␉␉␉␉␉␉␉Mouse.INTERNAL_WindowHeight = evt.window.data2;␊ |
␊ |
␉␉␉␉␉␉␉// Should be called on user resize only, NOT ApplyChanges!␊ |
␉␉␉␉␉␉␉((SDL2_GameWindow) Game.Window).INTERNAL_ClientSizeChanged();␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉Mouse.INTERNAL_WindowWidth = evt.window.data1;␊ |
␉␉␉␉␉␉␉Mouse.INTERNAL_WindowHeight = evt.window.data2;␊ |
␊ |
␉␉␉␉␉␉␉// Need to reset the graphics device any time the window size changes␊ |
␉␉␉␉␉␉␉GraphicsDeviceManager gdm = Game.Services.GetService(␊ |
␉␉␉␉␉␉␉␉typeof(IGraphicsDeviceService)␊ |
␉␉␉␉␉␉␉) as GraphicsDeviceManager;␊ |
␉␉␉␉␉␉␉// FIXME: gdm == null? -flibit␊ |
␉␉␉␉␉␉␉if (gdm.IsFullScreen)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉GraphicsDevice device = Game.GraphicsDevice;␊ |
␉␉␉␉␉␉␉␉gdm.INTERNAL_ResizeGraphicsDevice(␊ |
␉␉␉␉␉␉␉␉␉device.GLDevice.Backbuffer.Width,␊ |
␉␉␉␉␉␉␉␉␉device.GLDevice.Backbuffer.Height␊ |
␉␉␉␉␉␉␉␉);␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉else␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉gdm.INTERNAL_ResizeGraphicsDevice(␊ |
␉␉␉␉␉␉␉␉␉evt.window.data1,␊ |
␉␉␉␉␉␉␉␉␉evt.window.data2␊ |
␉␉␉␉␉␉␉␉);␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉␉// Window Move␊ |
␉␉␉␉␉␉else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MOVED)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉/* Apparently if you move the window to a new␊ |
␉␉␉␉␉␉␉ * display, a GraphicsDevice Reset occurs.␊ |
␉␉␉␉␉␉␉ * -flibit␊ |
␉␉␉␉␉␉␉ */␊ |
␉␉␉␉␉␉␉int newIndex = SDL.SDL_GetWindowDisplayIndex(␊ |
␉␉␉␉␉␉␉␉Game.Window.Handle␊ |
␉␉␉␉␉␉␉);␊ |
␉␉␉␉␉␉␉if (newIndex != displayIndex)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉displayIndex = newIndex;␊ |
␉␉␉␉␉␉␉␉Game.GraphicsDevice.Reset(␊ |
␉␉␉␉␉␉␉␉␉Game.GraphicsDevice.PresentationParameters,␊ |
␉␉␉␉␉␉␉␉␉GraphicsAdapter.Adapters[displayIndex]␊ |
␉␉␉␉␉␉␉␉);␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉␉// Mouse Focus␊ |
␉␉␉␉␉␉else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_ENTER)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉SDL.SDL_DisableScreenSaver();␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉SDL.SDL_EnableScreenSaver();␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉// Controller device management␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉GamePad.INTERNAL_AddInstance(evt.cdevice.which);␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉GamePad.INTERNAL_RemoveInstance(evt.cdevice.which);␊ |
␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉// Text Input␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_TEXTINPUT && !INTERNAL_TextInputSuppress)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉string text;␊ |
␊ |
␉␉␉␉␉␉// Based on the SDL2# LPUtf8StrMarshaler␊ |
␉␉␉␉␉␉unsafe␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉byte* endPtr = evt.text.text;␊ |
␉␉␉␉␉␉␉while (*endPtr != 0)␊ |
␉␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉␉endPtr++;␊ |
␉␉␉␉␉␉␉}␊ |
␉␉␉␉␉␉␉byte[] bytes = new byte[endPtr - evt.text.text];␊ |
␉␉␉␉␉␉␉Marshal.Copy((IntPtr) evt.text.text, bytes, 0, bytes.Length);␊ |
␉␉␉␉␉␉␉text = System.Text.Encoding.UTF8.GetString(bytes);␊ |
␉␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉␉if (text.Length > 0)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉TextInputEXT.OnTextInput(text[0]);␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉}␊ |
␊ |
␉␉␉␉␉// Quit␊ |
␉␉␉␉␉else if (evt.type == SDL.SDL_EventType.SDL_QUIT)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉Game.RunApplication = false;␊ |
␉␉␉␉␉␉break;␊ |
␉␉␉␉␉}␊ |
␉␉␉␉}␊ |
␉␉␉␉// Text Input Controls Key Handling␊ |
␉␉␉␉if (INTERNAL_TextInputControlDown[0] && INTERNAL_TextInputControlRepeat[0] <= Environment.TickCount)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉TextInputEXT.OnTextInput((char) 8);␊ |
␉␉␉␉}␊ |
␉␉␉␉if (INTERNAL_TextInputControlDown[1] && INTERNAL_TextInputControlRepeat[1] <= Environment.TickCount)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉TextInputEXT.OnTextInput((char) 9);␊ |
␉␉␉␉}␊ |
␉␉␉␉if (INTERNAL_TextInputControlDown[2] && INTERNAL_TextInputControlRepeat[2] <= Environment.TickCount)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉TextInputEXT.OnTextInput((char) 13);␊ |
␉␉␉␉}␊ |
␉␉␉␉if (INTERNAL_TextInputControlDown[3] && INTERNAL_TextInputControlRepeat[3] <= Environment.TickCount)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉TextInputEXT.OnTextInput((char) 22);␊ |
␉␉␉␉}␊ |
␊ |
␉␉␉␉Keyboard.SetKeys(keys);␊ |
␉␉␉␉Game.Tick();␊ |
␉␉␉}␊ |
␊ |
␉␉␉// We out.␊ |
␉␉␉Game.Exit();␊ |
␉␉}␊ |
␊ |
␉␉public override void BeforeInitialize()␊ |
␉␉{␊ |
␉␉␉// We want to initialize the controllers ASAP!␊ |
␉␉␉SDL.SDL_Event[] evt = new SDL.SDL_Event[1];␊ |
␉␉␉SDL.SDL_PumpEvents(); // Required to get OSX device events this early.␊ |
␉␉␉while (SDL.SDL_PeepEvents(␊ |
␉␉␉␉evt,␊ |
␉␉␉␉1,␊ |
␉␉␉␉SDL.SDL_eventaction.SDL_GETEVENT,␊ |
␉␉␉␉SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED,␊ |
␉␉␉␉SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED␊ |
␉␉␉) == 1) {␊ |
␉␉␉␉GamePad.INTERNAL_AddInstance(evt[0].cdevice.which);␊ |
␉␉␉}␊ |
␉␉}␊ |
␊ |
␉␉public override void Log(string Message)␊ |
␉␉{␊ |
␉␉␉Console.WriteLine(Message);␊ |
␉␉}␊ |
␊ |
␉␉public override void ShowRuntimeError(string title, string message)␊ |
␉␉{␊ |
␉␉␉SDL.SDL_ShowSimpleMessageBox(␊ |
␉␉␉␉SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,␊ |
␉␉␉␉title,␊ |
␉␉␉␉message,␊ |
␉␉␉␉Game.Window.Handle␊ |
␉␉␉);␊ |
␉␉}␊ |
␊ |
␉␉public override GraphicsAdapter[] GetGraphicsAdapters()␊ |
␉␉{␊ |
␉␉␉SDL.SDL_DisplayMode filler = new SDL.SDL_DisplayMode();␊ |
␉␉␉GraphicsAdapter[] adapters = new GraphicsAdapter[SDL.SDL_GetNumVideoDisplays()];␊ |
␉␉␉for (int i = 0; i < adapters.Length; i += 1)␊ |
␉␉␉{␊ |
␉␉␉␉List<DisplayMode> modes = new List<DisplayMode>();␊ |
␉␉␉␉int numModes = SDL.SDL_GetNumDisplayModes(i);␊ |
␉␉␉␉for (int j = 0; j < numModes; j += 1)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉SDL.SDL_GetDisplayMode(i, j, out filler);␊ |
␊ |
␉␉␉␉␉// Check for dupes caused by varying refresh rates.␊ |
␉␉␉␉␉bool dupe = false;␊ |
␉␉␉␉␉foreach (DisplayMode mode in modes)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉if (filler.w == mode.Width && filler.h == mode.Height)␊ |
␉␉␉␉␉␉{␊ |
␉␉␉␉␉␉␉dupe = true;␊ |
␉␉␉␉␉␉}␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉if (!dupe)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉modes.Add(␊ |
␉␉␉␉␉␉␉new DisplayMode(␊ |
␉␉␉␉␉␉␉␉filler.w,␊ |
␉␉␉␉␉␉␉␉filler.h,␊ |
␉␉␉␉␉␉␉␉SurfaceFormat.Color // FIXME: Assumption!␊ |
␉␉␉␉␉␉␉)␊ |
␉␉␉␉␉␉);␊ |
␉␉␉␉␉}␊ |
␉␉␉␉}␊ |
␉␉␉␉SDL.SDL_GetCurrentDisplayMode(i, out filler);␊ |
␉␉␉␉adapters[i] = new GraphicsAdapter(␊ |
␉␉␉␉␉new DisplayMode(␊ |
␉␉␉␉␉␉filler.w,␊ |
␉␉␉␉␉␉filler.h,␊ |
␉␉␉␉␉␉SurfaceFormat.Color // FIXME: Assumption!␊ |
␉␉␉␉␉),␊ |
␉␉␉␉␉new DisplayModeCollection(modes),␊ |
␉␉␉␉␉SDL.SDL_GetDisplayName(i)␊ |
␉␉␉␉);␊ |
␉␉␉}␊ |
␉␉␉return adapters;␊ |
␉␉}␊ |
␊ |
␉␉public override void SetPresentationInterval(PresentInterval interval)␊ |
␉␉{␊ |
␉␉␉if (interval == PresentInterval.Default || interval == PresentInterval.One)␊ |
␉␉␉{␊ |
␉␉␉␉if (OSVersion.Equals("Mac OS X"))␊ |
␉␉␉␉{␊ |
␉␉␉␉␉// Apple is a big fat liar about swap_control_tear. Use stock VSync.␊ |
␉␉␉␉␉SDL.SDL_GL_SetSwapInterval(1);␊ |
␉␉␉␉}␊ |
␉␉␉␉else␊ |
␉␉␉␉{␊ |
␉␉␉␉␉if (SDL.SDL_GL_SetSwapInterval(-1) != -1)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉System.Console.WriteLine("Using EXT_swap_control_tear VSync!");␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉else␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉System.Console.WriteLine("EXT_swap_control_tear unsupported. Fall back to standard VSync.");␊ |
␉␉␉␉␉␉SDL.SDL_ClearError();␊ |
␉␉␉␉␉␉SDL.SDL_GL_SetSwapInterval(1);␊ |
␉␉␉␉␉}␊ |
␉␉␉␉}␊ |
␉␉␉}␊ |
␉␉␉else if (interval == PresentInterval.Immediate)␊ |
␉␉␉{␊ |
␉␉␉␉SDL.SDL_GL_SetSwapInterval(0);␊ |
␉␉␉}␊ |
␉␉␉else if (interval == PresentInterval.Two)␊ |
␉␉␉{␊ |
␉␉␉␉SDL.SDL_GL_SetSwapInterval(2);␊ |
␉␉␉}␊ |
␉␉␉else␊ |
␉␉␉{␊ |
␉␉␉␉throw new Exception("Unrecognized PresentInterval!");␊ |
␉␉␉}␊ |
␉␉}␊ |
␊ |
␉␉public override void TextureDataFromStream(␊ |
␉␉␉Stream stream,␊ |
␉␉␉out int width,␊ |
␉␉␉out int height,␊ |
␉␉␉out byte[] pixels,␊ |
␉␉␉int reqWidth = -1,␊ |
␉␉␉int reqHeight = -1,␊ |
␉␉␉bool zoom = false␊ |
␉␉) {␊ |
␉␉␉// Load the Stream into an SDL_RWops*␊ |
␉␉␉byte[] mem = new byte[stream.Length];␊ |
␉␉␉GCHandle handle = GCHandle.Alloc(mem, GCHandleType.Pinned);␊ |
␉␉␉stream.Read(mem, 0, mem.Length);␊ |
␉␉␉IntPtr rwops = SDL.SDL_RWFromMem(mem, mem.Length);␊ |
␊ |
␉␉␉// Load the SDL_Surface* from RWops, get the image data␊ |
␉␉␉IntPtr surface = SDL_image.IMG_Load_RW(rwops, 1);␊ |
␉␉␉handle.Free();␊ |
␉␉␉if (surface == IntPtr.Zero)␊ |
␉␉␉{␊ |
␉␉␉␉// File not found, supported, etc.␊ |
␉␉␉␉width = 0;␊ |
␉␉␉␉height = 0;␊ |
␉␉␉␉pixels = null;␊ |
␉␉␉␉return;␊ |
␉␉␉}␊ |
␉␉␉surface = INTERNAL_convertSurfaceFormat(surface);␊ |
␊ |
␉␉␉// Image scaling, if applicable␊ |
␉␉␉if (reqWidth != -1 && reqHeight != -1)␊ |
␉␉␉{␊ |
␉␉␉␉// Get the file surface dimensions now...␊ |
␉␉␉␉int rw;␊ |
␉␉␉␉int rh;␊ |
␉␉␉␉unsafe␊ |
␉␉␉␉{␊ |
␉␉␉␉␉SDL_Surface* surPtr = (SDL_Surface*) surface;␊ |
␉␉␉␉␉rw = surPtr->w;␊ |
␉␉␉␉␉rh = surPtr->h;␊ |
␉␉␉␉}␊ |
␊ |
␉␉␉␉// Calculate the image scale factor␊ |
␉␉␉␉bool scaleWidth;␊ |
␉␉␉␉if (zoom)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉scaleWidth = rw < rh;␊ |
␉␉␉␉}␊ |
␉␉␉␉else␊ |
␉␉␉␉{␊ |
␉␉␉␉␉scaleWidth = rw > rh;␊ |
␉␉␉␉}␊ |
␉␉␉␉float scale;␊ |
␉␉␉␉if (scaleWidth)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉scale = reqWidth / (float) rw;␊ |
␉␉␉␉}␊ |
␉␉␉␉else␊ |
␉␉␉␉{␊ |
␉␉␉␉␉scale = reqHeight / (float) rh;␊ |
␉␉␉␉}␊ |
␊ |
␉␉␉␉// Calculate the scaled image size, crop if zoomed␊ |
␉␉␉␉int resultWidth;␊ |
␉␉␉␉int resultHeight;␊ |
␉␉␉␉SDL.SDL_Rect crop = new SDL.SDL_Rect();␊ |
␉␉␉␉if (zoom)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉resultWidth = reqWidth;␊ |
␉␉␉␉␉resultHeight = reqHeight;␊ |
␉␉␉␉␉if (scaleWidth)␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉crop.x = 0;␊ |
␉␉␉␉␉␉crop.w = rw;␊ |
␉␉␉␉␉␉crop.y = (int) (rh / 2 - (reqHeight / scale) / 2);␊ |
␉␉␉␉␉␉crop.h = (int) (reqHeight / scale);␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉else␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉crop.y = 0;␊ |
␉␉␉␉␉␉crop.h = rh;␊ |
␉␉␉␉␉␉crop.x = (int) (rw / 2 - (reqWidth / scale) / 2);␊ |
␉␉␉␉␉␉crop.w = (int) (reqWidth / scale);␊ |
␉␉␉␉␉}␊ |
␉␉␉␉}␊ |
␉␉␉␉else␊ |
␉␉␉␉{␊ |
␉␉␉␉␉resultWidth = (int) (rw * scale);␊ |
␉␉␉␉␉resultHeight = (int) (rh * scale);␊ |
␉␉␉␉}␊ |
␊ |
␉␉␉␉// Alloc surface, blit!␊ |
␉␉␉␉IntPtr newSurface = SDL.SDL_CreateRGBSurface(␊ |
␉␉␉␉␉0,␊ |
␉␉␉␉␉resultWidth,␊ |
␉␉␉␉␉resultHeight,␊ |
␉␉␉␉␉32,␊ |
␉␉␉␉␉0x000000FF,␊ |
␉␉␉␉␉0x0000FF00,␊ |
␉␉␉␉␉0x00FF0000,␊ |
␉␉␉␉␉0xFF000000␊ |
␉␉␉␉);␊ |
␉␉␉␉SDL.SDL_SetSurfaceBlendMode(␊ |
␉␉␉␉␉surface,␊ |
␉␉␉␉␉SDL.SDL_BlendMode.SDL_BLENDMODE_NONE␊ |
␉␉␉␉);␊ |
␉␉␉␉if (zoom)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉SDL.SDL_BlitScaled(␊ |
␉␉␉␉␉␉surface,␊ |
␉␉␉␉␉␉ref crop,␊ |
␉␉␉␉␉␉newSurface,␊ |
␉␉␉␉␉␉IntPtr.Zero␊ |
␉␉␉␉␉);␊ |
␉␉␉␉}␊ |
␉␉␉␉else␊ |
␉␉␉␉{␊ |
␉␉␉␉␉SDL.SDL_BlitScaled(␊ |
␉␉␉␉␉␉surface,␊ |
␉␉␉␉␉␉IntPtr.Zero,␊ |
␉␉␉␉␉␉newSurface,␊ |
␉␉␉␉␉␉IntPtr.Zero␊ |
␉␉␉␉␉);␊ |
␉␉␉␉}␊ |
␉␉␉␉SDL.SDL_FreeSurface(surface);␊ |
␉␉␉␉surface = newSurface;␊ |
␉␉␉}␊ |
␊ |
␉␉␉// Copy surface data to output managed byte array␊ |
␉␉␉unsafe␊ |
␉␉␉{␊ |
␉␉␉␉SDL_Surface* surPtr = (SDL_Surface*) surface;␊ |
␉␉␉␉width = surPtr->w;␊ |
␉␉␉␉height = surPtr->h;␊ |
␉␉␉␉pixels = new byte[width * height * 4]; // MUST be SurfaceFormat.Color!␊ |
␉␉␉␉Marshal.Copy(surPtr->pixels, pixels, 0, pixels.Length);␊ |
␉␉␉}␊ |
␉␉␉SDL.SDL_FreeSurface(surface);␊ |
␊ |
␉␉␉/* Ensure that the alpha pixels are... well, actual alpha.␊ |
␉␉␉ * You think this looks stupid, but be assured: Your paint program is␊ |
␉␉␉ * almost certainly even stupider.␊ |
␉␉␉ * -flibit␊ |
␉␉␉ */␊ |
␉␉␉for (int i = 0; i < pixels.Length; i += 4)␊ |
␉␉␉{␊ |
␉␉␉␉if (pixels[i + 3] == 0)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉pixels[i] = 0;␊ |
␉␉␉␉␉pixels[i + 1] = 0;␊ |
␉␉␉␉␉pixels[i + 2] = 0;␊ |
␉␉␉␉}␊ |
␉␉␉}␊ |
␉␉}␊ |
␊ |
␉␉public override void SavePNG(␊ |
␉␉␉Stream stream,␊ |
␉␉␉int width,␊ |
␉␉␉int height,␊ |
␉␉␉int imgWidth,␊ |
␉␉␉int imgHeight,␊ |
␉␉␉byte[] data␊ |
␉␉) {␊ |
␉␉␉// Create an SDL_Surface*, write the pixel data␊ |
␉␉␉IntPtr surface = SDL.SDL_CreateRGBSurface(␊ |
␉␉␉␉0,␊ |
␉␉␉␉imgWidth,␊ |
␉␉␉␉imgHeight,␊ |
␉␉␉␉32,␊ |
␉␉␉␉0x000000FF,␊ |
␉␉␉␉0x0000FF00,␊ |
␉␉␉␉0x00FF0000,␊ |
␉␉␉␉0xFF000000␊ |
␉␉␉);␊ |
␉␉␉SDL.SDL_LockSurface(surface);␊ |
␉␉␉unsafe␊ |
␉␉␉{␊ |
␉␉␉␉SDL_Surface* surPtr = (SDL_Surface*) surface;␊ |
␉␉␉␉Marshal.Copy(␊ |
␉␉␉␉␉data,␊ |
␉␉␉␉␉0,␊ |
␉␉␉␉␉surPtr->pixels,␊ |
␉␉␉␉␉data.Length␊ |
␉␉␉␉);␊ |
␉␉␉}␊ |
␉␉␉SDL.SDL_UnlockSurface(surface);␊ |
␉␉␉data = null; // We're done with the original pixel data.␊ |
␊ |
␉␉␉// Blit to a scaled surface of the size we want, if needed.␊ |
␉␉␉if (width != imgWidth || height != imgHeight)␊ |
␉␉␉{␊ |
␉␉␉␉IntPtr scaledSurface = SDL.SDL_CreateRGBSurface(␊ |
␉␉␉␉␉0,␊ |
␉␉␉␉␉width,␊ |
␉␉␉␉␉height,␊ |
␉␉␉␉␉32,␊ |
␉␉␉␉␉0x000000FF,␊ |
␉␉␉␉␉0x0000FF00,␊ |
␉␉␉␉␉0x00FF0000,␊ |
␉␉␉␉␉0xFF000000␊ |
␉␉␉␉);␊ |
␉␉␉␉SDL.SDL_BlitScaled(␊ |
␉␉␉␉␉surface,␊ |
␉␉␉␉␉IntPtr.Zero,␊ |
␉␉␉␉␉scaledSurface,␊ |
␉␉␉␉␉IntPtr.Zero␊ |
␉␉␉␉);␊ |
␉␉␉␉SDL.SDL_FreeSurface(surface);␊ |
␉␉␉␉surface = scaledSurface;␊ |
␉␉␉}␊ |
␊ |
␉␉␉// Create an SDL_RWops*, save PNG to RWops␊ |
␉␉␉const int pngHeaderSize = 41;␊ |
␉␉␉const int pngFooterSize = 57;␊ |
␉␉␉byte[] pngOut = new byte[␊ |
␉␉␉␉(width * height * 4) +␊ |
␉␉␉␉pngHeaderSize +␊ |
␉␉␉␉pngFooterSize +␊ |
␉␉␉␉256 // FIXME: Arbitrary zlib data padding for low-res images␊ |
␉␉␉]; // Max image size␊ |
␉␉␉IntPtr dst = SDL.SDL_RWFromMem(pngOut, pngOut.Length);␊ |
␉␉␉SDL_image.IMG_SavePNG_RW(surface, dst, 1);␊ |
␉␉␉SDL.SDL_FreeSurface(surface); // We're done with the surface.␊ |
␊ |
␉␉␉// Get PNG size, write to Stream␊ |
␉␉␉int size = (␊ |
␉␉␉␉(pngOut[33] << 24) |␊ |
␉␉␉␉(pngOut[34] << 16) |␊ |
␉␉␉␉(pngOut[35] << 8) |␊ |
␉␉␉␉(pngOut[36])␊ |
␉␉␉) + pngHeaderSize + pngFooterSize;␊ |
␉␉␉stream.Write(pngOut, 0, size);␊ |
␉␉}␊ |
␊ |
␉␉public override Keys GetKeyFromScancode(Keys scancode)␊ |
␉␉{␊ |
␉␉␉return SDL2_KeyboardUtil.KeyFromScancode(scancode);␊ |
␉␉}␊ |
␊ |
␉␉public override string GetStorageRoot()␊ |
␉␉{␊ |
␉␉␉if (OSVersion.Equals("Windows"))␊ |
␉␉␉{␊ |
␉␉␉␉return Path.Combine(␊ |
␉␉␉␉␉Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),␊ |
␉␉␉␉␉"SavedGames"␊ |
␉␉␉␉);␊ |
␉␉␉}␊ |
␉␉␉if (OSVersion.Equals("Mac OS X"))␊ |
␉␉␉{␊ |
␉␉␉␉string osConfigDir = Environment.GetEnvironmentVariable("HOME");␊ |
␉␉␉␉if (String.IsNullOrEmpty(osConfigDir))␊ |
␉␉␉␉{␊ |
␉␉␉␉␉return "."; // Oh well.␊ |
␉␉␉␉}␊ |
␉␉␉␉osConfigDir += "/Library/Application Support";␊ |
␉␉␉␉return osConfigDir;␊ |
␉␉␉}␊ |
␉␉␉if (OSVersion.Equals("Linux"))␊ |
␉␉␉{␊ |
␉␉␉␉// Assuming a non-OSX Unix platform will follow the XDG. Which it should.␊ |
␉␉␉␉string osConfigDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");␊ |
␉␉␉␉if (String.IsNullOrEmpty(osConfigDir))␊ |
␉␉␉␉{␊ |
␉␉␉␉␉osConfigDir = Environment.GetEnvironmentVariable("HOME");␊ |
␉␉␉␉␉if (String.IsNullOrEmpty(osConfigDir))␊ |
␉␉␉␉␉{␊ |
␉␉␉␉␉␉return ".";␉// Oh well.␊ |
␉␉␉␉␉}␊ |
␉␉␉␉␉osConfigDir += "/.local/share";␊ |
␉␉␉␉}␊ |
␉␉␉␉return osConfigDir;␊ |
␉␉␉}␊ |
␉␉␉throw new Exception("StorageDevice: Platform.OSVersion not handled!");␊ |
␉␉}␊ |
␊ |
␉␉public override bool IsStoragePathConnected(string path)␊ |
␉␉{␊ |
␉␉␉if (␉OSVersion.Equals("Linux") ||␊ |
␉␉␉␉OSVersion.Equals("Mac OS X")␉)␊ |
␉␉␉{␊ |
␉␉␉␉/* Linux and Mac use locally connected storage in the user's␊ |
␉␉␉␉ * home location, which should always be "connected".␊ |
␉␉␉␉ */␊ |
␉␉␉␉return true;␊ |
␉␉␉}␊ |
␉␉␉if (OSVersion.Equals("Windows"))␊ |
␉␉␉{␊ |
␉␉␉␉try␊ |
␉␉␉␉{␊ |
␉␉␉␉␉return new DriveInfo(path).IsReady;␊ |
␉␉␉␉}␊ |
␉␉␉␉catch␊ |
␉␉␉␉{␊ |
␉␉␉␉␉// The storageRoot path is invalid / has been removed.␊ |
␉␉␉␉␉return false;␊ |
␉␉␉␉}␊ |
␉␉␉}␊ |
␉␉␉throw new Exception("StorageDevice: Platform.OSVersion not handled!");␊ |
␉␉}␊ |
␊ |
␉␉public override void OnIsMouseVisibleChanged(bool visible)␊ |
␉␉{␊ |
␉␉␉SDL.SDL_ShowCursor(visible ? 1 : 0);␊ |
␉␉}␊ |
␊ |
␉␉#endregion␊ |
␊ |
␉␉#region Protected GamePlatform Methods␊ |
␊ |
␉␉protected override void Dispose(bool disposing)␊ |
␉␉{␊ |
␉␉␉if (!IsDisposed)␊ |
␉␉␉{␊ |
␉␉␉␉if (Game.Window != null)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉/* Some window managers might try to minimize the window as we're␊ |
␉␉␉␉␉ * destroying it. This looks pretty stupid and could cause problems,␊ |
␉␉␉␉␉ * so set this hint right before we destroy everything.␊ |
␉␉␉␉␉ * -flibit␊ |
␉␉␉␉␉ */␊ |
␉␉␉␉␉SDL.SDL_SetHintWithPriority(␊ |
␉␉␉␉␉␉SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,␊ |
␉␉␉␉␉␉"0",␊ |
␉␉␉␉␉␉SDL.SDL_HintPriority.SDL_HINT_OVERRIDE␊ |
␉␉␉␉␉);␊ |
␊ |
␉␉␉␉␉SDL.SDL_DestroyWindow(Game.Window.Handle);␊ |
␊ |
␉␉␉␉␉Game.Window = null;␊ |
␉␉␉␉}␊ |
␊ |
␉␉␉␉// This _should_ be the last SDL call we make...␊ |
␉␉␉␉SDL.SDL_Quit();␊ |
␉␉␉}␊ |
␊ |
␉␉␉base.Dispose(disposing);␊ |
␉␉}␊ |
␊ |
␉␉#endregion␊ |
␊ |
␉␉#region Private Static SDL_Surface Interop␊ |
␊ |
␉␉[StructLayout(LayoutKind.Sequential)]␊ |
␉␉private struct SDL_Surface␊ |
␉␉{␊ |
#pragma warning disable 0169␊ |
␉␉␉UInt32 flags;␊ |
␉␉␉public IntPtr format;␊ |
␉␉␉public Int32 w;␊ |
␉␉␉public Int32 h;␊ |
␉␉␉Int32 pitch;␊ |
␉␉␉public IntPtr pixels;␊ |
␉␉␉IntPtr userdata;␊ |
␉␉␉Int32 locked;␊ |
␉␉␉IntPtr lock_data;␊ |
␉␉␉SDL.SDL_Rect clip_rect;␊ |
␉␉␉IntPtr map;␊ |
␉␉␉Int32 refcount;␊ |
#pragma warning restore 0169␊ |
␉␉}␊ |
␊ |
␉␉private static unsafe IntPtr INTERNAL_convertSurfaceFormat(IntPtr surface)␊ |
␉␉{␊ |
␉␉␉IntPtr result = surface;␊ |
␉␉␉unsafe␊ |
␉␉␉{␊ |
␉␉␉␉SDL_Surface* surPtr = (SDL_Surface*) surface;␊ |
␉␉␉␉SDL.SDL_PixelFormat* pixelFormatPtr = (SDL.SDL_PixelFormat*) surPtr->format;␊ |
␊ |
␉␉␉␉// SurfaceFormat.Color is SDL_PIXELFORMAT_ABGR8888␊ |
␉␉␉␉if (pixelFormatPtr->format != SDL.SDL_PIXELFORMAT_ABGR8888)␊ |
␉␉␉␉{␊ |
␉␉␉␉␉// Create a properly formatted copy, free the old surface␊ |
␉␉␉␉␉result = SDL.SDL_ConvertSurfaceFormat(surface, SDL.SDL_PIXELFORMAT_ABGR8888, 0);␊ |
␉␉␉␉␉SDL.SDL_FreeSurface(surface);␊ |
␉␉␉␉}␊ |
␉␉␉}␊ |
␉␉␉return result;␊ |
␉␉}␊ |
␊ |
␉␉#endregion␊ |
␉}␊ |
}␊ |