Browse Source

Refactor sampler state parameters for textures.

Alex Szpakowski 5 years ago
parent
commit
f9248d478c

+ 0 - 3
src/modules/graphics/Canvas.cpp

@@ -64,10 +64,7 @@ Canvas::Canvas(const Settings &settings)
 		throw love::Exception("Non-readable and MSAA textures cannot have mipmaps.");
 		throw love::Exception("Non-readable and MSAA textures cannot have mipmaps.");
 
 
 	if (settings.mipmaps != MIPMAPS_NONE)
 	if (settings.mipmaps != MIPMAPS_NONE)
-	{
 		mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth);
 		mipmapCount = getTotalMipmapCount(pixelWidth, pixelHeight, depth);
-		filter.mipmap = defaultMipmapFilter;
-	}
 
 
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
 	const Graphics::Capabilities &caps = gfx->getCapabilities();
 	const Graphics::Capabilities &caps = gfx->getCapabilities();

+ 14 - 10
src/modules/graphics/Font.cpp

@@ -47,18 +47,20 @@ int Font::fontCount = 0;
 
 
 const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub;
 const vertex::CommonFormat Font::vertexFormat = vertex::CommonFormat::XYf_STus_RGBAub;
 
 
-Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
+Font::Font(love::font::Rasterizer *r, const SamplerState &s)
 	: rasterizers({r})
 	: rasterizers({r})
 	, height(r->getHeight())
 	, height(r->getHeight())
 	, lineHeight(1)
 	, lineHeight(1)
 	, textureWidth(128)
 	, textureWidth(128)
 	, textureHeight(128)
 	, textureHeight(128)
-	, filter(f)
+	, samplerState()
 	, dpiScale(r->getDPIScale())
 	, dpiScale(r->getDPIScale())
 	, useSpacesAsTab(false)
 	, useSpacesAsTab(false)
 	, textureCacheID(0)
 	, textureCacheID(0)
 {
 {
-	filter.mipmap = Texture::FILTER_NONE;
+	samplerState.minFilter = s.minFilter;
+	samplerState.magFilter = s.magFilter;
+	samplerState.maxAnisotropy = s.maxAnisotropy;
 
 
 	// Try to find the best texture size match for the font size. default to the
 	// Try to find the best texture size match for the font size. default to the
 	// largest texture size if no rough match is found.
 	// largest texture size if no rough match is found.
@@ -150,7 +152,7 @@ void Font::createTexture()
 
 
 	Image::Settings settings;
 	Image::Settings settings;
 	image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings);
 	image = gfx->newImage(TEXTURE_2D, pixelFormat, size.width, size.height, 1, settings);
-	image->setFilter(filter);
+	image->setSamplerState(samplerState);
 
 
 	{
 	{
 		size_t bpp = getPixelFormatSize(pixelFormat);
 		size_t bpp = getPixelFormatSize(pixelFormat);
@@ -918,17 +920,19 @@ float Font::getLineHeight() const
 	return lineHeight;
 	return lineHeight;
 }
 }
 
 
-void Font::setFilter(const Texture::Filter &f)
+void Font::setSamplerState(const SamplerState &s)
 {
 {
-	for (const auto &image : images)
-		image->setFilter(f);
+	samplerState.minFilter = s.minFilter;
+	samplerState.magFilter = s.magFilter;
+	samplerState.maxAnisotropy = s.maxAnisotropy;
 
 
-	filter = f;
+	for (const auto &image : images)
+		image->setSamplerState(samplerState);
 }
 }
 
 
-const Texture::Filter &Font::getFilter() const
+const SamplerState &Font::getSamplerState() const
 {
 {
-	return filter;
+	return samplerState;
 }
 }
 
 
 int Font::getAscent() const
 int Font::getAscent() const

+ 4 - 4
src/modules/graphics/Font.h

@@ -96,7 +96,7 @@ public:
 		int vertexcount;
 		int vertexcount;
 	};
 	};
 
 
-	Font(love::font::Rasterizer *r, const Texture::Filter &filter);
+	Font(love::font::Rasterizer *r, const SamplerState &samplerState);
 
 
 	virtual ~Font();
 	virtual ~Font();
 
 
@@ -158,8 +158,8 @@ public:
 	 **/
 	 **/
 	float getLineHeight() const;
 	float getLineHeight() const;
 
 
-	void setFilter(const Texture::Filter &f);
-	const Texture::Filter &getFilter() const;
+	void setSamplerState(const SamplerState &s);
+	const SamplerState &getSamplerState() const;
 
 
 	// Extra font metrics
 	// Extra font metrics
 	int getAscent() const;
 	int getAscent() const;
@@ -227,7 +227,7 @@ private:
 
 
 	PixelFormat pixelFormat;
 	PixelFormat pixelFormat;
 
 
-	Texture::Filter filter;
+	SamplerState samplerState;
 
 
 	float dpiScale;
 	float dpiScale;
 
 

+ 10 - 28
src/modules/graphics/Graphics.cpp

@@ -184,19 +184,19 @@ Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
 	return new Quad(v, sw, sh);
 	return new Quad(v, sw, sh);
 }
 }
 
 
-Font *Graphics::newFont(love::font::Rasterizer *data, const Texture::Filter &filter)
+Font *Graphics::newFont(love::font::Rasterizer *data)
 {
 {
-	return new Font(data, filter);
+	return new Font(data, states.back().defaultSamplerState);
 }
 }
 
 
-Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter)
+Font *Graphics::newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting)
 {
 {
 	auto fontmodule = Module::getInstance<font::Font>(M_FONT);
 	auto fontmodule = Module::getInstance<font::Font>(M_FONT);
 	if (!fontmodule)
 	if (!fontmodule)
 		throw love::Exception("Font module has not been loaded.");
 		throw love::Exception("Font module has not been loaded.");
 
 
 	StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(size, hinting), Acquire::NORETAIN);
 	StrongRef<font::Rasterizer> r(fontmodule->newTrueTypeRasterizer(size, hinting), Acquire::NORETAIN);
-	return newFont(r.get(), filter);
+	return newFont(r.get());
 }
 }
 
 
 Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale)
 Video *Graphics::newVideo(love::video::VideoStream *stream, float dpiscale)
@@ -402,8 +402,7 @@ void Graphics::restoreState(const DisplayState &s)
 	setColorMask(s.colorMask);
 	setColorMask(s.colorMask);
 	setWireframe(s.wireframe);
 	setWireframe(s.wireframe);
 
 
-	setDefaultFilter(s.defaultFilter);
-	setDefaultMipmapFilter(s.defaultMipmapFilter, s.defaultMipmapSharpness);
+	setDefaultSamplerState(s.defaultSamplerState);
 }
 }
 
 
 void Graphics::restoreStateChecked(const DisplayState &s)
 void Graphics::restoreStateChecked(const DisplayState &s)
@@ -479,8 +478,7 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 	if (s.wireframe != cur.wireframe)
 	if (s.wireframe != cur.wireframe)
 		setWireframe(s.wireframe);
 		setWireframe(s.wireframe);
 
 
-	setDefaultFilter(s.defaultFilter);
-	setDefaultMipmapFilter(s.defaultMipmapFilter, s.defaultMipmapSharpness);
+	setDefaultSamplerState(s.defaultSamplerState);
 }
 }
 
 
 Colorf Graphics::getColor() const
 Colorf Graphics::getColor() const
@@ -923,30 +921,14 @@ const BlendState &Graphics::getBlendState() const
 	return states.back().blend;
 	return states.back().blend;
 }
 }
 
 
-void Graphics::setDefaultFilter(const Texture::Filter &f)
+void Graphics::setDefaultSamplerState(const SamplerState &s)
 {
 {
-	Texture::defaultFilter = f;
-	states.back().defaultFilter = f;
+	states.back().defaultSamplerState = s;
 }
 }
 
 
-const Texture::Filter &Graphics::getDefaultFilter() const
+const SamplerState &Graphics::getDefaultSamplerState() const
 {
 {
-	return Texture::defaultFilter;
-}
-
-void Graphics::setDefaultMipmapFilter(Texture::FilterMode filter, float sharpness)
-{
-	Texture::defaultMipmapFilter = filter;
-	Texture::defaultMipmapSharpness = sharpness;
-
-	states.back().defaultMipmapFilter = filter;
-	states.back().defaultMipmapSharpness = sharpness;
-}
-
-void Graphics::getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpness) const
-{
-	*filter = Texture::defaultMipmapFilter;
-	*sharpness = Texture::defaultMipmapSharpness;
+	return states.back().defaultSamplerState;
 }
 }
 
 
 void Graphics::setLineWidth(float width)
 void Graphics::setLineWidth(float width)

+ 7 - 16
src/modules/graphics/Graphics.h

@@ -433,8 +433,8 @@ public:
 	virtual Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) = 0;
 	virtual Image *newImage(TextureType textype, PixelFormat format, int width, int height, int slices, const Image::Settings &settings) = 0;
 
 
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
 	Quad *newQuad(Quad::Viewport v, double sw, double sh);
-	Font *newFont(love::font::Rasterizer *data, const Texture::Filter &filter = Texture::defaultFilter);
-	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting, const Texture::Filter &filter = Texture::defaultFilter);
+	Font *newFont(love::font::Rasterizer *data);
+	Font *newDefaultFont(int size, font::TrueTypeRasterizer::Hinting hinting);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 	Video *newVideo(love::video::VideoStream *stream, float dpiscale);
 
 
 	SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage);
 	SpriteBatch *newSpriteBatch(Texture *texture, int size, vertex::Usage usage);
@@ -620,20 +620,14 @@ public:
 	const BlendState &getBlendState() const;
 	const BlendState &getBlendState() const;
 
 
 	/**
 	/**
-	 * Sets the default filter for images, canvases, and fonts.
+	 * Sets the default sampler state for images, canvases, and fonts.
 	 **/
 	 **/
-	void setDefaultFilter(const Texture::Filter &f);
+	void setDefaultSamplerState(const SamplerState &s);
 
 
 	/**
 	/**
-	 * Gets the default filter for images, canvases, and fonts.
+	 * Gets the default sampler state for images, canvases, and fonts.
 	 **/
 	 **/
