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
This commit is contained in:
Alex Szpakowski
2019-12-27 09:57:42 -05:00
parent efa2bd7837
commit b41f1316fa
7 changed files with 226 additions and 86 deletions
+22 -5
View File
@@ -376,7 +376,7 @@ void Graphics::restoreState(const DisplayState &s)
setColor(s.color);
setBackgroundColor(s.backgroundColor);
setBlendMode(s.blendMode, s.blendAlphaMode);
setBlendState(s.blend);
setLineWidth(s.lineWidth);
setLineStyle(s.lineStyle);
@@ -415,8 +415,8 @@ void Graphics::restoreStateChecked(const DisplayState &s)
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.
setLineWidth(s.lineWidth);
@@ -901,10 +901,26 @@ ColorChannelMask Graphics::getColorMask() const
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
{
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)
@@ -1881,6 +1897,7 @@ StringMap<Graphics::Feature, Graphics::FEATURE_MAX_ENUM>::Entry Graphics::featur
{
{ "multicanvasformats", FEATURE_MULTI_CANVAS_FORMATS },
{ "clampzero", FEATURE_CLAMP_ZERO },
{ "blendminmax", FEATURE_BLENDMINMAX },
{ "lighten", FEATURE_LIGHTEN },
{ "fullnpot", FEATURE_FULL_NPOT },
{ "pixelshaderhighp", FEATURE_PIXEL_SHADER_HIGHP },
+9 -7
View File
@@ -137,7 +137,8 @@ public:
{
FEATURE_MULTI_CANVAS_FORMATS,
FEATURE_CLAMP_ZERO,
FEATURE_LIGHTEN,
FEATURE_BLENDMINMAX,
FEATURE_LIGHTEN, // Deprecated
FEATURE_FULL_NPOT,
FEATURE_PIXEL_SHADER_HIGHP,
FEATURE_SHADER_DERIVATIVES,
@@ -607,14 +608,16 @@ public:
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.
@@ -886,8 +889,7 @@ protected:
Colorf color = Colorf(1.0, 1.0, 1.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;
LineStyle lineStyle = LINE_SMOOTH;
+16 -32
View File
@@ -1268,46 +1268,29 @@ void Graphics::setColorMask(ColorChannelMask 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();
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);
glBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA);
states.back().blendMode = mode;
states.back().blendAlphaMode = alphamode;
states.back().blend = blend;
}
void Graphics::setPointSize(float size)
@@ -1375,14 +1358,15 @@ void Graphics::initCapabilities()
{
capabilities.features[FEATURE_MULTI_CANVAS_FORMATS] = Canvas::isMultiFormatMultiCanvasSupported();
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_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_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_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_TEXTURE_SIZE] = gl.getMax2DTextureSize();
+1 -1
View File
@@ -98,7 +98,7 @@ public:
void setColorMask(ColorChannelMask mask) override;
void setBlendMode(BlendMode mode, BlendAlpha alphamode) override;
void setBlendState(const BlendState &blend) override;
void setPointSize(float size) override;
+75 -39
View File
@@ -26,48 +26,44 @@ namespace love
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.
if (s.srcFactorRGB == BLENDFACTOR_ONE && alphamode == BLENDALPHA_MULTIPLY && mode != BLEND_NONE)
@@ -76,6 +72,45 @@ BlendState getBlendState(BlendMode mode, BlendAlpha alphamode)
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)
{
switch (mode)
@@ -99,6 +134,7 @@ static StringMap<BlendMode, BLEND_MAX_ENUM>::Entry blendModeEntries[] =
{ "screen", BLEND_SCREEN },
{ "replace", BLEND_REPLACE },
{ "none", BLEND_NONE },
{ "custom", BLEND_CUSTOM },
};
static StringMap<BlendMode, BLEND_MAX_ENUM> blendModes(blendModeEntries, sizeof(blendModeEntries));
+17 -2
View File
@@ -34,7 +34,7 @@ namespace graphics
class Shader;
enum BlendMode // High level wrappers
enum BlendMode // High level wrappers. Order is important (see renderstate.cpp)
{
BLEND_ALPHA,
BLEND_ADD,
@@ -45,6 +45,7 @@ enum BlendMode // High level wrappers
BLEND_SCREEN,
BLEND_REPLACE,
BLEND_NONE,
BLEND_CUSTOM,
BLEND_MAX_ENUM
};
@@ -117,6 +118,18 @@ struct BlendState
BlendFactor dstFactorRGB = 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
{
return enable == b.enable
@@ -176,7 +189,9 @@ struct ScissorState
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
+86
View File
@@ -1847,6 +1847,90 @@ int w_getBlendMode(lua_State *L)
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)
{
Texture::Filter f;
@@ -2946,6 +3030,8 @@ static const luaL_Reg functions[] =
{ "getColorMask", w_getColorMask },
{ "setBlendMode", w_setBlendMode },
{ "getBlendMode", w_getBlendMode },
{ "setBlendState", w_setBlendState },
{ "getBlendState", w_getBlendState },
{ "setDefaultFilter", w_setDefaultFilter },
{ "getDefaultFilter", w_getDefaultFilter },
{ "setDefaultMipmapFilter", w_setDefaultMipmapFilter },