From b41f1316fa3e9fab3182fa0d008d145ff992ef44 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Fri, 27 Dec 2019 09:57:42 -0500 Subject: [PATCH] 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 --- src/modules/graphics/Graphics.cpp | 27 +++++- src/modules/graphics/Graphics.h | 16 ++-- src/modules/graphics/opengl/Graphics.cpp | 48 ++++------ src/modules/graphics/opengl/Graphics.h | 2 +- src/modules/graphics/renderstate.cpp | 114 +++++++++++++++-------- src/modules/graphics/renderstate.h | 19 +++- src/modules/graphics/wrap_Graphics.cpp | 86 +++++++++++++++++ 7 files changed, 226 insertions(+), 86 deletions(-) diff --git a/src/modules/graphics/Graphics.cpp b/src/modules/graphics/Graphics.cpp index ed8463388..953a4e7ec 100644 --- a/src/modules/graphics/Graphics.cpp +++ b/src/modules/graphics/Graphics.cpp @@ -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::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 }, diff --git a/src/modules/graphics/Graphics.h b/src/modules/graphics/Graphics.h index 80befd39d..6754f0172 100644 --- a/src/modules/graphics/Graphics.h +++ b/src/modules/graphics/Graphics.h @@ -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; diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index afe98f460..33c33b424 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -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(); diff --git a/src/modules/graphics/opengl/Graphics.h b/src/modules/graphics/opengl/Graphics.h index 1bbf182ff..a7ada3114 100644 --- a/src/modules/graphics/opengl/Graphics.h +++ b/src/modules/graphics/opengl/Graphics.h @@ -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; diff --git a/src/modules/graphics/renderstate.cpp b/src/modules/graphics/renderstate.cpp index 989c285d0..b1f354d5e 100644 --- a/src/modules/graphics/renderstate.cpp +++ b/src/modules/graphics/renderstate.cpp @@ -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::Entry blendModeEntries[] = { "screen", BLEND_SCREEN }, { "replace", BLEND_REPLACE }, { "none", BLEND_NONE }, + { "custom", BLEND_CUSTOM }, }; static StringMap blendModes(blendModeEntries, sizeof(blendModeEntries)); diff --git a/src/modules/graphics/renderstate.h b/src/modules/graphics/renderstate.h index b5898ce2b..c6ffa9c17 100644 --- a/src/modules/graphics/renderstate.h +++ b/src/modules/graphics/renderstate.h @@ -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 diff --git a/src/modules/graphics/wrap_Graphics.cpp b/src/modules/graphics/wrap_Graphics.cpp index 1e192efda..42f462caf 100644 --- a/src/modules/graphics/wrap_Graphics.cpp +++ b/src/modules/graphics/wrap_Graphics.cpp @@ -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 },