Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
7.6 KiB
wolf_3d_flutter
Flutter integration package for the shared Wolfenstein 3D runtime.
What This Package Provides
wolf_3d_flutter layers Flutter-specific host concerns on top of wolf_3d_dart:
- high-level engine facade (
Wolf3dFlutterEngine), - Flutter input adapter,
- platform audio integration,
- renderer host widgets and runtime mode helpers,
- app/session managers and persistence adapters,
- shader-enabled rendering support.
Primary entrypoint: lib/wolf_3d_flutter.dart
Prerequisites
- Flutter SDK
- Dart SDK
^3.11.1 - Workspace dependency on
wolf_3d_dart
Setup
From this directory:
flutter pub get
Usage
Typical host initialization pattern:
final Wolf3dFlutterEngine engine = await Wolf3dFlutterEngine(
debug: kDebugMode,
).init();
init() handles platform setup, audio init, configured external discovery, and
optional seeded game injection.
To load packaged game data from wolf_3d_assets, use
Wolf3dFlutterEngine.loadGameDataFromAssets(...) and pass the result via
seededGames:
final retail = await Wolf3dFlutterEngine.loadGameDataFromAssets(
version: GameVersion.retail,
packageName: 'wolf_3d_assets',
assetDirectory: 'assets/retail',
);
final shareware = await Wolf3dFlutterEngine.loadGameDataFromAssets(
version: GameVersion.shareware,
packageName: 'wolf_3d_assets',
assetDirectory: 'assets/shareware',
);
final Wolf3dFlutterEngine engine = await Wolf3dFlutterEngine(
debug: kDebugMode,
).init(seededGames: [retail, shareware]);
The facade itself lives in lib/engine/wolf3d_flutter_engine.dart and is re-exported
through the package barrel at lib/wolf_3d_flutter.dart. External consumers
should keep importing the barrel unless they have a specific reason to target
the engine library directly.
The same pattern applies to the Flutter input adapter and the desktop
persistence adapters: they now live in focused subdirectories and are
re-exported through lib/wolf_3d_flutter.dart.
For full host wiring examples, see:
apps/wolf_3d_gui/lib/main.dart
Package Structure
lib/wolf_3d_flutter.dart— barrel export for the public Flutter package surface.lib/engine/wolf3d_flutter_engine.dart— high-level engine facade and discovery bootstrap.lib/input/— Flutter-specific input adapters.lib/persistence/— desktop persistence adapters for saves and renderer settings.lib/renderer/— renderer host widgets.lib/managers/— runtime/session/display/persistence managers.lib/audio/— platform-aware audio backends.shaders/wolf_world.frag— base fragment shader included in package configuration.shaders/wolf_world_bloom.frag— bloom-enabled fragment shader variant.
Integration Notes
- Keep UI/platform concerns in this package or app hosts, not in
wolf_3d_dart. - Use exported APIs from
lib/wolf_3d_flutter.dartrather than importing private internals fromlib/srcin dependencies. - Shader paths are declared in this package
pubspec.yamland must stay synchronized with renderer usage.
Shader Architecture And Performance Notes
This package ships two shader variants:
shaders/wolf_world.frag(base pass, no bloom taps)shaders/wolf_world_bloom.frag(bloom-enabled variant)
The renderer selects one variant in Dart based on runtime settings. This is a performance decision: when bloom is disabled, we do not run bloom sampling code at all.
No-Branch Shader Policy
For package-owned shader sources, do not use if statements. Use branchless
selection patterns (mix, step, smoothstep, mask algebra) instead.
Static check:
rg "\bif\s*\(" packages/wolf_3d_flutter/shaders
Expected result: no matches in source shader files.
Why This Is Different From Dart
If you are primarily a Dart developer, this is the key mindset shift:
- Dart code runs on CPU cores with branch prediction and comparatively cheap control flow.
- Fragment shaders run across many pixels in parallel on GPU SIMD/SIMT lanes.
- If neighboring pixels take different branches, the GPU can serialize branch paths (divergence), reducing throughput.
- Texture reads are usually more expensive than scalar ALU math. Removing bloom work entirely when disabled is often better than trying to gate it inside one shader.
In short: in Dart, if can be good structure. In fragment shaders, branchless
math and pass selection are often better for frame time.
Dart-Style Thinking vs Shader-Style Thinking
CPU/Dart style:
if (effectsEnabled) {
uv = warp(uv);
}
if (outsideScreen(uv)) {
return bezelColor;
}
Shader style used here:
vec2 effectiveUv = mix(uv, warpedUv, effectsMask);
float bezelMask = (1.0 - insideMask) * effectsMask;
vec3 outColor = mix(screenColor, bezelColor, bezelMask);
Both produce feature-equivalent behavior, but the second keeps execution paths uniform across fragments.
Shader Block Guide (What / Why)
-
UV normalization: Convert fragment coordinates to 0..1 UV so all sampling math is resolution agnostic.
-
Barrel warp: Simulates curved CRT glass by pushing UVs outward as radius increases.
-
Inside/outside mask: Computes whether warped UV remains on the emissive screen rectangle. This replaces branch-based bezel routing.
-
Edge-aware AA: Computes local luma span from N/S/E/W neighbors and blends toward neighborhood average only where contrast indicates potential aliasing.
-
CRT modulation: Applies scanlines, moving sweep, center lift, and vignette to mimic phosphor and lens behavior.
-
Bezel shading: Uses overflow distance and edge bleed sampling to build depth, inner lip, and scene-tinted glow on bezel regions.
-
Bloom variant only: Adds three-ring cross taps, brightness gating, and tone mapping. This code is in a separate shader so bloom-off mode avoids paying this cost.
Constant Tuning Reference
-
Warp factor:
0.045Higher = stronger curvature. -
AA blend ceiling:
0.45Higher = softer edges, more blur risk. -
Scanline band:
0.88 + 0.12 * sin(...)Lower floor or higher amplitude increases CRT stripe intensity. -
Sweep speed:
uTime * 0.08Higher = faster sweep line travel. -
Bloom ring radii:
3,7,13texels Larger radii spread glow farther but increase halo size. -
Bloom gain:
0.42Higher = brighter bloom before tone map. -
Tone map:
color / (color + 0.75) * 1.75Controls highlight rolloff and midtone lift.
Glossary
- UV: normalized texture coordinates in [0, 1].
- Luma: perceived brightness estimate from RGB.
- Mask: scalar 0..1 value used to blend between alternatives.
- Vignette: edge darkening effect.
- Tone map: compresses highlights into displayable range.
- Tap: one texture sample read.
- Divergence: parallel shader lanes taking different branches.
Profiling Expectations
- Bloom disabled: base shader variant runs, no bloom taps.
- Bloom enabled: bloom shader variant runs, additional texture sampling cost.
- Effects disabled: both shaders still remain branchless; effect contribution is blended out by mask values.
Troubleshooting
- No discovered game data: confirm configured/persisted data directory paths are valid.
- Desktop behavior mismatch: verify desktop windowing and audio dependencies are available on the target OS.
- Render issues after shader changes: confirm shader path and package config are still aligned.
Related Modules
- Core runtime package:
../wolf_3d_dart/README.md - GUI host app:
../../apps/wolf_3d_gui/README.md - Shared assets package:
../wolf_3d_assets/README.md - Workspace overview:
../../README.md