-	const Texture::Filter &getDefaultFilter() const;
-
-	/**
-	 * Default Image mipmap filter mode and sharpness values.
-	 **/
-	void setDefaultMipmapFilter(Texture::FilterMode filter, float sharpness);
-	void getDefaultMipmapFilter(Texture::FilterMode *filter, float *sharpness) const;
+	const SamplerState &getDefaultSamplerState() const;
 
 
 	/**
 	/**
 	 * Sets the line width.
 	 * Sets the line width.
@@ -920,10 +914,7 @@ protected:
 
 
 		bool wireframe = false;
 		bool wireframe = false;
 
 
-		Texture::Filter defaultFilter = Texture::Filter();
-
-		Texture::FilterMode defaultMipmapFilter = Texture::FILTER_LINEAR;
-		float defaultMipmapSharpness = 0.0f;
+		SamplerState defaultSamplerState = SamplerState();
 	};
 	};
 
 
 	struct StreamBufferState
 	struct StreamBufferState

+ 0 - 3
src/modules/graphics/Image.cpp

@@ -103,9 +103,6 @@ void Image::init(PixelFormat fmt, int w, int h, const Settings &settings)
 
 
 	mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getTotalMipmapCount(w, h, depth);
 	mipmapCount = mipmapsType == MIPMAPS_NONE ? 1 : getTotalMipmapCount(w, h, depth);
 
 
-	if (mipmapCount > 1)
-		filter.mipmap = defaultMipmapFilter;
-
 	initQuad();
 	initQuad();
 
 
 	++imageCount;
 	++imageCount;

+ 1 - 1
src/modules/graphics/Shader.cpp

@@ -125,7 +125,7 @@ void Shader::checkMainTexture(Texture *tex) const
 	if (!tex->isReadable())
 	if (!tex->isReadable())
 		throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
 		throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
 
 
-	checkMainTextureType(tex->getTextureType(), tex->getDepthSampleMode().hasValue);
+	checkMainTextureType(tex->getTextureType(), tex->getSamplerState().depthSampleMode.hasValue);
 }
 }
 
 
 bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err)
 bool Shader::validate(ShaderStage *vertex, ShaderStage *pixel, std::string &err)

+ 136 - 104
src/modules/graphics/Texture.cpp

@@ -33,11 +33,129 @@ namespace love
 namespace graphics
 namespace graphics
 {
 {
 
 
-love::Type Texture::type("Texture", &Drawable::type);
+uint64 SamplerState::toKey() const
+{
+	union { float f; uint32 i; } conv;
+	conv.f = lodBias;
+
+	return (minFilter << 0) | (magFilter << 1) | (mipmapFilter << 2)
+	     | (wrapU << 4) | (wrapV << 7) | (wrapW << 10)
+	     | (maxAnisotropy << 12) | (minLod << 16) | (maxLod << 20)
+	     | (depthSampleMode.hasValue << 24) | (depthSampleMode.value << 25)
+	     | ((uint64)conv.i << 32);
+}
+
+SamplerState SamplerState::fromKey(uint64 key)
+{
+	const uint32 BITS_1 = 0x1;
+	const uint32 BITS_2 = 0x3;
+	const uint32 BITS_3 = 0x7;
+	const uint32 BITS_4 = 0xF;
+
+	SamplerState s;
+
+	s.minFilter = (FilterMode) ((key >> 0) & BITS_1);
+	s.magFilter = (FilterMode) ((key >> 1) & BITS_1);
+	s.mipmapFilter = (MipmapFilterMode) ((key >> 2) & BITS_2);
+
+	s.wrapU = (WrapMode) ((key >> 4 ) & BITS_3);
+	s.wrapV = (WrapMode) ((key >> 7 ) & BITS_3);
+	s.wrapW = (WrapMode) ((key >> 10) & BITS_3);
+
+	s.maxAnisotropy = (key >> 12) & BITS_4;
+
+	s.minLod = (key >> 16) & BITS_4;
+	s.maxLod = (key >> 20) & BITS_4;
+
+	s.depthSampleMode.hasValue = ((key >> 24) & BITS_1) != 0;
+	s.depthSampleMode.value = (CompareMode) ((key >> 25) & BITS_4);
+
+	union { float f; uint32 i; } conv;
+	conv.i = (uint32) (key >> 32);
+	s.lodBias = conv.f;
+
+	return s;
+}
+
+bool SamplerState::isClampZeroOrOne(WrapMode w)
+{
+	return w == WRAP_CLAMP_ONE || w == WRAP_CLAMP_ZERO;
+}
+
+static StringMap<SamplerState::FilterMode, SamplerState::FILTER_MAX_ENUM>::Entry filterModeEntries[] =
+{
+	{ "linear",  SamplerState::FILTER_LINEAR  },
+	{ "nearest", SamplerState::FILTER_NEAREST },
+};
+
+static StringMap<SamplerState::FilterMode, SamplerState::FILTER_MAX_ENUM> filterModes(filterModeEntries, sizeof(filterModeEntries));
+
+static StringMap<SamplerState::MipmapFilterMode, SamplerState::MIPMAP_FILTER_MAX_ENUM>::Entry mipmapFilterModeEntries[] =
+{
+	{ "none",    SamplerState::MIPMAP_FILTER_NONE    },
+	{ "linear",  SamplerState::MIPMAP_FILTER_LINEAR  },
+	{ "nearest", SamplerState::MIPMAP_FILTER_NEAREST },
+};
+
+static StringMap<SamplerState::MipmapFilterMode, SamplerState::MIPMAP_FILTER_MAX_ENUM> mipmapFilterModes(mipmapFilterModeEntries, sizeof(mipmapFilterModeEntries));
+
+static StringMap<SamplerState::WrapMode, SamplerState::WRAP_MAX_ENUM>::Entry wrapModeEntries[] =
+{
+	{ "clamp",          SamplerState::WRAP_CLAMP           },
+	{ "clampzero",      SamplerState::WRAP_CLAMP_ZERO      },
+	{ "clampone",       SamplerState::WRAP_CLAMP_ONE       },
+	{ "repeat",         SamplerState::WRAP_REPEAT          },
+	{ "mirroredrepeat", SamplerState::WRAP_MIRRORED_REPEAT },
+};
+
+static StringMap<SamplerState::WrapMode, SamplerState::WRAP_MAX_ENUM> wrapModes(wrapModeEntries, sizeof(wrapModeEntries));
+
+bool SamplerState::getConstant(const char *in, FilterMode &out)
+{
+	return filterModes.find(in, out);
+}
+
+bool SamplerState::getConstant(FilterMode in, const char *&out)
+{
+	return filterModes.find(in, out);
+}
+
+std::vector<std::string> SamplerState::getConstants(FilterMode)
+{
+	return filterModes.getNames();
+}
+
+bool SamplerState::getConstant(const char *in, MipmapFilterMode &out)
+{
+	return mipmapFilterModes.find(in, out);
+}
+
+bool SamplerState::getConstant(MipmapFilterMode in, const char *&out)
+{
+	return mipmapFilterModes.find(in, out);
+}
+
+std::vector<std::string> SamplerState::getConstants(MipmapFilterMode)
+{
+	return mipmapFilterModes.getNames();
+}
+
+bool SamplerState::getConstant(const char *in, WrapMode &out)
+{
+	return wrapModes.find(in, out);
+}
+
+bool SamplerState::getConstant(WrapMode in, const char *&out)
+{
+	return wrapModes.find(in, out);
+}
+
+std::vector<std::string> SamplerState::getConstants(WrapMode)
+{
+	return wrapModes.getNames();
+}
 
 
-Texture::Filter Texture::defaultFilter;
-Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR;
-float Texture::defaultMipmapSharpness = 0.0f;
+love::Type Texture::type("Texture", &Drawable::type);
 int64 Texture::totalGraphicsMemory = 0;
 int64 Texture::totalGraphicsMemory = 0;
 
 
 Texture::Texture(TextureType texType)
 Texture::Texture(TextureType texType)
@@ -51,11 +169,12 @@ Texture::Texture(TextureType texType)
 	, mipmapCount(1)
 	, mipmapCount(1)
 	, pixelWidth(0)
 	, pixelWidth(0)
 	, pixelHeight(0)
 	, pixelHeight(0)
-	, filter(defaultFilter)
-	, wrap()
-	, mipmapSharpness(defaultMipmapSharpness)
+	, samplerState()
 	, graphicsMemorySize(0)
 	, graphicsMemorySize(0)
 {
 {
+	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+	if (gfx != nullptr)
+		samplerState = gfx->getDefaultSamplerState();
 }
 }
 
 
 Texture::~Texture()
 Texture::~Texture()
@@ -252,45 +371,25 @@ float Texture::getDPIScale() const
 	return (float) pixelHeight / (float) height;
 	return (float) pixelHeight / (float) height;
 }
 }
 
 
-void Texture::setFilter(const Filter &f)
+void Texture::setSamplerState(const SamplerState &s)
 {
 {
-	if (!validateFilter(f, getMipmapCount() > 1))
-	{
-		if (f.mipmap != FILTER_NONE && getMipmapCount() == 1)
-			throw love::Exception("Non-mipmapped texture cannot have mipmap filtering.");
-		else
-			throw love::Exception("Invalid texture filter.");
-	}
+	if (s.depthSampleMode.hasValue && (!readable || !isPixelFormatDepthStencil(format)))
+		throw love::Exception("Only readable depth textures can have a depth sample compare mode.");
 
 
 	Graphics::flushStreamDrawsGlobal();
 	Graphics::flushStreamDrawsGlobal();
 
 
-	filter = f;
-}
-
-const Texture::Filter &Texture::getFilter() const
-{
-	return filter;
-}
-
-const Texture::Wrap &Texture::getWrap() const
-{
-	return wrap;
-}
+	samplerState = s;
 
 
-float Texture::getMipmapSharpness() const
-{
-	return mipmapSharpness;
-}
+	if (samplerState.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE && getMipmapCount() == 1)
+		samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
 
 
-void Texture::setDepthSampleMode(Optional<CompareMode> mode)
-{
-	if (mode.hasValue && (!readable || !isPixelFormatDepthStencil(format)))
-		throw love::Exception("Only readable depth textures can have a depth sample compare mode.");
+	if (texType == TEXTURE_CUBE)
+		samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP;
 }
 }
 
 
-Optional<CompareMode> Texture::getDepthSampleMode() const
+const SamplerState &Texture::getSamplerState() const
 {
 {
-	return depthCompareMode;
+	return samplerState;
 }
 }
 
 
 Quad *Texture::getQuad() const
 Quad *Texture::getQuad() const
@@ -298,23 +397,6 @@ Quad *Texture::getQuad() const
 	return quad;
 	return quad;
 }
 }
 
 
-bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed)
-{
-	if (!mipmapsAllowed && f.mipmap != FILTER_NONE)
-		return false;
-
-	if (f.mag != FILTER_LINEAR && f.mag != FILTER_NEAREST)
-		return false;
-
-	if (f.min != FILTER_LINEAR && f.min != FILTER_NEAREST)
-		return false;
-
-	if (f.mipmap != FILTER_LINEAR && f.mipmap != FILTER_NEAREST && f.mipmap != FILTER_NONE)
-		return false;
-
-	return true;
-}
-
 int Texture::getTotalMipmapCount(int w, int h)
 int Texture::getTotalMipmapCount(int w, int h)
 {
 {
 	return (int) log2(std::max(w, h)) + 1;
 	return (int) log2(std::max(w, h)) + 1;
@@ -556,36 +638,6 @@ std::vector<std::string> Texture::getConstants(TextureType)
 	return texTypes.getNames();
 	return texTypes.getNames();
 }
 }
 
 
-bool Texture::getConstant(const char *in, FilterMode &out)
-{
-	return filterModes.find(in, out);
-}
-
-bool Texture::getConstant(FilterMode in, const char *&out)
-{
-	return filterModes.find(in, out);
-}
-
-std::vector<std::string> Texture::getConstants(FilterMode)
-{
-	return filterModes.getNames();
-}
-
-bool Texture::getConstant(const char *in, WrapMode &out)
-{
-	return wrapModes.find(in, out);
-}
-
-bool Texture::getConstant(WrapMode in, const char *&out)
-{
-	return wrapModes.find(in, out);
-}
-
-std::vector<std::string> Texture::getConstants(WrapMode)
-{
-	return wrapModes.getNames();
-}
-
 StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry Texture::texTypeEntries[] =
 StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry Texture::texTypeEntries[] =
 {
 {
 	{ "2d", TEXTURE_2D },
 	{ "2d", TEXTURE_2D },
@@ -596,25 +648,5 @@ StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry Texture::texTypeEntries[] =
 
 
 StringMap<TextureType, TEXTURE_MAX_ENUM> Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries));
 StringMap<TextureType, TEXTURE_MAX_ENUM> Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries));
 
 
-StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
-{
-	{ "linear", FILTER_LINEAR },
-	{ "nearest", FILTER_NEAREST },
-	{ "none", FILTER_NONE },
-};
-
-StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM> Texture::filterModes(Texture::filterModeEntries, sizeof(Texture::filterModeEntries));
-
-StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEntries[] =
-{
-	{ "clamp", WRAP_CLAMP },
-	{ "clampzero", WRAP_CLAMP_ZERO },
-	{ "clampone", WRAP_CLAMP_ONE },
-	{ "repeat", WRAP_REPEAT },
-	{ "mirroredrepeat", WRAP_MIRRORED_REPEAT },
-};
-
-StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
-
 } // graphics
 } // graphics
 } // love
 } // love

+ 53 - 61
src/modules/graphics/Texture.h

@@ -55,16 +55,8 @@ enum TextureType
 	TEXTURE_MAX_ENUM
 	TEXTURE_MAX_ENUM
 };
 };
 
 
-/**
- * Base class for 2D textures. All textures can be drawn with Quads, have a
- * width and height, and have filter and wrap modes.
- **/
-class Texture : public Drawable, public Resource
+struct SamplerState
 {
 {
-public:
-
-	static love::Type type;
-
 	enum WrapMode
 	enum WrapMode
 	{
 	{
 		WRAP_CLAMP,
 		WRAP_CLAMP,
@@ -77,26 +69,63 @@ public:
 
 
 	enum FilterMode
 	enum FilterMode
 	{
 	{
-		FILTER_NONE,
 		FILTER_LINEAR,
 		FILTER_LINEAR,
 		FILTER_NEAREST,
 		FILTER_NEAREST,
 		FILTER_MAX_ENUM
 		FILTER_MAX_ENUM
 	};
 	};
 
 
-	struct Filter
+	enum MipmapFilterMode
 	{
 	{
-		FilterMode min    = FILTER_LINEAR;
-		FilterMode mag    = FILTER_LINEAR;
-		FilterMode mipmap = FILTER_NONE;
-		float anisotropy  = 1.0f;
+		MIPMAP_FILTER_NONE,
+		MIPMAP_FILTER_LINEAR,
+		MIPMAP_FILTER_NEAREST,
+		MIPMAP_FILTER_MAX_ENUM
 	};
 	};
 
 
-	struct Wrap
-	{
-		WrapMode s = WRAP_CLAMP;
-		WrapMode t = WRAP_CLAMP;
-		WrapMode r = WRAP_CLAMP;
-	};
+	FilterMode minFilter = FILTER_LINEAR;
+	FilterMode magFilter = FILTER_LINEAR;
+	MipmapFilterMode mipmapFilter = MIPMAP_FILTER_NONE;
+
+	WrapMode wrapU = WRAP_CLAMP;
+	WrapMode wrapV = WRAP_CLAMP;
+	WrapMode wrapW = WRAP_CLAMP;
+
+	float lodBias = 0.0f;
+
+	uint8 maxAnisotropy = 1;
+
+	uint8 minLod = 0;
+	uint8 maxLod = LOVE_UINT8_MAX;
+
+	Optional<CompareMode> depthSampleMode;
+
+	uint64 toKey() const;
+	static SamplerState fromKey(uint64 key);
+
+	static bool isClampZeroOrOne(WrapMode w);
+
+	static bool getConstant(const char *in, FilterMode &out);
+	static bool getConstant(FilterMode in, const char *&out);
+	static std::vector<std::string> getConstants(FilterMode);
+
+	static bool getConstant(const char *in, MipmapFilterMode &out);
+	static bool getConstant(MipmapFilterMode in, const char *&out);
+	static std::vector<std::string> getConstants(MipmapFilterMode);
+
+	static bool getConstant(const char *in, WrapMode &out);
+	static bool getConstant(WrapMode in, const char *&out);
+	static std::vector<std::string> getConstants(WrapMode);
+};
+
+/**
+ * Base class for 2D textures. All textures can be drawn with Quads, have a
+ * width and height, and have filter and wrap modes.
+ **/
+class Texture : public Drawable, public Resource
+{
+public:
+
+	static love::Type type;
 
 
 	enum MipmapsType
 	enum MipmapsType
 	{
 	{
@@ -135,10 +164,6 @@ public:
 
 
 	}; // Slices
 	}; // Slices
 
 
-	static Filter defaultFilter;
-	static FilterMode defaultMipmapFilter;
-	static float defaultMipmapSharpness;
-
 	static int64 totalGraphicsMemory;
 	static int64 totalGraphicsMemory;
 
 
 	Texture(TextureType texType);
 	Texture(TextureType texType);
@@ -173,40 +198,18 @@ public:
 
 
 	float getDPIScale() const;
 	float getDPIScale() const;
 
 
-	virtual void setFilter(const Filter &f);
-	virtual const Filter &getFilter() const;
-
-	virtual bool setWrap(const Wrap &w) = 0;
-	virtual const Wrap &getWrap() const;
-
-	// Sets the mipmap texture LOD bias (sharpness) value.
-	virtual bool setMipmapSharpness(float sharpness) = 0;
-	float getMipmapSharpness() const;
-
-	virtual void setDepthSampleMode(Optional<CompareMode> mode = Optional<CompareMode>());
-	Optional<CompareMode> getDepthSampleMode() const;
+	virtual void setSamplerState(const SamplerState &s);
+	const SamplerState &getSamplerState() const;
 
 
 	Quad *getQuad() const;
 	Quad *getQuad() const;
 
 
-	static bool validateFilter(const Filter &f, bool mipmapsAllowed);
-
 	static int getTotalMipmapCount(int w, int h);
 	static int getTotalMipmapCount(int w, int h);
 	static int getTotalMipmapCount(int w, int h, int d);
 	static int getTotalMipmapCount(int w, int h, int d);
 
 
-	static bool isClampZeroOrOne(WrapMode w) { return w == WRAP_CLAMP_ZERO || w == WRAP_CLAMP_ONE; }
-
 	static bool getConstant(const char *in, TextureType &out);
 	static bool getConstant(const char *in, TextureType &out);
 	static bool getConstant(TextureType in, const char *&out);
 	static bool getConstant(TextureType in, const char *&out);
 	static std::vector<std::string> getConstants(TextureType);
 	static std::vector<std::string> getConstants(TextureType);
 
 
-	static bool getConstant(const char *in, FilterMode &out);
-	static bool getConstant(FilterMode in, const char *&out);
-	static std::vector<std::string> getConstants(FilterMode);
-
-	static bool getConstant(const char *in, WrapMode &out);
-	static bool getConstant(WrapMode in, const char *&out);
-	static std::vector<std::string> getConstants(WrapMode);
-
 protected:
 protected:
 
 
 	void initQuad();
 	void initQuad();
@@ -229,12 +232,7 @@ protected:
 	int pixelWidth;
 	int pixelWidth;
 	int pixelHeight;
 	int pixelHeight;
 
 
-	Filter filter;
-	Wrap wrap;
-
-	float mipmapSharpness;
-
-	Optional<CompareMode> depthCompareMode;
+	SamplerState samplerState;
 
 
 	StrongRef<Quad> quad;
 	StrongRef<Quad> quad;
 
 
@@ -245,12 +243,6 @@ private:
 	static StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry texTypeEntries[];
 	static StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry texTypeEntries[];
 	static StringMap<TextureType, TEXTURE_MAX_ENUM> texTypes;
 	static StringMap<TextureType, TEXTURE_MAX_ENUM> texTypes;
 
 
-	static StringMap<FilterMode, FILTER_MAX_ENUM>::Entry filterModeEntries[];
-	static StringMap<FilterMode, FILTER_MAX_ENUM> filterModes;
-
-	static StringMap<WrapMode, WRAP_MAX_ENUM>::Entry wrapModeEntries[];
-	static StringMap<WrapMode, WRAP_MAX_ENUM> wrapModes;
-
 }; // Texture
 }; // Texture
 
 
 } // graphics
 } // graphics

