Ver código fonte

Make backdrop-filter be affected by any filter and mask-image applied to the same element

Michael Ragazzon 1 ano atrás
pai
commit
b470b2b0e8

+ 20 - 6
Backends/RmlUi_Renderer_GL3.cpp

@@ -1432,10 +1432,10 @@ void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& so
 	const Rml::Vector2i dst_max = window_flipped.p1;
 	const Rml::Vector2i dst_max = window_flipped.p1;
 	glBlitFramebuffer(src_min.x, src_min.y, src_max.x, src_max.y, dst_min.x, dst_min.y, dst_max.x, dst_max.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
 	glBlitFramebuffer(src_min.x, src_min.y, src_max.x, src_max.y, dst_min.x, dst_min.y, dst_max.x, dst_max.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
 
 
-	// The above upscale blit might be jittery at low resolutions (large pass levels). This is especially noticable when moving an element with
+	// The above upscale blit might be jittery at low resolutions (large pass levels). This is especially noticeable when moving an element with
 	// backdrop blur around or when trying to click/hover an element within a blurred region since it may be rendered at an offset. For more stable
 	// backdrop blur around or when trying to click/hover an element within a blurred region since it may be rendered at an offset. For more stable
 	// and accurate rendering we next upscale the blur image by an exact power-of-two. However, this may not fill the edges completely so we need to
 	// and accurate rendering we next upscale the blur image by an exact power-of-two. However, this may not fill the edges completely so we need to
-	// do the above first. Note that this strategy may sometimes result in visible seams. Alternatively, we could try to enlargen the window to the
+	// do the above first. Note that this strategy may sometimes result in visible seams. Alternatively, we could try to enlarge the window to the
 	// next power-of-two size and then downsample and blur that.
 	// next power-of-two size and then downsample and blur that.
 	const Rml::Vector2i target_min = src_min * (1 << pass_level);
 	const Rml::Vector2i target_min = src_min * (1 << pass_level);
 	const Rml::Vector2i target_max = src_max * (1 << pass_level);
 	const Rml::Vector2i target_max = src_max * (1 << pass_level);
@@ -1875,14 +1875,28 @@ void RenderInterface_GL3::RenderFilters(Rml::Span<const Rml::CompiledFilterHandl
 
 
 void RenderInterface_GL3::PushLayer(Rml::LayerFill layer_fill)
 void RenderInterface_GL3::PushLayer(Rml::LayerFill layer_fill)
 {
 {
-	if (layer_fill == Rml::LayerFill::Clone)
+	const Gfx::FramebufferData source = render_layers.GetTopLayer();
+
+	if (layer_fill == Rml::LayerFill::Link)
 		render_layers.PushLayerClone();
 		render_layers.PushLayerClone();
 	else
 	else
 		render_layers.PushLayer();
 		render_layers.PushLayer();
 
 
-	glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer);
-	if (layer_fill == Rml::LayerFill::Clear)
-		glClear(GL_COLOR_BUFFER_BIT);
+	if (layer_fill == Rml::LayerFill::Copy)
+	{
+		const Gfx::FramebufferData& destination = render_layers.GetTopLayer();
+		glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer);
+		glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination.framebuffer);
+		// Note that the blit region will be clipped by any active scissor region.
+		glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+		glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer);
+	}
+	else
+	{
+		glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer);
+		if (layer_fill == Rml::LayerFill::Clear)
+			glClear(GL_COLOR_BUFFER_BIT);
+	}
 }
 }
 
 
 void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, Rml::Span<const Rml::CompiledFilterHandle> filters)
 void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, Rml::Span<const Rml::CompiledFilterHandle> filters)

+ 2 - 1
Include/RmlUi/Core/RenderInterface.h

