Merged default into minor

--HG--
branch : minor
This commit is contained in:
Alex Szpakowski
2014-05-21 21:16:37 -03:00
35 changed files with 485 additions and 237 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ public:
*/
enum DistanceModel
{
DISTANCE_NONE = 1,
DISTANCE_NONE,
DISTANCE_INVERSE,
DISTANCE_INVERSE_CLAMPED,
DISTANCE_LINEAR,
+2 -2
View File
@@ -36,14 +36,14 @@ public:
enum Type
{
TYPE_STATIC = 1,
TYPE_STATIC,
TYPE_STREAM,
TYPE_MAX_ENUM
}; // Type
enum Unit
{
UNIT_SECONDS = 1,
UNIT_SECONDS,
UNIT_SAMPLES,
UNIT_MAX_ENUM
};
+37 -12
View File
@@ -130,6 +130,11 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
// (No error on fail, it means that the path doesn't exist).
PHYSFS_addToSearchPath(save_path_full.c_str(), appendToPath);
// HACK: This forces setupWriteDirectory to be called the next time a file
// is opened for writing - otherwise it won't be called at all if it was
// already called at least once before.
PHYSFS_setWriteDir(nullptr);
return true;
}
@@ -171,16 +176,36 @@ bool Filesystem::setupWriteDirectory()
if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
return false;
// Set the appdata folder as writable directory.
// We need to make sure the write directory is created. To do that, we also
// need to make sure all its parent directories are also created.
std::string temp_writedir = getDriveRoot(save_path_full);
std::string temp_createdir = skipDriveRoot(save_path_full);
// On some sandboxed platforms, physfs will break when its write directory
// is the root of the drive and it tries to create a folder (even if the
// folder's path is in a writable location.) If the user's home folder is
// in the save path, we'll try starting from there instead.
if (save_path_full.find(getUserDirectory()) == 0)
{
temp_writedir = getUserDirectory();
temp_createdir = save_path_full.substr(getUserDirectory().length());
// Strip leading '/' characters from the path we want to create.
size_t startpos = temp_createdir.find_first_not_of('/');
if (startpos != std::string::npos)
temp_createdir = temp_createdir.substr(startpos);
}
// Set either '/' or the user's home as a writable directory.
// (We must create the save folder before mounting it).
if (!PHYSFS_setWriteDir(getDriveRoot(save_path_full).c_str()))
if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
return false;
// Create the save folder. (We're now "at" %APPDATA%).
if (!createDirectory(skipDriveRoot(save_path_full).c_str()))
// Create the save folder. (We're now "at" either '/' or the user's home).
if (!createDirectory(temp_createdir.c_str()))
{
// Clear the write directory in case of error.
PHYSFS_setWriteDir(0);
PHYSFS_setWriteDir(nullptr);
return false;
}
@@ -191,7 +216,7 @@ bool Filesystem::setupWriteDirectory()
// Add the directory. (Will not be readded if already present).
if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0))
{
PHYSFS_setWriteDir(0); // Clear the write directory in case of error.
PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
return false;
}
@@ -327,12 +352,12 @@ const char *Filesystem::getWorkingDirectory()
return cwd.c_str();
}
const char *Filesystem::getUserDirectory()
std::string Filesystem::getUserDirectory()
{
return PHYSFS_getUserDir();
return std::string(PHYSFS_getUserDir());
}
const char *Filesystem::getAppdataDirectory()
std::string Filesystem::getAppdataDirectory()
{
#ifdef LOVE_WINDOWS
if (appdata.empty())
@@ -341,7 +366,7 @@ const char *Filesystem::getAppdataDirectory()
appdata = to_utf8(w_appdata);
replace_char(appdata, '\\', '/');
}
return appdata.c_str();
return appdata;
#elif defined(LOVE_MACOSX)
if (appdata.empty())
{
@@ -349,7 +374,7 @@ const char *Filesystem::getAppdataDirectory()
udir.append("/Library/Application Support");
appdata = udir;
}
return appdata.c_str();
return appdata;
#elif defined(LOVE_LINUX)
if (appdata.empty())
{
@@ -359,7 +384,7 @@ const char *Filesystem::getAppdataDirectory()
else
appdata = xdgdatahome;
}
return appdata.c_str();
return appdata;
#else
return getUserDirectory();
#endif
+2 -2
View File
@@ -145,14 +145,14 @@ public:
/**
* Gets the user home directory.
**/
const char *getUserDirectory();
std::string getUserDirectory();
/**
* Gets the APPDATA directory. On Windows, this is the folder
* in the %APPDATA% enviroment variable. On Linux, this is the
* user home folder.
**/
const char *getAppdataDirectory();
std::string getAppdataDirectory();
/**
* Gets the full path of the save folder.
@@ -211,13 +211,13 @@ int w_getWorkingDirectory(lua_State *L)
int w_getUserDirectory(lua_State *L)
{
lua_pushstring(L, instance->getUserDirectory());
luax_pushstring(L, instance->getUserDirectory());
return 1;
}
int w_getAppdataDirectory(lua_State *L)
{
lua_pushstring(L, instance->getAppdataDirectory());
luax_pushstring(L, instance->getAppdataDirectory());
return 1;
}
+7 -7
View File
@@ -36,14 +36,14 @@ public:
enum DrawMode
{
DRAW_LINE = 1,
DRAW_LINE,
DRAW_FILL,
DRAW_MAX_ENUM
};
enum AlignMode
{
ALIGN_LEFT = 1,
ALIGN_LEFT,
ALIGN_CENTER,
ALIGN_RIGHT,
ALIGN_JUSTIFY,
@@ -52,7 +52,7 @@ public:
enum BlendMode
{
BLEND_ALPHA = 1,
BLEND_ALPHA,
BLEND_ADD,
BLEND_SUBTRACT,
BLEND_MULTIPLY,
@@ -64,14 +64,14 @@ public:
enum LineStyle
{
LINE_ROUGH = 1,
LINE_ROUGH,
LINE_SMOOTH,
LINE_MAX_ENUM
};
enum LineJoin
{
LINE_JOIN_NONE = 1,
LINE_JOIN_NONE,
LINE_JOIN_MITER,
LINE_JOIN_BEVEL,
LINE_JOIN_MAX_ENUM
@@ -79,7 +79,7 @@ public:
enum Support
{
SUPPORT_HDR_CANVAS = 1,
SUPPORT_HDR_CANVAS,
SUPPORT_MULTI_CANVAS,
SUPPORT_DXT,
SUPPORT_BC5,
@@ -90,7 +90,7 @@ public:
enum RendererInfo
{
RENDERER_INFO_NAME = 1,
RENDERER_INFO_NAME,
RENDERER_INFO_VERSION,
RENDERER_INFO_VENDOR,
RENDERER_INFO_DEVICE,
-20
View File
@@ -109,16 +109,6 @@ bool Texture::getConstant(WrapMode in, const char *&out)
return wrapModes.find(in, out);
}
bool Texture::getConstant(const char *in, Format &out)
{
return formats.find(in, out);
}
bool Texture::getConstant(Format in, const char *&out)
{
return formats.find(in, out);
}
StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
{
{ "linear", Texture::FILTER_LINEAR },
@@ -135,15 +125,5 @@ StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEnt
StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM>::Entry Texture::formatEntries[] =
{
{"normal", Texture::FORMAT_NORMAL},
{"hdr", Texture::FORMAT_HDR},
{"srgb", Texture::FORMAT_SRGB},
};
StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM> Texture::formats(Texture::formatEntries, sizeof(Texture::formatEntries));
} // graphics
} // love
-14
View File
@@ -55,14 +55,6 @@ public:
FILTER_MAX_ENUM
};
enum Format
{
FORMAT_NORMAL,
FORMAT_HDR,
FORMAT_SRGB,
FORMAT_MAX_ENUM
};
struct Filter
{
Filter();
@@ -119,9 +111,6 @@ public:
static bool getConstant(const char *in, WrapMode &out);
static bool getConstant(WrapMode in, const char *&out);
static bool getConstant(const char *in, Format &out);
static bool getConstant(Format in, const char *&out);
protected:
int width;
@@ -143,9 +132,6 @@ private:
static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
static StringMap<Format, FORMAT_MAX_ENUM> formats;
}; // Texture
} // graphics
+177 -37
View File
@@ -417,7 +417,7 @@ static void getStrategy()
}
}
Canvas::Canvas(int width, int height, Texture::Format format, int fsaa)
Canvas::Canvas(int width, int height, Format format, int fsaa)
: fbo(0)
, resolve_fbo(0)
, texture(0)
@@ -525,33 +525,21 @@ bool Canvas::loadVolatile()
setFilter(filter);
setWrap(wrap);
GLint internalformat;
GLenum textype;
switch (format)
{
case Texture::FORMAT_HDR:
internalformat = GL_RGBA16F;
textype = GL_FLOAT;
break;
case Texture::FORMAT_SRGB:
internalformat = GL_SRGB8_ALPHA8;
textype = GL_UNSIGNED_BYTE;
break;
case Texture::FORMAT_NORMAL:
default:
internalformat = GL_RGBA8;
textype = GL_UNSIGNED_BYTE;
}
GLenum internalformat = GL_RGBA;
GLenum externalformat = GL_RGBA;
GLenum textype = GL_UNSIGNED_BYTE;
convertFormat(format, internalformat, externalformat, textype);
while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */;
glTexImage2D(GL_TEXTURE_2D,
0,
internalformat,
(GLint) internalformat,
width, height,
0,
GL_RGBA,
externalformat,
textype,
nullptr);
@@ -975,41 +963,193 @@ bool Canvas::resolveMSAA()
return true;
}
Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
{
switch (format)
{
case FORMAT_NORMAL:
return FORMAT_RGBA8;
case FORMAT_HDR:
return FORMAT_RGBA16F;
default:
return format;
}
}
void Canvas::convertFormat(Canvas::Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type)
{
format = getSizedFormat(format);
externalformat = GL_RGBA;
switch (format)
{
case FORMAT_RGBA8:
default:
internalformat = GL_RGBA8;
type = GL_UNSIGNED_BYTE;
break;
case FORMAT_RGBA4:
internalformat = GL_RGBA4;
type = GL_UNSIGNED_SHORT_4_4_4_4;
break;
case FORMAT_RGB5A1:
internalformat = GL_RGB5_A1;
type = GL_UNSIGNED_SHORT_5_5_5_1;
break;
case FORMAT_RGB565:
internalformat = GL_RGB565;
externalformat = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5;
break;
case FORMAT_RGB10A2:
internalformat = GL_RGB10_A2;
type = GL_UNSIGNED_INT_10_10_10_2;
break;
case FORMAT_RG11B10F:
internalformat = GL_R11F_G11F_B10F;
externalformat = GL_RGB;
type = GL_UNSIGNED_INT_10F_11F_11F_REV;
break;
case FORMAT_RGBA16F:
internalformat = GL_RGBA16F;
type = GL_FLOAT;
break;
case FORMAT_RGBA32F:
internalformat = GL_RGBA32F;
type = GL_FLOAT;
break;
case FORMAT_SRGB:
internalformat = GL_SRGB8_ALPHA8;
type = GL_UNSIGNED_BYTE;
break;
}
}
bool Canvas::isSupported()
{
getStrategy();
return (strategy != &strategyNone);
}
bool Canvas::isHDRSupported()
{
return GLEE_VERSION_3_0 || (isSupported() && GLEE_ARB_texture_float);
}
bool Canvas::isSRGBSupported()
{
if (GLEE_VERSION_3_0)
return true;
if (!isSupported())
return false;
return (GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
&& GLEE_EXT_texture_sRGB;
}
bool Canvas::isMultiCanvasSupported()
{
// system must support at least 4 simultanious active canvases.
return gl.getMaxRenderTargets() >= 4;
}
bool Canvas::supportedFormats[] = {false};
bool Canvas::checkedFormats[] = {false};
bool Canvas::isFormatSupported(Canvas::Format format)
{
if (!isSupported())
return false;
bool supported = true;
format = getSizedFormat(format);
switch (format)
{
case FORMAT_RGBA8:
case FORMAT_RGBA4:
case FORMAT_RGB5A1:
case FORMAT_RGB10A2:
supported = true;
break;
case FORMAT_RGB565:
supported = GLEE_VERSION_4_2 || GLEE_ARB_ES2_compatibility;
break;
case FORMAT_RG11B10F:
supported = GLEE_VERSION_3_0 || GLEE_EXT_packed_float;
break;
case FORMAT_RGBA16F:
case FORMAT_RGBA32F:
supported = GLEE_VERSION_3_0 || GLEE_ARB_texture_float;
break;
case FORMAT_SRGB:
supported = GLEE_VERSION_3_0 || ((GLEE_ARB_framebuffer_sRGB || GLEE_EXT_framebuffer_sRGB)
&& (GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB));
break;
default:
supported = false;
break;
}
if (!supported)
return false;
if (checkedFormats[format])
return supportedFormats[format];
// Even though we might have the necessary OpenGL version or extension,
// drivers are still allowed to throw FRAMEBUFFER_UNSUPPORTED when attaching
// a texture to a FBO whose format the driver doesn't like. So we should
// test with an actual FBO.
GLenum internalformat = GL_RGBA;
GLenum externalformat = GL_RGBA;
GLenum textype = GL_UNSIGNED_BYTE;
convertFormat(format, internalformat, externalformat, textype);
GLuint texture = 0;
glGenTextures(1, &texture);
gl.bindTexture(texture);
Texture::Filter f;
f.min = f.mag = Texture::FILTER_NEAREST;
gl.setTextureFilter(f);
Texture::Wrap w;
gl.setTextureWrap(w);
glTexImage2D(GL_TEXTURE_2D, 0, internalformat, 2, 2, 0, externalformat, textype, nullptr);
GLuint fbo = 0;
supported = (strategy->createFBO(fbo, texture) == GL_FRAMEBUFFER_COMPLETE);
strategy->deleteFBO(fbo, 0, 0);
gl.deleteTexture(texture);
// Cache the result so we don't do this for every isFormatSupported call.
checkedFormats[format] = true;
supportedFormats[format] = supported;
return supported;
}
void Canvas::bindDefaultCanvas()
{
if (current != nullptr)
current->stopGrab();
}
bool Canvas::getConstant(const char *in, Format &out)
{
return formats.find(in, out);
}
bool Canvas::getConstant(Format in, const char *&out)
{
return formats.find(in, out);
}
StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM>::Entry Canvas::formatEntries[] =
{
{"normal", Canvas::FORMAT_NORMAL},
{"hdr", Canvas::FORMAT_HDR},
{"rgba8", Canvas::FORMAT_RGBA8},
{"rgba4", Canvas::FORMAT_RGBA4},
{"rgb5a1", Canvas::FORMAT_RGB5A1},
{"rgb565", Canvas::FORMAT_RGB565},
{"rgb10a2", Canvas::FORMAT_RGB10A2},
{"rg11b10f", Canvas::FORMAT_RG11B10F},
{"rgba16f", Canvas::FORMAT_RGBA16F},
{"rgba32f", Canvas::FORMAT_RGBA32F},
{"srgb", Canvas::FORMAT_SRGB},
};
StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM> Canvas::formats(Canvas::formatEntries, sizeof(Canvas::formatEntries));
} // opengl
} // graphics
} // love
+33 -4
View File
@@ -25,6 +25,7 @@
#include "image/Image.h"
#include "image/ImageData.h"
#include "common/Matrix.h"
#include "common/StringMap.h"
#include "Texture.h"
#include "OpenGL.h"
@@ -39,7 +40,24 @@ class Canvas : public Texture
{
public:
Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
// Different Canvas render target formats.
enum Format
{
FORMAT_NORMAL, // Usually RGBA8 or a similar fallback. Always supported.
FORMAT_HDR, // Usually RGBA16F. Not always supported.
FORMAT_RGBA8, // RGBA with 8 bits per component.
FORMAT_RGBA4, // RGBA with 4 bits per component.
FORMAT_RGB5A1, // RGB with 5 bits per component, and A with 1 bit.
FORMAT_RGB565, // RGB with 5, 6, and 5 bits each, respectively.
FORMAT_RGB10A2, // RGB with 10 bits each, and A with 2 bits.
FORMAT_RG11B10F, // Floating point [0, +inf]. RG with 11 FP bits each, and B with 10 FP bits.
FORMAT_RGBA16F, // Floating point [-inf, +inf]. RGBA with 16 FP bits per component.
FORMAT_RGBA32F, // Floating point [-inf, +inf]. RGBA with 32 FP bits per component.
FORMAT_SRGB, // sRGB with 8 bits per component, plus 8 bit linear A.
FORMAT_MAX_ENUM
};
Canvas(int width, int height, Format format = FORMAT_NORMAL, int fsaa = 0);
virtual ~Canvas();
// Implements Volatile.
@@ -85,7 +103,7 @@ public:
return status;
}
inline Texture::Format getTextureFormat() const
inline Format getTextureFormat() const
{
return format;
}
@@ -98,9 +116,8 @@ public:
bool resolveMSAA();
static bool isSupported();
static bool isHDRSupported();
static bool isSRGBSupported();
static bool isMultiCanvasSupported();
static bool isFormatSupported(Format format);
static Canvas *current;
static void bindDefaultCanvas();
@@ -111,10 +128,16 @@ public:
// Whether the main screen should have linear -> sRGB conversions enabled.
static bool screenHasSRGB;
static bool getConstant(const char *in, Format &out);
static bool getConstant(Format in, const char *&out);
private:
bool createFSAAFBO(GLenum internalformat);
static Format getSizedFormat(Format format);
static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type);
GLuint fbo;
GLuint resolve_fbo;
@@ -134,6 +157,12 @@ private:
void setupGrab();
void drawv(const Matrix &t, const Vertex *v);
static bool supportedFormats[FORMAT_MAX_ENUM];
static bool checkedFormats[FORMAT_MAX_ENUM];
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
static StringMap<Format, FORMAT_MAX_ENUM> formats;
}; // Canvas
} // opengl
+1 -1
View File
@@ -142,7 +142,7 @@ private:
enum FontType
{
FONT_TRUETYPE = 1,
FONT_TRUETYPE,
FONT_IMAGE,
FONT_UNKNOWN
};
+11 -10
View File
@@ -428,7 +428,7 @@ void Graphics::discardStencil()
activeStencil = false;
}
Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
Image *Graphics::newImage(love::image::ImageData *data, Image::Format format)
{
// Create the image.
Image *image = new Image(data, format);
@@ -455,7 +455,7 @@ Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
return image;
}
Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format)
Image *Graphics::newImage(love::image::CompressedData *cdata, Image::Format format)
{
// Create the image.
Image *image = new Image(cdata, format);
@@ -502,13 +502,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
return new ParticleSystem(texture, size);
}
Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa)
Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int fsaa)
{
if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported())
throw Exception("HDR Canvases are not supported by your OpenGL implementation");
if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported())
throw Exception("sRGB Canvases are not supported by your OpenGL implementation");
if (!Canvas::isFormatSupported(format))
{
const char *fstr = "rgba8";
Canvas::getConstant(format, fstr);
throw love::Exception("The %s canvas format is not supported by your OpenGL implementation.", fstr);
}
if (width > gl.getMaxTextureSize())
throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
@@ -559,8 +560,8 @@ Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int f
}
canvas->release();
throw Exception(error_string.str().c_str());
return NULL; // never reached
throw Exception("%s", error_string.str().c_str());
return nullptr; // never reached
}
Shader *Graphics::newShader(const Shader::ShaderSources &sources)
+3 -3
View File
@@ -192,8 +192,8 @@ public:
/**
* Creates an Image object with padding and/or optimization.
**/
Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
Image *newImage(love::image::ImageData *data, Image::Format format = Image::FORMAT_NORMAL);
Image *newImage(love::image::CompressedData *cdata, Image::Format format = Image::FORMAT_NORMAL);
Quad *newQuad(Quad::Viewport v, float sw, float sh);
@@ -206,7 +206,7 @@ public:
ParticleSystem *newParticleSystem(Texture *texture, int size);
Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
Canvas *newCanvas(int width, int height, Canvas::Format format = Canvas::FORMAT_NORMAL, int fsaa = 0);
Shader *newShader(const Shader::ShaderSources &sources);
+21 -3
View File
@@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f;
Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
float Image::defaultMipmapSharpness = 0.0f;
Image::Image(love::image::ImageData *data, Texture::Format format)
Image::Image(love::image::ImageData *data, Format format)
: data(data)
, cdata(nullptr)
, texture(0)
@@ -53,7 +53,7 @@ Image::Image(love::image::ImageData *data, Texture::Format format)
preload();
}
Image::Image(love::image::CompressedData *cdata, Texture::Format format)
Image::Image(love::image::CompressedData *cdata, Format format)
: data(nullptr)
, cdata(cdata)
, texture(0)
@@ -420,7 +420,7 @@ bool Image::refresh()
return true;
}
Texture::Format Image::getFormat() const
Image::Format Image::getFormat() const
{
return format;
}
@@ -556,6 +556,24 @@ bool Image::hasSRGBSupport()
return GLEE_VERSION_2_1 || GLEE_EXT_texture_sRGB;
}
bool Image::getConstant(const char *in, Format &out)
{
return formats.find(in, out);
}
bool Image::getConstant(Format in, const char *&out)
{
return formats.find(in, out);
}
StringMap<Image::Format, Image::FORMAT_MAX_ENUM>::Entry Image::formatEntries[] =
{
{"normal", Image::FORMAT_NORMAL},
{"srgb", Image::FORMAT_SRGB},
};
StringMap<Image::Format, Image::FORMAT_MAX_ENUM> Image::formats(Image::formatEntries, sizeof(Image::formatEntries));
} // opengl
} // graphics
} // love
+19 -5
View File
@@ -25,6 +25,7 @@
#include "common/config.h"
#include "common/Matrix.h"
#include "common/Vector.h"
#include "common/StringMap.h"
#include "common/math.h"
#include "image/ImageData.h"
#include "image/CompressedData.h"
@@ -50,20 +51,27 @@ class Image : public Texture
{
public:
enum Format
{
FORMAT_NORMAL,
FORMAT_SRGB,
FORMAT_MAX_ENUM
};
/**
* Creates a new Image. Not that anything is ready to use
* before load is called.
*
* @param data The data from which to load the image.
**/
Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
Image(love::image::ImageData *data, Format format = FORMAT_NORMAL);
/**
* Creates a new Image with compressed image data.
*
* @param cdata The compressed data from which to load the image.
**/
Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
Image(love::image::CompressedData *cdata, Format format = FORMAT_NORMAL);
/**
* Destructor. Deletes the hardware texture and other resources.
@@ -120,7 +128,7 @@ public:
**/
bool refresh();
Texture::Format getFormat() const;
Format getFormat() const;
static void setDefaultMipmapSharpness(float sharpness);
static float getDefaultMipmapSharpness();
@@ -131,6 +139,9 @@ public:
static bool hasCompressedTextureSupport(image::CompressedData::Format format);
static bool hasSRGBSupport();
static bool getConstant(const char *in, Format &out);
static bool getConstant(Format in, const char *&out);
private:
void uploadDefaultTexture();
@@ -157,8 +168,8 @@ private:
// Whether this Image is using a compressed texture.
bool compressed;
// The format to interpret the texture's data as.
Texture::Format format;
// The format to interpret the image's data as.
Format format;
// True if the image wasn't able to be properly created and it had to fall
// back to a default texture.
@@ -179,6 +190,9 @@ private:
GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
static StringMap<Format, FORMAT_MAX_ENUM> formats;
}; // Image
} // opengl
+2 -3
View File
@@ -98,10 +98,9 @@ Shader::~Shader()
detach();
for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it)
{
it->second->release();
boundRetainables.erase(it);
}
boundRetainables.clear();
unloadVolatile();
}
+1 -1
View File
@@ -52,7 +52,7 @@ public:
enum UsageHint
{
USAGE_DYNAMIC = 1,
USAGE_DYNAMIC,
USAGE_STATIC,
USAGE_STREAM,
USAGE_MAX_ENUM
+12 -4
View File
@@ -115,10 +115,18 @@ void VertexBuffer::unmap()
is_bound = true;
}
// "orphan" current buffer to avoid implicit synchronisation on the GPU:
// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage());
glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
if (getUsage() == GL_STATIC_DRAW)
{
// Upload the mapped data to the buffer.
glBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map);
}
else
{
// "orphan" current buffer to avoid implicit synchronisation on the GPU:
// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage());
glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
}
is_mapped = false;
}
+3 -3
View File
@@ -113,10 +113,10 @@ int w_Canvas_clear(lua_State *L)
int w_Canvas_getFormat(lua_State *L)
{
Canvas *canvas = luax_checkcanvas(L, 1);
Texture::Format format = canvas->getTextureFormat();
Canvas::Format format = canvas->getTextureFormat();
const char *str;
if (!Texture::getConstant(format, str))
return luaL_error(L, "Unknown texture format.");
if (!Canvas::getConstant(format, str))
return luaL_error(L, "Unknown Canvas format.");
lua_pushstring(L, str);
return 1;
+21 -11
View File
@@ -149,14 +149,11 @@ int w_newImage(lua_State *L)
love::image::ImageData *data = nullptr;
love::image::CompressedData *cdata = nullptr;
Texture::Format format = Texture::FORMAT_NORMAL;
Image::Format format = Image::FORMAT_NORMAL;
const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
if (fstr != nullptr && !Texture::getConstant(fstr, format))
return luaL_error(L, "Invalid texture format: %s", fstr);
if (format == Texture::FORMAT_HDR) // For now...
return luaL_error(L, "HDR images are not supported.");
if (fstr != nullptr && !Image::getConstant(fstr, format))
return luaL_error(L, "Invalid Image format: %s", fstr);
// Convert to FileData, if necessary.
if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
@@ -328,9 +325,9 @@ int w_newCanvas(lua_State *L)
const char *str = luaL_optstring(L, 3, "normal");
int fsaa = luaL_optint(L, 4, 0);
Texture::Format format;
if (!Texture::getConstant(str, format))
return luaL_error(L, "Invalid texture format: %s", str);
Canvas::Format format;
if (!Canvas::getConstant(str, format))
return luaL_error(L, "Invalid Canvas format: %s", str);
Canvas *canvas = nullptr;
EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
@@ -944,7 +941,7 @@ int w_isSupported(lua_State *L)
switch (support)
{
case Graphics::SUPPORT_HDR_CANVAS:
if (!Canvas::isHDRSupported())
if (!Canvas::isFormatSupported(Canvas::FORMAT_HDR))
supported = false;
break;
case Graphics::SUPPORT_MULTI_CANVAS:
@@ -964,7 +961,7 @@ int w_isSupported(lua_State *L)
supported = false;
break;
case Graphics::SUPPORT_SRGB:
if (!Canvas::isSRGBSupported())
if (!Canvas::isFormatSupported(Canvas::FORMAT_SRGB))
supported = false;
break;
default:
@@ -977,6 +974,18 @@ int w_isSupported(lua_State *L)
return 1;
}
int w_hasCanvasFormat(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
Canvas::Format format;
if (!Canvas::getConstant(str, format))
return luaL_error(L, "Invalid canvas format: %s", str);
luax_pushboolean(L, Canvas::isFormatSupported(format));
return 1;
}
int w_getRendererInfo(lua_State *L)
{
std::string name, version, vendor, device;
@@ -1351,6 +1360,7 @@ static const luaL_Reg functions[] =
{ "getShader", w_getShader },
{ "isSupported", w_isSupported },
{ "hasCanvasFormat", w_hasCanvasFormat },
{ "getRendererInfo", w_getRendererInfo },
{ "getSystemLimit", w_getSystemLimit },
@@ -89,6 +89,7 @@ int w_getCanvas(lua_State *L);
int w_setShader(lua_State *L);
int w_getShader(lua_State *L);
int w_isSupported(lua_State *L);
int w_hasCanvasFormat(lua_State *L);
int w_getRendererInfo(lua_State *L);
int w_getSystemLimit(lua_State *L);
int w_draw(lua_State *L);
+1 -1
View File
@@ -49,7 +49,7 @@ public:
enum Format
{
FORMAT_TGA = 1,
FORMAT_TGA,
FORMAT_BMP,
FORMAT_JPG,
FORMAT_PNG,
+2 -2
View File
@@ -157,9 +157,9 @@ public:
virtual int getID() const = 0;
virtual bool isVibrationSupported() = 0;
virtual bool setVibration(float left, float right) = 0;
virtual bool setVibration(float left, float right, float duration = -1.0f) = 0;
virtual bool setVibration() = 0;
virtual void getVibration(float &left, float &right) const = 0;
virtual void getVibration(float &left, float &right) = 0;
static bool getConstant(const char *in, Hat &out);
static bool getConstant(Hat in, const char *&out);
+78 -42
View File
@@ -25,6 +25,11 @@
// C++
#include <algorithm>
#include <limits>
#ifndef SDL_TICKS_PASSED
#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
#endif
namespace love
{
@@ -34,9 +39,9 @@ namespace sdl
{
Joystick::Joystick(int id)
: joyhandle(0)
, controller(0)
, haptic(0)
: joyhandle(nullptr)
, controller(nullptr)
, haptic(nullptr)
, instanceid(-1)
, id(id)
, vibration()
@@ -44,9 +49,9 @@ Joystick::Joystick(int id)
}
Joystick::Joystick(int id, int joyindex)
: joyhandle(0)
, controller(0)
, haptic(0)
: joyhandle(nullptr)
, controller(nullptr)
, haptic(nullptr)
, instanceid(-1)
, id(id)
, vibration()
@@ -95,10 +100,7 @@ bool Joystick::open(int deviceindex)
void Joystick::close()
{
if (haptic)
{
SDL_HapticRumbleStop(haptic);
SDL_HapticClose(haptic);
}
if (controller)
SDL_GameControllerClose(controller);
@@ -106,16 +108,16 @@ void Joystick::close()
if (joyhandle)
SDL_JoystickClose(joyhandle);
joyhandle = 0;
controller = 0;
haptic = 0;
joyhandle = nullptr;
controller = nullptr;
haptic = nullptr;
instanceid = -1;
vibration = Vibration();
}
bool Joystick::isConnected() const
{
return joyhandle != 0 && SDL_JoystickGetAttached(joyhandle);
return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle);
}
const char *Joystick::getName() const
@@ -209,7 +211,7 @@ bool Joystick::openGamepad(int deviceindex)
if (isGamepad())
{
SDL_GameControllerClose(controller);
controller = 0;
controller = nullptr;
}
controller = SDL_GameControllerOpen(deviceindex);
@@ -218,7 +220,7 @@ bool Joystick::openGamepad(int deviceindex)
bool Joystick::isGamepad() const
{
return controller != 0;
return controller != nullptr;
}
float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
@@ -289,13 +291,13 @@ bool Joystick::checkCreateHaptic()
if (haptic)
{
SDL_HapticClose(haptic);
haptic = 0;
haptic = nullptr;
}
haptic = SDL_HapticOpenFromJoystick(joyhandle);
vibration = Vibration();
return haptic != 0;
return haptic != nullptr;
}
bool Joystick::isVibrationSupported()
@@ -312,8 +314,8 @@ bool Joystick::isVibrationSupported()
if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
return true;
// Check SDL's simple rumble as a last resort.
if (SDL_HapticRumbleSupported(haptic) == 1)
// Test for simple sine wave support as a last resort.
if ((features & SDL_HAPTIC_SINE) != 0)
return true;
return false;
@@ -325,7 +327,7 @@ bool Joystick::runVibrationEffect()
{
if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
{
if (SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
if (SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
return true;
}
@@ -336,17 +338,14 @@ bool Joystick::runVibrationEffect()
vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
return true;
return false;
}
bool Joystick::setVibration(float left, float right)
bool Joystick::setVibration(float left, float right, float duration)
{
// TODO: support non-infinite durations? The working Tattiebogle Xbox
// controller driver in OS X seems to ignore durations under 1 second.
left = std::min(std::max(left, 0.0f), 1.0f);
right = std::min(std::max(right, 0.0f), 1.0f);
@@ -356,24 +355,32 @@ bool Joystick::setVibration(float left, float right)
if (!checkCreateHaptic())
return false;
Uint32 length = SDL_HAPTIC_INFINITY;
if (duration >= 0.0f)
{
float maxduration = std::numeric_limits<Uint32>::max() / 1000.0f;
length = Uint32(std::min(duration, maxduration) * 1000);
}
bool success = false;
unsigned int features = SDL_HapticQuery(haptic);
int axes = SDL_HapticNumAxes(haptic);
if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
{
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
vibration.effect.leftright.length = SDL_HAPTIC_INFINITY;
vibration.effect.leftright.length = length;
vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
success = runVibrationEffect();
}
// Some gamepad drivers only give support for controlling the motors via
// a custom FF effect.
if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
// Some gamepad drivers only give support for controlling individual motors
// through a custom FF effect.
if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2)
{
// NOTE: this may cause issues with drivers which support custom effects
// but aren't similar to https://github.com/d235j/360Controller .
@@ -385,7 +392,7 @@ bool Joystick::setVibration(float left, float right)
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_CUSTOM;
vibration.effect.custom.length = SDL_HAPTIC_INFINITY;
vibration.effect.custom.length = length;
vibration.effect.custom.channels = 2;
vibration.effect.custom.period = 10;
vibration.effect.custom.samples = 2;
@@ -394,19 +401,36 @@ bool Joystick::setVibration(float left, float right)
success = runVibrationEffect();
}
// Fall back to a simple rumble if all else fails. SDL's simple rumble API
// only supports a single strength value.
if (!success && SDL_HapticRumbleInit(haptic) == 0)
// Fall back to a simple sine wave if all else fails. This only supports a
// single strength value.
if (!success && (features & SDL_HAPTIC_SINE) != 0)
{
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_SINE;
vibration.effect.periodic.length = length;
vibration.effect.periodic.period = 10;
float strength = std::max(left, right);
int played = SDL_HapticRumblePlay(haptic, strength, SDL_HAPTIC_INFINITY);
success = (played == 0);
vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF);
success = runVibrationEffect();
}
if (success)
{
vibration.left = left;
vibration.right = right;
if (length == SDL_HAPTIC_INFINITY)
vibration.endtime = SDL_HAPTIC_INFINITY;
else
vibration.endtime = SDL_GetTicks() + length;
}
else
{
vibration.left = vibration.right = 0.0f;
vibration.endtime = SDL_HAPTIC_INFINITY;
}
return success;
@@ -417,12 +441,7 @@ bool Joystick::setVibration()
bool success = true;
if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
{
// Stop all playing effects on the haptic device.
// FIXME: We should only stop the vibration effect, in case we use the
// Haptic API for other things in the future.
success = (SDL_HapticStopAll(haptic) == 0);
}
success = (SDL_HapticStopEffect(haptic, vibration.id) == 0);
if (success)
vibration.left = vibration.right = 0.0f;
@@ -430,8 +449,25 @@ bool Joystick::setVibration()
return success;
}
void Joystick::getVibration(float &left, float &right) const
void Joystick::getVibration(float &left, float &right)
{
if (vibration.endtime != SDL_HAPTIC_INFINITY)
{
// With some drivers, the effect physically stops at the right time, but
// SDL_HapticGetEffectStatus still thinks it's playing. So we explicitly
// stop it once it's done, just to be sure.
if (SDL_TICKS_PASSED(SDL_GetTicks(), vibration.endtime))
{
setVibration();
vibration.endtime = SDL_HAPTIC_INFINITY;
}
}
// Check if the haptic effect has stopped playing.
int id = vibration.id;
if (!haptic || id == -1 || SDL_HapticGetEffectStatus(haptic, id) != 1)
vibration.left = vibration.right = 0.0f;
left = vibration.left;
right = vibration.right;
}
+7 -3
View File
@@ -74,9 +74,9 @@ public:
int getID() const;
bool isVibrationSupported();
bool setVibration(float left, float right);
bool setVibration(float left, float right, float duration = -1.0f);
bool setVibration();
void getVibration(float &left, float &right) const;
void getVibration(float &left, float &right);
static bool getConstant(Hat in, Uint8 &out);
static bool getConstant(Uint8 in, Hat &out);
@@ -111,8 +111,12 @@ private:
Uint16 data[4];
int id;
Uint32 endtime;
Vibration()
: left(0.0f), right(0.0f), effect(), data(), id(-1)
: left(0.0f), right(0.0f)
, effect(), data(), id(-1)
, endtime(SDL_HAPTIC_INFINITY)
{}
} vibration;
+7 -7
View File
@@ -77,7 +77,7 @@ const char *JoystickModule::getName() const
love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
{
if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
return 0;
return nullptr;
return activeSticks[joyindex];
}
@@ -107,13 +107,13 @@ love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
return activeSticks[i];
}
return 0;
return nullptr;
}
love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
{
if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
return 0;
return nullptr;
std::string guidstr = getDeviceGUID(deviceindex);
joystick::Joystick *joystick = 0;
@@ -140,7 +140,7 @@ love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
removeJoystick(joystick);
if (!joystick->open(deviceindex))
return 0;
return nullptr;
// Make sure multiple instances of the same physical joystick aren't added
// to the active list.
@@ -262,7 +262,7 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
if (status == 1)
checkGamepads(guid);
return status >= 0;
return status >= 0;
}
Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput)
@@ -318,7 +318,7 @@ std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinpu
SDL_GameControllerAxis sdlaxis;
SDL_GameControllerButton sdlbutton;
const char *gpinputname = 0;
const char *gpinputname = nullptr;
switch (gpinput.type)
{
@@ -435,7 +435,7 @@ void JoystickModule::checkGamepads(const std::string &guid) const
// Big hack time: open the index as a game controller and compare
// the underlying joystick handle to the active stick's.
SDL_GameController *ctrl = SDL_GameControllerOpen(d_index);
if (ctrl == NULL)
if (ctrl == nullptr)
continue;
SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl);
+2 -1
View File
@@ -207,7 +207,8 @@ int w_Joystick_setVibration(lua_State *L)
{
float left = (float) luaL_checknumber(L, 2);
float right = (float) luaL_optnumber(L, 3, left);
success = j->setVibration(left, right);
float duration = (float) luaL_optnumber(L, 4, -1.0); // -1 is infinite.
success = j->setVibration(left, right, duration);
}
luax_pushboolean(L, success);
@@ -28,7 +28,7 @@ namespace joystick
namespace sdl
{
static JoystickModule *instance = 0;
static JoystickModule *instance = nullptr;
int w_getJoysticks(lua_State *L)
{
@@ -194,7 +194,7 @@ static const lua_CFunction types[] =
extern "C" int luaopen_love_joystick(lua_State *L)
{
if (instance == 0)
if (instance == nullptr)
{
EXCEPT_GUARD(instance = new JoystickModule();)
}
+2 -2
View File
@@ -117,7 +117,7 @@ int Mpg123Decoder::decode()
size += numbytes;
long rate = 0;
int encoding = 0;
int ret = mpg123_getformat(handle, &rate, &channels, &encoding);
mpg123_getformat(handle, &rate, &channels, &encoding);
if (rate == 0)
rate = sampleRate;
else
@@ -126,7 +126,7 @@ int Mpg123Decoder::decode()
channels = MPG123_STEREO;
if (encoding == 0)
encoding = MPG123_ENC_SIGNED_16;
ret = mpg123_format(handle, rate, channels, encoding);
int ret = mpg123_format(handle, rate, channels, encoding);
if (ret != MPG123_OK)
throw love::Exception("Could not set output format.");
}
+22 -28
View File
@@ -25,8 +25,9 @@
#if defined(LOVE_MACOSX)
#include <CoreServices/CoreServices.h>
#elif defined(LOVE_LINUX)
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
//#include <stdlib.h>
//#include <unistd.h>
#include <sys/wait.h>
#elif defined(LOVE_WINDOWS)
#include "common/utf8.h"
@@ -53,12 +54,17 @@ std::string System::getOS() const
#endif
}
extern "C"
{
extern char **environ; // The environment, always available
}
bool System::openURL(const std::string &url) const
{
bool success = false;
#if defined(LOVE_MACOSX)
bool success = false;
// We could be lazy and use system("open " + url), but this is safer.
CFURLRef cfurl = CFURLCreateWithBytes(nullptr,
(const UInt8 *) url.c_str(),
@@ -68,34 +74,24 @@ bool System::openURL(const std::string &url) const
success = LSOpenCFURLRef(cfurl, nullptr) == noErr;
CFRelease(cfurl);
return success;
#elif defined(LOVE_LINUX)
// Spawn a child process, which we'll replace with xdg-open.
pid_t pid = vfork();
pid_t pid;
const char *argv[] = {"xdg-open", url.c_str(), nullptr};
if (pid == 0) // Child process.
{
// Replace the child process with xdg-open and pass in the URL.
execlp("xdg-open", "xdg-open", url.c_str(), nullptr);
// Note: at the moment this process inherits our file descriptors.
// Note: the below const_cast is really ugly as well.
if (posix_spawnp(&pid, "xdg-open", nullptr, nullptr, const_cast<char **>(argv), environ) != 0)
return false;
// exec will only return if it errored, so we should exit with non-zero.
_exit(1);
}
else if (pid > 0) // Parent process.
{
// Wait for xdg-open to complete (or fail.)
int status = 0;
if (waitpid(pid, &status, 0) == pid)
success = (status == 0);
else
success = false;
}
// Wait for xdg-open to complete (or fail.)
int status = 0;
if (waitpid(pid, &status, 0) == pid)
return (status == 0);
else
{
// vfork() failed.
success = false;
}
return false;
#elif defined(LOVE_WINDOWS)
@@ -109,11 +105,9 @@ bool System::openURL(const std::string &url) const
nullptr,
SW_SHOW);
success = (int) result > 32;
return (int) result > 32;
#endif
return success;
}
bool System::getConstant(const char *in, System::PowerState &out)
+1
View File
@@ -304,6 +304,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
// FSAA.
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0);