Browse Source

Add Depth compare / shadow sampler support. Officially this is only supported in glsl3 shaders.

- Add Texture:setDepthSampleMode(comparemode). Only works on textures with depth pixel formats. A texture with the depth sample mode set will only work with a depth sampler.
- Add DepthImage, DepthArrayImage, and DepthCubeImage sampler keywords to glsl3.

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
a0ef2ac28b

+ 2 - 0
CMakeLists.txt

@@ -475,6 +475,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT
 	src/modules/graphics/Canvas.cpp
 	src/modules/graphics/Canvas.h
 	src/modules/graphics/Color.h
+	src/modules/graphics/depthstencil.cpp
+	src/modules/graphics/depthstencil.h
 	src/modules/graphics/Drawable.cpp
 	src/modules/graphics/Drawable.h
 	src/modules/graphics/Font.cpp

+ 8 - 0
platform/xcode/liblove.xcodeproj/project.pbxproj

@@ -1132,6 +1132,8 @@
 		FAF140DB1E20934C00F898D2 /* InitializeDll.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF1403B1E20934C00F898D2 /* InitializeDll.cpp */; };
 		FAF140DC1E20934C00F898D2 /* InitializeDll.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF1403B1E20934C00F898D2 /* InitializeDll.cpp */; };
 		FAF140DD1E20934C00F898D2 /* InitializeDll.h in Headers */ = {isa = PBXBuildFile; fileRef = FAF1403C1E20934C00F898D2 /* InitializeDll.h */; };
+		FAF1889F1E9DBC4B008C1479 /* depthstencil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF1889E1E9DBC4B008C1479 /* depthstencil.cpp */; };
+		FAF188A01E9DBC4B008C1479 /* depthstencil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAF1889E1E9DBC4B008C1479 /* depthstencil.cpp */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -1937,6 +1939,8 @@
 		FAF1403B1E20934C00F898D2 /* InitializeDll.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InitializeDll.cpp; sourceTree = "<group>"; };
 		FAF1403C1E20934C00F898D2 /* InitializeDll.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InitializeDll.h; sourceTree = "<group>"; };
 		FAF1889C1E9DA834008C1479 /* Optional.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Optional.h; sourceTree = "<group>"; };
+		FAF1889D1E9DBBC8008C1479 /* depthstencil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = depthstencil.h; sourceTree = "<group>"; };
+		FAF1889E1E9DBC4B008C1479 /* depthstencil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = depthstencil.cpp; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -2663,6 +2667,8 @@
 				FA1BA0A51E16F20600AA2803 /* Canvas.cpp */,
 				FA1BA0A61E16F20600AA2803 /* Canvas.h */,
 				FA0B7B881A95902C000E1D17 /* Color.h */,
+				FAF1889E1E9DBC4B008C1479 /* depthstencil.cpp */,
+				FAF1889D1E9DBBC8008C1479 /* depthstencil.h */,
 				FA9D8DDC1DEF842A002CD881 /* Drawable.cpp */,
 				FA0B7B891A95902C000E1D17 /* Drawable.h */,
 				FA1BA09B1E16CFCE00AA2803 /* Font.cpp */,
@@ -4185,6 +4191,7 @@
 				FA0B7DDD1A95902C000E1D17 /* wrap_BezierCurve.cpp in Sources */,
 				FA0B7A2A1A958EA3000E1D17 /* b2BroadPhase.cpp in Sources */,
 				FA0B7A811A958EA3000E1D17 /* b2EdgeAndCircleContact.cpp in Sources */,
+				FAF188A01E9DBC4B008C1479 /* depthstencil.cpp in Sources */,
 				FA0B7D071A95902C000E1D17 /* wrap_File.cpp in Sources */,
 				FAD19A181DFF8CA200D5398A /* ImageDataBase.cpp in Sources */,
 				FA0B7AD01A958EA3000E1D17 /* peer.c in Sources */,
@@ -4545,6 +4552,7 @@
 				FA0B7DDC1A95902C000E1D17 /* wrap_BezierCurve.cpp in Sources */,
 				FA0B7A951A958EA3000E1D17 /* b2Joint.cpp in Sources */,
 				FA0B7D061A95902C000E1D17 /* wrap_File.cpp in Sources */,
+				FAF1889F1E9DBC4B008C1479 /* depthstencil.cpp in Sources */,
 				FA0B7A4E1A958EA3000E1D17 /* b2Draw.cpp in Sources */,
 				FAD19A171DFF8CA200D5398A /* ImageDataBase.cpp in Sources */,
 				FA27B3C01B4985BF008A9DCE /* wrap_VideoStream.cpp in Sources */,

+ 6 - 3
src/common/Optional.h

@@ -23,7 +23,7 @@
 namespace love
 {
 
-// Currently only meant for simple types.
+// Currently only meant for simple and small types.
 template <typename T>
 struct Optional
 {
@@ -40,8 +40,11 @@ struct Optional
 		, value(val)
 	{}
 
-private:
-
+	void set(T val)
+	{
+		hasValue = true;
+		value = val;
+	}
 };
 
 typedef Optional<bool> OptionalBool;

+ 6 - 0
src/common/pixelformat.cpp

@@ -125,6 +125,12 @@ bool isPixelFormatDepthStencil(PixelFormat format)
 	return iformat >= (int) PIXELFORMAT_STENCIL8 && iformat <= (int) PIXELFORMAT_DEPTH32F_STENCIL8;
 }
 
