untie the engine from SDL2 and MiniTK

This commit is contained in:
Matthias Mailänder
2015-06-28 18:54:54 +02:00
parent f325463204
commit 17f3466451
27 changed files with 196 additions and 146 deletions

View File

@@ -0,0 +1,32 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Drawing;
using System.Reflection;
using OpenRA;
[assembly: Platform(typeof(OpenRA.Platforms.Default.DeviceFactory))]
namespace OpenRA.Platforms.Default
{
public class DeviceFactory : IDeviceFactory
{
public IGraphicsDevice CreateGraphics(Size size, WindowMode windowMode)
{
return new Sdl2GraphicsDevice(size, windowMode);
}
public ISoundEngine CreateSound()
{
return new OpenAlSoundEngine();
}
}
}

View File

@@ -0,0 +1,72 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Diagnostics;
using OpenTK.Graphics.OpenGL;
namespace OpenRA.Platforms.Default
{
public static class ErrorHandler
{
public static void CheckGlVersion()
{
var versionString = GL.GetString(StringName.Version);
var version = versionString.Contains(" ") ? versionString.Split(' ')[0].Split('.') : versionString.Split('.');
var major = 0;
if (version.Length > 0)
int.TryParse(version[0], out major);
var minor = 0;
if (version.Length > 1)
int.TryParse(version[1], out minor);
Console.WriteLine("Detected OpenGL version: {0}.{1}".F(major, minor));
if (major < 2)
{
WriteGraphicsLog("OpenRA requires OpenGL version 2.0 or greater and detected {0}.{1}".F(major, minor));
throw new InvalidProgramException("OpenGL Version Error: See graphics.log for details.");
}
}
public static void CheckGlError()
{
var n = GL.GetError();
if (n != ErrorCode.NoError)
{
var error = "GL Error: {0}\n{1}".F(n, new StackTrace());
WriteGraphicsLog(error);
throw new InvalidOperationException("OpenGL Error: See graphics.log for details.");
}
}
public static void WriteGraphicsLog(string message)
{
Log.Write("graphics", message);
Log.Write("graphics", "");
Log.Write("graphics", "OpenGL Information:");
Log.Write("graphics", "Vendor: {0}", GL.GetString(StringName.Vendor));
if (GL.GetString(StringName.Vendor).Contains("Microsoft"))
{
var msg = "";
msg += "Note: The default driver provided by Microsoft does not include full OpenGL support.\n";
msg += "Please install the latest drivers from your graphics card manufacturer's website.\n";
Log.Write("graphics", msg);
}
Log.Write("graphics", "Renderer: {0}", GL.GetString(StringName.Renderer));
Log.Write("graphics", "GL Version: {0}", GL.GetString(StringName.Version));
Log.Write("graphics", "Shader Version: {0}", GL.GetString(StringName.ShadingLanguageVersion));
Log.Write("graphics", "Available extensions:");
Log.Write("graphics", GL.GetString(StringName.Extensions));
}
}
}

View File

@@ -0,0 +1,137 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using OpenTK.Graphics.OpenGL;
namespace OpenRA.Platforms.Default
{
public sealed class FrameBuffer : IFrameBuffer
{
Texture texture;
Size size;
int framebuffer, depth;
bool disposed;
public FrameBuffer(Size size)
{
this.size = size;
if (!Exts.IsPowerOf2(size.Width) || !Exts.IsPowerOf2(size.Height))
throw new InvalidDataException("Frame buffer size ({0}x{1}) must be a power of two".F(size.Width, size.Height));
GL.Ext.GenFramebuffers(1, out framebuffer);
ErrorHandler.CheckGlError();
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, framebuffer);
ErrorHandler.CheckGlError();
// Color
texture = new Texture();
texture.SetEmpty(size.Width, size.Height);
GL.Ext.FramebufferTexture2D(FramebufferTarget.FramebufferExt, FramebufferAttachment.ColorAttachment0Ext, TextureTarget.Texture2D, texture.ID, 0);
ErrorHandler.CheckGlError();
// Depth
GL.Ext.GenRenderbuffers(1, out depth);
ErrorHandler.CheckGlError();
GL.Ext.BindRenderbuffer(RenderbufferTarget.RenderbufferExt, depth);
ErrorHandler.CheckGlError();
GL.Ext.RenderbufferStorage(RenderbufferTarget.RenderbufferExt, RenderbufferStorage.DepthComponent, size.Width, size.Height);
ErrorHandler.CheckGlError();
GL.Ext.FramebufferRenderbuffer(FramebufferTarget.FramebufferExt, FramebufferAttachment.DepthAttachmentExt, RenderbufferTarget.RenderbufferExt, depth);
ErrorHandler.CheckGlError();
// Test for completeness
var status = GL.Ext.CheckFramebufferStatus(FramebufferTarget.FramebufferExt);
if (status != FramebufferErrorCode.FramebufferCompleteExt)
{
var error = "Error creating framebuffer: {0}\n{1}".F(status, new StackTrace());
ErrorHandler.WriteGraphicsLog(error);
throw new InvalidOperationException("OpenGL Error: See graphics.log for details.");
}
// Restore default buffer
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
ErrorHandler.CheckGlError();
}
static int[] ViewportRectangle()
{
var v = new int[4];
unsafe
{
fixed (int* ptr = &v[0])
GL.GetInteger(GetPName.Viewport, ptr);
}
ErrorHandler.CheckGlError();
return v;
}
int[] cv = new int[4];
public void Bind()
{
// Cache viewport rect to restore when unbinding
cv = ViewportRectangle();
GL.Flush();
ErrorHandler.CheckGlError();
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, framebuffer);
ErrorHandler.CheckGlError();
GL.Viewport(0, 0, size.Width, size.Height);
ErrorHandler.CheckGlError();
GL.ClearColor(0, 0, 0, 0);
ErrorHandler.CheckGlError();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
ErrorHandler.CheckGlError();
}
public void Unbind()
{
GL.Flush();
ErrorHandler.CheckGlError();
GL.Ext.BindFramebuffer(FramebufferTarget.FramebufferExt, 0);
ErrorHandler.CheckGlError();
GL.Viewport(cv[0], cv[1], cv[2], cv[3]);
ErrorHandler.CheckGlError();
}
public ITexture Texture { get { return texture; } }
~FrameBuffer()
{
Game.RunAfterTick(() => Dispose(false));
}
public void Dispose()
{
Game.RunAfterTick(() => Dispose(true));
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposed)
return;
disposed = true;
if (disposing)
texture.Dispose();
GL.Ext.DeleteFramebuffers(1, ref framebuffer);
ErrorHandler.CheckGlError();
GL.Ext.DeleteRenderbuffers(1, ref depth);
ErrorHandler.CheckGlError();
}
}
}

