Browse Source

First working draft of pressure hanlding

ssao
aolo2 10 months ago
parent
commit
704c833c16
  1. 13
      README.md
  2. 3
      client/aux.js
  3. 17
      client/client_recv.js
  4. 11
      client/client_send.js
  5. 4
      client/index.js
  6. 3
      client/math.js
  7. 30
      client/speed.js
  8. 4
      client/wasm/lod.c
  9. BIN
      client/wasm/lod.wasm
  10. 22
      client/webgl_draw.js
  11. 3
      client/webgl_geometry.js
  12. 5
      client/webgl_listeners.js
  13. 9
      client/webgl_shaders.js
  14. 8
      server/deserializer.js
  15. 4
      server/math.js
  16. 3
      server/recv.js
  17. 1
      server/send.js
  18. 3
      server/serializer.js
  19. 6
      server/storage.js

13
README.md

@ -13,6 +13,7 @@ Release:
+ GC stalls!!! + GC stalls!!!
+ Stroke previews get connected when drawn without panning on touch devices + Stroke previews get connected when drawn without panning on touch devices
+ Redraw HTML (cursors) on local canvas moves + Redraw HTML (cursors) on local canvas moves
- New strokes dissapear on the HMH desk
- Debug - Debug
- Restore ability to limit event range - Restore ability to limit event range
* Listeners/events/multiplayer * Listeners/events/multiplayer
@ -40,16 +41,20 @@ Release:
- Show what's happening while the desk is loading (downloading, processing, uploading to gpu) - Show what's happening while the desk is loading (downloading, processing, uploading to gpu)
- Settings panel for config values (including the setting for "offline mode") - Settings panel for config values (including the setting for "offline mode")
- Set up VAOs - Set up VAOs
- We are calling "geometry_prepare_stroke" twice for some reason
- Presentation / "marketing" - Presentation / "marketing"
- Title - Title
- Icon - Icon
- Product page (github readme, demo videos) - Product page (github readme, demo videos)
Bonus: Bonus:
- Handle pressure * Handle pressure
- Add pressure data to quads + Add pressure data to quads
- Draw capsules instead of segments + Account for pressure in quad/bbox calc
- Adjust curve simplification to include pressure info * Adjust curve simplification to include pressure info
- Migrate old non-pressure desks
- Check out e.pressure on touch devices
- Send pressure in PREDRAW event
- Curve modification - Curve modification
- Select curves (with a lasso?) - Select curves (with a lasso?)
- Move whole curve - Move whole curve

3
client/aux.js

@ -64,7 +64,8 @@ function event_size(event) {
} }
case EVENT.STROKE: { case EVENT.STROKE: {
size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2; // u32 stroke id + u16 (count) + u16 (width) + u32 (color + count * (f32, f32) points // u32 stroke id + u16 (count) + u16 (width) + u32 (color) + count * (f32, f32) points + count (u8) pressures
size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2 + round_to_pow2(event.points.length, 4);
break; break;
} }

17
client/client_recv.js

