1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
/// Web backend: the same V code compiled to WebAssembly by emscripten and
/// driven over `dart:js_interop`.
///
/// Selected by the conditional import in `../vflutter_ffi.dart` when
/// `dart.library.js_interop` is available. The native twin is
/// `backend_native.dart`; the two files must keep the same top-level
/// signatures.
///
/// The glue is emitted by tool/build_wasm.sh into example/web/ and loaded by
/// a <script> tag in example/web/index.html, which defines the global
/// `createVFlutterModule`.
library;
import 'dart:async';
import 'dart:js_interop';
import 'dart:typed_data';
import 'fractal_view.dart';
const String backendName = 'wasm (emscripten)';
/// The module is single-threaded. Splitting into bands still works and still
/// produces the identical frame, but the bands run one after another, so the
/// "parallel" timing on web is a sequential one.
const bool supportsIsolateParallelism = false;
@JS('createVFlutterModule')
external JSFunction? get _factory;
/// The emscripten module object. Only the pieces this package uses are
/// declared; `_`-prefixed members are the C exports.
extension type _Module._(JSObject _) implements JSObject {
@JS('_malloc')
external int malloc(int bytes);
@JS('_free')
external void free(int ptr);
@JS('_vf_init')
external void vfInit();
@JS('_vf_add')
external int vfAdd(int a, int b);
@JS('_vf_greet')
external int vfGreet(int namePtr);
@JS('_vf_free')
external void vfFree(int ptr);
@JS('_vf_mandelbrot')
external void vfMandelbrot(int buf, int bufLen, int w, int h, double cx,
double cy, double scale, int maxIter, int y0, int y1);
/// Re-read on every use: emscripten replaces the view when the heap grows.
external JSUint8Array get HEAPU8;
external String UTF8ToString(int ptr);
external void stringToUTF8(String s, int ptr, int maxBytesToWrite);
external int lengthBytesUTF8(String s);
}
extension type _U8Array._(JSObject _) implements JSObject {
external JSUint8Array subarray(int begin, int end);
}
_Module? _module;
bool get isInitialized => _module != null;
Future<void>? _pending;
/// Instantiates the wasm module. Safe to call repeatedly and from several
/// places at once; the work happens once.
Future<void> initialize() {
if (_module != null) return Future<void>.value();
return _pending ??= _load();
}
Future<void> _load() async {
final factory = _factory;
if (factory == null) {
throw StateError(
'createVFlutterModule is not defined. The wasm glue is missing: run '
'`just wasm` and make sure web/index.html loads vflutter.js before '
'flutter_bootstrap.js.',
);
}
final module = await (factory.callAsFunction() as JSPromise<JSObject>).toDart;
final m = _Module._(module);
m.vfInit();
_module = m;
_pending = null;
}
_Module get _m {
final m = _module;
if (m == null) {
throw StateError(
'vflutter_ffi is not initialised on web. Await initialize() before '
'calling into V — loading a wasm module cannot be synchronous.',
);
}
return m;
}
/// Present so callers written against the native backend keep compiling. On
/// web it only asserts; it cannot do the loading, because that is async.
void ensureInitialized() => _m;
int add(int a, int b) => _m.vfAdd(a, b);
String greet(String name) {
final m = _m;
// stringToUTF8 needs room for the trailing NUL.
final size = m.lengthBytesUTF8(name) + 1;
final arg = m.malloc(size);
var res = 0;
try {
m.stringToUTF8(name, arg, size);
res = m.vfGreet(arg);
if (res == 0) {
throw StateError('vf_greet returned null');
}
return m.UTF8ToString(res);
} finally {
m.free(arg);
// V allocated the result, so V releases it — same rule as on native.
if (res != 0) m.vfFree(res);
}
}
/// No isolates on web, so this is [greet] behind a `Future`. It does not move
/// the work off the UI thread.
Future<String> greetAsync(String name) async => greet(name);
Uint8List renderBand(FractalView view, int y0, int y1) {
final m = _m;
final rows = y1 - y0;
if (rows <= 0) return Uint8List(0);
final bytes = rows * view.width * 4;
final buf = m.malloc(bytes);
try {
m.vfMandelbrot(buf, bytes, view.width, view.height, view.centerX,
view.centerY, view.scale, view.maxIter, y0, y1);
// Take a view of just this band and copy it into Dart before the wasm
// allocation is released. Reading m.HEAPU8 here rather than earlier
// matters: malloc may have grown the heap and replaced the view.
final band = _U8Array._(m.HEAPU8).subarray(buf, buf + bytes).toDart;
return Uint8List.fromList(band);
} finally {
m.free(buf);
}
}
/// Splits the frame the same way the native backend does, so the output is
/// identical — but the bands are computed one after another. See
/// [supportsIsolateParallelism].
Future<Uint8List> renderParallel(FractalView view, int tiles) async {
final count = tiles.clamp(1, view.height);
final rowsPer = (view.height / count).ceil();
final out = Uint8List(view.width * view.height * 4);
var offset = 0;
for (var t = 0; t < count; t++) {
final y0 = t * rowsPer;
final y1 = ((t + 1) * rowsPer).clamp(0, view.height);
if (y0 >= y1) break;
final band = renderBand(view, y0, y1);
out.setRange(offset, offset + band.length, band);
offset += band.length;
}
return out;
}
|