Browse Source

Implement drop-shadow filter, integrate filter in GL3 renderer, and add to effect sample

Michael Ragazzon 2 years ago
parent
commit
19281fd7d1

+ 76 - 3
Backends/RmlUi_Renderer_GL3.cpp

@@ -182,6 +182,19 @@ void main() {
 	finalColor = color;
 }
 )";
+static const char* shader_frag_drop_shadow = RMLUI_SHADER_HEADER R"(
+uniform sampler2D _tex;
+uniform vec2 _texCoordMin;
+uniform vec2 _texCoordMax;
+uniform vec4 _color;
+
+in vec2 fragTexCoord;
+out vec4 finalColor;
+
+void main() {
+	finalColor = texture(_tex, clamp(fragTexCoord, _texCoordMin, _texCoordMax)).a * _color;
+}
+)";
 
 enum class ProgramId {
 	None,
@@ -190,6 +203,7 @@ enum class ProgramId {
 	Passthrough,
 	ColorMatrix,
 	Blur,
+	DropShadow,
 	Count,
 };
 enum class VertShaderId {
@@ -204,12 +218,14 @@ enum class FragShaderId {
 	Passthrough,
 	ColorMatrix,
 	Blur,
+	DropShadow,
 	Count,
 };
 enum class UniformId {
 	Translate,
 	Transform,
 	Tex,
+	Color,
 	ColorMatrix,
 	TexelOffset,
 	TexCoordMin,
@@ -220,8 +236,8 @@ enum class UniformId {
 
 namespace Gfx {
 
-static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color_matrix", "_texelOffset",
-	"_texCoordMin", "_texCoordMax", "_weights[0]"};
+static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color", "_color_matrix",
+	"_texelOffset", "_texCoordMin", "_texCoordMax", "_weights[0]"};
 
 enum class VertexAttribute { Position, Color0, TexCoord0, Count };
 static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"};
@@ -255,6 +271,7 @@ static const FragShaderDefinition frag_shader_definitions[] = {
 	{FragShaderId::Passthrough, "passthrough",  shader_frag_passthrough},
 	{FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix},
 	{FragShaderId::Blur,        "blur",         shader_frag_blur},
+	{FragShaderId::DropShadow,  "drop_shadow",  shader_frag_drop_shadow},
 };
 static const ProgramDefinition program_definitions[] = {
 	{ProgramId::Color,       "color",        VertShaderId::Main,        FragShaderId::Color},
@@ -262,6 +279,7 @@ static const ProgramDefinition program_definitions[] = {
 	{ProgramId::Passthrough, "passthrough",  VertShaderId::Passthrough, FragShaderId::Passthrough},
 	{ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix},
 	{ProgramId::Blur,        "blur",         VertShaderId::Blur,        FragShaderId::Blur},
+	{ProgramId::DropShadow,  "drop_shadow",  VertShaderId::Passthrough, FragShaderId::DropShadow},
 };
 // clang-format on
 
@@ -1279,7 +1297,7 @@ void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform)
 	program_transform_dirty.set();
 }
 
-enum class FilterType { Invalid = 0, Passthrough, Blur, ColorMatrix };
+enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix };
 struct CompiledFilter {
 	FilterType type;
 
@@ -1289,6 +1307,10 @@ struct CompiledFilter {
 	// Blur
 	float sigma;
 
+	// Drop shadow
+	Rml::Vector2f offset;
+	Rml::Colourb color;
+
 	// ColorMatrix
 	Rml::Matrix4f color_matrix;
 };
@@ -1307,6 +1329,13 @@ Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String&
 		filter.type = FilterType::Blur;
 		filter.sigma = 0.5f * Rml::Get(parameters, "radius", 1.0f);
 	}
+	else if (name == "drop-shadow")
+	{
+		filter.type = FilterType::DropShadow;
+		filter.sigma = Rml::Get(parameters, "sigma", 0.f);
+		filter.color = Rml::Get(parameters, "color", Rml::Colourb());
+		filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f));
+	}
 	else if (name == "brightness")
 	{
 		filter.type = FilterType::ColorMatrix;
@@ -1414,6 +1443,16 @@ void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary()
 	glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
 }
 
