render method
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;
}