using latest love-android @ changeset 8659be0e75a3

This commit is contained in:
fysx
2014-05-25 16:05:26 +02:00
parent 1d29da2d80
commit 49265f0596
58 changed files with 2008 additions and 366 deletions
+30
View File
@@ -21,6 +21,15 @@
#ifndef LOVE_INT_H #ifndef LOVE_INT_H
#define LOVE_INT_H #define LOVE_INT_H
// fixes compile errors of the form:
// "error: expected unqualified-id before '__extension__'"
#ifdef LOVE_ANDROID
#include <sys/endian.h>
#undef swap16
#undef swap32
#undef swap64
#endif
// C standard sized integer types. // C standard sized integer types.
// This header was added to Visual studio in VS 2012, which is LOVE's current // This header was added to Visual studio in VS 2012, which is LOVE's current
// minimum supported VS version (as of this comment's commit date.) // minimum supported VS version (as of this comment's commit date.)
@@ -47,6 +56,27 @@ typedef uint32_t uint32;
typedef int64_t int64; typedef int64_t int64;
typedef uint64_t uint64; typedef uint64_t uint64;
static inline uint16 swap16(uint16 x)
{
return (x >> 8) | (x << 8);
}
static inline uint32 swap32(uint32 x)
{
return ((x & 0x000000FF) << 24) |
((x & 0x0000FF00) << 8) |
((x & 0x00FF0000) >> 8) |
((x & 0xFF000000) >> 24);
}
static inline uint64 swap64(uint64 x)
{
return ((x << 56) & 0xFF00000000000000ULL) | ((x << 40) & 0x00FF000000000000ULL) |
((x << 24) & 0x0000FF0000000000ULL) | ((x << 8) & 0x000000FF00000000ULL) |
((x >> 8) & 0x00000000FF000000ULL) | ((x >> 24) & 0x0000000000FF0000ULL) |
((x >> 40) & 0x000000000000FF00ULL) | ((x >> 56) & 0x00000000000000FFULL);
}
} // love } // love
#endif // LOVE_INT_H #endif // LOVE_INT_H
@@ -37,6 +37,7 @@ Audio::PoolThread::PoolThread(Pool *pool)
, finish(false) , finish(false)
{ {
mutex = thread::newMutex(); mutex = thread::newMutex();
threadName = "AudioPool";
} }
Audio::PoolThread::~PoolThread() Audio::PoolThread::~PoolThread()
+4 -19
View File
@@ -255,10 +255,7 @@ bool Source::update()
alGetSourcef(source, AL_SAMPLE_OFFSET, &curOffsetSamples); alGetSourcef(source, AL_SAMPLE_OFFSET, &curOffsetSamples);
ALint b; int freq = decoder->getSampleRate();
alGetSourcei(source, AL_BUFFER, &b);
int freq;
alGetBufferi(b, AL_FREQUENCY, &freq);
curOffsetSecs = curOffsetSamples / freq; curOffsetSecs = curOffsetSamples / freq;
// Get a free buffer. // Get a free buffer.
@@ -334,11 +331,7 @@ void Source::seekAtomic(float offset, void *unit)
if (type == TYPE_STREAM) if (type == TYPE_STREAM)
{ {
offsetSamples = offset; offsetSamples = offset;
ALint buffer; offset /= decoder->getSampleRate();
alGetSourcei(source, AL_BUFFER, &buffer);
int freq;
alGetBufferi(buffer, AL_FREQUENCY, &freq);
offset /= freq;
offsetSeconds = offset; offsetSeconds = offset;
decoder->seek(offset); decoder->seek(offset);
} }
@@ -353,11 +346,7 @@ void Source::seekAtomic(float offset, void *unit)
{ {
offsetSeconds = offset; offsetSeconds = offset;
decoder->seek(offset); decoder->seek(offset);
ALint buffer; offsetSamples = offset * decoder->getSampleRate();
alGetSourcei(source, AL_BUFFER, &buffer);
int freq;
alGetBufferi(buffer, AL_FREQUENCY, &freq);
offsetSamples = offset*freq;
} }
else else
{ {
@@ -399,11 +388,7 @@ float Source::tellAtomic(void *unit) const
default: default:
{ {
alGetSourcef(source, AL_SAMPLE_OFFSET, &offset); alGetSourcef(source, AL_SAMPLE_OFFSET, &offset);
ALint buffer; offset /= decoder->getSampleRate();
alGetSourcei(source, AL_BUFFER, &buffer);
int freq;
alGetBufferi(buffer, AL_FREQUENCY, &freq);
offset /= freq;
if (type == TYPE_STREAM) offset += offsetSeconds; if (type == TYPE_STREAM) offset += offsetSeconds;
} }
break; break;
+32
View File
@@ -59,6 +59,34 @@ static void windowToPixelCoords(int *x, int *y)
#endif #endif
} }
// SDL's event watch callbacks trigger when the event is actually posted inside
// SDL, unlike with SDL_PollEvents. This is useful for some events which require
// handling inside the function which triggered them on some backends.
static int SDLCALL watchAppEvents(void * /*udata*/, SDL_Event *event)
{
graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
switch (event->type)
{
// On iOS, calling any OpenGL ES function after the function which triggers
// SDL_APP_DIDENTERBACKGROUND is called will kill the app, so we handle it
// with an event watch callback, which will be called inside that function.
case SDL_APP_DIDENTERBACKGROUND:
case SDL_APP_WILLENTERFOREGROUND:
// On Android, before calling setActive we should probably also call a
// window function to un-set the graphics mode and delete the context,
// after DIDENTERBACKGROUND (and the reverse after WILLENTERFOREGROUND.)
if (gfx)
gfx->setActive(event->type == SDL_APP_WILLENTERFOREGROUND);
break;
default:
break;
}
// Don't prevent the event from being processed further.
return 1;
}
const char *Event::getName() const const char *Event::getName() const
{ {
@@ -69,10 +97,13 @@ Event::Event()
{ {
if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0)
throw love::Exception("%s", SDL_GetError()); throw love::Exception("%s", SDL_GetError());
SDL_AddEventWatch(watchAppEvents, this);
} }
Event::~Event() Event::~Event()
{ {
SDL_DelEventWatch(watchAppEvents, this);
SDL_QuitSubSystem(SDL_INIT_EVENTS); SDL_QuitSubSystem(SDL_INIT_EVENTS);
} }
@@ -260,6 +291,7 @@ Message *Event::convert(const SDL_Event &e) const
SDL_free(e.drop.file); SDL_free(e.drop.file);
break; break;
case SDL_QUIT: case SDL_QUIT:
case SDL_APP_TERMINATING:
msg = new Message("quit"); msg = new Message("quit");
break; break;
case SDL_APP_LOWMEMORY: case SDL_APP_LOWMEMORY:
@@ -279,6 +279,11 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath)
// (No error on fail, it means that the path doesn't exist). // (No error on fail, it means that the path doesn't exist).
PHYSFS_addToSearchPath(save_path_full.c_str(), appendToPath); 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; return true;
} }
@@ -349,16 +354,36 @@ bool Filesystem::setupWriteDirectory()
if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
return false; 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). // (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; return false;
// Create the save folder. (We're now "at" %APPDATA%). // Create the save folder. (We're now "at" either '/' or the user's home).
if (!createDirectory(skipDriveRoot(save_path_full).c_str())) if (!createDirectory(temp_createdir.c_str()))
{ {
// Clear the write directory in case of error. // Clear the write directory in case of error.
PHYSFS_setWriteDir(0); PHYSFS_setWriteDir(nullptr);
return false; return false;
} }
@@ -369,7 +394,7 @@ bool Filesystem::setupWriteDirectory()
// Add the directory. (Will not be readded if already present). // Add the directory. (Will not be readded if already present).
if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0)) 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; return false;
} }
@@ -505,12 +530,12 @@ const char *Filesystem::getWorkingDirectory()
return cwd.c_str(); 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 #ifdef LOVE_WINDOWS
if (appdata.empty()) if (appdata.empty())
@@ -519,7 +544,7 @@ const char *Filesystem::getAppdataDirectory()
appdata = to_utf8(w_appdata); appdata = to_utf8(w_appdata);
replace_char(appdata, '\\', '/'); replace_char(appdata, '\\', '/');
} }
return appdata.c_str(); return appdata;
#elif defined(LOVE_MACOSX) #elif defined(LOVE_MACOSX)
if (appdata.empty()) if (appdata.empty())
{ {
@@ -527,7 +552,7 @@ const char *Filesystem::getAppdataDirectory()
udir.append("/Library/Application Support"); udir.append("/Library/Application Support");
appdata = udir; appdata = udir;
} }
return appdata.c_str(); return appdata;
#elif defined(LOVE_LINUX) #elif defined(LOVE_LINUX)
if (appdata.empty()) if (appdata.empty())
{ {
@@ -537,7 +562,7 @@ const char *Filesystem::getAppdataDirectory()
else else
appdata = xdgdatahome; appdata = xdgdatahome;
} }
return appdata.c_str(); return appdata;
#else #else
return getUserDirectory(); return getUserDirectory();
#endif #endif
@@ -145,14 +145,14 @@ public:
/** /**
* Gets the user home directory. * Gets the user home directory.
**/ **/
const char *getUserDirectory(); std::string getUserDirectory();
/** /**
* Gets the APPDATA directory. On Windows, this is the folder * Gets the APPDATA directory. On Windows, this is the folder
* in the %APPDATA% enviroment variable. On Linux, this is the * in the %APPDATA% enviroment variable. On Linux, this is the
* user home folder. * user home folder.
**/ **/
const char *getAppdataDirectory(); std::string getAppdataDirectory();
/** /**
* Gets the full path of the save folder. * Gets the full path of the save folder.
@@ -211,13 +211,13 @@ int w_getWorkingDirectory(lua_State *L)
int w_getUserDirectory(lua_State *L) int w_getUserDirectory(lua_State *L)
{ {
lua_pushstring(L, instance->getUserDirectory()); luax_pushstring(L, instance->getUserDirectory());
return 1; return 1;
} }
int w_getAppdataDirectory(lua_State *L) int w_getAppdataDirectory(lua_State *L)
{ {
lua_pushstring(L, instance->getAppdataDirectory()); luax_pushstring(L, instance->getAppdataDirectory());
return 1; return 1;
} }
@@ -176,6 +176,8 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
{ "mipmap", Graphics::SUPPORT_MIPMAP }, { "mipmap", Graphics::SUPPORT_MIPMAP },
{ "dxt", Graphics::SUPPORT_DXT }, { "dxt", Graphics::SUPPORT_DXT },
{ "bc5", Graphics::SUPPORT_BC5 }, { "bc5", Graphics::SUPPORT_BC5 },
{ "etc1", Graphics::SUPPORT_ETC1 },
{ "pvrtc1", Graphics::SUPPORT_PVRTC1 },
{ "instancing", Graphics::SUPPORT_INSTANCING }, { "instancing", Graphics::SUPPORT_INSTANCING },
{ "srgb", Graphics::SUPPORT_SRGB }, { "srgb", Graphics::SUPPORT_SRGB },
}; };
+16
View File
@@ -95,6 +95,8 @@ public:
SUPPORT_MIPMAP, SUPPORT_MIPMAP,
SUPPORT_DXT, SUPPORT_DXT,
SUPPORT_BC5, SUPPORT_BC5,
SUPPORT_ETC1,
SUPPORT_PVRTC1,
SUPPORT_INSTANCING, SUPPORT_INSTANCING,
SUPPORT_SRGB, SUPPORT_SRGB,
SUPPORT_MAX_ENUM SUPPORT_MAX_ENUM
@@ -145,6 +147,20 @@ public:
**/ **/
virtual void unSetMode() = 0; virtual void unSetMode() = 0;
/**
* Sets whether the module is active (internal use only.)
**/
virtual void setActive(bool active) = 0;
/**
* Gets whether the module is active. Graphics module methods are only
* guaranteed to work when it is active. Calling them otherwise may cause
* the program to crash (or worse.)
* Normally the module will always be active as long as a window exists, it
* may be different on some platforms (especially mobile ones.)
**/
virtual bool isActive() const = 0;
static bool getConstant(const char *in, DrawMode &out); static bool getConstant(const char *in, DrawMode &out);
static bool getConstant(DrawMode in, const char *&out); static bool getConstant(DrawMode in, const char *&out);
-20
View File
@@ -109,16 +109,6 @@ bool Texture::getConstant(WrapMode in, const char *&out)
return wrapModes.find(in, 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[] = StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
{ {
{ "linear", Texture::FILTER_LINEAR }, { "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::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 } // graphics
} // love } // love
-14
View File
@@ -55,14 +55,6 @@ public:
FILTER_MAX_ENUM FILTER_MAX_ENUM
}; };
enum Format
{
FORMAT_NORMAL,
FORMAT_HDR,
FORMAT_SRGB,
FORMAT_MAX_ENUM
};
struct Filter struct Filter
{ {
Filter(); Filter();
@@ -119,9 +111,6 @@ public:
static bool getConstant(const char *in, WrapMode &out); static bool getConstant(const char *in, WrapMode &out);
static bool getConstant(WrapMode in, const char *&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: protected:
int width; int width;
@@ -143,9 +132,6 @@ private:
static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[]; static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes; static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
static StringMap<Format, FORMAT_MAX_ENUM> formats;
}; // Texture }; // Texture
} // graphics } // graphics
+241 -47
View File
@@ -451,7 +451,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) : fbo(0)
, resolve_fbo(0) , resolve_fbo(0)
, texture(0) , texture(0)
@@ -556,36 +556,31 @@ bool Canvas::loadVolatile()
glGenTextures(1, &texture); glGenTextures(1, &texture);
gl.bindTexture(texture); gl.bindTexture(texture);
if (GLAD_ANGLE_texture_usage)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
setFilter(filter); setFilter(filter);
setWrap(wrap); setWrap(wrap);
GLint internalformat; GLenum internalformat = GL_RGBA;
GLenum textype; GLenum externalformat = GL_RGBA;
switch (format) GLenum textype = GL_UNSIGNED_BYTE;
{
case Texture::FORMAT_HDR: convertFormat(format, internalformat, externalformat, textype);
internalformat = GL_RGBA16F;
textype = GL_FLOAT; // in ES2, the internalformat and format params of TexImage have to match.
break; if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
case Texture::FORMAT_SRGB: internalformat = externalformat;
internalformat = GL_SRGB_ALPHA;
textype = GL_UNSIGNED_BYTE;
break;
case Texture::FORMAT_NORMAL:
default:
internalformat = GL_RGBA;
textype = GL_UNSIGNED_BYTE;
}
while (glGetError() != GL_NO_ERROR) while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */; /* Clear the error buffer. */;
glTexImage2D(GL_TEXTURE_2D, glTexImage2D(GL_TEXTURE_2D,
0, 0,
internalformat, (GLint) internalformat,
width, height, width, height,
0, 0,
GL_RGBA, externalformat,
textype, textype,
nullptr); nullptr);
@@ -736,10 +731,13 @@ void Canvas::setupGrab()
gl.matrices.projection.push(Matrix::ortho(0.0, width, 0.0, height)); gl.matrices.projection.push(Matrix::ortho(0.0, width, 0.0, height));
// Make sure the correct sRGB setting is used when drawing to the canvas. // Make sure the correct sRGB setting is used when drawing to the canvas.
if (format == FORMAT_SRGB) if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
glEnable(GL_FRAMEBUFFER_SRGB); {
else if (screenHasSRGB) if (format == FORMAT_SRGB)
glDisable(GL_FRAMEBUFFER_SRGB); glEnable(GL_FRAMEBUFFER_SRGB);
else if (screenHasSRGB)
glDisable(GL_FRAMEBUFFER_SRGB);
}
if (fsaa_buffer != 0) if (fsaa_buffer != 0)
fsaa_dirty = true; fsaa_dirty = true;
@@ -828,8 +826,11 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
if (switchingToOtherCanvas) if (switchingToOtherCanvas)
{ {
if (format == FORMAT_SRGB) if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
glDisable(GL_FRAMEBUFFER_SRGB); {
if (format == FORMAT_SRGB)
glDisable(GL_FRAMEBUFFER_SRGB);
}
} }
else else
{ {
@@ -838,10 +839,13 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
current = nullptr; current = nullptr;
gl.setViewport(systemViewport); gl.setViewport(systemViewport);
if (format == FORMAT_SRGB && !screenHasSRGB) if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
glDisable(GL_FRAMEBUFFER_SRGB); {
else if (format != FORMAT_SRGB && screenHasSRGB) if (format == FORMAT_SRGB && !screenHasSRGB)
glEnable(GL_FRAMEBUFFER_SRGB); glDisable(GL_FRAMEBUFFER_SRGB);
else if (format != FORMAT_SRGB && screenHasSRGB)
glEnable(GL_FRAMEBUFFER_SRGB);
}
} }
gl.matrices.projection.pop(); gl.matrices.projection.pop();
@@ -1007,41 +1011,231 @@ bool Canvas::resolveMSAA()
return true; return true;
} }
Canvas::Format Canvas::getSizedFormat(Canvas::Format format)
{
switch (format)
{
case FORMAT_NORMAL:
// 32-bit render targets don't have guaranteed support on OpenGL ES 2.
if (GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8))
return FORMAT_RGBA4;
else
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_2_10_10_10_REV;
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;
if (GLAD_OES_texture_float)
type = GL_HALF_FLOAT_OES;
else if (GLAD_VERSION_1_1)
type = GL_FLOAT;
else
type = GL_HALF_FLOAT;
break;
case FORMAT_RGBA32F:
internalformat = GL_RGBA32F;
type = GL_FLOAT;
break;
case FORMAT_SRGB:
internalformat = GL_SRGB8_ALPHA8;
type = GL_UNSIGNED_BYTE;
if (GLAD_ES_VERSION_2_0)
externalformat = GL_SRGB_ALPHA;
break;
}
}
bool Canvas::isSupported() bool Canvas::isSupported()
{ {
getStrategy(); getStrategy();
return (strategy != &strategyNone); return (strategy != &strategyNone);
} }
bool Canvas::isHDRSupported()
{
return GLAD_VERSION_3_0 || (isSupported() && GLAD_ARB_texture_float);
}
bool Canvas::isSRGBSupported()
{
if (GLAD_VERSION_3_0)
return true;
if (!isSupported())
return false;
return (GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB)
&& GLAD_EXT_texture_sRGB;
}
bool Canvas::isMultiCanvasSupported() bool Canvas::isMultiCanvasSupported()
{ {
// system must support at least 4 simultanious active canvases. // system must support at least 4 simultanious active canvases.
return gl.getMaxRenderTargets() >= 4; 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:
if (GLAD_VERSION_1_1)
supported = true;
else
supported = GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8;
break;
case FORMAT_RGBA4:
case FORMAT_RGB5A1:
supported = true;
break;
case FORMAT_RGB565:
supported = GLAD_ES_VERSION_2_0 || GLAD_VERSION_4_2 || GLAD_ARB_ES2_compatibility;
break;
case FORMAT_RGB10A2:
if (GLAD_VERSION_1_1)
supported = true;
else
supported = GLAD_ES_VERSION_3_0;
break;
case FORMAT_RG11B10F:
if (GLAD_VERSION_1_1)
supported = GLAD_VERSION_3_0 || GLAD_EXT_packed_float;
else
supported = GLAD_ES_VERSION_3_0 /*|| GLAD_NV_packed_float*/;
break;
case FORMAT_RGBA16F:
if (GLAD_VERSION_1_1)
supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_float;
else
supported = GLAD_EXT_color_buffer_half_float && (GLAD_ES_VERSION_3_0 || GLAD_OES_texture_half_float);
break;
case FORMAT_RGBA32F:
if (GLAD_VERSION_1_1)
supported = GLAD_VERSION_3_0 || GLAD_ARB_texture_float;
else
supported = false; // GLAD_EXT_color_buffer_float && GLAD_ES_VERSION_3_0;
break;
case FORMAT_SRGB:
if (GLAD_VERSION_1_1)
supported = GLAD_VERSION_3_0 || ((GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB)
&& (GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB));
else
supported = GLAD_ES_VERSION_3_0 || GLAD_EXT_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);
if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
internalformat = externalformat;
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() void Canvas::bindDefaultCanvas()
{ {
if (current != nullptr) if (current != nullptr)
current->stopGrab(); 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 } // opengl
} // graphics } // graphics
} // love } // love
+33 -4
View File
@@ -25,6 +25,7 @@
#include "image/Image.h" #include "image/Image.h"
#include "image/ImageData.h" #include "image/ImageData.h"
#include "common/Matrix.h" #include "common/Matrix.h"
#include "common/StringMap.h"
#include "Texture.h" #include "Texture.h"
#include "OpenGL.h" #include "OpenGL.h"
@@ -39,7 +40,24 @@ class Canvas : public Texture
{ {
public: 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(); virtual ~Canvas();
// Implements Volatile. // Implements Volatile.
@@ -85,7 +103,7 @@ public:
return status; return status;
} }
inline Texture::Format getTextureFormat() const inline Format getTextureFormat() const
{ {
return format; return format;
} }
@@ -98,9 +116,8 @@ public:
bool resolveMSAA(); bool resolveMSAA();
static bool isSupported(); static bool isSupported();
static bool isHDRSupported();
static bool isSRGBSupported();
static bool isMultiCanvasSupported(); static bool isMultiCanvasSupported();
static bool isFormatSupported(Format format);
static Canvas *current; static Canvas *current;
static void bindDefaultCanvas(); static void bindDefaultCanvas();
@@ -111,10 +128,16 @@ public:
// Whether the main screen should have linear -> sRGB conversions enabled. // Whether the main screen should have linear -> sRGB conversions enabled.
static bool screenHasSRGB; static bool screenHasSRGB;
static bool getConstant(const char *in, Format &out);
static bool getConstant(Format in, const char *&out);
private: private:
bool createFSAAFBO(GLenum internalformat); bool createFSAAFBO(GLenum internalformat);
static Format getSizedFormat(Format format);
static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type);
GLuint fbo; GLuint fbo;
GLuint resolve_fbo; GLuint resolve_fbo;
@@ -134,6 +157,12 @@ private:
void setupGrab(); void setupGrab();
void drawv(const Matrix &t, const Vertex *v); 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 }; // Canvas
} // opengl } // opengl
+36 -23
View File
@@ -42,6 +42,7 @@ const int Font::TEXTURE_HEIGHTS[] = {128, 128, 256, 256, 512, 512, 1024};
Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter) Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
: rasterizer(r) : rasterizer(r)
, indexBuffer(nullptr)
, height(r->getHeight()) , height(r->getHeight())
, lineHeight(1) , lineHeight(1)
, mSpacing(1) , mSpacing(1)
@@ -68,6 +69,8 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
textureWidth = TEXTURE_WIDTHS[textureSizeIndex]; textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
textureHeight = TEXTURE_HEIGHTS[textureSizeIndex]; textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
indexBuffer = new VertexIndex(20);
love::font::GlyphData *gd = nullptr; love::font::GlyphData *gd = nullptr;
try try
@@ -93,6 +96,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
Font::~Font() Font::~Font()
{ {
delete indexBuffer;
rasterizer->release(); rasterizer->release();
unloadVolatile(); unloadVolatile();
} }
@@ -368,28 +372,17 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing,
// second (using the struct's < operator). // second (using the struct's < operator).
std::sort(glyphinfolist.begin(), glyphinfolist.end()); std::sort(glyphinfolist.begin(), glyphinfolist.end());
std::vector<uint16> indices; int maxvertices = 0;
int indicescount = 0;
for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it) for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it)
maxvertices = std::max(maxvertices, it->vertexcount);
// If the index buffer is too small for the number of glyphs we're going to
// draw, we need to make a new one that has the correct size.
if ((size_t) maxvertices / 4 > indexBuffer->getSize())
{ {
if ((it->vertexcount / 4) * 6 > indicescount) VertexIndex *newIndexBuffer = new VertexIndex(maxvertices / 4);
indicescount = (it->vertexcount / 4) * 6; delete indexBuffer;
} indexBuffer = newIndexBuffer;
indices.reserve(indicescount);
for (int i = 0; i < indicescount / 6; i++)
{
// First triangle.
indices.push_back(i * 4 + 0);
indices.push_back(i * 4 + 1);
indices.push_back(i * 4 + 2);
// Second triangle.
indices.push_back(i * 4 + 0);
indices.push_back(i * 4 + 2);
indices.push_back(i * 4 + 3);
} }
gl.matrices.transform.push(gl.matrices.transform.top()); gl.matrices.transform.push(gl.matrices.transform.top());
@@ -403,16 +396,36 @@ void Font::print(const std::string &text, float x, float y, float extra_spacing,
gl.prepareDraw(); gl.prepareDraw();
VertexBuffer::Bind index_bind(*indexBuffer->getVertexBuffer());
GLenum elemtype = indexBuffer->getType();
const GLvoid *elemoffset = indexBuffer->getPointer(0);
// Optimization: we can take these calls out of the loop if we have support.
if (GLAD_VERSION_3_2 || GLAD_ARB_draw_elements_base_vertex)
{
gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[0].x);
gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[0].s);
}
// We need to draw a new vertex array for every section of the string which // We need to draw a new vertex array for every section of the string which
// uses a different texture than the previous section. // uses a different texture than the previous section.
for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it) for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it)
{ {
gl.bindTexture(it->texture); gl.bindTexture(it->texture);
gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[it->startvertex].x); if (GLAD_VERSION_3_2 || GLAD_ARB_draw_elements_base_vertex)
gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[it->startvertex].s); {
// Optimization: setting a base vertex is faster than redoing the
// setVertexAttribArray calls before each draw.
glDrawElementsBaseVertex(GL_TRIANGLES, (it->vertexcount / 4) * 6, elemtype, elemoffset, it->startvertex);
}
else
{
gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[it->startvertex].x);
gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), &glyphverts[it->startvertex].s);
glDrawElements(GL_TRIANGLES, (it->vertexcount / 4) * 6, GL_UNSIGNED_SHORT, &indices[0]); glDrawElements(GL_TRIANGLES, (it->vertexcount / 4) * 6, elemtype, elemoffset);
}
} }
gl.disableVertexAttribArray(OpenGL::ATTRIB_POS); gl.disableVertexAttribArray(OpenGL::ATTRIB_POS);
@@ -31,6 +31,7 @@
#include "font/Rasterizer.h" #include "font/Rasterizer.h"
#include "graphics/Texture.h" #include "graphics/Texture.h"
#include "graphics/Volatile.h" #include "graphics/Volatile.h"
#include "VertexBuffer.h"
#include "OpenGL.h" #include "OpenGL.h"
@@ -185,6 +186,8 @@ private:
love::font::Rasterizer *rasterizer; love::font::Rasterizer *rasterizer;
VertexIndex *indexBuffer;
int height; int height;
float lineHeight; float lineHeight;
float mSpacing; // modifies the spacing by multiplying it with this value float mSpacing; // modifies the spacing by multiplying it with this value
@@ -52,6 +52,7 @@ Graphics::Graphics()
, width(0) , width(0)
, height(0) , height(0)
, created(false) , created(false)
, active(true)
, activeStencil(false) , activeStencil(false)
, savedState() , savedState()
{ {
@@ -72,7 +73,10 @@ Graphics::~Graphics()
currentFont->release(); currentFont->release();
if (Shader::defaultShader) if (Shader::defaultShader)
{
Shader::defaultShader->release(); Shader::defaultShader->release();
Shader::defaultShader = nullptr;
}
currentWindow->release(); currentWindow->release();
} }
@@ -235,12 +239,16 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
} }
// Set whether drawing converts input from linear -> sRGB colorspace. // Set whether drawing converts input from linear -> sRGB colorspace.
if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB) if (GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_sRGB || GLAD_EXT_framebuffer_sRGB
|| (GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB))
{ {
if (sRGB) if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
glEnable(GL_FRAMEBUFFER_SRGB); {
else if (sRGB)
glDisable(GL_FRAMEBUFFER_SRGB); glEnable(GL_FRAMEBUFFER_SRGB);
else
glDisable(GL_FRAMEBUFFER_SRGB);
}
} }
else else
sRGB = false; sRGB = false;
@@ -282,6 +290,23 @@ void Graphics::unSetMode()
created = false; created = false;
} }
void Graphics::setActive(bool active)
{
// Make sure all pending OpenGL commands have fully executed before
// returning, if we're going from active to inactive.
if (isCreated() && this->active && !active)
glFinish();
this->active = active;
}
bool Graphics::isActive() const
{
// The graphics module is only completely 'active' if there's a window, a
// context, and the active variable is set.
return active && isCreated() && currentWindow && currentWindow->isCreated();
}
static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/) static void APIENTRY debugCB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei /*len*/, const GLchar *msg, const GLvoid* /*usr*/)
{ {
// Human-readable strings for the debug info. // Human-readable strings for the debug info.
@@ -364,6 +389,9 @@ void Graphics::clear()
void Graphics::present() void Graphics::present()
{ {
if (!isActive())
return;
// Make sure we don't have a canvas active. // Make sure we don't have a canvas active.
Canvas *c = Canvas::current; Canvas *c = Canvas::current;
Canvas::bindDefaultCanvas(); Canvas::bindDefaultCanvas();
@@ -463,7 +491,7 @@ void Graphics::discardStencil()
activeStencil = false; 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. // Create the image.
Image *image = new Image(data, format); Image *image = new Image(data, format);
@@ -490,7 +518,7 @@ Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
return image; 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. // Create the image.
Image *image = new Image(cdata, format); Image *image = new Image(cdata, format);
@@ -537,13 +565,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
return new ParticleSystem(texture, 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()) if (!Canvas::isFormatSupported(format))
throw Exception("HDR Canvases are not supported by your OpenGL implementation"); {
const char *fstr = "rgba8";
if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported()) Canvas::getConstant(format, fstr);
throw Exception("sRGB Canvases are not supported by your OpenGL implementation"); throw love::Exception("The %s canvas format is not supported by your OpenGL implementation.", fstr);
}
if (width > gl.getMaxTextureSize()) if (width > gl.getMaxTextureSize())
throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width); throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
@@ -594,8 +623,8 @@ Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int f
} }
canvas->release(); canvas->release();
throw Exception(error_string.str().c_str()); throw Exception("%s", error_string.str().c_str());
return NULL; // never reached return nullptr; // never reached
} }
Shader *Graphics::newShader(const Shader::ShaderSources &sources) Shader *Graphics::newShader(const Shader::ShaderSources &sources)
@@ -972,7 +1001,24 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius, int points)
float angle_shift = (two_pi / points); float angle_shift = (two_pi / points);
float phi = .0f; float phi = .0f;
float *coords = new float[2 * (points + 1)]; float *coords = nullptr;
int extrapoints = 1;
// Fill mode will use a triangle fan internally, so we want the "hub" of the
// fan (its first vertex) to be the midpoint of the circle.
if (mode == DRAW_FILL)
{
extrapoints = 2;
coords = new float[2 * (points + extrapoints)];
coords[0] = x;
coords[1] = y;
coords += 2;
}
else
coords = new float[2 * (points + extrapoints)];
for (int i = 0; i < points; ++i, phi += angle_shift) for (int i = 0; i < points; ++i, phi += angle_shift)
{ {
coords[2*i] = x + radius * cosf(phi); coords[2*i] = x + radius * cosf(phi);
@@ -982,7 +1028,10 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius, int points)
coords[2*points] = coords[0]; coords[2*points] = coords[0];
coords[2*points+1] = coords[1]; coords[2*points+1] = coords[1];
polygon(mode, coords, (points + 1) * 2); if (mode == DRAW_FILL)
coords -= 2;
polygon(mode, coords, 2 * (points + extrapoints));
delete[] coords; delete[] coords;
} }
@@ -1059,7 +1108,7 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count)
gl.enableVertexAttribArray(OpenGL::ATTRIB_POS); gl.enableVertexAttribArray(OpenGL::ATTRIB_POS);
gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, 0, (GLvoid *) coords); gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, 0, (GLvoid *) coords);
glDrawArrays(GL_TRIANGLE_FAN, 0, count/2-1); // opengl will close the polygon for us glDrawArrays(GL_TRIANGLE_FAN, 0, count / 2);
gl.disableVertexAttribArray(OpenGL::ATTRIB_POS); gl.disableVertexAttribArray(OpenGL::ATTRIB_POS);
} }
@@ -118,6 +118,9 @@ public:
virtual bool setMode(int width, int height, bool &sRGB); virtual bool setMode(int width, int height, bool &sRGB);
virtual void unSetMode(); virtual void unSetMode();
virtual void setActive(bool active);
virtual bool isActive() const;
void setDebug(bool enable); void setDebug(bool enable);
/** /**
@@ -194,8 +197,8 @@ public:
/** /**
* Creates an Image object with padding and/or optimization. * 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::ImageData *data, Image::Format format = Image::FORMAT_NORMAL);
Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL); Image *newImage(love::image::CompressedData *cdata, Image::Format format = Image::FORMAT_NORMAL);
Quad *newQuad(Quad::Viewport v, float sw, float sh); Quad *newQuad(Quad::Viewport v, float sw, float sh);
@@ -208,7 +211,7 @@ public:
ParticleSystem *newParticleSystem(Texture *texture, int size); 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); Shader *newShader(const Shader::ShaderSources &sources);
@@ -480,6 +483,7 @@ private:
int width; int width;
int height; int height;
bool created; bool created;
bool active;
bool activeStencil; bool activeStencil;
+46 -4
View File
@@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f;
Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE; Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
float Image::defaultMipmapSharpness = 0.0f; float Image::defaultMipmapSharpness = 0.0f;
Image::Image(love::image::ImageData *data, Texture::Format format) Image::Image(love::image::ImageData *data, Format format)
: data(data) : data(data)
, cdata(nullptr) , cdata(nullptr)
, paddedWidth(width) , paddedWidth(width)
@@ -55,7 +55,7 @@ Image::Image(love::image::ImageData *data, Texture::Format format)
preload(); preload();
} }
Image::Image(love::image::CompressedData *cdata, Texture::Format format) Image::Image(love::image::CompressedData *cdata, Format format)
: data(nullptr) : data(nullptr)
, cdata(cdata) , cdata(cdata)
, paddedWidth(width) , paddedWidth(width)
@@ -504,7 +504,7 @@ bool Image::refresh()
return true; return true;
} }
Texture::Format Image::getFormat() const Image::Format Image::getFormat() const
{ {
return format; return format;
} }
@@ -602,6 +602,20 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const
return GL_COMPRESSED_RG_RGTC2; return GL_COMPRESSED_RG_RGTC2;
case image::CompressedData::FORMAT_BC5s: case image::CompressedData::FORMAT_BC5s:
return GL_COMPRESSED_SIGNED_RG_RGTC2; return GL_COMPRESSED_SIGNED_RG_RGTC2;
case image::CompressedData::FORMAT_ETC1:
// The ETC2 format can load ETC1 textures.
if (GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_0 || GLAD_ARB_ES3_compatibility)
return GL_COMPRESSED_RGB8_ETC2;
else
return GL_ETC1_RGB8_OES;
case image::CompressedData::FORMAT_PVR1_RGB2:
return GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
case image::CompressedData::FORMAT_PVR1_RGB4:
return GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
case image::CompressedData::FORMAT_PVR1_RGBA2:
return GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
case image::CompressedData::FORMAT_PVR1_RGBA4:
return GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
default: default:
if (srgb) if (srgb)
return GL_SRGB_ALPHA; return GL_SRGB_ALPHA;
@@ -635,14 +649,24 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format)
switch (format) switch (format)
{ {
case image::CompressedData::FORMAT_DXT1: case image::CompressedData::FORMAT_DXT1:
return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
case image::CompressedData::FORMAT_DXT3: case image::CompressedData::FORMAT_DXT3:
return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3;
case image::CompressedData::FORMAT_DXT5: case image::CompressedData::FORMAT_DXT5:
return GLAD_EXT_texture_compression_s3tc; return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt5;
case image::CompressedData::FORMAT_BC4: case image::CompressedData::FORMAT_BC4:
case image::CompressedData::FORMAT_BC4s: case image::CompressedData::FORMAT_BC4s:
case image::CompressedData::FORMAT_BC5: case image::CompressedData::FORMAT_BC5:
case image::CompressedData::FORMAT_BC5s: case image::CompressedData::FORMAT_BC5s:
return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc); return (GLAD_VERSION_3_0 || GLAD_ARB_texture_compression_rgtc || GLAD_EXT_texture_compression_rgtc);
case image::CompressedData::FORMAT_ETC1:
// ETC2 support guarantees ETC1 support as well.
return GLAD_VERSION_4_3 || GLAD_ES_VERSION_3_0 || GLAD_ARB_ES3_compatibility || GLAD_OES_compressed_ETC1_RGB8_texture;
case image::CompressedData::FORMAT_PVR1_RGB2:
case image::CompressedData::FORMAT_PVR1_RGB4:
case image::CompressedData::FORMAT_PVR1_RGBA2:
case image::CompressedData::FORMAT_PVR1_RGBA4:
return GLAD_IMG_texture_compression_pvrtc;
default: default:
break; break;
} }
@@ -655,6 +679,24 @@ bool Image::hasSRGBSupport()
return GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB || GLAD_EXT_sRGB; return GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB || GLAD_EXT_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 } // opengl
} // graphics } // graphics
} // love } // love
+19 -5
View File
@@ -25,6 +25,7 @@
#include "common/config.h" #include "common/config.h"
#include "common/Matrix.h" #include "common/Matrix.h"
#include "common/Vector.h" #include "common/Vector.h"
#include "common/StringMap.h"
#include "common/math.h" #include "common/math.h"
#include "image/ImageData.h" #include "image/ImageData.h"
#include "image/CompressedData.h" #include "image/CompressedData.h"
@@ -50,20 +51,27 @@ class Image : public Texture
{ {
public: public:
enum Format
{
FORMAT_NORMAL,
FORMAT_SRGB,
FORMAT_MAX_ENUM
};
/** /**
* Creates a new Image. Not that anything is ready to use * Creates a new Image. Not that anything is ready to use
* before load is called. * before load is called.
* *
* @param data The data from which to load the image. * @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. * Creates a new Image with compressed image data.
* *
* @param cdata The compressed data from which to load the image. * @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. * Destructor. Deletes the hardware texture and other resources.
@@ -120,7 +128,7 @@ public:
**/ **/
bool refresh(); bool refresh();
Texture::Format getFormat() const; Format getFormat() const;
static void setDefaultMipmapSharpness(float sharpness); static void setDefaultMipmapSharpness(float sharpness);
static float getDefaultMipmapSharpness(); static float getDefaultMipmapSharpness();
@@ -136,6 +144,9 @@ public:
static bool hasSRGBSupport(); static bool hasSRGBSupport();
static bool getConstant(const char *in, Format &out);
static bool getConstant(Format in, const char *&out);
private: private:
void uploadDefaultTexture(); void uploadDefaultTexture();
@@ -165,8 +176,8 @@ private:
// Whether this Image is using a compressed texture. // Whether this Image is using a compressed texture.
bool compressed; bool compressed;
// The format to interpret the texture's data as. // The format to interpret the image's data as.
Texture::Format format; Format format;
// True if the image wasn't able to be properly created and it had to fall // True if the image wasn't able to be properly created and it had to fall
// back to a default texture. // back to a default texture.
@@ -188,6 +199,9 @@ private:
GLenum getCompressedFormat(image::CompressedData::Format cformat) const; GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
static StringMap<Format, FORMAT_MAX_ENUM> formats;
}; // Image }; // Image
} // opengl } // opengl
@@ -160,6 +160,7 @@ void OpenGL::initVendor()
} }
// http://feedback.wildfiregames.com/report/opengl/feature/GL_VENDOR // http://feedback.wildfiregames.com/report/opengl/feature/GL_VENDOR
// http://stackoverflow.com/questions/2093594/opengl-extensions-available-on-different-android-devices
if (strstr(vstr, "ATI Technologies")) if (strstr(vstr, "ATI Technologies"))
vendor = VENDOR_ATI_AMD; vendor = VENDOR_ATI_AMD;
else if (strstr(vstr, "NVIDIA")) else if (strstr(vstr, "NVIDIA"))
@@ -172,6 +173,16 @@ void OpenGL::initVendor()
vendor = VENDOR_APPLE; vendor = VENDOR_APPLE;
else if (strstr(vstr, "Microsoft")) else if (strstr(vstr, "Microsoft"))
vendor = VENDOR_MICROSOFT; vendor = VENDOR_MICROSOFT;
else if (strstr(vstr, "Imagination"))
vendor = VENDOR_IMGTEC;
else if (strstr(vstr, "ARM"))
vendor = VENDOR_ARM;
else if (strstr(vstr, "Qualcomm"))
vendor = VENDOR_QUALCOMM;
else if (strstr(vstr, "Broadcom"))
vendor = VENDOR_BROADCOM;
else if (strstr(vstr, "Vivante"))
vendor = VENDOR_VIVANTE;
else else
vendor = VENDOR_UNKNOWN; vendor = VENDOR_UNKNOWN;
} }
@@ -66,8 +66,13 @@ public:
VENDOR_NVIDIA, VENDOR_NVIDIA,
VENDOR_INTEL, VENDOR_INTEL,
VENDOR_MESA_SOFT, // Software renderer. VENDOR_MESA_SOFT, // Software renderer.
VENDOR_APPLE, // Software renderer. VENDOR_APPLE, // Software renderer (or Apple A7 chips and newer.)
VENDOR_MICROSOFT, // Software renderer. VENDOR_MICROSOFT, // Software renderer.
VENDOR_IMGTEC,
VENDOR_ARM,
VENDOR_QUALCOMM,
VENDOR_BROADCOM,
VENDOR_VIVANTE,
VENDOR_UNKNOWN VENDOR_UNKNOWN
}; };
@@ -101,10 +101,9 @@ Shader::~Shader()
detach(); detach();
for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it) for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it)
{
it->second->release(); it->second->release();
boundRetainables.erase(it);
} boundRetainables.clear();
unloadVolatile(); unloadVolatile();
} }
@@ -189,10 +189,18 @@ void VBO::unmap()
is_bound = true; is_bound = true;
} }
// "orphan" current buffer to avoid implicit synchronisation on the GPU: if (getUsage() == GL_STATIC_DRAW)
// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf {
glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); // Upload the mapped data to the buffer.
glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); 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; is_mapped = false;
} }
@@ -113,10 +113,10 @@ int w_Canvas_clear(lua_State *L)
int w_Canvas_getFormat(lua_State *L) int w_Canvas_getFormat(lua_State *L)
{ {
Canvas *canvas = luax_checkcanvas(L, 1); Canvas *canvas = luax_checkcanvas(L, 1);
Texture::Format format = canvas->getTextureFormat(); Canvas::Format format = canvas->getTextureFormat();
const char *str; const char *str;
if (!Texture::getConstant(format, str)) if (!Canvas::getConstant(format, str))
return luaL_error(L, "Unknown texture format."); return luaL_error(L, "Unknown Canvas format.");
lua_pushstring(L, str); lua_pushstring(L, str);
return 1; return 1;
@@ -60,6 +60,12 @@ int w_isCreated(lua_State *L)
return 1; return 1;
} }
int w_isActive(lua_State *L)
{
luax_pushboolean(L, instance->isActive());
return 1;
}
int w_getWidth(lua_State *L) int w_getWidth(lua_State *L)
{ {
lua_pushinteger(L, instance->getWidth()); lua_pushinteger(L, instance->getWidth());
@@ -155,14 +161,11 @@ int w_newImage(lua_State *L)
love::image::ImageData *data = nullptr; love::image::ImageData *data = nullptr;
love::image::CompressedData *cdata = 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); const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
if (fstr != nullptr && !Texture::getConstant(fstr, format)) if (fstr != nullptr && !Image::getConstant(fstr, format))
return luaL_error(L, "Invalid texture format: %s", fstr); return luaL_error(L, "Invalid Image format: %s", fstr);
if (format == Texture::FORMAT_HDR) // For now...
return luaL_error(L, "HDR images are not supported.");
// Convert to FileData, if necessary. // Convert to FileData, if necessary.
if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T)) if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
@@ -334,9 +337,9 @@ int w_newCanvas(lua_State *L)
const char *str = luaL_optstring(L, 3, "normal"); const char *str = luaL_optstring(L, 3, "normal");
int fsaa = luaL_optint(L, 4, 0); int fsaa = luaL_optint(L, 4, 0);
Texture::Format format; Canvas::Format format;
if (!Texture::getConstant(str, format)) if (!Canvas::getConstant(str, format))
return luaL_error(L, "Invalid texture format: %s", str); return luaL_error(L, "Invalid Canvas format: %s", str);
Canvas *canvas = nullptr; Canvas *canvas = nullptr;
EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);) EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
@@ -1015,7 +1018,7 @@ int w_isSupported(lua_State *L)
supported = false; supported = false;
break; break;
case Graphics::SUPPORT_HDR_CANVAS: case Graphics::SUPPORT_HDR_CANVAS:
if (!Canvas::isHDRSupported()) if (!Canvas::isFormatSupported(Canvas::FORMAT_HDR))
supported = false; supported = false;
break; break;
case Graphics::SUPPORT_MULTI_CANVAS: case Graphics::SUPPORT_MULTI_CANVAS:
@@ -1046,12 +1049,20 @@ int w_isSupported(lua_State *L)
if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5)) if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5))
supported = false; supported = false;
break; break;
case Graphics::SUPPORT_ETC1:
if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_ETC1))
supported = false;
break;
case Graphics::SUPPORT_PVRTC1:
if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_PVR1_RGBA4))
supported = false;
break;
case Graphics::SUPPORT_INSTANCING: case Graphics::SUPPORT_INSTANCING:
if (!(GLAD_ARB_draw_instanced || (GLAD_ES_VERSION_2_0 && GLAD_EXT_draw_instanced))) if (!(GLAD_ARB_draw_instanced || (GLAD_ES_VERSION_2_0 && GLAD_EXT_draw_instanced)))
supported = false; supported = false;
break; break;
case Graphics::SUPPORT_SRGB: case Graphics::SUPPORT_SRGB:
if (!Canvas::isSRGBSupported()) if (!Canvas::isFormatSupported(Canvas::FORMAT_SRGB))
supported = false; supported = false;
break; break;
default: default:
@@ -1064,6 +1075,18 @@ int w_isSupported(lua_State *L)
return 1; 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) int w_getRendererInfo(lua_State *L)
{ {
std::string name, version, vendor, device; std::string name, version, vendor, device;
@@ -1441,6 +1464,7 @@ static const luaL_Reg functions[] =
{ "_setDefaultShaderCode", w_setDefaultShaderCode }, { "_setDefaultShaderCode", w_setDefaultShaderCode },
{ "isSupported", w_isSupported }, { "isSupported", w_isSupported },
{ "hasCanvasFormat", w_hasCanvasFormat },
{ "getRendererInfo", w_getRendererInfo }, { "getRendererInfo", w_getRendererInfo },
{ "getSystemLimit", w_getSystemLimit }, { "getSystemLimit", w_getSystemLimit },
@@ -1450,6 +1474,7 @@ static const luaL_Reg functions[] =
{ "printf", w_printf }, { "printf", w_printf },
{ "isCreated", w_isCreated }, { "isCreated", w_isCreated },
{ "isActive", w_isActive },
{ "getWidth", w_getWidth }, { "getWidth", w_getWidth },
{ "getHeight", w_getHeight }, { "getHeight", w_getHeight },
{ "getDimensions", w_getDimensions }, { "getDimensions", w_getDimensions },
@@ -43,6 +43,7 @@ int w_reset(lua_State *L);
int w_clear(lua_State *L); int w_clear(lua_State *L);
int w_present(lua_State *L); int w_present(lua_State *L);
int w_isCreated(lua_State *L); int w_isCreated(lua_State *L);
int w_isActive(lua_State *L);
int w_getWidth(lua_State *L); int w_getWidth(lua_State *L);
int w_getHeight(lua_State *L); int w_getHeight(lua_State *L);
int w_getDimensions(lua_State *L); int w_getDimensions(lua_State *L);
@@ -94,6 +95,7 @@ int w_setShader(lua_State *L);
int w_getShader(lua_State *L); int w_getShader(lua_State *L);
int w_setDefaultShaderCode(lua_State *L); int w_setDefaultShaderCode(lua_State *L);
int w_isSupported(lua_State *L); int w_isSupported(lua_State *L);
int w_hasCanvasFormat(lua_State *L);
int w_getRendererInfo(lua_State *L); int w_getRendererInfo(lua_State *L);
int w_getSystemLimit(lua_State *L); int w_getSystemLimit(lua_State *L);
int w_draw(lua_State *L); int w_draw(lua_State *L);
@@ -110,6 +110,11 @@ StringMap<CompressedData::Format, CompressedData::FORMAT_MAX_ENUM>::Entry Compre
{"bc4s", CompressedData::FORMAT_BC4s}, {"bc4s", CompressedData::FORMAT_BC4s},
{"bc5", CompressedData::FORMAT_BC5}, {"bc5", CompressedData::FORMAT_BC5},
{"bc5s", CompressedData::FORMAT_BC5s}, {"bc5s", CompressedData::FORMAT_BC5s},
{"etc1", CompressedData::FORMAT_ETC1},
{"pvr1rgb2", CompressedData::FORMAT_PVR1_RGB2},
{"pvr1rgb4", CompressedData::FORMAT_PVR1_RGB4},
{"pvr1rgba2", CompressedData::FORMAT_PVR1_RGBA2},
{"pvr1rgba4", CompressedData::FORMAT_PVR1_RGBA4},
}; };
StringMap<CompressedData::Format, CompressedData::FORMAT_MAX_ENUM> CompressedData::formats(CompressedData::formatEntries, sizeof(CompressedData::formatEntries)); StringMap<CompressedData::Format, CompressedData::FORMAT_MAX_ENUM> CompressedData::formats(CompressedData::formatEntries, sizeof(CompressedData::formatEntries));
@@ -54,6 +54,11 @@ public:
FORMAT_BC4s, FORMAT_BC4s,
FORMAT_BC5, FORMAT_BC5,
FORMAT_BC5s, FORMAT_BC5s,
FORMAT_ETC1,
FORMAT_PVR1_RGB2,
FORMAT_PVR1_RGB4,
FORMAT_PVR1_RGBA2,
FORMAT_PVR1_RGBA4,
FORMAT_MAX_ENUM FORMAT_MAX_ENUM
}; };
@@ -29,29 +29,47 @@ namespace image
namespace magpie namespace magpie
{ {
CompressedData::CompressedData(love::filesystem::FileData *filedata) CompressedData::CompressedData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata)
: formatHandlers(formats)
{ {
for (CompressedFormatHandler *handler : formatHandlers)
handler->retain();
load(filedata); load(filedata);
} }
CompressedData::~CompressedData() CompressedData::~CompressedData()
{ {
delete[] data; delete[] data;
for (CompressedFormatHandler *handler : formatHandlers)
handler->release();
} }
void CompressedData::load(love::filesystem::FileData *filedata) void CompressedData::load(love::filesystem::FileData *filedata)
{ {
CompressedFormatHandler *parser = nullptr;
for (CompressedFormatHandler *handler : formatHandlers)
{
if (handler->canParse(filedata))
{
parser = handler;
break;
}
}
if (parser == nullptr)
throw love::Exception("Could not parse compressed data: Unknown format.");
// SubImage vector will be populated by a parser. // SubImage vector will be populated by a parser.
std::vector<SubImage> parsedimages; std::vector<SubImage> parsedimages;
Format texformat = FORMAT_UNKNOWN; Format texformat = FORMAT_UNKNOWN;
uint8 *newdata = 0;
size_t newdata_size = 0; size_t newdata_size = 0;
uint8 *newdata = parser->parse(filedata, parsedimages, newdata_size, texformat);
if (ddsHandler::canParse(filedata)) if (newdata == nullptr)
newdata = ddsHandler::parse(filedata, parsedimages, newdata_size, texformat);
if (newdata == 0)
throw love::Exception("Could not parse compressed data."); throw love::Exception("Could not parse compressed data.");
if (texformat == FORMAT_UNKNOWN) if (texformat == FORMAT_UNKNOWN)
@@ -76,14 +94,6 @@ void CompressedData::load(love::filesystem::FileData *filedata)
format = texformat; format = texformat;
} }
bool CompressedData::isCompressed(love::filesystem::FileData *filedata)
{
if (ddsHandler::canParse(filedata))
return true;
return false;
}
} // magpie } // magpie
} // image } // image
} // love } // love
@@ -22,9 +22,13 @@
#define LOVE_IMAGE_MAGPIE_COMPRESSED_DATA_H #define LOVE_IMAGE_MAGPIE_COMPRESSED_DATA_H
// LOVE // LOVE
#include "CompressedFormatHandler.h"
#include "filesystem/FileData.h" #include "filesystem/FileData.h"
#include "image/CompressedData.h" #include "image/CompressedData.h"
// C++
#include <list>
namespace love namespace love
{ {
namespace image namespace image
@@ -36,15 +40,16 @@ class CompressedData : public love::image::CompressedData
{ {
public: public:
CompressedData(love::filesystem::FileData *filedata); CompressedData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata);
virtual ~CompressedData(); virtual ~CompressedData();
static bool isCompressed(love::filesystem::FileData *filedata);
private: private:
void load(love::filesystem::FileData *filedata); void load(love::filesystem::FileData *filedata);
// Compressed image format handlers we use for parsing.
std::list<CompressedFormatHandler *> formatHandlers;
}; // CompressedData }; // CompressedData
} // magpie } // magpie
@@ -0,0 +1,74 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H
#define LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H
// LOVE
#include "filesystem/FileData.h"
#include "image/CompressedData.h"
#include "common/Object.h"
namespace love
{
namespace image
{
namespace magpie
{
/**
* Base class for all CompressedData parser library interfaces.
* We inherit from love::Object to take advantage of reference counting...
**/
class CompressedFormatHandler : public love::Object
{
public:
CompressedFormatHandler() {}
virtual ~CompressedFormatHandler() {}
/**
* Determines whether a particular FileData can be parsed as CompressedData
* by this handler.
* @param data The data to parse.
**/
virtual bool canParse(const filesystem::FileData *data) = 0;
/**
* Parses compressed image filedata into a list of sub-images and returns
* a single block of memory containing all the images.
*
* @param[in] filedata The data to parse.
* @param[out] image The list of sub-images generated. Byte data is a pointer
* to the returned data.
* @param[out] dataSize The total size in bytes of the returned data.
* @param[out] format The format of the Compressed Data.
*
* @return The single block of memory containing the parsed images.
**/
virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format) = 0;
}; // CompressedFormatHandler
} // magpie
} // image
} // love
#endif // LOVE_IMAGE_MAGPIE_COMPRESSED_HANDLER_H
+23 -4
View File
@@ -25,6 +25,11 @@
#include "DevilHandler.h" #include "DevilHandler.h"
#include "ddsHandler.h"
#include "PVRHandler.h"
#include "KTXHandler.h"
#include "PKMHandler.h"
namespace love namespace love
{ {
namespace image namespace image
@@ -35,14 +40,22 @@ namespace magpie
Image::Image() Image::Image()
{ {
formatHandlers.push_back(new DevilHandler); formatHandlers.push_back(new DevilHandler);
compressedFormatHandlers.push_back(new DDSHandler);
compressedFormatHandlers.push_back(new PVRHandler);
compressedFormatHandlers.push_back(new KTXHandler);
compressedFormatHandlers.push_back(new PKMHandler);
} }
Image::~Image() Image::~Image()
{ {
// ImageData objects reference the FormatHandlers in our list, so we should // ImageData objects reference the FormatHandlers in our list, so we should
// release them instead of deleting them completely here. // release them instead of deleting them completely here.
for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it) for (FormatHandler *handler : formatHandlers)
(*it)->release(); handler->release();
for (CompressedFormatHandler *handler : compressedFormatHandlers)
handler->release();
} }
const char *Image::getName() const const char *Image::getName() const
@@ -67,12 +80,18 @@ love::image::ImageData *Image::newImageData(int width, int height, void *data, b
love::image::CompressedData *Image::newCompressedData(love::filesystem::FileData *data) love::image::CompressedData *Image::newCompressedData(love::filesystem::FileData *data)
{ {
return new CompressedData(data); return new CompressedData(compressedFormatHandlers, data);
} }
bool Image::isCompressed(love::filesystem::FileData *data) bool Image::isCompressed(love::filesystem::FileData *data)
{ {
return CompressedData::isCompressed(data); for (CompressedFormatHandler *handler : compressedFormatHandlers)
{
if (handler->canParse(data))
return true;
}
return false;
} }
} // magpie } // magpie
@@ -24,6 +24,7 @@
// LOVE // LOVE
#include "image/Image.h" #include "image/Image.h"
#include "FormatHandler.h" #include "FormatHandler.h"
#include "CompressedFormatHandler.h"
// C++ // C++
#include <list> #include <list>
@@ -63,6 +64,9 @@ private:
// Image format handlers we can use for decoding and encoding ImageData. // Image format handlers we can use for decoding and encoding ImageData.
std::list<FormatHandler *> formatHandlers; std::list<FormatHandler *> formatHandlers;
// Compressed image format handers we can use for parsing CompressedData.
std::list<CompressedFormatHandler *> compressedFormatHandlers;
}; // Image }; // Image
} // magpie } // magpie
@@ -0,0 +1,241 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "KTXHandler.h"
#include "common/int.h"
// C
#include <string.h>
// C++
#include <algorithm>
namespace love
{
namespace image
{
namespace magpie
{
namespace
{
#define KTX_IDENTIFIER_REF {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A}
#define KTX_ENDIAN_REF (0x04030201)
#define KTX_ENDIAN_REF_REV (0x01020304)
#define KTX_HEADER_SIZE (64)
struct KTXHeader
{
uint8 identifier[12];
uint32 endianness;
uint32 glType;
uint32 glTypeSize;
uint32 glFormat;
uint32 glInternalFormat;
uint32 glBaseInternalFormat;
uint32 pixelWidth;
uint32 pixelHeight;
uint32 pixelDepth;
uint32 numberOfArrayElements;
uint32 numberOfFaces;
uint32 numberOfMipmapLevels;
uint32 bytesOfKeyValueData;
};
static_assert(sizeof(KTXHeader) == KTX_HEADER_SIZE, "Real size of KTX header doesn't match struct size!");
enum KTXGLInternalFormat
{
KTX_GL_ETC1_RGB8_OES = 0x8D64,
// LOVE doesn't support EAC or ETC2 yet, but it won't be hard to add.
KTX_GL_COMPRESSED_R11_EAC = 0x9270,
KTX_GL_COMPRESSED_SIGNED_R11_EAC = 0x9271,
KTX_GL_COMPRESSED_RG11_EAC = 0x9272,
KTX_GL_COMPRESSED_SIGNED_RG11_EAC = 0x9273,
KTX_GL_COMPRESSED_RGB8_ETC2 = 0x9274,
KTX_GL_COMPRESSED_SRGB8_ETC2 = 0x9275,
KTX_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276,
KTX_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277,
KTX_GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278,
KTX_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279,
// I don't know if any KTX file contains PVR data, but why not support it.
KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00,
KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01,
KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02,
KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03,
// Same with DXT1/3/5.
KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0,
KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2,
KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3
};
CompressedData::Format convertFormat(uint32 glformat)
{
switch (glformat)
{
case KTX_GL_ETC1_RGB8_OES:
return CompressedData::FORMAT_ETC1;
case KTX_GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
return CompressedData::FORMAT_PVR1_RGB4;
case KTX_GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
return CompressedData::FORMAT_PVR1_RGB2;
case KTX_GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
return CompressedData::FORMAT_PVR1_RGBA4;
case KTX_GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
return CompressedData::FORMAT_PVR1_RGBA2;
case KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
return CompressedData::FORMAT_DXT1;
case KTX_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
return CompressedData::FORMAT_DXT3;
case KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return CompressedData::FORMAT_DXT5;
default:
return CompressedData::FORMAT_UNKNOWN;
}
}
} // Anonymous namespace.
bool KTXHandler::canParse(const filesystem::FileData *data)
{
if (data->getSize() < sizeof(KTXHeader))
return false;
KTXHeader *header = (KTXHeader *) data->getData();
uint8 ktxidentifier[12] = KTX_IDENTIFIER_REF;
if (memcmp(header->identifier, ktxidentifier, 12) != 0)
return false;
if (header->endianness != KTX_ENDIAN_REF && header->endianness != KTX_ENDIAN_REF_REV)
return false;
return true;
}
uint8 *KTXHandler::parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format)
{
if (!canParse(filedata))
throw love::Exception("Could not decode compressed data (not a KTX file?)");
KTXHeader header = *(KTXHeader *) filedata->getData();
if (header.endianness == KTX_ENDIAN_REF_REV)
{
uint32 *headerArray = (uint32 *) &header.glType;
for (int i = 0; i < 12; i++)
headerArray[i] = swap32(headerArray[i]);
}
header.numberOfMipmapLevels = std::max(header.numberOfMipmapLevels, 1u);
CompressedData::Format cformat = convertFormat(header.glInternalFormat);
if (cformat == CompressedData::FORMAT_UNKNOWN)
throw love::Exception("Unsupported image format in KTX file.");
if (header.numberOfArrayElements > 0)
throw love::Exception("Texture arrays in KTX files are not supported.");
if (header.pixelDepth > 1)
throw love::Exception("3D textures in KTX files are not supported.");
if (header.numberOfFaces > 1)
throw love::Exception("Cubemap textures in KTX files are not supported.");
size_t fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
const uint8 *filebytes = (uint8 *) filedata->getData();
size_t totalsize = 0;
// Calculate the total size needed to hold the data in memory.
for (int i = 0; i < (int) header.numberOfMipmapLevels; i++)
{
if (fileoffset + sizeof(uint32) > filedata->getSize())
throw love::Exception("Could not parse KTX file: unexpected EOF.");
uint32 mipsize = *(uint32 *) (filebytes + fileoffset);
if (header.endianness == KTX_ENDIAN_REF_REV)
mipsize = swap32(mipsize);
fileoffset += sizeof(uint32);
// All mipsize fields are at a file offset that's a multiple of 4, so
// there might be some padding after the actual data in this mip level.
uint32 mipsizepadded = (mipsize + 3) & ~uint32(3);
totalsize += mipsizepadded;
fileoffset += mipsizepadded;
}
uint8 *data = nullptr;
try
{
data = new uint8[totalsize];
}
catch (std::bad_alloc &)
{
throw love::Exception("Out of memory.");
}
// Reset the file offset to the start of the file's image data.
fileoffset = sizeof(KTXHeader) + header.bytesOfKeyValueData;
size_t dataoffset = 0;
// Copy each mipmap level of the image from the file to our block of memory.
for (int i = 0; i < (int) header.numberOfMipmapLevels; i++)
{
uint32 mipsize = *(uint32 *) (filebytes + fileoffset);
if (header.endianness == KTX_ENDIAN_REF_REV)
mipsize = swap32(mipsize);
fileoffset += sizeof(uint32);
uint32 mipsizepadded = (mipsize + 3) & ~uint32(3);
CompressedData::SubImage mip;
mip.width = (int) std::max(header.pixelWidth >> i, 1u);
mip.height = (int) std::max(header.pixelHeight >> i, 1u);
mip.size = mipsize;
memcpy(data + dataoffset, filebytes + fileoffset, mipsize);
mip.data = data + dataoffset;
fileoffset += mipsizepadded;
dataoffset += mipsizepadded;
images.push_back(mip);
}
dataSize = totalsize;
format = cformat;
return data;
}
} // magpie
} // image
} // love
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_IMAGE_MAGPIE_KTX_HANDLER_H
#define LOVE_IMAGE_MAGPIE_KTX_HANDLER_H
#include "common/config.h"
#include "CompressedFormatHandler.h"
namespace love
{
namespace image
{
namespace magpie
{
/**
* Handles KTX files with compressed image data inside.
**/
class KTXHandler : public CompressedFormatHandler
{
public:
virtual ~KTXHandler() {}
// Implements CompressedFormatHandler.
virtual bool canParse(const filesystem::FileData *data);
virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
}; // KTXHandler
} // magpie
} // image
} // love
#endif // LOVE_IMAGE_MAGPIE_KTX_HANDLER_H
@@ -0,0 +1,156 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "PKMHandler.h"
#include "common/int.h"
namespace love
{
namespace image
{
namespace magpie
{
namespace
{
// Big endian to host (and vice versa.)
inline uint16 swap16big(uint16 x)
{
#ifdef LOVE_BIG_ENDIAN
return x;
#else
return swap16(x);
#endif
}
static const uint8 pkmIdentifier[] = {'P','K','M',' '};
struct PKMHeader
{
uint8 identifier[4];
uint8 version[2];
uint16 textureFormatBig;
uint16 extendedWidthBig;
uint16 extendedHeightBig;
uint16 widthBig;
uint16 heightBig;
};
enum PKMTextureFormat
{
ETC1_RGB_NO_MIPMAPS = 0,
ETC2PACKAGE_RGB_NO_MIPMAPS,
ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD,
ETC2PACKAGE_RGBA_NO_MIPMAPS,
ETC2PACKAGE_RGBA1_NO_MIPMAPS,
ETC2PACKAGE_R_NO_MIPMAPS,
ETC2PACKAGE_RG_NO_MIPMAPS,
ETC2PACKAGE_R_SIGNED_NO_MIPMAPS,
ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS
};
CompressedData::Format convertFormat(uint16 texformat)
{
switch (texformat)
{
case ETC1_RGB_NO_MIPMAPS:
return CompressedData::FORMAT_ETC1;
default:
return CompressedData::FORMAT_UNKNOWN;
}
}
} // Anonymous namespace.
bool PKMHandler::canParse(const filesystem::FileData *data)
{
if (data->getSize() <= sizeof(PKMHeader))
return false;
const PKMHeader *header = (const PKMHeader *) data->getData();
if (memcmp(header->identifier, pkmIdentifier, 4) != 0)
return false;
// At the time of this writing, only v1.0 and v2.0 exist.
if ((header->version[0] != '2' && header->version[0] != '1') || header->version[1] != '0')
return false;
return true;
}
uint8 *PKMHandler::parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format)
{
if (!canParse(filedata))
throw love::Exception("Could not decode compressed data (not a PKM file?)");
PKMHeader header = *(const PKMHeader *) filedata->getData();
header.textureFormatBig = swap16big(header.textureFormatBig);
header.extendedWidthBig = swap16big(header.extendedWidthBig);
header.extendedHeightBig = swap16big(header.extendedHeightBig);
header.widthBig = swap16big(header.widthBig);
header.heightBig = swap16big(header.heightBig);
CompressedData::Format cformat = convertFormat(header.textureFormatBig);
if (cformat == CompressedData::FORMAT_UNKNOWN)
throw love::Exception("Could not parse PKM file: unsupported texture format.");
// The rest of the file after the header is all texture data.
size_t totalsize = filedata->getSize() - sizeof(PKMHeader);
uint8 *data = nullptr;
try
{
data = new uint8[totalsize];
}
catch (std::bad_alloc &)
{
throw love::Exception("Out of memory.");
}
// PKM files only store a single mipmap level.
memcpy(data, (uint8 *) filedata->getData() + sizeof(PKMHeader), totalsize);
CompressedData::SubImage mip;
// TODO: verify whether glCompressedTexImage works properly with the unpadded
// width and height values (extended == padded.)
mip.width = header.widthBig;
mip.height = header.heightBig;
mip.size = totalsize;
mip.data = data;
images.push_back(mip);
dataSize = totalsize;
format = cformat;
return data;
}
} // magpie
} // image
} // love
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_IMAGE_MAGPIE_PKM_HANDLER_H
#define LOVE_IMAGE_MAGPIE_PKM_HANDLER_H
#include "common/config.h"
#include "CompressedFormatHandler.h"
namespace love
{
namespace image
{
namespace magpie
{
/**
* Handles PKM files with compressed ETC data inside.
**/
class PKMHandler : public CompressedFormatHandler
{
public:
virtual ~PKMHandler() {}
// Implements CompressedFormatHandler.
virtual bool canParse(const filesystem::FileData *data);
virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
}; // PKMHandler
} // magpie
} // image
} // love
#endif // LOVE_IMAGE_MAGPIE_PKM_HANDLER_H
@@ -0,0 +1,385 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "PVRHandler.h"
#include "common/int.h"
// C++
#include <algorithm>
namespace love
{
namespace image
{
namespace magpie
{
namespace
{
// 'P' 'V' 'R' 3
static const uint32 PVRTEX3_IDENT = 0x03525650;
static const uint32 PVRTEX3_IDENT_REV = 0x50565203;
#pragma pack(push, 4)
struct PVRTexHeaderV3
{
uint32 version; /// Version of the file header, used to identify it.
uint32 flags; /// Various format flags.
uint64 pixelFormat; /// The pixel format, 8cc value storing the 4 channel identifiers and their respective sizes.
uint32 colorSpace; /// The Color Space of the texture, currently either linear RGB or sRGB.
uint32 channelType; /// Variable type that the channel is stored in. Supports signed/unsigned int/short/byte or float for now.
uint32 height; /// Height of the texture.
uint32 width; /// Width of the texture.
uint32 depth; /// Depth of the texture. (Z-slices)
uint32 numSurfaces; /// Number of members in a Texture Array.
uint32 numFaces; /// Number of faces in a Cube Map. Maybe be a value other than 6.
uint32 numMipmaps; /// Number of MIP Maps in the texture - NB: Includes top level.
uint32 metaDataSize; /// Size of the accompanying meta data.
};
#pragma pack(pop)
enum PVRV3PixelFormat
{
ePVRTPF_PVRTCI_2bpp_RGB = 0x00,
ePVRTPF_PVRTCI_2bpp_RGBA,
ePVRTPF_PVRTCI_4bpp_RGB,
ePVRTPF_PVRTCI_4bpp_RGBA,
ePVRTPF_PVRTCII_2bpp,
ePVRTPF_PVRTCII_4bpp,
ePVRTPF_ETC1 = 0x06,
ePVRTPF_DXT1,
ePVRTPF_DXT2,
ePVRTPF_DXT3,
ePVRTPF_DXT4,
ePVRTPF_DXT5,
ePVRTF_UNKNOWN_FORMAT = 0x7F
};
// 'P' 'V' 'R' '!'
static const uint32 PVRTEX2_IDENT = 0x21525650;
static const uint32 PVRTEX2_IDENT_REV = 0x50565221;
struct PVRTexHeaderV2
{
uint32 headerSize;
uint32 height;
uint32 width;
uint32 numMipmaps;
uint32 flags;
uint32 dataSize;
uint32 bpp;
uint32 bitmaskRed;
uint32 bitmaskGreen;
uint32 bitmaskBlue;
uint32 bitmaskAlpha;
uint32 pvrTag;
uint32 numSurfaces;
};
// The legacy V2 pixel types we support.
enum PVRPixelTypeV2
{
PixelTypePVRTC2 = 0x18,
PixelTypePVRTC4,
PixelTypePVRTCII2 = 0x1C,
PixelTypePVRTCII4,
PixelTypeDXT1 = 0x20,
PixelTypeDXT3 = 0x22,
PixelTypeDXT5 = 0x24,
PixelTypeETC1 = 0x36
};
// Convert a V2 header to V3.
void ConvertPVRHeader(PVRTexHeaderV2 header2, PVRTexHeaderV3 *header3)
{
// If the header's endianness doesn't match our own, we swap everything.
if (header2.pvrTag == PVRTEX2_IDENT_REV)
{
// All of the struct's members are uint32 values, so we can do this.
uint32 *headerArray = (uint32 *) &header2;
for (size_t i = 0; i < sizeof(PVRTexHeaderV2) / sizeof(uint32); i++)
headerArray[i] = swap32(headerArray[i]);
}
memset(header3, 0, sizeof(PVRTexHeaderV3));
header3->version = PVRTEX3_IDENT;
header3->height = header2.height;
header3->width = header2.width;
header3->depth = 1;
header3->numSurfaces = header2.numSurfaces;
header3->numFaces = 1;
header3->numMipmaps = header2.numMipmaps;
header3->metaDataSize = 0;
switch ((PVRPixelTypeV2) (header2.flags & 0xFF))
{
case PixelTypePVRTC2:
header3->pixelFormat = ePVRTPF_PVRTCI_2bpp_RGBA;
break;
case PixelTypePVRTC4:
header3->pixelFormat = ePVRTPF_PVRTCI_4bpp_RGBA;
break;
case PixelTypePVRTCII2:
header3->pixelFormat = ePVRTPF_PVRTCII_2bpp;
break;
case PixelTypePVRTCII4:
header3->pixelFormat = ePVRTPF_PVRTCII_4bpp;
break;
case PixelTypeDXT1:
header3->pixelFormat = ePVRTPF_DXT1;
break;
case PixelTypeDXT3:
header3->pixelFormat = ePVRTPF_DXT3;
break;
case PixelTypeDXT5:
header3->pixelFormat = ePVRTPF_DXT5;
break;
case PixelTypeETC1:
header3->pixelFormat = ePVRTPF_ETC1;
break;
default:
header3->pixelFormat = ePVRTF_UNKNOWN_FORMAT;
break;
}
}
CompressedData::Format convertFormat(PVRV3PixelFormat format)
{
switch (format)
{
case ePVRTPF_PVRTCI_2bpp_RGB:
return CompressedData::FORMAT_PVR1_RGB2;
case ePVRTPF_PVRTCI_2bpp_RGBA:
return CompressedData::FORMAT_PVR1_RGBA2;
case ePVRTPF_PVRTCI_4bpp_RGB:
return CompressedData::FORMAT_PVR1_RGB4;
case ePVRTPF_PVRTCI_4bpp_RGBA:
return CompressedData::FORMAT_PVR1_RGBA4;
case ePVRTPF_ETC1:
return CompressedData::FORMAT_ETC1;
case ePVRTPF_DXT1:
return CompressedData::FORMAT_DXT1;
case ePVRTPF_DXT3:
return CompressedData::FORMAT_DXT3;
case ePVRTPF_DXT5:
return CompressedData::FORMAT_DXT5;
default:
return CompressedData::FORMAT_UNKNOWN;
}
}
int getBitsPerPixel(uint64 pixelformat)
{
// Uncompressed formats have their bits per pixel stored in the high bits.
if ((pixelformat & 0xFFFFFFFF) != pixelformat)
{
const uint8 *charformat = (const uint8 *) &pixelformat;
return charformat[4] + charformat[5] + charformat[6] + charformat[7];
}
switch (pixelformat)
{
case ePVRTPF_PVRTCI_2bpp_RGB:
case ePVRTPF_PVRTCI_2bpp_RGBA:
case ePVRTPF_PVRTCII_2bpp:
return 2;
case ePVRTPF_PVRTCI_4bpp_RGB:
case ePVRTPF_PVRTCI_4bpp_RGBA:
case ePVRTPF_PVRTCII_4bpp:
case ePVRTPF_ETC1:
case ePVRTPF_DXT1:
return 4;
case ePVRTPF_DXT2:
case ePVRTPF_DXT3:
case ePVRTPF_DXT4:
case ePVRTPF_DXT5:
return 8;
default:
return 0;
}
}
void getFormatMinDimensions(uint64 pixelformat, int &minX, int &minY)
{
switch (pixelformat)
{
case ePVRTPF_PVRTCI_2bpp_RGB:
case ePVRTPF_PVRTCI_2bpp_RGBA:
minX = 16;
minY = 8;
break;
case ePVRTPF_PVRTCI_4bpp_RGB:
case ePVRTPF_PVRTCI_4bpp_RGBA:
minX = minY = 8;
break;
case ePVRTPF_PVRTCII_2bpp:
minX = 8;
minY = 4;
break;
case ePVRTPF_PVRTCII_4bpp:
minX = minY = 4;
break;
case ePVRTPF_DXT1:
case ePVRTPF_DXT2:
case ePVRTPF_DXT3:
case ePVRTPF_DXT4:
case ePVRTPF_DXT5:
case ePVRTPF_ETC1:
minX = minY = 4;
break;
default: // We don't handle all possible formats, but that's fine.
minX = minY = 1;
break;
}
}
size_t getMipLevelSize(const PVRTexHeaderV3 &header, int miplevel)
{
int smallestwidth = 1;
int smallestheight = 1;
getFormatMinDimensions(header.pixelFormat, smallestwidth, smallestheight);
int width = std::max((int) header.width >> miplevel, 1);
int height = std::max((int) header.height >> miplevel, 1);
int depth = std::max((int) header.depth >> miplevel, 1);
// Pad the dimensions.
width += (-width) % smallestwidth;
height += (-height) % smallestheight;
return getBitsPerPixel(header.pixelFormat) * width * height * depth / 8;
}
} // Anonymous namespace.
bool PVRHandler::canParse(const filesystem::FileData *data)
{
if (data->getSize() < sizeof(PVRTexHeaderV2) || data->getSize() < sizeof(PVRTexHeaderV3))
return false;
PVRTexHeaderV3 *header3 = (PVRTexHeaderV3 *) data->getData();
// Magic number (FourCC identifier.)
if (header3->version == PVRTEX3_IDENT || header3->version == PVRTEX3_IDENT_REV)
return true;
// Maybe it has a V2 header.
PVRTexHeaderV2 *header2 = (PVRTexHeaderV2 *) data->getData();
// FourCC identifier.
if (header2->pvrTag == PVRTEX2_IDENT || header2->pvrTag == PVRTEX2_IDENT_REV)
return true;
return false;
}
uint8 *PVRHandler::parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format)
{
if (!canParse(filedata))
throw love::Exception("Could not decode compressed data (not a PVR file?)");
PVRTexHeaderV3 header3 = *(PVRTexHeaderV3 *) filedata->getData();
// If the header isn't the V3 format, assume it's V2 and convert.
if (header3.version != PVRTEX3_IDENT && header3.version != PVRTEX3_IDENT_REV)
ConvertPVRHeader(*(PVRTexHeaderV2 *) filedata->getData(), &header3);
// If the header's endianness doesn't match our own, then we swap everything.
if (header3.version == PVRTEX3_IDENT_REV)
{
header3.version = PVRTEX3_IDENT;
header3.flags = swap32(header3.flags);
header3.pixelFormat = swap64(header3.pixelFormat);
header3.colorSpace = swap32(header3.colorSpace);
header3.channelType = swap32(header3.channelType);
header3.height = swap32(header3.height);
header3.width = swap32(header3.width);
header3.depth = swap32(header3.depth);
header3.numFaces = swap32(header3.numFaces);
header3.numMipmaps = swap32(header3.numMipmaps);
header3.metaDataSize = swap32(header3.metaDataSize);
}
if (header3.depth > 1)
throw love::Exception("Image depths greater than 1 in PVR files are unsupported.");
CompressedData::Format cformat = convertFormat((PVRV3PixelFormat) header3.pixelFormat);
if (cformat == CompressedData::FORMAT_UNKNOWN)
throw love::Exception("Could not parse PVR file: unsupported image format.");
size_t totalsize = 0;
uint8 *data = nullptr;
// Ignore faces and surfaces except the first ones (for now.)
for (int i = 0; i < (int) header3.numMipmaps; i++)
totalsize += getMipLevelSize(header3, i);
size_t fileoffset = sizeof(PVRTexHeaderV3) + header3.metaDataSize;
// Make sure the file actually holds this much data...
if (filedata->getSize() < fileoffset + totalsize)
throw love::Exception("Could not parse PVR file: invalid size calculation.");
try
{
data = new uint8[totalsize];
}
catch (std::bad_alloc &)
{
throw love::Exception("Out of memory.");
}
size_t curoffset = 0;
const uint8 *filebytes = (uint8 *) filedata->getData() + fileoffset;
for (int i = 0; i < (int) header3.numMipmaps; i++)
{
size_t mipsize = getMipLevelSize(header3, i);
if (curoffset + mipsize > totalsize)
break; // Just in case.
CompressedData::SubImage mip;
mip.width = std::max((int) header3.width >> i, 1);
mip.height = std::max((int) header3.height >> i, 1);
mip.size = mipsize;
memcpy(data + curoffset, filebytes + curoffset, mipsize);
mip.data = data + curoffset;
curoffset += mipsize;
images.push_back(mip);
}
dataSize = totalsize;
format = cformat;
return data;
}
} // magpie
} // image
} // love
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2006-2014 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
#ifndef LOVE_IMAGE_MAGPIE_PVR_HANDLER_H
#define LOVE_IMAGE_MAGPIE_PVR_HANDLER_H
// LOVE
#include "common/config.h"
#include "CompressedFormatHandler.h"
namespace love
{
namespace image
{
namespace magpie
{
class PVRHandler : public CompressedFormatHandler
{
public:
virtual ~PVRHandler() {}
// Implements CompressedFormatHandler.
virtual bool canParse(const filesystem::FileData *data);
virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
}; // PVRHandler
} // magpie
} // image
} // love
#endif // LOVE_IMAGE_MAGPIE_PVR_HANDLER_H
@@ -18,8 +18,10 @@
* 3. This notice may not be removed or altered from any source distribution. * 3. This notice may not be removed or altered from any source distribution.
**/ **/
// LOVE
#include "ddsHandler.h" #include "ddsHandler.h"
// C++
#include <algorithm> #include <algorithm>
namespace love namespace love
@@ -29,7 +31,7 @@ namespace image
namespace magpie namespace magpie
{ {
bool ddsHandler::canParse(const filesystem::FileData *data) bool DDSHandler::canParse(const filesystem::FileData *data)
{ {
std::string ext = data->getExtension(); std::string ext = data->getExtension();
std::transform(ext.begin(), ext.end(), ext.begin(), tolower); std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
@@ -40,14 +42,14 @@ bool ddsHandler::canParse(const filesystem::FileData *data)
return dds::isCompressedDDS(data->getData(), data->getSize()); return dds::isCompressedDDS(data->getData(), data->getSize());
} }
uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format) uint8 *DDSHandler::parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format)
{ {
if (!dds::isDDS(filedata->getData(), filedata->getSize())) if (!dds::isDDS(filedata->getData(), filedata->getSize()))
throw love::Exception("Could not decode compressed data (not a DDS file?)"); throw love::Exception("Could not decode compressed data (not a DDS file?)");
CompressedData::Format texformat = CompressedData::FORMAT_UNKNOWN; CompressedData::Format texformat = CompressedData::FORMAT_UNKNOWN;
uint8 *data = 0; uint8 *data = nullptr;
dataSize = 0; dataSize = 0;
images.clear(); images.clear();
@@ -107,7 +109,7 @@ uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector<CompressedD
return data; return data;
} }
CompressedData::Format ddsHandler::convertFormat(dds::Format ddsformat) CompressedData::Format DDSHandler::convertFormat(dds::Format ddsformat)
{ {
switch (ddsformat) switch (ddsformat)
{ {
@@ -128,8 +130,6 @@ CompressedData::Format ddsHandler::convertFormat(dds::Format ddsformat)
default: default:
return CompressedData::FORMAT_UNKNOWN; return CompressedData::FORMAT_UNKNOWN;
} }
return CompressedData::FORMAT_UNKNOWN;
} }
} // magpie } // magpie
+7 -24
View File
@@ -22,9 +22,7 @@
#define LOVE_IMAGE_MAGPIE_DDS_HANDLER_H #define LOVE_IMAGE_MAGPIE_DDS_HANDLER_H
// LOVE // LOVE
#include "common/EnumMap.h" #include "CompressedFormatHandler.h"
#include "filesystem/FileData.h"
#include "image/CompressedData.h"
// dds parser // dds parser
#include "ddsparse/ddsparse.h" #include "ddsparse/ddsparse.h"
@@ -42,36 +40,21 @@ namespace magpie
/** /**
* Interface between CompressedData and the ddsparse library. * Interface between CompressedData and the ddsparse library.
**/ **/
class ddsHandler class DDSHandler : public CompressedFormatHandler
{ {
public: public:
/** virtual ~DDSHandler() {}
* Determines whether a particular FileData can be parsed as CompressedData
* by this handler.
* @param data The data to parse.
**/
static bool canParse(const filesystem::FileData *data);
/** // Implements CompressedFormatHandler.
* Parses compressed image filedata into a list of sub-images and returns virtual bool canParse(const filesystem::FileData *data);
* a single block of memory containing all the images. virtual uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
*
* @param[in] filedata The data to parse.
* @param[out] image The list of sub-images generated. Byte data is a pointer
* to the returned data.
* @param[out] dataSize The total size in bytes of the returned data.
* @param[out] format The format of the Compressed Data.
*
* @return The single block of memory containing the parsed images.
**/
static uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
private: private:
static CompressedData::Format convertFormat(dds::Format ddsformat); static CompressedData::Format convertFormat(dds::Format ddsformat);
}; // ddsHandler }; // DDSHandler
} // magpie } // magpie
} // image } // image
+2 -2
View File
@@ -157,9 +157,9 @@ public:
virtual int getID() const = 0; virtual int getID() const = 0;
virtual bool isVibrationSupported() = 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 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(const char *in, Hat &out);
static bool getConstant(Hat in, const char *&out); static bool getConstant(Hat in, const char *&out);
+78 -42
View File
@@ -25,6 +25,11 @@
// C++ // C++
#include <algorithm> #include <algorithm>
#include <limits>
#ifndef SDL_TICKS_PASSED
#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
#endif
namespace love namespace love
{ {
@@ -34,9 +39,9 @@ namespace sdl
{ {
Joystick::Joystick(int id) Joystick::Joystick(int id)
: joyhandle(0) : joyhandle(nullptr)
, controller(0) , controller(nullptr)
, haptic(0) , haptic(nullptr)
, instanceid(-1) , instanceid(-1)
, id(id) , id(id)
, vibration() , vibration()
@@ -44,9 +49,9 @@ Joystick::Joystick(int id)
} }
Joystick::Joystick(int id, int joyindex) Joystick::Joystick(int id, int joyindex)
: joyhandle(0) : joyhandle(nullptr)
, controller(0) , controller(nullptr)
, haptic(0) , haptic(nullptr)
, instanceid(-1) , instanceid(-1)
, id(id) , id(id)
, vibration() , vibration()
@@ -95,10 +100,7 @@ bool Joystick::open(int deviceindex)
void Joystick::close() void Joystick::close()
{ {
if (haptic) if (haptic)
{
SDL_HapticRumbleStop(haptic);
SDL_HapticClose(haptic); SDL_HapticClose(haptic);
}
if (controller) if (controller)
SDL_GameControllerClose(controller); SDL_GameControllerClose(controller);
@@ -106,16 +108,16 @@ void Joystick::close()
if (joyhandle) if (joyhandle)
SDL_JoystickClose(joyhandle); SDL_JoystickClose(joyhandle);
joyhandle = 0; joyhandle = nullptr;
controller = 0; controller = nullptr;
haptic = 0; haptic = nullptr;
instanceid = -1; instanceid = -1;
vibration = Vibration(); vibration = Vibration();
} }
bool Joystick::isConnected() const bool Joystick::isConnected() const
{ {
return joyhandle != 0 && SDL_JoystickGetAttached(joyhandle); return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle);
} }
const char *Joystick::getName() const const char *Joystick::getName() const
@@ -209,7 +211,7 @@ bool Joystick::openGamepad(int deviceindex)
if (isGamepad()) if (isGamepad())
{ {
SDL_GameControllerClose(controller); SDL_GameControllerClose(controller);
controller = 0; controller = nullptr;
} }
controller = SDL_GameControllerOpen(deviceindex); controller = SDL_GameControllerOpen(deviceindex);
@@ -218,7 +220,7 @@ bool Joystick::openGamepad(int deviceindex)
bool Joystick::isGamepad() const bool Joystick::isGamepad() const
{ {
return controller != 0; return controller != nullptr;
} }
float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
@@ -289,13 +291,13 @@ bool Joystick::checkCreateHaptic()
if (haptic) if (haptic)
{ {
SDL_HapticClose(haptic); SDL_HapticClose(haptic);
haptic = 0; haptic = nullptr;
} }
haptic = SDL_HapticOpenFromJoystick(joyhandle); haptic = SDL_HapticOpenFromJoystick(joyhandle);
vibration = Vibration(); vibration = Vibration();
return haptic != 0; return haptic != nullptr;
} }
bool Joystick::isVibrationSupported() bool Joystick::isVibrationSupported()
@@ -312,8 +314,8 @@ bool Joystick::isVibrationSupported()
if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0) if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
return true; return true;
// Check SDL's simple rumble as a last resort. // Test for simple sine wave support as a last resort.
if (SDL_HapticRumbleSupported(haptic) == 1) if ((features & SDL_HAPTIC_SINE) != 0)
return true; return true;
return false; return false;
@@ -325,7 +327,7 @@ bool Joystick::runVibrationEffect()
{ {
if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0) 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; return true;
} }
@@ -336,17 +338,14 @@ bool Joystick::runVibrationEffect()
vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect); 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 true;
return false; 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); left = std::min(std::max(left, 0.0f), 1.0f);
right = std::min(std::max(right, 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()) if (!checkCreateHaptic())
return false; 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; bool success = false;
unsigned int features = SDL_HapticQuery(haptic); unsigned int features = SDL_HapticQuery(haptic);
int axes = SDL_HapticNumAxes(haptic);
if ((features & SDL_HAPTIC_LEFTRIGHT) != 0) if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
{ {
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect)); memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_LEFTRIGHT; 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.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX); vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
success = runVibrationEffect(); success = runVibrationEffect();
} }
// Some gamepad drivers only give support for controlling the motors via // Some gamepad drivers only give support for controlling individual motors
// a custom FF effect. // through a custom FF effect.
if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0) if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2)
{ {
// NOTE: this may cause issues with drivers which support custom effects // NOTE: this may cause issues with drivers which support custom effects
// but aren't similar to https://github.com/d235j/360Controller . // 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)); memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
vibration.effect.type = SDL_HAPTIC_CUSTOM; 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.channels = 2;
vibration.effect.custom.period = 10; vibration.effect.custom.period = 10;
vibration.effect.custom.samples = 2; vibration.effect.custom.samples = 2;
@@ -394,19 +401,36 @@ bool Joystick::setVibration(float left, float right)
success = runVibrationEffect(); success = runVibrationEffect();
} }
// Fall back to a simple rumble if all else fails. SDL's simple rumble API // Fall back to a simple sine wave if all else fails. This only supports a
// only supports a single strength value. // single strength value.
if (!success && SDL_HapticRumbleInit(haptic) == 0) 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); float strength = std::max(left, right);
int played = SDL_HapticRumblePlay(haptic, strength, SDL_HAPTIC_INFINITY); vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF);
success = (played == 0);
success = runVibrationEffect();
} }
if (success) if (success)
{ {
vibration.left = left; vibration.left = left;
vibration.right = right; 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; return success;
@@ -417,12 +441,7 @@ bool Joystick::setVibration()
bool success = true; bool success = true;
if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1) if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
{ success = (SDL_HapticStopEffect(haptic, vibration.id) == 0);
// 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);
}
if (success) if (success)
vibration.left = vibration.right = 0.0f; vibration.left = vibration.right = 0.0f;
@@ -430,8 +449,25 @@ bool Joystick::setVibration()
return success; 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; left = vibration.left;
right = vibration.right; right = vibration.right;
} }
+7 -3
View File
@@ -74,9 +74,9 @@ public:
int getID() const; int getID() const;
bool isVibrationSupported(); bool isVibrationSupported();
bool setVibration(float left, float right); bool setVibration(float left, float right, float duration = -1.0f);
bool setVibration(); 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(Hat in, Uint8 &out);
static bool getConstant(Uint8 in, Hat &out); static bool getConstant(Uint8 in, Hat &out);
@@ -111,8 +111,12 @@ private:
Uint16 data[4]; Uint16 data[4];
int id; int id;
Uint32 endtime;
Vibration() 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; } vibration;
@@ -77,7 +77,7 @@ const char *JoystickModule::getName() const
love::joystick::Joystick *JoystickModule::getJoystick(int joyindex) love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
{ {
if (joyindex < 0 || (size_t) joyindex >= activeSticks.size()) if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
return 0; return nullptr;
return activeSticks[joyindex]; return activeSticks[joyindex];
} }
@@ -107,13 +107,13 @@ love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
return activeSticks[i]; return activeSticks[i];
} }
return 0; return nullptr;
} }
love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex) love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
{ {
if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks()) if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
return 0; return nullptr;
std::string guidstr = getDeviceGUID(deviceindex); std::string guidstr = getDeviceGUID(deviceindex);
joystick::Joystick *joystick = 0; joystick::Joystick *joystick = 0;
@@ -140,7 +140,7 @@ love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
removeJoystick(joystick); removeJoystick(joystick);
if (!joystick->open(deviceindex)) if (!joystick->open(deviceindex))
return 0; return nullptr;
// Make sure multiple instances of the same physical joystick aren't added // Make sure multiple instances of the same physical joystick aren't added
// to the active list. // to the active list.
@@ -262,7 +262,7 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
if (status == 1) if (status == 1)
checkGamepads(guid); checkGamepads(guid);
return status >= 0; return status >= 0;
} }
Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput) 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_GameControllerAxis sdlaxis;
SDL_GameControllerButton sdlbutton; SDL_GameControllerButton sdlbutton;
const char *gpinputname = 0; const char *gpinputname = nullptr;
switch (gpinput.type) 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 // Big hack time: open the index as a game controller and compare
// the underlying joystick handle to the active stick's. // the underlying joystick handle to the active stick's.
SDL_GameController *ctrl = SDL_GameControllerOpen(d_index); SDL_GameController *ctrl = SDL_GameControllerOpen(d_index);
if (ctrl == NULL) if (ctrl == nullptr)
continue; continue;
SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl); SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl);
@@ -207,7 +207,8 @@ int w_Joystick_setVibration(lua_State *L)
{ {
float left = (float) luaL_checknumber(L, 2); float left = (float) luaL_checknumber(L, 2);
float right = (float) luaL_optnumber(L, 3, left); 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); luax_pushboolean(L, success);
@@ -28,7 +28,7 @@ namespace joystick
namespace sdl namespace sdl
{ {
static JoystickModule *instance = 0; static JoystickModule *instance = nullptr;
int w_getJoysticks(lua_State *L) int w_getJoysticks(lua_State *L)
{ {
@@ -194,7 +194,7 @@ static const lua_CFunction types[] =
extern "C" int luaopen_love_joystick(lua_State *L) extern "C" int luaopen_love_joystick(lua_State *L)
{ {
if (instance == 0) if (instance == nullptr)
{ {
EXCEPT_GUARD(instance = new JoystickModule();) EXCEPT_GUARD(instance = new JoystickModule();)
} }
@@ -117,7 +117,7 @@ int Mpg123Decoder::decode()
size += numbytes; size += numbytes;
long rate = 0; long rate = 0;
int encoding = 0; int encoding = 0;
int ret = mpg123_getformat(handle, &rate, &channels, &encoding); mpg123_getformat(handle, &rate, &channels, &encoding);
if (rate == 0) if (rate == 0)
rate = sampleRate; rate = sampleRate;
else else
@@ -126,7 +126,7 @@ int Mpg123Decoder::decode()
channels = MPG123_STEREO; channels = MPG123_STEREO;
if (encoding == 0) if (encoding == 0)
encoding = MPG123_ENC_SIGNED_16; encoding = MPG123_ENC_SIGNED_16;
ret = mpg123_format(handle, rate, channels, encoding); int ret = mpg123_format(handle, rate, channels, encoding);
if (ret != MPG123_OK) if (ret != MPG123_OK)
throw love::Exception("Could not set output format."); throw love::Exception("Could not set output format.");
} }
+23 -29
View File
@@ -27,8 +27,9 @@
#elif defined(LOVE_ANDROID) #elif defined(LOVE_ANDROID)
#include "common/android.h" #include "common/android.h"
#elif defined(LOVE_LINUX) #elif defined(LOVE_LINUX)
#include <stdlib.h> #include <spawn.h>
#include <unistd.h> //#include <stdlib.h>
//#include <unistd.h>
#include <sys/wait.h> #include <sys/wait.h>
#elif defined(LOVE_WINDOWS) #elif defined(LOVE_WINDOWS)
#include "common/utf8.h" #include "common/utf8.h"
@@ -57,12 +58,17 @@ std::string System::getOS() const
#endif #endif
} }
extern "C"
{
extern char **environ; // The environment, always available
}
bool System::openURL(const std::string &url) const bool System::openURL(const std::string &url) const
{ {
bool success = false;
#if defined(LOVE_MACOSX) #if defined(LOVE_MACOSX)
bool success = false;
// We could be lazy and use system("open " + url), but this is safer. // We could be lazy and use system("open " + url), but this is safer.
CFURLRef cfurl = CFURLCreateWithBytes(nullptr, CFURLRef cfurl = CFURLCreateWithBytes(nullptr,
(const UInt8 *) url.c_str(), (const UInt8 *) url.c_str(),
@@ -72,38 +78,28 @@ bool System::openURL(const std::string &url) const
success = LSOpenCFURLRef(cfurl, nullptr) == noErr; success = LSOpenCFURLRef(cfurl, nullptr) == noErr;
CFRelease(cfurl); CFRelease(cfurl);
return success;
#elif defined(LOVE_ANDROID) #elif defined(LOVE_ANDROID)
success = love::android::openURL (url); return love::android::openURL (url);
#elif defined(LOVE_LINUX) #elif defined(LOVE_LINUX)
// Spawn a child process, which we'll replace with xdg-open. pid_t pid;
pid_t pid = vfork(); const char *argv[] = {"xdg-open", url.c_str(), nullptr};
if (pid == 0) // Child process. // Note: at the moment this process inherits our file descriptors.
{ // Note: the below const_cast is really ugly as well.
// Replace the child process with xdg-open and pass in the URL. if (posix_spawnp(&pid, "xdg-open", nullptr, nullptr, const_cast<char **>(argv), environ) != 0)
execlp("xdg-open", "xdg-open", url.c_str(), nullptr); return false;
// exec will only return if it errored, so we should exit with non-zero. // Wait for xdg-open to complete (or fail.)
_exit(1); int status = 0;
} if (waitpid(pid, &status, 0) == pid)
else if (pid > 0) // Parent process. return (status == 0);
{
// Wait for xdg-open to complete (or fail.)
int status = 0;
if (waitpid(pid, &status, 0) == pid)
success = (status == 0);
else
success = false;
}
else else
{ return false;
// vfork() failed.
success = false;
}
#elif defined(LOVE_WINDOWS) #elif defined(LOVE_WINDOWS)
@@ -117,11 +113,9 @@ bool System::openURL(const std::string &url) const
nullptr, nullptr,
SW_SHOW); SW_SHOW);
success = (int) result > 32; return (int) result > 32;
#endif #endif
return success;
} }
bool System::getConstant(const char *in, System::PowerState &out) bool System::getConstant(const char *in, System::PowerState &out)
@@ -38,6 +38,7 @@ LuaThread::LuaThread(const std::string &name, love::Data *code)
, nargs(0) , nargs(0)
{ {
code->retain(); code->retain();
threadName = name;
} }
LuaThread::~LuaThread() LuaThread::~LuaThread()
+1 -1
View File
@@ -51,7 +51,7 @@ bool Thread::start()
return false; return false;
if (thread) // Clean old handle up if (thread) // Clean old handle up
SDL_WaitThread(thread, 0); SDL_WaitThread(thread, 0);
thread = SDL_CreateThread(thread_runner, NULL, this); thread = SDL_CreateThread(thread_runner, t->getThreadName(), this);
running = (thread != 0); running = (thread != 0);
return running; return running;
} }
+5
View File
@@ -99,5 +99,10 @@ bool Threadable::isRunning() const
return owner->isRunning(); return owner->isRunning();
} }
const char *Threadable::getThreadName() const
{
return threadName.empty() ? nullptr : threadName.c_str();
}
} // thread } // thread
} // love } // love
+8
View File
@@ -21,9 +21,13 @@
#ifndef LOVE_THREAD_THREADS_H #ifndef LOVE_THREAD_THREADS_H
#define LOVE_THREAD_THREADS_H #define LOVE_THREAD_THREADS_H
// LOVE
#include "common/config.h" #include "common/config.h"
#include "Thread.h" #include "Thread.h"
// C++
#include <string>
namespace love namespace love
{ {
namespace thread namespace thread
@@ -83,9 +87,13 @@ public:
bool start(); bool start();
void wait(); void wait();
bool isRunning() const; bool isRunning() const;
const char *getThreadName() const;
protected: protected:
Thread *owner; Thread *owner;
std::string threadName;
}; };
Mutex *newMutex(); Mutex *newMutex();
+18 -7
View File
@@ -269,11 +269,14 @@ bool Window::setContext(int fsaa, bool vsync, bool sRGB)
if (!context) if (!context)
{ {
int flags = 0; int flags = 0;
int profilemask = 0;
SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags); SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags);
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profilemask);
if (flags & SDL_GL_CONTEXT_DEBUG_FLAG) if (flags & SDL_GL_CONTEXT_DEBUG_FLAG)
{ {
profilemask &= ~SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
context = SDL_GL_CreateContext(window); context = SDL_GL_CreateContext(window);
} }
} }
@@ -315,6 +318,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
// FSAA. // FSAA.
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0);
@@ -331,19 +335,26 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
int contextprofile = 0; int contextprofile = 0;
bool tryES2 = false; bool tryGLES = false;
const char *driver = SDL_GetCurrentVideoDriver(); const char *driver = SDL_GetCurrentVideoDriver();
const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt"};
// We always want to use OpenGL ES on certain video backends. // We always want to use OpenGL ES on certain video backends.
if (driver && (strstr(driver, "RPI") || strstr(driver, "Android") || strstr(driver, "uikit"))) for (size_t i = 0; i < sizeof(glesdrivers) / sizeof(glesdrivers[0]); i++)
tryES2 = true; {
if (driver && strstr(driver, glesdrivers[i]))
{
tryGLES = true;
break;
}
}
const char *hint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES"); const char *hint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
if (hint) if (hint)
tryES2 = (*hint) != '0'; tryGLES = (*hint) != '0';
if (tryES2) if (tryGLES)
{ {
// Use OpenGL ES 2+. // Use OpenGL ES 2+.
contextprofile = SDL_GL_CONTEXT_PROFILE_ES; contextprofile = SDL_GL_CONTEXT_PROFILE_ES;
@@ -363,7 +374,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
if (debugenv && *debugenv == '1') if (debugenv && *debugenv == '1')
{ {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
if (!tryES2) if (!tryGLES)
contextprofile |= SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; contextprofile |= SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
} }
else else
+1 -1
View File
@@ -526,7 +526,7 @@ function love.run()
-- Call update and draw -- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
if love.window and love.graphics and love.window.isCreated() then if love.window and love.graphics and love.graphics.isActive() then
love.graphics.clear() love.graphics.clear()
love.graphics.origin() love.graphics.origin()
if love.draw then love.draw() end if love.draw then love.draw() end
+2 -2
View File
@@ -943,8 +943,8 @@ const unsigned char boot_lua[] =
0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x0a, 0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x0a,
0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x61, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x61,
0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x20, 0x61,
0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x69, 0x73, 0x43, 0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x69,
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x73, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63,
0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a, 0x6c, 0x65, 0x61, 0x72, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6f,
+28 -7
View File
@@ -1317,9 +1317,9 @@ uniform mat4 TransformMatrix;
uniform mat4 ProjectionMatrix; uniform mat4 ProjectionMatrix;
uniform mat4 TransformProjectionMatrix; uniform mat4 TransformProjectionMatrix;
// uniform mat4 NormalMatrix; // uniform mat4 NormalMatrix;
uniform float love_PointSize;
uniform sampler2D _tex0_; uniform sampler2D _tex0_;
uniform vec4 love_ScreenSize;]] uniform mediump vec4 love_ScreenSize;
uniform mediump float love_PointSize;]]
GLSL.VERTEX = { GLSL.VERTEX = {
HEADER = [[ HEADER = [[
@@ -1357,7 +1357,7 @@ attribute vec4 VertexPosition;
attribute vec4 VertexTexCoord; attribute vec4 VertexTexCoord;
attribute vec4 VertexColor; attribute vec4 VertexColor;
varying vec4 VaryingTexCoord; varying mediump vec4 VaryingTexCoord;
varying lowp vec4 VaryingColor; varying lowp vec4 VaryingColor;
//#if defined(GL_EXT_draw_instanced) //#if defined(GL_EXT_draw_instanced)
@@ -1414,18 +1414,36 @@ void main() {
precision mediump float; precision mediump float;
varying vec4 VaryingTexCoord; varying mediump vec4 VaryingTexCoord;
varying lowp vec4 VaryingColor;]], varying lowp vec4 VaryingColor;]],
FOOTER = GLSL.PIXEL.FOOTER, FOOTER = GLSL.PIXEL.FOOTER,
FOOTER_MULTI_CANVAS = GLSL.PIXEL.FOOTER_MULTI_CANVAS, FOOTER_MULTI_CANVAS = GLSL.PIXEL.FOOTER_MULTI_CANVAS,
} }
-- Unfortunately, GLSL 1.20 can't deal with precision qualifier statements.
local function stripPrecision(code)
-- Remove "precision mediump float;", etc.
code = code:gsub("precision(%s+)lowp(%s+)(%w+)(%s*);", "")
code = code:gsub("precision(%s+)mediump(%s+)(%w+)(%s*);", "")
code = code:gsub("precision(%s+)highp(%s+)(%w+)(%s*);", "")
-- Remove all remaining "lowp", "mediump", "highp".
code = code:gsub("(%W+)lowp(%s+)", "%1"):gsub("^lowp(%s+)", "")
code = code:gsub("(%W+)mediump(%s+)", "%1"):gsub("^mediump(%s+)", "")
code = code:gsub("(%W+)highp(%s+)", "%1"):gsub("^highp(%s+)", "")
return code
end
local function createVertexCode(vertexcode, lang) local function createVertexCode(vertexcode, lang)
if lang == GLSL then
vertexcode = stripPrecision(vertexcode)
end
local vertexcodes = { local vertexcodes = {
lang.VERSION, lang.VERSION,
lang.SYNTAX, lang.VERTEX.HEADER, lang.UNIFORMS, lang.SYNTAX, lang.VERTEX.HEADER, lang.UNIFORMS,
"#line 1", lang == GLSLES and "#line 1" or "#line 0", -- specification differences for #line...
vertexcode, vertexcode,
lang.VERTEX.FOOTER, lang.VERTEX.FOOTER,
} }
@@ -1433,10 +1451,13 @@ varying lowp vec4 VaryingColor;]],
end end
local function createPixelCode(pixelcode, lang, is_multicanvas) local function createPixelCode(pixelcode, lang, is_multicanvas)
if lang == GLSL then
pixelcode = stripPrecision(pixelcode)
end
local pixelcodes = { local pixelcodes = {
lang.VERSION, lang.VERSION,
lang.SYNTAX, lang.PIXEL.HEADER, lang.UNIFORMS, lang.SYNTAX, lang.PIXEL.HEADER, lang.UNIFORMS,
"#line 1", lang == GLSLES and "#line 1" or "#line 0",
pixelcode, pixelcode,
is_multicanvas and lang.PIXEL.FOOTER_MULTI_CANVAS or lang.PIXEL.FOOTER, is_multicanvas and lang.PIXEL.FOOTER_MULTI_CANVAS or lang.PIXEL.FOOTER,
} }
@@ -1539,7 +1560,7 @@ vec4 position(mat4 transform_proj, vec4 vertpos) {
return transform_proj * vertpos; return transform_proj * vertpos;
}]], }]],
pixel = [[ pixel = [[
vec4 effect(vec4 vcolor, Image texture, vec2 texcoord, vec2 pixcoord) { lowp vec4 effect(lowp vec4 vcolor, Image texture, vec2 texcoord, vec2 pixcoord) {
return Texel(texture, texcoord) * vcolor; return Texel(texture, texcoord) * vcolor;
}]] }]]
} }
+76 -14
View File
@@ -6312,12 +6312,14 @@ const unsigned char graphics_lua[] =
0x69, 0x78, 0x3b, 0x0a, 0x69, 0x78, 0x3b, 0x0a,
0x2f, 0x2f, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x4e, 0x6f, 0x2f, 0x2f, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x61, 0x74, 0x34, 0x20, 0x4e, 0x6f,
0x72, 0x6d, 0x61, 0x6c, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3b, 0x0a, 0x72, 0x6d, 0x61, 0x6c, 0x4d, 0x61, 0x74, 0x72, 0x69, 0x78, 0x3b, 0x0a,
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65,
0x5f, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x0a,
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x72, 0x32, 0x44, 0x20,
0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x3b, 0x0a, 0x5f, 0x74, 0x65, 0x78, 0x30, 0x5f, 0x3b, 0x0a,
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65,
0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x5d, 0x5d, 0x0a, 0x63, 0x34, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, 0x65,
0x3b, 0x0a,
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x66, 0x6c,
0x6f, 0x61, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x5f, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x53, 0x69, 0x7a, 0x65,
0x3b, 0x5d, 0x5d, 0x0a,
0x09, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x09, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
0x09, 0x09, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, 0x09, 0x09, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a,
0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x0a,
@@ -6373,8 +6375,9 @@ const unsigned char graphics_lua[] =
0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x74, 0x65, 0x78, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a,
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x65, 0x72, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x65, 0x72,
0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a,
0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65,
0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64,
0x3b, 0x0a,
0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20,
0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x0a,
0x2f, 0x2f, 0x23, 0x69, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x28, 0x47, 0x4c, 0x5f, 0x45, 0x2f, 0x2f, 0x23, 0x69, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x28, 0x47, 0x4c, 0x5f, 0x45,
@@ -6470,8 +6473,9 @@ const unsigned char graphics_lua[] =
0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x0a, 0x23, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x0a,
0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20,
0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3b, 0x0a, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3b, 0x0a,
0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65,
0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a, 0x63, 0x34, 0x20, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64,
0x3b, 0x0a,
0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20,
0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x5d, 0x5d, 0x2c, 0x0a, 0x56, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x5d, 0x5d, 0x2c, 0x0a,
0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x50, 0x49, 0x09, 0x09, 0x46, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x20, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x50, 0x49,
@@ -6481,16 +6485,65 @@ const unsigned char graphics_lua[] =
0x4f, 0x4f, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x43, 0x41, 0x4e, 0x56, 0x41, 0x53, 0x4f, 0x4f, 0x54, 0x45, 0x52, 0x5f, 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x43, 0x41, 0x4e, 0x56, 0x41, 0x53,
0x2c, 0x0a, 0x2c, 0x0a,
0x09, 0x7d, 0x0a, 0x09, 0x7d, 0x0a,
0x09, 0x2d, 0x2d, 0x20, 0x55, 0x6e, 0x66, 0x6f, 0x72, 0x74, 0x75, 0x6e, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x2c,
0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20, 0x31, 0x2e, 0x32, 0x30, 0x20, 0x63, 0x61, 0x6e, 0x27, 0x74, 0x20, 0x64,
0x65, 0x61, 0x6c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e,
0x20, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x73, 0x2e, 0x0a,
0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x74,
0x72, 0x69, 0x70, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69,
0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
0x3b, 0x22, 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62,
0x28, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x6c, 0x6f,
0x77, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x28, 0x25, 0x77, 0x2b, 0x29, 0x28, 0x25, 0x73, 0x2a, 0x29, 0x3b,
0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62,
0x28, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x6d, 0x65,
0x64, 0x69, 0x75, 0x6d, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x28, 0x25, 0x77, 0x2b, 0x29, 0x28, 0x25, 0x73,
0x2a, 0x29, 0x3b, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62,
0x28, 0x22, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x68, 0x69,
0x67, 0x68, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x28, 0x25, 0x77, 0x2b, 0x29, 0x28, 0x25, 0x73, 0x2a, 0x29,
0x3b, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x72, 0x65,
0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x22, 0x6c, 0x6f, 0x77, 0x70, 0x22, 0x2c, 0x20, 0x22, 0x6d,
0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x22, 0x2c, 0x20, 0x22, 0x68, 0x69, 0x67, 0x68, 0x70, 0x22, 0x2e, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62,
0x28, 0x22, 0x28, 0x25, 0x57, 0x2b, 0x29, 0x6c, 0x6f, 0x77, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c,
0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x6c, 0x6f, 0x77, 0x70,
0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62,
0x28, 0x22, 0x28, 0x25, 0x57, 0x2b, 0x29, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x28, 0x25, 0x73, 0x2b,
0x29, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x6d,
0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62,
0x28, 0x22, 0x28, 0x25, 0x57, 0x2b, 0x29, 0x68, 0x69, 0x67, 0x68, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22,
0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5e, 0x68, 0x69, 0x67,
0x68, 0x70, 0x28, 0x25, 0x73, 0x2b, 0x29, 0x22, 0x2c, 0x20, 0x22, 0x22, 0x29, 0x0a,
0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x72,
0x65, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x76, 0x65, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, 0x74, 0x65, 0x78, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x76, 0x65, 0x72,
0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x29, 0x0a, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x29, 0x0a,
0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20,
0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x74,
0x72, 0x69, 0x70, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x76, 0x65, 0x72, 0x74, 0x65,
0x78, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65,
0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x6c, 0x61, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x6c, 0x61,
0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20,
0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x45, 0x53, 0x20,
0x61, 0x6e, 0x64, 0x20, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x22,
0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x30, 0x22, 0x2c, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63,
0x65, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x2e, 0x2e, 0x2e, 0x0a,
0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x76, 0x65, 0x72, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x46, 0x4f, 0x4f, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x46, 0x4f, 0x4f,
0x54, 0x45, 0x52, 0x2c, 0x0a, 0x54, 0x45, 0x52, 0x2c, 0x0a,
@@ -6503,13 +6556,21 @@ const unsigned char graphics_lua[] =
0x65, 0x61, 0x74, 0x65, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65, 0x65, 0x61, 0x74, 0x65, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x43, 0x6f, 0x64, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65,
0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2c, 0x20, 0x69, 0x73, 0x5f, 0x6d, 0x75,
0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x29, 0x0a, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x29, 0x0a,
0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x20,
0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72,
0x69, 0x70, 0x50, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63,
0x6f, 0x64, 0x65, 0x29, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x73,
0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x6c, 0x61, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x53, 0x59, 0x4e, 0x54, 0x41, 0x58, 0x2c, 0x20, 0x6c, 0x61,
0x6e, 0x67, 0x2e, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 0x6c, 0x6e, 0x67, 0x2e, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x2c, 0x20, 0x6c,
0x61, 0x6e, 0x67, 0x2e, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a, 0x61, 0x6e, 0x67, 0x2e, 0x55, 0x4e, 0x49, 0x46, 0x4f, 0x52, 0x4d, 0x53, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x6e, 0x67, 0x20, 0x3d, 0x3d, 0x20, 0x47, 0x4c, 0x53, 0x4c, 0x45, 0x53, 0x20,
0x61, 0x6e, 0x64, 0x20, 0x22, 0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x22,
0x23, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x30, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a, 0x09, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x63, 0x6f, 0x64, 0x65, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20, 0x09, 0x09, 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x63, 0x61, 0x6e, 0x76, 0x61, 0x73, 0x20,
0x61, 0x6e, 0x64, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f, 0x61, 0x6e, 0x64, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x50, 0x49, 0x58, 0x45, 0x4c, 0x2e, 0x46, 0x4f, 0x4f,
@@ -6725,10 +6786,11 @@ const unsigned char graphics_lua[] =
0x70, 0x72, 0x6f, 0x6a, 0x20, 0x2a, 0x20, 0x76, 0x65, 0x72, 0x74, 0x70, 0x6f, 0x73, 0x3b, 0x09, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x20, 0x2a, 0x20, 0x76, 0x65, 0x72, 0x74, 0x70, 0x6f, 0x73, 0x3b, 0x09, 0x0a,
0x7d, 0x5d, 0x5d, 0x2c, 0x0a, 0x7d, 0x5d, 0x5d, 0x2c, 0x0a,
0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a, 0x09, 0x09, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x3d, 0x20, 0x5b, 0x5b, 0x0a,
0x76, 0x65, 0x63, 0x34, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x6c,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x49,
0x72, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32,
0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 0x20, 0x70, 0x69,
0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x7b, 0x0a,
0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x54, 0x65, 0x78, 0x65, 0x6c, 0x28, 0x74, 0x65, 0x78, 0x74, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x54, 0x65, 0x78, 0x65, 0x6c, 0x28, 0x74, 0x65, 0x78, 0x74,
0x75, 0x72, 0x65, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x76, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x74, 0x65, 0x78, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x29, 0x20, 0x2a, 0x20, 0x76,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x09, 0x0a, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3b, 0x09, 0x0a,