Browse Source

Something like a CPU pipeline appears (missing: off by one, lanes, CPU perf, colors, text, UI, controls)

master
A.Olokhtonov 2 months ago
parent
commit
902a897852
  1. 5
      default.css
  2. 65
      geometry.js
  3. 2
      index.html
  4. 3
      index.js
  5. 29
      input.js
  6. 91
      parse.js
  7. 250
      render.js

5
default.css

@ -9,3 +9,8 @@ html, body {
body .main { body .main {
height: 100%; height: 100%;
} }
.main #c {
width: 100%;
height: 100%;
}

65
geometry.js

@ -0,0 +1,65 @@
let colors = {};
function get_color(stage_name) {
if (stage_name in colors) {
return colors[stage_name];
}
const r = Math.floor(Math.random() * 155);
const g = Math.floor(Math.random() * 155);
const b = Math.floor(Math.random() * 155);
colors[stage_name] = { 'r': 100 + r, 'g': 100 + g, 'b': 100 + b };
return colors[stage_name];
}
function generate(trace_id) {
const result = {
'count': 0,
};
const positions = [];
const sizes = [];
const colors = [];
let instructions = [];
if (trace_id in traces) {
instructions = traces[trace_id];
}
console.log(instructions);
let y = 0;
for (const instruction of instructions) {
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;
}
const color = get_color(stage.name);
sizes.push(stage_cycles * config.w, 1 * config.h);
positions.push(config.w * stage.c, config.h * y);
colors.push(color.r, color.g, color.b, 255);
result.count++;
}
++y;
}
result.pos = new Float32Array(positions);
result.size = new Float32Array(sizes);
result.color = new Uint8Array(colors);
return result;
}

2
index.html

@ -15,9 +15,11 @@
<script type="text/javascript" src="parse.js"></script> <script type="text/javascript" src="parse.js"></script>
<script type="text/javascript" src="render.js"></script> <script type="text/javascript" src="render.js"></script>
<script type="text/javascript" src="input.js"></script> <script type="text/javascript" src="input.js"></script>
<script type="text/javascript" src="geometry.js"></script>
</head> </head>
<body> <body>
<div class="main"> <div class="main">
<canvas id="c"></canvas>
</div> </div>
</body> </body>
</html> </html>

3
index.js

@ -1,5 +1,8 @@
document.addEventListener('DOMContentLoaded', main); document.addEventListener('DOMContentLoaded', main);
let traces = {};
function main() { function main() {
init_webgl();
init_listeners(); init_listeners();
} }

29
input.js