@@ -44,7 +44,8 @@ enum class ClipMaskOperation {
 enum class LayerFill {
 enum class LayerFill {
 	None,  // No operation necessary, does not care about the layer color.
 	None,  // No operation necessary, does not care about the layer color.
 	Clear, // Clear the layer to transparent black.
 	Clear, // Clear the layer to transparent black.
-	Clone, // Copy the color data from the previous layer.
+	Copy,  // Copy the color data from the previous layer.
+	Link,  // Link the color data with the previous layer.
 };
 };
 enum class BlendMode {
 enum class BlendMode {
 	Blend,   // Normal alpha blending.
 	Blend,   // Normal alpha blending.

+ 19 - 19
Samples/basic/effect/data/effect.rml

@@ -163,32 +163,32 @@
 	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-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"
 	data-class-transform_all="transform_all"
 >
 >
+	<div class="box boxshadow_blur transform"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box boxshadow_trail"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box boxshadow_inset"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
 
 
-<div class="box boxshadow_blur transform"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box boxshadow_trail"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box boxshadow_inset"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box big shader"><div class="label">"Creation" (Danilo Guanabara)</div></div>
+	<div class="box big gradient"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box mask"><div class="placeholder"/>Hello, do you feel the funk?</div>
 
 
-<div class="box"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box big shader"><div class="label">"Creation" (Danilo Guanabara)</div></div>
-<div class="box big gradient"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box mask"><div class="placeholder"/>Hello, do you feel the funk?</div>
+	<div class="box hue_rotate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
 
 
-<div class="box hue_rotate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box animate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box saturate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box invert"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box blur"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
 
 
-<div class="box animate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box saturate"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box invert"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box blur"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box brightness"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box contrast"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box dropshadow"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
 
 
-<div class="box brightness"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box contrast"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box dropshadow"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box grayscale"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+	<div class="box opacity_low"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+</div>
 
 
 <div class="box window sepia" style="top: 375dp"><handle move_target="#parent"/><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
 <div class="box window sepia" style="top: 375dp"><handle move_target="#parent"/><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box window back_blur" style="top: 475dp"><handle move_target="#parent"/><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-
-<div class="box grayscale"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
-<div class="box opacity_low"><img sprite="icon-invader"/>Hello, do you feel the funk?</div>
+<div class="box window back_blur" style="top: 475dp"><handle move_target="#parent"/><img sprite="icon-invader"/>Hello, do you feel the funk?
 </div>
 </div>
 </body>
 </body>
 </rml>
 </rml>

+ 30 - 19
Source/Core/ElementDecoration.cpp

@@ -258,30 +258,22 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage)
 		scissor_region.IntersectIfValid(render_manager->GetState().scissor_region);
 		scissor_region.IntersectIfValid(render_manager->GetState().scissor_region);
 		render_manager->SetScissorRegion(scissor_region);
 		render_manager->SetScissorRegion(scissor_region);
 	};
 	};
-
-	if (!backdrop_filters.empty())
-	{
-		if (render_stage == RenderStage::Enter)
-		{
-			ApplyClippingRegion(PropertyId::BackdropFilter);
-
-			render_manager->PushLayer(LayerFill::Clone);
-
-			FilterHandleList filter_handles;
-			for (auto& filter : backdrop_filters)
-				filter.compiled.AddHandleTo(filter_handles);
-
-			render_manager->PopLayer(BlendMode::Replace, filter_handles);
-
-			render_manager->SetScissorRegion(initial_scissor_region);
-		}
-	}
+	auto ApplyScissorRegionForBackdrop = [this, &render_manager]() {
+		// Set the scissor region for backdrop drawing, which covers the element's border box plus any area we may need
+		// to read from, such as any blur radius.
+		Rectanglef filter_region = Rectanglef::MakeInvalid();
+		ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Border);
+		for (const auto& filter : backdrop_filters)
+			filter.filter->ExtendInkOverflow(element, filter_region);
+		Math::ExpandToPixelGrid(filter_region);
+		render_manager->SetScissorRegion(Rectanglei(filter_region));
+	};
 
 
 	if (!filters.empty() || !mask_images.empty())
 	if (!filters.empty() || !mask_images.empty())
 	{
 	{
 		if (render_stage == RenderStage::Enter)
 		if (render_stage == RenderStage::Enter)
 		{
 		{
-			render_manager->PushLayer(LayerFill::Clear);
+			render_manager->PushLayer(backdrop_filters.empty() ? LayerFill::Clear : LayerFill::Copy);
 		}
 		}
 		else if (render_stage == RenderStage::Exit)
 		else if (render_stage == RenderStage::Exit)
 		{
 		{
@@ -313,6 +305,25 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage)
 			render_manager->SetScissorRegion(initial_scissor_region);
 			render_manager->SetScissorRegion(initial_scissor_region);
 		}
 		}
 	}
 	}
+
+	if (!backdrop_filters.empty())
+	{
+		if (render_stage == RenderStage::Enter)
+		{
+			ApplyScissorRegionForBackdrop();
+			render_manager->PushLayer(LayerFill::Copy);
+			render_manager->PushLayer(LayerFill::Link);
+
+			FilterHandleList filter_handles;
+			for (auto& filter : backdrop_filters)
+				filter.compiled.AddHandleTo(filter_handles);
+
+			render_manager->PopLayer(BlendMode::Replace, filter_handles);
+			ApplyClippingRegion(PropertyId::BackdropFilter);
+			render_manager->PopLayer(BlendMode::Blend, {});
+			render_manager->SetScissorRegion(initial_scissor_region);
+		}
+	}
 }
 }
 
 
 void ElementDecoration::DirtyDecorators()
 void ElementDecoration::DirtyDecorators()