+static inline Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0)
+{
+	Rml::Colourf result;
+	result.alpha = (1.f / 255.f) * float(c0.alpha);
+	result.red = (1.f / 255.f) * float(c0.red) * result.alpha;
+	result.green = (1.f / 255.f) * float(c0.green) * result.alpha;
+	result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha;
+	return result;
+}
+
 void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_handles)
 {
 	for (const Rml::CompiledFilterHandle filter_handle : filter_handles)
@@ -1453,6 +1492,40 @@ void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_hand
 			glEnable(GL_BLEND);
 		}
 		break;
+		case FilterType::DropShadow:
+		{
+			UseProgram(ProgramId::DropShadow);
+			glDisable(GL_BLEND);
+
+			Rml::Colourf color = ToPremultipliedAlpha(filter.color);
+			glUniform4fv(GetUniformLocation(UniformId::Color), 1, &color[0]);
+
+			const Gfx::FramebufferData& primary = render_layers.GetPostprocessPrimary();
+			const Gfx::FramebufferData& secondary = render_layers.GetPostprocessSecondary();
+			Gfx::BindTexture(primary);
+			glBindFramebuffer(GL_FRAMEBUFFER, secondary.framebuffer);
+
+			const Rml::Rectanglei window_flipped = VerticallyFlipped(scissor_state, viewport_height);
+			SetTexCoordLimits(GetUniformLocation(UniformId::TexCoordMin), GetUniformLocation(UniformId::TexCoordMax), window_flipped,
+				{primary.width, primary.height});
+
+			const Rml::Vector2f uv_offset = filter.offset / Rml::Vector2f(-(float)viewport_width, (float)viewport_height);
+			DrawFullscreenQuad(uv_offset);
+
+			if (filter.sigma >= 0.5f)
+			{
+				const Gfx::FramebufferData& tertiary = render_layers.GetPostprocessTertiary();
+				RenderBlur(filter.sigma, secondary, tertiary, window_flipped);
+			}
+
+			UseProgram(ProgramId::Passthrough);
+			BindTexture(primary);
+			glEnable(GL_BLEND);
+			DrawFullscreenQuad();
+
+			render_layers.SwapPostprocessPrimarySecondary();
+		}
+		break;
 		case FilterType::ColorMatrix:
 		{
 			UseProgram(ProgramId::ColorMatrix);

+ 1 - 1
Backends/RmlUi_Renderer_GL3.h

@@ -100,7 +100,7 @@ private:
 	void SetScissor(Rml::Rectanglei region, bool vertically_flip = false);
 
 	void DrawFullscreenQuad();
-	void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling);
+	void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f));
 
 	void RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, Rml::Rectanglei window_flipped);
 

+ 2 - 0
CMake/FileList.cmake

@@ -49,6 +49,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterDropShadow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h
@@ -307,6 +308,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/Filter.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/FilterDropShadow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp

+ 4 - 1
Samples/basic/effect/data/effect.rml

@@ -76,6 +76,8 @@
 .blur { filter: blur(25px); }
 .back_blur { backdrop-filter: blur(15px);  }
 