+bool isPixelFormatDepth(PixelFormat format)
+{
+	int iformat = (int) format;
+	return iformat >= (int) PIXELFORMAT_DEPTH16 && iformat <= (int) PIXELFORMAT_DEPTH32F_STENCIL8;
+}
+
 size_t getPixelFormatSize(PixelFormat format)
 {
 	switch (format)

+ 6 - 1
src/common/pixelformat.h

@@ -115,10 +115,15 @@ bool getConstant(const char *in, PixelFormat &out);
 bool isPixelFormatCompressed(PixelFormat format);
 
 /**
- * Gets whether the specified pixel format is a depth/stencil type.
+ * Gets whether the specified pixel format is a depth or stencil type.
  **/
 bool isPixelFormatDepthStencil(PixelFormat format);
 
+/**
+ * Gets whether the specified pixel format is a depth type.
+ **/
+bool isPixelFormatDepth(PixelFormat format);
+
 /**
  * Gets the size in bytes of the specified pixel format.
  * NOTE: Currently returns 0 for compressed formats.

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

@@ -589,6 +589,9 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
 
 	StreamBufferState &state = streamBufferState;
 
+	if (state.vertexCount == 0 && Shader::current != nullptr && req.texture != nullptr)
+		Shader::current->checkMainTexture(req.texture);
+
 	bool shouldflush = false;
 	bool shouldresize = false;
 
@@ -706,6 +709,13 @@ Graphics::StreamVertexData Graphics::requestStreamDraw(const StreamDrawRequest &
 	return d;
 }
 
+void Graphics::flushStreamDrawsGlobal()
+{
+	Graphics *instance = getInstance<Graphics>(M_GRAPHICS);
+	if (instance != nullptr)
+		instance->flushStreamDraws();
+}
+
 /**
  * Drawing
  **/
@@ -1266,26 +1276,6 @@ bool Graphics::getConstant(LineJoin in, const char *&out)
 	return lineJoins.find(in, out);
 }
 
