Browse Source

Add love.graphics.setBlendState, which sets the lower level blend operation and factor values which higher level blend modes are based on.

--HG--
branch : minor
Alex Szpakowski 5 years ago
parent
commit
b41f1316fa

+ 22 - 5
src/modules/graphics/Graphics.cpp

@@ -376,7 +376,7 @@ void Graphics::restoreState(const DisplayState &s)
 	setColor(s.color);
 	setColor(s.color);
 	setBackgroundColor(s.backgroundColor);
 	setBackgroundColor(s.backgroundColor);
 
 
-	setBlendMode(s.blendMode, s.blendAlphaMode);
+	setBlendState(s.blend);
 
 
 	setLineWidth(s.lineWidth);
 	setLineWidth(s.lineWidth);
 	setLineStyle(s.lineStyle);
 	setLineStyle(s.lineStyle);
@@ -415,8 +415,8 @@ void Graphics::restoreStateChecked(const DisplayState &s)
 
 
 	setBackgroundColor(s.backgroundColor);
 	setBackgroundColor(s.backgroundColor);
 
 
-	if (s.blendMode != cur.blendMode || s.blendAlphaMode != cur.blendAlphaMode)
-		setBlendMode(s.blendMode, s.blendAlphaMode);
+	if (!(s.blend == cur.blend))
+		setBlendState(s.blend);
 
 
 	// These are just simple assignments.
 	// These are just simple assignments.
 	setLineWidth(s.lineWidth);
 	setLineWidth(s.lineWidth);
@@ -901,10 +901,26 @@ ColorChannelMask Graphics::getColorMask() const
 	return states.back().colorMask;
 	return states.back().colorMask;
 }
 }
 
 
+void Graphics::setBlendMode(BlendMode mode, BlendAlpha alphamode)
+{
+	if (alphamode == BLENDALPHA_MULTIPLY && !isAlphaMultiplyBlendSupported(mode))
+	{
+		const char *modestr = "unknown";
+		love::graphics::getConstant(mode, modestr);
+		throw love::Exception("The '%s' blend mode must be used with premultiplied alpha.", modestr);
+	}
+
+	setBlendState(computeBlendState(mode, alphamode));
+}
+
 BlendMode Graphics::getBlendMode(BlendAlpha &alphamode) const
 BlendMode Graphics::getBlendMode(BlendAlpha &alphamode) const
 {
 {
-	alphamode = states.back().blendAlphaMode;
-	return states.back().blendMode;
+	return computeBlendMode(states.back().blend, alphamode);
+}
+
+const BlendState &Graphics::getBlendState() const
+{
+	return states.back().blend;
 }
 }
 
 
 void Graphics::setDefaultFilter(const Texture::Filter &f)
 void Graphics::setDefaultFilter(const Texture::Filter &f)
