copyToCanvas method

void copyToCanvas(
  1. TextureHandle frame
)

Copies frame into the canvas the browser composites.

WebGpuFramePresenter in this same package calls this, then applyCanvasStyle, before building the HtmlElementView that shows viewType.

A copy rather than a render, because getCurrentTexture is valid only for the task it was asked in and cannot be held across an await. The engine's target already exists and the copy is one command on an encoder submitted immediately — the same one GPU copy the WebGL2 backend's presenting blit is, arrived at from the other side.

Implementation

void copyToCanvas(TextureHandle frame) {
  // **The canvas takes the frame's size, and the clamp below is no longer the
  // thing that reconciles them.** A frame is as big as the surface asked for
  // — `SceneSurface` renders at the layout size times the device pixel ratio
  // — while the canvas was made once, at whatever `openDevice` was given.
  // Those are different numbers on any display with a ratio above one, and a
  // texture-to-texture copy cannot scale: it took the frame's top-left corner
  // the size of the canvas, and CSS then stretched that corner over the whole
  // element. What the player saw was a magnified crop whose optical centre
  // sat below and to the right of the middle of the screen — so a shot down
  // the camera's axis landed there rather than under the crosshair, which is
  // drawn at the centre by Flutter.
  //
  // Resizing the canvas is what makes the copy one-to-one; the scaling is
  // CSS's, which is what `objectFit` in [applyCanvasStyle] has always been
  // for. The WebGPU context keeps its configuration across a resize and
  // hands back a texture of the new size, so nothing has to be
  // reconfigured here.
  if (_canvas.width != frame.width || _canvas.height != frame.height) {
    _canvas
      ..width = frame.width
      ..height = frame.height;
  }
  final target = _context.getCurrentTexture();
  final source = frame.backend as WebGpuTexture;
  final width = frame.width < target.width ? frame.width : target.width;
  final height = frame.height < target.height ? frame.height : target.height;
  guard('the copy that presents a frame', () {
    final encoder = gpuDevice.createCommandEncoder()
      ..copyTextureToTexture(
        GPUTexelCopyTextureInfo(
          texture: source.texture,
          mipLevel: 0,
          origin: GPUOrigin3DDict(x: 0, y: 0, z: 0),
          aspect: 'all',
        ),
        GPUTexelCopyTextureInfo(
          texture: target,
          mipLevel: 0,
          origin: GPUOrigin3DDict(x: 0, y: 0, z: 0),
          aspect: 'all',
        ),
        GPUExtent3DDict(width: width, height: height, depthOrArrayLayers: 1),
      );
    gpuDevice.queue.submit(<GPUCommandBuffer>[encoder.finish()].toJS);
  });
}