diff --git a/client/client_recv.js b/client/client_recv.js index 3d445a0..d1c63bb 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -175,7 +175,7 @@ function handle_event(state, context, event) { need_draw = true; } - geometry_add_stroke(state, context, event); + geometry_add_stroke(state, context, event, state.events.length); break; } diff --git a/client/index.js b/client/index.js index be8101f..b69c17d 100644 --- a/client/index.js +++ b/client/index.js @@ -17,10 +17,11 @@ const config = { initial_offline_timeout: 1000, default_color: 0x00, default_width: 8, - bytes_per_point: 8 * 4, + bytes_per_point: 9 * 4, initial_static_bytes: 4096 * 16, initial_dynamic_bytes: 4096, frametime_window_size: 100, + tile_size: 16, }; const EVENT = Object.freeze({ @@ -192,7 +193,8 @@ function main() { 'buffers': {}, 'locations': {}, 'textures': {}, - + 'framebuffers': {}, + 'static_serializer': serializer_create(config.initial_static_bytes), 'static_index_serializer': serializer_create(config.initial_static_bytes), 'dynamic_serializer': serializer_create(config.initial_dynamic_bytes), diff --git a/client/math.js b/client/math.js index 73d31c8..d533f9a 100644 --- a/client/math.js +++ b/client/math.js @@ -234,6 +234,14 @@ function segments_onscreen(state, context) { const screen_topleft = screen_to_canvas(state, {'x': 0, 'y': 0}); const screen_bottomright = screen_to_canvas(state, {'x': context.canvas.width, 'y': context.canvas.height}); + + /* + screen_topleft.x += 300; + screen_topleft.y += 300; + screen_bottomright.x -= 300; + screen_bottomright.y -= 300; + */ + const screen_topright = { 'x': screen_bottomright.x, 'y': screen_topleft.y }; const screen_bottomleft = { 'x': screen_topleft.x, 'y': screen_bottomright.y }; const screen = {'x1': screen_topleft.x, 'y1': screen_topleft.y, 'x2': screen_bottomright.x, 'y2': screen_bottomright.y}; diff --git a/client/webgl_draw.js b/client/webgl_draw.js index 7703e50..520b571 100644 --- a/client/webgl_draw.js +++ b/client/webgl_draw.js @@ -1,6 +1,12 @@ function schedule_draw(state, context) { if (!state.timers.raf) { - window.requestAnimationFrame(() => draw(state, context)); + window.requestAnimationFrame(() => { + context._DRAW_TO_TEXTURE = true; + draw(state, context); + + context._DRAW_TO_TEXTURE = false; + draw(state, context) + }); state.timers.raf = true; } } @@ -14,6 +20,12 @@ function draw(state, context) { let query = null; + if (context._DRAW_TO_TEXTURE) { + gl.bindFramebuffer(gl.FRAMEBUFFER, context.framebuffers['sdf'].tiles); + } else { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + if (context.gpu_timer_ext !== null) { query = gl.createQuery(); gl.beginQuery(context.gpu_timer_ext.TIME_ELAPSED_EXT, query); @@ -22,20 +34,36 @@ function draw(state, context) { let locations; let buffers; - gl.viewport(0, 0, context.canvas.width, context.canvas.height); - gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1); - gl.clear(gl.COLOR_BUFFER_BIT); + + if (!context._DRAW_TO_TEXTURE) { + gl.viewport(0, 0, context.canvas.width, context.canvas.height); + gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + } else { + //gl.clearBufferuiv(gl.COLOR, 0, new Uint8Array([0, 0, 0, 1])); + gl.viewport(0, 0, Math.ceil(context.canvas.width / config.tile_size), Math.ceil(context.canvas.height / config.tile_size)); + gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + } // SDF - locations = context.locations['sdf']; buffers = context.buffers['sdf']; - gl.useProgram(context.programs['sdf']); + if (!context._DRAW_TO_TEXTURE) { + locations = context.locations['sdf'].main; + gl.useProgram(context.programs['sdf'].main); + } else { + locations = context.locations['sdf'].tiles; + gl.useProgram(context.programs['sdf'].tiles); + } gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); - gl.uniform1i(locations['u_debug_mode'], context.debug_mode ? 1 : 0); + + if (!context._DRAW_TO_TEXTURE) { + gl.uniform1i(locations['u_debugmode'], context.debug_mode ? 1 : 0); + } const static_points = context.static_serializer.offset / config.bytes_per_point; const dynamic_points = context.dynamic_serializer.offset / config.bytes_per_point; @@ -45,11 +73,25 @@ function draw(state, context) { gl.enableVertexAttribArray(locations['a_pos']); gl.enableVertexAttribArray(locations['a_line']); - gl.enableVertexAttribArray(locations['a_color']); - gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0); - gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3); - gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4); + if (!context._DRAW_TO_TEXTURE) { + gl.enableVertexAttribArray(locations['a_color']); + } + + if (context._DRAW_TO_TEXTURE) { + gl.enableVertexAttribArray(locations['a_stroke_id']); + } + + gl.vertexAttribPointer(locations['a_pos'], 3, gl.FLOAT, false, config.bytes_per_point, 0); + gl.vertexAttribPointer(locations['a_line'], 4, gl.FLOAT, false, config.bytes_per_point, 4 * 3); + + if (!context._DRAW_TO_TEXTURE) { + gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, config.bytes_per_point, 4 * 3 + 4 * 4); + } + + if (context._DRAW_TO_TEXTURE) { + gl.vertexAttribIPointer(locations['a_stroke_id'], 1, gl.UNSIGNED_INT, config.bytes_per_point, 4 * 3 + 4 * 4 + 4); + } if (context.need_static_allocate) { if (config.debug_print) console.debug('static allocate'); @@ -71,7 +113,7 @@ function draw(state, context) { const before_clip = performance.now(); const index_count = segments_onscreen(state, context); const after_clip = performance.now(); - console.debug('clip', after_clip - before_clip); + //console.debug('clip', after_clip - before_clip); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers['b_packed_static_index']); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(state.onscreen_segments.buffer, 0, index_count), gl.DYNAMIC_DRAW); @@ -81,6 +123,7 @@ function draw(state, context) { gl.drawElements(gl.TRIANGLES, index_count, gl.UNSIGNED_INT, 0); } + /* if (dynamic_points > 0) { gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed_dynamic']); @@ -99,6 +142,7 @@ function draw(state, context) { gl.drawArrays(gl.TRIANGLES, 0, dynamic_points); } + */ /* const next_tick = () => { const wait_status = gl.clientWaitSync(sync, 0, 0); diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 62232a5..94f7157 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -1,4 +1,4 @@ -function push_point(s, x, y, ax, ay, bx, by, thickness, r, g, b) { +function push_point(s, x, y, ax, ay, bx, by, thickness, r, g, b, stroke_id) { ser_f32(s, x); ser_f32(s, y); ser_f32(s, thickness); @@ -10,16 +10,17 @@ function push_point(s, x, y, ax, ay, bx, by, thickness, r, g, b) { ser_u8(s, g); ser_u8(s, b); ser_align(s, 4); + ser_u32(s, stroke_id); } -function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ax, ay, bx, by, thickness, r, g, b) { - push_point(s, p1x, p1y, ax, ay, bx, by, thickness, r, g, b); - push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b); - push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b); - push_point(s, p4x, p4y, ax, ay, bx, by, thickness, r, g, b); +function push_quad(s, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ax, ay, bx, by, thickness, r, g, b, stroke_id) { + push_point(s, p1x, p1y, ax, ay, bx, by, thickness, r, g, b, stroke_id); + push_point(s, p2x, p2y, ax, ay, bx, by, thickness, r, g, b, stroke_id); + push_point(s, p3x, p3y, ax, ay, bx, by, thickness, r, g, b, stroke_id); + push_point(s, p4x, p4y, ax, ay, bx, by, thickness, r, g, b, stroke_id); } -function push_stroke(s, stroke) { +function push_stroke(s, stroke, stroke_index) { // if (stroke.stroke_id !== 1123776468) { // return; // } @@ -71,7 +72,8 @@ function push_stroke(s, stroke) { from.x, from.y, to.x, to.y, stroke_width, - r, g, b + r, g, b, + stroke_index ); } } @@ -95,7 +97,7 @@ function geometry_prepare_stroke(state) { }; } -function geometry_add_stroke(state, context, stroke) { +function geometry_add_stroke(state, context, stroke, stroke_index) { if (!state.online || !stroke) return; stroke.bbox = stroke_bbox(stroke.points); @@ -104,12 +106,12 @@ function geometry_add_stroke(state, context, stroke) { let bytes_needed = stroke.points.length * 4 * config.bytes_per_point; if (bytes_left < bytes_needed) { - const extend_to = Math.ceil((context.static_serializer.size + bytes_needed) * 1.62); + const extend_to = Math.ceil((context.static_serializer.size + bytes_needed) * 1.62 / 4) * 4; context.static_serializer = ser_extend(context.static_serializer, extend_to); context.need_static_allocate = true; } - push_stroke(context.static_serializer, stroke); + push_stroke(context.static_serializer, stroke, stroke_index); context.need_static_upload = true; } @@ -153,7 +155,7 @@ function recompute_dynamic_data(state, context) { // player has the same data as their current stroke: points, color, width const player = state.players[player_id]; if (player.points.length > 0) { - push_stroke(context.dynamic_serializer, player); + push_stroke(context.dynamic_serializer, player, 0); // TODO: stroke index ?? } } diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 435e4f4..e18a296 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -207,7 +207,7 @@ function mouseup(e, state, context) { const stroke = geometry_prepare_stroke(state); if (stroke) { - geometry_add_stroke(state, context, stroke); + geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index? queue_event(state, stroke_event(state)); geometry_clear_player(state, context, state.me); schedule_draw(state, context); @@ -408,7 +408,7 @@ function touchend(e, state, context) { const stroke = geometry_prepare_stroke(state); if (stroke) { - geometry_add_stroke(state, context, stroke); + geometry_add_stroke(state, context, stroke, 0); // TODO: stroke index queue_event(state, stroke_event(state)); geometry_clear_player(state, context, state.me); schedule_draw(state, context); diff --git a/client/webgl_shaders.js b/client/webgl_shaders.js index 37dc93d..5a23f1e 100644 --- a/client/webgl_shaders.js +++ b/client/webgl_shaders.js @@ -3,6 +3,8 @@ const sdf_vs_src = `#version 300 es in vec4 a_line; in vec3 a_color; + in uint a_stroke_id; + uniform vec2 u_scale; uniform vec2 u_res; uniform vec2 u_translation; @@ -11,6 +13,7 @@ const sdf_vs_src = `#version 300 es out vec2 v_texcoord; out vec3 v_color; + flat out uint v_stroke_id; flat out float v_thickness; void main() { @@ -48,6 +51,7 @@ const sdf_vs_src = `#version 300 es v_line = a_line; v_color = a_color; v_thickness = a_pos.z; + v_stroke_id = a_stroke_id; gl_Position = vec4(screen02 - 1.0, 0.0, 1); } @@ -88,6 +92,28 @@ const sdf_fs_src = `#version 300 es } `; +const tiles_fs_src = `#version 300 es + precision highp float; + + uniform int u_debug_mode; + + in vec4 v_line; + in vec2 v_texcoord; + in vec3 v_color; + + flat in uint v_stroke_id; + flat in float v_thickness; + + //out uint TileId; + out vec4 FragColor; + + void main() { + //TileId = uint(1); + vec3 color = vec3(float(v_stroke_id * 3245u % 255u) / 255.0, float(v_stroke_id * 7343u % 255u) / 255.0, float(v_stroke_id * 5528u % 255u) / 255.0); + FragColor = vec4(color, 1); + } +`; + const tquad_vs_src = `#version 300 es in vec2 a_pos; in vec2 a_texcoord; @@ -148,8 +174,13 @@ function init_webgl(state, context) { const sdf_vs = create_shader(gl, gl.VERTEX_SHADER, sdf_vs_src); const sdf_fs = create_shader(gl, gl.FRAGMENT_SHADER, sdf_fs_src); + const tiles_fs = create_shader(gl, gl.FRAGMENT_SHADER, tiles_fs_src); + context.programs['image'] = create_program(gl, quad_vs, quad_fs); - context.programs['sdf'] = create_program(gl, sdf_vs, sdf_fs); + context.programs['sdf'] = { + 'main': create_program(gl, sdf_vs, sdf_fs), + 'tiles': create_program(gl, sdf_vs, tiles_fs), // same vertex shader + }; context.locations['image'] = { 'a_pos': gl.getAttribLocation(context.programs['image'], 'a_pos'), @@ -163,16 +194,33 @@ function init_webgl(state, context) { }; context.locations['sdf'] = { - 'a_pos': gl.getAttribLocation(context.programs['sdf'], 'a_pos'), - 'a_line': gl.getAttribLocation(context.programs['sdf'], 'a_line'), - 'a_color': gl.getAttribLocation(context.programs['sdf'], 'a_color'), - - 'u_res': gl.getUniformLocation(context.programs['sdf'], 'u_res'), - 'u_scale': gl.getUniformLocation(context.programs['sdf'], 'u_scale'), - 'u_translation': gl.getUniformLocation(context.programs['sdf'], 'u_translation'), - 'u_texture_points': gl.getUniformLocation(context.programs['sdf'], 'u_texture_points'), - 'u_texture_indices': gl.getUniformLocation(context.programs['sdf'], 'u_texture_indices'), - 'u_debug_mode': gl.getUniformLocation(context.programs['sdf'], 'u_debug_mode') + 'main': { + 'a_pos': gl.getAttribLocation(context.programs['sdf'].main, 'a_pos'), + 'a_line': gl.getAttribLocation(context.programs['sdf'].main, 'a_line'), + 'a_color': gl.getAttribLocation(context.programs['sdf'].main, 'a_color'), + 'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].main, 'a_stroke_id'), + + 'u_res': gl.getUniformLocation(context.programs['sdf'].main, 'u_res'), + 'u_scale': gl.getUniformLocation(context.programs['sdf'].main, 'u_scale'), + 'u_translation': gl.getUniformLocation(context.programs['sdf'].main, 'u_translation'), + 'u_texture_points': gl.getUniformLocation(context.programs['sdf'].main, 'u_texture_points'), + 'u_texture_indices': gl.getUniformLocation(context.programs['sdf'].main, 'u_texture_indices'), + 'u_debug_mode': gl.getUniformLocation(context.programs['sdf'].main, 'u_debug_mode'), + }, + + 'tiles': { + 'a_pos': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_pos'), + 'a_line': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_line'), + 'a_color': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_color'), + 'a_stroke_id': gl.getAttribLocation(context.programs['sdf'].tiles, 'a_stroke_id'), + + 'u_res': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_res'), + 'u_scale': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_scale'), + 'u_translation': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_translation'), + 'u_texture_points': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_texture_points'), + 'u_texture_indices': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_texture_indices'), + 'u_debug_mode': gl.getUniformLocation(context.programs['sdf'].tiles, 'u_debug_mode'), + } }; context.buffers['image'] = { @@ -187,7 +235,25 @@ function init_webgl(state, context) { 'b_packed_dynamic_index': gl.createBuffer(), }; - context.textures['sdf'] = {}; + context.textures['sdf'] = { + 'tiles': gl.createTexture(), + }; + + context.framebuffers['sdf'] = { + 'tiles': gl.createFramebuffer(), + }; + + gl.bindTexture(gl.TEXTURE_2D, context.textures['sdf'].tiles); + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32UI, context.canvas.width, context.canvas.height, 0, gl.RED_INTEGER, gl.UNSIGNED_INT, null); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, Math.ceil(context.canvas.width / config.tile_size), Math.ceil(context.canvas.height / config.tile_size), 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + gl.bindFramebuffer(gl.FRAMEBUFFER, context.framebuffers['sdf'].tiles); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, context.textures['sdf'].tiles, 0); + + // gl.bindFramebuffer(gl.FRAMEBUFFER, null); + context.textures['image'] = {}; const resize_canvas = (entries) => { @@ -209,6 +275,9 @@ function init_webgl(state, context) { context.canvas.width = width; context.canvas.height = height; + gl.bindTexture(gl.TEXTURE_2D, context.textures['sdf'].tiles); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, Math.ceil(context.canvas.width / config.tile_size), Math.ceil(context.canvas.height / config.tile_size), 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + schedule_draw(state, context); }