Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit b1cbf54

Browse files
author
Jonah Williams
authored
[Impeller] add emulated advanced blend support for exp canvas. (#54020)
Refactors flip backdrop into a shared method and adds supported for emulated advanced blends in exp canvas. Last missing feature (AFAIK).
1 parent fef21a6 commit b1cbf54

File tree

1 file changed

+132
-71
lines changed

1 file changed

+132
-71
lines changed

impeller/aiks/experimental_canvas.cc

Lines changed: 132 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "impeller/aiks/canvas.h"
88
#include "impeller/aiks/paint_pass_delegate.h"
99
#include "impeller/base/validation.h"
10+
#include "impeller/core/allocator.h"
1011
#include "impeller/core/formats.h"
1112
#include "impeller/entity/contents/framebuffer_blend_contents.h"
1213
#include "impeller/entity/contents/text_contents.h"
@@ -44,6 +45,106 @@ static void ApplyFramebufferBlend(Entity& entity) {
4445
entity.SetBlendMode(BlendMode::kSource);
4546
}
4647

48+
/// End the current render pass, saving the result as a texture, and then
49+
/// restart it with the backdrop cleared to the previous contents.
50+
///
51+
/// This method is used to set up the input for emulated advanced blends and
52+
/// backdrop filters.
53+
///
54+
/// Returns the previous render pass stored as a texture, or nullptr if there
55+
/// was a validation failure.
56+
static std::shared_ptr<Texture> FlipBackdrop(
57+
std::vector<LazyRenderingConfig>& render_passes,
58+
Point global_pass_position,
59+
EntityPassClipStack& clip_coverage_stack,
60+
ContentContext& renderer) {
61+
auto rendering_config = std::move(render_passes.back());
62+
render_passes.pop_back();
63+
64+
// If the very first thing we render in this EntityPass is a subpass that
65+
// happens to have a backdrop filter or advanced blend, than that backdrop
66+
// filter/blend will sample from an uninitialized texture.
67+
//
68+
// By calling `pass_context.GetRenderPass` here, we force the texture to pass
69+
// through at least one RenderPass with the correct clear configuration before
70+
// any sampling occurs.
71+
//
72+
// In cases where there are no contents, we
73+
// could instead check the clear color and initialize a 1x2 CPU texture
74+
// instead of ending the pass.
75+
rendering_config.inline_pass_context->GetRenderPass(0);
76+
if (!rendering_config.inline_pass_context->EndPass()) {
77+
VALIDATION_LOG
78+
<< "Failed to end the current render pass in order to read from "
79+
"the backdrop texture and apply an advanced blend or backdrop "
80+
"filter.";
81+
// Note: adding this render pass ensures there are no later crashes from
82+
// unbalanced save layers. Ideally, this method would return false and the
83+
// renderer could handle that by terminating dispatch.
84+
render_passes.push_back(LazyRenderingConfig(
85+
renderer, std::move(rendering_config.entity_pass_target)));
86+
return nullptr;
87+
}
88+
89+
std::shared_ptr<Texture> input_texture =
90+
rendering_config.entity_pass_target->Flip(
91+
*renderer.GetContext()->GetResourceAllocator());
92+
93+
if (!input_texture) {
94+
VALIDATION_LOG << "Failed to fetch the color texture in order to "
95+
"apply an advanced blend or backdrop filter.";
96+
97+
// Note: see above.
98+
render_passes.push_back(LazyRenderingConfig(
99+
renderer, std::move(rendering_config.entity_pass_target)));
100+
return nullptr;
101+
}
102+
103+
render_passes.push_back(LazyRenderingConfig(
104+
renderer, std::move(rendering_config.entity_pass_target)));
105+
// Eagerly restore the BDF contents.
106+
107+
// If the pass context returns a backdrop texture, we need to draw it to the
108+
// current pass. We do this because it's faster and takes significantly less
109+
// memory than storing/loading large MSAA textures. Also, it's not possible
110+
// to blit the non-MSAA resolve texture of the previous pass to MSAA
111+
// textures (let alone a transient one).
112+
Rect size_rect = Rect::MakeSize(input_texture->GetSize());
113+
auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect);
114+
msaa_backdrop_contents->SetStencilEnabled(false);
115+
msaa_backdrop_contents->SetLabel("MSAA backdrop");
116+
msaa_backdrop_contents->SetSourceRect(size_rect);
117+
msaa_backdrop_contents->SetTexture(input_texture);
118+
119+
Entity msaa_backdrop_entity;
120+
msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents));
121+
msaa_backdrop_entity.SetBlendMode(BlendMode::kSource);
122+
msaa_backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
123+
if (!msaa_backdrop_entity.Render(
124+
renderer,
125+
*render_passes.back().inline_pass_context->GetRenderPass(0).pass)) {
126+
VALIDATION_LOG << "Failed to render MSAA backdrop entity.";
127+
return nullptr;
128+
}
129+
130+
// Restore any clips that were recorded before the backdrop filter was
131+
// applied.
132+
auto& replay_entities = clip_coverage_stack.GetReplayEntities();
133+
for (const auto& replay : replay_entities) {
134+
SetClipScissor(
135+
clip_coverage_stack.CurrentClipCoverage(),
136+
*render_passes.back().inline_pass_context->GetRenderPass(0).pass,
137+
global_pass_position);
138+
if (!replay.entity.Render(
139+
renderer,
140+
*render_passes.back().inline_pass_context->GetRenderPass(0).pass)) {
141+
VALIDATION_LOG << "Failed to render entity for clip restore.";
142+
}
143+
}
144+
145+
return input_texture;
146+
}
147+
47148
} // namespace
48149

