Skip to content

[Flutter GPU] Add a surface API for framework presentation #187357

Description

@bdero

Use case

Flutter GPU users can currently render into a gpu.Texture, convert it to a ui.Image, then draw that image with Canvas.drawImageRect.

final color = gpu.gpuContext.createTexture(
  gpu.StorageMode.devicePrivate,
  width,
  height,
  format: gpu.gpuContext.defaultColorFormat,
  enableRenderTargetUsage: true,
  enableShaderReadUsage: true,
);

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

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

commandBuffer.submit();

final image = color.asImage();
canvas.drawImageRect(image, src, dst, paint);

This works with Impeller because the image can wrap the underlying shader readable Impeller texture. The hard part is lifetime. If an app renders a new frame each build, it must avoid overwriting a texture that the framework or rasterizer may still be sampling. Apps can work around this with a small texture ring, but that forces application code to guess how many frames may be in flight. Some backends or future backend modes may require three buffers, dynamic buffer growth, or explicit acquire and release fences.

The missing API is not another way to create a texture. It is a presentation contract between Flutter GPU and the Flutter framework.

Proposal

Add a first class Flutter GPU surface that owns a pool of presentable, shader readable render targets.

final surface = gpu.gpuContext.createSurface(
  width,
  height,
  format: gpu.gpuContext.defaultColorFormat,
);

final frame = surface.acquireNextFrame();

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

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

final image = frame.present(commandBuffer);
commandBuffer.submit();

canvas.drawImageRect(image, src, dst, paint);

Expected semantics:

  • GpuSurface owns the backing texture pool and chooses the buffer count.
  • SurfaceFrame.colorTexture is writable only until present.
  • present ties publication to command buffer completion.
  • surface.currentImage returns the latest presented image, not the texture currently being written.
  • The engine does not reuse a backing texture until producer GPU work and framework raster sampling are complete.
  • Resizing invalidates future acquired frames while keeping old presented images alive until safe.
  • Texture.asImage() remains useful for static and expert cases, but animated render targets should use GpuSurface.

The surface should own only the final presentable color texture. Renderers can still use normal Flutter GPU render targets for depth, stencil, MSAA, and intermediate color 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();
final renderPass = commandBuffer.createRenderPass(
  gpu.RenderTarget.singleColor(
    gpu.ColorAttachment(texture: frame.colorTexture),
    depthStencilAttachment: gpu.DepthStencilAttachment(texture: depthStencil),
  ),
);

For multiple color attachments, the renderer would keep using renderer owned textures for intermediate passes, then resolve or composite the final result into frame.colorTexture before presenting. A later convenience API could provide per frame transient attachments, but the core surface abstraction only needs to manage the image that will be presented to Flutter.

Rationale from related APIs:

This would let Flutter GPU keep Flutter's normal composition model while removing the fragile part from user code: guessing how many textures are needed and when a previous texture is safe to render into again.

Metadata

Metadata

Assignees

Labels

c: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Fluttere: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.flutter-gputeam-fluttergpuOwned by Flutter GPU team

Type

No type
No fields configured for issues without a type.

Projects

Status
✅ Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions