Browse Source

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

master
A.Olokhtonov 5 months ago
parent
commit
94a7e1dbd5
  1. 10
      default.css
  2. 74
      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 { @@ -8,9 +8,19 @@ html, body {
body .main {
height: 100%;
position: relative;
}
.main #c {
width: 100%;
height: 100%;
}
.main #offscreen {
position: absolute;
top: -999px;
left: -999px;
width: 128px;
height: 128px;
z-index: 1;
}

74
geometry.js

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
let colors = {};
let rasterized = {};
function get_color(stage_name) {
if (stage_name in config.predefined_colors) {
@ -18,7 +19,57 @@ function get_color(stage_name) { @@ -18,7 +19,57 @@ function get_color(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) {
const before = performance.now();
const result = {
'count': 0,
};
@ -26,6 +77,7 @@ function generate(trace_id) { @@ -26,6 +77,7 @@ function generate(trace_id) {
const positions = [];
const sizes = [];
const colors = [];
const uvs = [];
let instructions = {};
@ -56,9 +108,12 @@ function generate(trace_id) { @@ -56,9 +108,12 @@ function generate(trace_id) {
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);
positions.push(config.w * stage.c, config.h * y);
colors.push(r, g, b, 255);
uvs.push(u, v);
result.count++;
@ -70,9 +125,22 @@ function generate(trace_id) { @@ -70,9 +125,22 @@ function generate(trace_id) {
++y;
}
result.pos = new Float32Array(positions);
result.size = new Float32Array(sizes);
result.color = new Uint8Array(colors);
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.size = new Float32Array(sizes);
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;
}

2
index.html

@ -16,10 +16,12 @@ @@ -16,10 +16,12 @@
<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>
</head>
<body>
<div class="main">
<canvas id="c"></canvas>
<canvas id="offscreen" width="128" height="128"></canvas>
</div>
</body>
</html>

1
index.js

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

10
input.js

@ -6,7 +6,7 @@ function init_listeners() { @@ -6,7 +6,7 @@ function init_listeners() {
document.querySelector('#c').addEventListener('mousedown', mousedown);
document.querySelector('#c').addEventListener('mousemove', mousemove);
document.querySelector('#c').addEventListener('mouseup', mouseup);
document.querySelector('#c').addEventListener('mouseleave', mouseup);
document.querySelector('#c').addEventListener('mouseleave', mouseleave);
document.querySelector('#c').addEventListener('wheel', wheel);
window.addEventListener('keydown', keydown);
@ -43,6 +43,12 @@ function mousemove(e) { @@ -43,6 +43,12 @@ function mousemove(e) {
}
}
function mouseleave(e) {
if (moving) {
moving = false;
}
}
function mouseup(e) {
if (e.button === 1 && moving) {
moving = false;
@ -53,7 +59,7 @@ function wheel(e) { @@ -53,7 +59,7 @@ 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;
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_target = Math.pow(1.0 + dz, Math.abs(zoom_level))

15
rasterizer.js

@ -0,0 +1,15 @@ @@ -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 @@ @@ -1,10 +1,11 @@
let programs = {};
let buffers = {};
let textures = {};
let timers = {};
let config = {
bytes_per_quad: 20,
w: 24,
h: 24,
bytes_per_quad: 28,
w: 32,
h: 32,
predefined_colors: {
'Np': [75, 62, 143],
@ -25,6 +26,7 @@ let config = { @@ -25,6 +26,7 @@ let config = {
limit: -1,
zoom_delta: 0.05,
raster_texture_size: 4096,
};
let canvas = null;
@ -38,39 +40,58 @@ let zoom_level = 0; @@ -38,39 +40,58 @@ 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 };
const tquad_vs_src = `#version 300 es
in vec2 a_pos;
in vec2 a_size;
in vec4 a_color;
in vec2 a_uv;
uniform vec2 u_res;
uniform vec2 u_translation;
uniform float u_scale;
uniform vec2 u_textile;
uniform vec2 u_tile;
out vec4 v_color;
out vec2 v_uv;
void main() {
int vertex_index = gl_VertexID % 6;
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) {
// "top left" aka "p1"
corner = a_pos;
corner = a_pos + inset;
uv = a_uv;
} else if (vertex_index == 1 || vertex_index == 5) {
// "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) {
// "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 {
// "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;
screen02.y = 2.0 - screen02.y;
v_color = a_color;
v_uv = uv - u_textile * vec2(0.2);
gl_Position = vec4(screen02 - 1.0, 1.0, 1.0);
}
@ -80,11 +101,15 @@ const tquad_fs_src = `#version 300 es @@ -80,11 +101,15 @@ const tquad_fs_src = `#version 300 es
precision highp float;
in vec4 v_color;
in vec2 v_uv;
uniform sampler2D u_texture;
layout(location = 0) out vec4 FragColor;
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) { @@ -129,28 +154,37 @@ function draw(ts, animation) {
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);
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'], 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_size']);
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_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.vertexAttribDivisor(program.locations['a_pos'], 1);
gl.vertexAttribDivisor(program.locations['a_size'], 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.vertexAttribDivisor(program.locations['a_pos'], 0);
gl.vertexAttribDivisor(program.locations['a_size'], 0);
gl.vertexAttribDivisor(program.locations['a_color'], 0);
gl.vertexAttribDivisor(program.locations['a_uv'], 0);
}
if (gpu_timer_ext) {
@ -227,6 +261,17 @@ function init_webgl() { @@ -227,6 +261,17 @@ function init_webgl() {
'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) => {
// https://www.khronos.org/webgl/wiki/HandlingHighDPI
const entry = entries[0];

Loading…
Cancel
Save