49150
static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig =
@@ -276,23 +377,12 @@ void ExperimentalCanvas::SaveLayer(
276377
return filter;
277378
};
278379

279-
auto rendering_config = std::move(render_passes_.back());
280-
render_passes_.pop_back();
281-
282-
// If the very first thing we render in this EntityPass is a subpass that
283-
// happens to have a backdrop filter, than that backdrop filter will end
284-
// may wind up sampling from the raw, uncleared texture that came straight
285-
// out of the texture cache. By calling `pass_context.GetRenderPass` here,
286-
// we force the texture to pass through at least one RenderPass with the
287-
// correct clear configuration before any sampling occurs.
288-
rendering_config.inline_pass_context->GetRenderPass(0);
289-
290-
ISize restore_size =
291-
rendering_config.inline_pass_context->GetTexture()->GetSize();
292-
293-
std::shared_ptr<Texture> input_texture =
294-
rendering_config.entity_pass_target->Flip(
295-
*renderer_.GetContext()->GetResourceAllocator());
380+
auto input_texture = FlipBackdrop(render_passes_, GetGlobalPassPosition(),
381+
clip_coverage_stack_, renderer_);
382+
if (!input_texture) {
383+
// Validation failures are logged in FlipBackdrop.
384+
return;
385+
}
296386

