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);