Browse Source

Quite awful text rendering is in (texture atlas filled by Canvas2D)

master
A.Olokhtonov 2 months ago
parent
commit
94a7e1dbd5
  1. 10
      default.css
  2. 68
      geometry.js
  3. 2
      index.html
  4. 1
      index.js
  5. 10
      input.js
  6. 15
      rasterizer.js
  7. 61
      render.js

10
default.css

@ -8,9 +8,19 @@ html, body {
body .main { body .main {
height: 100%; height: 100%;
position: relative;
} }
.main #c { .main #c {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.main #offscreen {
position: absolute;
top: -999px;
left: -999px;
width: 128px;
height: 128px;
z-index: 1;
}

68
geometry.js

@ -1,4 +1,5 @@
let colors = {}; let colors = {};
let rasterized = {};
function get_color(stage_name) { function get_color(stage_name) {
if (stage_name in config.predefined_colors) { if (stage_name in config.predefined_colors) {
@ -18,7 +19,57 @@ function get_color(stage_name) {
return colors[stage_name]; return colors[stage_name];
} }
function rasterize_and_pack(text, cycles) {
// TODO: handle texture is full or stuff don't fit (unlikely)
const key = text + '@' + cycles;
if (key in rasterized) {
return rasterized[key];
}
const tiles_needed = cycles - 1 + 1; // stage name + count from one
if (tiles_needed > config.raster_texture_size / config.w - raster_tile.x) {
raster_tile.x = 0;
raster_tile.y += 1;
}
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;
for (let i = 1; i <= cycles; ++i) {
rasterize(i);
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];
return [u, v];
}
function generate(trace_id) { function generate(trace_id) {
const before = performance.now();
const result = { const result = {
'count': 0, 'count': 0,
}; };
@ -26,6 +77,7 @@ function generate(trace_id) {
const positions = []; const positions = [];
const sizes = []; const sizes = [];
const colors = []; const colors = [];
const uvs = [];
let instructions = {}; let instructions = {};
@ -56,9 +108,12 @@ function generate(trace_id) {
b = Math.max(50, b - 50); b = Math.max(50, b - 50);
} }
const [u, v] = rasterize_and_pack(stage.name, stage_cycles);
sizes.push(stage_cycles * config.w, 1 * config.h); sizes.push(stage_cycles * config.w, 1 * config.h);
positions.push(config.w * stage.c, config.h * y); positions.push(config.w * stage.c, config.h * y);
colors.push(r, g, b, 255); colors.push(r, g, b, 255);
uvs.push(u, v);
result.count++; result.count++;
@ -70,9 +125,22 @@ function generate(trace_id) {
++y; ++y;
} }
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;
} else {
result.pos = new Float32Array(positions); result.pos = new Float32Array(positions);
result.size = new Float32Array(sizes); result.size = new Float32Array(sizes);
result.color = new Uint8Array(colors); result.color = new Uint8Array(colors);
result.uv = new Float32Array(uvs);
}
const after = performance.now();
console.log(`Generated geometry in ${Math.round(after - before)}ms`);
return result; return result;
} }

2
index.html

@ -16,10 +16,12 @@
<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> <script type="text/javascript" src="geometry.js"></script>
<script type="text/javascript" src="rasterizer.js"></script>
</head> </head>
<body> <body>
<div class="main"> <div class="main">
<canvas id="c"></canvas> <canvas id="c"></canvas>
<canvas id="offscreen" width="128" height="128"></canvas>
</div> </div>
</body> </body>
</html> </html>

1
index.js

@ -4,5 +4,6 @@ let traces = {};
function main() { function main() {
init_webgl(); init_webgl();
init_rasterizer();
init_listeners(); init_listeners();
} }

10
input.js

@ -6,7 +6,7 @@ function init_listeners() {
document.querySelector('#c').addEventListener('mousedown', mousedown); document.querySelector('#c').addEventListener('mousedown', mousedown);
document.querySelector('#c').addEventListener('mousemove', mousemove); document.querySelector('#c').addEventListener('mousemove', mousemove);
document.querySelector('#c').addEventListener('mouseup', mouseup); document.querySelector('#c').addEventListener('mouseup', mouseup);
document.querySelector('#c').addEventListener('mouseleave', mouseup); document.querySelector('#c').addEventListener('mouseleave', mouseleave);
document.querySelector('#c').addEventListener('wheel', wheel); document.querySelector('#c').addEventListener('wheel', wheel);
window.addEventListener('keydown', keydown); window.addEventListener('keydown', keydown);
@ -43,6 +43,12 @@ function mousemove(e) {
} }
} }
function mouseleave(e) {
if (moving) {
moving = false;
}
}
function mouseup(e) { function mouseup(e) {
if (e.button === 1 && moving) { if (e.button === 1 && moving) {
moving = false; moving = false;
@ -53,7 +59,7 @@ function wheel(e) {
const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY}; const screenp = {'x': window.devicePixelRatio * e.clientX, 'y': window.devicePixelRatio * e.clientY};
const zooming_in = e.deltaY < 0; const zooming_in = e.deltaY < 0;
const level = zooming_in ? zoom_level + 2 : zoom_level - 2; const level = zooming_in ? zoom_level + 2 : zoom_level - 2;
const dz = (zoom_level > 0 ? config.zoom_delta : -config.zoom_delta); const dz = (level > 0 ? config.zoom_delta : -config.zoom_delta);
zoom_level = level; zoom_level = level;
zoom_target = Math.pow(1.0 + dz, Math.abs(zoom_level)) zoom_target = Math.pow(1.0 + dz, Math.abs(zoom_level))

15
rasterizer.js

@ -0,0 +1,15 @@
// TODO: move this to a worker
let c2d = null;
function init_rasterizer() {
c2d = document.querySelector('#offscreen').getContext('2d');
c2d.font = '14px monospace';
c2d.fillStyle = 'white';
}
function rasterize(text) {
c2d.clearRect(0, 0, c2d.canvas.width, c2d.canvas.height);
c2d.fillText(text, 0, 14);
//c2d.fillRect(0, 0, 32, 32);
}

61
render.js

@ -1,10 +1,11 @@
let programs = {}; let programs = {};
let buffers = {}; let buffers = {};
let textures = {};
let timers = {}; let timers = {};
let config = { let config = {
bytes_per_quad: 20, bytes_per_quad: 28,
w: 24, w: 32,
h: 24, h: 32,
predefined_colors: { predefined_colors: {
'Np': [75, 62, 143], 'Np': [75, 62, 143],
@ -25,6 +26,7 @@ let config = {
limit: -1, limit: -1,
zoom_delta: 0.05, zoom_delta: 0.05,
raster_texture_size: 4096,
}; };
let canvas = null; let canvas = null;
@ -38,39 +40,58 @@ let zoom_level = 0;
let zoom_screenp = { 'x': 0, 'y': 0 }; let zoom_screenp = { 'x': 0, 'y': 0 };
let last_frame_dt = 0; let last_frame_dt = 0;
let last_frame_ts = 0; let last_frame_ts = 0;
let raster_tile = { 'x': 0, 'y': 0 };
const tquad_vs_src = `#version 300 es const tquad_vs_src = `#version 300 es
in vec2 a_pos; in vec2 a_pos;
in vec2 a_size; in vec2 a_size;
in vec4 a_color; in vec4 a_color;
in vec2 a_uv;
uniform vec2 u_res; uniform vec2 u_res;
uniform vec2 u_translation; uniform vec2 u_translation;
uniform float u_scale; uniform float u_scale;
uniform vec2 u_textile;
uniform vec2 u_tile;
out vec4 v_color; out vec4 v_color;
out vec2 v_uv;
void main() { void main() {
int vertex_index = gl_VertexID % 6; int vertex_index = gl_VertexID % 6;
vec2 corner; vec2 corner;
vec2 uv;
vec2 inset = vec2(1.0);
// "Fix" for zero-width stages
if (a_size.x < 0.1) {
inset = vec2(0.0);
}
vec2 cycles = a_size / u_tile;
if (vertex_index == 0) { if (vertex_index == 0) {
// "top left" aka "p1" // "top left" aka "p1"
corner = a_pos; corner = a_pos + inset;
uv = a_uv;
} else if (vertex_index == 1 || vertex_index == 5) { } else if (vertex_index == 1 || vertex_index == 5) {
// "top right" aka "p2" // "top right" aka "p2"
corner = a_pos + vec2(a_size.x, 0); corner = a_pos + vec2(a_size.x, 0) + vec2(-inset.x, inset.y);
uv = a_uv + vec2(u_textile.x * cycles.x, 0);
} else if (vertex_index == 2 || vertex_index == 4) { } else if (vertex_index == 2 || vertex_index == 4) {
// "bottom left" aka "p3" // "bottom left" aka "p3"
corner = a_pos + vec2(0, a_size.y); corner = a_pos + vec2(0, a_size.y) + vec2(inset.x, -inset.y);
uv = a_uv + vec2(0, u_textile.y * cycles.y);
} else { } else {
// "bottom right" aka "p4" // "bottom right" aka "p4"
corner = a_pos + a_size; corner = a_pos + a_size - inset;
uv = a_uv + u_textile * cycles;
} }
vec2 screen02 = (corner.xy * vec2(u_scale) + u_translation) / u_res * 2.0; vec2 screen02 = (corner.xy * vec2(u_scale) + u_translation) / u_res * 2.0;
screen02.y = 2.0 - screen02.y; screen02.y = 2.0 - screen02.y;
v_color = a_color; v_color = a_color;
v_uv = uv - u_textile * vec2(0.2);
gl_Position = vec4(screen02 - 1.0, 1.0, 1.0); gl_Position = vec4(screen02 - 1.0, 1.0, 1.0);
} }
@ -80,11 +101,15 @@ const tquad_fs_src = `#version 300 es
precision highp float; precision highp float;
in vec4 v_color; in vec4 v_color;
in vec2 v_uv;
uniform sampler2D u_texture;
layout(location = 0) out vec4 FragColor; layout(location = 0) out vec4 FragColor;
void main() { void main() {
FragColor = v_color; vec4 text = texture(u_texture, v_uv);
FragColor = vec4(text.rgb * text.a + v_color.rgb * (1.0 - text.a), 1.0);
} }
`; `;
@ -129,28 +154,37 @@ function draw(ts, animation) {
gl.bufferSubData(gl.ARRAY_BUFFER, 0, quads.pos); 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);
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);
gl.bufferSubData(gl.ARRAY_BUFFER, quads.pos.byteLength + quads.size.byteLength + quads.color.byteLength, quads.uv);
gl.uniform2f(program.locations['u_res'], canvas.width, canvas.height); gl.uniform2f(program.locations['u_res'], canvas.width, canvas.height);
gl.uniform2f(program.locations['u_translation'], offset.x, offset.y); gl.uniform2f(program.locations['u_translation'], offset.x, offset.y);
gl.uniform1f(program.locations['u_scale'], zoom); gl.uniform1f(program.locations['u_scale'], zoom);
gl.uniform2f(program.locations['u_textile'], config.w / config.raster_texture_size, config.h / config.raster_texture_size);
gl.uniform1i(program.locations['u_texture'], textures['raster']);
gl.uniform2f(program.locations['u_tile'], config.w, config.h);
gl.enableVertexAttribArray(program.locations['a_pos']); gl.enableVertexAttribArray(program.locations['a_pos']);
gl.enableVertexAttribArray(program.locations['a_size']); gl.enableVertexAttribArray(program.locations['a_size']);
gl.enableVertexAttribArray(program.locations['a_color']); gl.enableVertexAttribArray(program.locations['a_color']);
gl.enableVertexAttribArray(program.locations['a_uv']);
gl.vertexAttribPointer(program.locations['a_pos'], 2, gl.FLOAT, false, 2 * 4, 0); 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_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_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.vertexAttribDivisor(program.locations['a_pos'], 1); gl.vertexAttribDivisor(program.locations['a_pos'], 1);
gl.vertexAttribDivisor(program.locations['a_size'], 1); gl.vertexAttribDivisor(program.locations['a_size'], 1);
gl.vertexAttribDivisor(program.locations['a_color'], 1); gl.vertexAttribDivisor(program.locations['a_color'], 1);
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, quads.count);
gl.vertexAttribDivisor(program.locations['a_pos'], 0); gl.vertexAttribDivisor(program.locations['a_pos'], 0);
gl.vertexAttribDivisor(program.locations['a_size'], 0); gl.vertexAttribDivisor(program.locations['a_size'], 0);
gl.vertexAttribDivisor(program.locations['a_color'], 0); gl.vertexAttribDivisor(program.locations['a_color'], 0);
gl.vertexAttribDivisor(program.locations['a_uv'], 0);
} }
if (gpu_timer_ext) { if (gpu_timer_ext) {
@ -227,6 +261,17 @@ function init_webgl() {
'b_packed': gl.createBuffer(), 'b_packed': gl.createBuffer(),
}; };
textures = {
'raster': gl.createTexture(),
};
const zeroes = new Uint8Array(config.raster_texture_size * config.raster_texture_size * 4);
gl.bindTexture(gl.TEXTURE_2D, textures['raster']);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, config.raster_texture_size, config.raster_texture_size, 0, gl.RGBA, gl.UNSIGNED_BYTE, zeroes); // fill the whole texture once with zeroes to kill a warning about a partial upload
const resize_canvas = (entries) => { const resize_canvas = (entries) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI // https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0]; const entry = entries[0];

Loading…
Cancel
Save