diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 9e997c595..9b7781810 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -158,13 +158,8 @@ StringMap Graphics::lineJoins( StringMap::Entry Graphics::supportEntries[] = { - { "canvas", Graphics::SUPPORT_CANVAS }, { "hdrcanvas", Graphics::SUPPORT_HDR_CANVAS }, { "multicanvas", Graphics::SUPPORT_MULTI_CANVAS }, - { "shader", Graphics::SUPPORT_SHADER }, - { "npot", Graphics::SUPPORT_NPOT }, - { "subtractive", Graphics::SUPPORT_SUBTRACTIVE }, - { "mipmap", Graphics::SUPPORT_MIPMAP }, { "dxt", Graphics::SUPPORT_DXT }, { "bc5", Graphics::SUPPORT_BC5 }, { "instancing", Graphics::SUPPORT_INSTANCING }, diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index b8a0532bf..90e685371 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -78,13 +78,8 @@ public: enum Support { - SUPPORT_CANVAS = 1, - SUPPORT_HDR_CANVAS, + SUPPORT_HDR_CANVAS = 1, SUPPORT_MULTI_CANVAS, - SUPPORT_SHADER, - SUPPORT_NPOT, - SUPPORT_SUBTRACTIVE, - SUPPORT_MIPMAP, SUPPORT_DXT, SUPPORT_BC5, SUPPORT_INSTANCING, diff --git a/src/modules/graphics/opengl/Canvas.cpp b/src/modules/graphics/opengl/Canvas.cpp index 58ee616c4..3a3e011cc 100644 --- a/src/modules/graphics/opengl/Canvas.cpp +++ b/src/modules/graphics/opengl/Canvas.cpp @@ -213,10 +213,7 @@ struct FramebufferStrategyGL3 : public FramebufferStrategy } // set up multiple render targets - if (GLEE_VERSION_2_0) - glDrawBuffers(drawbuffers.size(), &drawbuffers[0]); - else if (GLEE_ARB_draw_buffers) - glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]); + glDrawBuffers(drawbuffers.size(), &drawbuffers[0]); } }; @@ -342,10 +339,7 @@ struct FramebufferStrategyPackedEXT : public FramebufferStrategy } // set up multiple render targets - if (GLEE_VERSION_2_0) - glDrawBuffers(drawbuffers.size(), &drawbuffers[0]); - else if (GLEE_ARB_draw_buffers) - glDrawBuffersARB(drawbuffers.size(), &drawbuffers[0]); + glDrawBuffers(drawbuffers.size(), &drawbuffers[0]); } }; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index 0a25792f9..361b4a180 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -55,6 +55,7 @@ Graphics::Graphics() , created(false) , activeStencil(false) , savedState() + , displayedMinReqWarning(false) { currentWindow = love::window::sdl::Window::createSingleton(); @@ -161,16 +162,38 @@ bool Graphics::setMode(int width, int height, bool &sRGB) this->width = width; this->height = height; - // Okay, setup OpenGL. gl.initContext(); + // Does the system meet LOVE's minimum requirements for graphics? + if (!(GLEE_VERSION_2_0 && Shader::isSupported() && Canvas::isSupported()) + && !displayedMinReqWarning) + { + love::window::MessageBoxType type = love::window::MESSAGEBOX_ERROR; + + const char *title = "Minimum system requirements not met!"; + + std::string message; + message += "Detected OpenGL version: "; + message += (const char *) glGetString(GL_VERSION); + message += "\nRequired OpenGL version: 2.1."; // -ish + message += "\nThe program may crash or have graphical issues."; + + ::printf("%s\n%s\n", title, message.c_str()); + currentWindow->showMessageBox(type, title, message.c_str()); + + // We should only show the message once, instead of after every setMode. + displayedMinReqWarning = true; + } + + // Okay, setup OpenGL. + gl.setupContext(); + created = true; setViewportSize(width, height); // Make sure antialiasing works when set elsewhere - if (GLEE_VERSION_1_3 || GLEE_ARB_multisample) - glEnable(GL_MULTISAMPLE); + glEnable(GL_MULTISAMPLE); // Enable blending glEnable(GL_BLEND); @@ -184,8 +207,7 @@ bool Graphics::setMode(int width, int height, bool &sRGB) glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // Auto-generated mipmaps should be the best quality possible - if (GLEE_VERSION_1_4 || GLEE_SGIS_generate_mipmap) - glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); + glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); // Enable textures glEnable(GL_TEXTURE_2D); @@ -609,21 +631,9 @@ void Graphics::setBlendMode(Graphics::BlendMode mode) switch (mode) { case BLEND_ALPHA: - if (GLEE_VERSION_1_4 || GLEE_EXT_blend_func_separate) - { - state.srcRGB = GL_SRC_ALPHA; - state.srcA = GL_ONE; - state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA; - } - else - { - // Fallback for OpenGL implementations without support for separate blend functions. - // This will most likely only be used for the Microsoft software renderer and - // since it's still stuck with OpenGL 1.1, the only expected difference is a - // different alpha value when reading back the default framebuffer (newScreenshot). - state.srcRGB = state.srcA = GL_SRC_ALPHA; - state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA; - } + state.srcRGB = GL_SRC_ALPHA; + state.srcA = GL_ONE; + state.dstRGB = state.dstA = GL_ONE_MINUS_SRC_ALPHA; break; case BLEND_MULTIPLICATIVE: state.srcRGB = state.srcA = GL_DST_COLOR; @@ -986,8 +996,10 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool { delete[] pixels; delete[] screenshot; + if (curcanvas) curcanvas->startGrab(curcanvas->getAttachedCanvases()); + throw love::Exception("Out of memory."); } @@ -1019,8 +1031,10 @@ love::image::ImageData *Graphics::newScreenshot(love::image::Image *image, bool catch (love::Exception &) { delete[] screenshot; + if (curcanvas) curcanvas->startGrab(curcanvas->getAttachedCanvases()); + throw; } diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index c2595174a..3a3d4f797 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -473,6 +473,8 @@ private: DisplayState savedState; + bool displayedMinReqWarning; + }; // Graphics } // opengl diff --git a/src/modules/graphics/opengl/Image.cpp b/src/modules/graphics/opengl/Image.cpp index 2166fd4a5..21a08865a 100644 --- a/src/modules/graphics/opengl/Image.cpp +++ b/src/modules/graphics/opengl/Image.cpp @@ -39,8 +39,6 @@ float Image::defaultMipmapSharpness = 0.0f; Image::Image(love::image::ImageData *data, Texture::Format format) : data(data) , cdata(nullptr) - , paddedWidth(width) - , paddedHeight(height) , texture(0) , mipmapSharpness(defaultMipmapSharpness) , mipmapsCreated(false) @@ -58,8 +56,6 @@ Image::Image(love::image::ImageData *data, Texture::Format format) Image::Image(love::image::CompressedData *cdata, Texture::Format format) : data(nullptr) , cdata(cdata) - , paddedWidth(width) - , paddedHeight(height) , texture(0) , mipmapSharpness(defaultMipmapSharpness) , mipmapsCreated(false) @@ -112,25 +108,10 @@ void Image::drawq(Quad *quad, float x, float y, float angle, float sx, float sy, void Image::predraw() { bind(); - - if (width != paddedWidth || height != paddedHeight) - { - // NPOT image padded to POT size, so the texcoords should be scaled. - glMatrixMode(GL_TEXTURE); - glPushMatrix(); - glScalef(float(width) / float(paddedWidth), float(height) / float(paddedHeight), 0.0f); - glMatrixMode(GL_MODELVIEW); - } } void Image::postdraw() { - if (width != paddedWidth || height != paddedHeight) - { - glMatrixMode(GL_TEXTURE); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - } } GLuint Image::getGLTexture() const @@ -148,27 +129,18 @@ void Image::uploadCompressedMipmaps() int count = cdata->getMipmapCount(); // We have to inform OpenGL if the image doesn't have all mipmap levels. - if (GLEE_VERSION_1_2 || GLEE_SGIS_texture_lod) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, count - 1); - } - else if (cdata->getWidth(count-1) > 1 || cdata->getHeight(count-1) > 1) - { - // Telling OpenGL to ignore certain levels isn't always supported. - throw love::Exception("Cannot load mipmaps: " - "compressed image does not have all required levels."); - } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, count - 1); for (int i = 1; i < count; i++) { - glCompressedTexImage2DARB(GL_TEXTURE_2D, - i, - getCompressedFormat(cdata->getFormat()), - cdata->getWidth(i), - cdata->getHeight(i), - 0, - GLsizei(cdata->getSize(i)), - cdata->getData(i)); + glCompressedTexImage2D(GL_TEXTURE_2D, + i, + getCompressedFormat(cdata->getFormat()), + cdata->getWidth(i), + cdata->getHeight(i), + 0, + GLsizei(cdata->getSize(i)), + cdata->getData(i)); } } @@ -178,9 +150,6 @@ void Image::createMipmaps() if (!data || isCompressed()) return; - if (!hasMipmapSupport()) - throw love::Exception("Mipmap filtering is not supported on this system."); - // Some old drivers claim support for NPOT textures, but fail when creating // mipmaps. We can't detect which systems will do this, so we fail gracefully // for all NPOT images. @@ -196,7 +165,7 @@ void Image::createMipmaps() // Prevent other threads from changing the ImageData while we upload it. love::thread::Lock lock(data->getMutex()); - if (hasNpot() && (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object)) + if (GLEE_VERSION_3_0 || GLEE_ARB_framebuffer_object) { if (gl.getVendor() == OpenGL::VENDOR_ATI_AMD) { @@ -266,18 +235,13 @@ void Image::setWrap(const Texture::Wrap &w) void Image::setMipmapSharpness(float sharpness) { - if (hasMipmapSharpnessSupport()) - { - // LOD bias has the range (-maxbias, maxbias) - mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f); + // LOD bias has the range (-maxbias, maxbias) + mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f); - bind(); + bind(); - // negative bias is sharper - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness); - } - else - mipmapSharpness = 0.0f; + // negative bias is sharper + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -mipmapSharpness); } float Image::getMipmapSharpness() const @@ -345,28 +309,18 @@ bool Image::loadVolatile() throw love::Exception("cannot create image: format is not supported on this system."); } - if (hasMipmapSharpnessSupport() && maxMipmapSharpness == 0.0f) + if (maxMipmapSharpness == 0.0f) glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS, &maxMipmapSharpness); glGenTextures(1, &texture); gl.bindTexture(texture); - filter.anisotropy = gl.setTextureFilter(filter); + gl.setTextureFilter(filter); gl.setTextureWrap(wrap); setMipmapSharpness(mipmapSharpness); - paddedWidth = width; - paddedHeight = height; - - if (!hasNpot()) - { - // NPOT textures will be padded to POT dimensions if necessary. - paddedWidth = next_p2(width); - paddedHeight = next_p2(height); - } - // Use a default texture if the size is too big for the system. - if (paddedWidth > gl.getMaxTextureSize() || paddedHeight > gl.getMaxTextureSize()) + if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize()) { uploadDefaultTexture(); return true; @@ -379,10 +333,7 @@ bool Image::loadVolatile() while (glGetError() != GL_NO_ERROR); // Clear errors. - if (hasNpot() || (width == paddedWidth && height == paddedHeight)) - uploadTexture(); - else - uploadTexturePadded(); + uploadTexture(); GLenum glerr = glGetError(); if (glerr != GL_NO_ERROR) @@ -395,51 +346,19 @@ bool Image::loadVolatile() return true; } -void Image::uploadTexturePadded() -{ - if (isCompressed() && cdata) - { - // Padded textures don't really work if they're compressed... - throw love::Exception("Cannot create image: " - "compressed NPOT images are not supported on this system."); - } - else if (data) - { - GLenum iformat = (format == FORMAT_SRGB) ? GL_SRGB8_ALPHA8 : GL_RGBA8; - glTexImage2D(GL_TEXTURE_2D, - 0, - iformat, - (GLsizei)paddedWidth, - (GLsizei)paddedHeight, - 0, - GL_RGBA, - GL_UNSIGNED_BYTE, - 0); - - glTexSubImage2D(GL_TEXTURE_2D, - 0, - 0, 0, - (GLsizei)width, - (GLsizei)height, - GL_RGBA, - GL_UNSIGNED_BYTE, - data->getData()); - } -} - void Image::uploadTexture() { if (isCompressed() && cdata) { GLenum format = getCompressedFormat(cdata->getFormat()); - glCompressedTexImage2DARB(GL_TEXTURE_2D, - 0, - format, - cdata->getWidth(0), - cdata->getHeight(0), - 0, - GLsizei(cdata->getSize(0)), - cdata->getData(0)); + glCompressedTexImage2D(GL_TEXTURE_2D, + 0, + format, + cdata->getWidth(0), + cdata->getHeight(0), + 0, + GLsizei(cdata->getSize(0)), + cdata->getData(0)); } else if (data) { @@ -488,10 +407,7 @@ bool Image::refresh() while (glGetError() != GL_NO_ERROR); // Clear errors. - if (hasNpot() || (width == paddedWidth && height == paddedHeight)) - uploadTexture(); - else - uploadTexturePadded(); + uploadTexture(); if (glGetError() != GL_NO_ERROR) uploadDefaultTexture(); @@ -610,36 +526,13 @@ GLenum Image::getCompressedFormat(image::CompressedData::Format cformat) const } } -bool Image::hasNpot() -{ - return GLEE_VERSION_2_0 || GLEE_ARB_texture_non_power_of_two; -} - bool Image::hasAnisotropicFilteringSupport() { return GLEE_EXT_texture_filter_anisotropic; } -bool Image::hasMipmapSupport() -{ - return GLEE_VERSION_1_4 || GLEE_SGIS_generate_mipmap; -} - -bool Image::hasMipmapSharpnessSupport() -{ - return GLEE_VERSION_1_4; -} - -bool Image::hasCompressedTextureSupport() -{ - return GLEE_VERSION_1_3 || GLEE_ARB_texture_compression; -} - bool Image::hasCompressedTextureSupport(image::CompressedData::Format format) { - if (!hasCompressedTextureSupport()) - return false; - switch (format) { case image::CompressedData::FORMAT_DXT1: diff --git a/src/modules/graphics/opengl/Image.h b/src/modules/graphics/opengl/Image.h index d23f385ec..99fe9b4a0 100644 --- a/src/modules/graphics/opengl/Image.h +++ b/src/modules/graphics/opengl/Image.h @@ -127,14 +127,8 @@ public: static void setDefaultMipmapFilter(FilterMode f); static FilterMode getDefaultMipmapFilter(); - static bool hasNpot(); static bool hasAnisotropicFilteringSupport(); - static bool hasMipmapSupport(); - static bool hasMipmapSharpnessSupport(); - - static bool hasCompressedTextureSupport(); static bool hasCompressedTextureSupport(image::CompressedData::Format format); - static bool hasSRGBSupport(); private: @@ -151,9 +145,6 @@ private: // null if raw ImageData was used to create the texture. love::image::CompressedData *cdata; - // Real dimensions of the texture, if it was auto-padded to POT size. - int paddedWidth, paddedHeight; - // OpenGL texture identifier. GLuint texture; @@ -175,7 +166,6 @@ private: void preload(); - void uploadTexturePadded(); void uploadTexture(); void uploadCompressedMipmaps(); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index fea9ca877..8ab6de22d 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -57,6 +57,14 @@ void OpenGL::initContext() initOpenGLFunctions(); initVendor(); + contextInitialized = true; +} + +void OpenGL::setupContext() +{ + if (!contextInitialized) + return; + // Store the current color so we don't have to get it through GL later. GLfloat glcolor[4]; glGetFloatv(GL_CURRENT_COLOR, glcolor); @@ -80,37 +88,28 @@ void OpenGL::initContext() glGetIntegerv(GL_SCISSOR_BOX, (GLint *) &state.scissor.x); state.scissor.y = state.viewport.h - (state.scissor.y + state.scissor.h); - // Initialize multiple texture unit support for shaders, if available. + // Initialize multiple texture unit support for shaders. state.textureUnits.clear(); - if (Shader::isSupported()) + + GLint maxtextureunits; + glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtextureunits); + + state.textureUnits.resize(maxtextureunits, 0); + + GLenum curgltextureunit; + glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint *) &curgltextureunit); + + state.curTextureUnit = (int) curgltextureunit - GL_TEXTURE0; + + // Retrieve currently bound textures for each texture unit. + for (size_t i = 0; i < state.textureUnits.size(); i++) { - GLint maxtextureunits; - glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxtextureunits); - - state.textureUnits.resize(maxtextureunits, 0); - - GLenum curgltextureunit; - glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint *) &curgltextureunit); - - state.curTextureUnit = (int) curgltextureunit - GL_TEXTURE0; - - // Retrieve currently bound textures for each texture unit. - for (size_t i = 0; i < state.textureUnits.size(); i++) - { - glActiveTexture(GL_TEXTURE0 + i); - glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[i]); - } - - glActiveTexture(curgltextureunit); - } - else - { - // Multitexturing not supported, so we only have 1 texture unit. - state.textureUnits.resize(1, 0); - state.curTextureUnit = 0; - glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[0]); + glActiveTexture(GL_TEXTURE0 + i); + glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint *) &state.textureUnits[i]); } + glActiveTexture(curgltextureunit); + BlendState blend = {GL_ONE, GL_ONE, GL_ZERO, GL_ZERO, GL_FUNC_ADD}; setBlendState(blend); @@ -118,8 +117,6 @@ void OpenGL::initContext() createDefaultTexture(); state.lastPseudoInstanceID = -1; - - contextInitialized = true; } void OpenGL::deInitContext() @@ -158,31 +155,6 @@ void OpenGL::initVendor() void OpenGL::initOpenGLFunctions() { - // The functionality of the core and ARB VBOs are identical, so we can - // assign the pointers of the core functions to the names of the ARB - // functions, if the latter isn't supported but the former is. - if (GLEE_VERSION_1_5 && !GLEE_ARB_vertex_buffer_object) - { - glBindBufferARB = (GLEEPFNGLBINDBUFFERARBPROC) glBindBuffer; - glBufferDataARB = (GLEEPFNGLBUFFERDATAARBPROC) glBufferData; - glBufferSubDataARB = (GLEEPFNGLBUFFERSUBDATAARBPROC) glBufferSubData; - glDeleteBuffersARB = (GLEEPFNGLDELETEBUFFERSARBPROC) glDeleteBuffers; - glGenBuffersARB = (GLEEPFNGLGENBUFFERSARBPROC) glGenBuffers; - glGetBufferParameterivARB = (GLEEPFNGLGETBUFFERPARAMETERIVARBPROC) glGetBufferParameteriv; - glGetBufferPointervARB = (GLEEPFNGLGETBUFFERPOINTERVARBPROC) glGetBufferPointerv; - glGetBufferSubDataARB = (GLEEPFNGLGETBUFFERSUBDATAARBPROC) glGetBufferSubData; - glIsBufferARB = (GLEEPFNGLISBUFFERARBPROC) glIsBuffer; - glMapBufferARB = (GLEEPFNGLMAPBUFFERARBPROC) glMapBuffer; - glUnmapBufferARB = (GLEEPFNGLUNMAPBUFFERARBPROC) glUnmapBuffer; - } - - // Same deal for compressed textures. - if (GLEE_VERSION_1_3 && !GLEE_ARB_texture_compression) - { - glCompressedTexImage2DARB = (GLEEPFNGLCOMPRESSEDTEXIMAGE2DARBPROC) glCompressedTexImage2D; - glCompressedTexSubImage2DARB = (GLEEPFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) glCompressedTexSubImage2D; - glGetCompressedTexImageARB = (GLEEPFNGLGETCOMPRESSEDTEXIMAGEARBPROC) glGetCompressedTexImage; - } } void OpenGL::initMaxValues() @@ -195,18 +167,13 @@ void OpenGL::initMaxValues() glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); - if (Canvas::isSupported() && (GLEE_VERSION_2_0 || GLEE_ARB_draw_buffers)) - { - int maxattachments = 0; - glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxattachments); + int maxattachments = 0; + glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxattachments); - int maxdrawbuffers = 0; - glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers); + int maxdrawbuffers = 0; + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxdrawbuffers); - maxRenderTargets = std::min(maxattachments, maxdrawbuffers); - } - else - maxRenderTargets = 0; + maxRenderTargets = std::min(maxattachments, maxdrawbuffers); } void OpenGL::createDefaultTexture() @@ -369,28 +336,8 @@ OpenGL::Viewport OpenGL::getScissor() const void OpenGL::setBlendState(const BlendState &blend) { - if (GLEE_VERSION_1_4 || GLEE_ARB_imaging) - glBlendEquation(blend.func); - else if (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract) - glBlendEquationEXT(blend.func); - else - { - if (blend.func == GL_FUNC_REVERSE_SUBTRACT) - throw love::Exception("This graphics card does not support the subtractive blend mode!"); - // GL_FUNC_ADD is the default even without access to glBlendEquation, so that'll still work. - } - - if (blend.srcRGB == blend.srcA && blend.dstRGB == blend.dstA) - glBlendFunc(blend.srcRGB, blend.dstRGB); - else - { - if (GLEE_VERSION_1_4) - glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA); - else if (GLEE_EXT_blend_func_separate) - glBlendFuncSeparateEXT(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA); - else - throw love::Exception("This graphics card does not support separated rgb and alpha blend functions!"); - } + glBlendEquation(blend.func); + glBlendFuncSeparate(blend.srcRGB, blend.dstRGB, blend.srcA, blend.dstA); state.blend = blend; } @@ -406,12 +353,7 @@ void OpenGL::setTextureUnit(int textureunit) throw love::Exception("Invalid texture unit index (%d).", textureunit); if (textureunit != state.curTextureUnit) - { - if (state.textureUnits.size() > 1) - glActiveTexture(GL_TEXTURE0 + textureunit); - else - throw love::Exception("Multitexturing is not supported."); - } + glActiveTexture(GL_TEXTURE0 + textureunit); state.curTextureUnit = textureunit; } @@ -457,7 +399,7 @@ void OpenGL::deleteTexture(GLuint texture) glDeleteTextures(1, &texture); } -float OpenGL::setTextureFilter(graphics::Texture::Filter &f) +void OpenGL::setTextureFilter(graphics::Texture::Filter &f) { GLint gmin, gmag; @@ -502,8 +444,8 @@ float OpenGL::setTextureFilter(graphics::Texture::Filter &f) f.anisotropy = std::min(std::max(f.anisotropy, 1.0f), maxAnisotropy); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy); } - - return f.anisotropy; + else + f.anisotropy = 1.0f; } graphics::Texture::Filter OpenGL::getTextureFilter() diff --git a/src/modules/graphics/opengl/OpenGL.h b/src/modules/graphics/opengl/OpenGL.h index b912d4aac..1fd5e4c57 100644 --- a/src/modules/graphics/opengl/OpenGL.h +++ b/src/modules/graphics/opengl/OpenGL.h @@ -104,11 +104,16 @@ public: OpenGL(); /** - * Initializes some required context state based on current and default - * OpenGL state. Call this directly after creating an OpenGL context! + * Initializes the active OpenGL context. **/ void initContext(); + /** + * Sets up some required context state based on current and default OpenGL + * state. Call this directly after initializing an OpenGL context! + **/ + void setupContext(); + /** * Marks current context state as invalid and deletes OpenGL objects owned * by this class instance. Call this directly before potentially deleting @@ -214,9 +219,10 @@ public: /** * Sets the texture filter mode for the currently bound texture. - * Returns the actual amount of anisotropic filtering set. + * The anisotropy parameter of the argument is set to the actual amount of + * anisotropy that was used. **/ - float setTextureFilter(graphics::Texture::Filter &f); + void setTextureFilter(graphics::Texture::Filter &f); /** * Returns the texture filter mode for the currently bound texture. diff --git a/src/modules/graphics/opengl/Shader.cpp b/src/modules/graphics/opengl/Shader.cpp index 7b94e805c..ac7ee2de0 100644 --- a/src/modules/graphics/opengl/Shader.cpp +++ b/src/modules/graphics/opengl/Shader.cpp @@ -734,11 +734,7 @@ const std::map &Shader::getBoundRetainables() const std::string Shader::getGLSLVersion() { - const char *tmp = nullptr; - - // GL_SHADING_LANGUAGE_VERSION isn't available in OpenGL < 2.0. - if (GLEE_VERSION_2_0 || GLEE_ARB_shading_language_100) - tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION); + const char *tmp = (const char *) glGetString(GL_SHADING_LANGUAGE_VERSION); if (tmp == nullptr) return "0.0"; @@ -755,7 +751,7 @@ std::string Shader::getGLSLVersion() bool Shader::isSupported() { - return GLEE_VERSION_2_0 && getGLSLVersion() >= "1.2"; + return getGLSLVersion() >= "1.2"; } StringMap::Entry Shader::typeNameEntries[] = diff --git a/src/modules/graphics/opengl/VertexBuffer.cpp b/src/modules/graphics/opengl/VertexBuffer.cpp index 181c0aea8..79e90edb2 100644 --- a/src/modules/graphics/opengl/VertexBuffer.cpp +++ b/src/modules/graphics/opengl/VertexBuffer.cpp @@ -44,16 +44,7 @@ namespace opengl VertexBuffer *VertexBuffer::Create(size_t size, GLenum target, GLenum usage, MemoryBacking backing) { - try - { - // Try to create a VBO. - return new VBO(size, target, usage, backing); - } - catch(const love::Exception &) - { - // VBO not supported ... create regular array. - return new VertexArray(size, target, usage, backing); - } + return new VertexBuffer(size, target, usage, backing); } VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacking backing) @@ -63,68 +54,10 @@ VertexBuffer::VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacki , target(target) , usage(usage) , backing(backing) -{ -} - -VertexBuffer::~VertexBuffer() -{ -} - -// VertexArray - -VertexArray::VertexArray(size_t size, GLenum target, GLenum usage, MemoryBacking backing) - : VertexBuffer(size, target, usage, backing) - , buf(new char[size]) -{ -} - -VertexArray::~VertexArray() -{ - delete [] buf; -} - -void *VertexArray::map() -{ - is_mapped = true; - return buf; -} - -void VertexArray::unmap() -{ - is_mapped = false; -} - -void VertexArray::bind() -{ - is_bound = true; -} - -void VertexArray::unbind() -{ - is_bound = false; -} - -void VertexArray::fill(size_t offset, size_t size, const void *data) -{ - memcpy(buf + offset, data, size); -} - -const void *VertexArray::getPointer(size_t offset) const -{ - return buf + offset; -} - -// VBO - -VBO::VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing) - : VertexBuffer(size, target, usage, backing) , vbo(0) , memory_map(0) , is_dirty(true) { - if (!(GLEE_ARB_vertex_buffer_object || GLEE_VERSION_1_5)) - throw love::Exception("Not supported"); - if (getMemoryBacking() == BACKING_FULL) memory_map = malloc(getSize()); @@ -137,7 +70,7 @@ VBO::VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing) } } -VBO::~VBO() +VertexBuffer::~VertexBuffer() { if (vbo != 0) unload(false); @@ -146,7 +79,7 @@ VBO::~VBO() free(memory_map); } -void *VBO::map() +void *VertexBuffer::map() { if (is_mapped) return memory_map; @@ -160,7 +93,7 @@ void *VBO::map() if (is_dirty) { - glGetBufferSubDataARB(getTarget(), 0, (GLsizeiptr) getSize(), memory_map); + glGetBufferSubData(getTarget(), 0, (GLsizeiptr) getSize(), memory_map); is_dirty = false; } @@ -169,7 +102,7 @@ void *VBO::map() return memory_map; } -void VBO::unmap() +void VertexBuffer::unmap() { if (!is_mapped) return; @@ -178,36 +111,36 @@ void VBO::unmap() // bound here. if (!is_bound) { - glBindBufferARB(getTarget(), vbo); + glBindBuffer(getTarget(), vbo); is_bound = true; } // "orphan" current buffer to avoid implicit synchronisation on the GPU: // http://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf - glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); - glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); + glBufferData(getTarget(), (GLsizeiptr) getSize(), NULL, getUsage()); + glBufferData(getTarget(), (GLsizeiptr) getSize(), memory_map, getUsage()); is_mapped = false; } -void VBO::bind() +void VertexBuffer::bind() { if (!is_mapped) { - glBindBufferARB(getTarget(), vbo); + glBindBuffer(getTarget(), vbo); is_bound = true; } } -void VBO::unbind() +void VertexBuffer::unbind() { if (is_bound) - glBindBufferARB(getTarget(), 0); + glBindBuffer(getTarget(), 0); is_bound = false; } -void VBO::fill(size_t offset, size_t size, const void *data) +void VertexBuffer::fill(size_t offset, size_t size, const void *data) { if (is_mapped || getMemoryBacking() == BACKING_FULL) memcpy(static_cast(memory_map) + offset, data, size); @@ -217,7 +150,7 @@ void VBO::fill(size_t offset, size_t size, const void *data) // Not all systems have access to some faster paths... if (GLEE_APPLE_flush_buffer_range) { - void *mapdata = glMapBufferARB(getTarget(), GL_WRITE_ONLY); + void *mapdata = glMapBuffer(getTarget(), GL_WRITE_ONLY); if (mapdata) { @@ -228,12 +161,12 @@ void VBO::fill(size_t offset, size_t size, const void *data) glFlushMappedBufferRangeAPPLE(getTarget(), (GLintptr) offset, (GLsizei) size); } - glUnmapBufferARB(getTarget()); + glUnmapBuffer(getTarget()); } else { // Fall back to a possibly slower SubData (more chance of syncing.) - glBufferSubDataARB(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data); + glBufferSubData(getTarget(), (GLintptr) offset, (GLsizeiptr) size, data); } if (getMemoryBacking() != BACKING_FULL) @@ -241,24 +174,24 @@ void VBO::fill(size_t offset, size_t size, const void *data) } } -const void *VBO::getPointer(size_t offset) const +const void *VertexBuffer::getPointer(size_t offset) const { return BUFFER_OFFSET(offset); } -bool VBO::loadVolatile() +bool VertexBuffer::loadVolatile() { return load(true); } -void VBO::unloadVolatile() +void VertexBuffer::unloadVolatile() { unload(true); } -bool VBO::load(bool restore) +bool VertexBuffer::load(bool restore) { - glGenBuffersARB(1, &vbo); + glGenBuffers(1, &vbo); VertexBuffer::Bind bind(*this); @@ -275,13 +208,13 @@ bool VBO::load(bool restore) glBufferParameteriAPPLE(getTarget(), GL_BUFFER_FLUSHING_UNMAP_APPLE, GL_FALSE); // Note that if 'src' is '0', no data will be copied. - glBufferDataARB(getTarget(), (GLsizeiptr) getSize(), src, getUsage()); + glBufferData(getTarget(), (GLsizeiptr) getSize(), src, getUsage()); GLenum err = glGetError(); return (GL_NO_ERROR == err); } -void VBO::unload(bool save) +void VertexBuffer::unload(bool save) { // Save data before unloading, if we need to. if (save && getMemoryBacking() == BACKING_PARTIAL) @@ -294,7 +227,7 @@ void VBO::unload(bool save) is_mapped = mapped; } - glDeleteBuffersARB(1, &vbo); + glDeleteBuffers(1, &vbo); vbo = 0; } diff --git a/src/modules/graphics/opengl/VertexBuffer.h b/src/modules/graphics/opengl/VertexBuffer.h index d29710cc4..9a79fff96 100644 --- a/src/modules/graphics/opengl/VertexBuffer.h +++ b/src/modules/graphics/opengl/VertexBuffer.h @@ -36,15 +36,12 @@ namespace opengl { /** - * VertexBuffer is an abstraction over VBOs (Vertex Buffer Objects), which - * falls back to regular vertex arrays if VBOs are not supported. - * - * This allows code to take advantage of VBOs where available, but still - * work on older systems where it's *not* available. Everyone's happy. + * VertexBuffer is a thin abstraction over VBOs (Vertex Buffer Objects) and + * other OpenGL Buffer Objects. * * The class is (for now) meant for internal use. */ -class VertexBuffer +class VertexBuffer : public Volatile { public: @@ -61,11 +58,7 @@ public: }; /** - * Create a new VertexBuffer (either a plain vertex array, or a VBO), - * based on what's supported on the system. - * - * If VBOs are not supported, a plain vertex array will automatically - * be created and returned instead. + * Create a new VertexBuffer. * * @param size The size of the VertexBuffer (in bytes). * @param target GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER. @@ -86,7 +79,7 @@ public: VertexBuffer(size_t size, GLenum target, GLenum usage, MemoryBacking backing = BACKING_PARTIAL); /** - * Destructor. Does nothing, but must be declared virtual. + * Destructor. */ virtual ~VertexBuffer(); @@ -145,7 +138,7 @@ public: * * @return A pointer to memory which represents the buffer. */ - virtual void *map() = 0; + virtual void *map(); /** * Unmap a previously mapped VertexBuffer. The buffer must be unmapped @@ -153,18 +146,18 @@ public: * * The VertexBuffer must be bound to use this function. */ - virtual void unmap() = 0; + virtual void unmap(); /** * Bind the VertexBuffer to its specified target. * (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, etc). */ - virtual void bind() = 0; + virtual void bind(); /** * Unbind a prevously bound VertexBuffer. */ - virtual void unbind() = 0; + virtual void unbind(); /** * Fill a portion of the buffer with data. @@ -175,7 +168,7 @@ public: * @param size The size of the incoming data. * @param data Pointer to memory to copy data from. */ - virtual void fill(size_t offset, size_t size, const void *data) = 0; + virtual void fill(size_t offset, size_t size, const void *data); /** * Get a pointer which represents the specified byte offset. @@ -183,7 +176,11 @@ public: * @param offset The byte offset. (0 is first byte). * @return A pointer which represents the offset. */ - virtual const void *getPointer(size_t offset) const = 0; + virtual const void *getPointer(size_t offset) const; + + // Implements Volatile. + virtual bool loadVolatile(); + virtual void unloadVolatile(); /** * This helper class can bind a VertexArray temporarily, and @@ -214,11 +211,13 @@ public: // VertexBuffer to work on. VertexBuffer &buf; - }; + + }; // Bind class Mapper { public: + /** * Memory-maps a VertexBuffer. */ @@ -245,98 +244,11 @@ public: } private: + VertexBuffer &buf; void *elems; - }; -protected: - - // Whether the buffer is currently bound. - bool is_bound; - - // Whether the buffer is currently mapped to main memory. - bool is_mapped; - -private: - - // The size of the buffer, in bytes. - size_t size; - - // The target buffer object. (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER). - GLenum target; - - // Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW. - GLenum usage; - - // - MemoryBacking backing; -}; - -/** - * Implementation of VertexBuffer which uses plain arrays to store the data. - * - * This implementation should be supported everywhere, and acts as a fallback - * on systems which do not support VBOs. - */ -class VertexArray : public VertexBuffer -{ -public: - - /** - * @copydoc VertexBuffer(int, GLenum, GLenum, Backing) - */ - VertexArray(size_t size, GLenum target, GLenum usage, MemoryBacking backing); - - /** - * Frees the data we've allocated. - */ - virtual ~VertexArray(); - - // Implements VertexBuffer. - virtual void *map(); - virtual void unmap(); - virtual void bind(); - virtual void unbind(); - virtual void fill(size_t offset, size_t size, const void *data); - virtual const void *getPointer(size_t offset) const ; - -private: - // Holds the data. - char *buf; -}; - -/** - * Vertex Buffer Object (VBO) implementation of VertexBuffer. - * - * This will be used on all systems that support it. It's in general - * faster than vertex arrays, but especially in use-cases where there - * is no need to update the data every frame. - **/ -class VBO : public VertexBuffer, public Volatile -{ -public: - - /** - * @copydoc VertexBuffer(size_t, GLenum, GLenum, Backing) - **/ - VBO(size_t size, GLenum target, GLenum usage, MemoryBacking backing); - - /** - * Deletes the VBOs from OpenGL. - **/ - virtual ~VBO(); - - // Implements VertexBuffer. - virtual void *map(); - virtual void unmap(); - virtual void bind(); - virtual void unbind(); - virtual void fill(size_t offset, size_t size, const void *data); - virtual const void *getPointer(size_t offset) const ; - - // Implements Volatile. - bool loadVolatile(); - void unloadVolatile(); + }; // Mapper private: @@ -355,6 +267,25 @@ private: */ void unload(bool save); + + // Whether the buffer is currently bound. + bool is_bound; + + // Whether the buffer is currently mapped to main memory. + bool is_mapped; + + // The size of the buffer, in bytes. + size_t size; + + // The target buffer object. (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER). + GLenum target; + + // Usage hint. GL_[DYNAMIC, STATIC, STREAM]_DRAW. + GLenum usage; + + // + MemoryBacking backing; + // The VBO identifier. Assigned by OpenGL. GLuint vbo; @@ -365,7 +296,9 @@ private: // Set if the buffer was modified while operating on gpu memory // and needs to be synchronized. bool is_dirty; -}; + +}; // VertexBuffer + /** * VertexIndex manages one shared VertexBuffer that stores the indices for an diff --git a/src/modules/graphics/opengl/wrap_Graphics.cpp b/src/modules/graphics/opengl/wrap_Graphics.cpp index 31c62345d..b9c28b6c4 100644 --- a/src/modules/graphics/opengl/wrap_Graphics.cpp +++ b/src/modules/graphics/opengl/wrap_Graphics.cpp @@ -344,9 +344,6 @@ int w_newCanvas(lua_State *L) int w_newShader(lua_State *L) { - if (!Shader::isSupported()) - return luaL_error(L, "Sorry, your graphics card does not support shaders."); - // clamp stack to 2 elements lua_settop(L, 2); @@ -946,10 +943,6 @@ int w_isSupported(lua_State *L) switch (support) { - case Graphics::SUPPORT_CANVAS: - if (!Canvas::isSupported()) - supported = false; - break; case Graphics::SUPPORT_HDR_CANVAS: if (!Canvas::isHDRSupported()) supported = false; @@ -958,22 +951,6 @@ int w_isSupported(lua_State *L) if (!Canvas::isMultiCanvasSupported()) supported = false; break; - case Graphics::SUPPORT_SHADER: - if (!Shader::isSupported()) - supported = false; - break; - case Graphics::SUPPORT_NPOT: - if (!Image::hasNpot()) - supported = false; - break; - case Graphics::SUPPORT_SUBTRACTIVE: - if (!((GLEE_VERSION_1_4 || GLEE_ARB_imaging) || (GLEE_EXT_blend_minmax && GLEE_EXT_blend_subtract))) - supported = false; - break; - case Graphics::SUPPORT_MIPMAP: - if (!Image::hasMipmapSupport()) - supported = false; - break; case Graphics::SUPPORT_DXT: if (!Image::hasCompressedTextureSupport(image::CompressedData::FORMAT_DXT5)) supported = false; diff --git a/src/modules/window/Window.cpp b/src/modules/window/Window.cpp index 1e3216c8a..ecd0fb73b 100644 --- a/src/modules/window/Window.cpp +++ b/src/modules/window/Window.cpp @@ -74,6 +74,16 @@ bool Window::getConstant(Window::Setting in, const char *&out) return settings.find(in, out); } +bool Window::getConstant(const char *in, MessageBoxType &out) +{ + return messageBoxTypes.find(in, out); +} + +bool Window::getConstant(MessageBoxType in, const char *&out) +{ + return messageBoxTypes.find(in, out); +} + StringMap::Entry Window::settingEntries[] = { {"fullscreen", SETTING_FULLSCREEN}, @@ -100,5 +110,14 @@ StringMap::Entry Windo StringMap Window::fullscreenTypes(Window::fullscreenTypeEntries, sizeof(Window::fullscreenTypeEntries)); +StringMap::Entry Window::messageBoxTypeEntries[] = +{ + {"error", MESSAGEBOX_ERROR}, + {"warning", MESSAGEBOX_WARNING}, + {"info", MESSAGEBOX_INFO}, +}; + +StringMap Window::messageBoxTypes(Window::messageBoxTypeEntries, sizeof(Window::messageBoxTypeEntries)); + } // window } // love diff --git a/src/modules/window/Window.h b/src/modules/window/Window.h index b52900c66..9faf0390a 100644 --- a/src/modules/window/Window.h +++ b/src/modules/window/Window.h @@ -39,6 +39,14 @@ namespace window // whole thing here because it uses the Window::Type enum. struct WindowSettings; +enum MessageBoxType +{ + MESSAGEBOX_ERROR, + MESSAGEBOX_WARNING, + MESSAGEBOX_INFO, + MESSAGEBOX_MAX_ENUM +}; + class Window : public Module { public: @@ -119,6 +127,8 @@ public: virtual const void *getHandle() const = 0; + virtual void showMessageBox(MessageBoxType type, const char *title, const char *message) = 0; + //virtual static Window *createSingleton() = 0; //virtual static Window *getSingleton() = 0; // No virtual statics, of course, but you are supposed to implement these statics. @@ -129,6 +139,9 @@ public: static bool getConstant(const char *in, FullscreenType &out); static bool getConstant(FullscreenType in, const char *&out); + static bool getConstant(const char *in, MessageBoxType &out); + static bool getConstant(MessageBoxType in, const char *&out); + protected: static Window *singleton; @@ -141,6 +154,9 @@ private: static StringMap::Entry fullscreenTypeEntries[]; static StringMap fullscreenTypes; + static StringMap::Entry messageBoxTypeEntries[]; + static StringMap messageBoxTypes; + }; // Window struct WindowSettings diff --git a/src/modules/window/sdl/Window.cpp b/src/modules/window/sdl/Window.cpp index af8a0940e..0253296db 100644 --- a/src/modules/window/sdl/Window.cpp +++ b/src/modules/window/sdl/Window.cpp @@ -654,6 +654,27 @@ const void *Window::getHandle() const return window; } +void Window::showMessageBox(MessageBoxType type, const char *title, const char *message) +{ + SDL_MessageBoxFlags sdlflags; + + switch (type) + { + case MESSAGEBOX_ERROR: + sdlflags = SDL_MESSAGEBOX_ERROR; + break; + case MESSAGEBOX_WARNING: + sdlflags = SDL_MESSAGEBOX_WARNING; + break; + case MESSAGEBOX_INFO: + default: + sdlflags = SDL_MESSAGEBOX_INFORMATION; + break; + } + + SDL_ShowSimpleMessageBox(sdlflags, title, message, window); +} + love::window::Window *Window::createSingleton() { if (!singleton) diff --git a/src/modules/window/sdl/Window.h b/src/modules/window/sdl/Window.h index 3af5d46c5..9657ec03d 100644 --- a/src/modules/window/sdl/Window.h +++ b/src/modules/window/sdl/Window.h @@ -83,6 +83,8 @@ public: const void *getHandle() const; + void showMessageBox(MessageBoxType type, const char *title, const char *message); + static love::window::Window *createSingleton(); static love::window::Window *getSingleton(); diff --git a/src/scripts/boot.lua b/src/scripts/boot.lua index 183b1725f..dc7c6d148 100644 --- a/src/scripts/boot.lua +++ b/src/scripts/boot.lua @@ -1331,13 +1331,7 @@ function love.nogame() local create_rain function love.load() - -- Subtractive blending isn't supported on some ancient systems, so - -- we should make sure it still looks decent in that case. - if love.graphics.isSupported("subtractive") then - love.graphics.setBackgroundColor(137, 194, 218) - else - love.graphics.setBackgroundColor(11, 88, 123) - end + love.graphics.setBackgroundColor(137, 194, 218) local win_w = love.graphics.getWidth() local win_h = love.graphics.getHeight() @@ -1429,18 +1423,12 @@ function love.nogame() end local function draw_grid() - local blendmode = "subtractive" - if not love.graphics.isSupported("subtractive") then - -- We also change the background color in this case, so it looks OK. - blendmode = "additive" - end - local y = rain.spacing_y * rain.t local small_y = -rain.spacing_y + y / 2 local big_y = -rain.spacing_y + y - love.graphics.setBlendMode(blendmode) + love.graphics.setBlendMode("subtractive") love.graphics.setColor(255, 255, 255, 128) love.graphics.draw(rain.batch, -rain.spacing_x, small_y, 0, 0.5, 0.5) diff --git a/src/scripts/boot.lua.h b/src/scripts/boot.lua.h index 5e6f539ad..60919b0d9 100644 --- a/src/scripts/boot.lua.h +++ b/src/scripts/boot.lua.h @@ -4850,25 +4850,9 @@ const unsigned char boot_lua[] = 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x69, 0x6e, 0x0a, 0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x0a, - 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x62, - 0x6c, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x61, 0x6e, 0x63, 0x69, - 0x65, 0x6e, 0x74, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x2c, 0x20, 0x73, 0x6f, 0x0a, - 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x77, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6d, 0x61, 0x6b, - 0x65, 0x20, 0x73, 0x75, 0x72, 0x65, 0x20, 0x69, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x6c, 0x6f, - 0x6f, 0x6b, 0x73, 0x20, 0x64, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, - 0x20, 0x63, 0x61, 0x73, 0x65, 0x2e, 0x0a, - 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, - 0x2e, 0x69, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x28, 0x22, 0x73, 0x75, 0x62, 0x74, - 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, - 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, - 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, - 0x31, 0x33, 0x37, 0x2c, 0x20, 0x31, 0x39, 0x34, 0x2c, 0x20, 0x32, 0x31, 0x38, 0x29, 0x0a, - 0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a, - 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, - 0x65, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, - 0x31, 0x31, 0x2c, 0x20, 0x38, 0x38, 0x2c, 0x20, 0x31, 0x32, 0x33, 0x29, 0x0a, - 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, + 0x74, 0x42, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, + 0x33, 0x37, 0x2c, 0x20, 0x31, 0x39, 0x34, 0x2c, 0x20, 0x32, 0x31, 0x38, 0x29, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x69, 0x6e, 0x5f, 0x77, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69, 0x64, 0x74, 0x68, 0x28, 0x29, 0x0a, @@ -5024,18 +5008,6 @@ const unsigned char boot_lua[] = 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x67, 0x72, 0x69, 0x64, 0x28, 0x29, 0x0a, - 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x6d, 0x6f, 0x64, 0x65, 0x20, - 0x3d, 0x20, 0x22, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x0a, - 0x09, 0x09, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, - 0x68, 0x69, 0x63, 0x73, 0x2e, 0x69, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x28, 0x22, - 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, - 0x09, 0x09, 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x65, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, - 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x73, 0x65, - 0x2c, 0x20, 0x73, 0x6f, 0x20, 0x69, 0x74, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x73, 0x20, 0x4f, 0x4b, 0x2e, 0x0a, - 0x09, 0x09, 0x09, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x76, 0x65, 0x22, 0x0a, - 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x79, 0x20, 0x2a, 0x20, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x74, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x79, 0x20, 0x3d, 0x20, @@ -5044,8 +5016,8 @@ const unsigned char boot_lua[] = 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x62, 0x69, 0x67, 0x5f, 0x79, 0x20, 0x3d, 0x20, 0x2d, 0x72, 0x61, 0x69, 0x6e, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x79, 0x20, 0x2b, 0x20, 0x79, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, - 0x74, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x28, 0x62, 0x6c, 0x65, 0x6e, 0x64, 0x6d, 0x6f, - 0x64, 0x65, 0x29, 0x0a, + 0x74, 0x42, 0x6c, 0x65, 0x6e, 0x64, 0x4d, 0x6f, 0x64, 0x65, 0x28, 0x22, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x0a, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x32, 0x38, 0x29, 0x0a,