Skip to content

[Flutter GPU] Add surface API for framework presentation#187358

Merged
auto-submit[bot] merged 20 commits into
flutter:masterfrom
bdero:bdero/flutter-gpu-surface
Jun 13, 2026
Merged

[Flutter GPU] Add surface API for framework presentation#187358
auto-submit[bot] merged 20 commits into
flutter:masterfrom
bdero:bdero/flutter-gpu-surface

Conversation

@bdero

@bdero bdero commented May 31, 2026

Copy link
Copy Markdown
Member

Fixes #187357

Adds a Flutter GPU surface API for rendering into a presentable color texture and drawing the latest presented image through Flutter. The surface owns the backing texture pool, tracks acquired frames, keeps the current image out of reuse, and waits for producer command buffer completion before a presented texture can be reused.

The API is shaped as a small family so later destinations (a Flutter canvas, and eventually the window swapchain) can share one per-frame contract. GpuSurface and GpuSurfaceFrame are the destination-agnostic base types. This PR ships the first concrete destination, GpuImageSurface, which Flutter draws as a ui.Image.

Two parts of the contract are uniform across all future destinations, even though the image destination never exercises them. acquireNextFrame is nullable, because a window swapchain can skip a frame. present returns a GpuPresentStatus, because a window swapchain can report that it needs reconfiguring. For an image surface, acquire always returns a frame and present always reports success, and the published image is read from GpuImageSurface.currentImage.

The API keeps depth, stencil, MSAA, and intermediate attachments renderer-owned. GpuSurfaceFrame.colorTexture is only the final color target handed back to Flutter as a ui.Image.

A minimal example.

final surface = gpu.gpuContext.createImageSurface(width, height);
final frame = surface.acquireNextFrame();

final commandBuffer = gpu.gpuContext.createCommandBuffer();
commandBuffer.createRenderPass(
  gpu.RenderTarget.singleColor(
    gpu.ColorAttachment(texture: frame.colorTexture),
  ),
);

// Bind a pipeline, bind resources, and draw.

frame.present(commandBuffer);
commandBuffer.submit();
canvas.drawImageRect(surface.currentImage!, src, dst, paint);

Depth and stencil remain normal renderer-owned attachments.

final frame = surface.acquireNextFrame();
final depthStencil = gpu.gpuContext.createTexture(
  gpu.StorageMode.deviceTransient,
  width,
  height,
  format: gpu.gpuContext.defaultDepthStencilFormat,
  enableRenderTargetUsage: true,
  enableShaderReadUsage: false,
);

final commandBuffer = gpu.gpuContext.createCommandBuffer();
commandBuffer.createRenderPass(
  gpu.RenderTarget.singleColor(
    gpu.ColorAttachment(texture: frame.colorTexture),
    depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthStencil),
  ),
);

Tests cover surface acquisition, presentation, current image behavior, discard, resize, and rendering a clear color into a surface.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the AI contribution guidelines and understand my responsibilities, or I am not using AI tools.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • I followed the breaking change policy and added Data Driven Fixes where supported.
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 31, 2026
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. flutter-gpu team-fluttergpu Owned by Flutter GPU team labels May 31, 2026
@github-project-automation github-project-automation Bot moved this to 🤔 Needs Triage in Flutter GPU May 31, 2026
@bdero bdero changed the title [flutter_gpu] Add surface API for framework presentation [Flutter GPU] Add surface API for framework presentation May 31, 2026
@bdero bdero force-pushed the bdero/flutter-gpu-surface branch from 81855ad to b9288a4 Compare May 31, 2026 08:43
@github-actions github-actions Bot removed the CICD Run CI/CD label May 31, 2026
@bdero bdero force-pushed the bdero/flutter-gpu-surface branch from b9288a4 to 47f537c Compare May 31, 2026 08:48
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label May 31, 2026
@bdero bdero marked this pull request as ready for review May 31, 2026 08:52

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces GpuSurface and SurfaceFrame to the Flutter GPU library, allowing applications to manage a pool of presentable render targets. It also adds completion callback support to CommandBuffer and includes comprehensive unit tests. The review feedback suggests invalidating the colorTexture when a frame is presented or discarded to prevent accidental post-lease usage, and pruning inactive, old-sized texture records during a surface resize to avoid a potential GPU memory leak.