View File

@@ -0,0 +1,83 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using OpenRA.Primitives;
namespace OpenRA.Platforms.Default
{
public static class MultiTapDetection
{
static Cache<Keycode, TapHistory> keyHistoryCache =
new Cache<Keycode, TapHistory>(_ => new TapHistory(DateTime.Now - TimeSpan.FromSeconds(1)));
static Cache<byte, TapHistory> clickHistoryCache =
new Cache<byte, TapHistory>(_ => new TapHistory(DateTime.Now - TimeSpan.FromSeconds(1)));
public static int DetectFromMouse(byte button, int2 xy)
{
return clickHistoryCache[button].GetTapCount(xy);
}
public static int InfoFromMouse(byte button)
{
return clickHistoryCache[button].LastTapCount();
}
public static int DetectFromKeyboard(Keycode key)
{
return keyHistoryCache[key].GetTapCount(int2.Zero);
}
public static int InfoFromKeyboard(Keycode key)
{
return keyHistoryCache[key].LastTapCount();
}
}
class TapHistory
{
public Pair<DateTime, int2> FirstRelease, SecondRelease, ThirdRelease;
public TapHistory(DateTime now)
{
FirstRelease = SecondRelease = ThirdRelease = Pair.New(now, int2.Zero);
}
static bool CloseEnough(Pair<DateTime, int2> a, Pair<DateTime, int2> b)
{
return a.First - b.First < TimeSpan.FromMilliseconds(250)
&& (a.Second - b.Second).Length < 4;
}
public int GetTapCount(int2 xy)
{
FirstRelease = SecondRelease;
SecondRelease = ThirdRelease;
ThirdRelease = Pair.New(DateTime.Now, xy);
if (!CloseEnough(ThirdRelease, SecondRelease))
return 1;
if (!CloseEnough(SecondRelease, FirstRelease))
return 2;
return 3;
}
public int LastTapCount()
{
if (!CloseEnough(ThirdRelease, SecondRelease))
return 1;
if (!CloseEnough(SecondRelease, FirstRelease))
return 2;
return 3;
}
}
}

View File

