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);