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) } } } }