@@ -0,0 +1,369 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.FileSystem;
using OpenRA.GameRules;
using OpenRA.Primitives;
using OpenRA.Traits;
using OpenTK;
using OpenTK.Audio.OpenAL;
namespace OpenRA.Platforms.Default
{
class OpenAlSoundEngine : ISoundEngine
{
public SoundDevice[] AvailableDevices()
{
var defaultDevices = new[]
{
new SoundDevice("Default", null, "Default Output"),
new SoundDevice("Null", null, "Output Disabled")
};
var physicalDevices = PhysicalDevices().Select(d => new SoundDevice("Default", d, d));
return defaultDevices.Concat(physicalDevices).ToArray();
}
class PoolSlot
{
public bool IsActive;
public int FrameStarted;
public WPos Pos;
public bool IsRelative;
public ISoundSource Sound;
}
const int MaxInstancesPerFrame = 3;
const int GroupDistance = 2730;
const int GroupDistanceSqr = GroupDistance * GroupDistance;
const int PoolSize = 32;
float volume = 1f;
Dictionary<int, PoolSlot> sourcePool = new Dictionary<int, PoolSlot>();
static string[] QueryDevices(string label, AlcGetStringList type)
{
// Clear error bit
AL.GetError();
var devices = Alc.GetString(IntPtr.Zero, type).ToArray();
if (AL.GetError() != ALError.NoError)
{
Log.Write("sound", "Failed to query OpenAL device list using {0}", label);
return new string[] { };
}
return devices;
}
static string[] PhysicalDevices()
{
// Returns all devices under Windows Vista and newer
if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATE_ALL_EXT"))
return QueryDevices("ALC_ENUMERATE_ALL_EXT", AlcGetStringList.AllDevicesSpecifier);
if (Alc.IsExtensionPresent(IntPtr.Zero, "ALC_ENUMERATION_EXT"))
return QueryDevices("ALC_ENUMERATION_EXT", AlcGetStringList.DeviceSpecifier);
return new string[] { };
}
public OpenAlSoundEngine()
{
Console.WriteLine("Using OpenAL sound engine");
if (Game.Settings.Sound.Device != null)
Console.WriteLine("Using device `{0}`", Game.Settings.Sound.Device);
else
Console.WriteLine("Using default device");
var dev = Alc.OpenDevice(Game.Settings.Sound.Device);
if (dev == IntPtr.Zero)
{
Console.WriteLine("Failed to open device. Falling back to default");
dev = Alc.OpenDevice(null);
if (dev == IntPtr.Zero)
throw new InvalidOperationException("Can't create OpenAL device");
}
var ctx = Alc.CreateContext(dev, (int[])null);
if (ctx == ContextHandle.Zero)
throw new InvalidOperationException("Can't create OpenAL context");
Alc.MakeContextCurrent(ctx);
for (var i = 0; i < PoolSize; i++)
{
var source = 0;
AL.GenSources(1, out source);
if (0 != AL.GetError())
{
Log.Write("sound", "Failed generating OpenAL source {0}", i);
return;
}
sourcePool.Add(source, new PoolSlot() { IsActive = false });
}
}
int GetSourceFromPool()
{
foreach (var kvp in sourcePool)
{
if (!kvp.Value.IsActive)
{
sourcePool[kvp.Key].IsActive = true;
return kvp.Key;
}
}
var freeSources = new List<int>();
foreach (var key in sourcePool.Keys)
{
int state;
AL.GetSource(key, ALGetSourcei.SourceState, out state);
if (state != (int)ALSourceState.Playing && state != (int)ALSourceState.Paused)
freeSources.Add(key);
}
if (freeSources.Count == 0)
return -1;
foreach (var i in freeSources)
sourcePool[i].IsActive = false;
sourcePool[freeSources[0]].IsActive = true;
return freeSources[0];
}
public ISoundSource AddSoundSourceFromMemory(byte[] data, int channels, int sampleBits, int sampleRate)
{
return new OpenAlSoundSource(data, channels, sampleBits, sampleRate);
}
public ISound Play2D(ISoundSource sound, bool loop, bool relative, WPos pos, float volume, bool attenuateVolume)
{
if (sound == null)
{
Log.Write("sound", "Attempt to Play2D a null `ISoundSource`");
return null;
}
var currFrame = Game.LocalTick;
var atten = 1f;
// Check if max # of instances-per-location reached:
if (attenuateVolume)
{
int instances = 0, activeCount = 0;
foreach (var s in sourcePool.Values)
{
if (!s.IsActive)
continue;
if (s.IsRelative != relative)
continue;
++activeCount;
if (s.Sound != sound)
continue;
if (currFrame - s.FrameStarted >= 5)
continue;
// Too far away to count?
var lensqr = (s.Pos - pos).LengthSquared;
if (lensqr >= GroupDistanceSqr)
continue;
// If we are starting too many instances of the same sound within a short time then stop this one:
if (++instances == MaxInstancesPerFrame)
return null;
}
// Attenuate a little bit based on number of active sounds:
atten = 0.66f * ((PoolSize - activeCount * 0.5f) / PoolSize);
}
var source = GetSourceFromPool();
if (source == -1)
return null;
var slot = sourcePool[source];
slot.Pos = pos;
slot.FrameStarted = currFrame;
slot.Sound = sound;
slot.IsRelative = relative;
return new OpenAlSound(source, ((OpenAlSoundSource)sound).Buffer, loop, relative, pos, volume * atten);
}
public float Volume
{
get { return volume; }
set { AL.Listener(ALListenerf.Gain, volume = value); }
}
public void PauseSound(ISound sound, bool paused)
{
if (sound == null)
return;
var key = ((OpenAlSound)sound).Source;
int state;
AL.GetSource(key, ALGetSourcei.SourceState, out state);
if (state == (int)ALSourceState.Playing && paused)
AL.SourcePause(key);
else if (state == (int)ALSourceState.Paused && !paused)
AL.SourcePlay(key);
}
public void SetAllSoundsPaused(bool paused)
{
foreach (var key in sourcePool.Keys)
{
int state;
AL.GetSource(key, ALGetSourcei.SourceState, out state);
if (state == (int)ALSourceState.Playing && paused)
AL.SourcePause(key);
else if (state == (int)ALSourceState.Paused && !paused)
AL.SourcePlay(key);
}
}
public void SetSoundVolume(float volume, ISound music, ISound video)
{
var sounds = sourcePool.Select(s => s.Key).Where(b =>
{
int state;
AL.GetSource(b, ALGetSourcei.SourceState, out state);
return (state == (int)ALSourceState.Playing || state == (int)ALSourceState.Paused) &&
(music == null || b != ((OpenAlSound)music).Source) &&
(video == null || b != ((OpenAlSound)video).Source);
});
foreach (var s in sounds)
AL.Source(s, ALSourcef.Gain, volume);
}
public void StopSound(ISound sound)
{
if (sound == null)
return;
var key = ((OpenAlSound)sound).Source;
int state;
AL.GetSource(key, ALGetSourcei.SourceState, out state);
if (state == (int)ALSourceState.Playing || state == (int)ALSourceState.Paused)
AL.SourceStop(key);
}
public void StopAllSounds()
{
foreach (var key in sourcePool.Keys)
{
int state;
AL.GetSource(key, ALGetSourcei.SourceState, out state);
if (state == (int)ALSourceState.Playing || state == (int)ALSourceState.Paused)
AL.SourceStop(key);
}
}
public void SetListenerPosition(WPos position)
{
// Move the listener out of the plane so that sounds near the middle of the screen aren't too positional
AL.Listener(ALListener3f.Position, position.X, position.Y, position.Z + 2133);
var orientation = new[] { 0f, 0f, 1f, 0f, -1f, 0f };
AL.Listener(ALListenerfv.Orientation, ref orientation);
AL.Listener(ALListenerf.EfxMetersPerUnit, .01f);
}
}
class OpenAlSoundSource : ISoundSource
{
public readonly int Buffer;
static ALFormat MakeALFormat(int channels, int bits)
{
if (channels == 1)
return bits == 16 ? ALFormat.Mono16 : ALFormat.Mono8;
else
return bits == 16 ? ALFormat.Stereo16 : ALFormat.Stereo8;
}
public OpenAlSoundSource(byte[] data, int channels, int sampleBits, int sampleRate)
{
AL.GenBuffers(1, out Buffer);
AL.BufferData(Buffer, MakeALFormat(channels, sampleBits), data, data.Length, sampleRate);
}
}
class OpenAlSound : ISound
{
public readonly int Source = -1;
float volume = 1f;
public OpenAlSound(int source, int buffer, bool looping, bool relative, WPos pos, float volume)
{
if (source == -1)
return;
Source = source;
Volume = volume;
AL.Source(source, ALSourcef.Pitch, 1f);
AL.Source(source, ALSource3f.Position, pos.X, pos.Y, pos.Z);
AL.Source(source, ALSource3f.Velocity, 0f, 0f, 0f);
AL.Source(source, ALSourcei.Buffer, buffer);
AL.Source(source, ALSourceb.Looping, looping);
AL.Source(source, ALSourceb.SourceRelative, relative);
AL.Source(source, ALSourcef.ReferenceDistance, 6826);
AL.Source(source, ALSourcef.MaxDistance, 136533);
AL.SourcePlay(source);
}
public float Volume
{
get
{
return volume;
}
set
{
if (Source != -1)
AL.Source(Source, ALSourcef.Gain, volume = value);
}
}
public float SeekPosition
{
get
{
int pos;
AL.GetSource(Source, ALGetSourcei.SampleOffset, out pos);
return pos / 22050f;
}
}
public bool Playing
{
get
{
int state;
AL.GetSource(Source, ALGetSourcei.SourceState, out state);
return state == (int)ALSourceState.Playing;
}
}
}
}

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{33D03738-C154-4028-8EA8-63A3C488A651}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>OpenRA.Platforms.Default</RootNamespace>
<AssemblyName>OpenRA.Platforms.Default</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<OutputPath>..\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="Eluant">
<HintPath>..\thirdparty\download\Eluant.dll</HintPath>
</Reference>
<Reference Include="SDL2-CS">
<HintPath>..\thirdparty\download\SDL2-CS.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DefaultPlatform.cs" />
<Compile Include="Sdl2GraphicsDevice.cs" />
<Compile Include="Sdl2Input.cs" />
<Compile Include="Shader.cs" />
<Compile Include="ErrorHandler.cs" />
<Compile Include="FrameBuffer.cs" />
<Compile Include="MultiTapDetection.cs" />
<Compile Include="Texture.cs" />
<Compile Include="VertexBuffer.cs" />
<Compile Include="OpenAlSoundEngine.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
<Project>{0DFB103F-2962-400F-8C6D-E2C28CCBA633}</Project>
<Name>OpenRA.Game</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,360 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using OpenRA;
using OpenRA.Graphics;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using SDL2;
namespace OpenRA.Platforms.Default
{
public sealed class Sdl2GraphicsDevice : IGraphicsDevice
{
Size size;
Sdl2Input input;
IntPtr context, window;
bool disposed;
public Size WindowSize { get { return size; } }
public Sdl2GraphicsDevice(Size windowSize, WindowMode windowMode)
{
Console.WriteLine("Using SDL 2 with OpenGL renderer");
size = windowSize;
SDL.SDL_Init(SDL.SDL_INIT_NOPARACHUTE | SDL.SDL_INIT_VIDEO);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8);
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 0);
SDL.SDL_DisplayMode display;
SDL.SDL_GetCurrentDisplayMode(0, out display);
Console.WriteLine("Desktop resolution: {0}x{1}", display.w, display.h);
if (size.Width == 0 && size.Height == 0)
{
Console.WriteLine("No custom resolution provided, using desktop resolution");
size = new Size(display.w, display.h);
}
Console.WriteLine("Using resolution: {0}x{1}", size.Width, size.Height);
window = SDL.SDL_CreateWindow("OpenRA", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, size.Width, size.Height, SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL);
if (Game.Settings.Game.LockMouseWindow)
GrabWindowMouseFocus();
else
ReleaseWindowMouseFocus();
if (windowMode == WindowMode.Fullscreen)
SDL.SDL_SetWindowFullscreen(window, (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN);
else if (windowMode == WindowMode.PseudoFullscreen)
{
// Work around a visual glitch in OSX: the window is offset
// partially offscreen if the dock is at the left of the screen
if (Platform.CurrentPlatform == PlatformType.OSX)
SDL.SDL_SetWindowPosition(window, 0, 0);
SDL.SDL_SetWindowFullscreen(window, (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
}
context = SDL.SDL_GL_CreateContext(window);
SDL.SDL_GL_MakeCurrent(window, context);
GL.LoadAll();
ErrorHandler.CheckGlVersion();
ErrorHandler.CheckGlError();
if (SDL.SDL_GL_ExtensionSupported("GL_EXT_framebuffer_object") == SDL.SDL_bool.SDL_FALSE)
{
ErrorHandler.WriteGraphicsLog("OpenRA requires the OpenGL extension GL_EXT_framebuffer_object.\n"
+ "Please try updating your GPU driver to the latest version provided by the manufacturer.");
throw new InvalidProgramException("Missing OpenGL extension GL_EXT_framebuffer_object. See graphics.log for details.");
}
GL.EnableClientState(ArrayCap.VertexArray);
ErrorHandler.CheckGlError();
GL.EnableClientState(ArrayCap.TextureCoordArray);
ErrorHandler.CheckGlError();
SDL.SDL_SetModState(0);
input = new Sdl2Input();
}
public IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot)
{
try
{
return new SDL2HardwareCursor(size, data, hotspot);
}
catch (Exception ex)
{
throw new InvalidDataException("Failed to create hardware cursor `{0}`".F(name), ex);
}
}
public void SetHardwareCursor(IHardwareCursor cursor)
{
var c = cursor as SDL2HardwareCursor;
if (c == null)
SDL.SDL_ShowCursor(0);
else
{
SDL.SDL_ShowCursor(1);
SDL.SDL_SetCursor(c.Cursor);
}
}
sealed class SDL2HardwareCursor : IHardwareCursor
{
public IntPtr Cursor { get; private set; }
IntPtr surface;
public SDL2HardwareCursor(Size size, byte[] data, int2 hotspot)
{
try
{
surface = SDL.SDL_CreateRGBSurface(0, size.Width, size.Height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
if (surface == IntPtr.Zero)
throw new InvalidDataException("Failed to create surface: {0}".F(SDL.SDL_GetError()));
var sur = (SDL2.SDL.SDL_Surface)Marshal.PtrToStructure(surface, typeof(SDL2.SDL.SDL_Surface));
Marshal.Copy(data, 0, sur.pixels, data.Length);
// This call very occasionally fails on Windows, but often works when retried.
for (var retries = 0; retries < 3 && Cursor == IntPtr.Zero; retries++)
Cursor = SDL.SDL_CreateColorCursor(surface, hotspot.X, hotspot.Y);
if (Cursor == IntPtr.Zero)
throw new InvalidDataException("Failed to create cursor: {0}".F(SDL.SDL_GetError()));
}
catch
{
Dispose();
throw;
}
}
~SDL2HardwareCursor()
{
Game.RunAfterTick(() => Dispose(false));
}
public void Dispose()
{
Game.RunAfterTick(() => Dispose(true));
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (Cursor != IntPtr.Zero)
{
SDL.SDL_FreeCursor(Cursor);
Cursor = IntPtr.Zero;
}
if (surface != IntPtr.Zero)
{
SDL.SDL_FreeSurface(surface);
surface = IntPtr.Zero;
}
}
}
public void Dispose()
{
if (disposed)
return;
disposed = true;
if (context != IntPtr.Zero)
{
SDL.SDL_GL_DeleteContext(context);
context = IntPtr.Zero;
}
if (window != IntPtr.Zero)
{
SDL.SDL_DestroyWindow(window);
window = IntPtr.Zero;
}
SDL.SDL_Quit();
}
static BeginMode ModeFromPrimitiveType(PrimitiveType pt)
{
switch (pt)
{
case PrimitiveType.PointList: return BeginMode.Points;
case PrimitiveType.LineList: return BeginMode.Lines;
case PrimitiveType.TriangleList: return BeginMode.Triangles;
case PrimitiveType.QuadList: return BeginMode.Quads;
}
throw new NotImplementedException();
}
public void DrawPrimitives(PrimitiveType pt, int firstVertex, int numVertices)
{
GL.DrawArrays(ModeFromPrimitiveType(pt), firstVertex, numVertices);
ErrorHandler.CheckGlError();
}
public void Clear()
{
GL.ClearColor(0, 0, 0, 1);
ErrorHandler.CheckGlError();
GL.Clear(ClearBufferMask.ColorBufferBit);
ErrorHandler.CheckGlError();
}
public void EnableDepthBuffer()
{
GL.Clear(ClearBufferMask.DepthBufferBit);
ErrorHandler.CheckGlError();
GL.Enable(EnableCap.DepthTest);
ErrorHandler.CheckGlError();
}
public void DisableDepthBuffer()
{
GL.Disable(EnableCap.DepthTest);
ErrorHandler.CheckGlError();
}
public void SetBlendMode(BlendMode mode)
{
GL.BlendEquation(BlendEquationMode.FuncAdd);
ErrorHandler.CheckGlError();
switch (mode)
{
case BlendMode.None:
GL.Disable(EnableCap.Blend);
break;
case BlendMode.Alpha:
GL.Enable(EnableCap.Blend);
ErrorHandler.CheckGlError();
GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);
break;
case BlendMode.Additive:
case BlendMode.Subtractive:
GL.Enable(EnableCap.Blend);
ErrorHandler.CheckGlError();
GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.One);
if (mode == BlendMode.Subtractive)
{
ErrorHandler.CheckGlError();
GL.BlendEquation(BlendEquationMode.FuncReverseSubtract);
}
break;
case BlendMode.Multiply:
GL.Enable(EnableCap.Blend);
ErrorHandler.CheckGlError();
GL.BlendFunc(BlendingFactorSrc.DstColor, BlendingFactorDest.OneMinusSrcAlpha);
ErrorHandler.CheckGlError();
break;
case BlendMode.Multiplicative:
GL.Enable(EnableCap.Blend);
ErrorHandler.CheckGlError();
GL.BlendFunc(BlendingFactorSrc.Zero, BlendingFactorDest.SrcColor);
break;
case BlendMode.DoubleMultiplicative:
GL.Enable(EnableCap.Blend);
ErrorHandler.CheckGlError();
GL.BlendFunc(BlendingFactorSrc.DstColor, BlendingFactorDest.SrcColor);
break;
}
ErrorHandler.CheckGlError();
}
public void GrabWindowMouseFocus()
{
SDL.SDL_SetWindowGrab(window, SDL.SDL_bool.SDL_TRUE);
}
public void ReleaseWindowMouseFocus()
{
SDL.SDL_SetWindowGrab(window, SDL.SDL_bool.SDL_FALSE);
}
public void EnableScissor(int left, int top, int width, int height)
{
if (width < 0)
width = 0;
if (height < 0)
height = 0;
GL.Scissor(left, size.Height - (top + height), width, height);
ErrorHandler.CheckGlError();
GL.Enable(EnableCap.ScissorTest);
ErrorHandler.CheckGlError();
}
public void DisableScissor()
{
GL.Disable(EnableCap.ScissorTest);
ErrorHandler.CheckGlError();
}
public void SetLineWidth(float width)
{
GL.LineWidth(width);
ErrorHandler.CheckGlError();
}
public Bitmap TakeScreenshot()
{
var rect = new Rectangle(Point.Empty, size);
var bitmap = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var data = bitmap.LockBits(rect,
System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
GL.PushClientAttrib(ClientAttribMask.ClientPixelStoreBit);
GL.PixelStore(PixelStoreParameter.PackRowLength, data.Stride / 4f);
GL.PixelStore(PixelStoreParameter.PackAlignment, 1);
GL.ReadPixels(rect.X, rect.Y, rect.Width, rect.Height, PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
GL.Finish();
GL.PopClientAttrib();
bitmap.UnlockBits(data);
// OpenGL standard defines the origin in the bottom left corner which is why this is upside-down by default.
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
return bitmap;
}
public void Present() { SDL.SDL_GL_SwapWindow(window); }
public void PumpInput(IInputHandler inputHandler) { input.PumpInput(inputHandler); }
public string GetClipboardText() { return input.GetClipboardText(); }
public IVertexBuffer<Vertex> CreateVertexBuffer(int size) { return new VertexBuffer<Vertex>(size); }
public ITexture CreateTexture() { return new Texture(); }
public ITexture CreateTexture(Bitmap bitmap) { return new Texture(bitmap); }
public IFrameBuffer CreateFrameBuffer(Size s) { return new FrameBuffer(s); }
public IShader CreateShader(string name) { return new Shader(name); }
}
}

View File

@@ -0,0 +1,180 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Runtime.InteropServices;
using System.Text;
using SDL2;
namespace OpenRA.Platforms.Default
{
public class Sdl2Input
{
MouseButton lastButtonBits = (MouseButton)0;
public string GetClipboardText() { return SDL.SDL_GetClipboardText(); }
static MouseButton MakeButton(byte b)
{
return b == SDL.SDL_BUTTON_LEFT ? MouseButton.Left
: b == SDL.SDL_BUTTON_RIGHT ? MouseButton.Right
: b == SDL.SDL_BUTTON_MIDDLE ? MouseButton.Middle
: 0;
}
static Modifiers MakeModifiers(int raw)
{
return ((raw & (int)SDL.SDL_Keymod.KMOD_ALT) != 0 ? Modifiers.Alt : 0)
| ((raw & (int)SDL.SDL_Keymod.KMOD_CTRL) != 0 ? Modifiers.Ctrl : 0)
| ((raw & (int)SDL.SDL_Keymod.KMOD_LGUI) != 0 ? Modifiers.Meta : 0)
| ((raw & (int)SDL.SDL_Keymod.KMOD_RGUI) != 0 ? Modifiers.Meta : 0)
| ((raw & (int)SDL.SDL_Keymod.KMOD_SHIFT) != 0 ? Modifiers.Shift : 0);
}
public void PumpInput(IInputHandler inputHandler)
{
var mods = MakeModifiers((int)SDL.SDL_GetModState());
var scrollDelta = 0;
inputHandler.ModifierKeys(mods);
MouseInput? pendingMotion = null;
SDL.SDL_Event e;
while (SDL.SDL_PollEvent(out e) != 0)
{
switch (e.type)
{
case SDL.SDL_EventType.SDL_QUIT:
Game.Exit();
break;
case SDL.SDL_EventType.SDL_WINDOWEVENT:
{
switch (e.window.windowEvent)
{
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST:
Game.HasInputFocus = false;
break;
case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED:
Game.HasInputFocus = true;
break;
}
break;
}
case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN:
{
if (pendingMotion != null)
{
inputHandler.OnMouseInput(pendingMotion.Value);
pendingMotion = null;
}
var button = MakeButton(e.button.button);
lastButtonBits |= button;
var pos = new int2(e.button.x, e.button.y);
inputHandler.OnMouseInput(new MouseInput(
MouseInputEvent.Down, button, scrollDelta, pos, mods,
MultiTapDetection.DetectFromMouse(e.button.button, pos)));
break;
}
case SDL.SDL_EventType.SDL_MOUSEBUTTONUP:
{
if (pendingMotion != null)
{
inputHandler.OnMouseInput(pendingMotion.Value);
pendingMotion = null;
}
var button = MakeButton(e.button.button);
lastButtonBits &= ~button;
var pos = new int2(e.button.x, e.button.y);
inputHandler.OnMouseInput(new MouseInput(
MouseInputEvent.Up, button, scrollDelta, pos, mods,
MultiTapDetection.InfoFromMouse(e.button.button)));
break;
}
case SDL.SDL_EventType.SDL_MOUSEMOTION:
{
pendingMotion = new MouseInput(
MouseInputEvent.Move, lastButtonBits, scrollDelta,
new int2(e.motion.x, e.motion.y), mods, 0);
break;
}
case SDL.SDL_EventType.SDL_MOUSEWHEEL:
{
int x, y;
SDL.SDL_GetMouseState(out x, out y);
scrollDelta = e.wheel.y;
inputHandler.OnMouseInput(new MouseInput(MouseInputEvent.Scroll, MouseButton.None, scrollDelta, new int2(x, y), Modifiers.None, 0));
break;
}
case SDL.SDL_EventType.SDL_TEXTINPUT:
{
var rawBytes = new byte[SDL.SDL_TEXTINPUTEVENT_TEXT_SIZE];
unsafe { Marshal.Copy((IntPtr)e.text.text, rawBytes, 0, SDL.SDL_TEXTINPUTEVENT_TEXT_SIZE); }
inputHandler.OnTextInput(Encoding.UTF8.GetString(rawBytes, 0, Array.IndexOf(rawBytes, (byte)0)));
break;
}
case SDL.SDL_EventType.SDL_KEYDOWN:
case SDL.SDL_EventType.SDL_KEYUP:
{
var keyCode = (Keycode)e.key.keysym.sym;
var type = e.type == SDL.SDL_EventType.SDL_KEYDOWN ?
KeyInputEvent.Down : KeyInputEvent.Up;
var tapCount = e.type == SDL.SDL_EventType.SDL_KEYDOWN ?
MultiTapDetection.DetectFromKeyboard(keyCode) :
MultiTapDetection.InfoFromKeyboard(keyCode);
var keyEvent = new KeyInput
{
Event = type,
Key = keyCode,
Modifiers = mods,
UnicodeChar = (char)e.key.keysym.sym,
MultiTapCount = tapCount
};
// Special case workaround for windows users
if (e.key.keysym.sym == SDL.SDL_Keycode.SDLK_F4 && mods.HasModifier(Modifiers.Alt) &&
Platform.CurrentPlatform == PlatformType.Windows)
Game.Exit();
else
inputHandler.OnKeyInput(keyEvent);
break;
}
}
}
if (pendingMotion != null)
{
inputHandler.OnMouseInput(pendingMotion.Value);
pendingMotion = null;
}
ErrorHandler.CheckGlError();
}
}
}

View File

@@ -0,0 +1,198 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenRA.FileSystem;
using OpenTK.Graphics.OpenGL;
namespace OpenRA.Platforms.Default
{
public class Shader : IShader
{
readonly Dictionary<string, int> samplers = new Dictionary<string, int>();
readonly Dictionary<int, ITexture> textures = new Dictionary<int, ITexture>();
int program;
protected int CompileShaderObject(ShaderType type, string name)
{
string ext = type == ShaderType.VertexShader ? "vert" : "frag";
string filename = "glsl{0}{1}.{2}".F(Path.DirectorySeparatorChar, name, ext);
string code;
using (var file = new StreamReader(GlobalFileSystem.Open(filename)))
code = file.ReadToEnd();
var shader = GL.CreateShader(type);
ErrorHandler.CheckGlError();
GL.ShaderSource(shader, code);
ErrorHandler.CheckGlError();
GL.CompileShader(shader);
ErrorHandler.CheckGlError();
int success;
GL.GetShader(shader, ShaderParameter.CompileStatus, out success);
ErrorHandler.CheckGlError();
if (success == (int)All.False)
{
int len;
GL.GetShader(shader, ShaderParameter.InfoLogLength, out len);
var log = new StringBuilder(len);
unsafe
{
GL.GetShaderInfoLog(shader, len, null, log);
}
Log.Write("graphics", "GL Info Log:\n{0}", log.ToString());
throw new InvalidProgramException("Compile error in shader object '{0}'".F(filename));
}
return shader;
}
public Shader(string name)
{
var vertexShader = CompileShaderObject(ShaderType.VertexShader, name);
var fragmentShader = CompileShaderObject(ShaderType.FragmentShader, name);
// Assemble program
program = GL.CreateProgram();
ErrorHandler.CheckGlError();
GL.AttachShader(program, vertexShader);
ErrorHandler.CheckGlError();
GL.AttachShader(program, fragmentShader);
ErrorHandler.CheckGlError();
GL.LinkProgram(program);
ErrorHandler.CheckGlError();
int success;
GL.GetProgram(program, ProgramParameter.LinkStatus, out success);
ErrorHandler.CheckGlError();
if (success == (int)All.False)
{
int len;
GL.GetProgram(program, ProgramParameter.InfoLogLength, out len);
var log = new StringBuilder(len);
unsafe
{
GL.GetProgramInfoLog(program, len, null, log);
}
Log.Write("graphics", "GL Info Log:\n{0}", log.ToString());
throw new InvalidProgramException("Link error in shader program '{0}'".F(name));
}
GL.UseProgram(program);
ErrorHandler.CheckGlError();
int numUniforms;
GL.GetProgram(program, ProgramParameter.ActiveUniforms, out numUniforms);
ErrorHandler.CheckGlError();
var nextTexUnit = 0;
for (var i = 0; i < numUniforms; i++)
{
int length, size;
ActiveUniformType type;
var sb = new StringBuilder(128);
GL.GetActiveUniform(program, i, 128, out length, out size, out type, sb);
var sampler = sb.ToString();
ErrorHandler.CheckGlError();
if (type == ActiveUniformType.Sampler2D)
{
samplers.Add(sampler, nextTexUnit);
var loc = GL.GetUniformLocation(program, sampler);
ErrorHandler.CheckGlError();
GL.Uniform1(loc, nextTexUnit);
ErrorHandler.CheckGlError();
nextTexUnit++;
}
}
}
public void Render(Action a)
{
GL.UseProgram(program);
// bind the textures
foreach (var kv in textures)
{
GL.ActiveTexture(TextureUnit.Texture0 + kv.Key);
GL.BindTexture(TextureTarget.Texture2D, ((Texture)kv.Value).ID);
}
ErrorHandler.CheckGlError();
a();
ErrorHandler.CheckGlError();
}
public void SetTexture(string name, ITexture t)
{
if (t == null)
return;
int texUnit;
if (samplers.TryGetValue(name, out texUnit))
textures[texUnit] = t;
}
public void SetVec(string name, float x)
{
GL.UseProgram(program);
ErrorHandler.CheckGlError();
var param = GL.GetUniformLocation(program, name);
ErrorHandler.CheckGlError();
GL.Uniform1(param, x);
ErrorHandler.CheckGlError();
}
public void SetVec(string name, float x, float y)
{
GL.UseProgram(program);
ErrorHandler.CheckGlError();
var param = GL.GetUniformLocation(program, name);
ErrorHandler.CheckGlError();
GL.Uniform2(param, x, y);
ErrorHandler.CheckGlError();
}
public void SetVec(string name, float[] vec, int length)
{
var param = GL.GetUniformLocation(program, name);
ErrorHandler.CheckGlError();
switch (length)
{
case 1: GL.Uniform1(param, 1, vec); break;
case 2: GL.Uniform2(param, 1, vec); break;
case 3: GL.Uniform3(param, 1, vec); break;
case 4: GL.Uniform4(param, 1, vec); break;
default: throw new InvalidDataException("Invalid vector length");
}
ErrorHandler.CheckGlError();
}
public void SetMatrix(string name, float[] mtx)
{
if (mtx.Length != 16)
throw new InvalidDataException("Invalid 4x4 matrix");
GL.UseProgram(program);
ErrorHandler.CheckGlError();
var param = GL.GetUniformLocation(program, name);
ErrorHandler.CheckGlError();
GL.UniformMatrix4(param, 1, false, mtx);
ErrorHandler.CheckGlError();
}
}
}

View File

@@ -0,0 +1,204 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using OpenTK.Graphics.OpenGL;
namespace OpenRA.Platforms.Default
{
public sealed class Texture : ITexture
{
int texture;
TextureScaleFilter scaleFilter;
Size size;
public int ID { get { return texture; } }
public Size Size { get { return size; } }
bool disposed;
public TextureScaleFilter ScaleFilter
{
get
{
return scaleFilter;
}
set
{
if (scaleFilter == value)
return;
scaleFilter = value;
PrepareTexture();
}
}
public Texture()
{
GL.GenTextures(1, out texture);
ErrorHandler.CheckGlError();
}
public Texture(Bitmap bitmap)
{
GL.GenTextures(1, out texture);
ErrorHandler.CheckGlError();
SetData(bitmap);
}
void PrepareTexture()
{
ErrorHandler.CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, texture);
ErrorHandler.CheckGlError();
var filter = scaleFilter == TextureScaleFilter.Linear ? (int)TextureMinFilter.Linear : (int)TextureMinFilter.Nearest;
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, filter);
ErrorHandler.CheckGlError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, filter);
ErrorHandler.CheckGlError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (float)TextureWrapMode.ClampToEdge);
ErrorHandler.CheckGlError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (float)TextureWrapMode.ClampToEdge);
ErrorHandler.CheckGlError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0);
ErrorHandler.CheckGlError();
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, 0);
ErrorHandler.CheckGlError();
}
public void SetData(byte[] colors, int width, int height)
{
if (!Exts.IsPowerOf2(width) || !Exts.IsPowerOf2(height))
throw new InvalidDataException("Non-power-of-two array {0}x{1}".F(width, height));
size = new Size(width, height);
unsafe
{
fixed (byte* ptr = &colors[0])
{
var intPtr = new IntPtr((void*)ptr);
PrepareTexture();
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, width, height,
0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, intPtr);
ErrorHandler.CheckGlError();
}
}
}
// An array of RGBA
public void SetData(uint[,] colors)
{
var width = colors.GetUpperBound(1) + 1;
var height = colors.GetUpperBound(0) + 1;
if (!Exts.IsPowerOf2(width) || !Exts.IsPowerOf2(height))
throw new InvalidDataException("Non-power-of-two array {0}x{1}".F(width, height));
size = new Size(width, height);
unsafe
{
fixed (uint* ptr = &colors[0, 0])
{
var intPtr = new IntPtr((void*)ptr);
PrepareTexture();
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, width, height,
0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, intPtr);
ErrorHandler.CheckGlError();
}
}
}
public void SetData(Bitmap bitmap)
{
var allocatedBitmap = false;
if (!Exts.IsPowerOf2(bitmap.Width) || !Exts.IsPowerOf2(bitmap.Height))
{
bitmap = new Bitmap(bitmap, bitmap.Size.NextPowerOf2());
allocatedBitmap = true;
}
try
{
size = new Size(bitmap.Width, bitmap.Height);
var bits = bitmap.LockBits(bitmap.Bounds(),
ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
PrepareTexture();
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, bits.Width, bits.Height,
0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bits.Scan0); // TODO: weird strides
ErrorHandler.CheckGlError();
bitmap.UnlockBits(bits);
}
finally
{
if (allocatedBitmap)
bitmap.Dispose();
}
}
public byte[] GetData()
{
var data = new byte[4 * size.Width * size.Height];
ErrorHandler.CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, texture);
unsafe
{
fixed (byte* ptr = &data[0])
{
var intPtr = new IntPtr((void*)ptr);
GL.GetTexImage(TextureTarget.Texture2D, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, intPtr);
}
}
ErrorHandler.CheckGlError();
return data;
}
public void SetEmpty(int width, int height)
{
if (!Exts.IsPowerOf2(width) || !Exts.IsPowerOf2(height))
throw new InvalidDataException("Non-power-of-two array {0}x{1}".F(width, height));
size = new Size(width, height);
PrepareTexture();
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, width, height,
0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
ErrorHandler.CheckGlError();
}
~Texture()
{
Game.RunAfterTick(() => Dispose(false));
}
public void Dispose()
{
Game.RunAfterTick(() => Dispose(true));
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposed)
return;
disposed = true;
GL.DeleteTextures(1, ref texture);
}
}
}

