From 6e321c77f39b5ec9425ef0005acb39abdad6e1e4 Mon Sep 17 00:00:00 2001 From: gered Date: Sat, 17 Aug 2013 15:15:24 -0400 Subject: [PATCH] initial SDL platform implementation support yay for code-dumps! --- .../Blarg.GameFramework.SDL2.csproj | 4 + Blarg.GameFramework.SDL2/CurrentOS.cs | 152 +++++ Blarg.GameFramework.SDL2/SDLConfiguration.cs | 36 ++ Blarg.GameFramework.SDL2/SDLLooper.cs | 550 +++++++++++++++++- .../SDLPlatformServices.cs | 38 ++ Blarg.GameFramework.SDL2/SDLWindow.cs | 16 + Blarg.GameFramework/BaseLooper.cs | 91 +++ .../Blarg.GameFramework.csproj | 3 + Blarg.GameFramework/IGameApp.cs | 3 +- Blarg.GameFramework/ILooper.cs | 9 +- Blarg.GameFramework/IPlatformConfiguration.cs | 9 + Blarg.GameFramework/IPlatformWindow.cs | 16 + Blarg.GameFramework/Platform.cs | 16 +- Game.Core/Game.Core.csproj | 4 + Game.Core/GameApp.cs | 65 +++ Game.SDL2/Game.SDL2.csproj | 4 +- Game.SDL2/Program.cs | 10 +- 17 files changed, 986 insertions(+), 40 deletions(-) create mode 100644 Blarg.GameFramework.SDL2/CurrentOS.cs create mode 100644 Blarg.GameFramework.SDL2/SDLConfiguration.cs create mode 100644 Blarg.GameFramework.SDL2/SDLPlatformServices.cs create mode 100644 Blarg.GameFramework.SDL2/SDLWindow.cs create mode 100644 Blarg.GameFramework/BaseLooper.cs create mode 100644 Blarg.GameFramework/IPlatformConfiguration.cs create mode 100644 Blarg.GameFramework/IPlatformWindow.cs create mode 100644 Game.Core/GameApp.cs diff --git a/Blarg.GameFramework.SDL2/Blarg.GameFramework.SDL2.csproj b/Blarg.GameFramework.SDL2/Blarg.GameFramework.SDL2.csproj index 25a7532..5db992a 100644 --- a/Blarg.GameFramework.SDL2/Blarg.GameFramework.SDL2.csproj +++ b/Blarg.GameFramework.SDL2/Blarg.GameFramework.SDL2.csproj @@ -49,6 +49,10 @@ + + + + diff --git a/Blarg.GameFramework.SDL2/CurrentOS.cs b/Blarg.GameFramework.SDL2/CurrentOS.cs new file mode 100644 index 0000000..a01b34d --- /dev/null +++ b/Blarg.GameFramework.SDL2/CurrentOS.cs @@ -0,0 +1,152 @@ +// +// CurrentOS Class by blez +// http://blez.wordpress.com/2012/09/17/determine-os-with-netmono/ +// Detects the current OS (Windows, Linux, MacOS) +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +using System.Diagnostics; +using System.IO; + +namespace Blarg.GameFramework +{ + public static class CurrentOS + { + public static bool IsWindows { get; private set; } + public static bool IsUnix { get; private set; } + public static bool IsMac { get; private set; } + public static bool IsLinux { get; private set; } + public static bool IsUnknown { get; private set; } + public static bool Is32bit { get; private set; } + public static bool Is64bit { get; private set; } + public static bool Is64BitProcess { get { return (IntPtr.Size == 8); } } + public static bool Is32BitProcess { get { return (IntPtr.Size == 4); } } + public static string Name { get; private set; } + + [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWow64Process([In] IntPtr hProcess, [Out] out bool wow64Process); + + private static bool Is64bitWindows + { + get + { + if ((Environment.OSVersion.Version.Major == 5 && Environment.OSVersion.Version.Minor >= 1) || Environment.OSVersion.Version.Major >= 6) + { + using (Process p = Process.GetCurrentProcess()) + { + bool retVal; + if (!IsWow64Process(p.Handle, out retVal)) + return false; + return retVal; + } + } + else + return false; + } + } + + static CurrentOS() + { + IsWindows = Path.DirectorySeparatorChar == '\\'; + if (IsWindows) + { + Name = Environment.OSVersion.VersionString; + + Name = Name.Replace("Microsoft ", ""); + Name = Name.Replace(" ", " "); + Name = Name.Replace(" )", ")"); + Name = Name.Trim(); + + Name = Name.Replace("NT 6.2", "8 %bit 6.2"); + Name = Name.Replace("NT 6.1", "7 %bit 6.1"); + Name = Name.Replace("NT 6.0", "Vista %bit 6.0"); + Name = Name.Replace("NT 5.", "XP %bit 5."); + Name = Name.Replace("%bit", (Is64bitWindows ? "64bit" : "32bit")); + + if (Is64bitWindows) + Is64bit = true; + else + Is32bit = true; + } + else + { + string UnixName = ReadProcessOutput("uname"); + if (UnixName.Contains("Darwin")) + { + IsUnix = true; + IsMac = true; + + Name = "MacOS X " + ReadProcessOutput("sw_vers", "-productVersion"); + Name = Name.Trim(); + + string machine = ReadProcessOutput("uname", "-m"); + if (machine.Contains("x86_64")) + Is64bit = true; + else + Is32bit = true; + + Name += " " + (Is32bit ? "32bit" : "64bit"); + } + else if (UnixName.Contains("Linux")) + { + IsUnix = true; + IsLinux = true; + + Name = ReadProcessOutput("lsb_release", "-d"); + Name = Name.Substring(Name.IndexOf(":") + 1); + Name = Name.Trim(); + + string machine = ReadProcessOutput("uname", "-m"); + if (machine.Contains("x86_64")) + Is64bit = true; + else + Is32bit = true; + + Name += " " + (Is32bit ? "32bit" : "64bit"); + } + else if (UnixName != "") + IsUnix = true; + else + IsUnknown = true; + } + } + + private static string ReadProcessOutput(string name) + { + return ReadProcessOutput(name, null); + } + + private static string ReadProcessOutput(string name, string args) + { + try + { + Process p = new Process(); + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + if (args != null && args != "") + p.StartInfo.Arguments = " " + args; + p.StartInfo.FileName = name; + p.Start(); + // Do not wait for the child process to exit before + // reading to the end of its redirected stream. + // p.WaitForExit(); + // Read the output stream first and then wait. + string output = p.StandardOutput.ReadToEnd(); + p.WaitForExit(); + if (output == null) + output = ""; + output = output.Trim(); + return output; + } + catch + { + return ""; + } + } + } +} + diff --git a/Blarg.GameFramework.SDL2/SDLConfiguration.cs b/Blarg.GameFramework.SDL2/SDLConfiguration.cs new file mode 100644 index 0000000..412dac5 --- /dev/null +++ b/Blarg.GameFramework.SDL2/SDLConfiguration.cs @@ -0,0 +1,36 @@ +using System; + +namespace Blarg.GameFramework +{ + public class SDLConfiguration : IPlatformConfiguration + { + public string Title; + public int Width; + public int Height; + public bool Fullscreen; + public bool Resizeable; + + public bool glDoubleBuffer; + public int glDepthBufferSize; + public int glRedSize; + public int glGreenSize; + public int glBlueSize; + public int glAlphaSize; + + public SDLConfiguration() + { + Title = "SDL Application"; + Width = 854; + Height = 480; + Fullscreen = false; + Resizeable = false; + glDoubleBuffer = true; + glDepthBufferSize = 24; + glRedSize = 8; + glGreenSize = 8; + glBlueSize = 8; + glAlphaSize = 8; + } + } +} + diff --git a/Blarg.GameFramework.SDL2/SDLLooper.cs b/Blarg.GameFramework.SDL2/SDLLooper.cs index 7c102d9..af77bc1 100644 --- a/Blarg.GameFramework.SDL2/SDLLooper.cs +++ b/Blarg.GameFramework.SDL2/SDLLooper.cs @@ -1,84 +1,578 @@ using System; +using System.Diagnostics; using SDL2; +using Blarg.GameFramework.Graphics; using Blarg.GameFramework.Input; using Blarg.GameFramework.IO; namespace Blarg.GameFramework { - public class SDLLooper : ILooper + public class SDLLooper : BaseLooper { + const string LOOPER_TAG = "SDLLOOPER"; + bool _isSDLinited; IntPtr _window; IntPtr _glContext; SDLKeyboard _keyboard; SDLMouse _mouse; SDLFileSystem _filesystem; + SDLWindow _windowInfo; + + bool _isWindowActive; + bool _isPaused; + bool _isQuitting; + + int _fps; + float _frameTime; + int _rendersPerSecond; + int _updatesPerSecond; + int _renderTime; + int _updateTime; + + int _targetUpdatesPerSecond; + int _ticksPerUpdate; + float _fixedUpdateInterval; + float _fixedRenderInterval; + int _maxFrameSkip = 10; + + public override int FPS + { + get { return _fps; } + } + + public override float FrameTime + { + get { return _frameTime; } + } + + public override int RendersPerSecond + { + get { return _rendersPerSecond; } + } + + public override int UpdatesPerSecond + { + get { return _updatesPerSecond; } + } + + public override int RenderTime + { + get { return _renderTime; } + } + + public override int UpdateTime + { + get { return _updateTime; } + } public SDLLooper() { + Platform.Services = new SDLPlatformServices(); + _windowInfo = new SDLWindow(); + + SetUpdateFrequency(60); } - public void Run(IGameApp gameApp) + public override void Run(IGameApp gameApp, IPlatformConfiguration config) { if (gameApp == null) throw new ArgumentNullException("gameApp"); + GameApp = gameApp; - Platform.Services.Logger.Info("Looper", "Running..."); + if (config == null) + throw new ArgumentNullException("config"); + if (!(config is SDLConfiguration)) + throw new ArgumentException("Must pass a SDLConfiguration object.", "config"); + + Platform.Services.Logger.Info(LOOPER_TAG, "Running..."); + + SDLConfiguration sdlConfig = (SDLConfiguration)config; + + Platform.Services.Logger.Info(LOOPER_TAG, "Received SDL configuration:"); + Platform.Services.Logger.Info(LOOPER_TAG, "\tTitle: {0}", sdlConfig.Title); + Platform.Services.Logger.Info(LOOPER_TAG, "\tWidth: {0}", sdlConfig.Width); + Platform.Services.Logger.Info(LOOPER_TAG, "\tHeight: {0}", sdlConfig.Height); + Platform.Services.Logger.Info(LOOPER_TAG, "\tFullscreen: {0}", sdlConfig.Fullscreen); + Platform.Services.Logger.Info(LOOPER_TAG, "\tResizeable: {0}", sdlConfig.Resizeable); + Platform.Services.Logger.Info(LOOPER_TAG, "GL Doublebuffer: {0}", sdlConfig.glDoubleBuffer); + Platform.Services.Logger.Info(LOOPER_TAG, "GL Depth Buffer Size: {0}", sdlConfig.glDepthBufferSize); + Platform.Services.Logger.Info(LOOPER_TAG, "GL Red Size: {0}", sdlConfig.glRedSize); + Platform.Services.Logger.Info(LOOPER_TAG, "GL Green Size: {0}", sdlConfig.glGreenSize); + Platform.Services.Logger.Info(LOOPER_TAG, "GL Blue Size: {0}", sdlConfig.glBlueSize); + Platform.Services.Logger.Info(LOOPER_TAG, "GL Alpha Size: {0}", sdlConfig.glAlphaSize); if (!InitSDL()) { - Platform.Services.Logger.Error("Looper", "SDL initialization failed. Aborting."); + Platform.Services.Logger.Error(LOOPER_TAG, "SDL initialization failed. Aborting."); return; } + + if (!InitSDLWindow(sdlConfig)) + { + Platform.Services.Logger.Error(LOOPER_TAG, "SDL window creation failed. Aborting."); + return; + } + + (Platform.Services as SDLPlatformServices).Keyboard = _keyboard; + (Platform.Services as SDLPlatformServices).Mouse = _mouse; + (Platform.Services as SDLPlatformServices).FileSystem = _filesystem; + (Platform.Services as SDLPlatformServices).GL = new PortableGL.SDL.SDLGL20(); + + OnNewContext(); + OnResize(ScreenOrientation.Rotation0, _windowInfo.ClientRectangle); + OnLoad(); + + Platform.Services.Logger.Info(LOOPER_TAG, "Main loop starting."); + MainLoop(); + Platform.Services.Logger.Info(LOOPER_TAG, "Main loop finished."); + + OnUnload(); + OnLostContext(); + + GameApp.Dispose(); + GameApp = null; + + ReleaseSDL(); } private bool InitSDL() { - Platform.Services.Logger.Info("Looper", "SDL initialization starting."); + Platform.Services.Logger.Info(LOOPER_TAG, "SDL initialization starting."); SDL.SDL_version sdlVersion; SDL.SDL_VERSION(out sdlVersion); - Platform.Services.Logger.Info("Looper", "SDL Runtime Version: {0}.{1}.{2}", sdlVersion.major, sdlVersion.minor, sdlVersion.patch); - Platform.Services.Logger.Info("Looper", "SDL Linked Version: {0}.{1}.{2}", SDL.SDL_MAJOR_VERSION, SDL.SDL_MINOR_VERSION, SDL.SDL_PATCHLEVEL); + Platform.Services.Logger.Info(LOOPER_TAG, "SDL Runtime Version: {0}.{1}.{2}", sdlVersion.major, sdlVersion.minor, sdlVersion.patch); + Platform.Services.Logger.Info(LOOPER_TAG, "SDL Linked Version: {0}.{1}.{2}", SDL.SDL_MAJOR_VERSION, SDL.SDL_MINOR_VERSION, SDL.SDL_PATCHLEVEL); if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO | SDL.SDL_INIT_AUDIO | SDL.SDL_INIT_GAMECONTROLLER | SDL.SDL_INIT_JOYSTICK | SDL.SDL_INIT_TIMER) == -1) { - Platform.Services.Logger.Error("Looper", "SDL_Init() failed: {0}", SDL.SDL_GetError()); + Platform.Services.Logger.Error(LOOPER_TAG, "SDL_Init() failed: {0}", SDL.SDL_GetError()); return false; } + _isSDLinited = true; _keyboard = new SDLKeyboard(); - Platform.Services.Logger.Info("Looper", "Keyboard input device ready."); + Platform.Services.Logger.Info(LOOPER_TAG, "Keyboard input device ready."); _mouse = new SDLMouse(); - Platform.Services.Logger.Info("Looper", "Mouse input device ready."); + Platform.Services.Logger.Info(LOOPER_TAG, "Mouse input device ready."); int numJoysticks = SDL.SDL_NumJoysticks(); - Platform.Services.Logger.Info("Looper", "{0} joystick input devices found.", numJoysticks); + Platform.Services.Logger.Info(LOOPER_TAG, "{0} joystick input devices found.", numJoysticks); for (int i = 0; i < numJoysticks; ++i) { - Platform.Services.Logger.Info("Looper", "Joystick #{0}. {1}:", (i + 1), SDL.SDL_JoystickNameForIndex(i)); + Platform.Services.Logger.Info(LOOPER_TAG, "Joystick #{0}. {1}:", (i + 1), SDL.SDL_JoystickNameForIndex(i)); IntPtr joystick = SDL.SDL_JoystickOpen(i); if (joystick != IntPtr.Zero) { - Platform.Services.Logger.Info("Looper", "\tAxes: {0}", SDL.SDL_JoystickNumAxes(joystick)); - Platform.Services.Logger.Info("Looper", "\tBalls: {0}", SDL.SDL_JoystickNumBalls(joystick)); - Platform.Services.Logger.Info("Looper", "\tHats: {0}", SDL.SDL_JoystickNumHats(joystick)); - Platform.Services.Logger.Info("Looper", "\tButtons: {0}", SDL.SDL_JoystickNumButtons(joystick)); + Platform.Services.Logger.Info(LOOPER_TAG, "\tAxes: {0}", SDL.SDL_JoystickNumAxes(joystick)); + Platform.Services.Logger.Info(LOOPER_TAG, "\tBalls: {0}", SDL.SDL_JoystickNumBalls(joystick)); + Platform.Services.Logger.Info(LOOPER_TAG, "\tHats: {0}", SDL.SDL_JoystickNumHats(joystick)); + Platform.Services.Logger.Info(LOOPER_TAG, "\tButtons: {0}", SDL.SDL_JoystickNumButtons(joystick)); SDL.SDL_JoystickClose(joystick); } else - Platform.Services.Logger.Warn("Looper", "\tMore information could not be obtained."); + Platform.Services.Logger.Warn(LOOPER_TAG, "\tMore information could not be obtained."); } _filesystem = new SDLFileSystem(); - Platform.Services.Logger.Info("Looper", "Filesystem access initialized."); + Platform.Services.Logger.Info(LOOPER_TAG, "Filesystem access initialized."); - Platform.Services.Logger.Info("Looper", "SDL initialization finished."); + int numVideoDrivers = SDL.SDL_GetNumVideoDrivers(); + Platform.Services.Logger.Info(LOOPER_TAG, "Video drivers present: {0}.", numVideoDrivers); + for (int i = 0; i < numVideoDrivers; ++i) + Platform.Services.Logger.Info(LOOPER_TAG, "\t{0}: {1}", (i + 1), SDL.SDL_GetVideoDriver(i)); + Platform.Services.Logger.Info(LOOPER_TAG, "Currently using video driver: {0}", SDL.SDL_GetCurrentVideoDriver()); + + int numAudioDrivers = SDL.SDL_GetNumAudioDrivers(); + Platform.Services.Logger.Info(LOOPER_TAG, "Audio drivers present: {0}", numAudioDrivers); + for (int i = 0; i < numAudioDrivers; ++i) + Platform.Services.Logger.Info(LOOPER_TAG, "\t{0}: {1}", (i + 1), SDL.SDL_GetAudioDriver(i)); + Platform.Services.Logger.Info(LOOPER_TAG, "Currently using audio driver: {0}", SDL.SDL_GetCurrentAudioDriver()); + + Platform.Services.Logger.Info(LOOPER_TAG, "SDL initialization finished."); return true; } + private void SetUpdateFrequency(int targetFrequency) + { + _targetUpdatesPerSecond = targetFrequency; + _ticksPerUpdate = 1000 / _targetUpdatesPerSecond; + _fixedUpdateInterval = _ticksPerUpdate / 1000.0f; + } + + private void MainLoop() + { + _isWindowActive = true; + _isPaused = false; + _isQuitting = false; + + int numUpdatesThisFrame = 0; + int numLoops = 0; + int timeElapsed = 0; + bool isDirty = false; + + bool isRunningSlowly = false; + + int updateTime = 0; + int renderTime = 0; + int numUpdates = 0; + int numRenders = 0; + + int nextUpdateAt = Environment.TickCount; + int currentTime = Environment.TickCount; + + while (!_isQuitting) + { + if (_isPaused) + { + // we always want to be processing events, even when paused + DoEvents(); + } + else + { + int newTime = Environment.TickCount; + int frameTime = newTime - currentTime; + currentTime = newTime; + timeElapsed += frameTime; + + // every second recalculate the FPS + if (timeElapsed >= 1000) + { + _fps = numLoops; + _frameTime = 1000.0f / _fps; + + _rendersPerSecond = numRenders; + _updatesPerSecond = numUpdates; + _renderTime = renderTime; + _updateTime = updateTime; + + numUpdates = 0; + numRenders = 0; + + numLoops = 0; + timeElapsed = 0; + } + + // we're "running slowly" if we're more then one update behind + if (currentTime > nextUpdateAt + _ticksPerUpdate) + isRunningSlowly = true; + else + isRunningSlowly = false; + + numUpdatesThisFrame = 0; + while (Environment.TickCount >= nextUpdateAt && numUpdatesThisFrame < _maxFrameSkip) + { + if (numUpdatesThisFrame > 0) + isRunningSlowly = true; + + int before = Environment.TickCount; + DoEvents(); + OnUpdate(_fixedUpdateInterval); + updateTime += Environment.TickCount - before; + + ++numUpdatesThisFrame; + nextUpdateAt += _ticksPerUpdate; + + ++numUpdates; + + // just updated, so we need to render the new game state + isDirty = true; + } + + if (isDirty && _isWindowActive) + { + int before = Environment.TickCount; + OnRender(_fixedRenderInterval); // TODO + SDL.SDL_GL_SwapWindow(_window); + renderTime += Environment.TickCount - before; + + ++numRenders; + + // don't render again until we hve something new to show + isDirty = false; + } + + ++numLoops; + } + } + } + + #region Window Management + + private bool InitSDLWindow(SDLConfiguration config) + { + Platform.Services.Logger.Info(LOOPER_TAG, "SDL Window initialization starting."); + + int flags = (int)SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | (int)SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN; + if (config.Fullscreen) + flags |= (int)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN; + if (config.Resizeable) + flags |= (int)SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE; + + if (!CreateWindow(config.Title, config.Width, config.Height, flags)) + return false; + + if (!CreateOpenGLContext(config)) + return false; + + Platform.Services.Logger.Info(LOOPER_TAG, "SDL Window initialization finished."); + return true; + } + + private bool CreateWindow(string title, int width, int height, int flags) + { + Platform.Services.Logger.Info(LOOPER_TAG, "Attempting to set up new window with dimensions {0}x{1}.", width, height); + if (_window != IntPtr.Zero) + throw new InvalidOperationException("Cannot create new window before destorying existing one."); + + _window = SDL.SDL_CreateWindow(title, SDL.SDL_WINDOWPOS_UNDEFINED, SDL.SDL_WINDOWPOS_UNDEFINED, width, height, (SDL.SDL_WindowFlags)flags); + if (_window == IntPtr.Zero) + { + Platform.Services.Logger.Error(LOOPER_TAG, "Window creation failed: {0}", SDL.SDL_GetError()); + return false; + } + + SetWindowInfo(); + + Platform.Services.Logger.Info(LOOPER_TAG, "Window creation succeeded."); + return true; + } + + private bool DestroyWindow() + { + Platform.Services.Logger.Info(LOOPER_TAG, "Destroying window."); + if (_window == IntPtr.Zero) + { + Platform.Services.Logger.Warn(LOOPER_TAG, "No window currently exists, not doing anything."); + return true; + } + + SDL.SDL_DestroyWindow(_window); + _window = IntPtr.Zero; + + Platform.Services.Logger.Info(LOOPER_TAG, "Window destroyed."); + return true; + } + + private void SetWindowInfo() + { + if (_window == IntPtr.Zero) + throw new InvalidOperationException("Cannot set window info for a non-existant window."); + + int windowX; + int windowY; + SDL.SDL_GetWindowPosition(_window, out windowX, out windowY); + + int clientWidth; + int clientHeight; + SDL.SDL_GetWindowSize(_window, out clientWidth, out clientHeight); + + _windowInfo.ClientWidth = clientWidth; + _windowInfo.ClientHeight = clientHeight; + _windowInfo.ClientRectangle = new Rect(0, 0, clientWidth, clientHeight); + + Platform.Services.Logger.Info(LOOPER_TAG, "Window content area set to {0}", _windowInfo.ClientRectangle); + } + + #endregion + + #region OpenGL Context Management + + private bool CreateOpenGLContext(SDLConfiguration config) + { + Platform.Services.Logger.Info(LOOPER_TAG, "Attempting to create OpenGL context."); + if (_glContext != IntPtr.Zero) + throw new InvalidOperationException("Cannoy create new OpenGL context before destroying existing one."); + if (_window == IntPtr.Zero) + throw new InvalidOperationException("Cannot create an OpenGL context without an existing window."); + + // minimum requirements + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, config.glDoubleBuffer ? 1 : 0); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, config.glDepthBufferSize); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, config.glRedSize); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, config.glGreenSize); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, config.glBlueSize); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, config.glAlphaSize); + + _glContext = SDL.SDL_GL_CreateContext(_window); + if (_glContext == IntPtr.Zero) + { + Platform.Services.Logger.Error(LOOPER_TAG, "OpenGL context creation failed: {0}", SDL.SDL_GetError()); + return false; + } + + Platform.Services.Logger.Info(LOOPER_TAG, "OpenGL context creation succeeded."); + + Platform.Services.Logger.Info(LOOPER_TAG, "Setting OpenTK's OpenGL context and loading OpenGL extensions."); + OpenTK.Graphics.GraphicsContext.CurrentContext = _glContext; + OpenTK.Graphics.OpenGL.GL.LoadAll(); + + int redSize; + int greenSize; + int blueSize; + int alphaSize; + int bufferSize; + int doubleBuffer; + int depthSize; + int stencilSize; + int accumRedSize; + int accumGreenSize; + int accumBlueSize; + int accumAlphaSize; + int stereo; + int multisampleBuffers; + int multisampleSamples; + int acceleratedVisual; + int contextMajorVersion; + int contextMinorVersion; + + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, out redSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, out greenSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, out blueSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, out alphaSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_BUFFER_SIZE, out bufferSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, out doubleBuffer); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, out depthSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, out stencilSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_RED_SIZE, out accumRedSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_GREEN_SIZE, out accumGreenSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_BLUE_SIZE, out accumBlueSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCUM_ALPHA_SIZE, out accumAlphaSize); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_STEREO, out stereo); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_MULTISAMPLEBUFFERS, out multisampleBuffers); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_MULTISAMPLESAMPLES, out multisampleSamples); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, out acceleratedVisual); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, out contextMajorVersion); + SDL.SDL_GL_GetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, out contextMinorVersion); + + Platform.Services.Logger.Info(LOOPER_TAG, "OpenGL context attributes:"); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_RED_SIZE: {0}", redSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_GREEN_SIZE: {0}", greenSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_BLUE_SIZE: {0}", blueSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_ALPHA_SIZE: {0}", alphaSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_BUFFER_SIZE: {0}", bufferSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_DOUBLEBUFFER: {0}", doubleBuffer); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_DEPTH_SIZE: {0}", depthSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_STENCIL_SIZE: {0}", stencilSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_ACCUM_RED_SIZE: {0}", accumRedSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_ACCUM_GREEN_SIZE: {0}", accumGreenSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_ACCUM_BLUE_SIZE: {0}", accumBlueSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_ACCUM_ALPHA_SIZE: {0}", accumAlphaSize); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_STEREO: {0}", stereo); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_MULTISAMPLEBUFFERS: {0}", multisampleBuffers); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_MULTISAMPLESAMPLES: {0}", multisampleSamples); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_ACCELERATED_VISUAL: {0}", acceleratedVisual); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_CONTEXT_MAJOR_VERSION: {0}", contextMajorVersion); + Platform.Services.Logger.Info(LOOPER_TAG, "\tGL_CONTEXT_MINOR_VERSION: {0}", contextMinorVersion); + + Platform.Services.Logger.Info(LOOPER_TAG, "Attempting to enable V-sync."); + if (SDL.SDL_GL_SetSwapInterval(1) != 0) + Platform.Services.Logger.Warn(LOOPER_TAG, "Could not set swap interval: {0}", SDL.SDL_GetError()); + else + Platform.Services.Logger.Info(LOOPER_TAG, "Swap interval set successful."); + + return true; + } + + private bool DestroyOpenGLContext() + { + Platform.Services.Logger.Info(LOOPER_TAG, "Destroying OpenGL context."); + if (_glContext == IntPtr.Zero) + { + Platform.Services.Logger.Warn(LOOPER_TAG, "No OpenGL context currently exists, not doing anything."); + return true; + } + + SDL.SDL_GL_DeleteContext(_glContext); + _glContext = IntPtr.Zero; + + Platform.Services.Logger.Info(LOOPER_TAG, "OpenGL context destroyed."); + return true; + } + + #endregion + + #region Events + + private void DoEvents() + { + SDL.SDL_Event e; + while (SDL.SDL_PollEvent(out e) != 0) + { + if (e.type == SDL.SDL_EventType.SDL_WINDOWEVENT) + { + switch (e.window.windowEvent) + { + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MINIMIZED: + Platform.Services.Logger.Info(LOOPER_TAG, "Window focus lost."); + Platform.Services.Logger.Info(LOOPER_TAG, "Window marked inactive."); + break; + + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESTORED: + Platform.Services.Logger.Info(LOOPER_TAG, "Window focus gained."); + Platform.Services.Logger.Info(LOOPER_TAG, "Window marked active."); + break; + + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_ENTER: + Platform.Services.Logger.Info(LOOPER_TAG, "Gained mouse focus."); + break; + + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE: + Platform.Services.Logger.Info(LOOPER_TAG, "Lost mouse focus."); + break; + + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED: + Platform.Services.Logger.Info(LOOPER_TAG, "Gained input device focus."); + OnAppGainFocus(); + break; + + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST: + Platform.Services.Logger.Info(LOOPER_TAG, "Lost input device focus."); + OnAppLostFocus(); + break; + + case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED: + Platform.Services.Logger.Info(LOOPER_TAG, "Window resized to {0}x{1}.", e.window.data1, e.window.data2); + Rect size = new Rect(); + size.Right = e.window.data1; + size.Bottom = e.window.data2; + SetWindowInfo(); + OnResize(ScreenOrientation.Rotation0, size); + break; + } + } + else + { + switch (e.type) + { + case SDL.SDL_EventType.SDL_QUIT: + Platform.Services.Logger.Info(LOOPER_TAG, "Event: SQL_QUIT"); + _isQuitting = true; + break; + + case SDL.SDL_EventType.SDL_KEYDOWN: + case SDL.SDL_EventType.SDL_KEYUP: + _keyboard.OnKeyEvent(e.key); + break; + + case SDL.SDL_EventType.SDL_MOUSEMOTION: + _mouse.OnMotionEvent(e.motion); + break; + + case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN: + case SDL.SDL_EventType.SDL_MOUSEBUTTONUP: + _mouse.OnButtonEvent(e.button); + break; + } + } + } + } + + #endregion + #region IDisposable private void ReleaseSDL() @@ -86,20 +580,14 @@ namespace Blarg.GameFramework if (!_isSDLinited) return; - if (_glContext != IntPtr.Zero) - { - SDL.SDL_GL_DeleteContext(_glContext); - _glContext = IntPtr.Zero; - } - if (_window != IntPtr.Zero) - { - SDL.SDL_DestroyWindow(_window); - _window = IntPtr.Zero; - } + Platform.Services.Logger.Info(LOOPER_TAG, "Releasing SDL."); + DestroyOpenGLContext(); + DestroyWindow(); SDL.SDL_Quit(); + _isSDLinited = false; - Platform.Services.Logger.Info("Looper", "SDL shutdown."); + Platform.Services.Logger.Info(LOOPER_TAG, "SDL shutdown."); } ~SDLLooper() @@ -107,7 +595,7 @@ namespace Blarg.GameFramework ReleaseSDL(); } - public void Dispose() + public override void Dispose() { ReleaseSDL(); GC.SuppressFinalize(this); diff --git a/Blarg.GameFramework.SDL2/SDLPlatformServices.cs b/Blarg.GameFramework.SDL2/SDLPlatformServices.cs new file mode 100644 index 0000000..9417f62 --- /dev/null +++ b/Blarg.GameFramework.SDL2/SDLPlatformServices.cs @@ -0,0 +1,38 @@ +using System; +using PortableGL; +using Blarg.GameFramework.Input; +using Blarg.GameFramework.IO; + +namespace Blarg.GameFramework +{ + public class SDLPlatformServices : IPlatformServices + { + public SDLPlatformServices() + { + if (CurrentOS.IsWindows) + OperatingSystem = PlatformOS.Windows; + else if (CurrentOS.IsLinux) + OperatingSystem = PlatformOS.Linux; + else if (CurrentOS.IsMac) + OperatingSystem = PlatformOS.MacOS; + else + throw new Exception("Unable to determine OS."); + + Logger = new SDLLogger(); + } + + public PlatformOS OperatingSystem { get; private set; } + public PlatformType Type + { + get { return PlatformType.Desktop; } + } + + public IPlatformLogger Logger { get; internal set; } + public IFileSystem FileSystem { get; internal set; } + public IKeyboard Keyboard { get; internal set; } + public IMouse Mouse { get; internal set; } + public ITouchScreen TouchScreen { get; internal set; } + public GL20 GL { get; internal set; } + } +} + diff --git a/Blarg.GameFramework.SDL2/SDLWindow.cs b/Blarg.GameFramework.SDL2/SDLWindow.cs new file mode 100644 index 0000000..71bd42d --- /dev/null +++ b/Blarg.GameFramework.SDL2/SDLWindow.cs @@ -0,0 +1,16 @@ +using System; + +namespace Blarg.GameFramework +{ + public class SDLWindow : IPlatformWindow + { + public Rect ClientRectangle { get; internal set; } + public int ClientWidth { get; internal set; } + public int ClientHeight { get; internal set; } + public int X { get; internal set; } + public int Y { get; internal set; } + public int Width { get; internal set; } + public int Height { get; internal set; } + } +} + diff --git a/Blarg.GameFramework/BaseLooper.cs b/Blarg.GameFramework/BaseLooper.cs new file mode 100644 index 0000000..f0a96a9 --- /dev/null +++ b/Blarg.GameFramework/BaseLooper.cs @@ -0,0 +1,91 @@ +using System; +using Blarg.GameFramework.Graphics; + +namespace Blarg.GameFramework +{ + public abstract class BaseLooper : ILooper + { + IGameApp _gameApp; + + public IGameApp GameApp + { + get + { + return _gameApp; + } + protected set + { + _gameApp = value; + } + } + + public abstract int FPS { get; } + public abstract float FrameTime { get; } + public abstract int RendersPerSecond { get; } + public abstract int UpdatesPerSecond { get; } + public abstract int RenderTime { get; } + public abstract int UpdateTime { get; } + + public abstract void Run(IGameApp gameApp, IPlatformConfiguration config); + + protected void OnAppGainFocus() + { + GameApp.OnAppGainFocus(); + } + + protected void OnAppLostFocus() + { + GameApp.OnAppLostFocus(); + } + + protected void OnAppPause() + { + GameApp.OnAppPause(); + } + + protected void OnAppResume() + { + GameApp.OnAppResume(); + } + + protected void OnLoad() + { + GameApp.OnLoad(); + } + + protected void OnUnload() + { + GameApp.OnUnload(); + } + + protected void OnLostContext() + { + GameApp.OnLostContext(); + } + + protected void OnNewContext() + { + GameApp.OnNewContext(); + } + + protected void OnRender(float delta) + { + GameApp.OnRender(delta); + } + + protected void OnResize(ScreenOrientation orientation, Rect size) + { + GameApp.OnResize(orientation, size); + } + + protected void OnUpdate(float delta) + { + GameApp.OnUpdate(delta); + } + + public virtual void Dispose() + { + } + } +} + diff --git a/Blarg.GameFramework/Blarg.GameFramework.csproj b/Blarg.GameFramework/Blarg.GameFramework.csproj index 0af8688..3f6e104 100644 --- a/Blarg.GameFramework/Blarg.GameFramework.csproj +++ b/Blarg.GameFramework/Blarg.GameFramework.csproj @@ -82,6 +82,9 @@ + + + diff --git a/Blarg.GameFramework/IGameApp.cs b/Blarg.GameFramework/IGameApp.cs index 8073c26..d9da921 100644 --- a/Blarg.GameFramework/IGameApp.cs +++ b/Blarg.GameFramework/IGameApp.cs @@ -3,13 +3,12 @@ using Blarg.GameFramework.Graphics; namespace Blarg.GameFramework { - public interface IGameApp + public interface IGameApp : IDisposable { void OnAppGainFocus(); void OnAppLostFocus(); void OnAppPause(); void OnAppResume(); - bool OnInit(); void OnLoad(); void OnUnload(); void OnLostContext(); diff --git a/Blarg.GameFramework/ILooper.cs b/Blarg.GameFramework/ILooper.cs index f794b61..507eb9c 100644 --- a/Blarg.GameFramework/ILooper.cs +++ b/Blarg.GameFramework/ILooper.cs @@ -4,7 +4,14 @@ namespace Blarg.GameFramework { public interface ILooper : IDisposable { - void Run(IGameApp gameApp); + int FPS { get; } + float FrameTime { get; } + int RendersPerSecond { get; } + int UpdatesPerSecond { get; } + int RenderTime { get; } + int UpdateTime { get; } + + void Run(IGameApp gameApp, IPlatformConfiguration config); } } diff --git a/Blarg.GameFramework/IPlatformConfiguration.cs b/Blarg.GameFramework/IPlatformConfiguration.cs new file mode 100644 index 0000000..53e513c --- /dev/null +++ b/Blarg.GameFramework/IPlatformConfiguration.cs @@ -0,0 +1,9 @@ +using System; + +namespace Blarg.GameFramework +{ + public interface IPlatformConfiguration + { + } +} + diff --git a/Blarg.GameFramework/IPlatformWindow.cs b/Blarg.GameFramework/IPlatformWindow.cs new file mode 100644 index 0000000..7b7982f --- /dev/null +++ b/Blarg.GameFramework/IPlatformWindow.cs @@ -0,0 +1,16 @@ +using System; + +namespace Blarg.GameFramework +{ + public interface IPlatformWindow + { + Rect ClientRectangle { get; } + int ClientWidth { get; } + int ClientHeight { get; } + int X { get; } + int Y { get; } + int Width { get; } + int Height { get; } + } +} + diff --git a/Blarg.GameFramework/Platform.cs b/Blarg.GameFramework/Platform.cs index 37112dc..d7505fb 100644 --- a/Blarg.GameFramework/Platform.cs +++ b/Blarg.GameFramework/Platform.cs @@ -17,9 +17,21 @@ namespace Blarg.GameFramework Desktop } - public static partial class Platform + public static class Platform { - public static IPlatformServices Services { get; private set; } + static IPlatformServices _services; + + public static IPlatformServices Services + { + get { return _services; } + set + { + if (_services != null) + throw new InvalidOperationException(); + else + _services = value; + } + } } } diff --git a/Game.Core/Game.Core.csproj b/Game.Core/Game.Core.csproj index 8a45ad8..bc2a2f9 100644 --- a/Game.Core/Game.Core.csproj +++ b/Game.Core/Game.Core.csproj @@ -35,9 +35,13 @@ + + ..\Libs\PortableGL.dll + + diff --git a/Game.Core/GameApp.cs b/Game.Core/GameApp.cs new file mode 100644 index 0000000..3337205 --- /dev/null +++ b/Game.Core/GameApp.cs @@ -0,0 +1,65 @@ +using System; +using PortableGL; +using Blarg.GameFramework; +using Blarg.GameFramework.Graphics; + +namespace Game +{ + public class GameApp : IGameApp + { + public GameApp() + { + } + + public void OnAppGainFocus() + { + } + + public void OnAppLostFocus() + { + } + + public void OnAppPause() + { + } + + public void OnAppResume() + { + } + + public void OnLoad() + { + } + + public void OnUnload() + { + } + + public void OnLostContext() + { + } + + public void OnNewContext() + { + } + + public void OnRender(float delta) + { + Platform.Services.GL.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT); + Platform.Services.GL.glClearColor(0.25f, 0.5f, 1.0f, 1.0f); + } + + public void OnResize(ScreenOrientation orientation, Rect size) + { + } + + public void OnUpdate(float delta) + { + } + + public void Dispose() + { + } + } +} + diff --git a/Game.SDL2/Game.SDL2.csproj b/Game.SDL2/Game.SDL2.csproj index 5e8d065..cada48b 100644 --- a/Game.SDL2/Game.SDL2.csproj +++ b/Game.SDL2/Game.SDL2.csproj @@ -6,7 +6,7 @@ 10.0.0 2.0 {4C73802F-6EB0-490C-BB32-64718B0FEEBA} - Exe + WinExe Game Game.SDL2 @@ -18,7 +18,7 @@ DEBUG; prompt 4 - true + false full diff --git a/Game.SDL2/Program.cs b/Game.SDL2/Program.cs index 329ce39..5cbc3ef 100644 --- a/Game.SDL2/Program.cs +++ b/Game.SDL2/Program.cs @@ -1,12 +1,18 @@ using System; +using Blarg.GameFramework; namespace Game.SDL2 { - class MainClass + public static class MainClass { public static void Main(string[] args) { - Console.WriteLine("Hello World!"); + var game = new GameApp(); + var config = new SDLConfiguration(); + config.Title = "Test Game"; + + var looper = new SDLLooper(); + looper.Run(game, config); } } }