initial commit of wasm worker for sla rendering

This commit is contained in:
Stewart Allen 2020-05-01 22:12:42 -04:00
commit 027430e7a7
4 changed files with 336 additions and 81 deletions

View file

@ -609,61 +609,21 @@ let gs_kiri_sla = exports;
layermax = Math.max(widget.slices.length); layermax = Math.max(widget.slices.length);
}); });
function polyout(poly, ctx) { let render = renderLayer;
poly.forEachPoint((p,i) => { // let render = renderLayerWasm;
if (i === 0) {
ctx.moveTo(height - (p.y * scaleY + height2), p.x * scaleX + width2);
} else {
ctx.lineTo(height - (p.y * scaleY + height2), p.x * scaleX + width2);
}
}, true);
ctx.closePath();
}
// generate layer 8-bit bitaps using canvas // generate layer 8-bit bitaps using canvas
for (let index=0; index < layermax; index++) { for (let index=0; index < layermax; index++) {
let layer = new OffscreenCanvas(height,width); let param = { index, width, height, widgets, scaleX, scaleY };
let ctx = layer.getContext('2d'); let {data, end} = render(param);
ctx.fillStyle = 'rgb(200, 0, 0)'; images.push(data);
let count = 0;
widgets.forEach(widget => {
let slice = widget.slices[index];
if (slice) {
// prevent premature exit on empty synth slice
if (slice.synth) count++;
let polys = slice.solids.unioned;
if (!polys) polys = slice.tops.map(t => t.poly);
if (slice.supports) polys.appendAll(slice.supports);
polys.forEach(poly => {
poly.move(widget.track.pos);
ctx.beginPath();
polyout(poly.setClockwise(), ctx);
if (poly.inner) {
poly.inner.forEach(inner => {
polyout(inner.setCounterClockwise(), ctx);
});
}
ctx.fill();
count++;
});
} else {
// console.log({no_slice_at: index})
}
});
let data = ctx.getImageData(0,0,height,width).data;
// reduce RGBA to R
let red = new Uint8ClampedArray(data.length / 4);
for (let i=0; i<red.length; i++) {
red[i] = data[i*4];
}
images.push(red);
// transfer images to browser main // transfer images to browser main
online({ online({
progress: (index / layermax) * 0.25, progress: (index / layermax) * 0.25,
message: "image_gen", message: "image_gen",
data: red data: data
}); });
if (count === 0) break; if (end) break;
} }
let exp_func; let exp_func;
@ -1262,12 +1222,10 @@ let gs_kiri_sla = exports;
} }
} }
})();
class DataWriter { class DataWriter {
constructor(view) { constructor(view, pos) {
this.view = view; this.view = view;
this.pos = 0; this.pos = pos || 0;
} }
skip(v) { skip(v) {
@ -1306,3 +1264,159 @@ class DataWriter {
return this.skip(8); return this.skip(8);
} }
} }
// load wasm renderer in worker context
if (!self.window) {
fetch('/wasm/kiri-sla.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, {
env: {
last: (a,b) => { console.log('last',a,b) },
report: (a,b) => { console.log('report',a,b)
}}
}))
.then(results => {
let {module, instance} = results;
let {exports} = instance;
let heap = new Uint8Array(exports.memory.buffer);
self.wasm = {
heap,
memory: exports.memory,
render: exports.render
};
// console.log({heap});
});
self.renderLayerWasm = function renderLayer(params) {
let { width, height, index, widgets, scaleX, scaleY } = params;
let width2 = width / 2, height2 = height / 2;
let array = [];
let count = 0;
let wasm = self.wasm;
function scaleMovePoly(poly) {
let points = poly.points;
for (let i=0, il=points.length; i<il; i++) {
let p = points[i];
p.y = height - (p.y * scaleY + height2);
p.x = p.x * scaleX + width2;
}
if (poly.inner) {
for (let i=0, ia=poly.inner, il=poly.inner.length; i<il; i++) {
scaleMovePoly(ia[i]);
}
}
}
function writePoly(writer, poly) {
let pos = writer.skip(2);
let inner = poly.inner;
writer.writeU16(inner ? inner.length : 0, true);
let points = poly.points;
writer.writeU16(points.length, true);
for (let j=0, jl=points.length; j<jl; j++) {
let point = points[j];
writer.writeF32(point.x, true);
writer.writeF32(point.y, true);
}
if (inner && inner.length) {
for (let i=0, il=inner.length; i<il; i++) {
writePoly(inner[i]);
}
}
// write total struct length at struct head
writer.view.setUint16(pos, writer.pos - pos, true);
}
widgets.forEach(widget => {
let slice = widget.slices[index];
if (slice) {
if (slice.synth) count++;
let polys = slice.solids.unioned;
if (!polys) polys = slice.tops.map(t => t.poly);
if (slice.supports) polys.appendAll(slice.supports);
array.appendAll(polys);
count += polys.length;
}
});
let imagelen = width * height;
let writer = new DataWriter(new DataView(wasm.memory.buffer), imagelen);
writer.writeU16(width, true);
writer.writeU16(height, true);
writer.writeU16(array.length, true);
for (let i=0, il=array.length; i<il; i++) {
let poly = array[i];
scaleMovePoly(poly);
writePoly(writer, poly);
}
let out = wasm.render(0, imagelen, 0);
let data = wasm.heap.slice(0, imagelen);
// console.log({
// pos: writer.pos,
// arr: array.length,
// mem: wasm.memory.buffer.slice(imagelen,writer.pos),
// out,
// data: data.filter(v => v !== 0)
// });
return { data: data, end: count === 0 };
}
function polyout(poly, ctx, opt) {
let { scaleX, scaleY, width, height, width2, height2 } = opt;
poly.forEachPoint((p,i) => {
if (i === 0) {
ctx.moveTo(height - (p.y * scaleY + height2), p.x * scaleX + width2);
} else {
ctx.lineTo(height - (p.y * scaleY + height2), p.x * scaleX + width2);
}
}, true);
ctx.closePath();
}
self.renderLayer = function renderLayer(params) {
let {width, height, index, widgets, scaleX, scaleY} = params;
let layer = new OffscreenCanvas(height,width);
let opt = { scaleX, scaleY, width, height, width2: width/2, height2: height/2 };
let ctx = layer.getContext('2d');
ctx.fillStyle = 'rgb(200, 0, 0)';
let count = 0;
widgets.forEach(widget => {
let slice = widget.slices[index];
if (slice) {
// prevent premature exit on empty synth slice
if (slice.synth) count++;
let polys = slice.solids.unioned;
if (!polys) polys = slice.tops.map(t => t.poly);
if (slice.supports) polys.appendAll(slice.supports);
polys.forEach(poly => {
poly.move(widget.track.pos);
ctx.beginPath();
polyout(poly.setClockwise(), ctx, opt);
if (poly.inner) {
poly.inner.forEach(inner => {
polyout(inner.setCounterClockwise(), ctx, opt);
});
}
ctx.fill();
count++;
});
} else {
// console.log({no_slice_at: index})
}
});
let data = ctx.getImageData(0,0,height,width).data;
// reduce RGBA to R
let red = new Uint8ClampedArray(data.length / 4);
for (let i=0; i<red.length; i++) {
red[i] = data[i*4];
}
return { data: red, end: count === 0 };
}
}
})();

