initial commit of wasm worker for sla rendering
This commit is contained in:
parent
d270de3a92
commit
027430e7a7
4 changed files with 336 additions and 81 deletions
|
|
@ -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 {
|
||||||
|
constructor(view, pos) {
|
||||||
class DataWriter {
|
|
||||||
constructor(view) {
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.pos = 0;
|
this.pos = pos || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
skip(v) {
|
skip(v) {
|
||||||
|
|
@ -1305,4 +1263,160 @@ class DataWriter {
|
||||||
this.view.setFloat64(this.pos, v, le);
|
this.view.setFloat64(this.pos, v, le);
|
||||||
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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
112
wasm/kiri-sla.c
Normal 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
5
wasm/makefile
Normal 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
|
||||||
Loading…
Reference in a new issue