Comment thread engine/src/flutter/lib/gpu/lib/src/surface.dart Outdated
Comment thread engine/src/flutter/lib/gpu/lib/src/surface.dart
Comment thread engine/src/flutter/lib/gpu/surface.cc
@bdero bdero force-pushed the bdero/flutter-gpu-surface branch from 47f537c to 1a9cf59 Compare May 31, 2026 11:13
@github-actions github-actions Bot removed the CICD Run CI/CD label May 31, 2026
@bdero bdero added the CICD Run CI/CD label May 31, 2026
@bdero bdero force-pushed the bdero/flutter-gpu-surface branch from 1a9cf59 to 41527d3 Compare June 1, 2026 07:16
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 1, 2026
@bdero bdero added the CICD Run CI/CD label Jun 1, 2026
@bdero bdero requested a review from gaaclarke June 1, 2026 11:03
@bdero bdero moved this from 🤔 Needs Triage to ⚙️ In Progress in Flutter GPU Jun 1, 2026
@gaaclarke

Copy link
Copy Markdown
Member
final image = frame.present(commandBuffer);
commandBuffer.submit();

Is there a reason that present() does not imply submit()?

_checkDimensions(width, height);
final String? error = _resize(width, height);
if (error != null) {
throw Exception(error);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an Error, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7ec7a99, this now throws StateError.

/// [SurfaceFrame.colorTexture], call [SurfaceFrame.present] with the command
/// buffer that contains the final writes, submit that command buffer, and draw
/// [currentImage].
base class GpuSurface extends NativeFieldWrapperClass1 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you want final for this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7ec7a99.

/// [present] to publish the frame as the surface's current image. If the frame
/// will not be presented, call [discard] so the surface can reuse its backing
/// texture.
base class SurfaceFrame {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7ec7a99.

/// for future frames.
///
/// The returned [ui.Image] is also available from [GpuSurface.currentImage].
ui.Image present(CommandBuffer commandBuffer) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this submit the command buffer? (mentioned in comment on issue)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept submit explicit so one command buffer can produce multiple presented surface frames, for example rendering two GpuSurfaces and then submitting their shared command buffer once.


bool CommandBuffer::Submit(
const impeller::CommandBuffer::CompletionCallback& completion_callback) {
if (submitted_) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest adding a unit test for these c++ level changes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7ec7a99.

std::shared_ptr<Surface::TextureRecord> Surface::CreateTextureRecord() const {
#if !IMPELLER_SUPPORTS_RENDERING
FML_LOG(ERROR) << "Flutter GPU surfaces require Impeller rendering support.";
return nullptr;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we even compile this without impeller?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and I filed #187424 to track splitting the Flutter GPU build so most implementation code is compiled only when Impeller rendering is available.

resizedFrame.discard();
}, skip: !(impellerEnabled && flutterGpuEnabled));

test('Can render clear color into GpuSurface', () async {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a golden test would be good for this as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7ec7a99, the existing surface golden test was renamed to make that coverage explicit.

@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 2, 2026
@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Jun 2, 2026
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 2, 2026
gaaclarke
gaaclarke previously approved these changes Jun 11, 2026
@bdero bdero added CICD Run CI/CD and removed CICD Run CI/CD labels Jun 12, 2026
@bdero bdero requested a review from gaaclarke June 12, 2026 02:48
@bdero

bdero commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Resolved new conflicts

@bdero bdero added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 12, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jun 12, 2026
@auto-submit

auto-submit Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

auto label is removed for flutter/flutter/187358, Failed to enqueue flutter/flutter/187358 with HTTP 400: Pull request Required status check "Check Code Freeze" is expected..

@bdero

bdero commented Jun 12, 2026

Copy link
Copy Markdown
Member Author

Any idea why this is happening? Is the action broken? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. flutter-gpu team-fluttergpu Owned by Flutter GPU team

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

[Flutter GPU] Add a surface API for framework presentation

2 participants