+ 18 - 11
src/modules/graphics/Video.cpp

@@ -35,9 +35,14 @@ Video::Video(Graphics *gfx, love::video::VideoStream *stream, float dpiscale)
 	: stream(stream)
 	: stream(stream)
 	, width(stream->getWidth() / dpiscale)
 	, width(stream->getWidth() / dpiscale)
 	, height(stream->getHeight() / dpiscale)
 	, height(stream->getHeight() / dpiscale)
-	, filter(Texture::defaultFilter)
+	, samplerState()
 {
 {
-	filter.mipmap = Texture::FILTER_NONE;
+	const SamplerState &defaultSampler = gfx->getDefaultSamplerState();
+	samplerState.minFilter = defaultSampler.minFilter;
+	samplerState.magFilter = defaultSampler.magFilter;
+	samplerState.wrapU = defaultSampler.wrapU;
+	samplerState.wrapV = defaultSampler.wrapV;
+	samplerState.maxAnisotropy = defaultSampler.maxAnisotropy;
 
 
 	stream->fillBackBuffer();
 	stream->fillBackBuffer();
 
 
@@ -74,15 +79,13 @@ Video::Video(Graphics *gfx, love::video::VideoStream *stream, float dpiscale)
 
 
 	const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
 	const unsigned char *data[3] = {frame->yplane, frame->cbplane, frame->crplane};
 
 
-	Texture::Wrap wrap; // Clamp wrap mode.
 	Image::Settings settings;
 	Image::Settings settings;
 
 
 	for (int i = 0; i < 3; i++)
 	for (int i = 0; i < 3; i++)
 	{
 	{
 		Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8_UNORM, widths[i], heights[i], 1, settings);
 		Image *img = gfx->newImage(TEXTURE_2D, PIXELFORMAT_R8_UNORM, widths[i], heights[i], 1, settings);
 
 
-		img->setFilter(filter);
-		img->setWrap(wrap);
+		img->setSamplerState(samplerState);
 
 
 		size_t bpp = getPixelFormatSize(PIXELFORMAT_R8_UNORM);
 		size_t bpp = getPixelFormatSize(PIXELFORMAT_R8_UNORM);
 		size_t size = bpp * widths[i] * heights[i];
 		size_t size = bpp * widths[i] * heights[i];
@@ -200,17 +203,21 @@ int Video::getPixelHeight() const
 	return stream->getHeight();
 	return stream->getHeight();
 }
 }
 
 
-void Video::setFilter(const Texture::Filter &f)
+void Video::setSamplerState(const SamplerState &s)
 {
 {
-	for (const auto &image : images)
-		image->setFilter(f);
+	samplerState.minFilter = s.minFilter;
+	samplerState.magFilter = s.magFilter;
+	samplerState.wrapU = s.wrapU;
+	samplerState.wrapV = s.wrapV;
+	samplerState.maxAnisotropy = s.maxAnisotropy;
 
 
-	filter = f;
+	for (const auto &image : images)
+		image->setSamplerState(samplerState);
 }
 }
 
 
-const Texture::Filter &Video::getFilter() const
+const SamplerState &Video::getSamplerState() const
 {
 {
-	return filter;
+	return samplerState;
 }
 }
 
 
 } // graphics
 } // graphics

