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

Commit

Permalink
Composite multiple layers in Windows software rendering (#51759)
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
yaakovschectman and loic-sharma authored Apr 11, 2024
1 parent 23a5d23 commit fb2722c
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 10 deletions.
120 changes: 110 additions & 10 deletions shell/platform/windows/compositor_software.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,78 @@

namespace flutter {

constexpr int kOpaqueBlack = 0xff000000;

namespace {

/// Calculate the minimum and maximum x and y coordinates to enclose all layers.
FlutterRect CalculateBounds(const FlutterLayer** layers, size_t layers_count) {
double x_min = HUGE_VAL;
double x_max = -HUGE_VAL;
double y_min = HUGE_VAL;
double y_max = -HUGE_VAL;
for (const FlutterLayer** layer = layers; layer < layers + layers_count;
layer++) {
const FlutterPoint& offset = (*layer)->offset;
const FlutterSize& size = (*layer)->size;
x_min = std::min(x_min, offset.x);
y_min = std::min(y_min, offset.y);
x_max = std::max(x_max, offset.x + size.width);
y_max = std::max(y_max, offset.y + size.height);
}
return FlutterRect{x_min, y_min, x_max, y_max};
}

/// Blend layer in-place onto allocation, which already holds the previous
/// results of composition.
void BlendLayer(std::vector<uint32_t>& allocation,
const FlutterLayer& layer,
int x_min,
int y_min,
int width,
int height) {
FML_DCHECK(layer.type == kFlutterLayerContentTypeBackingStore);
auto& backing_store = *layer.backing_store;
FML_DCHECK(backing_store.type == kFlutterBackingStoreTypeSoftware);
auto src_data =
static_cast<const uint32_t*>(backing_store.software.allocation);
const FlutterPoint& offset = layer.offset;
const FlutterSize& size = layer.size;

// Bounds for iteration to prevent out-of-bounds destination coordinates.
int y_src_min = std::max(0., y_min - offset.y);
int y_src_max = std::min(size.height, height + y_min - offset.y);
int x_src_min = std::max(0., x_min - offset.x);
int x_src_max = std::min(size.width, width + x_min - offset.x);
for (int y_src = y_src_min; y_src < y_src_max; y_src++) {
int y_dst = y_src + offset.y - y_min;
for (int x_src = x_src_min; x_src < x_src_max; x_src++) {
int x_dst = x_src + offset.x + x_min;
size_t i_src = y_src * size.width + x_src;
size_t i_dst = y_dst * width + x_dst;
uint32_t src = src_data[i_src];
uint32_t dst = allocation[i_dst];

int r_src = (src >> 0) & 0xff;
int g_src = (src >> 8) & 0xff;
int b_src = (src >> 16) & 0xff;
int a_src = (src >> 24) & 0xff;

int r_dst = (dst >> 0) & 0xff;
int g_dst = (dst >> 8) & 0xff;
int b_dst = (dst >> 16) & 0xff;

int r = (r_dst * 255 + (r_src - r_dst) * a_src) / 255;
int g = (g_dst * 255 + (g_src - g_dst) * a_src) / 255;
int b = (b_dst * 255 + (b_src - b_dst) * a_src) / 255;

allocation[i_dst] = (r << 0) | (g << 8) | (b << 16) | (0xff << 24);
}
}
}

} // namespace

CompositorSoftware::CompositorSoftware() {}

bool CompositorSoftware::CreateBackingStore(
Expand Down Expand Up @@ -47,18 +119,46 @@ bool CompositorSoftware::Present(FlutterWindowsView* view,
return view->ClearSoftwareBitmap();
}

// TODO: Support compositing layers and platform views.
// See: https://github.com/flutter/flutter/issues/31713
FML_DCHECK(layers_count == 1);
FML_DCHECK(layers[0]->offset.x == 0 && layers[0]->offset.y == 0);
FML_DCHECK(layers[0]->type == kFlutterLayerContentTypeBackingStore);
FML_DCHECK(layers[0]->backing_store->type ==
kFlutterBackingStoreTypeSoftware);
// Bypass composition logic if there is only one layer.
if (layers_count == 1) {
const FlutterLayer* layer = layers[0];
FML_DCHECK(layer != nullptr);
if (layer->type == kFlutterLayerContentTypeBackingStore &&
layer->offset.x == 0 && layer->offset.y == 0) {
auto& backing_store = *layer->backing_store;
FML_DCHECK(backing_store.type == kFlutterBackingStoreTypeSoftware);
auto& software = backing_store.software;
return view->PresentSoftwareBitmap(software.allocation,
software.row_bytes, software.height);
}
}

// Composite many layers.
FlutterRect bounds = CalculateBounds(layers, layers_count);
// Truncate from double to integer to represent whole pixels.
int x_min = static_cast<int>(bounds.left);
int x_max = static_cast<int>(bounds.right);
int y_min = static_cast<int>(bounds.top);
int y_max = static_cast<int>(bounds.bottom);

const auto& backing_store = layers[0]->backing_store->software;
int width = x_max - x_min;
int height = y_max - y_min;
std::vector<uint32_t> allocation(width * height, kOpaqueBlack);

for (const FlutterLayer** layer = layers; layer < layers + layers_count;
layer++) {
// TODO(schectman): handle platform view type layers.
// https://github.com/flutter/flutter/issues/143375
if ((*layer)->type == kFlutterLayerContentTypeBackingStore) {
BlendLayer(allocation, **layer, x_min, y_min, width, height);
} else {
FML_UNREACHABLE();
return false;
}
}

return view->PresentSoftwareBitmap(
backing_store.allocation, backing_store.row_bytes, backing_store.height);
return view->PresentSoftwareBitmap(static_cast<void*>(allocation.data()),
width * sizeof(uint32_t), height);
}

} // namespace flutter
109 changes: 109 additions & 0 deletions shell/platform/windows/compositor_software_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,114 @@ TEST_F(CompositorSoftwareTest, PresentEmpty) {
EXPECT_TRUE(compositor.Present(view(), nullptr, 0));
}

// Test compositing an upper layer on a base layer, each 2x2 pixels.
// Base layer has opaque pixel values:
// BLACK RED
// GREEN WHITE
// Overlay layer has pixel values:
// RED: 127 WHITE: 0
// BLUE: 127 BLACK: 255
TEST_F(CompositorSoftwareTest, PresentMultiLayers) {
UseEngineWithView();

CompositorSoftware compositor;

FlutterBackingStoreConfig config = {sizeof(config), {2, 2}};
FlutterBackingStore backing_store0 = {sizeof(FlutterBackingStore), nullptr};
FlutterBackingStore backing_store1 = {sizeof(FlutterBackingStore), nullptr};

ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store0));
ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store1));

