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

Commit fb2722c

Browse files
Composite multiple layers in Windows software rendering (#51759)
Blend pixels per-alpha when presenting multiple layers from the software compositor. Part of flutter/flutter#143375 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat --------- Co-authored-by: Loïc Sharma <[email protected]>
1 parent 23a5d23 commit fb2722c

File tree

2 files changed

+219
-10
lines changed

2 files changed

+219
-10
lines changed

shell/platform/windows/compositor_software.cc

Lines changed: 110 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,78 @@
99

1010
namespace flutter {
1111

12+
constexpr int kOpaqueBlack = 0xff000000;
13+
14+
namespace {
15+
16+
/// Calculate the minimum and maximum x and y coordinates to enclose all layers.
17+
FlutterRect CalculateBounds(const FlutterLayer** layers, size_t layers_count) {
18+
double x_min = HUGE_VAL;
19+
double x_max = -HUGE_VAL;
20+
double y_min = HUGE_VAL;
21+
double y_max = -HUGE_VAL;
22+
for (const FlutterLayer** layer = layers; layer < layers + layers_count;
23+
layer++) {
24+
const FlutterPoint& offset = (*layer)->offset;
25+
const FlutterSize& size = (*layer)->size;
26+
x_min = std::min(x_min, offset.x);
27+
y_min = std::min(y_min, offset.y);
28+
x_max = std::max(x_max, offset.x + size.width);
29+
y_max = std::max(y_max, offset.y + size.height);
30+
}
31+
return FlutterRect{x_min, y_min, x_max, y_max};
32+
}
33+
34+
/// Blend layer in-place onto allocation, which already holds the previous
35+
/// results of composition.
36+
void BlendLayer(std::vector<uint32_t>& allocation,
37+
const FlutterLayer& layer,
38+
int x_min,
39+
int y_min,
40+
int width,
41+
int height) {
42+
FML_DCHECK(layer.type == kFlutterLayerContentTypeBackingStore);
43+
auto& backing_store = *layer.backing_store;
44+
FML_DCHECK(backing_store.type == kFlutterBackingStoreTypeSoftware);
45+
auto src_data =
46+
static_cast<const uint32_t*>(backing_store.software.allocation);
47+
const FlutterPoint& offset = layer.offset;
48+
const FlutterSize& size = layer.size;
49+
50+
// Bounds for iteration to prevent out-of-bounds destination coordinates.
51+
int y_src_min = std::max(0., y_min - offset.y);
52+
int y_src_max = std::min(size.height, height + y_min - offset.y);
53+
int x_src_min = std::max(0., x_min - offset.x);
54+
int x_src_max = std::min(size.width, width + x_min - offset.x);
55+
for (int y_src = y_src_min; y_src < y_src_max; y_src++) {
56+
int y_dst = y_src + offset.y - y_min;
57+
for (int x_src = x_src_min; x_src < x_src_max; x_src++) {
58+
int x_dst = x_src + offset.x + x_min;
59+
size_t i_src = y_src * size.width + x_src;
60+
size_t i_dst = y_dst * width + x_dst;
61+
uint32_t src = src_data[i_src];
62+
uint32_t dst = allocation[i_dst];
63+
64+
int r_src = (src >> 0) & 0xff;
65+
int g_src = (src >> 8) & 0xff;
66+
int b_src = (src >> 16) & 0xff;
67+
int a_src = (src >> 24) & 0xff;
68+
69+
int r_dst = (dst >> 0) & 0xff;
70+
int g_dst = (dst >> 8) & 0xff;
71+
int b_dst = (dst >> 16) & 0xff;
72+
73+
int r = (r_dst * 255 + (r_src - r_dst) * a_src) / 255;
74+
int g = (g_dst * 255 + (g_src - g_dst) * a_src) / 255;
75+
int b = (b_dst * 255 + (b_src - b_dst) * a_src) / 255;
76+
77+
allocation[i_dst] = (r << 0) | (g << 8) | (b << 16) | (0xff << 24);
78+
}
79+
}
80+
}
81+
82+
} // namespace
83+
1284
CompositorSoftware::CompositorSoftware() {}
1385

1486
bool CompositorSoftware::CreateBackingStore(
@@ -47,18 +119,46 @@ bool CompositorSoftware::Present(FlutterWindowsView* view,
47119
return view->ClearSoftwareBitmap();
48120
}
49121

50-
// TODO: Support compositing layers and platform views.
51-
// See: https://github.com/flutter/flutter/issues/31713
52-
FML_DCHECK(layers_count == 1);
53-
FML_DCHECK(layers[0]->offset.x == 0 && layers[0]->offset.y == 0);
54-
FML_DCHECK(layers[0]->type == kFlutterLayerContentTypeBackingStore);
55-
FML_DCHECK(layers[0]->backing_store->type ==
56-
kFlutterBackingStoreTypeSoftware);
122+
// Bypass composition logic if there is only one layer.
123+
if (layers_count == 1) {
124+
const FlutterLayer* layer = layers[0];
125+
FML_DCHECK(layer != nullptr);
126+
if (layer->type == kFlutterLayerContentTypeBackingStore &&
127+
layer->offset.x == 0 && layer->offset.y == 0) {
128+
auto& backing_store = *layer->backing_store;
129+
FML_DCHECK(backing_store.type == kFlutterBackingStoreTypeSoftware);
130+
auto& software = backing_store.software;
131+
return view->PresentSoftwareBitmap(software.allocation,
132+
software.row_bytes, software.height);
133+
}
134+
}
135+
136+
// Composite many layers.
137+
FlutterRect bounds = CalculateBounds(layers, layers_count);
138+
// Truncate from double to integer to represent whole pixels.
139+
int x_min = static_cast<int>(bounds.left);
140+
int x_max = static_cast<int>(bounds.right);
141+
int y_min = static_cast<int>(bounds.top);
142+
int y_max = static_cast<int>(bounds.bottom);
57143

58-
const auto& backing_store = layers[0]->backing_store->software;
144+
int width = x_max - x_min;
145+
int height = y_max - y_min;
146+
std::vector<uint32_t> allocation(width * height, kOpaqueBlack);
147+
148+
for (const FlutterLayer** layer = layers; layer < layers + layers_count;
149+
layer++) {
150+
// TODO(schectman): handle platform view type layers.
151+
// https://github.com/flutter/flutter/issues/143375
152+
if ((*layer)->type == kFlutterLayerContentTypeBackingStore) {
153+
BlendLayer(allocation, **layer, x_min, y_min, width, height);
154+
} else {
155+
FML_UNREACHABLE();
156+
return false;
157+
}
158+
}
59159

60-
return view->PresentSoftwareBitmap(
61-
backing_store.allocation, backing_store.row_bytes, backing_store.height);
160+
return view->PresentSoftwareBitmap(static_cast<void*>(allocation.data()),
161+
width * sizeof(uint32_t), height);
62162
}
63163

64164
} // namespace flutter

shell/platform/windows/compositor_software_unittests.cc

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,114 @@ TEST_F(CompositorSoftwareTest, PresentEmpty) {
107107
EXPECT_TRUE(compositor.Present(view(), nullptr, 0));
108108
}
109109

110+
// Test compositing an upper layer on a base layer, each 2x2 pixels.
111+
// Base layer has opaque pixel values:
112+
// BLACK RED
113+
// GREEN WHITE
114+
// Overlay layer has pixel values:
115+
// RED: 127 WHITE: 0
116+
// BLUE: 127 BLACK: 255
117+
TEST_F(CompositorSoftwareTest, PresentMultiLayers) {
118+
UseEngineWithView();
119+
120+
CompositorSoftware compositor;
121+
122+
FlutterBackingStoreConfig config = {sizeof(config), {2, 2}};
123+
FlutterBackingStore backing_store0 = {sizeof(FlutterBackingStore), nullptr};
124+
FlutterBackingStore backing_store1 = {sizeof(FlutterBackingStore), nullptr};
125+
126+
ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store0));
127+
ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store1));
128+
129+
uint32_t pixels0[4] = {0xff000000, 0xff0000ff, 0xff00ff00, 0xffffffff};
130+
uint32_t pixels1[4] = {0x7f0000ff, 0x00ffffff, 0x7fff0000, 0xff000000};
131+
132+
std::memcpy(const_cast<void*>(backing_store0.software.allocation), pixels0,
133+
sizeof(uint32_t) * 4);
134+
std::memcpy(const_cast<void*>(backing_store1.software.allocation), pixels1,
135+
sizeof(uint32_t) * 4);
136+
137+
FlutterLayer layer0 = {};
138+
layer0.type = kFlutterLayerContentTypeBackingStore;
139+
layer0.backing_store = &backing_store0;
140+
layer0.offset = {0, 0};
141+
layer0.size = {2, 2};
142+
143+
FlutterLayer layer1 = layer0;
144+
layer1.backing_store = &backing_store1;
145+
const FlutterLayer* layer_ptr[2] = {&layer0, &layer1};
146+
147+
EXPECT_CALL(*view(), PresentSoftwareBitmap)
148+
.WillOnce([&](const void* allocation, size_t row_bytes, size_t height) {
149+
auto pixel_data = static_cast<const uint32_t*>(allocation);
150+
EXPECT_EQ(row_bytes, 2 * sizeof(uint32_t));
151+
EXPECT_EQ(height, 2);
152+
EXPECT_EQ(pixel_data[0], 0xff00007f);
153+
EXPECT_EQ(pixel_data[1], 0xff0000ff);
154+
EXPECT_EQ(pixel_data[2], 0xff7f8000);
155+
EXPECT_EQ(pixel_data[3], 0xff000000);
156+
return true;
157+
});
158+
EXPECT_TRUE(compositor.Present(view(), layer_ptr, 2));
159+
160+
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store0));
161+
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store1));
162+
}
163+
164+
// Test compositing layers with offsets.
165+
// 0th layer is a single red pixel in the top-left.
166+
// 1st layer is a row of two blue pixels on the second row.
167+
TEST_F(CompositorSoftwareTest, PresentOffsetLayers) {
168+
UseEngineWithView();
169+
170+
CompositorSoftware compositor;
171+
172+
FlutterBackingStoreConfig config0 = {sizeof(FlutterBackingStoreConfig),
173+
{1, 1}};
174+
FlutterBackingStore backing_store0 = {sizeof(FlutterBackingStore), nullptr};
175+
FlutterBackingStoreConfig config1 = {sizeof(FlutterBackingStoreConfig),
176+
{2, 1}};
177+
FlutterBackingStore backing_store1 = {sizeof(FlutterBackingStore), nullptr};
178+
179+
ASSERT_TRUE(compositor.CreateBackingStore(config0, &backing_store0));
180+
ASSERT_TRUE(compositor.CreateBackingStore(config1, &backing_store1));
181+
182+
uint32_t pixels0 = 0xff0000ff;
183+
uint32_t pixels1[2] = {0xffff0000, 0xffff0000};
184+
185+
std::memcpy(const_cast<void*>(backing_store0.software.allocation), &pixels0,
186+
sizeof(uint32_t) * 1);
187+
std::memcpy(const_cast<void*>(backing_store1.software.allocation), pixels1,
188+
sizeof(uint32_t) * 2);
189+
190+
FlutterLayer layer0 = {};
191+
layer0.type = kFlutterLayerContentTypeBackingStore;
192+
layer0.backing_store = &backing_store0;
193+
layer0.offset = {0, 0};
194+
layer0.size = {1, 1};
195+
196+
FlutterLayer layer1 = layer0;
197+
layer1.backing_store = &backing_store1;
198+
layer1.offset = {0, 1};
199+
layer1.size = {2, 1};
200+
const FlutterLayer* layer_ptr[2] = {&layer0, &layer1};
201+
202+
EXPECT_CALL(*view(), PresentSoftwareBitmap)
203+
.WillOnce([&](const void* allocation, size_t row_bytes, size_t height) {
204+
auto pixel_data = static_cast<const uint32_t*>(allocation);
205+
EXPECT_EQ(row_bytes, 2 * sizeof(uint32_t));
206+
EXPECT_EQ(height, 2);
207+
EXPECT_EQ(pixel_data[0], 0xff0000ff);
208+
EXPECT_EQ(pixel_data[1], 0xff000000);
209+
EXPECT_EQ(pixel_data[2], 0xffff0000);
210+
EXPECT_EQ(pixel_data[3], 0xffff0000);
211+
return true;
212+
});
213+
EXPECT_TRUE(compositor.Present(view(), layer_ptr, 2));
214+
215+
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store0));
216+
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store1));
217+
}
218+
110219
} // namespace testing
111220
} // namespace flutter

0 commit comments

Comments
 (0)