mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Create AtlasPage
This commit is contained in:
@@ -77,6 +77,7 @@ void main() {
|
||||
const INDICES_PER_CELL = 11;
|
||||
const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT;
|
||||
const CELL_POSITION_INDICES = 2;
|
||||
const MAX_ATLAS_PAGES = 8;
|
||||
|
||||
// Work variables to avoid garbage collection
|
||||
let $i = 0;
|
||||
@@ -90,8 +91,7 @@ export class GlyphRenderer extends Disposable {
|
||||
private readonly _projectionLocation: WebGLUniformLocation;
|
||||
private readonly _resolutionLocation: WebGLUniformLocation;
|
||||
private readonly _textureLocation: WebGLUniformLocation;
|
||||
private readonly _atlasTexture: WebGLTexture;
|
||||
private readonly _atlasTexture1: WebGLTexture;
|
||||
private readonly _atlasTextures: WebGLTexture[];
|
||||
private readonly _attributesBuffer: WebGLBuffer;
|
||||
|
||||
private _atlas: ITextureAtlas | undefined;
|
||||
@@ -166,22 +166,18 @@ export class GlyphRenderer extends Disposable {
|
||||
gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 9 * Float32Array.BYTES_PER_ELEMENT);
|
||||
gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1);
|
||||
|
||||
// Setup empty texture atlas
|
||||
this._atlasTexture = throwIfFalsy(gl.createTexture());
|
||||
this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture)));
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]));
|
||||
|
||||
this._atlasTexture1 = throwIfFalsy(gl.createTexture());
|
||||
this.register(toDisposable(() => gl.deleteTexture(this._atlasTexture1)));
|
||||
gl.activeTexture(gl.TEXTURE0 + 1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
|
||||
// Setup empty textures for all potential atlas pages
|
||||
this._atlasTextures = [];
|
||||
for (let i = 0; i < MAX_ATLAS_PAGES; i++) {
|
||||
const texture = throwIfFalsy(gl.createTexture());
|
||||
this.register(toDisposable(() => gl.deleteTexture(texture)));
|
||||
gl.activeTexture(gl.TEXTURE0 + i);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
|
||||
this._atlasTextures[i] = texture;
|
||||
}
|
||||
|
||||
// Allow drawing of transparent texture
|
||||
gl.enable(gl.BLEND);
|
||||
@@ -334,15 +330,17 @@ export class GlyphRenderer extends Disposable {
|
||||
const layerTextureUnits = new Int32Array([0, 1]);
|
||||
gl.uniform1iv(this._textureLocation, layerTextureUnits);
|
||||
gl.activeTexture(gl.TEXTURE0 + 0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
|
||||
// TODO: Check if the particular texture page changed
|
||||
gl.activeTexture(gl.TEXTURE0 + 1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas1!);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
if (this._atlas.pages.length > 1) {
|
||||
// TODO: Check if the particular texture page changed
|
||||
gl.activeTexture(gl.TEXTURE0 + 1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.pages[1]);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -359,18 +357,20 @@ export class GlyphRenderer extends Disposable {
|
||||
this._atlas = atlas;
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[0]);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0 + 1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture1);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas1!);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
if (atlas.pages.length > 1) {
|
||||
gl.activeTexture(gl.TEXTURE0 + 1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, this._atlasTextures[1]);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.pages[1]);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
|
||||
public setDimensions(dimensions: IRenderDimensions): void {
|
||||
|
||||
@@ -56,10 +56,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
private _cacheMapCombined: FourKeyMap<string, number, number, number, IRasterizedGlyph> = new FourKeyMap();
|
||||
|
||||
// The texture that the atlas is drawn to
|
||||
public cacheCanvas: HTMLCanvasElement;
|
||||
public cacheCanvas1: HTMLCanvasElement | undefined;
|
||||
private _cacheCtx: CanvasRenderingContext2D;
|
||||
private _cacheCtx1: CanvasRenderingContext2D | undefined;
|
||||
private _pages: AtlasPage[] = [];
|
||||
public get pages(): HTMLCanvasElement[] { return this._pages.map(e => e.canvas); }
|
||||
|
||||
public get cacheCanvas(): HTMLCanvasElement { return this._pages[0].canvas; }
|
||||
|
||||
private _tmpCanvas: HTMLCanvasElement;
|
||||
// A temporary context that glyphs are drawn to before being transfered to the atlas.
|
||||
@@ -92,18 +92,9 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
private readonly _config: ICharAtlasConfig,
|
||||
private readonly _unicodeService: IUnicodeService
|
||||
) {
|
||||
this.cacheCanvas = this._createCanvas(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
||||
// It might also contain some characters with transparent backgrounds if allowTransparency is
|
||||
// set.
|
||||
this._cacheCtx = throwIfFalsy(this.cacheCanvas.getContext('2d', { alpha: true }));
|
||||
|
||||
this.cacheCanvas1 = this._createCanvas(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheCtx1 = throwIfFalsy(this.cacheCanvas1.getContext('2d', { alpha: true }));
|
||||
this._cacheCtx1.fillStyle = 'rgb(255, 255, 0)';
|
||||
this._cacheCtx1.fillRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
|
||||
this._tmpCanvas = this._createCanvas(
|
||||
this._pages.push(new AtlasPage(document));
|
||||
this._tmpCanvas = createCanvas(
|
||||
document,
|
||||
this._config.deviceCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2,
|
||||
this._config.deviceCellHeight + TMP_CANVAS_GLYPH_PADDING * 2
|
||||
);
|
||||
@@ -113,13 +104,6 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}));
|
||||
}
|
||||
|
||||
private _createCanvas(width: number, height: number): HTMLCanvasElement {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.cacheCanvas.parentElement) {
|
||||
this.cacheCanvas.parentElement.removeChild(this.cacheCanvas);
|
||||
@@ -159,7 +143,9 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
if (this._currentRow.x === 0 && this._currentRow.y === 0) {
|
||||
return;
|
||||
}
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
for (const page of this._pages) {
|
||||
page.clear();
|
||||
}
|
||||
this._cacheMap.clear();
|
||||
this._cacheMapCombined.clear();
|
||||
this._currentRow.x = 0;
|
||||
@@ -689,7 +675,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
activeRow.x += rasterizedGlyph.size.x;
|
||||
|
||||
// putImageData doesn't do any blending, so it will overwrite any existing cache entry for us
|
||||
this._cacheCtx.putImageData(
|
||||
this._pages[0].ctx.putImageData(
|
||||
imageData,
|
||||
rasterizedGlyph.texturePosition.x - this._workBoundingBox.left,
|
||||
rasterizedGlyph.texturePosition.y - this._workBoundingBox.top,
|
||||
@@ -792,6 +778,23 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
class AtlasPage {
|
||||
public readonly canvas: HTMLCanvasElement;
|
||||
public readonly ctx: CanvasRenderingContext2D;
|
||||
|
||||
constructor(document: Document) {
|
||||
this.canvas = createCanvas(document, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
||||
// It might also contain some characters with transparent backgrounds if allowTransparency is
|
||||
// set.
|
||||
this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.ctx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a particular rgb color and colors that are nearly the same in an ImageData completely
|
||||
* transparent.
|
||||
@@ -846,3 +849,10 @@ function checkCompletelyTransparent(imageData: ImageData): boolean {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function createCanvas(document: Document, width: number, height: number): HTMLCanvasElement {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
return canvas;
|
||||
}
|
||||
|
||||
+3
-1
@@ -87,8 +87,10 @@ export interface IRenderer extends IDisposable {
|
||||
}
|
||||
|
||||
export interface ITextureAtlas extends IDisposable {
|
||||
/** @deprecated */
|
||||
readonly cacheCanvas: HTMLCanvasElement;
|
||||
readonly cacheCanvas1: HTMLCanvasElement | undefined;
|
||||
|
||||
readonly pages: HTMLCanvasElement[];
|
||||
|
||||
hasCanvasChanged: boolean;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user