-bool Graphics::getConstant(const char *in, StencilAction &out)
-{
-	return stencilActions.find(in, out);
-}
-
-bool Graphics::getConstant(StencilAction in, const char *&out)
-{
-	return stencilActions.find(in, out);
-}
-
-bool Graphics::getConstant(const char *in, CompareMode &out)
-{
-	return compareModes.find(in, out);
-}
-
-bool Graphics::getConstant(CompareMode in, const char *&out)
-{
-	return compareModes.find(in, out);
-}
-
 bool Graphics::getConstant(const char *in, Feature &out)
 {
 	return features.find(in, out);
@@ -1373,31 +1363,6 @@ StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM>::Entry Graphics::lin
 
 StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(Graphics::lineJoinEntries, sizeof(Graphics::lineJoinEntries));
 
-StringMap<Graphics::StencilAction, Graphics::STENCIL_MAX_ENUM>::Entry Graphics::stencilActionEntries[] =
-{
-	{ "replace",       STENCIL_REPLACE        },
-	{ "increment",     STENCIL_INCREMENT      },
-	{ "decrement",     STENCIL_DECREMENT      },
-	{ "incrementwrap", STENCIL_INCREMENT_WRAP },
-	{ "decrementwrap", STENCIL_DECREMENT_WRAP },
-	{ "invert",        STENCIL_INVERT         },
-};
-
-StringMap<Graphics::StencilAction, Graphics::STENCIL_MAX_ENUM> Graphics::stencilActions(Graphics::stencilActionEntries, sizeof(Graphics::stencilActionEntries));
-
-StringMap<Graphics::CompareMode, Graphics::COMPARE_MAX_ENUM>::Entry Graphics::compareModeEntries[] =
-{
-	{ "less",     COMPARE_LESS     },
-	{ "lequal",   COMPARE_LEQUAL   },
-	{ "equal",    COMPARE_EQUAL    },
-	{ "gequal",   COMPARE_GEQUAL   },
-	{ "greater",  COMPARE_GREATER  },
-	{ "notequal", COMPARE_NOTEQUAL },
-	{ "always",   COMPARE_ALWAYS   },
-};
-
-StringMap<Graphics::CompareMode, Graphics::COMPARE_MAX_ENUM> Graphics::compareModes(Graphics::compareModeEntries, sizeof(Graphics::compareModeEntries));
-
 StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featureEntries[] =
 {
 	{ "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },

+ 3 - 35
src/modules/graphics/Graphics.h

@@ -37,6 +37,7 @@
 #include "Quad.h"
 #include "Mesh.h"
 #include "Image.h"
+#include "depthstencil.h"
 #include "math/Transform.h"
 #include "font/Rasterizer.h"
 #include "video/VideoStream.h"
@@ -150,29 +151,6 @@ public:
 		LINE_JOIN_MAX_ENUM
 	};
 
-	enum StencilAction
-	{
-		STENCIL_REPLACE,
-		STENCIL_INCREMENT,
-		STENCIL_DECREMENT,
-		STENCIL_INCREMENT_WRAP,
-		STENCIL_DECREMENT_WRAP,
-		STENCIL_INVERT,
-		STENCIL_MAX_ENUM
-	};
-
-	enum CompareMode
-	{
-		COMPARE_LESS,
-		COMPARE_LEQUAL,
-		COMPARE_EQUAL,
-		COMPARE_GEQUAL,
-		COMPARE_GREATER,
-		COMPARE_NOTEQUAL,
-		COMPARE_ALWAYS,
-		COMPARE_MAX_ENUM
-	};
-
 	enum Feature
 	{
 		FEATURE_MULTI_CANVAS_FORMATS,
@@ -761,6 +739,8 @@ public:
 	virtual void flushStreamDraws() = 0;
 	StreamVertexData requestStreamDraw(const StreamDrawRequest &request);
 
+	static void flushStreamDrawsGlobal();
+
 	virtual Shader::Language getShaderLanguageTarget() const = 0;
 	const Shader::ShaderSource &getCurrentDefaultShaderCode() const;
 
@@ -793,12 +773,6 @@ public:
 	static bool getConstant(const char *in, LineJoin &out);
 	static bool getConstant(LineJoin in, const char *&out);
 
-	static bool getConstant(const char *in, StencilAction &out);
-	static bool getConstant(StencilAction in, const char *&out);
-
-	static bool getConstant(const char *in, CompareMode &out);
-	static bool getConstant(CompareMode in, const char *&out);
-
 	static bool getConstant(const char *in, Feature &out);
 	static bool getConstant(Feature in, const char *&out);
 
@@ -932,12 +906,6 @@ private:
 	static StringMap<LineJoin, LINE_JOIN_MAX_ENUM>::Entry lineJoinEntries[];
 	static StringMap<LineJoin, LINE_JOIN_MAX_ENUM> lineJoins;
 
-	static StringMap<StencilAction, STENCIL_MAX_ENUM>::Entry stencilActionEntries[];
-	static StringMap<StencilAction, STENCIL_MAX_ENUM> stencilActions;
-
-	static StringMap<CompareMode, COMPARE_MAX_ENUM>::Entry compareModeEntries[];
-	static StringMap<CompareMode, COMPARE_MAX_ENUM> compareModes;
-
 	static StringMap<Feature, FEATURE_MAX_ENUM>::Entry featureEntries[];
 	static StringMap<Feature, FEATURE_MAX_ENUM> features;
 

+ 16 - 5
src/modules/graphics/Shader.cpp

@@ -192,11 +192,14 @@ TextureType Shader::getMainTextureType() const
 	return info != nullptr ? info->textureType : TEXTURE_MAX_ENUM;
 }
 
-void Shader::checkMainTextureType(TextureType textype) const
+void Shader::checkMainTextureType(TextureType textype, bool isDepthSampler) const
 {
 	const UniformInfo *info = getUniformInfo(BUILTIN_TEXTURE_MAIN);
 
-	if (info != nullptr && info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype)
+	if (info == nullptr)
+		return;
+
+	if (info->textureType != TEXTURE_MAX_ENUM && info->textureType != textype)
 	{
 		const char *textypestr = "unknown";
 		const char *shadertextypestr = "unknown";
@@ -204,14 +207,22 @@ void Shader::checkMainTextureType(TextureType textype) const
 		Texture::getConstant(info->textureType, shadertextypestr);
 		throw love::Exception("Texture's type (%s) must match the type of the shader's main texture type (%s).", textypestr, shadertextypestr);
 	}
+
+	if (info->isDepthSampler != isDepthSampler)
+	{
+		if (info->isDepthSampler)
+			throw love::Exception("Depth comparison samplers in shaders can only be used with depth textures which have depth comparison set.");
+		else
+			throw love::Exception("Depth textures which have depth comparison set can only be used with depth/shadow samplers in shaders.");
+	}
 }
 
-void Shader::checkMainTexture(Texture *texture) const
+void Shader::checkMainTexture(Texture *tex) const
 {
-	if (!texture->isReadable())
+	if (!tex->isReadable())
 		throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
 
-	checkMainTextureType(texture->getTextureType());
+	checkMainTextureType(tex->getTextureType(), tex->getDepthSampleMode().hasValue);
 }
 
 bool Shader::validate(Graphics *gfx, bool gles, const ShaderSource &source, bool checkWithDefaults, std::string &err)

+ 2 - 2
src/modules/graphics/Shader.h

@@ -128,7 +128,7 @@ public:
 
 		UniformType baseType;
 		TextureType textureType;
-		bool isDepthTexture;
+		bool isDepthSampler;
 		std::string name;
 
 		union
@@ -190,7 +190,7 @@ public:
 	virtual void setVideoTextures(Texture *ytexture, Texture *cbtexture, Texture *crtexture) = 0;
 
 	TextureType getMainTextureType() const;
-	void checkMainTextureType(TextureType textype) const;
+	void checkMainTextureType(TextureType textype, bool isDepthSampler) const;
 	void checkMainTexture(Texture *texture) const;
 
 	virtual ptrdiff_t getHandle() const = 0;

+ 11 - 0
src/modules/graphics/Texture.cpp

@@ -230,6 +230,17 @@ float Texture::getMipmapSharpness() const
 	return mipmapSharpness;
 }
 
+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.");
+}
+
+Optional<CompareMode> Texture::getDepthSampleMode() const
+{
+	return depthCompareMode;
+}
+
 Quad *Texture::getQuad() const
 {
 	return quad;

+ 7 - 0
src/modules/graphics/Texture.h

@@ -26,9 +26,11 @@
 #include "common/math.h"
 #include "common/pixelformat.h"
 #include "common/Exception.h"
+#include "common/Optional.h"
 #include "Drawable.h"
 #include "Quad.h"
 #include "vertex.h"
+#include "depthstencil.h"
 
 // C
 #include <stddef.h>
@@ -143,6 +145,9 @@ public:
 	virtual bool setMipmapSharpness(float sharpness) = 0;
 	float getMipmapSharpness() const;
 
+	virtual void setDepthSampleMode(Optional<CompareMode> mode = Optional<CompareMode>());
+	Optional<CompareMode> getDepthSampleMode() const;
+
 	Quad *getQuad() const;
 
 	virtual ptrdiff_t getHandle() const = 0;
@@ -185,6 +190,8 @@ protected:
 
 	float mipmapSharpness;
 
+	Optional<CompareMode> depthCompareMode;
+
 	StrongRef<Quad> quad;
 
 private:

+ 99 - 0
src/modules/graphics/depthstencil.cpp

@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include "depthstencil.h"
+#include "common/StringMap.h"
+
+namespace love
+{
+namespace graphics
+{
+
+CompareMode getReversedCompareMode(CompareMode mode)
+{
+	switch (mode)
+	{
+	case COMPARE_LESS:
+		return COMPARE_GREATER;
+	case COMPARE_LEQUAL:
+		return COMPARE_GEQUAL;
+	case COMPARE_EQUAL:
+		return COMPARE_EQUAL;
+	case COMPARE_GEQUAL:
+		return COMPARE_LEQUAL;
+	case COMPARE_GREATER:
+		return COMPARE_LESS;
+	case COMPARE_NOTEQUAL:
+		return COMPARE_NOTEQUAL;
+	case COMPARE_ALWAYS:
+		return COMPARE_ALWAYS;
+	case COMPARE_MAX_ENUM:
+	default:
+		return COMPARE_MAX_ENUM;
+	}
+}
+
+static StringMap<StencilAction, STENCIL_MAX_ENUM>::Entry stencilActionEntries[] =
+{
+	{ "replace",       STENCIL_REPLACE        },
+	{ "increment",     STENCIL_INCREMENT      },
+	{ "decrement",     STENCIL_DECREMENT      },
+	{ "incrementwrap", STENCIL_INCREMENT_WRAP },
+	{ "decrementwrap", STENCIL_DECREMENT_WRAP },
+	{ "invert",        STENCIL_INVERT         },
+};
+
+static StringMap<StencilAction, STENCIL_MAX_ENUM> stencilActions(stencilActionEntries, sizeof(stencilActionEntries));
+
+static StringMap<CompareMode, COMPARE_MAX_ENUM>::Entry compareModeEntries[] =
+{
+	{ "less",     COMPARE_LESS     },
+	{ "lequal",   COMPARE_LEQUAL   },
+	{ "equal",    COMPARE_EQUAL    },
+	{ "gequal",   COMPARE_GEQUAL   },
+	{ "greater",  COMPARE_GREATER  },
+	{ "notequal", COMPARE_NOTEQUAL },
+	{ "always",   COMPARE_ALWAYS   },
+};
+
+static StringMap<CompareMode, COMPARE_MAX_ENUM> compareModes(compareModeEntries, sizeof(compareModeEntries));
+
+bool getConstant(const char *in, StencilAction &out)
+{
+	return stencilActions.find(in, out);
+}
+
+bool getConstant(StencilAction in, const char *&out)
+{
+	return stencilActions.find(in, out);
+}
+
+bool getConstant(const char *in, CompareMode &out)
+{
+	return compareModes.find(in, out);
+}
+
+bool getConstant(CompareMode in, const char *&out)
+{
+	return compareModes.find(in, out);
+}
+
+} // graphics
+} // love

+ 68 - 0
src/modules/graphics/depthstencil.h

@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2006-2017 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#pragma once
+
+namespace love
+{
+namespace graphics
+{
+
+enum StencilAction
+{
+	STENCIL_REPLACE,
+	STENCIL_INCREMENT,
+	STENCIL_DECREMENT,
+	STENCIL_INCREMENT_WRAP,
+	STENCIL_DECREMENT_WRAP,
+	STENCIL_INVERT,
+	STENCIL_MAX_ENUM
+};
+
+enum CompareMode
+{
+	COMPARE_LESS,
+	COMPARE_LEQUAL,
+	COMPARE_EQUAL,
+	COMPARE_GEQUAL,
+	COMPARE_GREATER,
+	COMPARE_NOTEQUAL,
+	COMPARE_ALWAYS,
+	COMPARE_MAX_ENUM
+};
+
+/**
+ * GPU APIs do the comparison in the opposite way of what makes sense for some
+ * of love's APIs. For example in OpenGL if the compare function is GL_GREATER,
+ * then the stencil test will pass if the reference value is greater than the
+ * value in the stencil buffer. With our stencil API it's more intuitive to
+ * assume that setStencilTest(COMPARE_GREATER, 4) will make it pass if the
+ * stencil buffer has a value greater than 4.
+ **/
+CompareMode getReversedCompareMode(CompareMode mode);
+
+bool getConstant(const char *in, StencilAction &out);
+bool getConstant(StencilAction in, const char *&out);
+
+bool getConstant(const char *in, CompareMode &out);
+bool getConstant(CompareMode in, const char *&out);
+
+} // graphics
+} // love

+ 41 - 0
src/modules/graphics/opengl/Canvas.cpp

@@ -277,6 +277,7 @@ bool Canvas::loadVolatile()
 
 		setFilter(filter);
 		setWrap(wrap);
+		setDepthSampleMode(depthCompareMode);
 
 		while (glGetError() != GL_NO_ERROR)
 			/* Clear the error buffer. */;
@@ -358,6 +359,8 @@ void Canvas::setFilter(const Texture::Filter &f)
 	if (!validateFilter(f, false))
 		throw love::Exception("Invalid texture filter.");
 
+	Graphics::flushStreamDrawsGlobal();
+
 	filter = f;
 	gl.bindTextureToUnit(this, 0, false);
 	gl.setTextureFilter(texType, filter);
@@ -365,6 +368,8 @@ void Canvas::setFilter(const Texture::Filter &f)
 
 bool Canvas::setWrap(const Texture::Wrap &w)
 {
+	Graphics::flushStreamDrawsGlobal();
+
 	bool success = true;
 	bool forceclamp = texType == TEXTURE_CUBE;
 	wrap = w;
@@ -402,6 +407,42 @@ bool Canvas::setMipmapSharpness(float /*sharpness*/)
 	return false;
 }
 
+void Canvas::setDepthSampleMode(Optional<CompareMode> mode)
+{
+	Texture::setDepthSampleMode(mode);
+
+	bool supported = gl.isDepthCompareSampleSupported();
+
+	if (mode.hasValue && !supported)
+		throw love::Exception("Depth comparison sampling in shaders is not supported on this system.");
+
+	if (mode.hasValue)
+	{
+		Graphics::flushStreamDrawsGlobal();
+
+		gl.bindTextureToUnit(texType, texture, 0, false);
+		GLenum gltextype = OpenGL::getGLTextureType(texType);
+
+		// See the comment in depthstencil.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;
+}
+
 ptrdiff_t Canvas::getHandle() const
 {
 	return texture;

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

@@ -50,6 +50,7 @@ public:
 	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;
 	ptrdiff_t getHandle() const override;
 
 	love::image::ImageData *newImageData(love::image::Image *module, int slice, int x, int y, int w, int h) override;

+ 2 - 38
src/modules/graphics/opengl/Graphics.cpp

@@ -354,12 +354,6 @@ void Graphics::flushStreamDraws()
 			prevdefaultshader = Shader::current;
 			Shader::standardShaders[Shader::STANDARD_ARRAY]->attach();
 		}
-
-		if (!sbstate.texture->isReadable())
-			throw love::Exception("Textures with non-readable formats cannot be drawn.");
-
-		if (Shader::current)
-			Shader::current->checkMainTextureType(textype);
 	}
 
 	OpenGL::TempDebugGroup debuggroup("Stream vertices flush and draw");
@@ -1236,45 +1230,15 @@ void Graphics::setStencilTest(CompareMode compare, int value)
 		return;
 	}
 
