diff --git a/.github/workflows/periodic.yml b/.github/workflows/periodic.yml index c13e4d31c4..7d4c6fd3cf 100644 --- a/.github/workflows/periodic.yml +++ b/.github/workflows/periodic.yml @@ -49,6 +49,8 @@ jobs: run: ./v -o v2 cmd/v && ./v2 -o v3 cmd/v - name: Test symlink run: sudo ./v symlink + - name: Ensure thirdparty/cJSON/cJSON.o is compiled, before running tests. + run: ./v examples/json.v - name: Set up pg database run: | brew services start postgresql diff --git a/.gitignore b/.gitignore index 30b1928d31..434cbfb9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ info.log # vim/emacs editor backup files *~ thirdparty/freetype/ +cachegrind.out.* diff --git a/examples/.gitignore b/examples/.gitignore index 6f0b11537e..287844245b 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -13,6 +13,8 @@ /hello_v_js /fibonacci /sqlite +/path_tracing +*.ppm empty_gg_freetype game_of_life/life_gg random_ips diff --git a/examples/path_tracing.v b/examples/path_tracing.v index 11fa518cb7..5e753efa78 100644 --- a/examples/path_tracing.v +++ b/examples/path_tracing.v @@ -1,5 +1,4 @@ /********************************************************************** -* * path tracing demo * * Copyright (c) 2019-2020 Dario Deledda. All rights reserved. @@ -24,17 +23,19 @@ * in linux: ulimit -s byte_size_of_the_stack * example: ulimit -s 16000000 * - No OpenMP support -* **********************************************************************/ import os import math import rand +import time -/****************************************************************************** -* -* 3D Vector utility struct -* -******************************************************************************/ +const ( + inf = f64(1e+10) + eps = f64(1e-4) + f_0 = f64(0.0) +) + +/***************************** 3D Vector utility struct **********************/ struct Vec { mut: x f64 = f64(0.0) @@ -82,11 +83,38 @@ fn (v Vec) norm () Vec { return Vec{ v.x * tmp_norm , v.y * tmp_norm, v.z * tmp_norm } } -/****************************************************************************** -* -* Ray -* -******************************************************************************/ +/*********************************Image***************************************/ +struct Image { + width int + height int + data &Vec +} + +fn new_image(w int, h int) Image { + return Image{ + width: w, + height: h, + data: &Vec(calloc(sizeof(Vec)*w*h)) + } +} + +// write out a .ppm file +fn (image Image) save_as_ppm(file_name string) { + npixels := image.width * image.height + mut f_out := os.create(file_name) or { exit } + f_out.writeln('P3') + f_out.writeln('${image.width} ${image.height}') + f_out.writeln('255') + for i in 0..npixels { + c_r := to_int(image.data[i].x) + c_g := to_int(image.data[i].y) + c_b := to_int(image.data[i].z) + f_out.write('$c_r $c_g $c_b ') + } + f_out.close() +} + +/*********************************** Ray *************************************/ struct Ray { o Vec d Vec @@ -99,11 +127,7 @@ enum Refl_t { refr } -/****************************************************************************** -* -* Sphere -* -******************************************************************************/ +/********************************* Sphere ************************************/ struct Sphere { rad f64 = f64(0.0) // radius p Vec // position @@ -112,59 +136,52 @@ struct Sphere { refl Refl_t // reflection type => [diffuse, specular, refractive] } -[inline] fn (sp Sphere) intersect (r Ray) f64 { op := sp.p - r.o // Solve t^2*d.d + 2*t*(o-p).d + (o-p).(o-p)-R^2 = 0 - mut t := f64(0.0) - eps := f64(1e-4) b := op.dot(r.d) mut det := b * b - op.dot(op) + sp.rad * sp.rad if det < 0 { return f64(0) - } else { - det = math.sqrt(det) } - - t = b - det - if t > eps { return t } + + det = math.sqrt(det) + + mut t := b - det + if t > eps { + return t + } + t = b + det - if t > eps { return t } + if t > eps { + return t + } return f64(0) } -/****************************************************************************** -* -* Scenes -* +/*********************************** Scenes ********************************** * 0) Cornell Box with 2 spheres * 1) Sunset * 2) Psychedelic -* -* the sphere fileds are: Sphere{radius, position, emission, color, material} -* +* The sphere fileds are: Sphere{radius, position, emission, color, material} ******************************************************************************/ -const ( - +const ( Cen = Vec{50, 40.8, -860} // used by scene 1 - spheres = [ - [// scene 0 cornnel box Sphere{rad: 1e+5, p: Vec{ 1e+5 +1,40.8,81.6} , e: Vec{} , c: Vec{.75,.25,.25} , refl: .diff},//Left Sphere{rad: 1e+5, p: Vec{-1e+5 +99,40.8,81.6}, e: Vec{} , c: Vec{.25,.25,.75} , refl: .diff},//Rght Sphere{rad: 1e+5, p: Vec{50,40.8, 1e+5} , e: Vec{} , c: Vec{.75,.75,.75} , refl: .diff},//Back - Sphere{rad: 1e+5, p: Vec{50,40.8,-1e+5 +170} , e: Vec{} , c: Vec{1e-16, 1e-16, 1e-16}, refl: .diff},//Frnt + Sphere{rad: 1e+5, p: Vec{50,40.8,-1e+5 +170} , e: Vec{} , c: Vec{} , refl: .diff},//Frnt Sphere{rad: 1e+5, p: Vec{50, 1e+5, 81.6} , e: Vec{} , c: Vec{.75,.75,.75} , refl: .diff},//Botm Sphere{rad: 1e+5, p: Vec{50,-1e+5 +81.6,81.6}, e: Vec{} , c: Vec{.75,.75,.75} , refl: .diff},//Top - Sphere{rad: 16.5, p: Vec{27.0,16.5,47.0} , e: Vec{} , c: Vec{1,1,1}.mult_s(.999) , refl: .spec},//Mirr + Sphere{rad: 16.5, p: Vec{27,16.5,47} , e: Vec{} , c: Vec{1,1,1}.mult_s(.999) , refl: .spec},//Mirr Sphere{rad: 16.5, p: Vec{73,16.5,78} , e: Vec{} , c: Vec{1,1,1}.mult_s(.999) , refl: .refr},//Glas - Sphere{rad: 600 , p: Vec{50,681.6-.27,81.6} , e: Vec{12,12,12}, c: Vec{1e-16, 1e-16, 1e-16}, refl: .diff} //Lite -] + Sphere{rad: 600 , p: Vec{50,681.6-.27,81.6} , e: Vec{12,12,12}, c: Vec{}, refl: .diff} //Lite +], - -,[// scene 1 sunset - Sphere{rad: 1600, p: Vec{1.0,0.0,2.0}.mult_s(3000), e: Vec{1.0,.9,.8}.mult_s(1.2e+1*1.56*2) , c: Vec{} , refl: .diff}, // sun +[// scene 1 sunset + Sphere{rad: 1600, p: Vec{1.0,0.0,2.0}.mult_s(3000), e: Vec{1.0,.9,.8}.mult_s(1.2e+1*1.56*2) , c: Vec{} , refl: .diff}, // sun Sphere{rad: 1560, p: Vec{1,0,2}.mult_s(3500) , e: Vec{1.0,.5,.05}.mult_s(4.8e+1*1.56*2) , c: Vec{} , refl: .diff}, // horizon sun2 Sphere{rad: 10000, p: Cen+Vec{0,0,-200}, e: Vec{0.00063842, 0.02001478, 0.28923243}.mult_s(6e-2*8), c: Vec{.7,.7,1}.mult_s(.25), refl: .diff}, // sky @@ -175,10 +192,10 @@ spheres = [ Sphere{rad: 26.5, p: Vec{22,26.5,42}, e: Vec{}, c: Vec{1,1,1}.mult_s(.596) , refl: .spec}, // white Mirr Sphere{rad: 13, p: Vec{75,13,82 }, e: Vec{}, c: Vec{.96,.96,.96}.mult_s(.96), refl: .refr},// Glas Sphere{rad: 22, p: Vec{87,22,24 }, e: Vec{}, c: Vec{.6,.6,.6}.mult_s(.696) , refl: .refr} // Glas2 -] +], -,[// scene 3 Psychedelic +[// scene 3 Psychedelic Sphere{rad: 150, p: Vec{50+75,28,62}, e: Vec{1,1,1}.mult_s(0e-3), c: Vec{1,.9,.8}.mult_s(.93), refl: .refr}, Sphere{rad: 28 , p: Vec{50+5,-28,62}, e: Vec{1,1,1}.mult_s(1e+1), c: Vec{1,1,1}.mult_s(0) , refl: .diff}, Sphere{rad: 300, p: Vec{50,28,62} , e: Vec{1,1,1}.mult_s(0e-3), c: Vec{1,1,1}.mult_s(.93) , refl: .spec} @@ -188,15 +205,11 @@ spheres = [ ) -/****************************************************************************** -* -* Utility -* -******************************************************************************/ +/*********************************** Utilities *******************************/ [inline] fn clamp(x f64) f64 { - if x < f64(0.0) { return f64(0.0) } - if x > f64(1.0) { return f64(1.0) } + if x < 0 { return 0 } + if x > 1 { return 1 } return x } @@ -206,39 +219,27 @@ fn to_int(x f64) int { return int(p*f64(255.0)+f64(0.5)) } -[inline] -//fn intersect(r Ray, id1 int, scene int) (bool, f64, int){ -fn intersect(r Ray, id1 int, spheres []Sphere) (bool, f64, int){ +fn intersect(r Ray, spheres &Sphere, nspheres int) (bool, f64, int){ mut d := f64(0) - inf := f64(1e+20) - mut t := f64(1e+20) - //mut i := spheres[scene].len-1 - mut i := spheres.len-1 - mut id := id1 - for i >= 0 { - //d = spheres[scene][i].intersect(r) + mut t := inf + mut id := 0 + for i:=nspheres-1; i >= 0; i-- { d = spheres[i].intersect(r) - if d != 0.0 && d < t { + if d > 0 && d < t { t = d id = i } - i-- } return (t < inf) , t, id } // some casual random function, try to avoid the 0 -[inline] fn rand_f64() f64 { - x := (C.rand()+1) & 0x3FFF_FFFF - return f64(x)/f64(0x3FFF_FFFF) + x := (C.rand()+1) & 0x3FFF_FFFF + return f64(x)/f64(0x3FFF_FFFF) } -/****************************************************************************** -* -* Cache for sin/cos speed-up table and scene selector -* -******************************************************************************/ + const( cache_len = 65536 // the 2*pi angle will be splitted in 65536 part cache_mask = cache_len - 1 // mask to speed-up the module process @@ -246,243 +247,236 @@ const( struct Cache { mut: - scene int = 0 sin_tab [cache_len]f64 cos_tab [cache_len]f64 } -fn (c mut Cache) fill() { - inv_len := 1.0 / f64(cache_len) +fn new_tabs() Cache { + mut c := Cache{} + inv_len := f64(1.0) / f64(cache_len) for i in 0..cache_len { x := f64(i) * math.pi * 2.0 * inv_len c.sin_tab[i] = math.sin(x) c.cos_tab[i] = math.cos(x) } + return c } +/************* Cache for sin/cos speed-up table and scene selector ***********/ +const ( + tabs = new_tabs() +) -/****************************************************************************** -* -* main function for the radiance calculation -* -******************************************************************************/ -fn radiance(r Ray, depthi int, tb &Cache) Vec { - mut depth := depthi // actual depth in the reflection tree - mut t := f64(0) // distance to intersection - mut id := 0 // id of intersected object - mut res := false // result of intersect - - v_1 := f64(1.0) - //v_2 := f64(2.0) - - //res, t, id = intersect(r, id, tb.scene) - res, t, id = intersect(r, id, spheres[tb.scene]) - if !res { return Vec{} } //if miss, return black - - obj := spheres[tb.scene][id] // the hit object - - x := r.o + r.d.mult_s(t) - n := (x - obj.p).norm() - - mut nl := n - if n.dot(r.d) >= 0.0 { - nl = n.mult_s(-1) - } - - mut f := obj.c - - // max reflection - mut p := f.z - if f.x > f.y && f.x > f.z { - p = f.x - } else { - if f.y > f.z { - p = f.y - } - } - - depth++ - if depth > 5 { - if rand_f64() < p { - f = f.mult_s(1.0/p) - } else { - return obj.e //R.R. - } - } - - if obj.refl == .diff { // Ideal DIFFUSE reflection - // **Full Precision** - //r1 := f64(2.0 * math.pi) * rand_f64() - - // tabbed speed-up - r1 := C.rand() & cache_mask - - r2 := rand_f64() - r2s := math.sqrt(r2) - - w := nl - - mut u := Vec{1, 0, 0} - if math.abs(w.x) > 0.1 { - u = Vec{0, 1, 0} - } - u = u.cross(w) - u = u.norm() - - v := w.cross(u) - - // **Full Precision** - //d := (u.mult_s(math.cos(r1) * r2s) + v.mult_s(math.sin(r1) * r2s) + w.mult_s(1.0 - r2)).norm() - - // tabbed speed-up - d := (u.mult_s(tb.cos_tab[r1] * r2s) + v.mult_s(tb.sin_tab[r1] * r2s) + w.mult_s(1.0 - r2)).norm() - - return obj.e + (f * radiance(Ray{x, d}, depth, tb)) - } else { - if obj.refl == .spec { // Ideal SPECULAR reflection - return obj.e + (f * radiance(Ray{x, r.d - n.mult_s(2.0 * n.dot(r.d)) }, depth, tb)) - } - } - - refl_ray := Ray{x, r.d - n.mult_s(2.0 * n.dot(r.d))} // Ideal dielectric REFRACTION - into := n.dot(nl) > 0.0 // Ray from outside going in? - - nc := f64(1.0) - nt := f64(1.5) - - mut nnt := nt / nc - if into { nnt = nc / nt } - - ddn := r.d.dot(nl) - mut cos2t:= f64(0) - cos2t = v_1 - nnt * nnt * (v_1 - ddn * ddn) - - if cos2t < 0.0 { // Total internal reflection - return obj.e + (f * radiance(refl_ray, depth, tb)) - } - - mut dirc := -1 - if into { dirc = 1 } - tdir := r.d.mult_s(nnt) -n.mult_s(dirc).mult_s(ddn * nnt + math.sqrt(cos2t)).norm() - - a := nt - nc - b := nt + nc - r0 := a * a / (b * b) - mut c := v_1 - tdir.dot(n) - if into { c = v_1 + ddn } - - re := r0 + (v_1 - r0) * c * c * c * c * c - tr := v_1 - re - p = f64(.25) + f64(.5) * re - rp := re / p - tp := tr / (v_1 - p) - - mut res_f := obj.e - - mut tmp := radiance(Ray{x, tdir}, depth, tb).mult_s(tp) - if rand_f64() < p { - tmp = radiance(refl_ray, depth, tb).mult_s(rp) +/******************* main function for the radiance calculation **************/ +fn radiance(r Ray, depthi int, scene_id int) Vec { + sin_tab := &f64( tabs.sin_tab ) + cos_tab := &f64( tabs.cos_tab ) + mut depth := depthi // actual depth in the reflection tree + mut t := f64(0) // distance to intersection + mut id := 0 // id of intersected object + mut res := false // result of intersect + + v_1 := f64(1.0) + //v_2 := f64(2.0) + + scene := spheres[scene_id] + //res, t, id = intersect(r, id, tb.scene) + res, t, id = intersect(r, scene.data, scene.len) + if !res { return Vec{} } //if miss, return black + + obj := scene[id] // the hit object + + x := r.o + r.d.mult_s(t) + n := (x - obj.p).norm() + + nl := if n.dot(r.d) < 0.0 { n } else { n.mult_s(-1) } + + mut f := obj.c + + // max reflection + mut p := f.z + if f.x > f.y && f.x > f.z { + p = f.x + } else { + if f.y > f.z { + p = f.y + } } - + + depth++ + if depth > 5 { + if rand_f64() < p { + f = f.mult_s(f64(1.0)/p) + } else { + return obj.e //R.R. + } + } + + if obj.refl == .diff { // Ideal DIFFUSE reflection + // **Full Precision** + //r1 := f64(2.0 * math.pi) * rand_f64() + + // tabbed speed-up + r1 := C.rand() & cache_mask + + r2 := rand_f64() + r2s := math.sqrt(r2) + + w := nl + + mut u := if math.abs(w.x) > f64(0.1) { + Vec{0, 1, 0} + } else { + Vec{1, 0, 0} + } + u = u.cross(w).norm() + + v := w.cross(u) + + // **Full Precision** + //d := (u.mult_s(math.cos(r1) * r2s) + v.mult_s(math.sin(r1) * r2s) + w.mult_s(1.0 - r2)).norm() + + // tabbed speed-up + d := (u.mult_s(cos_tab[r1] * r2s) + v.mult_s(sin_tab[r1] * r2s) + w.mult_s(math.sqrt(f64(1.0) - r2))).norm() + + return obj.e + f * radiance(Ray{x, d}, depth, scene_id) + } else { + if obj.refl == .spec { // Ideal SPECULAR reflection + return obj.e + f * radiance(Ray{x, r.d - n.mult_s(2.0 * n.dot(r.d)) }, depth, scene_id) + } + } + + refl_ray := Ray{x, r.d - n.mult_s(2.0 * n.dot(r.d))} // Ideal dielectric REFRACTION + into := n.dot(nl) > 0 // Ray from outside going in? + + nc := f64(1.0) + nt := f64(1.5) + + nnt := if into { nc / nt } else { nt / nc } + + ddn := r.d.dot(nl) + cos2t := v_1 - nnt * nnt * (v_1 - ddn * ddn) + if cos2t < 0.0 { // Total internal reflection + return obj.e + f * radiance(refl_ray, depth, scene_id) + } + + dirc := if into { f64(1) } else { f64(-1) } + tdir := (r.d.mult_s(nnt) - n.mult_s(dirc * (ddn * nnt + math.sqrt(cos2t)))).norm() + + a := nt - nc + b := nt + nc + r0 := a * a / (b * b) + c := if into { v_1 + ddn } else { v_1 - tdir.dot(n) } + + re := r0 + (v_1 - r0) * c * c * c * c * c + tr := v_1 - re + pp := f64(.25) + f64(.5) * re + rp := re / pp + tp := tr / (v_1 - pp) + + mut tmp := Vec{} if depth > 2 { - res_f = res_f + f * tmp - return res_f + // Russian roulette + tmp = if rand_f64() < pp { + radiance(refl_ray, depth, scene_id).mult_s(rp) + } else { + radiance(Ray{x, tdir}, depth, scene_id).mult_s(tp) + } + } else { + tmp = (radiance(refl_ray, depth, scene_id).mult_s(re)) + (radiance( Ray{x, tdir}, depth, scene_id).mult_s(tr)) } + return obj.e + (f * tmp) +} - tmp1 := radiance(refl_ray, depth, tb).mult_s(re) + radiance( Ray{x, tdir}, depth, tb).mult_s(tr) - res_f = res_f + f * tmp1 - return res_f -} - -/****************************************************************************** -* -* beam scan routine -* -******************************************************************************/ -fn ray_trace(w int, h int, samps int, file_name string, tb &Cache) { - +/************************ beam scan routine **********************************/ +fn ray_trace(w int, h int, samps int, file_name string, scene_id int) Image { + image := new_image(w, h) + // inverse costants w1 := f64(1.0 / w) h1 := f64(1.0 / h) samps1 := f64(1.0 / samps) - - cam := Ray{Vec{50, 52, 296.5}, Vec{0, -0.042612, -1}.norm()} // cam position, direction - cx := Vec{ f64(w) * .5135 / f64(h), 0, 0} - cy := ((cx.cross(cam.d)).norm()).mult_s(0.5135) - mut c := [Vec{}].repeat(w * h) + + cam := Ray{Vec{50, 52, 295.6}, Vec{0, -0.042612, -1}.norm()} // cam position, direction + cx := Vec{ f64(w) * 0.5135 / f64(h), 0, 0} + cy := cx.cross(cam.d).norm().mult_s(0.5135) mut r := Vec{} + // speed-up constants + v_1 := f64(1.0) + v_2 := f64(2.0) + // OpenMP injection point! #pragma omp parallel for schedule(dynamic, 1) shared(c) for y:=0; y < h; y++ { - eprint("\rRendering (${samps * 4} spp) ${(100.0 * f64(y)) / (f64(h) - 1.0)}%") + eprint("\rRendering (${samps * 4} spp) ${(100.0 * f64(y)) / (f64(h) - 1.0):5.2f}%") for x := 0; x < w; x++ { - + i := (h - y - 1) * w + x + mut ivec := &image.data[i] // we use sx and sy to perform a square subsampling of 4 samples - for sy := f64(0.5) ; sy < 2.5; sy += 1.0 { - for sx := f64(0.5); sx < 2.5; sx += 1.0 { - r.x = 0 - r.y = 0 - r.z = 0 + for sy := 0; sy < 2; sy ++ { + for sx := 0; sx < 2; sx ++ { + r = Vec{0,0,0} for s := 0; s < samps; s++ { - // speed-up constants - v_1 := f64(1.0) - v_2 := f64(2.0) - r1 := v_2 * rand_f64() - mut dx := v_1 - math.sqrt(v_2 - r1) - if r1 < v_1 { dx = math.sqrt(r1) - v_1 } - + dx := if r1 < v_1 { math.sqrt(r1) - v_1 } else { v_1 - math.sqrt(v_2 - r1) } + r2 := v_2 * rand_f64() - mut dy := v_1 - math.sqrt(v_2 - r2) - if r2 < v_1 { dy = math.sqrt(r2) - v_1 } - - d := cx.mult_s( ( (sx + dx)*0.5 + f64(x))*w1 - .5) + - cy.mult_s( ( (sy + dy)*0.5 + f64(y))*h1 - .5) + cam.d - - r = r + radiance(Ray{cam.o+d.mult_s(140.0), d.norm()}, 0, tb).mult_s(samps1) - + dy := if r2 < v_1 { math.sqrt(r2) - v_1 } else { v_1 - math.sqrt(v_2 - r2) } + + d := cx.mult_s( ( (f64(sx) + 0.5 + dx)*0.5 + f64(x))*w1 - .5) + + cy.mult_s( ( (f64(sy) + 0.5 + dy)*0.5 + f64(y))*h1 - .5) + cam.d + r = r + radiance(Ray{cam.o+d.mult_s(140.0), d.norm()}, 0, scene_id).mult_s(samps1) } tmp_vec := Vec{clamp(r.x),clamp(r.y),clamp(r.z)}.mult_s(.25) - c[i] = c[i] + tmp_vec + *ivec = *ivec + tmp_vec } } } } - eprintln('\nRendering finished.') - - // - // write out a .ppm file - // - mut f_out := os.create(file_name) or { exit } - f_out.writeln('P3') - f_out.writeln('${w} ${h}') - f_out.writeln('255') - for i in 0..w*h { - c_r := to_int(c[i].x) - c_g := to_int(c[i].y) - c_b := to_int(c[i].z) - f_out.write('$c_r $c_g $c_b ') - } - f_out.close() - - println("image saved as [${file_name}]") + return image } fn main() { + if os.args.len > 6 { + eprintln('Usage:\n path_tracing [samples] [image.ppm] [scene_n] [width] [height]') + exit(1) + } + mut width := 320 // width of the rendering in pixels + mut height := 200 // height of the rendering in pixels + mut samples := 4 // number of samples per pixel, increase for better quality + mut scene_id := 0 // scene to render [0 cornell box,1 sunset,2 psyco] + mut file_name := 'image.ppm' // name of the output file in .ppm format + + if os.args.len >= 2 { + samples = os.args[1].int() / 4 + } + if os.args.len >= 3 { + file_name = os.args[2] + } + if os.args.len >= 4 { + scene_id = os.args[3].int() + } + if os.args.len >= 5 { + width = os.args[4].int() + } + if os.args.len == 6 { + height = os.args[5].int() + } + // init the rand, using the same seed allows to obtain the same result in different runs // change the seed from 2020 for different results - rand.seed(2020) + rand.seed(2020) - // init the sin/cos cache table - mut tb := Cache{} - tb.fill() + t1:=time.ticks() - width := 1280 // width of the rendering in pixels - height := 1280 // height of the rendering in pixels - samples := 10 // number of samples*4 per pixel, increase for better quality - tb.scene = 1 // scene to render [0 cornell box,1 sunset,2 psyco] - file_name := "image.ppm" // name of the output file in .ppm format - - ray_trace(width, height, samples, file_name, tb) + image := ray_trace(width, height, samples, file_name, scene_id) + t2:=time.ticks() + + eprintln('\nRendering finished. Took: ${t2-t1:5d}ms') + + image.save_as_ppm( file_name ) + t3:=time.ticks() + + eprintln('Image saved as [${file_name}]. Took: ${t3-t2:5d}ms') } diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 5779cd1a08..170524e32e 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -116,8 +116,10 @@ pub fn (a mut array) sort_with_compare(compare voidptr) { // a.insert(0, &i) // ---------------------------- pub fn (a mut array) insert(i int, val voidptr) { - if i < 0 || i > a.len { - panic('array.insert: index out of range (i == $i, a.len == $a.len)') + $if !no_bounds_checking? { + if i < 0 || i > a.len { + panic('array.insert: index out of range (i == $i, a.len == $a.len)') + } } a.ensure_cap(a.len + 1) size := a.element_size @@ -135,8 +137,10 @@ pub fn (a mut array) prepend(val voidptr) { // array.delete deletes array element at the given index pub fn (a mut array) delete(i int) { - if i < 0 || i >= a.len { - panic('array.delete: index out of range (i == $i, a.len == $a.len)') + $if !no_bounds_checking? { + if i < 0 || i >= a.len { + panic('array.delete: index out of range (i == $i, a.len == $a.len)') + } } size := a.element_size C.memmove(a.data + i * size, a.data + (i + 1) * size, (a.len - i) * size) @@ -150,24 +154,30 @@ pub fn (a mut array) clear() { // Private function. Used to implement array[] operator fn (a array) get(i int) voidptr { - if i < 0 || i >= a.len { - panic('array.get: index out of range (i == $i, a.len == $a.len)') + $if !no_bounds_checking? { + if i < 0 || i >= a.len { + panic('array.get: index out of range (i == $i, a.len == $a.len)') + } } return a.data + i * a.element_size } // array.first returns the first element of the array pub fn (a array) first() voidptr { - if a.len == 0 { - panic('array.first: array is empty') + $if !no_bounds_checking? { + if a.len == 0 { + panic('array.first: array is empty') + } } return a.data + 0 } // array.last returns the last element of the array pub fn (a array) last() voidptr { - if a.len == 0 { - panic('array.last: array is empty') + $if !no_bounds_checking? { + if a.len == 0 { + panic('array.last: array is empty') + } } return a.data + (a.len - 1) * a.element_size } @@ -176,9 +186,11 @@ pub fn (a array) last() voidptr { // array.left returns a new array using the same buffer as the given array // with the first `n` elements of the given array. fn (a array) left(n int) array { - if n < 0 { - panic('array.left: index is negative (n == $n)') - } +// $if !no_bounds_checking? { +// if n < 0 { +// panic('array.left: index is negative (n == $n)') +// } +// } if n >= a.len { return a.slice(0, a.len) } @@ -190,9 +202,11 @@ fn (a array) left(n int) array { // If `n` is bigger or equal to the length of the given array, // returns an empty array of the same type as the given array. fn (a array) right(n int) array { - if n < 0 { - panic('array.right: index is negative (n == $n)') - } +// $if !no_bounds_checking? { +// if n < 0 { +// panic('array.right: index is negative (n == $n)') +// } +// } if n >= a.len { return new_array(0, 0, a.element_size) } @@ -206,14 +220,16 @@ fn (a array) right(n int) array { // set to the number of the elements in the slice. fn (a array) slice(start, _end int) array { mut end := _end - if start > end { - panic('array.slice: invalid slice index ($start > $end)') - } - if end > a.len { - panic('array.slice: slice bounds out of range ($end >= $a.len)') - } - if start < 0 { - panic('array.slice: slice bounds out of range ($start < 0)') + $if !no_bounds_checking? { + if start > end { + panic('array.slice: invalid slice index ($start > $end)') + } + if end > a.len { + panic('array.slice: slice bounds out of range ($end >= $a.len)') + } + if start < 0 { + panic('array.slice: slice bounds out of range ($start < 0)') + } } l := end - start res := array{ @@ -249,14 +265,16 @@ pub fn (a array) clone() array { fn (a array) slice_clone(start, _end int) array { mut end := _end - if start > end { - panic('array.slice: invalid slice index ($start > $end)') - } - if end > a.len { - panic('array.slice: slice bounds out of range ($end >= $a.len)') - } - if start < 0 { - panic('array.slice: slice bounds out of range ($start < 0)') + $if !no_bounds_checking? { + if start > end { + panic('array.slice: invalid slice index ($start > $end)') + } + if end > a.len { + panic('array.slice: slice bounds out of range ($end >= $a.len)') + } + if start < 0 { + panic('array.slice: slice bounds out of range ($start < 0)') + } } l := end - start res := array{ @@ -270,8 +288,10 @@ fn (a array) slice_clone(start, _end int) array { // Private function. Used to implement assigment to the array element. fn (a mut array) set(i int, val voidptr) { - if i < 0 || i >= a.len { - panic('array.set: index out of range (i == $i, a.len == $a.len)') + $if !no_bounds_checking? { + if i < 0 || i >= a.len { + panic('array.set: index out of range (i == $i, a.len == $a.len)') + } } C.memcpy(a.data + a.element_size * i, val, a.element_size) } diff --git a/vlib/builtin/float.v b/vlib/builtin/float.v index abd3438aa8..d0a9d3ddea 100644 --- a/vlib/builtin/float.v +++ b/vlib/builtin/float.v @@ -33,20 +33,25 @@ pub fn (x f64) strlong() string { return tmpstr } +[inline] fn f32_abs(a f32) f32 { return if a < 0 { -a } else { a } } +[inline] fn f64_abs(a f64) f64 { return if a < 0 { -a } else { a } } // compare floats using C epsilon // == + +[inline] pub fn (a f64) eq(b f64) bool { return f64_abs(a - b) <= C.DBL_EPSILON } +[inline] pub fn (a f32) eq(b f32) bool { return f32_abs(a - b) <= C.FLT_EPSILON } diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index b9297f3f72..69bb5338dd 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -518,8 +518,10 @@ fn (s string) substr2(start, _end int, end_max bool) string { } pub fn (s string) substr(start, end int) string { - if start > end || start > s.len || end > s.len || start < 0 || end < 0 { - panic('substr($start, $end) out of bounds (len=$s.len)') + $if !no_bounds_checking? { + if start > end || start > s.len || end > s.len || start < 0 || end < 0 { + panic('substr($start, $end) out of bounds (len=$s.len)') + } } len := end - start mut res := string{ @@ -1043,8 +1045,10 @@ pub fn (u ustring) count(substr ustring) int { } pub fn (u ustring) substr(_start, _end int) string { - if _start > _end || _start > u.len || _end > u.len || _start < 0 || _end < 0 { - panic('substr($_start, $_end) out of bounds (len=$u.len)') + $if !no_bounds_checking? { + if _start > _end || _start > u.len || _end > u.len || _start < 0 || _end < 0 { + panic('substr($_start, $_end) out of bounds (len=$u.len)') + } } end := if _end >= u.len { u.s.len } else { u.runes[_end] } return u.s.substr(u.runes[_start], end) @@ -1065,15 +1069,19 @@ pub fn (u ustring) right(pos int) string { } fn (s string) at(idx int) byte { - if idx < 0 || idx >= s.len { - panic('string index out of range: $idx / $s.len') + $if !no_bounds_checking? { + if idx < 0 || idx >= s.len { + panic('string index out of range: $idx / $s.len') + } } return s.str[idx] } pub fn (u ustring) at(idx int) string { - if idx < 0 || idx >= u.len { - panic('string index out of range: $idx / $u.runes.len') + $if !no_bounds_checking? { + if idx < 0 || idx >= u.len { + panic('string index out of range: $idx / $u.runes.len') + } } return u.substr(idx, idx + 1) } diff --git a/vlib/compiler/cheaders.v b/vlib/compiler/cheaders.v index c2e8a629b7..b3e52f816b 100644 --- a/vlib/compiler/cheaders.v +++ b/vlib/compiler/cheaders.v @@ -140,6 +140,25 @@ $c_common_macros #define DEFAULT_LE(a, b) (a <= b) #define DEFAULT_GT(a, b) (a > b) #define DEFAULT_GE(a, b) (a >= b) + +// NB: macro_fXX_eq and macro_fXX_ne are NOT used +// in the generated C code. They are here just for +// completeness/testing. + +#define macro_f64_eq(a, b) (a == b) +#define macro_f64_ne(a, b) (a != b) +#define macro_f64_lt(a, b) (a < b) +#define macro_f64_le(a, b) (a <= b) +#define macro_f64_gt(a, b) (a > b) +#define macro_f64_ge(a, b) (a >= b) + +#define macro_f32_eq(a, b) (a == b) +#define macro_f32_ne(a, b) (a != b) +#define macro_f32_lt(a, b) (a < b) +#define macro_f32_le(a, b) (a <= b) +#define macro_f32_gt(a, b) (a > b) +#define macro_f32_ge(a, b) (a >= b) + //================================== GLOBALS =================================*/ byte g_str_buf[1024]; int load_so(byteptr); diff --git a/vlib/compiler/expression.v b/vlib/compiler/expression.v index 353a10be1e..eaf76fff16 100644 --- a/vlib/compiler/expression.v +++ b/vlib/compiler/expression.v @@ -217,6 +217,10 @@ fn (p mut Parser) bterm() string { if is_float && p.cur_fn.name != 'f32_abs' && p.cur_fn.name != 'f64_abs' { p.gen(')') match tok { + // NB: For more precision/stability, the == and != float + // comparisons are done with V functions that use the epsilon + // constants for the given type. + // Everything else uses native comparisons (C macros) for speed. .eq { p.cgen.set_placeholder(ph, '${expr_type}_eq(') } @@ -224,16 +228,16 @@ fn (p mut Parser) bterm() string { p.cgen.set_placeholder(ph, '${expr_type}_ne(') } .le { - p.cgen.set_placeholder(ph, '${expr_type}_le(') + p.cgen.set_placeholder(ph, 'macro_${expr_type}_le(') } .ge { - p.cgen.set_placeholder(ph, '${expr_type}_ge(') + p.cgen.set_placeholder(ph, 'macro_${expr_type}_ge(') } .gt { - p.cgen.set_placeholder(ph, '${expr_type}_gt(') + p.cgen.set_placeholder(ph, 'macro_${expr_type}_gt(') } .lt { - p.cgen.set_placeholder(ph, '${expr_type}_lt(') + p.cgen.set_placeholder(ph, 'macro_${expr_type}_lt(') } else { }}