@ -51,6 +51,12 @@ function des_f32array(d, count) {
return result; return result;
} }
function des_u8array(d, count) {
const result = new Uint8Array(d.buffer, d.offset, count);
d.offset += count;
return result;
}
function des_event(d, state = null) { function des_event(d, state = null) {
const event = {}; const event = {};
@ -100,10 +106,14 @@ function des_event(d, state = null) {
const color = des_u32(d); const color = des_u32(d);
const coords = des_f32array(d, point_count * 2); const coords = des_f32array(d, point_count * 2);
const press = des_u8array(d, point_count);
des_align(d, 4);
wasm_ensure_by(state, 1, coords.length); wasm_ensure_by(state, 1, coords.length);
const coordinates = state.wasm.buffers['coordinates']; const coordinates = state.wasm.buffers['coordinates'];
const pressures = state.wasm.buffers['pressures'];
tv_add(state.wasm.buffers['coords_from'].tv, coordinates.tv.size + point_count * 2); tv_add(state.wasm.buffers['coords_from'].tv, coordinates.tv.size + point_count * 2);
state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints state.wasm.buffers['coords_from'].used += 4; // 4 bytes, not 4 ints
@ -115,6 +125,9 @@ function des_event(d, state = null) {
tv_append(coordinates.tv, coords); tv_append(coordinates.tv, coords);
state.wasm.buffers['coordinates'].used += point_count * 2 * 4; state.wasm.buffers['coordinates'].used += point_count * 2 * 4;
tv_append(pressures.tv, press);
state.wasm.buffers['pressures'].used += point_count;
event.stroke_id = stroke_id; event.stroke_id = stroke_id;
event.color = color; event.color = color;
@ -187,7 +200,7 @@ function handle_event(state, context, event, options = {}) {
} }
case EVENT.PREDRAW: { case EVENT.PREDRAW: {
geometry_add_point(state, context, event.user_id, {'x': event.x, 'y': event.y}); geometry_add_point(state, context, event.user_id, {'x': event.x, 'y': event.y}, 0.5); // TODO: event.pressure
need_draw = true; need_draw = true;
break; break;
} }

11
client/client_send.js

@ -43,6 +43,11 @@ function ser_clear(s) {
s.gpu_upload_from = 0; s.gpu_upload_from = 0;
} }
function ser_u8(s, value) {
s.view.setUint8(s.offset, value);
s.offset += 1;
}
function ser_u16(s, value) { function ser_u16(s, value) {
s.view.setUint16(s.offset, value, true); s.view.setUint16(s.offset, value, true);
s.offset += 2; s.offset += 2;
@ -114,6 +119,12 @@ function ser_event(s, event) {
ser_f32(s, point.y); ser_f32(s, point.y);
} }
for (const point of event.points) {
ser_u8(s, point.pressure);
}
ser_align(s, 4);
break; break;
} }

4
client/index.js

@ -14,7 +14,7 @@ const config = {
buffer_first_touchmoves: 5, buffer_first_touchmoves: 5,
debug_print: false, debug_print: false,
min_zoom: 0.00001, min_zoom: 0.00001,
max_zoom: 1, max_zoom: 10,
initial_offline_timeout: 1000, initial_offline_timeout: 1000,
default_color: 0x00, default_color: 0x00,
default_width: 8, default_width: 8,
@ -237,8 +237,10 @@ async function main() {
'instance_data_points': tv_create(Float32Array, 4096), 'instance_data_points': tv_create(Float32Array, 4096),
'instance_data_ids': tv_create(Uint32Array, 4096), 'instance_data_ids': tv_create(Uint32Array, 4096),
'instance_data_pressures': tv_create(Uint8Array, 4096),
'dynamic_instance_points': tv_create(Float32Array, 4096), 'dynamic_instance_points': tv_create(Float32Array, 4096),
'dynamic_instance_pressure': tv_create(Uint8Array, 4096),
'dynamic_instance_ids': tv_create(Uint32Array, 4096), 'dynamic_instance_ids': tv_create(Uint32Array, 4096),
'stroke_data': serializer_create(config.initial_static_bytes), 'stroke_data': serializer_create(config.initial_static_bytes),

3
client/math.js

@ -251,8 +251,7 @@ function segment_interesects_quad(a, b, quad_topleft, quad_bottomright, quad_top
} }
function stroke_bbox(state, stroke) { function stroke_bbox(state, stroke) {
const radius = stroke.width / 2; const radius = stroke.width; // do not divide by 2 to account for max possible pressure
const coordinates = state.wasm.buffers['coordinates'].tv.data; const coordinates = state.wasm.buffers['coordinates'].tv.data;
let min_x = coordinates[stroke.coords_from + 0] - radius; let min_x = coordinates[stroke.coords_from + 0] - radius;

30
client/speed.js

@ -19,6 +19,10 @@ async function init_wasm(state) {
'offset': state.wasm.exports.alloc_static(state.wasm.stroke_bytes), 'offset': state.wasm.exports.alloc_static(state.wasm.stroke_bytes),
'used': 0, 'used': 0,
}, },
'pressures': {
'offset': state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8),
'used': 0
},
}; };
const mem = state.wasm.exports.memory.buffer; const mem = state.wasm.exports.memory.buffer;
@ -29,7 +33,8 @@ async function init_wasm(state) {
mem, state.wasm.buffers['coords_from'].offset); mem, state.wasm.buffers['coords_from'].offset);
state.wasm.buffers['line_threshold'].tv = tv_create_on(Float32Array, state.wasm.stroke_bytes / 4, state.wasm.buffers['line_threshold'].tv = tv_create_on(Float32Array, state.wasm.stroke_bytes / 4,
mem, state.wasm.buffers['line_threshold'].offset); mem, state.wasm.buffers['line_threshold'].offset);
state.wasm.buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8,
mem, state.wasm.buffers['pressures'].offset);
tv_add(state.wasm.buffers['coords_from'].tv, 0); tv_add(state.wasm.buffers['coords_from'].tv, 0);
state.wasm.buffers['coords_from'].used = 4; state.wasm.buffers['coords_from'].used = 4;
@ -40,6 +45,7 @@ function wasm_ensure_by(state, nstrokes, ncoords) {
const old_coords_from_offset = buffers['coords_from'].offset; const old_coords_from_offset = buffers['coords_from'].offset;
const old_line_threshold_offset = buffers['line_threshold'].offset; const old_line_threshold_offset = buffers['line_threshold'].offset;
const old_pressures_offset = buffers['pressures'].offset;
const old_size_coords = state.wasm.coords_bytes; const old_size_coords = state.wasm.coords_bytes;
const old_size_strokes = state.wasm.stroke_bytes; const old_size_strokes = state.wasm.stroke_bytes;
@ -67,23 +73,29 @@ function wasm_ensure_by(state, nstrokes, ncoords) {
buffers['coordinates'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes); buffers['coordinates'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes);
buffers['coords_from'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes); buffers['coords_from'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes);
buffers['line_threshold'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes); buffers['line_threshold'].offset = state.wasm.exports.alloc_static(state.wasm.stroke_bytes);
buffers['pressures'].offset = state.wasm.exports.alloc_static(state.wasm.coords_bytes / 8);
buffers['coordinates'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 4, mem, buffers['coordinates'].offset); buffers['coordinates'].tv = tv_create_on(Float32Array, state.wasm.coords_bytes / 4, mem, buffers['coordinates'].offset);
buffers['coords_from'].tv = tv_create_on(Uint32Array, state.wasm.stroke_bytes / 4, mem, buffers['coords_from'].offset); buffers['coords_from'].tv = tv_create_on(Uint32Array, state.wasm.stroke_bytes / 4, mem, buffers['coords_from'].offset);
buffers['line_threshold'].tv = tv_create_on(Float32Array, state.wasm.stroke_bytes / 4, mem, buffers['line_threshold'].offset); buffers['line_threshold'].tv = tv_create_on(Float32Array, state.wasm.stroke_bytes / 4, mem, buffers['line_threshold'].offset);
buffers['pressures'].tv = tv_create_on(Uint8Array, state.wasm.coords_bytes / 8, mem, buffers['pressures'].offset);
buffers['coordinates'].tv.size = buffers['coordinates'].used / 4; buffers['coordinates'].tv.size = buffers['coordinates'].used / 4;
buffers['coords_from'].tv.size = buffers['coords_from'].used / 4; buffers['coords_from'].tv.size = buffers['coords_from'].used / 4;
buffers['line_threshold'].tv.size = buffers['line_threshold'].used / 4; buffers['line_threshold'].tv.size = buffers['line_threshold'].used / 4;
buffers['pressures'].tv.size = buffers['pressures'].used;
const tmp = new Uint8Array(Math.max(state.wasm.coords_bytes / 8, state.wasm.stroke_bytes)); // TODO: needed?
const tmp = new Uint8Array(state.wasm.stroke_bytes); // TODO: needed? // Copy from back to front (otherwise we will overwrite)
tmp.set(new Uint8Array(mem, old_pressures_offset, buffers['pressures'].used));
memv.set(new Uint8Array(tmp.buffer, 0, buffers['pressures'].used), buffers['pressures'].offset);
// First we move the line_threshold, only then coords_from (otherwise we will overwrite)
tmp.set(new Uint8Array(mem, old_line_threshold_offset, old_size_strokes)); tmp.set(new Uint8Array(mem, old_line_threshold_offset, old_size_strokes));
memv.set(tmp, buffers['coordinates'].offset + state.wasm.coords_bytes + state.wasm.stroke_bytes); memv.set(new Uint8Array(tmp.buffer, 0, old_size_strokes), buffers['line_threshold'].offset);
tmp.set(new Uint8Array(mem, old_coords_from_offset, old_size_strokes)); tmp.set(new Uint8Array(mem, old_coords_from_offset, old_size_strokes));
memv.set(tmp, buffers['coordinates'].offset + state.wasm.coords_bytes); memv.set(new Uint8Array(tmp.buffer, 0, old_size_strokes), buffers['coords_from'].offset);
} }
} }
@ -102,6 +114,7 @@ function do_lod_wasm(state, context) {
buffers['coords_from'].offset, buffers['coords_from'].offset,
buffers['line_threshold'].offset, buffers['line_threshold'].offset,
buffers['coordinates'].offset, buffers['coordinates'].offset,
buffers['pressures'].offset,
buffers['coordinates'].used / 4, buffers['coordinates'].used / 4,
); );
@ -113,13 +126,18 @@ function do_lod_wasm(state, context) {
result_offset, segment_count * 2); result_offset, segment_count * 2);
const wasm_ids = new Uint32Array(state.wasm.exports.memory.buffer, const wasm_ids = new Uint32Array(state.wasm.exports.memory.buffer,
result_offset + segment_count * 2 * 4, segment_count); result_offset + segment_count * 2 * 4, segment_count);
const wasm_pressures = new Uint8Array(state.wasm.exports.memory.buffer,
result_offset + segment_count * 2 * 4 + segment_count * 4, segment_count);
context.instance_data_points.data = wasm_points; context.instance_data_points.data = wasm_points;
context.instance_data_points.size = segment_count * 2; context.instance_data_points.size = segment_count * 2;
context.instance_data_ids.data = wasm_ids; context.instance_data_ids.data = wasm_ids;
context.instance_data_ids.size = segment_count; context.instance_data_ids.size = segment_count;
context.instance_data_pressures.data = wasm_pressures;
context.instance_data_pressures.size = segment_count;
return segment_count; return segment_count;
} }

