GameEngineJava/profiler/vis/Code/WebGLFont.js

125 lines
4.4 KiB
JavaScript
Raw Normal View History

2023-11-13 21:31:36 +00:00
class glFont
{
constructor(gl)
{
// Offscreen canvas for rendering individual characters
this.charCanvas = document.createElement("canvas");
this.charContext = this.charCanvas.getContext("2d");
// Describe the font
const font_size = 9;
this.fontWidth = 5;
this.fontHeight = 13;
const font_face = "LocalFiraCode";
const font_desc = font_size + "px " + font_face;
// Ensure the CSS font is loaded before we do any work with it
const self = this;
document.fonts.load(font_desc).then(function (){
// Create a canvas atlas for all characters in the font
const atlas_canvas = document.createElement("canvas");
const atlas_context = atlas_canvas.getContext("2d");
atlas_canvas.width = 16 * self.fontWidth;
atlas_canvas.height = 16 * self.fontHeight;
// Add each character to the atlas
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-+=[]{};\'~#,./<>?!\"£$%%^&*()";
for (let char of chars)
{
// Render this character to the canvas on its own
self.RenderTextToCanvas(char, font_desc, self.fontWidth, self.fontHeight);
// Calculate a location for it in the atlas using its ASCII code
const ascii_code = char.charCodeAt(0);
assert(ascii_code < 256);
const y_index = Math.floor(ascii_code / 16);
const x_index = ascii_code - y_index * 16
assert(x_index < 16);
assert(y_index < 16);
// Copy into the atlas
atlas_context.drawImage(self.charCanvas, x_index * self.fontWidth, y_index * self.fontHeight);
}
// Create the atlas texture and store it in the destination object
self.atlasTexture = glCreateTexture(gl, atlas_canvas.width, atlas_canvas.height, atlas_canvas);
});
}
RenderTextToCanvas(text, font, width, height)
{
// Resize canvas to match
this.charCanvas.width = width;
this.charCanvas.height = height;
// Clear the background
this.charContext.fillStyle = "black";
this.charContext.clearRect(0, 0, width, height);
// TODO(don): I don't know why this results in the crispest text!
// Every pattern I've checked so far has thrown up no ideas... but it works, so it will do for now
let offset = 0.25;
if ("AFILMTWijmw4+{};\'#,.?!\"£*()".includes(text))
{
offset = 0.0;
}
// Render the text
this.charContext.font = font;
this.charContext.textAlign = "left";
this.charContext.textBaseline = "top";
this.charContext.fillText(text, offset, 2.5);
}
}
class glTextBuffer
{
constructor(gl, font)
{
this.font = font;
this.textMap = {};
this.textBuffer = new glDynamicBuffer(gl, glDynamicBufferType.Texture, gl.UNSIGNED_BYTE, 1, 8);
this.textBufferPos = 0;
this.textEncoder = new TextEncoder();
}
AddText(text)
{
// Return if it already exists
const existing_entry = this.textMap[text];
if (existing_entry != undefined)
{
return existing_entry;
}
// Add to the map
// Note we're leaving an extra NULL character before every piece of text so that the shader can sample into it on text
// boundaries and sample a zero colour for clamp.
let entry = {
offset: this.textBufferPos + 1,
length: text.length,
};
this.textMap[text] = entry;
// Ensure there's always enough space in the text buffer before adding
this.textBuffer.ResizeToFitNextPow2(entry.offset + entry.length + 1);
this.textBuffer.cpuArray.set(this.textEncoder.encode(text), entry.offset, entry.length);
this.textBuffer.dirty = true;
this.textBufferPos = entry.offset + entry.length;
return entry;
}
UploadData()
{
this.textBuffer.UploadDirtyData();
}
SetAsUniform(gl, program, name, index)
{
glSetUniform(gl, program, name, this.textBuffer.texture, index);
glSetUniform(gl, program, "inTextBufferDesc.textBufferLength", this.textBuffer.nbEntries);
}
}