@ -1,6 +1,14 @@
function init_listeners() { function init_listeners() {
document.querySelector('.main').addEventListener('dragover', cancel); document.querySelector('.main').addEventListener('dragover', cancel);
document.querySelector('.main').addEventListener('drop', drop); document.querySelector('.main').addEventListener('drop', drop);
document.querySelector('.main').addEventListener('click', debug);
window.addEventListener('keydown', keydown);
window.addEventListener('keyup', keyup);
}
function debug() {
schedule_draw();
} }
function cancel(e) { function cancel(e) {
@ -8,6 +16,27 @@ function cancel(e) {
e.stopPropagation(); e.stopPropagation();
} }
function keydown(e) {
if (e.code === 'ArrowLeft') {
offset.x += 10 / scale;
} else if (e.code === 'ArrowRight') {
offset.x -= 10 / scale;
} else if (e.code === 'ArrowDown') {
offset.y -= 10 / scale;
} else if (e.code === 'ArrowUp') {
offset.y += 10 / scale;
} else if (e.code === 'PageUp') {
scale *= 0.9;
} else if (e.code === 'PageDown') {
scale *= 1.1;
}
schedule_draw();
}
function keyup(e) {
}
function drop(e) { function drop(e) {
e.preventDefault(); e.preventDefault();

91
parse.js

@ -5,16 +5,23 @@ function parse(text) {
let line_start = 0; let line_start = 0;
let line_index = 0; let line_index = 0;
let c = -1;
const instructions = [];
//console.log(text); //console.log(text);
for (let i = 0; i < text.length; ++i) { for (let i = 0; i < text.length; ++i) {
const c = text[i]; if (text[i] === '\n') {
if (line_index === 0) {
if (c === '\n') { line_start = i + 1;
line_index += 1;
continue;
}
// TODO: speed // TODO: speed
const line_copy = text.substring(line_start, i); const line_copy = text.substring(line_start, i);
const line_parts = line_copy.split(/\s+/); const line_parts = line_copy.split(/\t/);
if (line_parts.length === 0) { if (line_parts.length === 0) {
console.error('Parser error: empty line'); console.error('Parser error: empty line');
@ -23,30 +30,22 @@ function parse(text) {
const command = line_parts[0]; const command = line_parts[0];
switch (command) { switch (command) {
case 'Kanata': {
if (!assert_arglen(line_parts, 1, 'Kanata')) return false;
const version = Number(line_parts[1]);
if (version !== 4) {
console.error('Parser error: only Kanata traces version 4 are supported');
return false;
}
break;
}
case 'C=': { case 'C=': {
// Specify the number of cycles since the start of simulation.
if (!assert_arglen(line_parts, 1, 'C=')) return false; if (!assert_arglen(line_parts, 1, 'C=')) return false;
const cycles = Number(line_parts[1]); const cycles = Number(line_parts[1]);
c = cycles;
break; break;
} }
case 'C': { case 'C': {
// Specifies the number of elapsed cycles since the last output of any commands.
if (!assert_arglen(line_parts, 1, 'C')) return false; if (!assert_arglen(line_parts, 1, 'C')) return false;
const cycles = Number(line_parts[1]); const cycles = Number(line_parts[1]);
c += cycles;
break; break;
} }
@ -58,6 +57,16 @@ function parse(text) {
const insn_id_in_sim = Number(line_parts[2]); const insn_id_in_sim = Number(line_parts[2]);
const thread_id = Number(line_parts[3]); const thread_id = Number(line_parts[3]);
instructions[insn_id_in_file] = {
'cycle': c,
'file_id': insn_id_in_file,
'sim_id': insn_id_in_sim,
'thread_id': thread_id,
'text': '',
'popover_text': '',
'lanes': {},
};
break; break;
} }
@ -66,7 +75,18 @@ function parse(text) {
const id = Number(line_parts[1]); const id = Number(line_parts[1]);
const type = Number(line_parts[2]); const type = Number(line_parts[2]);
const text = line_parts.slice(3).join(' '); // TODO: preserve spacing, newlines const text = line_parts[3].replaceAll('\\n', '\n');
if (id in instructions) {
if (type === 0) {
instructions[id].text += text;
} else {
instructions[id].popover_text += text;
}
} else {
console.error('Parser error: label for an instruction that does not exist');
return false;
}
break; break;
} }
@ -78,6 +98,22 @@ function parse(text) {
const lane = Number(line_parts[2]); const lane = Number(line_parts[2]);
const stage = line_parts[3]; const stage = line_parts[3];
if (id in instructions) {
if (!(lane in instructions[id].lanes)) {
instructions[id].lanes[lane] = [];
}
instructions[id].lanes[lane].push({
'name': stage,
'c': c,
});
} else {
console.error('Parser error: pipeline start for an instruction that does not exist');
return false;
}
break; break;
} }
@ -88,6 +124,8 @@ function parse(text) {
const lane = Number(line_parts[2]); const lane = Number(line_parts[2]);
const stage = line_parts[3]; const stage = line_parts[3];
// This command can be ommited
break; break;
} }
@ -98,6 +136,18 @@ function parse(text) {
const retire_id = Number(line_parts[2]); const retire_id = Number(line_parts[2]);
const type = Number(line_parts[3]); const type = Number(line_parts[3]);
if (id in instructions) {
if (type === 0) {
instructions[id].retired = true;
} else {
instructions[id].retired = false;
}
instructions[id].retcyc = c;
} else {
console.error('Parser error: retire for an instruction that does not exist');
return false;
}
break; break;
} }
@ -107,6 +157,8 @@ function parse(text) {
const consumer_id = Number(line_parts[1]); const consumer_id = Number(line_parts[1]);
const producer_id = Number(line_parts[2]); const producer_id = Number(line_parts[2]);
const type = Number(line_parts[3]); const type = Number(line_parts[3]);
// TODO
break; break;
} }
@ -116,7 +168,6 @@ function parse(text) {
return false; return false;
} }
} }
//console.log(command);
line_start = i + 1; line_start = i + 1;
line_index += 1; line_index += 1;
@ -126,6 +177,10 @@ function parse(text) {
const after = performance.now(); const after = performance.now();
console.log(`Parsed in ${Math.round(after - before)}ms`); console.log(`Parsed in ${Math.round(after - before)}ms`);
traces['0'] = instructions;
return true;
} }
function assert_arglen(args, arglen, command) { function assert_arglen(args, arglen, command) {

250
render.js

@ -0,0 +1,250 @@
let programs = {};
let buffers = {};
let timers = {};
let config = {
bytes_per_quad: 20,
w: 24,
h: 24,
};
let canvas = null;
let gl = null;
let gpu_timer_ext = null;
let offset = { x: 0, y: 0 };
let scale = 1;
const tquad_vs_src = `#version 300 es
in vec2 a_pos;
in vec2 a_size;
in vec4 a_color;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform float u_scale;
out vec4 v_color;
void main() {
int vertex_index = gl_VertexID % 6;
vec2 corner;
if (vertex_index == 0) {
// "top left" aka "p1"
corner = a_pos + vec2(1.0);
} else if (vertex_index == 1 || vertex_index == 5) {
// "top right" aka "p2"
corner = a_pos + vec2(a_size.x - 1.0, 1.0);
} else if (vertex_index == 2 || vertex_index == 4) {
// "bottom left" aka "p3"
corner = a_pos + vec2(1.0, a_size.y - 1.0);
} else {
// "bottom right" aka "p4"
corner = a_pos + a_size - vec2(1.0);
}
vec2 screen02 = (corner.xy * vec2(u_scale) + u_translation) / u_res * 2.0;
screen02.y = 2.0 - screen02.y;
v_color = a_color;
gl_Position = vec4(screen02 - 1.0, 1.0, 1.0);
}
`;
const tquad_fs_src = `#version 300 es
precision highp float;
in vec4 v_color;
layout(location = 0) out vec4 FragColor;
void main() {
FragColor = v_color;
}
`;
function schedule_draw() {
if (!timers.raf) {
window.requestAnimationFrame(draw);
timers.raf = true;
}
}
function draw() {
const cpu_before = performance.now();
const width = window.innerWidth;
const height = window.innerHeight;
let query = null;
if (gpu_timer_ext !== null) {
query = gl.createQuery();
gl.beginQuery(gpu_timer_ext.TIME_ELAPSED_EXT, query);
}
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const quads = generate('0');
if (quads.count > 0) {
const program = programs['quad'];
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.uniform2f(program.locations['u_res'], canvas.width, canvas.height);
gl.uniform2f(program.locations['u_translation'], offset.x, offset.y);
gl.uniform1f(program.locations['u_scale'], scale);
gl.enableVertexAttribArray(program.locations['a_pos']);
gl.enableVertexAttribArray(program.locations['a_size']);
gl.enableVertexAttribArray(program.locations['a_color']);
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.vertexAttribDivisor(program.locations['a_pos'], 1);
gl.vertexAttribDivisor(program.locations['a_size'], 1);
gl.vertexAttribDivisor(program.locations['a_color'], 1);
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, quads.count);
gl.vertexAttribDivisor(program.locations['a_pos'], 0);
gl.vertexAttribDivisor(program.locations['a_size'], 0);
gl.vertexAttribDivisor(program.locations['a_color'], 0);
}
if (gpu_timer_ext) {
gl.endQuery(gpu_timer_ext.TIME_ELAPSED_EXT);
const next_tick = () => {
if (query) {
const available = gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
const disjoint = gl.getParameter(gpu_timer_ext.GPU_DISJOINT_EXT);
if (available && !disjoint) {
const timeElapsed = gl.getQueryParameter(query, gl.QUERY_RESULT);
console.log('Last GPU Frametime: ' + Math.round(timeElapsed / 10000) / 100 + 'ms');
}
if (available || disjoint) {
gl.deleteQuery(query);
query = null;
} else if (!available) {
setTimeout(next_tick, 0);
}
}
}
setTimeout(next_tick, 0);
}
const cpu_after = performance.now();
timers.raf = false;
console.log('Last CPU Frametime: ' + Math.round((cpu_after - cpu_before) * 100) / 100 + 'ms');
}
function init_webgl() {
canvas = document.querySelector('#c');
gl = canvas.getContext('webgl2');
gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (gpu_timer_ext === null) {
gpu_timer_ext = gl.getExtension('EXT_disjoint_timer_query');
}
const quad_vs = create_shader(gl, gl.VERTEX_SHADER, tquad_vs_src);
const quad_fs = create_shader(gl, gl.FRAGMENT_SHADER, tquad_fs_src);
programs = {
'quad': create_program(gl, quad_vs, quad_fs),
};
buffers = {
'b_packed': gl.createBuffer(),
};
const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];
let width;
let height;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else if (entry.contentBoxSize) {
// fallback for Safari that will not always be correct
width = Math.round(entry.contentBoxSize[0].inlineSize * devicePixelRatio);
height = Math.round(entry.contentBoxSize[0].blockSize * devicePixelRatio);
}
canvas.width = width;
canvas.height = height;
schedule_draw();
}
const resize_observer = new ResizeObserver(resize_canvas);
resize_observer.observe(canvas);
}
function create_shader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
return shader;
}
console.error(type, ':', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
function create_program(gl, vs, fs) {
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (gl.getProgramParameter(program, gl.LINK_STATUS)) {
// src: tiny-sdf
// https://github.com/mapbox/tiny-sdf
const wrapper = {program};
const num_attrs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
const num_uniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
wrapper.locations = {};
for (let i = 0; i < num_attrs; i++) {
const attribute = gl.getActiveAttrib(program, i);
wrapper.locations[attribute.name] = gl.getAttribLocation(program, attribute.name);
}
for (let i = 0; i < num_uniforms; i++) {
const uniform = gl.getActiveUniform(program, i);
wrapper.locations[uniform.name] = gl.getUniformLocation(program, uniform.name);
}
return wrapper;
}
console.error('link:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
Loading…
Cancel
Save