Browse Source

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.
Sasha Szpakowski 2 years ago
parent
commit
defe811f62

+ 1 - 0
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)
 

+ 1 - 0
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
 	};
 

+ 23 - 8
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<Texture::SettingType, Texture::SETTING_MAX_ENUM>::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          },

+ 2 - 0
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;

+ 2 - 1
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;

+ 2 - 1
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();

+ 6 - 0
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]);

+ 4 - 0
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;

+ 2 - 1
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);

+ 5 - 0
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);