-	GLenum glcompare = GL_EQUAL;
-
 	/**
-	 * Q: Why are some of the compare modes inverted (e.g. COMPARE_LESS becomes
-	 * GL_GREATER)?
-	 *
-	 * A: OpenGL / GPUs do the comparison in the opposite way that makes sense
+	 * OpenGL / GPUs do the comparison in the opposite way that makes sense
 	 * for this API. For example, if the compare function is GL_GREATER then the
 	 * stencil test will pass if the reference value is greater than the value
 	 * in the stencil buffer. With our API it's more intuitive to assume that
 	 * setStencilTest(COMPARE_GREATER, 4) will make it pass if the stencil
 	 * buffer has a value greater than 4.
 	 **/
-
-	switch (compare)
-	{
-	case COMPARE_LESS:
-		glcompare = GL_GREATER;
-		break;
-	case COMPARE_LEQUAL:
-		glcompare = GL_GEQUAL;
-		break;
-	case COMPARE_EQUAL:
-	default:
-		glcompare = GL_EQUAL;
-		break;
-	case COMPARE_GEQUAL:
-		glcompare = GL_LEQUAL;
-		break;
-	case COMPARE_GREATER:
-		glcompare = GL_LESS;
-		break;
-	case COMPARE_NOTEQUAL:
-		glcompare = GL_NOTEQUAL;
-		break;
-	case COMPARE_ALWAYS:
-		glcompare = GL_ALWAYS;
-		break;
-	}
+	GLenum glcompare = OpenGL::getGLCompareMode(getReversedCompareMode(compare));
 
 	glEnable(GL_STENCIL_TEST);
 	glStencilFunc(glcompare, value, 0xFFFFFFFF);

