diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..d798427 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,8 @@ +localhost { + header { + Cross-Origin-Opener-Policy same-origin + Cross-Origin-Embedder-Policy require-corp + } + root * /home/aolo2/code/nitka + file_server +} diff --git a/default.css b/default.css index 722971f..636ba4d 100644 --- a/default.css +++ b/default.css @@ -24,3 +24,12 @@ body .main { height: 128px; z-index: 1; } + +.main .sidepanel { + position: absolute; + height: 100%; + width: 300px; + left: 0; + top: 0; + background: white; +} diff --git a/geometry.js b/geometry.js index 63a224e..f37d7db 100644 --- a/geometry.js +++ b/geometry.js @@ -31,7 +31,7 @@ function rasterize_and_pack(text, cycles) { let cycles_total_padding = (cycles - 1) * config.padding; let bonus_cells = Math.ceil(cycles_total_padding / config.w); - const tiles_needed = cycles - 1 + 1 + bonus_cells; // stage name + count cycles from one + const tiles_needed = 1; // cycles - 1 + 1 + bonus_cells; // stage name + count cycles from one if (tiles_needed > config.raster_texture_size / config.w - raster_tile.x) { raster_tile.x = 0; raster_tile.y += 1; @@ -50,18 +50,56 @@ function rasterize_and_pack(text, cycles) { ); raster_tile.x += 1; + + /* raster_tile.x += cycles + bonus_cells; if (raster_tile.x === config.raster_texture_size / config.w) { raster_tile.x = 0; raster_tile.y += 1; } + */ rasterized[key] = [u, v]; return [u, v]; } +function pack_instruction(instruction, positions, sizes, colors, uvs, starts, y) { + starts.push(positions.length); + + for (let i = 0; i < instruction.lanes['0'].length; ++i) { + const stage = instruction.lanes['0'][i]; + let stage_cycles; + + if (i < instruction.lanes['0'].length - 1) { + const next_stage = instruction.lanes['0'][i + 1]; + stage_cycles = next_stage.c - stage.c; + } else { + stage_cycles = instruction.retcyc - stage.c; + } + + let [r, g, b] = get_color(stage.name); + let a = 255; + + if (!instruction.retired) { + r = Math.max(50, r - 50); + g = Math.max(50, g - 50); + b = Math.max(50, b - 50); + a = 100; + } + + const [u, v] = rasterize_and_pack(stage.name, stage_cycles); + + sizes.push(stage_cycles * config.w + (stage_cycles - 1) * config.padding, 1 * config.h); + positions.push(config.w * stage.c + config.padding * (stage.c - 1), config.h * y + config.padding * (y - 1)); + colors.push(r, g, b, a); + uvs.push(u, v); + } + + return instruction.lanes['0'].length; +} + function generate(trace_id) { const before = performance.now(); @@ -73,6 +111,7 @@ function generate(trace_id) { const sizes = []; const colors = []; const uvs = []; + const starts = []; let instructions = {}; @@ -82,57 +121,33 @@ function generate(trace_id) { let y = 0; - for (const id in instructions) { - const instruction = instructions[id]; - for (let i = 0; i < instruction.lanes['0'].length; ++i) { - const stage = instruction.lanes['0'][i]; - let stage_cycles; - - if (i < instruction.lanes['0'].length - 1) { - const next_stage = instruction.lanes['0'][i + 1]; - stage_cycles = next_stage.c - stage.c; - } else { - stage_cycles = instruction.retcyc - stage.c; - } - - let [r, g, b] = get_color(stage.name); - let a = 255; - - if (!instruction.retired) { - r = Math.max(50, r - 50); - g = Math.max(50, g - 50); - b = Math.max(50, b - 50); - a = 100; - } - - const [u, v] = rasterize_and_pack(stage.name, stage_cycles); - - sizes.push(stage_cycles * config.w + (stage_cycles - 1) * config.padding, 1 * config.h); - positions.push(config.w * stage.c + config.padding * (stage.c - 1), config.h * y + config.padding * (y - 1)); - colors.push(r, g, b, a); - uvs.push(u, v); - - result.count++; - - if (config.limit > 0 && result.count >= config.limit) { - break; - } + for (let i = 0; i < instructions.length; ++i) { + const instruction = instructions[i]; + result.count += pack_instruction(instruction, positions, sizes, colors, uvs, starts, y); + if (config.limit > 0 && result.count >= config.limit) { + break; } - ++y; } + starts.push(positions.length); + if (false) { result.pos = new Float32Array([0, 0]); result.size = new Float32Array([config.raster_texture_size, config.raster_texture_size]); result.color = new Uint8Array([0, 0, 0, 255]); result.uv = new Float32Array([0, 0]); result.count = 1; + result.trace_id = trace_id; + result.uploaded = false; } else { result.pos = new Float32Array(positions); result.size = new Float32Array(sizes); result.color = new Uint8Array(colors); result.uv = new Float32Array(uvs); + result.trace_id = trace_id; + result.uploaded = false; + result.start = new Int32Array(starts); } const after = performance.now(); @@ -141,3 +156,70 @@ function generate(trace_id) { return result; } + +function clip(quads) { + const tl = screen_to_canvas({'x': 0, 'y': 0}); + const br = screen_to_canvas({'x': 0, 'y': canvas.height}); + + const x1 = tl.x; + const y1 = tl.y; + + const x2 = br.x; + const y2 = br.y; + + const result = { + 'count': 0, + }; + + if (quads.count === 0) { + return result; + } + + let i0 = -1; + let i1 = -1; + + for (let i = 0; i < quads.start.length - 1; ++i) { + const index = quads.start[i]; + const next = quads.start[i + 1]; + + const left = quads.pos[index]; + const top = quads.pos[index + 1]; + const bottom = top + quads.size[index + 1]; + const right = quads.pos[next - 2] + quads.size[next - 2]; + + if (bottom < y1) { + if (i < quads.start.length / 2 - 2) { + const index_ahead = quads.start[i * 2]; + const top_ahead = quads.pos[index_ahead + 1]; + const bottom_ahead = top_ahead + quads.size[index_ahead + 1]; + + if (bottom_ahead < y1) { + i *= 2; + continue; + } + } + } + + if (bottom < y1 || right < x1) { + continue; + } + + if (top > y2) { + i1 = quads.start[i + 1]; + break; + } + + if (i0 === -1) { + i0 = index; + } + } + + result.pos = quads.pos.subarray(i0, i1); + result.size = quads.size.subarray(i0, i1); + result.color = quads.color.subarray(i0 * 2, i1 * 2); + result.uv = quads.uv.subarray(i0, i1); + result.count = (i1 - i0) / 2; + result.uploaded = false; + + return result; +} diff --git a/index.html b/index.html index b031251..7d17418 100644 --- a/index.html +++ b/index.html @@ -17,9 +17,16 @@ +
+
+ +
diff --git a/input.js b/input.js index 6446386..5969365 100644 --- a/input.js +++ b/input.js @@ -9,6 +9,8 @@ function init_listeners() { document.querySelector('#c').addEventListener('mouseleave', mouseleave); document.querySelector('#c').addEventListener('wheel', wheel); + document.querySelector('#file-input').addEventListener('change', form_upload); + window.addEventListener('keydown', keydown); window.addEventListener('keyup', keyup); } @@ -23,7 +25,7 @@ function cancel(e) { } function mousedown(e) { - if (e.button === 1) { + if (e.button === 1 || (e.button === 0 && spacedown)) { moving = true; return; } @@ -50,7 +52,7 @@ function mouseleave(e) { } function mouseup(e) { - if (e.button === 1 && moving) { + if ((e.button === 1 || e.button === 0 && spacedown) && moving) { moving = false; } } @@ -58,9 +60,13 @@ function mouseup(e) { function wheel(e) { const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY}; const zooming_in = e.deltaY < 0; - const level = zooming_in ? zoom_level + 2 : zoom_level - 2; + let level = zooming_in ? zoom_level + 2 : zoom_level - 2; const dz = (level > 0 ? config.zoom_delta : -config.zoom_delta); + if (level > config.max_zoom_level) { + level = config.max_zoom_level; + } + zoom_level = level; zoom_target = Math.pow(1.0 + dz, Math.abs(zoom_level)) zoom_screenp = screenp; @@ -70,36 +76,33 @@ function wheel(e) { function jump_to_first_instruction() { if (Object.keys(traces).length > 0) { - const trace = traces[Object.keys(traces)[0]].raw; - if (Object.keys(trace).length > 0) { - const first_instruction = trace[Object.keys(trace)[0]]; - offset.x = -first_instruction.cycle * config.w + config.padding * 2; - offset.y = config.padding * 2; - zoom_target = 1; - zoom = 1; - schedule_draw(); - } + const quads = traces[Object.keys(traces)[0]].geo; + offset.x = -quads.pos[0] + config.padding * 2; + offset.y = quads.pos[1] + config.padding * 2; + zoom_target = 1; + zoom = 1; + schedule_draw(); } } function keydown(e) { if (e.code === 'Digit0') { jump_to_first_instruction(); + } else if (e.code === 'Space') { + spacedown = true; } } function keyup(e) { -} - -function drop(e) { - e.preventDefault(); - - if (e.dataTransfer.files.length !== 1) { - console.error('Only one file at once, please!'); - return false; + if (e.code === 'Space') { + spacedown = false; + if (moving) { + moving = false; + } } +} - const file = e.dataTransfer.files[0]; +function upload_file(file) { const fr = new FileReader(); const upload_failed = () => { @@ -136,3 +139,23 @@ function drop(e) { fr.readAsText(file); } + +function form_upload(e) { + if (e.target.files.length === 1) { + upload_file(e.target.files[0]); + } else if (e.target.files > 1) { + console.error('Only one file at a time for now!'); + } +} + +function drop(e) { + e.preventDefault(); + + if (e.dataTransfer.files.length !== 1) { + console.error('Only one file at once, please!'); + return false; + } + + const file = e.dataTransfer.files[0]; + upload_file(file); +} diff --git a/parse.js b/parse.js index 450c832..3f34916 100644 --- a/parse.js +++ b/parse.js @@ -13,19 +13,12 @@ function parse(text) { for (let i = 0; i < text.length; ++i) { if (text[i] === '\n') { - if (line_index === 0) { - line_start = i + 1; - line_index += 1; - continue; - } - // TODO: speed const line_copy = text.substring(line_start, i); const line_parts = line_copy.split(/\t/); if (line_parts.length === 0) { console.error('Parser error: empty line'); - return false; } const command = line_parts[0]; @@ -64,7 +57,7 @@ function parse(text) { 'thread_id': thread_id, 'text': '', 'popover_text': '', - 'lanes': {}, + 'lanes': {'0': [], '1': []}, }; break; @@ -85,7 +78,6 @@ function parse(text) { } } else { console.error('Parser error: label for an instruction that does not exist'); - return false; } break; @@ -110,7 +102,6 @@ function parse(text) { }); } else { console.error('Parser error: pipeline start for an instruction that does not exist'); - return false; } @@ -145,7 +136,6 @@ function parse(text) { instructions[id].retcyc = c; } else { console.error('Parser error: retire for an instruction that does not exist'); - return false; } break; @@ -165,7 +155,6 @@ function parse(text) { default: { console.error('Parser error: unexpected command', command); - return false; } } @@ -174,17 +163,18 @@ function parse(text) { } } - const after = performance.now(); - - console.log(`Parsed in ${Math.round(after - before)}ms`); traces['0'] = { - 'raw': instructions, + // The fact these are sorted is used extensively over the code + 'raw': Object.values(instructions).sort((a, b) => a.cycle - b.cycle), }; traces['0'].geo = generate('0'); + const after = performance.now(); + console.log(`Parsed in ${Math.round(after - before)}ms`); + return true; } diff --git a/render.js b/render.js index d2c41d2..a3c9e00 100644 --- a/render.js +++ b/render.js @@ -28,6 +28,7 @@ let config = { limit: -1, zoom_delta: 0.05, raster_texture_size: 4096, + max_zoom_level: 0, }; let canvas = null; @@ -42,6 +43,7 @@ let zoom_screenp = { 'x': 0, 'y': 0 }; let last_frame_dt = 0; let last_frame_ts = 0; let raster_tile = { 'x': 0, 'y': 0 }; +let spacedown = false; const tquad_vs_src = `#version 300 es in vec2 a_pos; @@ -149,17 +151,24 @@ function draw(ts, animation) { quads = traces['0'].geo; } - if (quads.count > 0) { + const clipped = clip(quads); + + if (clipped.count > 0) { const program = programs['quad']; const fade = Math.max(0, Math.min(1.25 * zoom - 0.25, 1)); gl.useProgram(program.program); gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_packed']); - gl.bufferData(gl.ARRAY_BUFFER, quads.count * config.bytes_per_quad, gl.STATIC_DRAW); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, quads.pos); - gl.bufferSubData(gl.ARRAY_BUFFER, quads.pos.byteLength, quads.size); - gl.bufferSubData(gl.ARRAY_BUFFER, quads.pos.byteLength + quads.size.byteLength, quads.color); - gl.bufferSubData(gl.ARRAY_BUFFER, quads.pos.byteLength + quads.size.byteLength + quads.color.byteLength, quads.uv); + + if (!clipped.uploaded) { + gl.bufferData(gl.ARRAY_BUFFER, clipped.count * config.bytes_per_quad, gl.STATIC_DRAW); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, clipped.pos); + gl.bufferSubData(gl.ARRAY_BUFFER, clipped.pos.byteLength, clipped.size); + gl.bufferSubData(gl.ARRAY_BUFFER, clipped.pos.byteLength + clipped.size.byteLength, clipped.color); + gl.bufferSubData(gl.ARRAY_BUFFER, clipped.pos.byteLength + clipped.size.byteLength + clipped.color.byteLength, clipped.uv); + + clipped.uploaded = true; + } gl.uniform2f(program.locations['u_res'], canvas.width, canvas.height); gl.uniform2f(program.locations['u_translation'], offset.x, offset.y); @@ -175,9 +184,9 @@ function draw(ts, animation) { gl.enableVertexAttribArray(program.locations['a_uv']); gl.vertexAttribPointer(program.locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, 0); - gl.vertexAttribPointer(program.locations['a_size'], 2, gl.FLOAT, false, 2 * 4, quads.pos.byteLength); - gl.vertexAttribPointer(program.locations['a_color'], 4, gl.UNSIGNED_BYTE, true, 4 * 1, quads.pos.byteLength + quads.size.byteLength); - gl.vertexAttribPointer(program.locations['a_uv'], 2, gl.FLOAT, false, 2 * 4, quads.pos.byteLength + quads.size.byteLength + quads.color.byteLength); + gl.vertexAttribPointer(program.locations['a_size'], 2, gl.FLOAT, false, 2 * 4, clipped.pos.byteLength); + gl.vertexAttribPointer(program.locations['a_color'], 4, gl.UNSIGNED_BYTE, true, 4 * 1, clipped.pos.byteLength + clipped.size.byteLength); + gl.vertexAttribPointer(program.locations['a_uv'], 2, gl.FLOAT, false, 2 * 4, clipped.pos.byteLength + clipped.size.byteLength + clipped.color.byteLength); gl.vertexAttribDivisor(program.locations['a_pos'], 1); gl.vertexAttribDivisor(program.locations['a_size'], 1); @@ -185,7 +194,7 @@ function draw(ts, animation) { gl.vertexAttribDivisor(program.locations['a_uv'], 1); gl.bindTexture(gl.TEXTURE_2D, textures['raster']); - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, quads.count); + gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, clipped.count); gl.vertexAttribDivisor(program.locations['a_pos'], 0); gl.vertexAttribDivisor(program.locations['a_size'], 0);