@@ -1881,6 +1897,7 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
 {
 {
 	{ "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },
 	{ "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },
 	{ "clampzero",          FEATURE_CLAMP_ZERO           },
 	{ "clampzero",          FEATURE_CLAMP_ZERO           },
+	{ "blendminmax",        FEATURE_BLENDMINMAX          },
 	{ "lighten",            FEATURE_LIGHTEN              },
 	{ "lighten",            FEATURE_LIGHTEN              },
 	{ "fullnpot",           FEATURE_FULL_NPOT            },
 	{ "fullnpot",           FEATURE_FULL_NPOT            },
 	{ "pixelshaderhighp",   FEATURE_PIXEL_SHADER_HIGHP   },
 	{ "pixelshaderhighp",   FEATURE_PIXEL_SHADER_HIGHP   },

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

@@ -137,7 +137,8 @@ public:
 	{
 	{
 		FEATURE_MULTI_CANVAS_FORMATS,
 		FEATURE_MULTI_CANVAS_FORMATS,
 		FEATURE_CLAMP_ZERO,
 		FEATURE_CLAMP_ZERO,
-		FEATURE_LIGHTEN,
+		FEATURE_BLENDMINMAX,
+		FEATURE_LIGHTEN, // Deprecated
 		FEATURE_FULL_NPOT,
 		FEATURE_FULL_NPOT,
 		FEATURE_PIXEL_SHADER_HIGHP,
 		FEATURE_PIXEL_SHADER_HIGHP,
 		FEATURE_SHADER_DERIVATIVES,
 		FEATURE_SHADER_DERIVATIVES,
@@ -607,14 +608,16 @@ public:
 	ColorChannelMask getColorMask() const;
 	ColorChannelMask getColorMask() const;
 
 
 	/**
 	/**
-	 * Sets the current blend mode.
+	 * High-level blend mode.
 	 **/
 	 **/
-	virtual void setBlendMode(BlendMode mode, BlendAlpha alphamode) = 0;
+	void setBlendMode(BlendMode mode, BlendAlpha alphamode);
+	BlendMode getBlendMode(BlendAlpha &alphamode) const;
 
 
 	/**
 	/**
-	 * Gets the current blend mode.
+	 * Low-level blend state.
 	 **/
 	 **/
-	BlendMode getBlendMode(BlendAlpha &alphamode) const;
+	virtual void setBlendState(const BlendState &blend) = 0;
+	const BlendState &getBlendState() const;
 
 
 	/**
 	/**
 	 * Sets the default filter for images, canvases, and fonts.
 	 * Sets the default filter for images, canvases, and fonts.
@@ -886,8 +889,7 @@ protected:
 		Colorf color = Colorf(1.0, 1.0, 1.0, 1.0);
 		Colorf color = Colorf(1.0, 1.0, 1.0, 1.0);
 		Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 1.0);
 		Colorf backgroundColor = Colorf(0.0, 0.0, 0.0, 1.0);
 
 
-		BlendMode blendMode = BLEND_ALPHA;
-		BlendAlpha blendAlphaMode = BLENDALPHA_MULTIPLY;
+		BlendState blend;
 
 
 		float lineWidth = 1.0f;
 		float lineWidth = 1.0f;
 		LineStyle lineStyle = LINE_SMOOTH;
 		LineStyle lineStyle = LINE_SMOOTH;

+ 16 - 32
src/modules/graphics/opengl/Graphics.cpp

@@ -1268,46 +1268,29 @@ void Graphics::setColorMask(ColorChannelMask mask)
 	states.back().colorMask = mask;
 	states.back().colorMask = mask;
 }
 }
 
 
-void Graphics::setBlendMode(BlendMode mode, BlendAlpha alphamode)
+void Graphics::setBlendState(const BlendState &blend)
 {
 {
-	if (mode != states.back().blendMode || alphamode != states.back().blendAlphaMode)
+	if (!(blend == states.back().blend))
 		flushStreamDraws();
 		flushStreamDraws();
 
 
-	if (mode == BLEND_LIGHTEN || mode == BLEND_DARKEN)
+	if (blend.operationRGB == BLENDOP_MAX || blend.operationA == BLENDOP_MAX
+		|| blend.operationRGB == BLENDOP_MIN || blend.operationA == BLENDOP_MIN)
 	{
 	{
-		if (!capabilities.features[FEATURE_LIGHTEN])
-			throw love::Exception("The 'lighten' and 'darken' blend modes are not supported on this system.");
+		if (!capabilities.features[FEATURE_BLENDMINMAX])
+			throw love::Exception("The 'min' and 'max' blend operations are not supported on this system.");
 	}
 	}
 
 
-	if (alphamode != BLENDALPHA_PREMULTIPLIED)
-	{
-		const char *modestr = "unknown";
-		switch (mode)
-		{
-		case BLEND_LIGHTEN:
-		case BLEND_DARKEN:
-		case BLEND_MULTIPLY:
-			love::graphics::getConstant(mode, modestr);
-			throw love::Exception("The '%s' blend mode must be used with premultiplied alpha.", modestr);
-			break;
-		default:
-			break;
-		}
-	}
-
-	BlendState state = getBlendState(mode, alphamode);
-	GLenum opRGB  = getGLBlendOperation(state.operationRGB);
-	GLenum opA    = getGLBlendOperation(state.operationA);
-	GLenum srcRGB = getGLBlendFactor(state.srcFactorRGB);
-	GLenum srcA   = getGLBlendFactor(state.srcFactorA);
-	GLenum dstRGB = getGLBlendFactor(state.dstFactorRGB);
-	GLenum dstA   = getGLBlendFactor(state.dstFactorA);
+	GLenum opRGB  = getGLBlendOperation(blend.operationRGB);
+	GLenum opA    = getGLBlendOperation(blend.operationA);
+	GLenum srcRGB = getGLBlendFactor(blend.srcFactorRGB);
+	GLenum srcA   = getGLBlendFactor(blend.srcFactorA);
+	GLenum dstRGB = getGLBlendFactor(blend.dstFactorRGB);
+	GLenum dstA   = getGLBlendFactor(blend.dstFactorA);
 
 
 	glBlendEquationSeparate(opRGB, opA);
 	glBlendEquationSeparate(opRGB, opA);
 	glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
 	glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
 
 
-	states.back().blendMode = mode;
-	states.back().blendAlphaMode = alphamode;
+	states.back().blend = blend;
 }
 }
 
 
 void Graphics::setPointSize(float size)
 void Graphics::setPointSize(float size)
@@ -1375,14 +1358,15 @@ void Graphics::initCapabilities()
 {
 {
 	capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = Canvas::isMultiFormatMultiCanvasSupported();
 	capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = Canvas::isMultiFormatMultiCanvasSupported();
 	capabilities.features[FEATURE_CLAMP_ZERO] = gl.isClampZeroTextureWrapSupported();
 	capabilities.features[FEATURE_CLAMP_ZERO] = gl.isClampZeroTextureWrapSupported();
-	capabilities.features[FEATURE_LIGHTEN] = GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax;
+	capabilities.features[FEATURE_BLENDMINMAX] = GLAD_VERSION_1_4 || GLAD_ES_VERSION_3_0 || GLAD_EXT_blend_minmax;
+	capabilities.features[FEATURE_LIGHTEN] = capabilities.features[FEATURE_BLENDMINMAX];
 	capabilities.features[FEATURE_FULL_NPOT] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot;
 	capabilities.features[FEATURE_FULL_NPOT] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot;
 	capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = gl.isPixelShaderHighpSupported();
 	capabilities.features[FEATURE_PIXEL_SHADER_HIGHP] = gl.isPixelShaderHighpSupported();
 	capabilities.features[FEATURE_SHADER_DERIVATIVES] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_standard_derivatives;
 	capabilities.features[FEATURE_SHADER_DERIVATIVES] = GLAD_VERSION_2_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_standard_derivatives;
 	capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	capabilities.features[FEATURE_GLSL3] = GLAD_ES_VERSION_3_0 || gl.isCoreProfile();
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_GLSL4] = GLAD_ES_VERSION_3_1 || (gl.isCoreProfile() && GLAD_VERSION_4_3);
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
 	capabilities.features[FEATURE_INSTANCING] = gl.isInstancingSupported();
-	static_assert(FEATURE_MAX_ENUM == 9, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
+	static_assert(FEATURE_MAX_ENUM == 10, "Graphics::initCapabilities must be updated when adding a new graphics feature!");
 
 
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_POINT_SIZE] = gl.getMaxPointSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();
 	capabilities.limits[LIMIT_TEXTURE_SIZE] = gl.getMax2DTextureSize();

+ 1 - 1
src/modules/graphics/opengl/Graphics.h

@@ -98,7 +98,7 @@ public:
 
 
 	void setColorMask(ColorChannelMask mask) override;
 	void setColorMask(ColorChannelMask mask) override;
 
 
-	void setBlendMode(BlendMode mode, BlendAlpha alphamode) override;
+	void setBlendState(const BlendState &blend) override;
 
 
 	void setPointSize(float size) override;
 	void setPointSize(float size) override;
 
 

+ 75 - 39
src/modules/graphics/renderstate.cpp

@@ -26,48 +26,44 @@ namespace love
 namespace graphics
 namespace graphics
 {
 {
 
 
-BlendState getBlendState(BlendMode mode, BlendAlpha alphamode)
+// These are all with premultiplied alpha. computeBlendState adjusts for
+// alpha-multiply if needed.
+static const BlendState states[BLEND_MAX_ENUM] =
 {
 {
-	BlendState s;
+	// BLEND_ALPHA
+	{BLENDOP_ADD, BLENDOP_ADD, BLENDFACTOR_ONE, BLENDFACTOR_ONE, BLENDFACTOR_ONE_MINUS_SRC_ALPHA, BLENDFACTOR_ONE_MINUS_SRC_ALPHA},
 
 
-	s.enable = mode != BLEND_NONE;
-	s.operationRGB = BLENDOP_ADD;
-	s.operationA = BLENDOP_ADD;
+	// BLEND_ADD
+	{BLENDOP_ADD, BLENDOP_ADD, BLENDFACTOR_ONE, BLENDFACTOR_ZERO, BLENDFACTOR_ONE, BLENDFACTOR_ONE},
 
 
-	switch (mode)
-	{
-	case BLEND_ALPHA:
-		s.srcFactorRGB = s.srcFactorA = BLENDFACTOR_ONE;
-		s.dstFactorRGB = s.dstFactorA = BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
-		break;
-	case BLEND_MULTIPLY:
-		s.srcFactorRGB = s.srcFactorA = BLENDFACTOR_DST_COLOR;
-		s.dstFactorRGB = s.dstFactorA = BLENDFACTOR_ZERO;
-		break;
-	case BLEND_SUBTRACT:
-		s.operationRGB = s.operationA = BLENDOP_REVERSE_SUBTRACT;
-	case BLEND_ADD:
-		s.srcFactorRGB = BLENDFACTOR_ONE;
-		s.srcFactorA = BLENDFACTOR_ZERO;
-		s.dstFactorRGB = s.dstFactorA = BLENDFACTOR_ONE;
-		break;
-	case BLEND_LIGHTEN:
-		s.operationRGB = s.operationA = BLENDOP_MAX;
-		break;
-	case BLEND_DARKEN:
-		s.operationRGB = s.operationA = BLENDOP_MIN;
-		break;
-	case BLEND_SCREEN:
-		s.srcFactorRGB = s.srcFactorA = BLENDFACTOR_ONE;
-		s.dstFactorRGB = s.dstFactorA = BLENDFACTOR_ONE_MINUS_SRC_COLOR;
-		break;
-	case BLEND_REPLACE:
-	case BLEND_NONE:
-	default:
-		s.srcFactorRGB = s.srcFactorA = BLENDFACTOR_ONE;
-		s.dstFactorRGB = s.dstFactorA = BLENDFACTOR_ZERO;
-		break;
-	}
+	// BLEND_SUBTRACT
+	{BLENDOP_REVERSE_SUBTRACT, BLENDOP_REVERSE_SUBTRACT, BLENDFACTOR_ONE, BLENDFACTOR_ZERO, BLENDFACTOR_ONE, BLENDFACTOR_ONE},
+
+	// BLEND_MULTIPLY
+	{BLENDOP_ADD, BLENDOP_ADD, BLENDFACTOR_DST_COLOR, BLENDFACTOR_DST_COLOR, BLENDFACTOR_ZERO, BLENDFACTOR_ZERO},
+
+	// BLEND_LIGHTEN
+	{BLENDOP_MAX, BLENDOP_MAX, BLENDFACTOR_ZERO, BLENDFACTOR_ZERO, BLENDFACTOR_ONE, BLENDFACTOR_ONE},
+
+	// BLEND_DARKEN
+	{BLENDOP_MAX, BLENDOP_MAX, BLENDFACTOR_ONE, BLENDFACTOR_ONE, BLENDFACTOR_ONE, BLENDFACTOR_ONE},
+
+	// BLEND_SCREEN
+	{BLENDOP_ADD, BLENDOP_ADD, BLENDFACTOR_ONE, BLENDFACTOR_ONE, BLENDFACTOR_ONE_MINUS_SRC_COLOR, BLENDFACTOR_ONE_MINUS_SRC_COLOR},
+
+	// BLEND_REPLACE
+	{BLENDOP_ADD, BLENDOP_ADD, BLENDFACTOR_ONE, BLENDFACTOR_ONE, BLENDFACTOR_ZERO, BLENDFACTOR_ZERO},
+
+	// BLEND_NONE
+	{},
+
+	// BLEND_CUSTOM - N/A
+	{},
+};
+
+BlendState computeBlendState(BlendMode mode, BlendAlpha alphamode)
+{
+	BlendState s = states[mode];
 
 
 	// We can only do alpha-multiplication when srcRGB would have been unmodified.
 	// We can only do alpha-multiplication when srcRGB would have been unmodified.
 	if (s.srcFactorRGB == BLENDFACTOR_ONE && alphamode == BLENDALPHA_MULTIPLY && mode != BLEND_NONE)
 	if (s.srcFactorRGB == BLENDFACTOR_ONE && alphamode == BLENDALPHA_MULTIPLY && mode != BLEND_NONE)
@@ -76,6 +72,45 @@ BlendState getBlendState(BlendMode mode, BlendAlpha alphamode)
 	return s;
 	return s;
 }
 }
 
 
+BlendMode computeBlendMode(BlendState s, BlendAlpha &alphamode)
+{
+	if (!s.enable)
+	{
+		alphamode = BLENDALPHA_PREMULTIPLIED;
+		return BLEND_NONE;
+	}
+
+	// Temporarily disable alpha multiplication when comparing to our list.
+	bool alphamultiply = s.srcFactorRGB == BLENDFACTOR_SRC_ALPHA;
+	if (alphamultiply)
+		s.srcFactorRGB = BLENDFACTOR_ONE;
+
+	for (int i = 0; i < (int) BLEND_MAX_ENUM; i++)
+	{
+		if (i != (int) BLEND_CUSTOM && states[i] == s)
+		{
+			alphamode = alphamultiply ? BLENDALPHA_MULTIPLY : BLENDALPHA_PREMULTIPLIED;
+			return (BlendMode) i;
+		}
+	}
+
+	alphamode = BLENDALPHA_PREMULTIPLIED;
+	return BLEND_CUSTOM;
+}
+
+bool isAlphaMultiplyBlendSupported(BlendMode mode)
+{
+	switch (mode)
+	{
+	case BLEND_LIGHTEN:
+	case BLEND_DARKEN:
+	case BLEND_MULTIPLY:
+		return false;
+	default:
+		return true;
+	}
+}
+
 CompareMode getReversedCompareMode(CompareMode mode)
 CompareMode getReversedCompareMode(CompareMode mode)
 {
 {
 	switch (mode)
 	switch (mode)
@@ -99,6 +134,7 @@ static StringMap<BlendMode, BLEND_MAX_ENUM>::Entry blendModeEntries[] =
 	{ "screen",   BLEND_SCREEN   },
 	{ "screen",   BLEND_SCREEN   },
 	{ "replace",  BLEND_REPLACE  },
 	{ "replace",  BLEND_REPLACE  },
 	{ "none",     BLEND_NONE     },
 	{ "none",     BLEND_NONE     },
+	{ "custom",   BLEND_CUSTOM   },
 };
 };
 
 
 static StringMap<BlendMode, BLEND_MAX_ENUM> blendModes(blendModeEntries, sizeof(blendModeEntries));
 static StringMap<BlendMode, BLEND_MAX_ENUM> blendModes(blendModeEntries, sizeof(blendModeEntries));

+ 17 - 2
src/modules/graphics/renderstate.h

@@ -34,7 +34,7 @@ namespace graphics
 
 
 class Shader;
 class Shader;
 
 
-enum BlendMode // High level wrappers
+enum BlendMode // High level wrappers. Order is important (see renderstate.cpp)
 {
 {
 	BLEND_ALPHA,
 	BLEND_ALPHA,
 	BLEND_ADD,
 	BLEND_ADD,
@@ -45,6 +45,7 @@ enum BlendMode // High level wrappers
 	BLEND_SCREEN,
 	BLEND_SCREEN,
 	BLEND_REPLACE,
 	BLEND_REPLACE,
 	BLEND_NONE,
 	BLEND_NONE,
+	BLEND_CUSTOM,
 	BLEND_MAX_ENUM
 	BLEND_MAX_ENUM
 };
 };
 
 
@@ -117,6 +118,18 @@ struct BlendState
 	BlendFactor dstFactorRGB = BLENDFACTOR_ZERO;
 	BlendFactor dstFactorRGB = BLENDFACTOR_ZERO;
 	BlendFactor dstFactorA = BLENDFACTOR_ZERO;
 	BlendFactor dstFactorA = BLENDFACTOR_ZERO;
 
 
+	BlendState() {}
+
+	BlendState(BlendOperation opRGB, BlendOperation opA, BlendFactor srcRGB, BlendFactor srcA, BlendFactor dstRGB, BlendFactor dstA)
+		: enable(true)
+		, operationRGB(opRGB)
+		, operationA(opA)
+		, srcFactorRGB(srcRGB)
+		, srcFactorA(srcA)
+		, dstFactorRGB(dstRGB)
+		, dstFactorA(dstA)
+	{}
+
 	bool operator == (const BlendState &b) const
 	bool operator == (const BlendState &b) const
 	{
 	{
 		return enable == b.enable
 		return enable == b.enable
@@ -176,7 +189,9 @@ struct ScissorState
 	bool enable = false;
 	bool enable = false;
 };
 };
 
 
-BlendState getBlendState(BlendMode mode, BlendAlpha alphamode);
+BlendState computeBlendState(BlendMode mode, BlendAlpha alphamode);
+BlendMode computeBlendMode(BlendState s, BlendAlpha &alphamode);
+bool isAlphaMultiplyBlendSupported(BlendMode mode);
 
 
 /**
 /**
  * GPU APIs do the comparison in the opposite way of what makes sense for some
  * GPU APIs do the comparison in the opposite way of what makes sense for some

+ 86 - 0
src/modules/graphics/wrap_Graphics.cpp

@@ -1847,6 +1847,90 @@ int w_getBlendMode(lua_State *L)
 	return 2;
 	return 2;
 }
 }
 
 
+static BlendOperation luax_checkblendop(lua_State *L, int idx)
+{
+	BlendOperation op = BLENDOP_ADD;
+	const char *str = luaL_checkstring(L, idx);
+	if (!getConstant(str, op))
+		luax_enumerror(L, "blend operation", getConstants(op), str);
+	return op;
+}
+
+static BlendFactor luax_checkblendfactor(lua_State *L, int idx)
+{
+	BlendFactor factor = BLENDFACTOR_ZERO;
+	const char *str = luaL_checkstring(L, idx);
+	if (!getConstant(str, factor))
+		luax_enumerror(L, "blend factor", getConstants(factor), str);
+	return factor;
+}
+
+static void luax_pushblendop(lua_State *L, BlendOperation op)
+{
+	const char *str;
+	if (!getConstant(op, str))
+		luaL_error(L, "unknown blend operation");
+	lua_pushstring(L, str);
+}
+
+static void luax_pushblendfactor(lua_State *L, BlendFactor factor)
+{
+	const char *str;
+	if (!getConstant(factor, str))
+		luaL_error(L, "unknown blend factor");
+	lua_pushstring(L, str);
+}
+
+int w_setBlendState(lua_State *L)
+{
+	BlendState state;
+
+	if (!lua_isnoneornil(L, 1))
+	{
+		state.enable = true;
+		if (lua_gettop(L) >= 4)
+		{
+			state.operationRGB = luax_checkblendop(L, 1);
+			state.operationA = luax_checkblendop(L, 2);
+			state.srcFactorRGB = luax_checkblendfactor(L, 3);
+			state.srcFactorA = luax_checkblendfactor(L, 4);
+			state.dstFactorRGB = luax_checkblendfactor(L, 5);
+			state.dstFactorA = luax_checkblendfactor(L, 6);
+		}
+		else
+		{
+			state.operationRGB = state.operationA = luax_checkblendop(L, 1);
+			state.srcFactorRGB = state.srcFactorA = luax_checkblendfactor(L, 2);
+			state.dstFactorRGB = state.dstFactorA = luax_checkblendfactor(L, 3);
+		}
+	}
+
+	luax_catchexcept(L, [&](){ instance()->setBlendState(state); });
+	return 0;
+}
+
+int w_getBlendState(lua_State *L)
+{
+	const BlendState &state = instance()->getBlendState();
+
+	if (state.enable)
+	{
+		luax_pushblendop(L, state.operationRGB);
+		luax_pushblendop(L, state.operationA);
+		luax_pushblendfactor(L, state.srcFactorRGB);
+		luax_pushblendfactor(L, state.srcFactorA);
+		luax_pushblendfactor(L, state.dstFactorRGB);
+		luax_pushblendfactor(L, state.dstFactorA);
+	}
+	else
+	{
+		for (int i = 0; i < 6; i++)
+			lua_pushnil(L);
+	}
+
+	return 6;
+}
+
 int w_setDefaultFilter(lua_State *L)
 int w_setDefaultFilter(lua_State *L)
 {
 {
 	Texture::Filter f;
 	Texture::Filter f;
@@ -2946,6 +3030,8 @@ static const luaL_Reg functions[] =
 	{ "getColorMask", w_getColorMask },
 	{ "getColorMask", w_getColorMask },
 	{ "setBlendMode", w_setBlendMode },
 	{ "setBlendMode", w_setBlendMode },
 	{ "getBlendMode", w_getBlendMode },
 	{ "getBlendMode", w_getBlendMode },
+	{ "setBlendState", w_setBlendState },
+	{ "getBlendState", w_getBlendState },
 	{ "setDefaultFilter", w_setDefaultFilter },
 	{ "setDefaultFilter", w_setDefaultFilter },
 	{ "getDefaultFilter", w_getDefaultFilter },
 	{ "getDefaultFilter", w_getDefaultFilter },
 	{ "setDefaultMipmapFilter", w_setDefaultMipmapFilter },
 	{ "setDefaultMipmapFilter", w_setDefaultMipmapFilter },