+ 10 - 0
src/modules/graphics/opengl/Image.cpp

@@ -380,6 +380,8 @@ void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap,
 	if (w != oldd->getWidth() || h != oldd->getHeight())
 		throw love::Exception("Dimensions must match the texture's dimensions for the specified mipmap level.");
 
+	Graphics::flushStreamDrawsGlobal();
+
 	d->retain();
 	oldd->release();
 
@@ -397,6 +399,8 @@ void Image::replacePixels(love::image::ImageDataBase *d, int slice, int mipmap,
 
 void Image::replacePixels(const void *data, size_t size, const Rect &rect, int slice, int mipmap, bool reloadmipmaps)
 {
+	Graphics::flushStreamDrawsGlobal();
+
 	OpenGL::TempDebugGroup debuggroup("Image replace pixels");
 
 	gl.bindTextureToUnit(this, 0, false);
@@ -422,6 +426,8 @@ void Image::setFilter(const Texture::Filter &f)
 			throw love::Exception("Invalid texture filter.");
 	}
 
+	Graphics::flushStreamDrawsGlobal();
+
 	filter = f;
 
 	if (!OpenGL::hasTextureFilteringSupport(getPixelFormat()))
@@ -445,6 +451,8 @@ void Image::setFilter(const Texture::Filter &f)
 
 bool Image::setWrap(const Texture::Wrap &w)
 {
+	Graphics::flushStreamDrawsGlobal();
+
 	bool success = true;
 	bool forceclamp = texType == TEXTURE_CUBE;
 	wrap = w;
@@ -483,6 +491,8 @@ bool Image::setMipmapSharpness(float sharpness)
 	if (!GLAD_VERSION_1_4)
 		return false;
 
+	Graphics::flushStreamDrawsGlobal();
+
 	// LOD bias has the range (-maxbias, maxbias)
 	mipmapSharpness = std::min(std::max(sharpness, -maxMipmapSharpness + 0.01f), maxMipmapSharpness - 0.01f);
 

+ 30 - 0
src/modules/graphics/opengl/OpenGL.cpp

@@ -987,6 +987,29 @@ GLint OpenGL::getGLWrapMode(Texture::WrapMode wmode)
 
 }
 
+GLint OpenGL::getGLCompareMode(CompareMode mode)
+{
+	switch (mode)
+	{
+	case COMPARE_LESS:
+		return GL_LESS;
+	case COMPARE_LEQUAL:
+		return GL_LEQUAL;
+	case COMPARE_EQUAL:
+		return GL_EQUAL;
+	case COMPARE_GEQUAL:
+		return GL_GEQUAL;
+	case COMPARE_GREATER:
+		return GL_GREATER;
+	case COMPARE_NOTEQUAL:
+		return GL_NOTEQUAL;
+	case COMPARE_ALWAYS:
+		return GL_ALWAYS;
+	default:
+		return GL_NEVER;
+	}
+}
+
 void OpenGL::setTextureWrap(TextureType target, const graphics::Texture::Wrap &w)
 {
 	glTexParameteri(getGLTextureType(target), GL_TEXTURE_WRAP_S, getGLWrapMode(w.s));
@@ -1108,6 +1131,13 @@ bool OpenGL::isInstancingSupported() const
 		|| GLAD_ARB_instanced_arrays || GLAD_EXT_instanced_arrays || GLAD_ANGLE_instanced_arrays;
 }
 
+bool OpenGL::isDepthCompareSampleSupported() const
+{
+	// Our official API only supports this in GLSL3 shaders, but unofficially
+	// the requirements are more lax.
+	return GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_EXT_shadow_samplers;
+}
+
 int OpenGL::getMax2DTextureSize() const
 {
 	return std::max(max2DTextureSize, 1);

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

@@ -28,6 +28,7 @@
 #include "graphics/Color.h"
 #include "graphics/Texture.h"
 #include "graphics/vertex.h"
+#include "graphics/depthstencil.h"
 #include "common/Matrix.h"
 
 // GLAD
@@ -326,6 +327,7 @@ public:
 	bool isClampZeroTextureWrapSupported() const;
 	bool isPixelShaderHighpSupported() const;
 	bool isInstancingSupported() const;
+	bool isDepthCompareSampleSupported() const;
 
 	/**
 	 * Returns the maximum supported width or height of a texture.
@@ -379,6 +381,7 @@ public:
 	static GLenum getGLBufferUsage(vertex::Usage usage);
 	static GLenum getGLTextureType(TextureType type);
 	static GLint getGLWrapMode(Texture::WrapMode wmode);
+	static GLint getGLCompareMode(CompareMode mode);
 
 	static TextureFormat convertPixelFormat(PixelFormat pixelformat, bool renderbuffer, bool &isSRGB);
 	static bool isTexStorageSupported();

+ 56 - 14
src/modules/graphics/opengl/Shader.cpp

@@ -173,6 +173,7 @@ void Shader::mapActiveUniforms()
 		u.location = glGetUniformLocation(program, u.name.c_str());
 		u.baseType = getUniformBaseType(gltype);
 		u.textureType = getUniformTextureType(gltype);
+		u.isDepthSampler = isDepthTextureType(gltype);
 
 		if (u.baseType == UNIFORM_MATRIX)
 			u.matrix = getMatrixSize(gltype);
@@ -675,22 +676,51 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 	// Bind the textures to the texture units.
 	for (int i = 0; i < count; i++)
 	{
-		if (textures[i] != nullptr)
+		Texture *tex = textures[i];
+
+		if (tex != nullptr)
 		{
-			if (textures[i]->getTextureType() != info->textureType || !textures[i]->isReadable())
-				continue;
+			if (!tex->isReadable())
+			{
+				if (internalUpdate)
+					continue;
+				else
+					throw love::Exception("Textures with non-readable formats cannot be sampled from in a shader.");
+			}
+			else if (info->isDepthSampler != tex->getDepthSampleMode().hasValue)
+			{
+				if (internalUpdate)
+					continue;
+				else if (info->isDepthSampler)
+					throw love::Exception("Depth comparison samplers in shaders can only be used with depth textures which have depth comparison set.");
+				else
+					throw love::Exception("Depth textures which have depth comparison set can only be used with depth/shadow samplers in shaders.");
+			}
+			else if (tex->getTextureType() != info->textureType)
+			{
+				if (internalUpdate)
+					continue;
+				else
+				{
+					const char *textypestr = "unknown";
+					const char *shadertextypestr = "unknown";
+					Texture::getConstant(tex->getTextureType(), textypestr);
+					Texture::getConstant(info->textureType, shadertextypestr);
+					throw love::Exception("Texture's type (%s) must match the type of %s (%s).", textypestr, info->name.c_str(), shadertextypestr);
+				}
+			}
 
-			textures[i]->retain();
+			tex->retain();
 		}
 
 		if (info->textures[i] != nullptr)
 			info->textures[i]->release();
 
-		info->textures[i] = textures[i];
+		info->textures[i] = tex;
 
 		GLuint gltex = 0;
 		if (textures[i] != nullptr)
-			gltex = (GLuint) textures[i]->getHandle();
+			gltex = (GLuint) tex->getHandle();
 		else
 			gltex = gl.getDefaultTexture(info->textureType);
 
@@ -707,11 +737,7 @@ void Shader::sendTextures(const UniformInfo *info, Texture **textures, int count
 void Shader::flushStreamDraws() const
 {
 	if (current == this)
-	{
-		auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
-		if (gfx != nullptr)
-			gfx->flushStreamDraws();
-	}
+		Graphics::flushStreamDrawsGlobal();
 }
 
 bool Shader::hasUniform(const std::string &name) const
@@ -1035,7 +1061,7 @@ TextureType Shader::getUniformTextureType(GLenum type) const
 		// 1D-typed textures are not supported.
 		return TEXTURE_MAX_ENUM;
 	case GL_SAMPLER_2D:
-	//case GL_SAMPLER_2D_SHADOW:
+	case GL_SAMPLER_2D_SHADOW:
 		return TEXTURE_2D;
 	case GL_SAMPLER_2D_MULTISAMPLE:
 	case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
@@ -1046,12 +1072,12 @@ TextureType Shader::getUniformTextureType(GLenum type) const
 		// Rectangle textures are not supported.
 		return TEXTURE_MAX_ENUM;
 	case GL_SAMPLER_2D_ARRAY:
-	//case GL_SAMPLER_2D_ARRAY_SHADOW:
+	case GL_SAMPLER_2D_ARRAY_SHADOW:
 		return TEXTURE_2D_ARRAY;
 	case GL_SAMPLER_3D:
 		return TEXTURE_VOLUME;
 	case GL_SAMPLER_CUBE:
-	//case GL_SAMPLER_CUBE_SHADOW:
+	case GL_SAMPLER_CUBE_SHADOW:
 		return TEXTURE_CUBE;
 	case GL_SAMPLER_CUBE_MAP_ARRAY:
 	case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
@@ -1062,6 +1088,22 @@ TextureType Shader::getUniformTextureType(GLenum type) const
 	}
 }
 
+bool Shader::isDepthTextureType(GLenum type) const
+{
+	switch (type)
+	{
+	case GL_SAMPLER_1D_SHADOW:
+	case GL_SAMPLER_1D_ARRAY_SHADOW:
+	case GL_SAMPLER_2D_SHADOW:
+	case GL_SAMPLER_2D_ARRAY_SHADOW:
+	case GL_SAMPLER_CUBE_SHADOW:
+	case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
+		return true;
+	default:
+		return false;
+	}
+}
+
 } // opengl
 } // graphics
 } // love

+ 1 - 0
src/modules/graphics/opengl/Shader.h

@@ -94,6 +94,7 @@ private:
 	MatrixSize getMatrixSize(GLenum type) const;
 	UniformType getUniformBaseType(GLenum type) const;
 	TextureType getUniformTextureType(GLenum type) const;
+	bool isDepthTextureType(GLenum type) const;
 
 	GLuint compileCode(ShaderStage stage, const std::string &code);
 

+ 1 - 1
src/modules/graphics/opengl/Text.cpp

@@ -46,7 +46,7 @@ void Text::draw(Graphics *gfx, const Matrix4 &m)
 		return;
 
 	if (Shader::current)
-		Shader::current->checkMainTextureType(TEXTURE_2D);
+		Shader::current->checkMainTextureType(TEXTURE_2D, false);
 
 	gfx->flushStreamDraws();
 

+ 6 - 6
src/modules/graphics/wrap_Graphics.cpp

@@ -489,12 +489,12 @@ int w_stencil(lua_State *L)
 {
 	luaL_checktype(L, 1, LUA_TFUNCTION);
 
-	Graphics::StencilAction action = Graphics::STENCIL_REPLACE;
+	StencilAction action = STENCIL_REPLACE;
 
 	if (!lua_isnoneornil(L, 2))
 	{
 		const char *actionstr = luaL_checkstring(L, 2);
-		if (!Graphics::getConstant(actionstr, action))
+		if (!getConstant(actionstr, action))
 			return luaL_error(L, "Invalid stencil draw action: %s", actionstr);
 	}
 
@@ -522,13 +522,13 @@ int w_stencil(lua_State *L)
 int w_setStencilTest(lua_State *L)
 {
 	// COMPARE_ALWAYS effectively disables stencil testing.
-	Graphics::CompareMode compare = Graphics::COMPARE_ALWAYS;
+	CompareMode compare = COMPARE_ALWAYS;
 	int comparevalue = 0;
 
 	if (!lua_isnoneornil(L, 1))
 	{
 		const char *comparestr = luaL_checkstring(L, 1);
-		if (!Graphics::getConstant(comparestr, compare))
+		if (!getConstant(comparestr, compare))
 			return luaL_error(L, "Invalid compare mode: %s", comparestr);
 
 		comparevalue = (int) luaL_checknumber(L, 2);
@@ -540,13 +540,13 @@ int w_setStencilTest(lua_State *L)
 
 int w_getStencilTest(lua_State *L)
 {
-	Graphics::CompareMode compare = Graphics::COMPARE_ALWAYS;
+	CompareMode compare = COMPARE_ALWAYS;
 	int comparevalue = 1;
 
 	instance()->getStencilTest(compare, comparevalue);
 
 	const char *comparestr;
-	if (!Graphics::getConstant(compare, comparestr))
+	if (!getConstant(compare, comparestr))
 		return luaL_error(L, "Unknown compare mode.");
 
 	lua_pushstring(L, comparestr);

+ 10 - 0
src/modules/graphics/wrap_Graphics.lua

@@ -50,6 +50,11 @@ GLSL.SYNTAX = [[
 #define ArrayImage sampler2DArray
 #define CubeImage samplerCube
 #define VolumeImage sampler3D
+#if __VERSION__ >= 300 && !defined(LOVE_GLSL1_ON_GLSL3)
+	#define DepthImage sampler2DShadow
+	#define DepthArrayImage sampler2DArrayShadow
+	#define DepthCubeImage samplerCubeShadow
+#endif
 #define extern uniform
 #ifdef GL_EXT_texture_array
 #extension GL_EXT_texture_array : enable
@@ -78,6 +83,11 @@ GLSL.FUNCTIONS = [[
 	#if __VERSION__ >= 300 || defined(GL_OES_texture_3D)
 		precision lowp sampler3D;
 	#endif
+	#if __VERSION__ >= 300
+		precision lowp sampler2DShadow;
+		precision lowp samplerCubeShadow;
+		precision lowp sampler2DArrayShadow;
+	#endif
 #endif
 
 #if __VERSION__ >= 130 && !defined(LOVE_GLSL1_ON_GLSL3)

+ 38 - 0
src/modules/graphics/wrap_Texture.cpp

@@ -244,6 +244,42 @@ int w_Texture_isReadable(lua_State *L)
 	return 1;
 }
 
+int w_Texture_setDepthSampleMode(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+
+	Optional<CompareMode> mode;
+	if (!lua_isnoneornil(L, 2))
+	{
+		const char *str = luaL_checkstring(L, 2);
+
+		mode.hasValue = true;
+		if (!getConstant(str, mode.value))
+			return luaL_error(L, "Invalid compare mode: %s", str);
+	}
+
+	luax_catchexcept(L, [&]() { t->setDepthSampleMode(mode); });
+	return 0;
+}
+
+int w_Texture_getDepthSampleMode(lua_State *L)
+{
+	Texture *t = luax_checktexture(L, 1);
+	Optional<CompareMode> mode = t->getDepthSampleMode();
+
+	if (mode.hasValue)
+	{
+		const char *str = nullptr;
+		if (!getConstant(mode.value, str))
+			return luaL_error(L, "Unknown compare mode.");
+		lua_pushstring(L, str);
+	}
+	else
+		lua_pushnil(L);
+
+	return 1;
+}
+
 const luaL_Reg w_Texture_functions[] =
 {
 	{ "getTextureType", w_Texture_getTextureType },
@@ -265,6 +301,8 @@ const luaL_Reg w_Texture_functions[] =
 	{ "getWrap", w_Texture_getWrap },
 	{ "getFormat", w_Texture_getFormat },
 	{ "isReadable", w_Texture_isReadable },
+	{ "getDepthSampleMode", w_Texture_getDepthSampleMode },
+	{ "setDepthSampleMode", w_Texture_setDepthSampleMode },
 	{ 0, 0 }
 };