+ 41 - 0
Tests/Data/VisualTests/filter_blur.rml

@@ -0,0 +1,41 @@
+<rml>
+<head>
+	<title>Filter: blur</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="Both the blur element in the center and the background can be moved. Requires filter support in the renderer." />
+	<meta name="Assert" content="See how the blur algorithm behaves while moving the elements around. Try with different sizes and blur radius. Ideally, there should be as little aliasing as possible during movement, and edges should be fairly stable, while keeping performance in mind."/>
+	<style>
+		body {
+			width: auto;
+			left: 0;
+			right: 400dp;
+			background: black;
+		}
+		handle {
+			position: absolute;
+			display: block;
+			cursor: move;
+			background: transparent;
+		}
+		.background {
+			width: 512dp;
+			height: 512dp;
+			background: #ccc;
+			decorator: image("/assets/invader.tga");
+			margin: auto;
+		}
+		.blur {
+			width: 300dp;
+			height: 300dp;
+			border-radius: 50dp;
+			margin: auto;
+			backdrop-filter: blur(50dp);
+		}
+	</style>
+</head>
+
+<body>
+	<handle class="background" move_target="#self"/>
+	<handle class="blur" move_target="#self"/>
+</body>
+</rml>

+ 34 - 0
Tests/Data/VisualTests/filter_overflow.rml

@@ -0,0 +1,34 @@
+<rml>
+<head>
+	<title>Filter: overflowing content</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="Test filtering of boxes with visible overflowing content. Requires filter support in the renderer." />
+	<meta name="Assert" content="There should be a red blurred box, and a blue blurred box to its lower-right." />
+	<style>
+		.box {
+			width: 100dp;
+			height: 100dp;
+			margin: 10dp;
+			box-sizing: border-box;
+		}
+		.parent {
+			background-color: red;
+			position: relative;
+			filter: blur(15dp);
+		}
+		.child {
+			background-color: blue;
+			position: absolute;
+			top: 200dp;
+			left: 200dp;
+		}
+	</style>
+</head>
+
+<body>
+<div class="box parent">
+	<div class="box child"></div>
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 112 - 0
Tests/Data/VisualTests/filter_with_backdrop.rml

@@ -0,0 +1,112 @@
+<rml>
+<head>
+	<title>Filter: backdrop</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="Filters should apply to the backdrop filter as well. Requires filter support in the renderer." />
+	<style>
+		.container {
+			display: flow-root;
+			padding-bottom: 15dp;
+			border-bottom: 2dp #aaa;
+			margin-bottom: 15dp;
+		}
+		.box {
+			margin: 0 10dp;
+			width: 100dp;
+			height: 100dp;
+			float: left;
+		}
+		.backdrop {
+			backdrop-filter: blur(10dp);
+		}
+		.filter {
+			filter: sepia(1);
+		}
+		.mask-image {
+			mask-image: image("/assets/present.tga");
+		}
+		.green {
+			background-color: green;
+		}
+		.red {
+			background-color: red;
+		}
+		.background {
+			width: 50dp;
+			height: 50dp;
+			margin: 25dp 35dp;
+		}
+
+		.foreground {
+			margin-left: -110dp;
+		}
+	</style>
+</head>
+
+<body>
+<div class="container">
+	<div class="box"></div>
+	<div id="backdrop" class="box backdrop"></div>
+	<div class="box backdrop filter"></div>
+	<div class="box backdrop filter mask-image"></div>
+</div>
+
+<div class="container">
+	<div class="box green backdrop"></div>
+	<div class="box green mask-image"></div>
+	<div class="box green backdrop mask-image"></div>
+	<div class="box green backdrop mask-image filter"></div>
+</div>
+
+<div class="container">
+	<div class="box green"></div>
+	<div class="box green backdrop"></div>
+	<div class="box green filter"></div>
+	<div class="box green filter mask-image"></div>
+</div>
+
+<div class="container">
+	<div class="box background green"></div>
+	<div class="box foreground backdrop"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground backdrop filter"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground backdrop mask-image"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground backdrop filter mask-image"></div>
+</div>
+
+<div class="container">
+	<div class="box background green"></div>
+	<div class="box foreground red filter"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground red filter backdrop"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground red filter mask-image"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground red filter backdrop mask-image"></div>
+</div>
+
+<div class="container">
+	<div class="box background green"></div>
+	<div class="box foreground red mask-image"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground red mask-image backdrop"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground red mask-image filter"></div>
+
+	<div class="box background green"></div>
+	<div class="box foreground red mask-image backdrop filter"></div>
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>