mirror of
https://github.com/love2d/love-android.git
synced 2026-08-13 00:50:56 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70cb26bb2a | |||
| 8cb7bac09f | |||
| 49265f0596 |
+2
-2
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.love2d.android"
|
||||
android:versionCode="12"
|
||||
android:versionName="0.9.1"
|
||||
android:versionCode="13"
|
||||
android:versionName="0.9.1a"
|
||||
android:installLocation="auto" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
@@ -41,9 +41,13 @@ SDL_EGL_MakeCurrent_impl(Android)
|
||||
void
|
||||
Android_GLES_SwapWindow(_THIS, SDL_Window * window)
|
||||
{
|
||||
/* FIXME: These two functions were in the Java code, do we really need them? */
|
||||
_this->egl_data->eglWaitNative(EGL_CORE_NATIVE_ENGINE);
|
||||
_this->egl_data->eglWaitGL();
|
||||
/* The following two calls existed in the original Java code
|
||||
* If you happen to have a device that's affected by their removal,
|
||||
* please report to Bugzilla. -- Gabriel
|
||||
*/
|
||||
|
||||
/*_this->egl_data->eglWaitNative(EGL_CORE_NATIVE_ENGINE);
|
||||
_this->egl_data->eglWaitGL();*/
|
||||
SDL_EGL_SwapBuffers(_this, ((SDL_WindowData *) window->driverdata)->egl_surface);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,15 @@
|
||||
#ifndef 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.
|
||||
// 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.)
|
||||
@@ -47,6 +56,27 @@ typedef uint32_t uint32;
|
||||
typedef int64_t int64;
|
||||
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
|
||||
|
||||
#endif // LOVE_INT_H
|
||||
|
||||
@@ -37,6 +37,7 @@ Audio::PoolThread::PoolThread(Pool *pool)
|
||||
, finish(false)
|
||||
{
|
||||
mutex = thread::newMutex();
|
||||
threadName = "AudioPool";
|
||||
}
|
||||
|
||||
Audio::PoolThread::~PoolThread()
|
||||
|
||||
@@ -255,10 +255,7 @@ bool Source::update()
|
||||
|
||||
alGetSourcef(source, AL_SAMPLE_OFFSET, &curOffsetSamples);
|
||||
|
||||
ALint b;
|
||||
alGetSourcei(source, AL_BUFFER, &b);
|
||||
int freq;
|
||||
alGetBufferi(b, AL_FREQUENCY, &freq);
|
||||
int freq = decoder->getSampleRate();
|
||||
curOffsetSecs = curOffsetSamples / freq;
|
||||
|
||||
// Get a free buffer.
|
||||
@@ -334,11 +331,7 @@ void Source::seekAtomic(float offset, void *unit)
|
||||
if (type == TYPE_STREAM)
|
||||
{
|
||||
offsetSamples = offset;
|
||||
ALint buffer;
|
||||
alGetSourcei(source, AL_BUFFER, &buffer);
|
||||
int freq;
|
||||
alGetBufferi(buffer, AL_FREQUENCY, &freq);
|
||||
offset /= freq;
|
||||
offset /= decoder->getSampleRate();
|
||||
offsetSeconds = offset;
|
||||
decoder->seek(offset);
|
||||
}
|
||||
@@ -353,11 +346,7 @@ void Source::seekAtomic(float offset, void *unit)
|
||||
{
|
||||
offsetSeconds = offset;
|
||||
decoder->seek(offset);
|
||||
ALint buffer;
|
||||
alGetSourcei(source, AL_BUFFER, &buffer);
|
||||
int freq;
|
||||
alGetBufferi(buffer, AL_FREQUENCY, &freq);
|
||||
offsetSamples = offset*freq;
|
||||
offsetSamples = offset * decoder->getSampleRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -399,11 +388,7 @@ float Source::tellAtomic(void *unit) const
|
||||
default:
|
||||
{
|
||||
alGetSourcef(source, AL_SAMPLE_OFFSET, &offset);
|
||||
ALint buffer;
|
||||
alGetSourcei(source, AL_BUFFER, &buffer);
|
||||
int freq;
|
||||
alGetBufferi(buffer, AL_FREQUENCY, &freq);
|
||||
offset /= freq;
|
||||
offset /= decoder->getSampleRate();
|
||||
if (type == TYPE_STREAM) offset += offsetSeconds;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -59,6 +59,34 @@ static void windowToPixelCoords(int *x, int *y)
|
||||
#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
|
||||
{
|
||||
@@ -69,10 +97,13 @@ Event::Event()
|
||||
{
|
||||
if (SDL_InitSubSystem(SDL_INIT_EVENTS) < 0)
|
||||
throw love::Exception("%s", SDL_GetError());
|
||||
|
||||
SDL_AddEventWatch(watchAppEvents, this);
|
||||
}
|
||||
|
||||
Event::~Event()
|
||||
{
|
||||
SDL_DelEventWatch(watchAppEvents, this);
|
||||
SDL_QuitSubSystem(SDL_INIT_EVENTS);
|
||||
}
|
||||
|
||||
@@ -260,6 +291,7 @@ Message *Event::convert(const SDL_Event &e) const
|
||||
SDL_free(e.drop.file);
|
||||
break;
|
||||
case SDL_QUIT:
|
||||
case SDL_APP_TERMINATING:
|
||||
msg = new Message("quit");
|
||||
break;
|
||||
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).
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -349,16 +354,36 @@ bool Filesystem::setupWriteDirectory()
|
||||
if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty())
|
||||
return false;
|
||||
|
||||
// Set the appdata folder as writable directory.
|
||||
// We need to make sure the write directory is created. To do that, we also
|
||||
// need to make sure all its parent directories are also created.
|
||||
std::string temp_writedir = getDriveRoot(save_path_full);
|
||||
std::string temp_createdir = skipDriveRoot(save_path_full);
|
||||
|
||||
// On some sandboxed platforms, physfs will break when its write directory
|
||||
// is the root of the drive and it tries to create a folder (even if the
|
||||
// folder's path is in a writable location.) If the user's home folder is
|
||||
// in the save path, we'll try starting from there instead.
|
||||
if (save_path_full.find(getUserDirectory()) == 0)
|
||||
{
|
||||
temp_writedir = getUserDirectory();
|
||||
temp_createdir = save_path_full.substr(getUserDirectory().length());
|
||||
|
||||
// Strip leading '/' characters from the path we want to create.
|
||||
size_t startpos = temp_createdir.find_first_not_of('/');
|
||||
if (startpos != std::string::npos)
|
||||
temp_createdir = temp_createdir.substr(startpos);
|
||||
}
|
||||
|
||||
// Set either '/' or the user's home as a writable directory.
|
||||
// (We must create the save folder before mounting it).
|
||||
if (!PHYSFS_setWriteDir(getDriveRoot(save_path_full).c_str()))
|
||||
if (!PHYSFS_setWriteDir(temp_writedir.c_str()))
|
||||
return false;
|
||||
|
||||
// Create the save folder. (We're now "at" %APPDATA%).
|
||||
if (!createDirectory(skipDriveRoot(save_path_full).c_str()))
|
||||
// Create the save folder. (We're now "at" either '/' or the user's home).
|
||||
if (!createDirectory(temp_createdir.c_str()))
|
||||
{
|
||||
// Clear the write directory in case of error.
|
||||
PHYSFS_setWriteDir(0);
|
||||
PHYSFS_setWriteDir(nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -369,7 +394,7 @@ bool Filesystem::setupWriteDirectory()
|
||||
// Add the directory. (Will not be readded if already present).
|
||||
if (!PHYSFS_addToSearchPath(save_path_full.c_str(), 0))
|
||||
{
|
||||
PHYSFS_setWriteDir(0); // Clear the write directory in case of error.
|
||||
PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -505,12 +530,12 @@ const char *Filesystem::getWorkingDirectory()
|
||||
return cwd.c_str();
|
||||
}
|
||||
|
||||
const char *Filesystem::getUserDirectory()
|
||||
std::string Filesystem::getUserDirectory()
|
||||
{
|
||||
return PHYSFS_getUserDir();
|
||||
return std::string(PHYSFS_getUserDir());
|
||||
}
|
||||
|
||||
const char *Filesystem::getAppdataDirectory()
|
||||
std::string Filesystem::getAppdataDirectory()
|
||||
{
|
||||
#ifdef LOVE_WINDOWS
|
||||
if (appdata.empty())
|
||||
@@ -519,7 +544,7 @@ const char *Filesystem::getAppdataDirectory()
|
||||
appdata = to_utf8(w_appdata);
|
||||
replace_char(appdata, '\\', '/');
|
||||
}
|
||||
return appdata.c_str();
|
||||
return appdata;
|
||||
#elif defined(LOVE_MACOSX)
|
||||
if (appdata.empty())
|
||||
{
|
||||
@@ -527,7 +552,7 @@ const char *Filesystem::getAppdataDirectory()
|
||||
udir.append("/Library/Application Support");
|
||||
appdata = udir;
|
||||
}
|
||||
return appdata.c_str();
|
||||
return appdata;
|
||||
#elif defined(LOVE_LINUX)
|
||||
if (appdata.empty())
|
||||
{
|
||||
@@ -537,7 +562,7 @@ const char *Filesystem::getAppdataDirectory()
|
||||
else
|
||||
appdata = xdgdatahome;
|
||||
}
|
||||
return appdata.c_str();
|
||||
return appdata;
|
||||
#else
|
||||
return getUserDirectory();
|
||||
#endif
|
||||
|
||||
@@ -145,14 +145,14 @@ public:
|
||||
/**
|
||||
* Gets the user home directory.
|
||||
**/
|
||||
const char *getUserDirectory();
|
||||
std::string getUserDirectory();
|
||||
|
||||
/**
|
||||
* Gets the APPDATA directory. On Windows, this is the folder
|
||||
* in the %APPDATA% enviroment variable. On Linux, this is the
|
||||
* user home folder.
|
||||
**/
|
||||
const char *getAppdataDirectory();
|
||||
std::string getAppdataDirectory();
|
||||
|
||||
/**
|
||||
* Gets the full path of the save folder.
|
||||
|
||||
@@ -211,13 +211,13 @@ int w_getWorkingDirectory(lua_State *L)
|
||||
|
||||
int w_getUserDirectory(lua_State *L)
|
||||
{
|
||||
lua_pushstring(L, instance->getUserDirectory());
|
||||
luax_pushstring(L, instance->getUserDirectory());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_getAppdataDirectory(lua_State *L)
|
||||
{
|
||||
lua_pushstring(L, instance->getAppdataDirectory());
|
||||
luax_pushstring(L, instance->getAppdataDirectory());
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -176,6 +176,8 @@ StringMap<Graphics::Support, Graphics::SUPPORT_MAX_ENUM>::Entry Graphics::suppor
|
||||
{ "mipmap", Graphics::SUPPORT_MIPMAP },
|
||||
{ "dxt", Graphics::SUPPORT_DXT },
|
||||
{ "bc5", Graphics::SUPPORT_BC5 },
|
||||
{ "etc1", Graphics::SUPPORT_ETC1 },
|
||||
{ "pvrtc1", Graphics::SUPPORT_PVRTC1 },
|
||||
{ "instancing", Graphics::SUPPORT_INSTANCING },
|
||||
{ "srgb", Graphics::SUPPORT_SRGB },
|
||||
};
|
||||
|
||||
@@ -95,6 +95,8 @@ public:
|
||||
SUPPORT_MIPMAP,
|
||||
SUPPORT_DXT,
|
||||
SUPPORT_BC5,
|
||||
SUPPORT_ETC1,
|
||||
SUPPORT_PVRTC1,
|
||||
SUPPORT_INSTANCING,
|
||||
SUPPORT_SRGB,
|
||||
SUPPORT_MAX_ENUM
|
||||
@@ -145,6 +147,20 @@ public:
|
||||
**/
|
||||
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(DrawMode in, const char *&out);
|
||||
|
||||
|
||||
@@ -109,16 +109,6 @@ bool Texture::getConstant(WrapMode in, const char *&out)
|
||||
return wrapModes.find(in, out);
|
||||
}
|
||||
|
||||
bool Texture::getConstant(const char *in, Format &out)
|
||||
{
|
||||
return formats.find(in, out);
|
||||
}
|
||||
|
||||
bool Texture::getConstant(Format in, const char *&out)
|
||||
{
|
||||
return formats.find(in, out);
|
||||
}
|
||||
|
||||
StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
|
||||
{
|
||||
{ "linear", Texture::FILTER_LINEAR },
|
||||
@@ -135,15 +125,5 @@ StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEnt
|
||||
|
||||
StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
|
||||
|
||||
StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM>::Entry Texture::formatEntries[] =
|
||||
{
|
||||
{"normal", Texture::FORMAT_NORMAL},
|
||||
{"hdr", Texture::FORMAT_HDR},
|
||||
{"srgb", Texture::FORMAT_SRGB},
|
||||
};
|
||||
|
||||
StringMap<Texture::Format, Texture::FORMAT_MAX_ENUM> Texture::formats(Texture::formatEntries, sizeof(Texture::formatEntries));
|
||||
|
||||
|
||||
} // graphics
|
||||
} // love
|
||||
|
||||
@@ -55,14 +55,6 @@ public:
|
||||
FILTER_MAX_ENUM
|
||||
};
|
||||
|
||||
enum Format
|
||||
{
|
||||
FORMAT_NORMAL,
|
||||
FORMAT_HDR,
|
||||
FORMAT_SRGB,
|
||||
FORMAT_MAX_ENUM
|
||||
};
|
||||
|
||||
struct Filter
|
||||
{
|
||||
Filter();
|
||||
@@ -119,9 +111,6 @@ public:
|
||||
static bool getConstant(const char *in, WrapMode &out);
|
||||
static bool getConstant(WrapMode in, const char *&out);
|
||||
|
||||
static bool getConstant(const char *in, Format &out);
|
||||
static bool getConstant(Format in, const char *&out);
|
||||
|
||||
protected:
|
||||
|
||||
int width;
|
||||
@@ -143,9 +132,6 @@ private:
|
||||
static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
|
||||
static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
|
||||
|
||||
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
|
||||
static StringMap<Format, FORMAT_MAX_ENUM> formats;
|
||||
|
||||
}; // Texture
|
||||
|
||||
} // graphics
|
||||
|
||||
@@ -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)
|
||||
, resolve_fbo(0)
|
||||
, texture(0)
|
||||
@@ -556,36 +556,31 @@ bool Canvas::loadVolatile()
|
||||
glGenTextures(1, &texture);
|
||||
gl.bindTexture(texture);
|
||||
|
||||
if (GLAD_ANGLE_texture_usage)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
|
||||
|
||||
setFilter(filter);
|
||||
setWrap(wrap);
|
||||
|
||||
GLint internalformat;
|
||||
GLenum textype;
|
||||
switch (format)
|
||||
{
|
||||
case Texture::FORMAT_HDR:
|
||||
internalformat = GL_RGBA16F;
|
||||
textype = GL_FLOAT;
|
||||
break;
|
||||
case Texture::FORMAT_SRGB:
|
||||
internalformat = GL_SRGB_ALPHA;
|
||||
textype = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
case Texture::FORMAT_NORMAL:
|
||||
default:
|
||||
internalformat = GL_RGBA;
|
||||
textype = GL_UNSIGNED_BYTE;
|
||||
}
|
||||
GLenum internalformat = GL_RGBA;
|
||||
GLenum externalformat = GL_RGBA;
|
||||
GLenum textype = GL_UNSIGNED_BYTE;
|
||||
|
||||
convertFormat(format, internalformat, externalformat, textype);
|
||||
|
||||
// in ES2, the internalformat and format params of TexImage have to match.
|
||||
if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
|
||||
internalformat = externalformat;
|
||||
|
||||
while (glGetError() != GL_NO_ERROR)
|
||||
/* Clear the error buffer. */;
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
internalformat,
|
||||
(GLint) internalformat,
|
||||
width, height,
|
||||
0,
|
||||
GL_RGBA,
|
||||
externalformat,
|
||||
textype,
|
||||
nullptr);
|
||||
|
||||
@@ -736,10 +731,13 @@ void Canvas::setupGrab()
|
||||
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.
|
||||
if (format == FORMAT_SRGB)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else if (screenHasSRGB)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
|
||||
{
|
||||
if (format == FORMAT_SRGB)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else if (screenHasSRGB)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
|
||||
if (fsaa_buffer != 0)
|
||||
fsaa_dirty = true;
|
||||
@@ -828,8 +826,11 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
|
||||
|
||||
if (switchingToOtherCanvas)
|
||||
{
|
||||
if (format == FORMAT_SRGB)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
|
||||
{
|
||||
if (format == FORMAT_SRGB)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -838,10 +839,13 @@ void Canvas::stopGrab(bool switchingToOtherCanvas)
|
||||
current = nullptr;
|
||||
gl.setViewport(systemViewport);
|
||||
|
||||
if (format == FORMAT_SRGB && !screenHasSRGB)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
else if (format != FORMAT_SRGB && screenHasSRGB)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
|
||||
{
|
||||
if (format == FORMAT_SRGB && !screenHasSRGB)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
else if (format != FORMAT_SRGB && screenHasSRGB)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
}
|
||||
|
||||
gl.matrices.projection.pop();
|
||||
@@ -1007,41 +1011,231 @@ bool Canvas::resolveMSAA()
|
||||
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()
|
||||
{
|
||||
getStrategy();
|
||||
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()
|
||||
{
|
||||
// system must support at least 4 simultanious active canvases.
|
||||
return gl.getMaxRenderTargets() >= 4;
|
||||
}
|
||||
|
||||
bool Canvas::supportedFormats[] = {false};
|
||||
bool Canvas::checkedFormats[] = {false};
|
||||
|
||||
bool Canvas::isFormatSupported(Canvas::Format format)
|
||||
{
|
||||
if (!isSupported())
|
||||
return false;
|
||||
|
||||
bool supported = true;
|
||||
format = getSizedFormat(format);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case FORMAT_RGBA8:
|
||||
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()
|
||||
{
|
||||
if (current != nullptr)
|
||||
current->stopGrab();
|
||||
}
|
||||
|
||||
bool Canvas::getConstant(const char *in, Format &out)
|
||||
{
|
||||
return formats.find(in, out);
|
||||
}
|
||||
|
||||
bool Canvas::getConstant(Format in, const char *&out)
|
||||
{
|
||||
return formats.find(in, out);
|
||||
}
|
||||
|
||||
StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM>::Entry Canvas::formatEntries[] =
|
||||
{
|
||||
{"normal", Canvas::FORMAT_NORMAL},
|
||||
{"hdr", Canvas::FORMAT_HDR},
|
||||
{"rgba8", Canvas::FORMAT_RGBA8},
|
||||
{"rgba4", Canvas::FORMAT_RGBA4},
|
||||
{"rgb5a1", Canvas::FORMAT_RGB5A1},
|
||||
{"rgb565", Canvas::FORMAT_RGB565},
|
||||
{"rgb10a2", Canvas::FORMAT_RGB10A2},
|
||||
{"rg11b10f", Canvas::FORMAT_RG11B10F},
|
||||
{"rgba16f", Canvas::FORMAT_RGBA16F},
|
||||
{"rgba32f", Canvas::FORMAT_RGBA32F},
|
||||
{"srgb", Canvas::FORMAT_SRGB},
|
||||
};
|
||||
|
||||
StringMap<Canvas::Format, Canvas::FORMAT_MAX_ENUM> Canvas::formats(Canvas::formatEntries, sizeof(Canvas::formatEntries));
|
||||
|
||||
} // opengl
|
||||
} // graphics
|
||||
} // love
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "image/Image.h"
|
||||
#include "image/ImageData.h"
|
||||
#include "common/Matrix.h"
|
||||
#include "common/StringMap.h"
|
||||
#include "Texture.h"
|
||||
#include "OpenGL.h"
|
||||
|
||||
@@ -39,7 +40,24 @@ class Canvas : public Texture
|
||||
{
|
||||
public:
|
||||
|
||||
Canvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
|
||||
// Different Canvas render target formats.
|
||||
enum Format
|
||||
{
|
||||
FORMAT_NORMAL, // Usually RGBA8 or a similar fallback. Always supported.
|
||||
FORMAT_HDR, // Usually RGBA16F. Not always supported.
|
||||
FORMAT_RGBA8, // RGBA with 8 bits per component.
|
||||
FORMAT_RGBA4, // RGBA with 4 bits per component.
|
||||
FORMAT_RGB5A1, // RGB with 5 bits per component, and A with 1 bit.
|
||||
FORMAT_RGB565, // RGB with 5, 6, and 5 bits each, respectively.
|
||||
FORMAT_RGB10A2, // RGB with 10 bits each, and A with 2 bits.
|
||||
FORMAT_RG11B10F, // Floating point [0, +inf]. RG with 11 FP bits each, and B with 10 FP bits.
|
||||
FORMAT_RGBA16F, // Floating point [-inf, +inf]. RGBA with 16 FP bits per component.
|
||||
FORMAT_RGBA32F, // Floating point [-inf, +inf]. RGBA with 32 FP bits per component.
|
||||
FORMAT_SRGB, // sRGB with 8 bits per component, plus 8 bit linear A.
|
||||
FORMAT_MAX_ENUM
|
||||
};
|
||||
|
||||
Canvas(int width, int height, Format format = FORMAT_NORMAL, int fsaa = 0);
|
||||
virtual ~Canvas();
|
||||
|
||||
// Implements Volatile.
|
||||
@@ -85,7 +103,7 @@ public:
|
||||
return status;
|
||||
}
|
||||
|
||||
inline Texture::Format getTextureFormat() const
|
||||
inline Format getTextureFormat() const
|
||||
{
|
||||
return format;
|
||||
}
|
||||
@@ -98,9 +116,8 @@ public:
|
||||
bool resolveMSAA();
|
||||
|
||||
static bool isSupported();
|
||||
static bool isHDRSupported();
|
||||
static bool isSRGBSupported();
|
||||
static bool isMultiCanvasSupported();
|
||||
static bool isFormatSupported(Format format);
|
||||
|
||||
static Canvas *current;
|
||||
static void bindDefaultCanvas();
|
||||
@@ -111,10 +128,16 @@ public:
|
||||
// Whether the main screen should have linear -> sRGB conversions enabled.
|
||||
static bool screenHasSRGB;
|
||||
|
||||
static bool getConstant(const char *in, Format &out);
|
||||
static bool getConstant(Format in, const char *&out);
|
||||
|
||||
private:
|
||||
|
||||
bool createFSAAFBO(GLenum internalformat);
|
||||
|
||||
static Format getSizedFormat(Format format);
|
||||
static void convertFormat(Format format, GLenum &internalformat, GLenum &externalformat, GLenum &type);
|
||||
|
||||
GLuint fbo;
|
||||
GLuint resolve_fbo;
|
||||
|
||||
@@ -134,6 +157,12 @@ private:
|
||||
void setupGrab();
|
||||
void drawv(const Matrix &t, const Vertex *v);
|
||||
|
||||
static bool supportedFormats[FORMAT_MAX_ENUM];
|
||||
static bool checkedFormats[FORMAT_MAX_ENUM];
|
||||
|
||||
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
|
||||
static StringMap<Format, FORMAT_MAX_ENUM> formats;
|
||||
|
||||
}; // Canvas
|
||||
|
||||
} // opengl
|
||||
|
||||
@@ -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)
|
||||
: rasterizer(r)
|
||||
, indexBuffer(nullptr)
|
||||
, height(r->getHeight())
|
||||
, lineHeight(1)
|
||||
, mSpacing(1)
|
||||
@@ -68,6 +69,8 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
|
||||
textureWidth = TEXTURE_WIDTHS[textureSizeIndex];
|
||||
textureHeight = TEXTURE_HEIGHTS[textureSizeIndex];
|
||||
|
||||
indexBuffer = new VertexIndex(20);
|
||||
|
||||
love::font::GlyphData *gd = nullptr;
|
||||
|
||||
try
|
||||
@@ -93,6 +96,7 @@ Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
|
||||
|
||||
Font::~Font()
|
||||
{
|
||||
delete indexBuffer;
|
||||
rasterizer->release();
|
||||
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).
|
||||
std::sort(glyphinfolist.begin(), glyphinfolist.end());
|
||||
|
||||
std::vector<uint16> indices;
|
||||
|
||||
int indicescount = 0;
|
||||
int maxvertices = 0;
|
||||
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)
|
||||
indicescount = (it->vertexcount / 4) * 6;
|
||||
}
|
||||
|
||||
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);
|
||||
VertexIndex *newIndexBuffer = new VertexIndex(maxvertices / 4);
|
||||
delete indexBuffer;
|
||||
indexBuffer = newIndexBuffer;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
// uses a different texture than the previous section.
|
||||
for (auto it = glyphinfolist.begin(); it != glyphinfolist.end(); ++it)
|
||||
{
|
||||
gl.bindTexture(it->texture);
|
||||
|
||||
gl.setVertexAttribArray(OpenGL::ATTRIB_POS, 2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[it->startvertex].x);
|
||||
gl.setVertexAttribArray(OpenGL::ATTRIB_TEXCOORD, 2, GL_FLOAT, sizeof(GlyphVertex), (GLvoid *)&glyphverts[it->startvertex].s);
|
||||
if (GLAD_VERSION_3_2 || GLAD_ARB_draw_elements_base_vertex)
|
||||
{
|
||||
// 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);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "font/Rasterizer.h"
|
||||
#include "graphics/Texture.h"
|
||||
#include "graphics/Volatile.h"
|
||||
#include "VertexBuffer.h"
|
||||
|
||||
#include "OpenGL.h"
|
||||
|
||||
@@ -185,6 +186,8 @@ private:
|
||||
|
||||
love::font::Rasterizer *rasterizer;
|
||||
|
||||
VertexIndex *indexBuffer;
|
||||
|
||||
int height;
|
||||
float lineHeight;
|
||||
float mSpacing; // modifies the spacing by multiplying it with this value
|
||||
|
||||
@@ -52,6 +52,7 @@ Graphics::Graphics()
|
||||
, width(0)
|
||||
, height(0)
|
||||
, created(false)
|
||||
, active(true)
|
||||
, activeStencil(false)
|
||||
, savedState()
|
||||
{
|
||||
@@ -72,7 +73,10 @@ Graphics::~Graphics()
|
||||
currentFont->release();
|
||||
|
||||
if (Shader::defaultShader)
|
||||
{
|
||||
Shader::defaultShader->release();
|
||||
Shader::defaultShader = nullptr;
|
||||
}
|
||||
|
||||
currentWindow->release();
|
||||
}
|
||||
@@ -235,12 +239,16 @@ bool Graphics::setMode(int width, int height, bool &sRGB)
|
||||
}
|
||||
|
||||
// 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)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
if (GLAD_VERSION_1_1 || GLAD_EXT_sRGB_write_control)
|
||||
{
|
||||
if (sRGB)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
else
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
}
|
||||
else
|
||||
sRGB = false;
|
||||
@@ -282,6 +290,23 @@ void Graphics::unSetMode()
|
||||
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*/)
|
||||
{
|
||||
// Human-readable strings for the debug info.
|
||||
@@ -364,6 +389,9 @@ void Graphics::clear()
|
||||
|
||||
void Graphics::present()
|
||||
{
|
||||
if (!isActive())
|
||||
return;
|
||||
|
||||
// Make sure we don't have a canvas active.
|
||||
Canvas *c = Canvas::current;
|
||||
Canvas::bindDefaultCanvas();
|
||||
@@ -463,7 +491,7 @@ void Graphics::discardStencil()
|
||||
activeStencil = false;
|
||||
}
|
||||
|
||||
Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
|
||||
Image *Graphics::newImage(love::image::ImageData *data, Image::Format format)
|
||||
{
|
||||
// Create the image.
|
||||
Image *image = new Image(data, format);
|
||||
@@ -490,7 +518,7 @@ Image *Graphics::newImage(love::image::ImageData *data, Texture::Format format)
|
||||
return image;
|
||||
}
|
||||
|
||||
Image *Graphics::newImage(love::image::CompressedData *cdata, Texture::Format format)
|
||||
Image *Graphics::newImage(love::image::CompressedData *cdata, Image::Format format)
|
||||
{
|
||||
// Create the image.
|
||||
Image *image = new Image(cdata, format);
|
||||
@@ -537,13 +565,14 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
|
||||
return new ParticleSystem(texture, size);
|
||||
}
|
||||
|
||||
Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int fsaa)
|
||||
Canvas *Graphics::newCanvas(int width, int height, Canvas::Format format, int fsaa)
|
||||
{
|
||||
if (format == Texture::FORMAT_HDR && !Canvas::isHDRSupported())
|
||||
throw Exception("HDR Canvases are not supported by your OpenGL implementation");
|
||||
|
||||
if (format == Texture::FORMAT_SRGB && !Canvas::isSRGBSupported())
|
||||
throw Exception("sRGB Canvases are not supported by your OpenGL implementation");
|
||||
if (!Canvas::isFormatSupported(format))
|
||||
{
|
||||
const char *fstr = "rgba8";
|
||||
Canvas::getConstant(format, fstr);
|
||||
throw love::Exception("The %s canvas format is not supported by your OpenGL implementation.", fstr);
|
||||
}
|
||||
|
||||
if (width > gl.getMaxTextureSize())
|
||||
throw Exception("Cannot create canvas: width of %d pixels is too large for this system.", width);
|
||||
@@ -594,8 +623,8 @@ Canvas *Graphics::newCanvas(int width, int height, Texture::Format format, int f
|
||||
}
|
||||
|
||||
canvas->release();
|
||||
throw Exception(error_string.str().c_str());
|
||||
return NULL; // never reached
|
||||
throw Exception("%s", error_string.str().c_str());
|
||||
return nullptr; // never reached
|
||||
}
|
||||
|
||||
Shader *Graphics::newShader(const Shader::ShaderSources &sources)
|
||||
@@ -972,7 +1001,24 @@ void Graphics::circle(DrawMode mode, float x, float y, float radius, int points)
|
||||
float angle_shift = (two_pi / points);
|
||||
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)
|
||||
{
|
||||
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+1] = coords[1];
|
||||
|
||||
polygon(mode, coords, (points + 1) * 2);
|
||||
if (mode == DRAW_FILL)
|
||||
coords -= 2;
|
||||
|
||||
polygon(mode, coords, 2 * (points + extrapoints));
|
||||
|
||||
delete[] coords;
|
||||
}
|
||||
@@ -1059,7 +1108,7 @@ void Graphics::polygon(DrawMode mode, const float *coords, size_t count)
|
||||
gl.enableVertexAttribArray(OpenGL::ATTRIB_POS);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
virtual bool setMode(int width, int height, bool &sRGB);
|
||||
virtual void unSetMode();
|
||||
|
||||
virtual void setActive(bool active);
|
||||
virtual bool isActive() const;
|
||||
|
||||
void setDebug(bool enable);
|
||||
|
||||
/**
|
||||
@@ -194,8 +197,8 @@ public:
|
||||
/**
|
||||
* Creates an Image object with padding and/or optimization.
|
||||
**/
|
||||
Image *newImage(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
|
||||
Image *newImage(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
|
||||
Image *newImage(love::image::ImageData *data, Image::Format format = Image::FORMAT_NORMAL);
|
||||
Image *newImage(love::image::CompressedData *cdata, Image::Format format = Image::FORMAT_NORMAL);
|
||||
|
||||
Quad *newQuad(Quad::Viewport v, float sw, float sh);
|
||||
|
||||
@@ -208,7 +211,7 @@ public:
|
||||
|
||||
ParticleSystem *newParticleSystem(Texture *texture, int size);
|
||||
|
||||
Canvas *newCanvas(int width, int height, Texture::Format format = Texture::FORMAT_NORMAL, int fsaa = 0);
|
||||
Canvas *newCanvas(int width, int height, Canvas::Format format = Canvas::FORMAT_NORMAL, int fsaa = 0);
|
||||
|
||||
Shader *newShader(const Shader::ShaderSources &sources);
|
||||
|
||||
@@ -480,6 +483,7 @@ private:
|
||||
int width;
|
||||
int height;
|
||||
bool created;
|
||||
bool active;
|
||||
|
||||
bool activeStencil;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ float Image::maxMipmapSharpness = 0.0f;
|
||||
Texture::FilterMode Image::defaultMipmapFilter = Texture::FILTER_NONE;
|
||||
float Image::defaultMipmapSharpness = 0.0f;
|
||||
|
||||
Image::Image(love::image::ImageData *data, Texture::Format format)
|
||||
Image::Image(love::image::ImageData *data, Format format)
|
||||
: data(data)
|
||||
, cdata(nullptr)
|
||||
, paddedWidth(width)
|
||||
@@ -55,7 +55,7 @@ Image::Image(love::image::ImageData *data, Texture::Format format)
|
||||
preload();
|
||||
}
|
||||
|
||||
Image::Image(love::image::CompressedData *cdata, Texture::Format format)
|
||||
Image::Image(love::image::CompressedData *cdata, Format format)
|
||||
: data(nullptr)
|
||||
, cdata(cdata)
|
||||
, paddedWidth(width)
|
||||
@@ -504,7 +504,7 @@ bool Image::refresh()
|
||||
return true;
|
||||
}
|
||||
|
||||
Texture::Format Image::getFormat() const
|
||||
Image::Format Image::getFormat() const
|
||||
{
|
||||
return format;
|
||||
}
|
||||
@@ -602,6 +602,20 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const
|
||||
return GL_COMPRESSED_RG_RGTC2;
|
||||
case image::CompressedData::FORMAT_BC5s:
|
||||
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:
|
||||
if (srgb)
|
||||
return GL_SRGB_ALPHA;
|
||||
@@ -635,14 +649,24 @@ bool Image::hasCompressedTextureSupport(image::CompressedData::Format format)
|
||||
switch (format)
|
||||
{
|
||||
case image::CompressedData::FORMAT_DXT1:
|
||||
return GLAD_EXT_texture_compression_s3tc || GLAD_EXT_texture_compression_dxt1;
|
||||
case image::CompressedData::FORMAT_DXT3:
|
||||
return GLAD_EXT_texture_compression_s3tc || GLAD_ANGLE_texture_compression_dxt3;
|
||||
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_BC4s:
|
||||
case image::CompressedData::FORMAT_BC5:
|
||||
case image::CompressedData::FORMAT_BC5s:
|
||||
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:
|
||||
break;
|
||||
}
|
||||
@@ -655,6 +679,24 @@ bool Image::hasSRGBSupport()
|
||||
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
|
||||
} // graphics
|
||||
} // love
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "common/config.h"
|
||||
#include "common/Matrix.h"
|
||||
#include "common/Vector.h"
|
||||
#include "common/StringMap.h"
|
||||
#include "common/math.h"
|
||||
#include "image/ImageData.h"
|
||||
#include "image/CompressedData.h"
|
||||
@@ -50,20 +51,27 @@ class Image : public Texture
|
||||
{
|
||||
public:
|
||||
|
||||
enum Format
|
||||
{
|
||||
FORMAT_NORMAL,
|
||||
FORMAT_SRGB,
|
||||
FORMAT_MAX_ENUM
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new Image. Not that anything is ready to use
|
||||
* before load is called.
|
||||
*
|
||||
* @param data The data from which to load the image.
|
||||
**/
|
||||
Image(love::image::ImageData *data, Texture::Format format = Texture::FORMAT_NORMAL);
|
||||
Image(love::image::ImageData *data, Format format = FORMAT_NORMAL);
|
||||
|
||||
/**
|
||||
* Creates a new Image with compressed image data.
|
||||
*
|
||||
* @param cdata The compressed data from which to load the image.
|
||||
**/
|
||||
Image(love::image::CompressedData *cdata, Texture::Format format = Texture::FORMAT_NORMAL);
|
||||
Image(love::image::CompressedData *cdata, Format format = FORMAT_NORMAL);
|
||||
|
||||
/**
|
||||
* Destructor. Deletes the hardware texture and other resources.
|
||||
@@ -120,7 +128,7 @@ public:
|
||||
**/
|
||||
bool refresh();
|
||||
|
||||
Texture::Format getFormat() const;
|
||||
Format getFormat() const;
|
||||
|
||||
static void setDefaultMipmapSharpness(float sharpness);
|
||||
static float getDefaultMipmapSharpness();
|
||||
@@ -136,6 +144,9 @@ public:
|
||||
|
||||
static bool hasSRGBSupport();
|
||||
|
||||
static bool getConstant(const char *in, Format &out);
|
||||
static bool getConstant(Format in, const char *&out);
|
||||
|
||||
private:
|
||||
|
||||
void uploadDefaultTexture();
|
||||
@@ -165,8 +176,8 @@ private:
|
||||
// Whether this Image is using a compressed texture.
|
||||
bool compressed;
|
||||
|
||||
// The format to interpret the texture's data as.
|
||||
Texture::Format format;
|
||||
// The format to interpret the image's data as.
|
||||
Format format;
|
||||
|
||||
// True if the image wasn't able to be properly created and it had to fall
|
||||
// back to a default texture.
|
||||
@@ -188,6 +199,9 @@ private:
|
||||
|
||||
GLenum getCompressedFormat(image::CompressedData::Format cformat) const;
|
||||
|
||||
static StringMap<Format, FORMAT_MAX_ENUM>::Entry formatEntries[];
|
||||
static StringMap<Format, FORMAT_MAX_ENUM> formats;
|
||||
|
||||
}; // Image
|
||||
|
||||
} // opengl
|
||||
|
||||
@@ -160,6 +160,7 @@ void OpenGL::initVendor()
|
||||
}
|
||||
|
||||
// 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"))
|
||||
vendor = VENDOR_ATI_AMD;
|
||||
else if (strstr(vstr, "NVIDIA"))
|
||||
@@ -172,6 +173,16 @@ void OpenGL::initVendor()
|
||||
vendor = VENDOR_APPLE;
|
||||
else if (strstr(vstr, "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
|
||||
vendor = VENDOR_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -66,8 +66,13 @@ public:
|
||||
VENDOR_NVIDIA,
|
||||
VENDOR_INTEL,
|
||||
VENDOR_MESA_SOFT, // Software renderer.
|
||||
VENDOR_APPLE, // Software renderer.
|
||||
VENDOR_APPLE, // Software renderer (or Apple A7 chips and newer.)
|
||||
VENDOR_MICROSOFT, // Software renderer.
|
||||
VENDOR_IMGTEC,
|
||||
VENDOR_ARM,
|
||||
VENDOR_QUALCOMM,
|
||||
VENDOR_BROADCOM,
|
||||
VENDOR_VIVANTE,
|
||||
VENDOR_UNKNOWN
|
||||
};
|
||||
|
||||
|
||||
@@ -101,10 +101,9 @@ Shader::~Shader()
|
||||
detach();
|
||||
|
||||
for (auto it = boundRetainables.begin(); it != boundRetainables.end(); ++it)
|
||||
{
|
||||
it->second->release();
|
||||
boundRetainables.erase(it);
|
||||
}
|
||||
|
||||
boundRetainables.clear();
|
||||
|
||||
unloadVolatile();
|
||||
}
|
||||
|
||||
@@ -189,10 +189,18 @@ void VBO::unmap()
|
||||
is_bound = true;
|
||||
}
|
||||
|
||||
// "orphan" current buffer to avoid implicit synchronisation on the GPU:
|
||||
// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
|
||||
glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage());
|
||||
glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
|
||||
if (getUsage() == GL_STATIC_DRAW)
|
||||
{
|
||||
// Upload the mapped data to the buffer.
|
||||
glBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map);
|
||||
}
|
||||
else
|
||||
{
|
||||
// "orphan" current buffer to avoid implicit synchronisation on the GPU:
|
||||
// http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
|
||||
glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage());
|
||||
glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage());
|
||||
}
|
||||
|
||||
is_mapped = false;
|
||||
}
|
||||
|
||||
@@ -113,10 +113,10 @@ int w_Canvas_clear(lua_State *L)
|
||||
int w_Canvas_getFormat(lua_State *L)
|
||||
{
|
||||
Canvas *canvas = luax_checkcanvas(L, 1);
|
||||
Texture::Format format = canvas->getTextureFormat();
|
||||
Canvas::Format format = canvas->getTextureFormat();
|
||||
const char *str;
|
||||
if (!Texture::getConstant(format, str))
|
||||
return luaL_error(L, "Unknown texture format.");
|
||||
if (!Canvas::getConstant(format, str))
|
||||
return luaL_error(L, "Unknown Canvas format.");
|
||||
|
||||
lua_pushstring(L, str);
|
||||
return 1;
|
||||
|
||||
@@ -60,6 +60,12 @@ int w_isCreated(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_isActive(lua_State *L)
|
||||
{
|
||||
luax_pushboolean(L, instance->isActive());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_getWidth(lua_State *L)
|
||||
{
|
||||
lua_pushinteger(L, instance->getWidth());
|
||||
@@ -155,14 +161,11 @@ int w_newImage(lua_State *L)
|
||||
love::image::ImageData *data = nullptr;
|
||||
love::image::CompressedData *cdata = nullptr;
|
||||
|
||||
Texture::Format format = Texture::FORMAT_NORMAL;
|
||||
Image::Format format = Image::FORMAT_NORMAL;
|
||||
const char *fstr = lua_isnoneornil(L, 2) ? nullptr : luaL_checkstring(L, 2);
|
||||
|
||||
if (fstr != nullptr && !Texture::getConstant(fstr, format))
|
||||
return luaL_error(L, "Invalid texture format: %s", fstr);
|
||||
|
||||
if (format == Texture::FORMAT_HDR) // For now...
|
||||
return luaL_error(L, "HDR images are not supported.");
|
||||
if (fstr != nullptr && !Image::getConstant(fstr, format))
|
||||
return luaL_error(L, "Invalid Image format: %s", fstr);
|
||||
|
||||
// Convert to FileData, if necessary.
|
||||
if (lua_isstring(L, 1) || luax_istype(L, 1, FILESYSTEM_FILE_T))
|
||||
@@ -334,9 +337,9 @@ int w_newCanvas(lua_State *L)
|
||||
const char *str = luaL_optstring(L, 3, "normal");
|
||||
int fsaa = luaL_optint(L, 4, 0);
|
||||
|
||||
Texture::Format format;
|
||||
if (!Texture::getConstant(str, format))
|
||||
return luaL_error(L, "Invalid texture format: %s", str);
|
||||
Canvas::Format format;
|
||||
if (!Canvas::getConstant(str, format))
|
||||
return luaL_error(L, "Invalid Canvas format: %s", str);
|
||||
|
||||
Canvas *canvas = nullptr;
|
||||
EXCEPT_GUARD(canvas = instance->newCanvas(width, height, format, fsaa);)
|
||||
@@ -1015,7 +1018,7 @@ int w_isSupported(lua_State *L)
|
||||
supported = false;
|
||||
break;
|
||||
case Graphics::SUPPORT_HDR_CANVAS:
|
||||
if (!Canvas::isHDRSupported())
|
||||
if (!Canvas::isFormatSupported(Canvas::FORMAT_HDR))
|
||||
supported = false;
|
||||
break;
|
||||
case Graphics::SUPPORT_MULTI_CANVAS:
|
||||
@@ -1046,12 +1049,20 @@ int w_isSupported(lua_State *L)
|
||||
if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_BC5))
|
||||
supported = false;
|
||||
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:
|
||||
if (!(GLAD_ARB_draw_instanced || (GLAD_ES_VERSION_2_0 && GLAD_EXT_draw_instanced)))
|
||||
supported = false;
|
||||
break;
|
||||
case Graphics::SUPPORT_SRGB:
|
||||
if (!Canvas::isSRGBSupported())
|
||||
if (!Canvas::isFormatSupported(Canvas::FORMAT_SRGB))
|
||||
supported = false;
|
||||
break;
|
||||
default:
|
||||
@@ -1064,6 +1075,18 @@ int w_isSupported(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_hasCanvasFormat(lua_State *L)
|
||||
{
|
||||
const char *str = luaL_checkstring(L, 1);
|
||||
Canvas::Format format;
|
||||
|
||||
if (!Canvas::getConstant(str, format))
|
||||
return luaL_error(L, "Invalid canvas format: %s", str);
|
||||
|
||||
luax_pushboolean(L, Canvas::isFormatSupported(format));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_getRendererInfo(lua_State *L)
|
||||
{
|
||||
std::string name, version, vendor, device;
|
||||
@@ -1441,6 +1464,7 @@ static const luaL_Reg functions[] =
|
||||
{ "_setDefaultShaderCode", w_setDefaultShaderCode },
|
||||
|
||||
{ "isSupported", w_isSupported },
|
||||
{ "hasCanvasFormat", w_hasCanvasFormat },
|
||||
{ "getRendererInfo", w_getRendererInfo },
|
||||
{ "getSystemLimit", w_getSystemLimit },
|
||||
|
||||
@@ -1450,6 +1474,7 @@ static const luaL_Reg functions[] =
|
||||
{ "printf", w_printf },
|
||||
|
||||
{ "isCreated", w_isCreated },
|
||||
{ "isActive", w_isActive },
|
||||
{ "getWidth", w_getWidth },
|
||||
{ "getHeight", w_getHeight },
|
||||
{ "getDimensions", w_getDimensions },
|
||||
|
||||
@@ -43,6 +43,7 @@ int w_reset(lua_State *L);
|
||||
int w_clear(lua_State *L);
|
||||
int w_present(lua_State *L);
|
||||
int w_isCreated(lua_State *L);
|
||||
int w_isActive(lua_State *L);
|
||||
int w_getWidth(lua_State *L);
|
||||
int w_getHeight(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_setDefaultShaderCode(lua_State *L);
|
||||
int w_isSupported(lua_State *L);
|
||||
int w_hasCanvasFormat(lua_State *L);
|
||||
int w_getRendererInfo(lua_State *L);
|
||||
int w_getSystemLimit(lua_State *L);
|
||||
int w_draw(lua_State *L);
|
||||
|
||||
@@ -110,6 +110,11 @@ StringMap<CompressedData::Format, CompressedData::FORMAT_MAX_ENUM>::Entry Compre
|
||||
{"bc4s", CompressedData::FORMAT_BC4s},
|
||||
{"bc5", CompressedData::FORMAT_BC5},
|
||||
{"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));
|
||||
|
||||
@@ -54,6 +54,11 @@ public:
|
||||
FORMAT_BC4s,
|
||||
FORMAT_BC5,
|
||||
FORMAT_BC5s,
|
||||
FORMAT_ETC1,
|
||||
FORMAT_PVR1_RGB2,
|
||||
FORMAT_PVR1_RGB4,
|
||||
FORMAT_PVR1_RGBA2,
|
||||
FORMAT_PVR1_RGBA4,
|
||||
FORMAT_MAX_ENUM
|
||||
};
|
||||
|
||||
|
||||
@@ -29,29 +29,47 @@ namespace image
|
||||
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);
|
||||
}
|
||||
|
||||
CompressedData::~CompressedData()
|
||||
{
|
||||
delete[] data;
|
||||
|
||||
for (CompressedFormatHandler *handler : formatHandlers)
|
||||
handler->release();
|
||||
}
|
||||
|
||||
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.
|
||||
std::vector<SubImage> parsedimages;
|
||||
Format texformat = FORMAT_UNKNOWN;
|
||||
|
||||
uint8 *newdata = 0;
|
||||
size_t newdata_size = 0;
|
||||
uint8 *newdata = parser->parse(filedata, parsedimages, newdata_size, texformat);
|
||||
|
||||
if (ddsHandler::canParse(filedata))
|
||||
newdata = ddsHandler::parse(filedata, parsedimages, newdata_size, texformat);
|
||||
|
||||
if (newdata == 0)
|
||||
if (newdata == nullptr)
|
||||
throw love::Exception("Could not parse compressed data.");
|
||||
|
||||
if (texformat == FORMAT_UNKNOWN)
|
||||
@@ -76,14 +94,6 @@ void CompressedData::load(love::filesystem::FileData *filedata)
|
||||
format = texformat;
|
||||
}
|
||||
|
||||
bool CompressedData::isCompressed(love::filesystem::FileData *filedata)
|
||||
{
|
||||
if (ddsHandler::canParse(filedata))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // magpie
|
||||
} // image
|
||||
} // love
|
||||
|
||||
@@ -22,9 +22,13 @@
|
||||
#define LOVE_IMAGE_MAGPIE_COMPRESSED_DATA_H
|
||||
|
||||
// LOVE
|
||||
#include "CompressedFormatHandler.h"
|
||||
#include "filesystem/FileData.h"
|
||||
#include "image/CompressedData.h"
|
||||
|
||||
// C++
|
||||
#include <list>
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace image
|
||||
@@ -36,15 +40,16 @@ class CompressedData : public love::image::CompressedData
|
||||
{
|
||||
public:
|
||||
|
||||
CompressedData(love::filesystem::FileData *filedata);
|
||||
CompressedData(std::list<CompressedFormatHandler *> formats, love::filesystem::FileData *filedata);
|
||||
virtual ~CompressedData();
|
||||
|
||||
static bool isCompressed(love::filesystem::FileData *filedata);
|
||||
|
||||
private:
|
||||
|
||||
void load(love::filesystem::FileData *filedata);
|
||||
|
||||
// Compressed image format handlers we use for parsing.
|
||||
std::list<CompressedFormatHandler *> formatHandlers;
|
||||
|
||||
}; // CompressedData
|
||||
|
||||
} // 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
|
||||
@@ -25,6 +25,11 @@
|
||||
|
||||
#include "DevilHandler.h"
|
||||
|
||||
#include "ddsHandler.h"
|
||||
#include "PVRHandler.h"
|
||||
#include "KTXHandler.h"
|
||||
#include "PKMHandler.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace image
|
||||
@@ -35,14 +40,22 @@ namespace magpie
|
||||
Image::Image()
|
||||
{
|
||||
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()
|
||||
{
|
||||
// ImageData objects reference the FormatHandlers in our list, so we should
|
||||
// release them instead of deleting them completely here.
|
||||
for (auto it = formatHandlers.begin(); it != formatHandlers.end(); ++it)
|
||||
(*it)->release();
|
||||
for (FormatHandler *handler : formatHandlers)
|
||||
handler->release();
|
||||
|
||||
for (CompressedFormatHandler *handler : compressedFormatHandlers)
|
||||
handler->release();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return new CompressedData(data);
|
||||
return new CompressedData(compressedFormatHandlers, 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
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
// LOVE
|
||||
#include "image/Image.h"
|
||||
#include "FormatHandler.h"
|
||||
#include "CompressedFormatHandler.h"
|
||||
|
||||
// C++
|
||||
#include <list>
|
||||
@@ -63,6 +64,9 @@ private:
|
||||
// Image format handlers we can use for decoding and encoding ImageData.
|
||||
std::list<FormatHandler *> formatHandlers;
|
||||
|
||||
// Compressed image format handers we can use for parsing CompressedData.
|
||||
std::list<CompressedFormatHandler *> compressedFormatHandlers;
|
||||
|
||||
}; // Image
|
||||
|
||||
} // 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.
|
||||
**/
|
||||
|
||||
// LOVE
|
||||
#include "ddsHandler.h"
|
||||
|
||||
// C++
|
||||
#include <algorithm>
|
||||
|
||||
namespace love
|
||||
@@ -29,7 +31,7 @@ namespace image
|
||||
namespace magpie
|
||||
{
|
||||
|
||||
bool ddsHandler::canParse(const filesystem::FileData *data)
|
||||
bool DDSHandler::canParse(const filesystem::FileData *data)
|
||||
{
|
||||
std::string ext = data->getExtension();
|
||||
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());
|
||||
}
|
||||
|
||||
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()))
|
||||
throw love::Exception("Could not decode compressed data (not a DDS file?)");
|
||||
|
||||
CompressedData::Format texformat = CompressedData::FORMAT_UNKNOWN;
|
||||
|
||||
uint8 *data = 0;
|
||||
uint8 *data = nullptr;
|
||||
dataSize = 0;
|
||||
images.clear();
|
||||
|
||||
@@ -107,7 +109,7 @@ uint8 *ddsHandler::parse(filesystem::FileData *filedata, std::vector<CompressedD
|
||||
return data;
|
||||
}
|
||||
|
||||
CompressedData::Format ddsHandler::convertFormat(dds::Format ddsformat)
|
||||
CompressedData::Format DDSHandler::convertFormat(dds::Format ddsformat)
|
||||
{
|
||||
switch (ddsformat)
|
||||
{
|
||||
@@ -128,8 +130,6 @@ CompressedData::Format ddsHandler::convertFormat(dds::Format ddsformat)
|
||||
default:
|
||||
return CompressedData::FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
return CompressedData::FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
} // magpie
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
#define LOVE_IMAGE_MAGPIE_DDS_HANDLER_H
|
||||
|
||||
// LOVE
|
||||
#include "common/EnumMap.h"
|
||||
#include "filesystem/FileData.h"
|
||||
#include "image/CompressedData.h"
|
||||
#include "CompressedFormatHandler.h"
|
||||
|
||||
// dds parser
|
||||
#include "ddsparse/ddsparse.h"
|
||||
@@ -42,36 +40,21 @@ namespace magpie
|
||||
/**
|
||||
* Interface between CompressedData and the ddsparse library.
|
||||
**/
|
||||
class ddsHandler
|
||||
class DDSHandler : public CompressedFormatHandler
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* 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);
|
||||
virtual ~DDSHandler() {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
static uint8 *parse(filesystem::FileData *filedata, std::vector<CompressedData::SubImage> &images, size_t &dataSize, CompressedData::Format &format);
|
||||
// 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);
|
||||
|
||||
private:
|
||||
|
||||
static CompressedData::Format convertFormat(dds::Format ddsformat);
|
||||
|
||||
}; // ddsHandler
|
||||
}; // DDSHandler
|
||||
|
||||
} // magpie
|
||||
} // image
|
||||
|
||||
@@ -157,9 +157,9 @@ public:
|
||||
virtual int getID() const = 0;
|
||||
|
||||
virtual bool isVibrationSupported() = 0;
|
||||
virtual bool setVibration(float left, float right) = 0;
|
||||
virtual bool setVibration(float left, float right, float duration = -1.0f) = 0;
|
||||
virtual bool setVibration() = 0;
|
||||
virtual void getVibration(float &left, float &right) const = 0;
|
||||
virtual void getVibration(float &left, float &right) = 0;
|
||||
|
||||
static bool getConstant(const char *in, Hat &out);
|
||||
static bool getConstant(Hat in, const char *&out);
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
|
||||
// C++
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#ifndef SDL_TICKS_PASSED
|
||||
#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
|
||||
#endif
|
||||
|
||||
namespace love
|
||||
{
|
||||
@@ -34,9 +39,9 @@ namespace sdl
|
||||
{
|
||||
|
||||
Joystick::Joystick(int id)
|
||||
: joyhandle(0)
|
||||
, controller(0)
|
||||
, haptic(0)
|
||||
: joyhandle(nullptr)
|
||||
, controller(nullptr)
|
||||
, haptic(nullptr)
|
||||
, instanceid(-1)
|
||||
, id(id)
|
||||
, vibration()
|
||||
@@ -44,9 +49,9 @@ Joystick::Joystick(int id)
|
||||
}
|
||||
|
||||
Joystick::Joystick(int id, int joyindex)
|
||||
: joyhandle(0)
|
||||
, controller(0)
|
||||
, haptic(0)
|
||||
: joyhandle(nullptr)
|
||||
, controller(nullptr)
|
||||
, haptic(nullptr)
|
||||
, instanceid(-1)
|
||||
, id(id)
|
||||
, vibration()
|
||||
@@ -95,10 +100,7 @@ bool Joystick::open(int deviceindex)
|
||||
void Joystick::close()
|
||||
{
|
||||
if (haptic)
|
||||
{
|
||||
SDL_HapticRumbleStop(haptic);
|
||||
SDL_HapticClose(haptic);
|
||||
}
|
||||
|
||||
if (controller)
|
||||
SDL_GameControllerClose(controller);
|
||||
@@ -106,16 +108,16 @@ void Joystick::close()
|
||||
if (joyhandle)
|
||||
SDL_JoystickClose(joyhandle);
|
||||
|
||||
joyhandle = 0;
|
||||
controller = 0;
|
||||
haptic = 0;
|
||||
joyhandle = nullptr;
|
||||
controller = nullptr;
|
||||
haptic = nullptr;
|
||||
instanceid = -1;
|
||||
vibration = Vibration();
|
||||
}
|
||||
|
||||
bool Joystick::isConnected() const
|
||||
{
|
||||
return joyhandle != 0 && SDL_JoystickGetAttached(joyhandle);
|
||||
return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle);
|
||||
}
|
||||
|
||||
const char *Joystick::getName() const
|
||||
@@ -209,7 +211,7 @@ bool Joystick::openGamepad(int deviceindex)
|
||||
if (isGamepad())
|
||||
{
|
||||
SDL_GameControllerClose(controller);
|
||||
controller = 0;
|
||||
controller = nullptr;
|
||||
}
|
||||
|
||||
controller = SDL_GameControllerOpen(deviceindex);
|
||||
@@ -218,7 +220,7 @@ bool Joystick::openGamepad(int deviceindex)
|
||||
|
||||
bool Joystick::isGamepad() const
|
||||
{
|
||||
return controller != 0;
|
||||
return controller != nullptr;
|
||||
}
|
||||
|
||||
float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
|
||||
@@ -289,13 +291,13 @@ bool Joystick::checkCreateHaptic()
|
||||
if (haptic)
|
||||
{
|
||||
SDL_HapticClose(haptic);
|
||||
haptic = 0;
|
||||
haptic = nullptr;
|
||||
}
|
||||
|
||||
haptic = SDL_HapticOpenFromJoystick(joyhandle);
|
||||
vibration = Vibration();
|
||||
|
||||
return haptic != 0;
|
||||
return haptic != nullptr;
|
||||
}
|
||||
|
||||
bool Joystick::isVibrationSupported()
|
||||
@@ -312,8 +314,8 @@ bool Joystick::isVibrationSupported()
|
||||
if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
|
||||
return true;
|
||||
|
||||
// Check SDL's simple rumble as a last resort.
|
||||
if (SDL_HapticRumbleSupported(haptic) == 1)
|
||||
// Test for simple sine wave support as a last resort.
|
||||
if ((features & SDL_HAPTIC_SINE) != 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -325,7 +327,7 @@ bool Joystick::runVibrationEffect()
|
||||
{
|
||||
if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
|
||||
{
|
||||
if (SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
|
||||
if (SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -336,17 +338,14 @@ bool Joystick::runVibrationEffect()
|
||||
|
||||
vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
|
||||
|
||||
if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) != -1)
|
||||
if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Joystick::setVibration(float left, float right)
|
||||
bool Joystick::setVibration(float left, float right, float duration)
|
||||
{
|
||||
// TODO: support non-infinite durations? The working Tattiebogle Xbox
|
||||
// controller driver in OS X seems to ignore durations under 1 second.
|
||||
|
||||
left = std::min(std::max(left, 0.0f), 1.0f);
|
||||
right = std::min(std::max(right, 0.0f), 1.0f);
|
||||
|
||||
@@ -356,24 +355,32 @@ bool Joystick::setVibration(float left, float right)
|
||||
if (!checkCreateHaptic())
|
||||
return false;
|
||||
|
||||
Uint32 length = SDL_HAPTIC_INFINITY;
|
||||
if (duration >= 0.0f)
|
||||
{
|
||||
float maxduration = std::numeric_limits<Uint32>::max() / 1000.0f;
|
||||
length = Uint32(std::min(duration, maxduration) * 1000);
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
unsigned int features = SDL_HapticQuery(haptic);
|
||||
int axes = SDL_HapticNumAxes(haptic);
|
||||
|
||||
if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
|
||||
{
|
||||
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
|
||||
vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
|
||||
|
||||
vibration.effect.leftright.length = SDL_HAPTIC_INFINITY;
|
||||
vibration.effect.leftright.length = length;
|
||||
vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
|
||||
vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
|
||||
|
||||
success = runVibrationEffect();
|
||||
}
|
||||
|
||||
// Some gamepad drivers only give support for controlling the motors via
|
||||
// a custom FF effect.
|
||||
if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
|
||||
// Some gamepad drivers only give support for controlling individual motors
|
||||
// through a custom FF effect.
|
||||
if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2)
|
||||
{
|
||||
// NOTE: this may cause issues with drivers which support custom effects
|
||||
// but aren't similar to https://github.com/d235j/360Controller .
|
||||
@@ -385,7 +392,7 @@ bool Joystick::setVibration(float left, float right)
|
||||
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
|
||||
vibration.effect.type = SDL_HAPTIC_CUSTOM;
|
||||
|
||||
vibration.effect.custom.length = SDL_HAPTIC_INFINITY;
|
||||
vibration.effect.custom.length = length;
|
||||
vibration.effect.custom.channels = 2;
|
||||
vibration.effect.custom.period = 10;
|
||||
vibration.effect.custom.samples = 2;
|
||||
@@ -394,19 +401,36 @@ bool Joystick::setVibration(float left, float right)
|
||||
success = runVibrationEffect();
|
||||
}
|
||||
|
||||
// Fall back to a simple rumble if all else fails. SDL's simple rumble API
|
||||
// only supports a single strength value.
|
||||
if (!success && SDL_HapticRumbleInit(haptic) == 0)
|
||||
// Fall back to a simple sine wave if all else fails. This only supports a
|
||||
// single strength value.
|
||||
if (!success && (features & SDL_HAPTIC_SINE) != 0)
|
||||
{
|
||||
memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
|
||||
vibration.effect.type = SDL_HAPTIC_SINE;
|
||||
|
||||
vibration.effect.periodic.length = length;
|
||||
vibration.effect.periodic.period = 10;
|
||||
|
||||
float strength = std::max(left, right);
|
||||
int played = SDL_HapticRumblePlay(haptic, strength, SDL_HAPTIC_INFINITY);
|
||||
success = (played == 0);
|
||||
vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF);
|
||||
|
||||
success = runVibrationEffect();
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
vibration.left = left;
|
||||
vibration.right = right;
|
||||
|
||||
if (length == SDL_HAPTIC_INFINITY)
|
||||
vibration.endtime = SDL_HAPTIC_INFINITY;
|
||||
else
|
||||
vibration.endtime = SDL_GetTicks() + length;
|
||||
}
|
||||
else
|
||||
{
|
||||
vibration.left = vibration.right = 0.0f;
|
||||
vibration.endtime = SDL_HAPTIC_INFINITY;
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -417,12 +441,7 @@ bool Joystick::setVibration()
|
||||
bool success = true;
|
||||
|
||||
if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
|
||||
{
|
||||
// Stop all playing effects on the haptic device.
|
||||
// FIXME: We should only stop the vibration effect, in case we use the
|
||||
// Haptic API for other things in the future.
|
||||
success = (SDL_HapticStopAll(haptic) == 0);
|
||||
}
|
||||
success = (SDL_HapticStopEffect(haptic, vibration.id) == 0);
|
||||
|
||||
if (success)
|
||||
vibration.left = vibration.right = 0.0f;
|
||||
@@ -430,8 +449,25 @@ bool Joystick::setVibration()
|
||||
return success;
|
||||
}
|
||||
|
||||
void Joystick::getVibration(float &left, float &right) const
|
||||
void Joystick::getVibration(float &left, float &right)
|
||||
{
|
||||
if (vibration.endtime != SDL_HAPTIC_INFINITY)
|
||||
{
|
||||
// With some drivers, the effect physically stops at the right time, but
|
||||
// SDL_HapticGetEffectStatus still thinks it's playing. So we explicitly
|
||||
// stop it once it's done, just to be sure.
|
||||
if (SDL_TICKS_PASSED(SDL_GetTicks(), vibration.endtime))
|
||||
{
|
||||
setVibration();
|
||||
vibration.endtime = SDL_HAPTIC_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the haptic effect has stopped playing.
|
||||
int id = vibration.id;
|
||||
if (!haptic || id == -1 || SDL_HapticGetEffectStatus(haptic, id) != 1)
|
||||
vibration.left = vibration.right = 0.0f;
|
||||
|
||||
left = vibration.left;
|
||||
right = vibration.right;
|
||||
}
|
||||
|
||||
@@ -74,9 +74,9 @@ public:
|
||||
int getID() const;
|
||||
|
||||
bool isVibrationSupported();
|
||||
bool setVibration(float left, float right);
|
||||
bool setVibration(float left, float right, float duration = -1.0f);
|
||||
bool setVibration();
|
||||
void getVibration(float &left, float &right) const;
|
||||
void getVibration(float &left, float &right);
|
||||
|
||||
static bool getConstant(Hat in, Uint8 &out);
|
||||
static bool getConstant(Uint8 in, Hat &out);
|
||||
@@ -111,8 +111,12 @@ private:
|
||||
Uint16 data[4];
|
||||
int id;
|
||||
|
||||
Uint32 endtime;
|
||||
|
||||
Vibration()
|
||||
: left(0.0f), right(0.0f), effect(), data(), id(-1)
|
||||
: left(0.0f), right(0.0f)
|
||||
, effect(), data(), id(-1)
|
||||
, endtime(SDL_HAPTIC_INFINITY)
|
||||
{}
|
||||
|
||||
} vibration;
|
||||
|
||||
@@ -77,7 +77,7 @@ const char *JoystickModule::getName() const
|
||||
love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
|
||||
{
|
||||
if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
|
||||
return 0;
|
||||
return nullptr;
|
||||
|
||||
return activeSticks[joyindex];
|
||||
}
|
||||
@@ -107,13 +107,13 @@ love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
|
||||
return activeSticks[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
|
||||
{
|
||||
if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
|
||||
return 0;
|
||||
return nullptr;
|
||||
|
||||
std::string guidstr = getDeviceGUID(deviceindex);
|
||||
joystick::Joystick *joystick = 0;
|
||||
@@ -140,7 +140,7 @@ love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
|
||||
removeJoystick(joystick);
|
||||
|
||||
if (!joystick->open(deviceindex))
|
||||
return 0;
|
||||
return nullptr;
|
||||
|
||||
// Make sure multiple instances of the same physical joystick aren't added
|
||||
// to the active list.
|
||||
@@ -262,7 +262,7 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
|
||||
if (status == 1)
|
||||
checkGamepads(guid);
|
||||
|
||||
return status >= 0;
|
||||
return status >= 0;
|
||||
}
|
||||
|
||||
Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput)
|
||||
@@ -318,7 +318,7 @@ std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinpu
|
||||
SDL_GameControllerAxis sdlaxis;
|
||||
SDL_GameControllerButton sdlbutton;
|
||||
|
||||
const char *gpinputname = 0;
|
||||
const char *gpinputname = nullptr;
|
||||
|
||||
switch (gpinput.type)
|
||||
{
|
||||
@@ -435,7 +435,7 @@ void JoystickModule::checkGamepads(const std::string &guid) const
|
||||
// Big hack time: open the index as a game controller and compare
|
||||
// the underlying joystick handle to the active stick's.
|
||||
SDL_GameController *ctrl = SDL_GameControllerOpen(d_index);
|
||||
if (ctrl == NULL)
|
||||
if (ctrl == nullptr)
|
||||
continue;
|
||||
|
||||
SDL_Joystick *stick = SDL_GameControllerGetJoystick(ctrl);
|
||||
|
||||
@@ -207,7 +207,8 @@ int w_Joystick_setVibration(lua_State *L)
|
||||
{
|
||||
float left = (float) luaL_checknumber(L, 2);
|
||||
float right = (float) luaL_optnumber(L, 3, left);
|
||||
success = j->setVibration(left, right);
|
||||
float duration = (float) luaL_optnumber(L, 4, -1.0); // -1 is infinite.
|
||||
success = j->setVibration(left, right, duration);
|
||||
}
|
||||
|
||||
luax_pushboolean(L, success);
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace joystick
|
||||
namespace sdl
|
||||
{
|
||||
|
||||
static JoystickModule *instance = 0;
|
||||
static JoystickModule *instance = nullptr;
|
||||
|
||||
int w_getJoysticks(lua_State *L)
|
||||
{
|
||||
@@ -194,7 +194,7 @@ static const lua_CFunction types[] =
|
||||
|
||||
extern "C" int luaopen_love_joystick(lua_State *L)
|
||||
{
|
||||
if (instance == 0)
|
||||
if (instance == nullptr)
|
||||
{
|
||||
EXCEPT_GUARD(instance = new JoystickModule();)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ int Mpg123Decoder::decode()
|
||||
size += numbytes;
|
||||
long rate = 0;
|
||||
int encoding = 0;
|
||||
int ret = mpg123_getformat(handle, &rate, &channels, &encoding);
|
||||
mpg123_getformat(handle, &rate, &channels, &encoding);
|
||||
if (rate == 0)
|
||||
rate = sampleRate;
|
||||
else
|
||||
@@ -126,7 +126,7 @@ int Mpg123Decoder::decode()
|
||||
channels = MPG123_STEREO;
|
||||
if (encoding == 0)
|
||||
encoding = MPG123_ENC_SIGNED_16;
|
||||
ret = mpg123_format(handle, rate, channels, encoding);
|
||||
int ret = mpg123_format(handle, rate, channels, encoding);
|
||||
if (ret != MPG123_OK)
|
||||
throw love::Exception("Could not set output format.");
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
#elif defined(LOVE_ANDROID)
|
||||
#include "common/android.h"
|
||||
#elif defined(LOVE_LINUX)
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <spawn.h>
|
||||
//#include <stdlib.h>
|
||||
//#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#elif defined(LOVE_WINDOWS)
|
||||
#include "common/utf8.h"
|
||||
@@ -57,12 +58,17 @@ std::string System::getOS() const
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
extern char **environ; // The environment, always available
|
||||
}
|
||||
|
||||
bool System::openURL(const std::string &url) const
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
#if defined(LOVE_MACOSX)
|
||||
|
||||
bool success = false;
|
||||
// We could be lazy and use system("open " + url), but this is safer.
|
||||
CFURLRef cfurl = CFURLCreateWithBytes(nullptr,
|
||||
(const UInt8 *) url.c_str(),
|
||||
@@ -72,38 +78,28 @@ bool System::openURL(const std::string &url) const
|
||||
|
||||
success = LSOpenCFURLRef(cfurl, nullptr) == noErr;
|
||||
CFRelease(cfurl);
|
||||
return success;
|
||||
|
||||
#elif defined(LOVE_ANDROID)
|
||||
|
||||
success = love::android::openURL (url);
|
||||
return love::android::openURL (url);
|
||||
|
||||
#elif defined(LOVE_LINUX)
|
||||
|
||||
// Spawn a child process, which we'll replace with xdg-open.
|
||||
pid_t pid = vfork();
|
||||
pid_t pid;
|
||||
const char *argv[] = {"xdg-open", url.c_str(), nullptr};
|
||||
|
||||
if (pid == 0) // Child process.
|
||||
{
|
||||
// Replace the child process with xdg-open and pass in the URL.
|
||||
execlp("xdg-open", "xdg-open", url.c_str(), nullptr);
|
||||
// Note: at the moment this process inherits our file descriptors.
|
||||
// Note: the below const_cast is really ugly as well.
|
||||
if (posix_spawnp(&pid, "xdg-open", nullptr, nullptr, const_cast<char **>(argv), environ) != 0)
|
||||
return false;
|
||||
|
||||
// exec will only return if it errored, so we should exit with non-zero.
|
||||
_exit(1);
|
||||
}
|
||||
else if (pid > 0) // Parent process.
|
||||
{
|
||||
// Wait for xdg-open to complete (or fail.)
|
||||
int status = 0;
|
||||
if (waitpid(pid, &status, 0) == pid)
|
||||
success = (status == 0);
|
||||
else
|
||||
success = false;
|
||||
}
|
||||
// Wait for xdg-open to complete (or fail.)
|
||||
int status = 0;
|
||||
if (waitpid(pid, &status, 0) == pid)
|
||||
return (status == 0);
|
||||
else
|
||||
{
|
||||
// vfork() failed.
|
||||
success = false;
|
||||
}
|
||||
return false;
|
||||
|
||||
#elif defined(LOVE_WINDOWS)
|
||||
|
||||
@@ -117,11 +113,9 @@ bool System::openURL(const std::string &url) const
|
||||
nullptr,
|
||||
SW_SHOW);
|
||||
|
||||
success = (int) result > 32;
|
||||
return (int) result > 32;
|
||||
|
||||
#endif
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool System::getConstant(const char *in, System::PowerState &out)
|
||||
|
||||
@@ -38,6 +38,7 @@ LuaThread::LuaThread(const std::string &name, love::Data *code)
|
||||
, nargs(0)
|
||||
{
|
||||
code->retain();
|
||||
threadName = name;
|
||||
}
|
||||
|
||||
LuaThread::~LuaThread()
|
||||
|
||||
@@ -51,7 +51,7 @@ bool Thread::start()
|
||||
return false;
|
||||
if (thread) // Clean old handle up
|
||||
SDL_WaitThread(thread, 0);
|
||||
thread = SDL_CreateThread(thread_runner, NULL, this);
|
||||
thread = SDL_CreateThread(thread_runner, t->getThreadName(), this);
|
||||
running = (thread != 0);
|
||||
return running;
|
||||
}
|
||||
|
||||
@@ -99,5 +99,10 @@ bool Threadable::isRunning() const
|
||||
return owner->isRunning();
|
||||
}
|
||||
|
||||
const char *Threadable::getThreadName() const
|
||||
{
|
||||
return threadName.empty() ? nullptr : threadName.c_str();
|
||||
}
|
||||
|
||||
} // thread
|
||||
} // love
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
#ifndef LOVE_THREAD_THREADS_H
|
||||
#define LOVE_THREAD_THREADS_H
|
||||
|
||||
// LOVE
|
||||
#include "common/config.h"
|
||||
#include "Thread.h"
|
||||
|
||||
// C++
|
||||
#include <string>
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace thread
|
||||
@@ -83,9 +87,13 @@ public:
|
||||
bool start();
|
||||
void wait();
|
||||
bool isRunning() const;
|
||||
const char *getThreadName() const;
|
||||
|
||||
protected:
|
||||
|
||||
Thread *owner;
|
||||
std::string threadName;
|
||||
|
||||
};
|
||||
|
||||
Mutex *newMutex();
|
||||
|
||||
@@ -269,11 +269,14 @@ bool Window::setContext(int fsaa, bool vsync, bool sRGB)
|
||||
if (!context)
|
||||
{
|
||||
int flags = 0;
|
||||
int profilemask = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_FLAGS, &flags);
|
||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profilemask);
|
||||
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_PROFILE_MASK, 0);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
|
||||
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_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
|
||||
|
||||
// FSAA.
|
||||
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (fsaa > 0) ? 1 : 0);
|
||||
@@ -331,19 +335,26 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
|
||||
|
||||
int contextprofile = 0;
|
||||
|
||||
bool tryES2 = false;
|
||||
bool tryGLES = false;
|
||||
|
||||
const char *driver = SDL_GetCurrentVideoDriver();
|
||||
const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt"};
|
||||
|
||||
// We always want to use OpenGL ES on certain video backends.
|
||||
if (driver && (strstr(driver, "RPI") || strstr(driver, "Android") || strstr(driver, "uikit")))
|
||||
tryES2 = true;
|
||||
for (size_t i = 0; i < sizeof(glesdrivers) / sizeof(glesdrivers[0]); i++)
|
||||
{
|
||||
if (driver && strstr(driver, glesdrivers[i]))
|
||||
{
|
||||
tryGLES = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const char *hint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
|
||||
if (hint)
|
||||
tryES2 = (*hint) != '0';
|
||||
tryGLES = (*hint) != '0';
|
||||
|
||||
if (tryES2)
|
||||
if (tryGLES)
|
||||
{
|
||||
// Use OpenGL ES 2+.
|
||||
contextprofile = SDL_GL_CONTEXT_PROFILE_ES;
|
||||
@@ -363,7 +374,7 @@ void Window::setWindowGLAttributes(int fsaa, bool /* sRGB */) const
|
||||
if (debugenv && *debugenv == '1')
|
||||
{
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
||||
if (!tryES2)
|
||||
if (!tryGLES)
|
||||
contextprofile |= SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -526,7 +526,7 @@ function love.run()
|
||||
-- Call update and draw
|
||||
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.origin()
|
||||
if love.draw then love.draw() end
|
||||
|
||||
@@ -943,8 +943,8 @@ const unsigned char boot_lua[] =
|
||||
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,
|
||||
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,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x28, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
|
||||
0x6e, 0x64, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x69,
|
||||
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,
|
||||
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,
|
||||
|
||||
@@ -1317,9 +1317,9 @@ uniform mat4 TransformMatrix;
|
||||
uniform mat4 ProjectionMatrix;
|
||||
uniform mat4 TransformProjectionMatrix;
|
||||
// uniform mat4 NormalMatrix;
|
||||
uniform float love_PointSize;
|
||||
uniform sampler2D _tex0_;
|
||||
uniform vec4 love_ScreenSize;]]
|
||||
uniform mediump vec4 love_ScreenSize;
|
||||
uniform mediump float love_PointSize;]]
|
||||
|
||||
GLSL.VERTEX = {
|
||||
HEADER = [[
|
||||
@@ -1357,7 +1357,7 @@ attribute vec4 VertexPosition;
|
||||
attribute vec4 VertexTexCoord;
|
||||
attribute vec4 VertexColor;
|
||||
|
||||
varying vec4 VaryingTexCoord;
|
||||
varying mediump vec4 VaryingTexCoord;
|
||||
varying lowp vec4 VaryingColor;
|
||||
|
||||
//#if defined(GL_EXT_draw_instanced)
|
||||
@@ -1414,18 +1414,36 @@ void main() {
|
||||
|
||||
precision mediump float;
|
||||
|
||||
varying vec4 VaryingTexCoord;
|
||||
varying mediump vec4 VaryingTexCoord;
|
||||
varying lowp vec4 VaryingColor;]],
|
||||
|
||||
FOOTER = GLSL.PIXEL.FOOTER,
|
||||
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)
|
||||
if lang == GLSL then
|
||||
vertexcode = stripPrecision(vertexcode)
|
||||
end
|
||||
local vertexcodes = {
|
||||
lang.VERSION,
|
||||
lang.SYNTAX, lang.VERTEX.HEADER, lang.UNIFORMS,
|
||||
"#line 1",
|
||||
lang == GLSLES and "#line 1" or "#line 0", -- specification differences for #line...
|
||||
vertexcode,
|
||||
lang.VERTEX.FOOTER,
|
||||
}
|
||||
@@ -1433,10 +1451,13 @@ varying lowp vec4 VaryingColor;]],
|
||||
end
|
||||
|
||||
local function createPixelCode(pixelcode, lang, is_multicanvas)
|
||||
if lang == GLSL then
|
||||
pixelcode = stripPrecision(pixelcode)
|
||||
end
|
||||
local pixelcodes = {
|
||||
lang.VERSION,
|
||||
lang.SYNTAX, lang.PIXEL.HEADER, lang.UNIFORMS,
|
||||
"#line 1",
|
||||
lang == GLSLES and "#line 1" or "#line 0",
|
||||
pixelcode,
|
||||
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;
|
||||
}]],
|
||||
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;
|
||||
}]]
|
||||
}
|
||||
|
||||
@@ -6312,12 +6312,14 @@ const unsigned char graphics_lua[] =
|
||||
0x69, 0x78, 0x3b, 0x0a,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x53, 0x69, 0x7a, 0x65, 0x3b, 0x5d, 0x5d, 0x0a,
|
||||
0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65,
|
||||
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, 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,
|
||||
@@ -6373,8 +6375,9 @@ const unsigned char graphics_lua[] =
|
||||
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,
|
||||
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,
|
||||
0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a,
|
||||
0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65,
|
||||
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,
|
||||
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,
|
||||
@@ -6470,8 +6473,9 @@ const unsigned char graphics_lua[] =
|
||||
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,
|
||||
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,
|
||||
0x6e, 0x67, 0x54, 0x65, 0x78, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x3b, 0x0a,
|
||||
0x76, 0x61, 0x72, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x64, 0x69, 0x75, 0x6d, 0x70, 0x20, 0x76, 0x65,
|
||||
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,
|
||||
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,
|
||||
@@ -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,
|
||||
0x2c, 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,
|
||||
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,
|
||||
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,
|
||||
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, 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,
|
||||
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, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x58, 0x2e, 0x46, 0x4f, 0x4f,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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, 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,
|
||||
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, 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,
|
||||
@@ -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,
|
||||
0x7d, 0x5d, 0x5d, 0x2c, 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,
|
||||
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75,
|
||||
0x72, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32, 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,
|
||||
0x6c, 0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x28, 0x6c,
|
||||
0x6f, 0x77, 0x70, 0x20, 0x76, 0x65, 0x63, 0x34, 0x20, 0x76, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x49,
|
||||
0x6d, 0x61, 0x67, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x76, 0x65, 0x63, 0x32,
|
||||
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,
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user