Browse Source

Move import to worker. Rasterize to single texture. Raster queue

master
A.Olokhtonov 4 months ago
parent
commit
77cf518ced
  1. 193
      geometry.js
  2. 220
      import_worker.js
  3. 3
      index.html
  4. 5
      index.js
  5. 22
      input.js
  6. 33
      rasterizer.js
  7. 93
      render.js

193
geometry.js

@ -1,196 +1,3 @@ @@ -1,196 +1,3 @@
let colors = {};
let rasterized = {};
// https://stackoverflow.com/a/17243070
function hsv_to_rgb(h, s, v) {
let r, g, b;
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255)
];
}
function get_color(stage_name) {
if (stage_name in config.predefined_colors) {
return config.predefined_colors[stage_name];
}
if (stage_name in colors) {
return colors[stage_name];
}
colors[stage_name] = hsv_to_rgb(Math.random(), 0.56, 0.56);
return colors[stage_name];
}
function rasterize_and_pack(text, cycles) {
// TODO: handle texture is full or stuff don't fit (unlikely)
const key = text;
if (key in rasterized) {
return rasterized[key];
}
const u = raster_tile.x * config.w / config.raster_texture_size;
const v = raster_tile.y * config.h / config.raster_texture_size;
rasterize(text);
gl.bindTexture(gl.TEXTURE_2D, textures['raster']);
gl.texSubImage2D(gl.TEXTURE_2D, 0,
raster_tile.x * config.w, raster_tile.y * config.h,
config.w, config.h,
gl.RGBA, gl.UNSIGNED_BYTE,
c2d.canvas
);
raster_tile.x += 1;
if (raster_tile.x === config.raster_texture_size / config.w) {
raster_tile.x = 0;
raster_tile.y += 1;
}
rasterized[key] = [u, v];
if (cycles > 1) {
gl.bindTexture(gl.TEXTURE_2D, textures['numbers']);
for (let i = numbers_rasterized + 1; i <= cycles; ++i) {
const u = number_tile.x * config.w / config.raster_texture_size;
const v = number_tile.y * config.h / config.raster_texture_size;
rasterize(i);
gl.texSubImage2D(gl.TEXTURE_2D, 0,
number_tile.x * config.w, number_tile.y * config.h,
config.w, config.h,
gl.RGBA, gl.UNSIGNED_BYTE,
c2d.canvas
);
number_tile.x += 1;
if (number_tile.x === config.raster_texture_size / config.w) {
number_tile.x = 0;
number_tile.y += 1;
}
rasterized[i] = [u, v];
}
if (cycles > numbers_rasterized) {
numbers_rasterized = cycles;
}
}
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) {
a = 80;
}
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();
const result = {
'count': 0,
};
const positions = [];
const sizes = [];
const colors = [];
const uvs = [];
const starts = [];
let instructions = {};
if (trace_id in traces) {
instructions = traces[trace_id].raw;
}
let y = 0;
for (let i = 0; i < instructions.length; ++i) {
const instruction = instructions[i];
// TODO: make all quads same size, abuse this fact
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 (config.show_texture) {
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();
console.log(`Generated geometry in ${Math.round(after - before)}ms`);
return result;
}
function clip(quads) {
if (config.show_texture) {
return quads;

220
parse.js → import_worker.js

@ -1,3 +1,22 @@ @@ -1,3 +1,22 @@
let traces = {};
let config = {};
let raster_tile = {'x': 0, 'y': 0};
let numbers_rasterized = 0;
onmessage = (e) => {
const msg = e.data;
if (msg.type === 'init') {
config = msg.config;
} else if (msg.type === 'import') {
const text = msg.text;
if (parse(text)) {
traces['0'].geo = generate('0');
postMessage({'type': 'trace', 'data': traces['0']});
}
}
}
function parse(text) {
// https://github.com/shioyadan/Konata/blob/master/docs/kanata-log-format.md
@ -12,6 +31,13 @@ function parse(text) { @@ -12,6 +31,13 @@ function parse(text) {
//console.log(text);
for (let i = 0; i < text.length; ++i) {
const last_percent = Math.round((i - 1) / text.length * 100);
const this_percent = Math.round(i / text.length * 100);
if (this_percent != last_percent) {
postMessage({'type': 'progress_parse', 'data': this_percent})
}
if (text[i] === '\n') {
// TODO: speed
const line_copy = text.substring(line_start, i);
@ -163,18 +189,15 @@ function parse(text) { @@ -163,18 +189,15 @@ function parse(text) {
}
}
traces['0'] = {
// 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;
}
@ -195,3 +218,192 @@ function assert_arglenmin(args, arglen, command) { @@ -195,3 +218,192 @@ function assert_arglenmin(args, arglen, command) {
return true;
}
let colors = {};
let rasterized = {};
// https://stackoverflow.com/a/17243070
function hsv_to_rgb(h, s, v) {
let r, g, b;
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return [
Math.round(r * 255),
Math.round(g * 255),
Math.round(b * 255)
];
}
function get_color(stage_name) {
if (stage_name in config.predefined_colors) {
return config.predefined_colors[stage_name];
}
if (stage_name in colors) {
return colors[stage_name];
}
colors[stage_name] = hsv_to_rgb(Math.random(), 0.56, 0.56);
return colors[stage_name];
}
function rasterize_and_pack(text, cycles) {
// TODO: handle texture is full or stuff don't fit (unlikely)
const key = text;
if (key in rasterized) {
return rasterized[key];
}
const u = raster_tile.x * config.w / config.raster_texture_size;
const v = raster_tile.y * config.h / config.raster_texture_size;
postMessage({'type': 'rasterize', 'data': { 'text': text, 'at': {'x': raster_tile.x, 'y': raster_tile.y}}});
raster_tile.x += 1;
if (raster_tile.x === config.raster_texture_size / config.w) {
raster_tile.x = 0;
raster_tile.y += 1;
}
rasterized[key] = [u, v];
if (cycles > 1) {
for (let i = numbers_rasterized + 1; i <= cycles; ++i) {
const u = raster_tile.x * config.w / config.raster_texture_size;
const v = raster_tile.y * config.h / config.raster_texture_size;
postMessage({'type': 'rasterize', 'data': { 'text': i, 'at': {'x': raster_tile.x, 'y': raster_tile.y}}});
raster_tile.x += 1;
if (raster_tile.x === config.raster_texture_size / config.w) {
raster_tile.x = 0;
raster_tile.y += 1;
}
rasterized[i] = [u, v];
}
if (cycles > numbers_rasterized) {
numbers_rasterized = cycles;
}
}
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) {
a = 80;
}
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();
const result = {
'count': 0,
};
const positions = [];
const sizes = [];
const colors = [];
const uvs = [];
const starts = [];
let instructions = {};
if (trace_id in traces) {
instructions = traces[trace_id].raw;
}
let y = 0;
for (let i = 0; i < instructions.length; ++i) {
const last_percent = Math.round((i - 1) / instructions.length * 100);
const this_percent = Math.round(i / instructions.length * 100);
if (this_percent !== last_percent) {
postMessage({'type': 'progress_generate', 'data': this_percent})
}
const instruction = instructions[i];
// TODO: make all quads same size, abuse this fact
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 (config.show_texture) {
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();
console.log(`Generated geometry in ${Math.round(after - before)}ms`);
return result;
}

3
index.html

@ -12,12 +12,11 @@ @@ -12,12 +12,11 @@
<!-- <link rel="preload" href="icons/picker.svg" as="image" type="image/svg+xml" /> -->
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="parse.js"></script>
<script type="text/javascript" src="render.js"></script>
<script type="text/javascript" src="input.js"></script>
<script type="text/javascript" src="geometry.js"></script>
<script type="text/javascript" src="rasterizer.js"></script>
<script type="text/javascript" src="math.js"></script>
<script type="text/javascript" src="geometry.js"></script>
</head>
<body>
<div class="main">

5
index.js

@ -1,11 +1,16 @@ @@ -1,11 +1,16 @@
document.addEventListener('DOMContentLoaded', main);
let traces = {};
let import_worker;
function main() {
const font = new FontFace('ibm8x16', 'url(ibm_vga8.woff2)');
document.fonts.add(font);
font.load();
import_worker = new Worker('import_worker.js');
import_worker.postMessage({'type': 'init', 'config': config});
document.fonts.ready.then(() => {
init_webgl();
init_rasterizer();

22
input.js

@ -115,19 +115,33 @@ function upload_file(file) { @@ -115,19 +115,33 @@ function upload_file(file) {
const upload_finished = () => {
const text = fr.result;
console.log('Finished. String length:', text.length);
if (parse(text)) {
console.log('Upload finished. String length:', text.length);
import_worker.onmessage = (e) => {
const msg = e.data;
if (msg.type === 'trace') {
traces['0'] = msg.data;
jump_to_first_instruction();
schedule_draw();
} else if (msg.type === 'rasterize') {
const data = msg.data;
queue_rasterize(data.text, data.at);
} else if (msg.type === 'progress_parse') {
console.log(`Parsing: ${msg.data}%`);
} else if (msg.type === 'progress_generate') {
console.log(`Generating geometry: ${msg.data}%`);
}
};
import_worker.postMessage({'type': 'import', 'text': text});
};
const upload_progress = (e) => {
if (e.lengthComputable) {
const percent = Math.floor(e.loaded / e.total * 100);
console.log(`Progress: ${percent}%`);
console.log(`Uploading: ${percent}%`);
} else {
console.log('Progress: unknown');
console.log('Uploading: unknown');
}
};

33
rasterizer.js

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
// TODO: move this to a worker
let c2d = null;
let raster_queue = [];
function init_rasterizer() {
c2d = document.querySelector('#offscreen').getContext('2d');
@ -16,3 +15,33 @@ function rasterize(text) { @@ -16,3 +15,33 @@ function rasterize(text) {
c2d.fillText(text, config.w / 2, config.h / 2);
//c2d.fillRect(0, 0, 32, 32);
}
function queue_rasterize(text, at) {
raster_queue.push({'text': text, 'at': at});
}
function copy_canvas_to_texture(at) {
gl.texSubImage2D(gl.TEXTURE_2D, 0,
at.x * config.w, at.y * config.h,
config.w, config.h,
gl.RGBA, gl.UNSIGNED_BYTE,
c2d.canvas
);
}
function rasterize_and_upload_batch(batch_size) {
let head = 0;
gl.bindTexture(gl.TEXTURE_2D, textures['raster']);
for (let i = 0; i < batch_size && i < raster_queue.length; ++i) {
const entry = raster_queue[i];
rasterize(entry.text);
copy_canvas_to_texture(entry.at);
head = i + 1;
}
raster_queue.splice(0, head);
return raster_queue.length;
}

93
render.js

@ -10,7 +10,6 @@ let config = { @@ -10,7 +10,6 @@ let config = {
predefined_colors: {
'Np': [75, 62, 143],
'F': [62, 123, 143],
'Pd': [61, 142, 88],
'Dc': [109, 143, 61],
'Rn': [143, 102, 61],
@ -23,6 +22,14 @@ let config = { @@ -23,6 +22,14 @@ let config = {
'Cm': [61, 81, 143],
'Mt': [142, 142, 61],
'Ma': [143, 68, 61],
'F': [72, 62, 143],
'LBr': [61, 122, 143],
'Iq': [61, 142, 88],
'D1': [109, 143, 62],
'D2': [143, 102, 62],
'L2': [255, 0, 0],
'SRS': [143, 61, 95],
'ARB': [116, 61, 143],
},
limit: -1,
@ -30,6 +37,7 @@ let config = { @@ -30,6 +37,7 @@ let config = {
raster_texture_size: 4096,
max_zoom_level: 0,
show_texture: false,
raster_batch_size: 32,
};
let canvas = null;
@ -43,10 +51,8 @@ let zoom_level = 0; @@ -43,10 +51,8 @@ let zoom_level = 0;
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 number_tile = { 'x': 0, 'y': 0 };
let spacedown = false;
let numbers_rasterized = 0;
const tquad_vs_src = `#version 300 es
in vec2 a_pos;
@ -154,6 +160,8 @@ function draw(ts, animation) { @@ -154,6 +160,8 @@ function draw(ts, animation) {
gl.beginQuery(gpu_timer_ext.TIME_ELAPSED_EXT, query);
}
const tiles_left = rasterize_and_upload_batch(config.raster_batch_size);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0.11, 0.11, 0.11, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
@ -233,7 +241,7 @@ function draw(ts, animation) { @@ -233,7 +241,7 @@ function draw(ts, animation) {
if (available && !disjoint) {
const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('Last GPU Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms');
console.debug('Last GPU Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms');
}
if (available || disjoint) {
@ -252,13 +260,17 @@ function draw(ts, animation) { @@ -252,13 +260,17 @@ function draw(ts, animation) {
timers.raf = false;
console.log('Last CPU Frametime: ' + Math.round((cpu_after - cpu_before) * 100) / 100 + 'ms');
console.debug('Last CPU Frametime: ' + Math.round((cpu_after - cpu_before) * 100) / 100 + 'ms');
if (zoom_target != zoom) {
update_canvas_zoom(zoom, zoom_target, animation ? dt : last_frame_dt);
schedule_draw(true);
}
if (tiles_left > 0) {
schedule_draw();
}
last_frame_dt = dt;
}
@ -392,3 +404,74 @@ function create_program(gl, vs, fs) { @@ -392,3 +404,74 @@ function create_program(gl, vs, fs) {
gl.deleteProgram(program);
}
function clip(quads) {
if (config.show_texture) {
return 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;
}

Loading…
Cancel
Save