+ 3 - 3
src/modules/graphics/Video.h

@@ -58,8 +58,8 @@ public:
 	int getPixelWidth() const;
 	int getPixelWidth() const;
 	int getPixelHeight() const;
 	int getPixelHeight() const;
 
 
-	void setFilter(const Texture::Filter &f);
-	const Texture::Filter &getFilter() const;
+	void setSamplerState(const SamplerState &s);
+	const SamplerState &getSamplerState() const;
 
 
 private:
 private:
 
 
@@ -70,7 +70,7 @@ private:
 	int width;
 	int width;
 	int height;
 	int height;
 
 
-	Texture::Filter filter;
+	SamplerState samplerState;
 
 
 	Vertex vertices[4];
 	Vertex vertices[4];
 
 

+ 11 - 97
src/modules/graphics/opengl/Canvas.cpp

@@ -239,10 +239,7 @@ bool Canvas::loadVolatile()
 		if (GLAD_ANGLE_texture_usage)
 		if (GLAD_ANGLE_texture_usage)
 			glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
 			glTexParameteri(gltype, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE);
 
 
-		setFilter(filter);
-		setWrap(wrap);
-		setMipmapSharpness(mipmapSharpness);
-		setDepthSampleMode(depthCompareMode);
+		setSamplerState(samplerState);
 
 
 		while (glGetError() != GL_NO_ERROR)
 		while (glGetError() != GL_NO_ERROR)
 			/* Clear the error buffer. */;
 			/* Clear the error buffer. */;
@@ -320,113 +317,30 @@ void Canvas::unloadVolatile()
 	setGraphicsMemorySize(0);
 	setGraphicsMemorySize(0);
 }
 }
 
 
-void Canvas::setFilter(const Texture::Filter &f)
+void Canvas::setSamplerState(const SamplerState &s)
 {
 {
-	Texture::setFilter(f);
+	Texture::setSamplerState(s);
+
+	if (samplerState.depthSampleMode.hasValue && !gl.isDepthCompareSampleSupported())
+		throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
 
 
 	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
 	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
 	{
 	{
-		filter.mag = filter.min = FILTER_NEAREST;
+		samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST;
 
 
-		if (filter.mipmap == FILTER_LINEAR)
-			filter.mipmap = FILTER_NEAREST;
+		if (samplerState.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
+			samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST;
 	}
 	}
 
 
-	gl.bindTextureToUnit(this, 0, false);
-	gl.setTextureFilter(texType, filter);
-}
-
-bool Canvas::setWrap(const Texture::Wrap &w)
-{
-	Graphics::flushStreamDrawsGlobal();
-
-	bool success = true;
-	bool forceclamp = texType == TEXTURE_CUBE;
-	wrap = w;
-
 	// If we only have limited NPOT support then the wrap mode must be CLAMP.
 	// If we only have limited NPOT support then the wrap mode must be CLAMP.
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
 	{
 	{
-		forceclamp = true;
-	}
-
-	if (forceclamp)
-	{
-		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
-			success = false;
-
-		wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
+		samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP;
 	}
 	}
 
 
-	if (!gl.isClampZeroOneTextureWrapSupported())
-	{
-		if (isClampZeroOrOne(wrap.s)) wrap.s = WRAP_CLAMP;
-		if (isClampZeroOrOne(wrap.t)) wrap.t = WRAP_CLAMP;
-		if (isClampZeroOrOne(wrap.r)) wrap.r = WRAP_CLAMP;
-	}
-
-	gl.bindTextureToUnit(this, 0, false);
-	gl.setTextureWrap(texType, wrap);
-
-	return success;
-}
-
-bool Canvas::setMipmapSharpness(float sharpness)
-{
-	if (!gl.isSamplerLODBiasSupported())
-		return false;
-
-	Graphics::flushStreamDrawsGlobal();
-
-	float maxbias = gl.getMaxLODBias();
-	if (maxbias > 0.01f)
-		maxbias -= 0.0f;
-
-	mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias);
-
 	gl.bindTextureToUnit(this, 0, false);
 	gl.bindTextureToUnit(this, 0, false);
-
-	// negative bias is sharper
-	glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
-
-	return true;
-}
-
-void Canvas::setDepthSampleMode(Optional<CompareMode> mode)
-{
-	Texture::setDepthSampleMode(mode);
-
-	bool supported = gl.isDepthCompareSampleSupported();
-
-	if (mode.hasValue)
-	{
-		if (!supported)
-			throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
-
-		Graphics::flushStreamDrawsGlobal();
-
-		gl.bindTextureToUnit(texType, texture, 0, false);
-		GLenum gltextype = OpenGL::getGLTextureType(texType);
-
-		// See the comment in renderstate.h
-		GLenum glmode = OpenGL::getGLCompareMode(getReversedCompareMode(mode.value));
-
-		glTexParameteri(gltextype, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
-		glTexParameteri(gltextype, GL_TEXTURE_COMPARE_FUNC, glmode);
-		
-	}
-	else if (isPixelFormatDepth(format) && supported)
-	{
-		Graphics::flushStreamDrawsGlobal();
-
-		gl.bindTextureToUnit(texType, texture, 0, false);
-		GLenum gltextype = OpenGL::getGLTextureType(texType);
-
-		glTexParameteri(gltextype, GL_TEXTURE_COMPARE_MODE, GL_NONE);
-	}
-
-	depthCompareMode = mode;
+	gl.setSamplerState(texType, samplerState);
 }
 }
 
 
 ptrdiff_t Canvas::getHandle() const
 ptrdiff_t Canvas::getHandle() const

+ 1 - 4
src/modules/graphics/opengl/Canvas.h

@@ -47,10 +47,7 @@ public:
 	void unloadVolatile() override;
 	void unloadVolatile() override;
 
 
 	// Implements Texture.
 	// Implements Texture.
-	void setFilter(const Texture::Filter &f) override;
-	bool setWrap(const Texture::Wrap &w) override;
-	bool setMipmapSharpness(float sharpness) override;
-	void setDepthSampleMode(Optional<CompareMode> mode) override;
+	void setSamplerState(const SamplerState &s) override;
 	ptrdiff_t getHandle() const override;
 	ptrdiff_t getHandle() const override;
 
 
 	love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect) override;
 	love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect) override;

+ 3 - 6
src/modules/graphics/opengl/Graphics.cpp