4
client/wasm/lod.c

@ -75,6 +75,7 @@ do_lod(int *clipped_indices, int clipped_count, float zoom,
int *stroke_coords_from, int *stroke_coords_from,
float *line_threshold, float *line_threshold,
float *coordinates, float *coordinates,
unsigned char *pressures,
int coordinates_count) int coordinates_count)
{ {
if (clipped_count == 0) { if (clipped_count == 0) {
@ -154,6 +155,7 @@ do_lod(int *clipped_indices, int clipped_count, float zoom,
// Write actual coordinates (points) and stroke ids // Write actual coordinates (points) and stroke ids
float *points = alloc_dynamic(segments_head * 2 * 4); float *points = alloc_dynamic(segments_head * 2 * 4);
int *ids = alloc_dynamic(segments_head * 4); int *ids = alloc_dynamic(segments_head * 4);
unsigned char *pressures_res = alloc_dynamic(segments_head);
int phead = 0; int phead = 0;
int ihead = 0; int ihead = 0;
@ -174,6 +176,8 @@ do_lod(int *clipped_indices, int clipped_count, float zoom,
points[phead++] = x; points[phead++] = x;
points[phead++] = y; points[phead++] = y;
pressures_res[ihead] = pressures[base_stroke / 2 + point_index];
if (j != to - 1) { if (j != to - 1) {
ids[ihead++] = stroke_index; ids[ihead++] = stroke_index;
} else { } else {

BIN
client/wasm/lod.wasm

Binary file not shown.

22
client/webgl_draw.js

@ -107,10 +107,16 @@ function draw(state, context) {
// "Static" data upload // "Static" data upload
if (segment_count > 0) { if (segment_count > 0) {
const total_static_size = context.instance_data_points.size * 4 +
context.instance_data_ids.size * 4 +
context.instance_data_pressures.size;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_instance']);
gl.bufferData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4, gl.STREAM_DRAW); gl.bufferData(gl.ARRAY_BUFFER, total_static_size, gl.STREAM_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points)); gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.instance_data_points));
gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids)); gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4, tv_data(context.instance_data_ids));
gl.bufferSubData(gl.ARRAY_BUFFER, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4,
tv_data(context.instance_data_pressures));
gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']); gl.bindTexture(gl.TEXTURE_2D, context.textures['stroke_data']);
upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size); upload_square_rgba16ui_texture(gl, context.stroke_data, config.stroke_texture_size);
@ -125,15 +131,18 @@ function draw(state, context) {
gl.enableVertexAttribArray(locations['a_a']); gl.enableVertexAttribArray(locations['a_a']);
gl.enableVertexAttribArray(locations['a_b']); gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']); gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.enableVertexAttribArray(locations['a_pressure']);
// Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values) // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0); gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4); gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4); gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.instance_data_points.size * 4);
gl.vertexAttribPointer(locations['a_pressure'], 1, gl.UNSIGNED_BYTE, true, 1, context.instance_data_points.size * 4 + context.instance_data_ids.size * 4);
gl.vertexAttribDivisor(locations['a_a'], 1); gl.vertexAttribDivisor(locations['a_a'], 1);
gl.vertexAttribDivisor(locations['a_b'], 1); gl.vertexAttribDivisor(locations['a_b'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1); gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.vertexAttribDivisor(locations['a_pressure'], 1);
// Static draw (everything already bound) // Static draw (everything already bound)
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, segment_count);
@ -151,24 +160,33 @@ function draw(state, context) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_dynamic_instance']);
// Dynamic data upload // Dynamic data upload
gl.bufferData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4, gl.STREAM_DRAW); const total_dynamic_size =
context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4 +
context.dynamic_instance_pressure.size;
gl.bufferData(gl.ARRAY_BUFFER, total_dynamic_size, gl.STREAM_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points)); gl.bufferSubData(gl.ARRAY_BUFFER, 0, tv_data(context.dynamic_instance_points));
gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids)); gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4, tv_data(context.dynamic_instance_ids));
gl.bufferSubData(gl.ARRAY_BUFFER, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4,
tv_data(context.dynamic_instance_pressure));
gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']); gl.bindTexture(gl.TEXTURE_2D, context.textures['dynamic_stroke_data']);
upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size); upload_square_rgba16ui_texture(gl, context.dynamic_stroke_data, config.dynamic_stroke_texture_size);
gl.enableVertexAttribArray(locations['a_a']); gl.enableVertexAttribArray(locations['a_a']);
gl.enableVertexAttribArray(locations['a_b']); gl.enableVertexAttribArray(locations['a_b']);
gl.enableVertexAttribArray(locations['a_stroke_id']); gl.enableVertexAttribArray(locations['a_stroke_id']);
gl.enableVertexAttribArray(locations['a_pressure']);
// Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values) // Points (a, b) and stroke ids are stored in separate cpu buffers so that points can be reused (look at stride and offset values)
gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0); gl.vertexAttribPointer(locations['a_a'], 2, gl.FLOAT, false, 2 * 4, 0);
gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4); gl.vertexAttribPointer(locations['a_b'], 2, gl.FLOAT, false, 2 * 4, 2 * 4);
gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.dynamic_instance_points.size * 4); gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.INT, 4, context.dynamic_instance_points.size * 4);
gl.vertexAttribPointer(locations['a_pressure'], 1, gl.UNSIGNED_BYTE, true, 1, context.dynamic_instance_points.size * 4 + context.dynamic_instance_ids.size * 4);
gl.vertexAttribDivisor(locations['a_a'], 1); gl.vertexAttribDivisor(locations['a_a'], 1);
gl.vertexAttribDivisor(locations['a_b'], 1); gl.vertexAttribDivisor(locations['a_b'], 1);
gl.vertexAttribDivisor(locations['a_stroke_id'], 1); gl.vertexAttribDivisor(locations['a_stroke_id'], 1);
gl.vertexAttribDivisor(locations['a_pressure'], 1);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count); gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, dynamic_segment_count);
} }

