diff --git a/geometry.js b/geometry.js index c69a0aa..1ab0134 100644 --- a/geometry.js +++ b/geometry.js @@ -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; diff --git a/parse.js b/import_worker.js similarity index 50% rename from parse.js rename to import_worker.js index 3f34916..141a4ce 100644 --- a/parse.js +++ b/import_worker.js @@ -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) { //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) { } } - 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) { 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; +} + diff --git a/index.html b/index.html index 7d17418..5161125 100644 --- a/index.html +++ b/index.html @@ -12,12 +12,11 @@ - - +
diff --git a/index.js b/index.js index 3ede6a4..acb0b96 100644 --- a/index.js +++ b/index.js @@ -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(); diff --git a/input.js b/input.js index 5969365..b3bd6c8 100644 --- a/input.js +++ b/input.js @@ -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)) { - jump_to_first_instruction(); - schedule_draw(); - } + 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'); } }; diff --git a/rasterizer.js b/rasterizer.js index c3247a3..52e82bc 100644 --- a/rasterizer.js +++ b/rasterizer.js @@ -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) { 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; +} diff --git a/render.js b/render.js index 8e6627b..d6ebbdf 100644 --- a/render.js +++ b/render.js @@ -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 = { '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 = { 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; 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) { 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) { 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) { 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) { 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; +}