@@ -1470,12 +1470,9 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, boo
 		glGenTextures(1, &texture);
 		glGenTextures(1, &texture);
 		gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
 		gl.bindTextureToUnit(TEXTURE_2D, texture, 0, false);
 
 
-		Texture::Filter f;
-		f.min = f.mag = Texture::FILTER_NEAREST;
-		gl.setTextureFilter(TEXTURE_2D, f);
-
-		Texture::Wrap w;
-		gl.setTextureWrap(TEXTURE_2D, w);
+		SamplerState s;
+		s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
+		gl.setSamplerState(TEXTURE_2D, s);
 
 
 		gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1);
 		gl.rawTexStorage(TEXTURE_2D, 1, format, sRGB, 1, 1);
 	}
 	}

+ 13 - 66
src/modules/graphics/opengl/Image.cpp

@@ -75,7 +75,7 @@ void Image::loadDefaultTexture()
 	usingDefaultTexture = true;
 	usingDefaultTexture = true;
 
 
 	gl.bindTextureToUnit(this, 0, false);
 	gl.bindTextureToUnit(this, 0, false);
-	setFilter(filter);
+	setSamplerState(samplerState);
 
 
 	bool isSRGB = false;
 	bool isSRGB = false;
 	gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8_UNORM, isSRGB, 2, 2, 1);
 	gl.rawTexStorage(texType, 1, PIXELFORMAT_RGBA8_UNORM, isSRGB, 2, 2, 1);
@@ -204,7 +204,7 @@ bool Image::loadVolatile()
 			&& mipmapsType != MIPMAPS_DATA)
 			&& mipmapsType != MIPMAPS_DATA)
 		{
 		{
 			mipmapsType = MIPMAPS_NONE;
 			mipmapsType = MIPMAPS_NONE;
-			filter.mipmap = FILTER_NONE;
+			samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
 		}
 		}
 	}
 	}
 
 
@@ -213,7 +213,7 @@ bool Image::loadVolatile()
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
 	{
 	{
 		mipmapsType = MIPMAPS_NONE;
 		mipmapsType = MIPMAPS_NONE;
-		filter.mipmap = FILTER_NONE;
+		samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
 	}
 	}
 
 
 	glGenTextures(1, &texture);
 	glGenTextures(1, &texture);
@@ -226,9 +226,7 @@ bool Image::loadVolatile()
 		return true;
 		return true;
 	}
 	}
 
 
-	setFilter(filter);
-	setWrap(wrap);
-	setMipmapSharpness(mipmapSharpness);
+	setSamplerState(samplerState);
 
 
 	GLenum gltextype = OpenGL::getGLTextureType(texType);
 	GLenum gltextype = OpenGL::getGLTextureType(texType);
 
 
@@ -282,85 +280,34 @@ ptrdiff_t Image::getHandle() const
 	return texture;
 	return texture;
 }
 }
 
 
-void Image::setFilter(const Texture::Filter &f)
+void Image::setSamplerState(const SamplerState &s)
 {
 {
-	Texture::setFilter(f);
+	Texture::setSamplerState(s);
 
 
 	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
 	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
 	{
 	{
-		filter.mag = filter.min = FILTER_NEAREST;
+		samplerState.magFilter = samplerState.minFilter = SamplerState::FILTER_NEAREST;
 
 
-		if (filter.mipmap == FILTER_LINEAR)
-			filter.mipmap = FILTER_NEAREST;
+		if (samplerState.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
+			samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NEAREST;
 	}
 	}
 
 
 	// We don't want filtering or (attempted) mipmaps on the default texture.
 	// We don't want filtering or (attempted) mipmaps on the default texture.
 	if (usingDefaultTexture)
 	if (usingDefaultTexture)
 	{
 	{
-		filter.mipmap = FILTER_NONE;
-		filter.min = filter.mag = FILTER_NEAREST;
+		samplerState.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
+		samplerState.minFilter = samplerState.magFilter = SamplerState::FILTER_NEAREST;
 	}
 	}
 
 
-	gl.bindTextureToUnit(this, 0, false);
-	gl.setTextureFilter(texType, filter);
-}
-
-bool Image::setWrap(const Texture::Wrap &w)
-{
-	Graphics::flushStreamDrawsGlobal();
-
-	bool success = true;
-	bool forceclamp = texType == TEXTURE_CUBE;
-	wrap = w;
-
 	// If we only have limited NPOT support then the wrap mode must be CLAMP.
 	// If we only have limited NPOT support then the wrap mode must be CLAMP.
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 	if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
 		&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight) || depth != nextP2(depth)))
 	{
 	{
-		forceclamp = true;
-	}
-
-	if (forceclamp)
-	{
-		if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP || wrap.r != WRAP_CLAMP)
-			success = false;
-
-		wrap.s = wrap.t = wrap.r = WRAP_CLAMP;
-	}
-
-	if (!gl.isClampZeroOneTextureWrapSupported())
-	{
-		if (isClampZeroOrOne(wrap.s)) wrap.s = WRAP_CLAMP;
-		if (isClampZeroOrOne(wrap.t)) wrap.t = WRAP_CLAMP;
-		if (isClampZeroOrOne(wrap.r)) wrap.r = WRAP_CLAMP;
+		samplerState.wrapU = samplerState.wrapV = samplerState.wrapW = SamplerState::WRAP_CLAMP;
 	}
 	}
 
 
 	gl.bindTextureToUnit(this, 0, false);
 	gl.bindTextureToUnit(this, 0, false);
-	gl.setTextureWrap(texType, wrap);
-
-	return success;
-}
-
-bool Image::setMipmapSharpness(float sharpness)
-{
-	if (!gl.isSamplerLODBiasSupported())
-		return false;
-
-	Graphics::flushStreamDrawsGlobal();
-
-	float maxbias = gl.getMaxLODBias();
-
-	if (maxbias > 0.01f)
-		maxbias -= 0.01f;
-
-	mipmapSharpness = std::min(std::max(sharpness, -maxbias), maxbias);
-
-	gl.bindTextureToUnit(this, 0, false);
-
-	// negative bias is sharper
-	glTexParameterf(gl.getGLTextureType(texType), GL_TEXTURE_LOD_BIAS, -mipmapSharpness);
-
-	return true;
+	gl.setSamplerState(texType, samplerState);
 }
 }
 
 
 } // opengl
 } // opengl

+ 1 - 4
src/modules/graphics/opengl/Image.h

@@ -49,10 +49,7 @@ public:
 
 
 	ptrdiff_t getHandle() const override;
 	ptrdiff_t getHandle() const override;
 
 
-	void setFilter(const Texture::Filter &f) override;
-	bool setWrap(const Texture::Wrap &w) override;
-
-	bool setMipmapSharpness(float sharpness) override;
+	void setSamplerState(const SamplerState &s) override;
 
 
 private:
 private:
 
 

+ 102 - 56
src/modules/graphics/opengl/OpenGL.cpp

@@ -502,11 +502,9 @@ void OpenGL::createDefaultTexture()
 	// untextured primitives vs images.
 	// untextured primitives vs images.
 	const GLubyte pix[] = {255, 255, 255, 255};
 	const GLubyte pix[] = {255, 255, 255, 255};
 
 
-	Texture::Filter filter;
-	filter.min = filter.mag = Texture::FILTER_NEAREST;
-
-	Texture::Wrap wrap;
-	wrap.s = wrap.t = wrap.r = Texture::WRAP_CLAMP;
+	SamplerState s;
+	s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
+	s.wrapU = s.wrapV = s.wrapW = SamplerState::WRAP_CLAMP;
 
 
 	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
 	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
 	{
 	{
@@ -522,8 +520,7 @@ void OpenGL::createDefaultTexture()
 		glGenTextures(1, &state.defaultTexture[type]);
 		glGenTextures(1, &state.defaultTexture[type]);
 		bindTextureToUnit(type, state.defaultTexture[type], 0, false);
 		bindTextureToUnit(type, state.defaultTexture[type], 0, false);
 
 
-		setTextureWrap(type, wrap);
-		setTextureFilter(type, filter);
+		setSamplerState(type, s);
 
 
 		bool isSRGB = false;
 		bool isSRGB = false;
 		rawTexStorage(type, 1, PIXELFORMAT_RGBA8_UNORM, isSRGB, 1, 1);
 		rawTexStorage(type, 1, PIXELFORMAT_RGBA8_UNORM, isSRGB, 1, 1);
@@ -1050,52 +1047,19 @@ void OpenGL::deleteTexture(GLuint texture)
 	glDeleteTextures(1, &texture);
 	glDeleteTextures(1, &texture);
 }
 }
 
 
-void OpenGL::setTextureFilter(TextureType target, graphics::Texture::Filter &f)
-{
-	GLint gmin = f.min == Texture::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR;
-	GLint gmag = f.mag == Texture::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR;
-
-	if (f.mipmap != Texture::FILTER_NONE)
-	{
-		if (f.min == Texture::FILTER_NEAREST && f.mipmap == Texture::FILTER_NEAREST)
-			gmin = GL_NEAREST_MIPMAP_NEAREST;
-		else if (f.min == Texture::FILTER_NEAREST && f.mipmap == Texture::FILTER_LINEAR)
-			gmin = GL_NEAREST_MIPMAP_LINEAR;
-		else if (f.min == Texture::FILTER_LINEAR && f.mipmap == Texture::FILTER_NEAREST)
-			gmin = GL_LINEAR_MIPMAP_NEAREST;
-		else if (f.min == Texture::FILTER_LINEAR && f.mipmap == Texture::FILTER_LINEAR)
-			gmin = GL_LINEAR_MIPMAP_LINEAR;
-		else
-			gmin = GL_LINEAR;
-	}
-
-	GLenum gltarget = getGLTextureType(target);
-
-	glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin);
-	glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, gmag);
-
-	if (GLAD_EXT_texture_filter_anisotropic)
-	{
-		f.anisotropy = std::min(std::max(f.anisotropy, 1.0f), maxAnisotropy);
-		glTexParameterf(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, f.anisotropy);
-	}
-	else
-		f.anisotropy = 1.0f;
-}
-
-GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode)
+GLint OpenGL::getGLWrapMode(SamplerState::WrapMode wmode)
 {
 {
 	switch (wmode)
 	switch (wmode)
 	{
 	{
-	case Texture::WRAP_CLAMP:
+	case SamplerState::WRAP_CLAMP:
 	default:
 	default:
 		return GL_CLAMP_TO_EDGE;
 		return GL_CLAMP_TO_EDGE;
-	case Texture::WRAP_CLAMP_ZERO:
-	case Texture::WRAP_CLAMP_ONE:
+	case SamplerState::WRAP_CLAMP_ZERO:
+	case SamplerState::WRAP_CLAMP_ONE:
 		return GL_CLAMP_TO_BORDER;
 		return GL_CLAMP_TO_BORDER;
-	case Texture::WRAP_REPEAT:
+	case SamplerState::WRAP_REPEAT:
 		return GL_REPEAT;
 		return GL_REPEAT;
-	case Texture::WRAP_MIRRORED_REPEAT:
+	case SamplerState::WRAP_MIRRORED_REPEAT:
 		return GL_MIRRORED_REPEAT;
 		return GL_MIRRORED_REPEAT;
 	}
 	}
 }
 }
