Use variants for underline dotted.

webgl
This commit is contained in:
tisilent
2023-08-21 19:09:02 +08:00
parent 6f0a5c9f0c
commit 32da1d81d5
8 changed files with 123 additions and 34 deletions
@@ -375,9 +375,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
let glyph: IRasterizedGlyph;
if (chars && chars.length > 1) {
glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, 0, true);
} else {
glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, 0, true);
}
if (!glyph.size.x || !glyph.size.y) {
return;
@@ -215,15 +215,15 @@ export class GlyphRenderer extends Disposable {
}
@traceCall
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number, variantOffset: number): void {
// Since this function is called for every cell (`rows*cols`), it must be very optimized. It
// should not instantiate any variables unless a new glyph is drawn to the cache where the
// slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
// each glyph.
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg);
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg, variantOffset);
}
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number, variantOffset: number): void {
$i = (y * this._terminal.cols + x) * INDICES_PER_CELL;
// Exit early if this is a null character, allow space character to continue as it may have
@@ -239,9 +239,9 @@ export class GlyphRenderer extends Disposable {
// Get the glyph
if (chars && chars.length > 1) {
$glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, false);
$glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext, variantOffset, false);
} else {
$glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, false);
$glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext, variantOffset, false);
}
$leftCellPadding = Math.floor((this._dimensions.device.cell.width - this._dimensions.device.char.width) / 2);
+3 -1
View File
@@ -8,7 +8,7 @@ import { ITerminal } from 'browser/Types';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { getSafariVersion, isSafari } from 'common/Platform';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
import { ICoreService, IDecorationService, ILogService, IOptionsService, IUnicodeService } from 'common/services/Services';
import { ITerminalAddon, Terminal } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
import { setTraceLogger } from 'common/services/LogService';
@@ -54,6 +54,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon {
const decorationService: IDecorationService = unsafeCore._decorationService;
const logService: ILogService = unsafeCore._logService;
const themeService: IThemeService = unsafeCore._themeService;
const unicodeService: IUnicodeService = unsafeCore.unicodeService;
// Set trace logger just in case it hasn't been yet which could happen when the addon is
// bundled separately to the core module
@@ -68,6 +69,7 @@ export class WebglAddon extends Disposable implements ITerminalAddon {
decorationService,
optionsService,
themeService,
unicodeService,
this._preserveDrawingBuffer
));
this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss));
+42 -4
View File
@@ -14,10 +14,10 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeS
import { ITerminal } from 'browser/Types';
import { AttributeData } from 'common/buffer/AttributeData';
import { CellData } from 'common/buffer/CellData';
import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { Attributes, Content, ExtFlags, NULL_CELL_CHAR, NULL_CELL_CODE, UnderlineStyle } from 'common/buffer/Constants';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { Disposable, MutableDisposable, getDisposeArrayDisposable, toDisposable } from 'common/Lifecycle';
import { ICoreService, IDecorationService, ILogService, IOptionsService } from 'common/services/Services';
import { ICoreService, IDecorationService, ILogService, IOptionsService, IUnicodeService } from 'common/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { IDisposable, Terminal } from 'xterm';
import { GlyphRenderer } from './GlyphRenderer';
@@ -71,6 +71,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private readonly _decorationService: IDecorationService,
private readonly _optionsService: IOptionsService,
private readonly _themeService: IThemeService,
private readonly _unicodeService: IUnicodeService,
preserveDrawingBuffer?: boolean
) {
super();
@@ -395,6 +396,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
let i: number;
let x: number;
let j: number;
let variantOffset: number = -1;
start = clamp(start, terminal.rows - 1, 0);
end = clamp(end, terminal.rows - 1, 0);
@@ -409,11 +411,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._model.cursor = undefined;
let modelUpdated = false;
const fontSize = this._optionsService.rawOptions.fontSize;
const drp = this._coreBrowserService.dpr;
const lineWidth = Math.max(1, Math.floor(fontSize * drp / 15));
const deviceCellWidth = this._charAtlas?.getDeviceCellWidth();
for (y = start; y <= end; y++) {
row = y + terminal.buffer.ydisp;
line = terminal.buffer.lines.get(row)!;
this._model.lineLengths[y] = 0;
joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
// row start variant init
variantOffset = 0;
for (x = 0; x < terminal.cols; x++) {
lastBg = this._cellColorResolver.result.bg;
line.loadCell(x, cell);
@@ -447,6 +456,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
chars = cell.getChars();
code = cell.getCode();
let chWidth: number;
if (typeof code === 'number') {
chWidth = this._unicodeService.wcwidth(code);
} else {
chWidth = this._unicodeService.getStringCellWidth(code);
}
i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
// Load colors/resolve overrides into work colors
@@ -480,13 +497,27 @@ export class WebglRenderer extends Disposable implements IRenderer {
if (code !== NULL_CELL_CODE) {
this._model.lineLengths[y] = x + 1;
} else {
if (cell.extended.underlineStyle !== UnderlineStyle.DOTTED) {
variantOffset = 0;
}
}
this._cellColorResolver.result.ext &= ~ExtFlags.VARIANT_OFFSET;
this._cellColorResolver.result.ext |= (variantOffset << 29) & ExtFlags.VARIANT_OFFSET;
// Nothing has changed, no updates needed
if (this._model.cells[i] === code &&
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg &&
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg &&
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) {
if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) {
if (code !== NULL_CELL_CODE) {
variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2);
}
} else {
variantOffset = 0;
}
continue;
}
@@ -503,7 +534,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;
this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg);
this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg, variantOffset);
if (cell.extended.underlineStyle === UnderlineStyle.DOTTED) {
if (code !== NULL_CELL_CODE) {
variantOffset = ((deviceCellWidth! * chWidth) - ((lineWidth * 2) - variantOffset)) % (lineWidth * 2);
}
} else {
variantOffset = 0;
}
if (isJoined) {
// Restore work cell
@@ -512,7 +550,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Null out non-first cells
for (x++; x < lastCharX; x++) {
j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0);
this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
this._model.cells[j] = NULL_CELL_CODE;
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
+41 -18
View File
@@ -11,7 +11,7 @@ import { color, NULL_COLOR, rgba } from 'common/Color';
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { IUnicodeService } from 'common/services/Services';
import { FourKeyMap } from 'common/MultiKeyMap';
import { FiveKeyMap } from 'common/MultiKeyMap';
import { IdleTaskQueue } from 'common/TaskQueue';
import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types';
import { EventEmitter } from 'common/EventEmitter';
@@ -58,8 +58,8 @@ let $glyph = undefined;
export class TextureAtlas implements ITextureAtlas {
private _didWarmUp: boolean = false;
private _cacheMap: FourKeyMap<number, number, number, number, IRasterizedGlyph> = new FourKeyMap();
private _cacheMapCombined: FourKeyMap<string, number, number, number, IRasterizedGlyph> = new FourKeyMap();
private _cacheMap: FiveKeyMap<number, number, number, number, number, IRasterizedGlyph> = new FiveKeyMap();
private _cacheMapCombined: FiveKeyMap<string, number, number, number, number, IRasterizedGlyph> = new FiveKeyMap();
// The texture that the atlas is drawn to
private _pages: AtlasPage[] = [];
@@ -121,9 +121,9 @@ export class TextureAtlas implements ITextureAtlas {
const queue = new IdleTaskQueue();
for (let i = 33; i < 126; i++) {
queue.enqueue(() => {
if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT);
this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph);
if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 1)) {
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 0);
this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, 1, rasterizedGlyph);
}
});
}
@@ -244,29 +244,30 @@ export class TextureAtlas implements ITextureAtlas {
}
}
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph {
return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, restrictToCellHeight);
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph {
return this._getFromCacheMap(this._cacheMapCombined, chars, bg, fg, ext, variantOffset, restrictToCellHeight);
}
public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph {
return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, restrictToCellHeight);
public getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph {
return this._getFromCacheMap(this._cacheMap, code, bg, fg, ext, variantOffset, restrictToCellHeight);
}
/**
* Gets the glyphs texture coords, drawing the texture if it's not already
*/
private _getFromCacheMap(
cacheMap: FourKeyMap<string | number, number, number, number, IRasterizedGlyph>,
cacheMap: FiveKeyMap<string | number, number, number, number, number, IRasterizedGlyph>,
key: string | number,
bg: number,
fg: number,
ext: number,
variantOffset: number,
restrictToCellHeight: boolean = false
): IRasterizedGlyph {
$glyph = cacheMap.get(key, bg, fg, ext);
$glyph = cacheMap.get(key, bg, fg, ext, variantOffset);
if (!$glyph) {
$glyph = this._drawToCache(key, bg, fg, ext, restrictToCellHeight);
cacheMap.set(key, bg, fg, ext, $glyph);
$glyph = this._drawToCache(key, bg, fg, ext, variantOffset, restrictToCellHeight);
cacheMap.set(key, bg, fg, ext, variantOffset, $glyph);
}
return $glyph;
}
@@ -427,7 +428,7 @@ export class TextureAtlas implements ITextureAtlas {
}
@traceCall
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean = false): IRasterizedGlyph {
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number,variantOffset: number = 0, restrictToCellHeight: boolean = false): IRasterizedGlyph {
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
// Uncomment for debugging
@@ -547,6 +548,7 @@ export class TextureAtlas implements ITextureAtlas {
const yTop = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
const yMid = yTop + lineWidth;
const yBot = yTop + lineWidth * 2;
let nextOffset = variantOffset;
for (let i = 0; i < chWidth; i++) {
this._tmpCtx.save();
@@ -596,9 +598,26 @@ export class TextureAtlas implements ITextureAtlas {
);
break;
case UnderlineStyle.DOTTED:
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
this._tmpCtx.moveTo(xChLeft, yTop);
this._tmpCtx.lineTo(xChRight, yTop);
const offsetWidth = nextOffset >= lineWidth ? lineWidth * 2 - nextOffset : lineWidth - nextOffset;
if (offsetWidth === 0) {
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
this._tmpCtx.moveTo(xChLeft, yTop);
this._tmpCtx.lineTo(xChRight, yTop);
} else {
const isFull = nextOffset >= lineWidth ? false : true;
if (isFull === false) {
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
this._tmpCtx.moveTo(xChLeft + offsetWidth, yTop);
this._tmpCtx.lineTo(xChRight, yTop);
} else {
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
this._tmpCtx.moveTo(xChLeft, yTop);
this._tmpCtx.lineTo(xChLeft + offsetWidth, yTop);
this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTop);
this._tmpCtx.lineTo(xChRight, yTop);
}
}
nextOffset = (xChRight - xChLeft - ((lineWidth * 2) - nextOffset)) % (Math.round(lineWidth) * 2);
break;
case UnderlineStyle.DASHED:
this._tmpCtx.setLineDash([this._config.devicePixelRatio * 4, this._config.devicePixelRatio * 3]);
@@ -940,6 +959,10 @@ export class TextureAtlas implements ITextureAtlas {
}
};
}
public getDeviceCellWidth(): number {
return this._config.deviceCellWidth;
}
}
class AtlasPage {
+4 -2
View File
@@ -103,12 +103,14 @@ export interface ITextureAtlas extends IDisposable {
*/
beginFrame(): boolean;
getDeviceCellWidth(): number;
/**
* Clear all glyphs from the texture atlas.
*/
clearTexture(): void;
getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph;
getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean): IRasterizedGlyph;
getRasterizedGlyph(code: number, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph;
getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number, variantOffset: number, restrictToCellHeight: boolean): IRasterizedGlyph;
}
/**
+19
View File
@@ -40,3 +40,22 @@ export class FourKeyMap<TFirst extends string | number, TSecond extends string |
this._data.clear();
}
}
export class FiveKeyMap<TFirst extends string | number, TSecond extends string | number, TThird extends string | number, TFourth extends string | number, TFive extends string | number, TValue> {
private _data: { [bg: string | number]: FourKeyMap<TSecond, TThird, TFourth, TFive, TValue> | undefined } = {};
public set(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive, value: TValue): void {
if (!this._data[first]) {
this._data[first] = new FourKeyMap();
}
this._data[first as string | number]!.set(second, third, fourth, five, value);
}
public get(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, five: TFive): TValue | undefined {
return this._data[first as string | number] ? this._data[first as string | number]?.get(second, third, fourth, five) : undefined;
}
public clear(): void {
this._data = {};
}
}
+7 -2
View File
@@ -134,9 +134,14 @@ export const enum BgFlags {
export const enum ExtFlags {
/**
* bit 27..32 (upper 3 unused)
* bit 27..29
*/
UNDERLINE_STYLE = 0x1C000000
UNDERLINE_STYLE = 0x1C000000,
/**
* bit 30..32
*/
VARIANT_OFFSET = 0xE0000000
}
export const enum UnderlineStyle {