View file

@ -714,6 +714,29 @@ function handleCode(req, res, next) {
}); });
} }
function handleWasm(req, res, next) {
let [root, file] = req.gs.path.split('/').slice(1);
let ext = (file || '').split('.')[1];
let path = `wasm/${file}`;
let mod = lastmod(path);
if (root === 'wasm' && ext === 'wasm' && mod) {
let imd = ifModifiedDate(req);
if (imd && mod <= imd) {
res.writeHead(304, "Not Modified");
return res.end();
}
res.writeHead(200, {
'Content-Type': 'application/wasm',
'Cache-Control': 'public, max-age=600',
'Last-Modified': new Date(mod).toGMTString(),
});
res.end(fs.readFileSync(path));
} else {
next();
}
}
function ifModifiedDate(req) { function ifModifiedDate(req) {
let ims = req.headers['if-modified-since']; let ims = req.headers['if-modified-since'];
if (ims) { if (ims) {
@ -1330,6 +1353,7 @@ handler.use(fullpath({
[ "/api/", api.rateLimit ], [ "/api/", api.rateLimit ],
[ "/data/", handleData ], [ "/data/", handleData ],
[ "/code/", handleCode ], [ "/code/", handleCode ],
[ "/wasm/", handleWasm ],
[ "/js/", handleJS ] [ "/js/", handleJS ]
])) ]))
.use(handleDebug) .use(handleDebug)

112
wasm/kiri-sla.c Normal file
View file

@ -0,0 +1,112 @@
#include <emscripten.h>
#include <string.h>
typedef unsigned char Uint8;
typedef unsigned short Uint16;
typedef unsigned int Uint32;
struct info {
Uint16 width; // image width
Uint16 height; // image height
Uint16 polys; // total number of polygons to render
};
struct poly {
Uint16 length; // offset to end of record in bytes
Uint16 inners; // number of inner polygons
Uint16 points; // number of points in polygon
};
struct point {
float x;
float y;
};
extern void last(float a, float b);
extern void report(int a, int b);
Uint32 readoff; // read position offset
Uint8 check_cross(Uint32 x, Uint32 y, struct point *p1, struct point *p2) {
return (
((p1->y >= y) != (p2->y >= y)) &&
(x < (p2->x - p1->x) * (y - p1->y) / (p2->y - p1->y) + p1->x)
) ? 1 : 0;
}
Uint8 check_inside(Uint32 x, Uint32 y, unsigned char *p) {
struct poly *poly = (struct poly *)(p + readoff);
Uint32 nextpoly = readoff + poly->length;
Uint8 side = 0; // 0=outside, 1=inside
Uint8 psize = sizeof(struct point);
// skip post header
readoff += sizeof(struct poly);
// save pointer to first point or last comparison
struct point *pf = (struct point *)(p + readoff);
struct point *p1 = pf;
struct point *p2;
// skip past first point
readoff += psize;
// read and check point pairs updating `side`
for (Uint16 i=1; i<poly->points; i++) {
p2 = (struct point *)(p + readoff);
// increment read pointe
readoff += psize;
// check current/previous point crossing
if (check_cross(x, y, p1, p2)) {
side = 1 - side;
}
p1 = p2;
}
// check last/first point crossing
if (check_cross(x, y, p1, pf)) {
side = 1 - side;
}
// check inner polygons only if inside the outer polygon
if (side)
for (Uint16 i=0; i<poly->inners; i++) {
// if inside an inner, then actually outside poly and term
if (check_inside(x, y, p + readoff)) {
side = 0;
break;
}
}
// update pointer to next polygon
readoff = nextpoly;
return side;
}
EMSCRIPTEN_KEEPALIVE
Uint32 render(unsigned char *m, Uint32 i, Uint32 o) {
struct info *info = (struct info *)(m + i);
Uint32 impos;
Uint8 inside;
Uint32 maxpos = 0;
Uint32 polypos = (sizeof (struct info)) + i;
memset(m+o, 0, info->width * info->height);
for (Uint32 x=0; x<info->width; x++) {
for (Uint32 y=0; y<info->height; y++) {
inside = 0;
impos = y + (x * info->height);
readoff = polypos;
for (Uint32 p=0; p<info->polys; p++) {
inside = inside | check_inside(x, y, m);
}
if (inside) (m + o)[impos] = 255;
if (impos > maxpos) maxpos = impos;
}
}
return maxpos;
}

5
wasm/makefile Normal file
View file

@ -0,0 +1,5 @@
kiri-sla.wasm: kiri-sla.c
emcc kiri-sla.c -o kiri-sla.wasm -O3 -s ERROR_ON_UNDEFINED_SYMBOLS=0
clean: *.wasm
rm *.wasm