+.dropshadow { filter: drop-shadow(#f33f 30px 20px 5px); }
+
 </style>
 </head>
 <body
@@ -104,6 +106,7 @@
 			<tr><td>Hue</td><td><input type="range" min="0" max="360" step="1" data-value="hue_rotate"/></td><td>{{ hue_rotate }} deg</td></tr>
 			<tr><td>Invert</td><td><input type="range" min="0" max="1" step="0.01" data-value="invert"/></td><td>{{ invert*100 }} %</td></tr>
 			<tr><td>Blur</td><td><input type="range" min="0" max="150" step="1" data-value="blur"/></td><td>{{ blur }} px</td></tr>
+			<tr><td><label for="drop_shadow">Drop shadow</label></td><td colspan="2"><input id="drop_shadow" type="checkbox" data-checked="drop_shadow"/></td></tr>
 		</tbody>
 		<tbody data-if="submenu == 'transform'">
 			<tr><td>Scale</td><td><input type="range" min="0.1" max="2.0" step="0.1" data-value="scale"/></td><td>{{ scale | format(1) }}x</td></tr>
@@ -120,7 +123,7 @@
 </div>
 <div class="filter"
 	data-style-transform="'scale(' + scale + ') rotateX(' + rotate_x + 'deg) rotateY(' + rotate_y + 'deg) rotateZ(' + rotate_z + 'deg)'"
-	data-style-filter="'opacity(' + opacity + ') sepia(' + sepia + ') grayscale(' + grayscale + ') saturate(' + saturate + ') brightness(' + brightness + ') contrast(' + contrast + ') hue-rotate(' + hue_rotate + 'deg) invert(' + invert + ') blur(' + blur + 'px)'"
+	data-style-filter="'opacity(' + opacity + ') sepia(' + sepia + ') grayscale(' + grayscale + ') saturate(' + saturate + ') brightness(' + brightness + ') contrast(' + contrast + ') hue-rotate(' + hue_rotate + 'deg) invert(' + invert + ') blur(' + blur + 'px)' + (drop_shadow ? ' drop-shadow(#f11b 10px 10px 8px)' : '')"
 	data-class-transform_all="transform_all"
 >
 

+ 1 - 1
Samples/basic/effect/data/effect_style.rcss

@@ -81,7 +81,7 @@ handle.size:hover, handle.size:active {
 	right: 25dp;
 	box-sizing: border-box;
 	width: 400dp;
-	height: 440dp;
+	height: 480dp;
 	overflow: auto;
 
 	background: #fffc;

+ 2 - 0
Samples/basic/effect/src/main.cpp

@@ -88,6 +88,7 @@ int main(int /*argc*/, char** /*argv*/)
 			float hue_rotate = 0.0f;
 			float invert = 0.0f;
 			float blur = 0.0f;
+			bool drop_shadow = false;
 		} filter;
 
 		struct Transform {
@@ -113,6 +114,7 @@ int main(int /*argc*/, char** /*argv*/)
 		constructor.Bind("hue_rotate", &data.filter.hue_rotate);
 		constructor.Bind("invert", &data.filter.invert);
 		constructor.Bind("blur", &data.filter.blur);
+		constructor.Bind("drop_shadow", &data.filter.drop_shadow);
 
 		constructor.Bind("scale", &data.transform.scale);
 		constructor.Bind("rotate_x", &data.transform.rotate.x);

+ 3 - 0
Source/Core/Factory.cpp

@@ -64,6 +64,7 @@
 #include "EventInstancerDefault.h"
 #include "FilterBasic.h"
 #include "FilterBlur.h"
+#include "FilterDropShadow.h"
 #include "FontEffectBlur.h"
 #include "FontEffectGlow.h"
 #include "FontEffectOutline.h"
@@ -158,6 +159,7 @@ struct DefaultInstancers {
 	FilterBasicInstancer filter_basic_d0 = {FilterBasicInstancer::ValueType::NumberPercent, "0"};
 	FilterBasicInstancer filter_basic_d1 = {FilterBasicInstancer::ValueType::NumberPercent, "1"};
 	FilterBlurInstancer filter_blur;
+	FilterDropShadowInstancer filter_drop_shadow;
 
 	// Font effects
 	FontEffectBlurInstancer font_effect_blur;
@@ -252,6 +254,7 @@ bool Factory::Initialise()
 	RegisterFilterInstancer("sepia", &default_instancers->filter_basic_d0);
 
 	RegisterFilterInstancer("blur", &default_instancers->filter_blur);
+	RegisterFilterInstancer("drop-shadow", &default_instancers->filter_drop_shadow);
 
 	// Font effect instancers
 	RegisterFontEffectInstancer("blur", &default_instancers->font_effect_blur);

+ 105 - 0
Source/Core/FilterDropShadow.cpp

@@ -0,0 +1,105 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "FilterDropShadow.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/PropertyDefinition.h"
+#include "../../Include/RmlUi/Core/PropertyDictionary.h"
+#include "../../Include/RmlUi/Core/RenderInterface.h"
+
+namespace Rml {
+
+bool FilterDropShadow::Initialise(Colourb in_color, NumericValue in_offset_x, NumericValue in_offset_y, NumericValue in_sigma)
+{
+	color = in_color;
+	value_offset_x = in_offset_x;
+	value_offset_y = in_offset_y;
+	value_sigma = in_sigma;
+	return Any(in_offset_x.unit & Unit::LENGTH) && Any(in_offset_y.unit & Unit::LENGTH) && Any(in_sigma.unit & Unit::LENGTH);
+}
+
+CompiledFilterHandle FilterDropShadow::CompileFilter(Element* element) const
+{
+	const float sigma = element->ResolveLength(value_sigma);
+	const Vector2f offset = {
+		element->ResolveLength(value_offset_x),
+		element->ResolveLength(value_offset_y),
+	};
+
+	CompiledFilterHandle handle = GetRenderInterface()->CompileFilter("drop-shadow",
+		Dictionary{{"color", Variant(color)}, {"offset", Variant(offset)}, {"sigma", Variant(sigma)}});
+
+	return handle;
+}
+
+void FilterDropShadow::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const
+{
+	GetRenderInterface()->ReleaseCompiledFilter(filter_handle);
+}
+
+void FilterDropShadow::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const
+{
+	// Expand the ink overflow area to cover both the native element *and* its offset shadow w/blur.
+	const float sigma = element->ResolveLength(value_sigma);
+	const Vector2f offset = {
+		element->ResolveLength(value_offset_x),
+		element->ResolveLength(value_offset_y),
+	};
+
+	const float blur_radius = 2.f * sigma;
+	const float blur_extent = 1.5f * blur_radius;
+	scissor_region.ExtendTopLeft(Math::Max(-offset, Vector2f(0.f)) + Vector2f(blur_extent));
+	scissor_region.ExtendBottomRight(Math::Max(offset, Vector2f(0.f)) + Vector2f(blur_extent));
+}
+
+FilterDropShadowInstancer::FilterDropShadowInstancer()
+{
+	ids.color = RegisterProperty("color", "transparent").AddParser("color").GetId();
+	ids.offset_x = RegisterProperty("offset-x", "0px").AddParser("length").GetId();
+	ids.offset_y = RegisterProperty("offset-y", "0px").AddParser("length").GetId();
+	ids.sigma = RegisterProperty("sigma", "0px").AddParser("length").GetId();
+	RegisterShorthand("filter", "color, offset-x, offset-y, sigma", ShorthandType::FallThrough);
+}
+
+SharedPtr<Filter> FilterDropShadowInstancer::InstanceFilter(const String& /*name*/, const PropertyDictionary& properties)
+{
+	const Property* p_color = properties.GetProperty(ids.color);
+	const Property* p_offset_x = properties.GetProperty(ids.offset_x);
+	const Property* p_offset_y = properties.GetProperty(ids.offset_y);
+	const Property* p_sigma = properties.GetProperty(ids.sigma);
+	if (!p_color || !p_offset_x || !p_offset_y || !p_sigma)
+		return nullptr;
+
+	auto decorator = MakeShared<FilterDropShadow>();
+	if (decorator->Initialise(p_color->Get<Colourb>(), p_offset_x->GetNumericValue(), p_offset_y->GetNumericValue(), p_sigma->GetNumericValue()))
+		return decorator;
+
+	return nullptr;
+}
+
+} // namespace Rml

+ 67 - 0
Source/Core/FilterDropShadow.h

@@ -0,0 +1,67 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019-2023 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_FILTERDROPSHADOW_H
+#define RMLUI_CORE_FILTERDROPSHADOW_H
+
+#include "../../Include/RmlUi/Core/Filter.h"
+#include "../../Include/RmlUi/Core/ID.h"
+#include "../../Include/RmlUi/Core/NumericValue.h"
+
+namespace Rml {
+
+class FilterDropShadow : public Filter {
+public:
+	bool Initialise(Colourb color, NumericValue offset_x, NumericValue offset_y, NumericValue sigma);
+
+	CompiledFilterHandle CompileFilter(Element* element) const override;
+
+	void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override;
+
+	void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override;
+
+private:
+	Colourb color;
+	NumericValue value_offset_x, value_offset_y, value_sigma;
+};
+
+class FilterDropShadowInstancer : public FilterInstancer {
+public:
+	FilterDropShadowInstancer();
+
+	SharedPtr<Filter> InstanceFilter(const String& name, const PropertyDictionary& properties) override;
+
+private:
+	struct PropertyIds {
+		PropertyId color, offset_x, offset_y, sigma;
+	};
+	PropertyIds ids;
+};
+
+} // namespace Rml
+#endif