@@ -1125,29 +1089,111 @@ GLint OpenGL::getGLCompareMode(CompareMode mode)
 	}
 	}
 }
 }
 
 
-static bool isClampOne(Texture::WrapMode mode)
+static bool isClampOne(SamplerState::WrapMode mode)
 {
 {
-	return mode == Texture::WRAP_CLAMP_ONE;
+	return mode == SamplerState::WRAP_CLAMP_ONE;
 }
 }
 
 
-void OpenGL::setTextureWrap(TextureType target, const graphics::Texture::Wrap &w)
+void OpenGL::setSamplerState(TextureType target, SamplerState &s)
 {
 {
-	GLenum textype = getGLTextureType(target);
+	GLenum gltarget = getGLTextureType(target);
+
+	GLint gmin = s.minFilter == SamplerState::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR;
+	GLint gmag = s.magFilter == SamplerState::FILTER_NEAREST ? GL_NEAREST : GL_LINEAR;
+
+	if (s.mipmapFilter != SamplerState::MIPMAP_FILTER_NONE)
+	{
+		if (s.minFilter == SamplerState::FILTER_NEAREST && s.mipmapFilter == SamplerState::MIPMAP_FILTER_NEAREST)
+			gmin = GL_NEAREST_MIPMAP_NEAREST;
+		else if (s.minFilter == SamplerState::FILTER_NEAREST && s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
+			gmin = GL_NEAREST_MIPMAP_LINEAR;
+		else if (s.minFilter == SamplerState::FILTER_LINEAR && s.mipmapFilter == SamplerState::MIPMAP_FILTER_NEAREST)
+			gmin = GL_LINEAR_MIPMAP_NEAREST;
+		else if (s.minFilter == SamplerState::FILTER_LINEAR && s.mipmapFilter == SamplerState::MIPMAP_FILTER_LINEAR)
+			gmin = GL_LINEAR_MIPMAP_LINEAR;
+	}
 
 
-	if (Texture::isClampZeroOrOne(w.s) || Texture::isClampZeroOrOne(w.t) || Texture::isClampZeroOrOne(w.r))
+	glTexParameteri(gltarget, GL_TEXTURE_MIN_FILTER, gmin);
+	glTexParameteri(gltarget, GL_TEXTURE_MAG_FILTER, gmag);
+
+	if (!isClampZeroOneTextureWrapSupported())
+	{
+		if (SamplerState::isClampZeroOrOne(s.wrapU)) s.wrapU = SamplerState::WRAP_CLAMP;
+		if (SamplerState::isClampZeroOrOne(s.wrapV)) s.wrapV = SamplerState::WRAP_CLAMP;
+		if (SamplerState::isClampZeroOrOne(s.wrapW)) s.wrapW = SamplerState::WRAP_CLAMP;
+	}
+
+	if (SamplerState::isClampZeroOrOne(s.wrapU) || SamplerState::isClampZeroOrOne(s.wrapV) || SamplerState::isClampZeroOrOne(s.wrapW))
 	{
 	{
 		GLfloat c[] = {0.0f, 0.0f, 0.0f, 0.0f};
 		GLfloat c[] = {0.0f, 0.0f, 0.0f, 0.0f};
-		if (isClampOne(w.s) || isClampOne(w.t) || isClampOne(w.r))
+		if (isClampOne(s.wrapU) || isClampOne(s.wrapU) || isClampOne(s.wrapV))
 			c[0] = c[1] = c[2] = c[3] = 1.0f;
 			c[0] = c[1] = c[2] = c[3] = 1.0f;
 
 
-		glTexParameterfv(textype, GL_TEXTURE_BORDER_COLOR, c);
+		glTexParameterfv(gltarget, GL_TEXTURE_BORDER_COLOR, c);
 	}
 	}
 
 
-	glTexParameteri(textype, GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
-	glTexParameteri(textype, GL_TEXTURE_WRAP_T, getGLWrapMode(w.t));
+	glTexParameteri(gltarget, GL_TEXTURE_WRAP_S, getGLWrapMode(s.wrapU));
+	glTexParameteri(gltarget, GL_TEXTURE_WRAP_T, getGLWrapMode(s.wrapV));
 
 
 	if (target == TEXTURE_VOLUME)
 	if (target == TEXTURE_VOLUME)
-		glTexParameteri(textype, GL_TEXTURE_WRAP_R, getGLWrapMode(w.r));
+		glTexParameteri(gltarget, GL_TEXTURE_WRAP_R, getGLWrapMode(s.wrapW));
+
+	if (isSamplerLODBiasSupported())
+	{
+		float maxbias = getMaxLODBias();
+		if (maxbias > 0.01f)
+			maxbias -= 0.01f;
+
+		s.lodBias = std::min(std::max(s.lodBias, -maxbias), maxbias);
+
+		glTexParameterf(gltarget, GL_TEXTURE_LOD_BIAS, s.lodBias);
+	}
+	else
+	{
+		s.lodBias = 0.0f;
+	}
+
+	if (GLAD_EXT_texture_filter_anisotropic)
+	{
+		uint8 maxAniso = (uint8) std::min(maxAnisotropy, (float)LOVE_UINT8_MAX);
+		s.maxAnisotropy = std::min(std::max(s.maxAnisotropy, (uint8)1), maxAniso);
+		glTexParameteri(gltarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, s.maxAnisotropy);
+	}
+	else
+	{
+		s.maxAnisotropy = 1;
+	}
+
+	if (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0)
+	{
+		glTexParameterf(gltarget, GL_TEXTURE_MIN_LOD, (float)s.minLod);
+		glTexParameterf(gltarget, GL_TEXTURE_MAX_LOD, (float)s.maxLod);
+	}
+	else
+	{
+		s.minLod = 0;
+		s.maxLod = LOVE_UINT8_MAX;
+	}
+
+	if (isDepthCompareSampleSupported())
+	{
+		if (s.depthSampleMode.hasValue)
+		{
+			// See the comment in renderstate.h
+			GLenum glmode = getGLCompareMode(getReversedCompareMode(s.depthSampleMode.value));
+
+			glTexParameteri(gltarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+			glTexParameteri(gltarget, GL_TEXTURE_COMPARE_FUNC, glmode);
+		}
+		else
+		{
+			glTexParameteri(gltarget, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+		}
+	}
+	else
+	{
+		s.depthSampleMode.hasValue = false;
+	}
 }
 }
 
 
 bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth)
 bool OpenGL::rawTexStorage(TextureType target, int levels, PixelFormat pixelformat, bool &isSRGB, int width, int height, int depth)

+ 3 - 10
src/modules/graphics/opengl/OpenGL.h

@@ -329,16 +329,9 @@ public:
 	void deleteTexture(GLuint texture);
 	void deleteTexture(GLuint texture);
 
 
 	/**
 	/**
-	 * Sets the texture filter mode for the currently bound texture.
-	 * The anisotropy parameter of the argument is set to the actual amount of
-	 * anisotropy that was used.
+	 * Sets sampler state parameters for the currently bound texture.
 	 **/
 	 **/
-	void setTextureFilter(TextureType target, graphics::Texture::Filter &f);
-
-	/**
-	 * Sets the texture wrap mode for the currently bound texture.
-	 **/
-	void setTextureWrap(TextureType target, const graphics::Texture::Wrap &w);
+	void setSamplerState(TextureType target, SamplerState &s);
 
 
 	/**
 	/**
 	 * Equivalent to glTexStorage2D/3D on platforms that support it. Equivalent
 	 * Equivalent to glTexStorage2D/3D on platforms that support it. Equivalent
@@ -407,7 +400,7 @@ public:
 	static GLenum getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat);
 	static GLenum getGLVertexDataType(vertex::DataType type, GLboolean &normalized, bool &intformat);
 	static GLenum getGLBufferUsage(vertex::Usage usage);
 	static GLenum getGLBufferUsage(vertex::Usage usage);
 	static GLenum getGLTextureType(TextureType type);
 	static GLenum getGLTextureType(TextureType type);
-	static GLint getGLWrapMode(Texture::WrapMode wmode);
+	static GLint getGLWrapMode(SamplerState::WrapMode wmode);
 	static GLint getGLCompareMode(CompareMode mode);
 	static GLint getGLCompareMode(CompareMode mode);
 
 
 	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
 	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);

+ 2 - 1
src/modules/graphics/opengl/Shader.cpp

@@ -588,6 +588,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 
 
 		if (tex != nullptr)
 		if (tex != nullptr)
 		{
 		{
+			const SamplerState &sampler = tex->getSamplerState();
 			if (!tex->isReadable())
 			if (!tex->isReadable())
 			{
 			{
 				if (internalUpdate)
 				if (internalUpdate)
@@ -595,7 +596,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 				else
 				else
 					throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
 					throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
 			}
 			}
-			else if (info->isDepthSampler != tex->getDepthSampleMode().hasValue)
+			else if (info->isDepthSampler != sampler.depthSampleMode.hasValue)
 			{
 			{
 				if (internalUpdate)
 				if (internalUpdate)
 					continue;
 					continue;

+ 11 - 11
src/modules/graphics/wrap_Font.cpp

@@ -139,33 +139,33 @@ int w_Font_getLineHeight(lua_State *L)
 int w_Font_setFilter(lua_State *L)
 int w_Font_setFilter(lua_State *L)
 {
 {
 	Font *t = luax_checkfont(L, 1);
 	Font *t = luax_checkfont(L, 1);
-	Texture::Filter f = t->getFilter();
+	SamplerState s = t->getSamplerState();
 
 
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *magstr = luaL_optstring(L, 3, minstr);
 	const char *magstr = luaL_optstring(L, 3, minstr);
 
 
-	if (!Texture::getConstant(minstr, f.min))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr);
-	if (!Texture::getConstant(magstr, f.mag))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr);
+	if (!SamplerState::getConstant(minstr, s.minFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr);
+	if (!SamplerState::getConstant(magstr, s.magFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr);
 
 
-	f.anisotropy = (float) luaL_optnumber(L, 4, 1.0);
+	s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX);
 
 
-	luax_catchexcept(L, [&](){ t->setFilter(f); });
+	luax_catchexcept(L, [&](){ t->setSamplerState(s); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_Font_getFilter(lua_State *L)
 int w_Font_getFilter(lua_State *L)
 {
 {
 	Font *t = luax_checkfont(L, 1);
 	Font *t = luax_checkfont(L, 1);
-	const Texture::Filter f = t->getFilter();
+	const SamplerState &s = t->getSamplerState();
 	const char *minstr;
 	const char *minstr;
 	const char *magstr;
 	const char *magstr;
-	Texture::getConstant(f.min, minstr);
-	Texture::getConstant(f.mag, magstr);
+	SamplerState::getConstant(s.minFilter, minstr);
+	SamplerState::getConstant(s.magFilter, magstr);
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, magstr);
 	lua_pushstring(L, magstr);
-	lua_pushnumber(L, f.anisotropy);
+	lua_pushnumber(L, s.maxAnisotropy);
 	return 3;
 	return 3;
 }
 }
 
 

+ 22 - 29
src/modules/graphics/wrap_Graphics.cpp

@@ -1102,7 +1102,7 @@ int w_newFont(lua_State *L)
 	love::font::Rasterizer *rasterizer = luax_checktype<love::font::Rasterizer>(L, 1);
 	love::font::Rasterizer *rasterizer = luax_checktype<love::font::Rasterizer>(L, 1);
 
 
 	luax_catchexcept(L, [&]() {
 	luax_catchexcept(L, [&]() {
-		font = instance()->newFont(rasterizer, instance()->getDefaultFilter()); }
+		font = instance()->newFont(rasterizer); }
 	);
 	);
 
 
 	// Push the type.
 	// Push the type.
@@ -1115,9 +1115,6 @@ int w_newImageFont(lua_State *L)
 {
 {
 	luax_checkgraphicscreated(L);
 	luax_checkgraphicscreated(L);
 
 
-	// filter for glyphs
-	Texture::Filter filter = instance()->getDefaultFilter();
-
 	// Convert to Rasterizer if necessary.
 	// Convert to Rasterizer if necessary.
 	if (!luax_istype(L, 1, love::font::Rasterizer::type))
 	if (!luax_istype(L, 1, love::font::Rasterizer::type))
 	{
 	{
@@ -1133,7 +1130,7 @@ int w_newImageFont(lua_State *L)
 	love::font::Rasterizer *rasterizer = luax_checktype<love::font::Rasterizer>(L, 1);
 	love::font::Rasterizer *rasterizer = luax_checktype<love::font::Rasterizer>(L, 1);
 
 
 	// Create the font.
 	// Create the font.
-	Font *font = instance()->newFont(rasterizer, filter);
+	Font *font = instance()->newFont(rasterizer);
 
 
 	// Push the type.
 	// Push the type.
 	luax_pushtype(L, font);
 	luax_pushtype(L, font);
@@ -1935,69 +1932,65 @@ int w_getBlendState(lua_State *L)
 
 
 int w_setDefaultFilter(lua_State *L)
 int w_setDefaultFilter(lua_State *L)
 {
 {
-	Texture::Filter f;
+	SamplerState s = instance()->getDefaultSamplerState();
 
 
 	const char *minstr = luaL_checkstring(L, 1);
 	const char *minstr = luaL_checkstring(L, 1);
 	const char *magstr = luaL_optstring(L, 2, minstr);
 	const char *magstr = luaL_optstring(L, 2, minstr);
 
 
-	if (!Texture::getConstant(minstr, f.min))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr);
-	if (!Texture::getConstant(magstr, f.mag))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr);
-
-	f.anisotropy = (float) luaL_optnumber(L, 3, 1.0);
+	if (!SamplerState::getConstant(minstr, s.minFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr);
+	if (!SamplerState::getConstant(magstr, s.magFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr);
 
 
-	instance()->setDefaultFilter(f);
+	s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 3, 1.0)), LOVE_UINT8_MAX);
 
 
+	instance()->setDefaultSamplerState(s);
 	return 0;
 	return 0;
 }
 }
 
 
 int w_getDefaultFilter(lua_State *L)
 int w_getDefaultFilter(lua_State *L)
 {
 {
-	const Texture::Filter &f = instance()->getDefaultFilter();
+	const SamplerState &s = instance()->getDefaultSamplerState();
 	const char *minstr;
 	const char *minstr;
 	const char *magstr;
 	const char *magstr;
-	if (!Texture::getConstant(f.min, minstr))
+	if (!SamplerState::getConstant(s.minFilter, minstr))
 		return luaL_error(L, "Unknown minification filter mode");
 		return luaL_error(L, "Unknown minification filter mode");
-	if (!Texture::getConstant(f.mag, magstr))
+	if (!SamplerState::getConstant(s.magFilter, magstr))
 		return luaL_error(L, "Unknown magnification filter mode");
 		return luaL_error(L, "Unknown magnification filter mode");
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, magstr);
 	lua_pushstring(L, magstr);
-	lua_pushnumber(L, f.anisotropy);
+	lua_pushnumber(L, s.maxAnisotropy);
 	return 3;
 	return 3;
 }
 }
 
 
 int w_setDefaultMipmapFilter(lua_State *L)
 int w_setDefaultMipmapFilter(lua_State *L)
 {
 {
-	Texture::FilterMode filter = Texture::FILTER_NONE;
+	SamplerState s = instance()->getDefaultSamplerState();
+	s.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
 	if (!lua_isnoneornil(L, 1))
 	if (!lua_isnoneornil(L, 1))
 	{
 	{
 		const char *str = luaL_checkstring(L, 1);
 		const char *str = luaL_checkstring(L, 1);
-		if (!Texture::getConstant(str, filter))
-			return luax_enumerror(L, "filter mode", Texture::getConstants(filter), str);
+		if (!SamplerState::getConstant(str, s.mipmapFilter))
+			return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.mipmapFilter), str);
 	}
 	}
 
 
-	float sharpness = (float) luaL_optnumber(L, 2, 0);
-
-	instance()->setDefaultMipmapFilter(filter, sharpness);
+	s.lodBias = -((float) luaL_optnumber(L, 2, 0.0));
 
 
+	instance()->setDefaultSamplerState(s);
 	return 0;
 	return 0;
 }
 }
 
 
 int w_getDefaultMipmapFilter(lua_State *L)
 int w_getDefaultMipmapFilter(lua_State *L)
 {
 {
-	Texture::FilterMode filter;
-	float sharpness;
-
-	instance()->getDefaultMipmapFilter(&filter, &sharpness);
+	const SamplerState &s = instance()->getDefaultSamplerState();
 
 
 	const char *str;
 	const char *str;
-	if (Texture::getConstant(filter, str))
+	if (SamplerState::getConstant(s.mipmapFilter, str))
 		lua_pushstring(L, str);
 		lua_pushstring(L, str);
 	else
 	else
 		lua_pushnil(L);
 		lua_pushnil(L);
 
 
-	lua_pushnumber(L, sharpness);
+	lua_pushnumber(L, -s.lodBias);
 
 
 	return 2;
 	return 2;
 }
 }

+ 42 - 41
src/modules/graphics/wrap_Texture.cpp

@@ -132,111 +132,111 @@ int w_Texture_getDPIScale(lua_State *L)
 int w_Texture_setFilter(lua_State *L)
 int w_Texture_setFilter(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	Texture::Filter f = t->getFilter();
+	SamplerState s = t->getSamplerState();
 
 
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *magstr = luaL_optstring(L, 3, minstr);
 	const char *magstr = luaL_optstring(L, 3, minstr);
 
 
-	if (!Texture::getConstant(minstr, f.min))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr);
-	if (!Texture::getConstant(magstr, f.mag))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr);
+	if (!SamplerState::getConstant(minstr, s.minFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr);
+	if (!SamplerState::getConstant(magstr, s.magFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr);
 
 
-	f.anisotropy = (float) luaL_optnumber(L, 4, 1.0);
+	s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX);
 
 
-	luax_catchexcept(L, [&](){ t->setFilter(f); });
+	luax_catchexcept(L, [&](){ t->setSamplerState(s); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_Texture_getFilter(lua_State *L)
 int w_Texture_getFilter(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	const Texture::Filter f = t->getFilter();
+	const SamplerState &s = t->getSamplerState();
 
 
 	const char *minstr = nullptr;
 	const char *minstr = nullptr;
 	const char *magstr = nullptr;
 	const char *magstr = nullptr;
 
 
-	if (!Texture::getConstant(f.min, minstr))
+	if (!SamplerState::getConstant(s.minFilter, minstr))
 		return luaL_error(L, "Unknown filter mode.");
 		return luaL_error(L, "Unknown filter mode.");
-	if (!Texture::getConstant(f.mag, magstr))
+	if (!SamplerState::getConstant(s.magFilter, magstr))
 		return luaL_error(L, "Unknown filter mode.");
 		return luaL_error(L, "Unknown filter mode.");
 
 
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, magstr);
 	lua_pushstring(L, magstr);
-	lua_pushnumber(L, f.anisotropy);
+	lua_pushnumber(L, s.maxAnisotropy);
 	return 3;
 	return 3;
 }
 }
 
 
 int w_Texture_setMipmapFilter(lua_State *L)
 int w_Texture_setMipmapFilter(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	Texture::Filter f = t->getFilter();
+	SamplerState s = t->getSamplerState();
 
 
+	// Mipmapping is disabled if no argument is given.
 	if (lua_isnoneornil(L, 2))
 	if (lua_isnoneornil(L, 2))
-		f.mipmap = Texture::FILTER_NONE; // mipmapping is disabled if no argument is given
+		s.mipmapFilter = SamplerState::MIPMAP_FILTER_NONE;
 	else
 	else
 	{
 	{
 		const char *mipmapstr = luaL_checkstring(L, 2);
 		const char *mipmapstr = luaL_checkstring(L, 2);
-		if (!Texture::getConstant(mipmapstr, f.mipmap))
-			return luax_enumerror(L, "filter mode", Texture::getConstants(f.mipmap), mipmapstr);
+		if (!SamplerState::getConstant(mipmapstr, s.mipmapFilter))
+			return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.mipmapFilter), mipmapstr);
 	}
 	}
 
 
-	luax_catchexcept(L, [&](){ t->setFilter(f); });
-	t->setMipmapSharpness((float) luaL_optnumber(L, 3, 0.0));
+	s.lodBias = -((float) luaL_optnumber(L, 3, 0.0));
 
 
+	luax_catchexcept(L, [&](){ t->setSamplerState(s); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_Texture_getMipmapFilter(lua_State *L)
 int w_Texture_getMipmapFilter(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-
-	const Texture::Filter &f = t->getFilter();
+	const SamplerState &s = t->getSamplerState();
 
 
 	const char *mipmapstr;
 	const char *mipmapstr;
-	if (Texture::getConstant(f.mipmap, mipmapstr))
+	if (SamplerState::getConstant(s.mipmapFilter, mipmapstr))
 		lua_pushstring(L, mipmapstr);
 		lua_pushstring(L, mipmapstr);
 	else
 	else
 		lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
 		lua_pushnil(L); // only return a mipmap filter if mipmapping is enabled
 
 
-	lua_pushnumber(L, t->getMipmapSharpness());
+	lua_pushnumber(L, -s.lodBias);
 	return 2;
 	return 2;
 }
 }
 
 
 int w_Texture_setWrap(lua_State *L)
 int w_Texture_setWrap(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	Texture::Wrap w;
+	SamplerState s = t->getSamplerState();
 
 
 	const char *sstr = luaL_checkstring(L, 2);
 	const char *sstr = luaL_checkstring(L, 2);
 	const char *tstr = luaL_optstring(L, 3, sstr);
 	const char *tstr = luaL_optstring(L, 3, sstr);
 	const char *rstr = luaL_optstring(L, 4, sstr);
 	const char *rstr = luaL_optstring(L, 4, sstr);
 
 
-	if (!Texture::getConstant(sstr, w.s))
-		return luax_enumerror(L, "wrap mode", Texture::getConstants(w.s), sstr);
-	if (!Texture::getConstant(tstr, w.t))
-		return luax_enumerror(L, "wrap mode", Texture::getConstants(w.t), tstr);
-	if (!Texture::getConstant(rstr, w.r))
-		return luax_enumerror(L, "wrap mode", Texture::getConstants(w.r), rstr);
+	if (!SamplerState::getConstant(sstr, s.wrapU))
+		return luax_enumerror(L, "wrap mode", SamplerState::getConstants(s.wrapU), sstr);
+	if (!SamplerState::getConstant(tstr, s.wrapV))
+		return luax_enumerror(L, "wrap mode", SamplerState::getConstants(s.wrapV), tstr);
+	if (!SamplerState::getConstant(rstr, s.wrapW))
+		return luax_enumerror(L, "wrap mode", SamplerState::getConstants(s.wrapW), rstr);
 
 
-	luax_pushboolean(L, t->setWrap(w));
+	luax_catchexcept(L, [&](){ t->setSamplerState(s); });
 	return 1;
 	return 1;
 }
 }
 
 
 int w_Texture_getWrap(lua_State *L)
 int w_Texture_getWrap(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	const Texture::Wrap w = t->getWrap();
+	const SamplerState &s = t->getSamplerState();
 
 
 	const char *sstr = nullptr;
 	const char *sstr = nullptr;
 	const char *tstr = nullptr;
 	const char *tstr = nullptr;
 	const char *rstr = nullptr;
 	const char *rstr = nullptr;
 
 
-	if (!Texture::getConstant(w.s, sstr))
+	if (!SamplerState::getConstant(s.wrapU, sstr))
 		return luaL_error(L, "Unknown wrap mode.");
 		return luaL_error(L, "Unknown wrap mode.");
-	if (!Texture::getConstant(w.t, tstr))
+	if (!SamplerState::getConstant(s.wrapV, tstr))
 		return luaL_error(L, "Unknown wrap mode.");
 		return luaL_error(L, "Unknown wrap mode.");
-	if (!Texture::getConstant(w.r, rstr))
+	if (!SamplerState::getConstant(s.wrapW, rstr))
 		return luaL_error(L, "Unknown wrap mode.");
 		return luaL_error(L, "Unknown wrap mode.");
 
 
 	lua_pushstring(L, sstr);
 	lua_pushstring(L, sstr);
@@ -267,30 +267,31 @@ int w_Texture_isReadable(lua_State *L)
 int w_Texture_setDepthSampleMode(lua_State *L)
 int w_Texture_setDepthSampleMode(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
+	SamplerState s = t->getSamplerState();
 
 
-	Optional<CompareMode> mode;
+	s.depthSampleMode.hasValue = false;
 	if (!lua_isnoneornil(L, 2))
 	if (!lua_isnoneornil(L, 2))
 	{
 	{
 		const char *str = luaL_checkstring(L, 2);
 		const char *str = luaL_checkstring(L, 2);
 
 
-		mode.hasValue = true;
-		if (!getConstant(str, mode.value))
-			return luax_enumerror(L, "compare mode", getConstants(mode.value), str);
+		s.depthSampleMode.hasValue = true;
+		if (!getConstant(str, s.depthSampleMode.value))
+			return luax_enumerror(L, "compare mode", getConstants(s.depthSampleMode.value), str);
 	}
 	}
 
 
-	luax_catchexcept(L, [&]() { t->setDepthSampleMode(mode); });
+	luax_catchexcept(L, [&](){ t->setSamplerState(s); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_Texture_getDepthSampleMode(lua_State *L)
 int w_Texture_getDepthSampleMode(lua_State *L)
 {
 {
 	Texture *t = luax_checktexture(L, 1);
 	Texture *t = luax_checktexture(L, 1);
-	Optional<CompareMode> mode = t->getDepthSampleMode();
+	const SamplerState &s = t->getSamplerState();
 
 
-	if (mode.hasValue)
+	if (s.depthSampleMode.hasValue)
 	{
 	{
 		const char *str = nullptr;
 		const char *str = nullptr;
-		if (!getConstant(mode.value, str))
+		if (!getConstant(s.depthSampleMode.value, str))
 			return luaL_error(L, "Unknown compare mode.");
 			return luaL_error(L, "Unknown compare mode.");
 		lua_pushstring(L, str);
 		lua_pushstring(L, str);
 	}
 	}

+ 11 - 11
src/modules/graphics/wrap_Video.cpp

@@ -113,38 +113,38 @@ int w_Video_getPixelDimensions(lua_State *L)
 int w_Video_setFilter(lua_State *L)
 int w_Video_setFilter(lua_State *L)
 {
 {
 	Video *video = luax_checkvideo(L, 1);
 	Video *video = luax_checkvideo(L, 1);
-	Texture::Filter f = video->getFilter();
+	SamplerState s = video->getSamplerState();
 
 
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *minstr = luaL_checkstring(L, 2);
 	const char *magstr = luaL_optstring(L, 3, minstr);
 	const char *magstr = luaL_optstring(L, 3, minstr);
 
 
-	if (!Texture::getConstant(minstr, f.min))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.min), minstr);
-	if (!Texture::getConstant(magstr, f.mag))
-		return luax_enumerror(L, "filter mode", Texture::getConstants(f.mag), magstr);
+	if (!SamplerState::getConstant(minstr, s.minFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.minFilter), minstr);
+	if (!SamplerState::getConstant(magstr, s.magFilter))
+		return luax_enumerror(L, "filter mode", SamplerState::getConstants(s.magFilter), magstr);
 
 
-	f.anisotropy = (float) luaL_optnumber(L, 4, 1.0);
+	s.maxAnisotropy = std::min(std::max(1, (int) luaL_optnumber(L, 4, 1.0)), LOVE_UINT8_MAX);
 
 
-	luax_catchexcept(L, [&](){ video->setFilter(f); });
+	luax_catchexcept(L, [&](){ video->setSamplerState(s); });
 	return 0;
 	return 0;
 }
 }
 
 
 int w_Video_getFilter(lua_State *L)
 int w_Video_getFilter(lua_State *L)
 {
 {
 	Video *video = luax_checkvideo(L, 1);
 	Video *video = luax_checkvideo(L, 1);
-	const Texture::Filter f = video->getFilter();
+	const SamplerState &s = video->getSamplerState();
 
 
 	const char *minstr = nullptr;
 	const char *minstr = nullptr;
 	const char *magstr = nullptr;
 	const char *magstr = nullptr;
 
 
-	if (!Texture::getConstant(f.min, minstr))
+	if (!SamplerState::getConstant(s.minFilter, minstr))
 		return luaL_error(L, "Unknown filter mode.");
 		return luaL_error(L, "Unknown filter mode.");
-	if (!Texture::getConstant(f.mag, magstr))
+	if (!SamplerState::getConstant(s.magFilter, magstr))
 		return luaL_error(L, "Unknown filter mode.");
 		return luaL_error(L, "Unknown filter mode.");
 
 
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, minstr);
 	lua_pushstring(L, magstr);
 	lua_pushstring(L, magstr);
-	lua_pushnumber(L, f.anisotropy);
+	lua_pushnumber(L, s.maxAnisotropy);
 	return 3;
 	return 3;
 }
 }