render method

Future<bool> render(
  1. RenderScene scene,
  2. RenderView view,
  3. GpuSurface surface
)

Draws scene seen from view into a new frame of surface and presents it. Completes when the GPU has finished.

Implementation

Future<bool> render(RenderScene scene, RenderView view, GpuSurface surface) {
  final caps = device.caps;
  final hdr = caps.hdrRenderTarget;
  final samples = msaa && caps.msaa ? 4 : 1;
  final width = math.max(1, view.width), height = math.max(1, view.height);
  if (surface.width != width || surface.height != height) {
    surface.resize(width, height);
  }

  device.beginFrame();
  _resetStats();
  _frame++;
  _engineViewProjection.setProduct(view.projection, view.view);
  _frustum.setFromViewProjection(_engineViewProjection);
  ClipFixup.flipY(view.projection, _gpuProjection);
  _gpuViewProjection.setProduct(_gpuProjection, view.view);

  _collect(scene, view);
  final sun = scene.sun;
  _cascades = 0;
  if (shadows.enabled && shadows.strength > 0 && sun.enabled) {
    final count = shadows.cascades.clamp(1, 2);
    _cascadeSplit = shadows.distance * shadows.cascadeSplit.clamp(0.05, 0.95);
    if (fitSunShadow(
      view,
      sun.direction,
      shadows,
      _shadowFits[0],
      to: count == 2 ? _cascadeSplit : null,
    )) {
      _cascades = 1;
      if (count == 2 &&
          fitSunShadow(
            view,
            sun.direction,
            shadows,
            _shadowFits[1],
            from: _cascadeSplit,
          )) {
        _cascades = 2;
      }
    }
  }
  // Lines only: nothing to shadow.
  if (viewMode == RenderViewMode.wireframe) _cascades = 0;
  final shadowed = _cascades > 0;
  for (final list in _casters) {
    list.clear();
  }
  for (final list in _batchCasters) {
    list.clear();
  }
  for (var c = 0; c < _cascades; c++) {
    _collectCasters(scene, _shadowFits[c], _casters[c]);
    _collectBatchCasters(scene, view, _shadowFits[c], _batchCasters[c]);
  }
  final joints = skinning == SkinningMode.texture ? _packPalettes() : null;

  final frameUniforms = device.frameData(
    _frameBlock(scene, view, toDisplay: !hdr, shadowed: shadowed),
  );
  final lightUniforms = device.frameData(_lightsBlock(scene, view));

  final frame = surface.acquire();
  final commands = device.commands();
  final maps = [
    for (var c = 0; c < _cascades; c++)
      _shadowPass(
        commands,
        joints,
        _shadowFits[c],
        _casters[c],
        _batchCasters[c],
      ),
  ];
  final bindings = _LitBindings(
    frameUniforms,
    lightUniforms,
    maps.isEmpty ? defaults.white : maps[0],
    maps.length > 1 ? maps[1] : defaults.white,
    joints,
    scene.environment.map?.texture ?? defaults.blackCube,
  );

  final env = scene.environment;
  final clear = (
    env.clearColor.$1,
    env.clearColor.$2,
    env.clearColor.$3,
    1.0,
  );
  final colorFormat = hdr ? GpuFormat.rgba16f : frame.texture.desc.format;

  final depth = targets.acquire(
    GpuTextureDesc(
      width: width,
      height: height,
      format: caps.depthFormat,
      storage: GpuStorage.transient,
      samples: samples,
      renderTarget: true,
      shaderRead: false,
    ),
  );
  // Where the scene is resolved to: an HDR texture, or the surface itself.
  final GpuTexture resolved = hdr
      ? targets.acquire(
          GpuTextureDesc(
            width: width,
            height: height,
            format: GpuFormat.rgba16f,
            renderTarget: true,
          ),
        )
      : frame.texture;
  final GpuColorTarget color = samples > 1
      ? GpuColorTarget(
          targets.acquire(
            GpuTextureDesc(
              width: width,
              height: height,
              format: colorFormat,
              storage: GpuStorage.transient,
              samples: samples,
              renderTarget: true,
              shaderRead: false,
            ),
          ),
          clear: clear,
          resolve: resolved,
          store: GpuStore.resolve,
        )
      : GpuColorTarget(resolved, clear: clear);

  final pass = commands.beginPass(
    GpuRenderTargets([color], depth: GpuDepthTarget(depth)),
  );
  final filled = viewMode != RenderViewMode.wireframe;
  if (filled) {
    _drawList(pass, _opaque, bindings, transparent: false);
    _drawBatches(pass, bindings);
  }

  if (env.sky) {
    final sky = _pipelines[RendererPipelines.sky];
    pass
      ..setPipeline(sky)
      ..setUniforms(
        'Sky',
        device.frameData(_skyBlock(sky, scene, view, toDisplay: !hdr)),
      )
      ..setTexture(
        'environment_texture',
        env.map?.texture ?? defaults.blackCube,
        sampler: GpuSampler.linearClamp,
      )
      ..setVertexBuffer(0, _fullscreen.view())
      ..setDepth(write: false, compare: GpuCompare.lessEqual)
      ..setCull(GpuCull.none)
      ..setBlend(GpuBlend.off)
      ..draw(3);
    stats.drawCalls++;
  }

  if (filled && _transparent.isNotEmpty) {
    _drawList(pass, _transparent, bindings, transparent: true);
  }
  // Lines last: a wireframe's every edge, then what is drawn over the
  // scene (a selection's highlight).
  if (!filled) _drawWireframe(pass);
  if (scene.overlays.isNotEmpty) _drawOverlays(pass, scene.overlays);

  if (hdr) {
    final glow = bloom.enabled && bloom.intensity > 0
        ? _bloom(commands, resolved, width, height)
        : null;
    final smooth = fxaa;
    final surfaceEncodes = frame.texture.desc.format.isSrgb;
    // With FXAA, tonemap into a display-encoded image first.
    final toned = smooth
        ? targets.acquire(
            GpuTextureDesc(width: width, height: height, renderTarget: true),
          )
        : frame.texture;
    final tonemap = _pipelines[RendererPipelines.tonemap];
    final block = tonemap.uniformBlock('Tonemap')!;
    final w = UniformWriter(block.sizeInBytes)
      ..vec4(
        block.offsetOf('options')!,
        env.exposure,
        smooth || !surfaceEncodes ? 1 : 0,
        glow == null ? 0 : bloom.intensity,
        0,
      );
    commands.beginPass(
        GpuRenderTargets([GpuColorTarget(toned, load: GpuLoad.dontCare)]),
      )
      ..setPipeline(tonemap)
      ..setUniforms('Tonemap', device.frameData(w.bytes))
      ..setTexture('hdr_texture', resolved, sampler: GpuSampler.nearestClamp)
      ..setTexture(
        'bloom_texture',
        glow ?? defaults.black,
        sampler: GpuSampler.linearClamp,
      )
      ..setVertexBuffer(0, _fullscreen.view())
      ..setDepth(test: false, write: false)
      ..setCull(GpuCull.none)
      ..draw(3);
    stats.drawCalls++;
    if (smooth) {
      final fx = _pipelines[RendererPipelines.fxaa];
      final fb = fx.uniformBlock('Fxaa')!;
      final f = UniformWriter(fb.sizeInBytes)
        ..vec4(
          fb.offsetOf('texel')!,
          1 / width,
          1 / height,
          surfaceEncodes ? 1 : 0,
          0,
        );
      commands.beginPass(
          GpuRenderTargets([
            GpuColorTarget(frame.texture, load: GpuLoad.dontCare),
          ]),
        )
        ..setPipeline(fx)
        ..setUniforms('Fxaa', device.frameData(f.bytes))
        ..setTexture('source', toned, sampler: GpuSampler.linearClamp)
        ..setVertexBuffer(0, _fullscreen.view())
        ..setDepth(test: false, write: false)
        ..setCull(GpuCull.none)
        ..draw(3);
      stats.drawCalls++;
    }
  }

  final done = commands.submit(present: frame);
  targets.endFrame();
  if (joints != null) {
    unawaited(
      done.then((_) {
        if (_spareJoints.length < 4) _spareJoints.add(joints);
      }, onError: (Object _) {}),
    );
  }
  return done;
}