297387
backdrop_filter_contents = backdrop_filter_proc(
298388
FilterInput::Make(std::move(input_texture)),
@@ -302,58 +392,6 @@ void ExperimentalCanvas::SaveLayer(
302392
transform_stack_.back().transform.HasTranslation()
303393
? Entity::RenderingMode::kSubpassPrependSnapshotTransform
304394
: Entity::RenderingMode::kSubpassAppendSnapshotTransform);
305-
306-
// The subpass will need to read from the current pass texture when
307-
// rendering the backdrop, so if there's an active pass, end it prior to
308-
// rendering the subpass.
309-
rendering_config.inline_pass_context->EndPass();
310-
311-
// Create a new render pass that the backdrop filter contents will be
312-
// restored to in order to continue rendering.
313-
render_passes_.push_back(LazyRenderingConfig(
314-
renderer_, std::move(rendering_config.entity_pass_target)));
315-
// Eagerly restore the BDF contents.
316-
317-
// If the pass context returns a backdrop texture, we need to draw it to the
318-
// current pass. We do this because it's faster and takes significantly less
319-
// memory than storing/loading large MSAA textures. Also, it's not possible
320-
// to blit the non-MSAA resolve texture of the previous pass to MSAA
321-
// textures (let alone a transient one).
322-
Rect size_rect = Rect::MakeSize(restore_size);
323-
auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect);
324-
msaa_backdrop_contents->SetStencilEnabled(false);
325-
msaa_backdrop_contents->SetLabel("MSAA backdrop");
326-
msaa_backdrop_contents->SetSourceRect(size_rect);
327-
msaa_backdrop_contents->SetTexture(
328-
rendering_config.inline_pass_context->GetTexture());
329-
330-
Entity msaa_backdrop_entity;
331-
msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents));
332-
msaa_backdrop_entity.SetBlendMode(BlendMode::kSource);
333-
msaa_backdrop_entity.SetClipDepth(std::numeric_limits<uint32_t>::max());
334-
if (!msaa_backdrop_entity.Render(renderer_,
335-
*render_passes_.back()
336-
.inline_pass_context->GetRenderPass(0)
337-
.pass)) {
338-
VALIDATION_LOG << "Failed to render MSAA backdrop filter entity.";
339-
return;
340-
}
341-
342-
// Restore any clips that were recorded before the backdrop filter was
343-
// applied.
344-
auto& replay_entities = clip_coverage_stack_.GetReplayEntities();
345-
for (const auto& replay : replay_entities) {
346-
SetClipScissor(
347-
clip_coverage_stack_.CurrentClipCoverage(),
348-
*render_passes_.back().inline_pass_context->GetRenderPass(0).pass,
349-
GetGlobalPassPosition());
350-
if (!replay.entity.Render(renderer_,
351-
*render_passes_.back()
352-
.inline_pass_context->GetRenderPass(0)
353-
.pass)) {
354-
VALIDATION_LOG << "Failed to render entity for clip restore.";
355-
}
356-
}
357395
}
358396

359397
// When applying a save layer, absorb any pending distributed opacity.
@@ -484,8 +522,31 @@ bool ExperimentalCanvas::Restore() {
484522
if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) {
485523
ApplyFramebufferBlend(element_entity);
486524
} else {
487-
VALIDATION_LOG << "Emulated advanced blends are currently unsupported.";
488-
element_entity.SetBlendMode(BlendMode::kSourceOver);
525+
// End the active pass and flush the buffer before rendering "advanced"
526+
// blends. Advanced blends work by binding the current render target
527+
// texture as an input ("destination"), blending with a second texture
528+
// input ("source"), writing the result to an intermediate texture, and
529+
// finally copying the data from the intermediate texture back to the
530+
// render target texture. And so all of the commands that have written
531+
// to the render target texture so far need to execute before it's bound
532+
// for blending (otherwise the blend pass will end up executing before
533+
// all the previous commands in the active pass).
534+
auto input_texture =
535+
FlipBackdrop(render_passes_, GetGlobalPassPosition(),
536+
clip_coverage_stack_, renderer_);
537+
if (!input_texture) {
538+
return false;
539+
}
540+
541+
FilterInput::Vector inputs = {
542+
FilterInput::Make(input_texture,
543+
element_entity.GetTransform().Invert()),
544+
FilterInput::Make(element_entity.GetContents())};
545+
auto contents = ColorFilterContents::MakeBlend(
546+
element_entity.GetBlendMode(), inputs);
547+
contents->SetCoverageHint(element_entity.GetCoverage());
548+
element_entity.SetContents(std::move(contents));
549+
element_entity.SetBlendMode(BlendMode::kSource);
489550
}
490551
}
491552

0 commit comments

Comments
 (0)