uint32_t pixels0[4] = {0xff000000, 0xff0000ff, 0xff00ff00, 0xffffffff};
uint32_t pixels1[4] = {0x7f0000ff, 0x00ffffff, 0x7fff0000, 0xff000000};

std::memcpy(const_cast<void*>(backing_store0.software.allocation), pixels0,
sizeof(uint32_t) * 4);
std::memcpy(const_cast<void*>(backing_store1.software.allocation), pixels1,
sizeof(uint32_t) * 4);

FlutterLayer layer0 = {};
layer0.type = kFlutterLayerContentTypeBackingStore;
layer0.backing_store = &backing_store0;
layer0.offset = {0, 0};
layer0.size = {2, 2};

FlutterLayer layer1 = layer0;
layer1.backing_store = &backing_store1;
const FlutterLayer* layer_ptr[2] = {&layer0, &layer1};

EXPECT_CALL(*view(), PresentSoftwareBitmap)
.WillOnce([&](const void* allocation, size_t row_bytes, size_t height) {
auto pixel_data = static_cast<const uint32_t*>(allocation);
EXPECT_EQ(row_bytes, 2 * sizeof(uint32_t));
EXPECT_EQ(height, 2);
EXPECT_EQ(pixel_data[0], 0xff00007f);
EXPECT_EQ(pixel_data[1], 0xff0000ff);
EXPECT_EQ(pixel_data[2], 0xff7f8000);
EXPECT_EQ(pixel_data[3], 0xff000000);
return true;
});
EXPECT_TRUE(compositor.Present(view(), layer_ptr, 2));

ASSERT_TRUE(compositor.CollectBackingStore(&backing_store0));
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store1));
}

// Test compositing layers with offsets.
// 0th layer is a single red pixel in the top-left.
// 1st layer is a row of two blue pixels on the second row.
TEST_F(CompositorSoftwareTest, PresentOffsetLayers) {
UseEngineWithView();

CompositorSoftware compositor;

FlutterBackingStoreConfig config0 = {sizeof(FlutterBackingStoreConfig),
{1, 1}};
FlutterBackingStore backing_store0 = {sizeof(FlutterBackingStore), nullptr};
FlutterBackingStoreConfig config1 = {sizeof(FlutterBackingStoreConfig),
{2, 1}};
FlutterBackingStore backing_store1 = {sizeof(FlutterBackingStore), nullptr};

ASSERT_TRUE(compositor.CreateBackingStore(config0, &backing_store0));
ASSERT_TRUE(compositor.CreateBackingStore(config1, &backing_store1));

uint32_t pixels0 = 0xff0000ff;
uint32_t pixels1[2] = {0xffff0000, 0xffff0000};

std::memcpy(const_cast<void*>(backing_store0.software.allocation), &pixels0,
sizeof(uint32_t) * 1);
std::memcpy(const_cast<void*>(backing_store1.software.allocation), pixels1,
sizeof(uint32_t) * 2);

FlutterLayer layer0 = {};
layer0.type = kFlutterLayerContentTypeBackingStore;
layer0.backing_store = &backing_store0;
layer0.offset = {0, 0};
layer0.size = {1, 1};

FlutterLayer layer1 = layer0;
layer1.backing_store = &backing_store1;
layer1.offset = {0, 1};
layer1.size = {2, 1};
const FlutterLayer* layer_ptr[2] = {&layer0, &layer1};

EXPECT_CALL(*view(), PresentSoftwareBitmap)
.WillOnce([&](const void* allocation, size_t row_bytes, size_t height) {
auto pixel_data = static_cast<const uint32_t*>(allocation);
EXPECT_EQ(row_bytes, 2 * sizeof(uint32_t));
EXPECT_EQ(height, 2);
EXPECT_EQ(pixel_data[0], 0xff0000ff);
EXPECT_EQ(pixel_data[1], 0xff000000);
EXPECT_EQ(pixel_data[2], 0xffff0000);
EXPECT_EQ(pixel_data[3], 0xffff0000);
return true;
});
EXPECT_TRUE(compositor.Present(view(), layer_ptr, 2));

ASSERT_TRUE(compositor.CollectBackingStore(&backing_store0));
ASSERT_TRUE(compositor.CollectBackingStore(&backing_store1));
}

} // namespace testing
} // namespace flutter

0 comments on commit fb2722c

Please sign in to comment.