3
client/webgl_geometry.js

@ -105,9 +105,11 @@ function recompute_dynamic_data(state, context) {
} }
tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096)); tv_ensure(context.dynamic_instance_points, round_to_pow2(total_points * 2, 4096));
tv_ensure(context.dynamic_instance_pressure, round_to_pow2(total_points, 4096));
tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096)); tv_ensure(context.dynamic_instance_ids, round_to_pow2(total_points, 4096));
tv_clear(context.dynamic_instance_points); tv_clear(context.dynamic_instance_points);
tv_clear(context.dynamic_instance_pressure);
tv_clear(context.dynamic_instance_ids); tv_clear(context.dynamic_instance_ids);
context.dynamic_stroke_data = ser_ensure(context.dynamic_stroke_data, config.bytes_per_stroke * total_strokes); context.dynamic_stroke_data = ser_ensure(context.dynamic_stroke_data, config.bytes_per_stroke * total_strokes);
@ -124,6 +126,7 @@ function recompute_dynamic_data(state, context) {
tv_add(context.dynamic_instance_points, p.x); tv_add(context.dynamic_instance_points, p.x);
tv_add(context.dynamic_instance_points, p.y); tv_add(context.dynamic_instance_points, p.y);
tv_add(context.dynamic_instance_pressure, p.pressure);
if (i !== player.points.length - 1) { if (i !== player.points.length - 1) {
tv_add(context.dynamic_instance_ids, stroke_index); tv_add(context.dynamic_instance_ids, stroke_index);

5
client/webgl_listeners.js

@ -204,6 +204,7 @@ function mousedown(e, state, context) {
} }
if (state.tools.active === 'pencil') { if (state.tools.active === 'pencil') {
canvasp.pressure = 128;
geometry_clear_player(state, context, state.me); geometry_clear_player(state, context, state.me);
geometry_add_point(state, context, state.me, canvasp); geometry_add_point(state, context, state.me, canvasp);
@ -253,7 +254,7 @@ function mousemove(e, state, context) {
} }
if (state.drawing) { if (state.drawing) {
//console.log(e.pressure); canvasp.pressure = Math.ceil(e.pressure * 255);
geometry_add_point(state, context, state.me, canvasp); geometry_add_point(state, context, state.me, canvasp);
fire_event(state, predraw_event(canvasp.x, canvasp.y)); fire_event(state, predraw_event(canvasp.x, canvasp.y));
do_draw = true; do_draw = true;
@ -442,7 +443,7 @@ function touchmove(e, state, context) {
state.touch.waiting_for_second_finger = false; state.touch.waiting_for_second_finger = false;
} }
canvasp.pressure = 128; // TODO: check out touch devices' e.pressure
geometry_add_point(state, context, state.me, canvasp); geometry_add_point(state, context, state.me, canvasp);
fire_event(state, predraw_event(canvasp.x, canvasp.y)); fire_event(state, predraw_event(canvasp.x, canvasp.y));

9
client/webgl_shaders.js

@ -104,8 +104,8 @@ const nop_fs_src = `#version 300 es
const sdf_vs_src = `#version 300 es const sdf_vs_src = `#version 300 es
in vec2 a_a; // point from in vec2 a_a; // point from
in vec2 a_b; // point to in vec2 a_b; // point to
in float a_radius;
in int a_stroke_id; in int a_stroke_id;
in float a_pressure;
uniform vec2 u_scale; uniform vec2 u_scale;
uniform vec2 u_res; uniform vec2 u_res;
@ -159,21 +159,21 @@ const sdf_vs_src = `#version 300 es
outwards = -up_dir + line_dir; outwards = -up_dir + line_dir;
} }
vec2 pos = origin + normalize(outwards) * radius; vec2 pos = origin + normalize(outwards) * radius * 2.0; // doubling is to account for max possible pressure
screen02 = (pos.xy * u_scale + u_translation) / u_res * 2.0 + outwards * pixel; screen02 = (pos.xy * u_scale + u_translation) / u_res * 2.0 + outwards * pixel;
v_texcoord = pos.xy + outwards * rscale; v_texcoord = pos.xy + outwards * rscale;
screen02.y = 2.0 - screen02.y; screen02.y = 2.0 - screen02.y;
v_line = vec4(a_a, a_b); v_line = vec4(a_a, a_b);
v_thickness = radius; v_thickness = radius * a_pressure * 2.0; // pressure 0.5 is the "neutral" pressure
v_color = vec3(stroke_data.xyz) / 255.0; v_color = vec3(stroke_data.xyz) / 255.0;
if (a_stroke_id >> 31 != 0) { if (a_stroke_id >> 31 != 0) {
screen02 += vec2(100.0); // shift offscreen screen02 += vec2(100.0); // shift offscreen
} }
gl_Position = vec4(screen02 - 1.0, (float(a_stroke_id) / float(u_stroke_count)) * 2.0 - 1.0, 1); gl_Position = vec4(screen02 - 1.0, (float(a_stroke_id) / float(u_stroke_count)) * 2.0 - 1.0, 1.0);
} }
`; `;
@ -312,6 +312,7 @@ function init_webgl(state, context) {
'a_a': gl.getAttribLocation(context.programs['sdf'].main, 'a_a'), 'a_a': gl.getAttribLocation(context.programs['sdf'].main, 'a_a'),
'a_b': gl.getAttribLocation(context.programs['sdf'].main, 'a_b'), 'a_b': gl.getAttribLocation(context.programs['sdf'].main, 'a_b'),
'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'), 'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'),
'a_pressure': gl.getAttribLocation(context.programs['sdf'].main, 'a_pressure'),
'u_res': gl.getUniformLocation(context.programs['sdf'].main, 'u_res'), 'u_res': gl.getUniformLocation(context.programs['sdf'].main, 'u_res'),
'u_scale': gl.getUniformLocation(context.programs['sdf'].main, 'u_scale'), 'u_scale': gl.getUniformLocation(context.programs['sdf'].main, 'u_scale'),

8
server/deserializer.js

@ -38,6 +38,12 @@ function f32array(d, count) {
return array; return array;
} }
function u8array(d, count) {
const array = new Uint8Array(d.view.buffer, d.offset, count);
d.offset += count;
return array;
}
export function align(d, to) { export function align(d, to) {
while (d.offset % to !== 0) { while (d.offset % to !== 0) {
d.offset++; d.offset++;
@ -91,6 +97,8 @@ export function event(d) {
event.width = width; event.width = width;
event.color = color; event.color = color;
event.points = f32array(d, point_count * 2); event.points = f32array(d, point_count * 2);
event.pressures = u8array(d, point_count);
align(d, 4);
break; break;
} }

4
server/math.js

@ -8,3 +8,7 @@ export function crypto_random32() {
return dataview.getUint32(0); return dataview.getUint32(0);
} }
export function round_to_pow2(value, multiple) {
return (value + multiple - 1) & -multiple;
}

3
server/recv.js

@ -110,7 +110,8 @@ function handle_event(session, event) {
const stroke_result = storage.queries.insert_stroke.get({ const stroke_result = storage.queries.insert_stroke.get({
'$width': event.width, '$width': event.width,
'$color': event.color, '$color': event.color,
'$points': event.points '$points': event.points,
'$pressures': event.pressures,
}); });
event.stroke_id = stroke_result.id; event.stroke_id = stroke_result.id;

1
server/send.js

@ -40,6 +40,7 @@ function event_size(event) {
case EVENT.STROKE: { case EVENT.STROKE: {
size += 4 + 2 + 2 + 4; // stroke id + point count + width + color size += 4 + 2 + 2 + 4; // stroke id + point count + width + color
size += event.points.byteLength; size += event.points.byteLength;
size += math.round_to_pow2(event.pressures.byteLength, 4);
break; break;
} }

3
server/serializer.js

@ -85,11 +85,14 @@ export function event(s, event) {
case EVENT.STROKE: { case EVENT.STROKE: {
const points_bytes = event.points; const points_bytes = event.points;
const pressures_bytes = event.pressures;
u32(s, event.stroke_id); u32(s, event.stroke_id);
u16(s, points_bytes.byteLength / 2 / 4); // each point is 2 * f32 u16(s, points_bytes.byteLength / 2 / 4); // each point is 2 * f32
u16(s, event.width); u16(s, event.width);
u32(s, event.color); u32(s, event.color);
bytes(s, points_bytes); bytes(s, points_bytes);
bytes(s, pressures_bytes);
align(s, 4);
break; break;
} }

6
server/storage.js

@ -38,7 +38,8 @@ export function startup() {
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
width INTEGER, width INTEGER,
color INTEGER, color INTEGER,
points BLOB points BLOB,
pressures BLOB
);`).run(); );`).run();
db.query(`CREATE TABLE IF NOT EXISTS events ( db.query(`CREATE TABLE IF NOT EXISTS events (
@ -69,7 +70,7 @@ export function startup() {
// INSERT // INSERT
queries.insert_desk = db.query('INSERT INTO desks (id, title, sn) VALUES ($id, $title, 0) RETURNING id'); queries.insert_desk = db.query('INSERT INTO desks (id, title, sn) VALUES ($id, $title, 0) RETURNING id');
queries.insert_stroke = db.query('INSERT INTO strokes (width, color, points) VALUES ($width, $color, $points) RETURNING id'); queries.insert_stroke = db.query('INSERT INTO strokes (width, color, points, pressures) VALUES ($width, $color, $points, $pressures) RETURNING id');
queries.insert_session = db.query('INSERT INTO sessions (id, desk_id, lsn) VALUES ($id, $desk_id, 0) RETURNING id'); queries.insert_session = db.query('INSERT INTO sessions (id, desk_id, lsn) VALUES ($id, $desk_id, 0) RETURNING id');
queries.insert_event = db.query('INSERT INTO events (type, desk_id, session_id, stroke_id, image_id, x, y) VALUES ($type, $desk_id, $session_id, $stroke_id, $image_id, $x, $y) RETURNING id'); queries.insert_event = db.query('INSERT INTO events (type, desk_id, session_id, stroke_id, image_id, x, y) VALUES ($type, $desk_id, $session_id, $stroke_id, $image_id, $x, $y) RETURNING id');
@ -115,6 +116,7 @@ export function startup() {
if (event.type === EVENT.STROKE) { if (event.type === EVENT.STROKE) {
const stroke = stroke_dict[event.stroke_id]; const stroke = stroke_dict[event.stroke_id];
event.points = stroke.points; event.points = stroke.points;
event.pressures = stroke.pressures;
event.color = stroke.color; event.color = stroke.color;
event.width = stroke.width; event.width = stroke.width;

Loading…
Cancel
Save