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
|
module main
import math
// ---------------------------------------------------------------------------
// C-ABI surface consumed by Dart FFI.
//
// Rules for everything below:
// * only C-compatible types cross the boundary (int, f64, &char, voidptr)
// * V strings/arrays/options/sumtypes never cross; convert first
// * anything V allocates and hands out is released by vf_free
// ---------------------------------------------------------------------------
// Touches the V runtime so the GC and global initialisers are demonstrably
// live. The ELF/Mach-O constructor emitted by `v -shared` already does this;
// this exists for static-archive builds (iOS) where the caller wants a
// guaranteed, idempotent entry point.
@[export: 'vf_init']
fn vf_init() {
probe := 'vf'
_ = probe.len
}
@[export: 'vf_add']
fn vf_add(a int, b int) int {
return a + b
}
// Returns a V-allocated C string. Caller releases it with vf_free.
//
// Built with `-gc none`, so every intermediate V allocation here must be
// freed by hand. Only the returned buffer outlives the call.
@[export: 'vf_greet']
fn vf_greet(name &char) &char {
n := unsafe { cstring_to_vstring(name) }
res := 'Hello, ${n}, from V!'
out := unsafe { res.str }
unsafe { n.free() }
return out
}
@[export: 'vf_free']
fn vf_free(p voidptr) {
unsafe { free(p) }
}
// ---------------------------------------------------------------------------
// Mandelbrot kernel.
//
// The buffer is allocated and owned by the *caller*: V only fills it. That
// sidesteps the -gc none ownership rules entirely for bulk data — there is
// nothing to vf_free, and no allocation happens inside the hot loop.
//
// Rows [y0, y1) of a w x h image are written as RGBA8888, packed from the
// start of `buf`, so each call fills a self-contained horizontal band and
// several bands can be computed concurrently from different isolates.
// ---------------------------------------------------------------------------
@[export: 'vf_mandelbrot']
fn vf_mandelbrot(buf &u8, w int, h int, cx f64, cy f64, scale f64, max_iter int, y0 int, y1 int) {
if w <= 0 || h <= 0 || max_iter <= 0 {
return
}
aspect := f64(w) / f64(h)
inv_w := 1.0 / f64(w)
inv_h := 1.0 / f64(h)
for y := y0; y < y1; y++ {
im := cy + (f64(y) * inv_h - 0.5) * scale
row := (y - y0) * w * 4
for x := 0; x < w; x++ {
re := cx + (f64(x) * inv_w - 0.5) * scale * aspect
mut zr := 0.0
mut zi := 0.0
mut zr2 := 0.0
mut zi2 := 0.0
mut i := 0
for i < max_iter {
zr2 = zr * zr
zi2 = zi * zi
if zr2 + zi2 > 4.0 {
break
}
zi = 2.0 * zr * zi + im
zr = zr2 - zi2 + re
i++
}
idx := row + x * 4
if i >= max_iter {
// Inside the set.
unsafe {
buf[idx] = u8(0)
buf[idx + 1] = u8(0)
buf[idx + 2] = u8(0)
buf[idx + 3] = u8(255)
}
continue
}
// Smooth (fractional) escape count, so bands don't posterise.
mag := math.sqrt(zr2 + zi2)
mut nu := f64(i)
if mag > 1.0 {
nu = f64(i) + 1.0 - math.log(math.log(mag) / math.log(2.0)) / math.log(2.0)
}
t := nu / f64(max_iter)
unsafe {
buf[idx] = u8(255.0 * (0.5 + 0.5 * math.sin(3.0 + t * 18.0)))
buf[idx + 1] = u8(255.0 * (0.5 + 0.5 * math.sin(3.6 + t * 18.0)))
buf[idx + 2] = u8(255.0 * (0.5 + 0.5 * math.sin(4.2 + t * 18.0)))
buf[idx + 3] = u8(255)
}
}
}
}
|