renderViews method

void renderViews(
  1. List<RenderView> views,
  2. Canvas canvas, {
  3. Rect? region,
  4. double? pixelRatio,
})

Renders a list of views of this scene onto canvas.

Each RenderView binds a camera to a normalized sub-rectangle of region (its RenderView.viewport), a RenderView.layerMask, and a compositing RenderView.order; views are drawn lowest-order first. This is how split-screen and picture-in-picture are rendered. render is the single-view convenience over this.

Each view renders into its own offscreen target (its own swapchain texture and transient texture pool), so simultaneous views never share a render target within a frame.

region is the canvas rectangle the views subdivide; it defaults to the canvas clip bounds. pixelRatio is the logical-to-physical multiplier for the offscreen render targets (defaults to the view's device pixel ratio).

The scene is advanced once per call (a single per-frame tick), then every view is rendered from that shared scene state.

Implementation

void renderViews(
  List<RenderView> views,
  ui.Canvas canvas, {
  ui.Rect? region,
  double? pixelRatio,
}) {
  if (!_readyToRender) {
    debugPrint('Flutter Scene is not ready to render. Skipping frame.');
    debugPrint(
      'You may wait on the Future returned by Scene.initializeStaticResources() before rendering.',
    );
    return;
  }

  final drawArea = region ?? canvas.getLocalClipBounds();
  if (drawArea.isEmpty || views.isEmpty) {
    return;
  }

  final dpr =
      pixelRatio ??
      ui.PlatformDispatcher.instance.implicitView?.devicePixelRatio ??
      1.0;

  // Re-bake the sky-driven environment when its refresh policy says one is
  // due. The bake submits its own passes, so like the lazy default-prefilter
  // below it must run before this frame's render graph is built.
  final skyEnv = skyEnvironment;
  if (skyEnv != null) {
    final baked = skyEnv.bakeIfDue(DateTime.now());
    if (baked != null) {
      environment = baked;
    }
  }

  // The web radiance prefilter is degenerate when built on a cold WebGL
  // context (before the first frame composites); environments built then
  // (the lazily built default below, or any the app built up front) are
  // re-baked once a frame has been presented and the context is warm. No-op
  // on other backends and after the one-time rebuild. See
  // EnvironmentMap.markContextWarmAndRebakeRadiance.
  if (_hasPresentedFrame) {
    EnvironmentMap.markContextWarmAndRebakeRadiance();
  }

  // Resolve the IBL environment up front (before building any render
  // graph): the default is built lazily here on first use, which submits
  // a one-time prefilter pass that must not be nested inside the frame's
  // render passes. Doing this in the constructor instead would break the
  // OpenGL ES backend, which sets up its context lazily on the raster
  // thread only after the first frame.
  final environmentMap = environment ?? Material.getDefaultEnvironmentMap();

  // Reuse one host buffer across frames; reset() cycles it to the next
  // frame's backing storage. Shared by every view this frame.
  final transientsBuffer = _transientsBuffer ??= gpu.gpuContext
      .createHostBuffer();
  transientsBuffer.reset();
  // Advance the instance-transform buffer pool to this frame's set (it
  // backs the instance-rate vertex buffer, separate from the uniform
  // host buffer above; see instance_packing.dart).
  instanceTransformBuffers.beginFrame();

  // Advance the scene once per frame (not once per view): tick components
  // and animations and refresh the flat render list before the passes
  // iterate it. Skipped when update() already ran the tick this frame.
  if (!_tickedThisFrame) {
    final nowMillis = DateTime.now().millisecondsSinceEpoch;
    final lastMillis = _lastTickMillis ?? nowMillis;
    _tick((nowMillis - lastMillis) / 1000.0);
  }
  _tickedThisFrame = false;

  // Rebuild the spatial culling structure once if the pre-pass changed the
  // scene, before the views' render passes query it.
  renderScene.rebuildIfDirty();

  // The renderer shades a single directional light: the first one
  // registered in the graph (the [directionalLight] convenience, or a
  // [DirectionalLightComponent] attached to any node).
  final lightComponent = renderScene.directionalLights.isEmpty
      ? null
      : renderScene.directionalLights.first;

  // Texture-target views render first so screen views (and the HUD)
  // composite this frame's captures, the simple form of the
  // produce-before-consume rule.
  // TODO(rendertarget): order texture views among themselves by
  // resource read/write edges once materials can sample render textures.
  final textureViews = <RenderView>[
    for (final view in this.views)
      if (view.target != null) view,
    for (final view in views)
      if (view.target != null) view,
  ]..sort((a, b) => a.order.compareTo(b.order));
  final now = DateTime.now();
  for (final view in textureViews) {
    final target = view.target!;
    if (!target.shouldUpdate(now)) {
      continue;
    }
    _renderViewToTexture(
      view: view,
      outputColor: target.acquireNextTexture(),
      pixelSize: ui.Size(target.width.toDouble(), target.height.toDouble()),
      pool: target.transientTexturePool,
      environmentMap: environmentMap,
      transientsBuffer: transientsBuffer,
      lightComponent: lightComponent,
    );
    target.markUpdated(now);
  }

  // Composite lower-order screen views first.
  final screenViews = [
    for (final view in views)
      if (view.target == null) view,
  ];
  final ordered = screenViews.length == 1
      ? screenViews
      : (screenViews..sort((a, b) => a.order.compareTo(b.order)));

  for (var i = 0; i < ordered.length; i++) {
    final view = ordered[i];
    final viewArea = _viewDrawArea(drawArea, view.viewport);
    if (viewArea.isEmpty) {
      continue;
    }
    _renderViewToCanvas(
      view: view,
      canvas: canvas,
      drawArea: viewArea,
      dpr: dpr,
      viewIndex: i,
      environmentMap: environmentMap,
      transientsBuffer: transientsBuffer,
      lightComponent: lightComponent,
    );
  }

  // A frame has now been submitted; the next one runs on a warm context (see
  // the rebuild near the environment resolution above).
  _hasPresentedFrame = true;
}