From defe811f6266ac20221a0ce9f6f39932fb625ade Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sun, 29 Jan 2023 16:02:39 -0400 Subject: [PATCH] Allow creating a mipmapped texture with less than the full mip range. When creating a texture with user-specified mipmaps, it no longer errors if all mipmaps down to 1x1 aren't present. Added a 'mipmapcount' field to the settings table in newImage and friends (only used when mipmaps are enabled for the texture.) If it's set, only mipmap levels up to the specified count will be created instead of always creating the full range. Old OpenGL ES 2 devices don't support loading a mipmapped texture without the full mipmap range, so there's a new 'mipmaprange' boolean field in the table returned by love.graphics.getSupported. --- src/modules/graphics/Graphics.cpp | 1 + src/modules/graphics/Graphics.h | 1 + src/modules/graphics/Texture.cpp | 31 ++++++++++++++++++------ src/modules/graphics/Texture.h | 2 ++ src/modules/graphics/metal/Graphics.mm | 3 ++- src/modules/graphics/opengl/Graphics.cpp | 3 ++- src/modules/graphics/opengl/OpenGL.cpp | 6 +++++ src/modules/graphics/opengl/Texture.cpp | 4 +++ src/modules/graphics/vulkan/Graphics.cpp | 3 ++- src/modules/graphics/wrap_Graphics.cpp | 5 ++++ 10 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index 85733e0be..3d688001f 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -2532,6 +2532,7 @@ STRINGMAP_CLASS_BEGIN(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, f { "copybuffertotexture", Graphics::FEATURE_COPY_BUFFER_TO_TEXTURE }, { "copytexturetobuffer", Graphics::FEATURE_COPY_TEXTURE_TO_BUFFER }, { "copyrendertargettobuffer", Graphics::FEATURE_COPY_RENDER_TARGET_TO_BUFFER }, + { "mipmaprange", Graphics::FEATURE_MIPMAP_RANGE }, } STRINGMAP_CLASS_END(Graphics, Graphics::Feature, Graphics::FEATURE_MAX_ENUM, feature) diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 328665ac9..df9917051 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -164,6 +164,7 @@ public: FEATURE_COPY_BUFFER_TO_TEXTURE, FEATURE_COPY_TEXTURE_TO_BUFFER, FEATURE_COPY_RENDER_TARGET_TO_BUFFER, + FEATURE_MIPMAP_RANGE, FEATURE_MAX_ENUM }; diff --git a/src/modules/graphics/Texture.cpp b/src/modules/graphics/Texture.cpp index 3cdd379ff..e9b7ab1f7 100644 --- a/src/modules/graphics/Texture.cpp +++ b/src/modules/graphics/Texture.cpp @@ -180,6 +180,9 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) , samplerState() , graphicsMemorySize(0) { + const auto &caps = gfx->getCapabilities(); + int requestedMipmapCount = settings.mipmapCount; + if (slices != nullptr && slices->getMipmapCount() > 0 && slices->getSliceCount() > 0) { texType = slices->getTextureType(); @@ -189,8 +192,15 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) int dataMipmaps = 1; if (slices->validate() && slices->getMipmapCount() > 1) + { dataMipmaps = slices->getMipmapCount(); + if (requestedMipmapCount > 0) + requestedMipmapCount = std::min(requestedMipmapCount, dataMipmaps); + else + requestedMipmapCount = dataMipmaps; + } + love::image::ImageDataBase *slice = slices->get(0, 0); format = slice->getFormat(); @@ -231,7 +241,17 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) mipmapsMode = MIPMAPS_MANUAL; if (mipmapsMode != MIPMAPS_NONE) - mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth); + { + int totalMipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth); + + if (requestedMipmapCount > 0) + mipmapCount = std::min(totalMipmapCount, requestedMipmapCount); + else + mipmapCount = totalMipmapCount; + + if (mipmapCount != totalMipmapCount && !caps.features[Graphics::FEATURE_MIPMAP_RANGE]) + throw love::Exception("Custom mipmap ranges for a texture are not supported on this system (%d mipmap levels are required but only %d levels were provided.)", totalMipmapCount, mipmapCount); + } const char *miperr = nullptr; if (mipmapsMode == MIPMAPS_AUTO && !supportsGenerateMipmaps(miperr)) @@ -288,7 +308,7 @@ Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices) throw love::Exception("The %s%s pixel format is not supported%s on this system.", fstr, readablestr, rtstr); } - if (!gfx->getCapabilities().textureTypes[texType]) + if (!caps.textureTypes[texType]) { const char *textypestr = "unknown"; Texture::getConstant(texType, textypestr); @@ -864,14 +884,8 @@ bool Texture::Slices::validate() const int w = firstdata->getWidth(); int h = firstdata->getHeight(); - int depth = textureType == TEXTURE_VOLUME ? slicecount : 1; PixelFormat format = firstdata->getFormat(); - int expectedmips = Texture::getTotalMipmapCount(w, h, depth); - - if (mipcount != expectedmips && mipcount != 1) - throw love::Exception("Texture does not have all required mipmap levels (expected %d, got %d)", expectedmips, mipcount); - if (textureType == TEXTURE_CUBE && w != h) throw love::Exception("Cube textures must have equal widths and heights for each cube face."); @@ -947,6 +961,7 @@ static StringMap::Entry setting { "height", Texture::SETTING_HEIGHT }, { "layers", Texture::SETTING_LAYERS }, { "mipmaps", Texture::SETTING_MIPMAPS }, + { "mipmapcount", Texture::SETTING_MIPMAP_COUNT }, { "format", Texture::SETTING_FORMAT }, { "linear", Texture::SETTING_LINEAR }, { "type", Texture::SETTING_TYPE }, diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index b9da6a44d..2132bf741 100644 --- a/src/modules/graphics/Texture.h +++ b/src/modules/graphics/Texture.h @@ -166,6 +166,7 @@ public: SETTING_HEIGHT, SETTING_LAYERS, SETTING_MIPMAPS, + SETTING_MIPMAP_COUNT, SETTING_FORMAT, SETTING_LINEAR, SETTING_TYPE, @@ -185,6 +186,7 @@ public: int layers = 1; // depth for 3D textures TextureType type = TEXTURE_2D; MipmapsMode mipmaps = MIPMAPS_NONE; + int mipmapCount = 0; // only used when > 0. PixelFormat format = PIXELFORMAT_NORMAL; bool linear = false; float dpiScale = 1.0f; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index acbbe3d3b..f6440affa 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -2172,7 +2172,8 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = true; capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true; capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = true; - static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + capabilities.features[FEATURE_MIPMAP_RANGE] = true; + static_assert(FEATURE_MAX_ENUM == 18, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf capabilities.limits[LIMIT_POINT_SIZE] = 511; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index b37bcdca2..33b25a887 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -1661,7 +1661,8 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = gl.isCopyBufferToTextureSupported(); capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = gl.isCopyTextureToBufferSupported(); capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = gl.isCopyRenderTargetToBufferSupported(); - static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + capabilities.features[FEATURE_MIPMAP_RANGE] = GLAD_VERSION_1_2 || GLAD_ES_VERSION_3_0; + static_assert(FEATURE_MAX_ENUM == 18, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize(); capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize(); diff --git a/src/modules/graphics/opengl/OpenGL.cpp b/src/modules/graphics/opengl/OpenGL.cpp index 3bd3ed0d8..e96a15765 100644 --- a/src/modules/graphics/opengl/OpenGL.cpp +++ b/src/modules/graphics/opengl/OpenGL.cpp @@ -1388,6 +1388,12 @@ bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelform GLenum gltarget = getGLTextureType(target); TextureFormat fmt = convertPixelFormat(pixelformat, false, isSRGB); + // This shouldn't be needed for glTexStorage, but some drivers don't follow + // the spec apparently. + // https://stackoverflow.com/questions/13859061/does-an-immutable-texture-need-a-gl-texture-max-level + if (GLAD_VERSION_1_2 || GLAD_ES_VERSION_3_0) + glTexParameteri(gltarget, GL_TEXTURE_MAX_LEVEL, levels - 1); + if (fmt.swizzled) { glTexParameteri(gltarget, GL_TEXTURE_SWIZZLE_R, fmt.swizzle[0]); diff --git a/src/modules/graphics/opengl/Texture.cpp b/src/modules/graphics/opengl/Texture.cpp index c8eef069b..70fdbd344 100644 --- a/src/modules/graphics/opengl/Texture.cpp +++ b/src/modules/graphics/opengl/Texture.cpp @@ -264,6 +264,10 @@ void Texture::createTexture() if (!isCompressed()) gl.rawTexStorage(texType, mipcount, format, sRGB, pixelWidth, pixelHeight, texType == TEXTURE_VOLUME ? depth : layers); + // rawTexStorage handles this for uncompressed textures. + if (isCompressed() && (GLAD_VERSION_1_1 || GLAD_ES_VERSION_3_0)) + glTexParameteri(gltype, GL_TEXTURE_MAX_LEVEL, mipcount - 1); + int w = pixelWidth; int h = pixelHeight; int d = depth; diff --git a/src/modules/graphics/vulkan/Graphics.cpp b/src/modules/graphics/vulkan/Graphics.cpp index 768d4056e..05b1d2e23 100644 --- a/src/modules/graphics/vulkan/Graphics.cpp +++ b/src/modules/graphics/vulkan/Graphics.cpp @@ -566,7 +566,8 @@ void Graphics::initCapabilities() capabilities.features[FEATURE_COPY_BUFFER_TO_TEXTURE] = true; capabilities.features[FEATURE_COPY_TEXTURE_TO_BUFFER] = true; capabilities.features[FEATURE_COPY_RENDER_TARGET_TO_BUFFER] = true; - static_assert(FEATURE_MAX_ENUM == 17, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); + capabilities.features[FEATURE_MIPMAP_RANGE] = true; + static_assert(FEATURE_MAX_ENUM == 18, "Graphics::initCapabilities must be updated when adding a new graphics feature!"); VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(physicalDevice, &properties); diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index d50c19d24..bec8225b7 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -781,6 +781,11 @@ static void luax_checktexturesettings(lua_State *L, int idx, bool opt, bool chec } lua_pop(L, 1); + lua_getfield(L, idx, Texture::getConstant(Texture::SETTING_MIPMAP_COUNT)); + if (!lua_isnoneornil(L, -1)) + s.mipmapCount = (int) luaL_checkinteger(L, -1); + lua_pop(L, 1); + s.linear = luax_boolflag(L, idx, Texture::getConstant(Texture::SETTING_LINEAR), s.linear); s.msaa = luax_intflag(L, idx, Texture::getConstant(Texture::SETTING_MSAA), s.msaa);