View File

@@ -0,0 +1,90 @@
#region Copyright & License Information
/*
* Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Runtime.InteropServices;
using OpenTK.Graphics.OpenGL;
namespace OpenRA.Platforms.Default
{
public sealed class VertexBuffer<T> : IVertexBuffer<T>
where T : struct
{
static readonly int VertexSize = Marshal.SizeOf(typeof(T));
int buffer;
bool disposed;
public VertexBuffer(int size)
{
GL.GenBuffers(1, out buffer);
ErrorHandler.CheckGlError();
Bind();
GL.BufferData(BufferTarget.ArrayBuffer,
new IntPtr(VertexSize * size),
new T[size],
BufferUsageHint.DynamicDraw);
ErrorHandler.CheckGlError();
}
public void SetData(T[] data, int length)
{
SetData(data, 0, length);
}
public void SetData(T[] data, int start, int length)
{
Bind();
GL.BufferSubData(BufferTarget.ArrayBuffer,
new IntPtr(VertexSize * start),
new IntPtr(VertexSize * length),
data);
ErrorHandler.CheckGlError();
}
public void SetData(IntPtr data, int start, int length)
{
Bind();
GL.BufferSubData(BufferTarget.ArrayBuffer,
new IntPtr(VertexSize * start),
new IntPtr(VertexSize * length),
data);
ErrorHandler.CheckGlError();
}
public void Bind()
{
GL.BindBuffer(BufferTarget.ArrayBuffer, buffer);
ErrorHandler.CheckGlError();
GL.VertexPointer(3, VertexPointerType.Float, VertexSize, IntPtr.Zero);
ErrorHandler.CheckGlError();
GL.TexCoordPointer(4, TexCoordPointerType.Float, VertexSize, new IntPtr(12));
ErrorHandler.CheckGlError();
}
~VertexBuffer()
{
Game.RunAfterTick(() => Dispose(false));
}
public void Dispose()
{
Game.RunAfterTick(() => Dispose(true));
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposed)
return;
disposed = true;
GL.DeleteBuffers(1, ref buffer);
}
}
}