Skip to content

Commit

Permalink
Sub-pixel offset rendering
Browse files Browse the repository at this point in the history
Part of #225428
  • Loading branch information
Tyriar committed Jan 24, 2025
1 parent ef61f9d commit 4260196
Show file tree
Hide file tree
Showing 7 changed files with 24 additions and 18 deletions.
7 changes: 6 additions & 1 deletion src/vs/editor/browser/gpu/atlas/textureAtlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,16 @@ export class TextureAtlas extends Disposable {
this._onDidDeleteGlyphs.fire();
}

getGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, decorationStyleSetId: number): Readonly<ITextureAtlasPageGlyph> {
getGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, decorationStyleSetId: number, x: number): Readonly<ITextureAtlasPageGlyph> {
// TODO: Encode font size and family into key
// Ignore metadata that doesn't affect the glyph
tokenMetadata &= ~(MetadataConsts.LANGUAGEID_MASK | MetadataConsts.TOKEN_TYPE_MASK | MetadataConsts.BALANCED_BRACKETS_MASK);

// Add x offset for sub-pixel rendering to the unused portion or tokenMetadata. This
// converts the decimal part of the x to a range from 0 to 9, where 0 = 0.0px x offset,
// 9 = 0.9px x offset
tokenMetadata |= Math.floor((x % 1) * 10);

// Warm up common glyphs
if (!this._warmedUpRasterizers.has(rasterizer.id)) {
this._warmUpAtlas(rasterizer);
Expand Down
5 changes: 4 additions & 1 deletion src/vs/editor/browser/gpu/raster/glyphRasterizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer {

this._ctx.save();

const xSubPixelOffset = (tokenMetadata & 0b1111) / 10;
// console.log('xSubPixelOffset', xSubPixelOffset);

const bgId = TokenMetadata.getBackground(tokenMetadata);
const bg = colorMap[bgId];

Expand Down Expand Up @@ -157,7 +160,7 @@ export class GlyphRasterizer extends Disposable implements IGlyphRasterizer {
this._ctx.globalAlpha = decorationStyleSet.opacity;
}

this._ctx.fillText(chars, originX, originY);
this._ctx.fillText(chars, originX + xSubPixelOffset, originY);
this._ctx.restore();

const imageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,6 @@ export class FullFileRenderStrategy extends BaseRenderStrategy {
this._scrollInitialized = true;
}

let localContentWidth = 0;

// Update cell data
const cellBuffer = new Float32Array(this._cellValueBuffers[this._activeDoubleBufferIndex]);
const lineIndexCount = FullFileRenderStrategy.maxSupportedColumns * Constants.IndicesPerCell;
Expand Down Expand Up @@ -445,7 +443,7 @@ export class FullFileRenderStrategy extends BaseRenderStrategy {
}

const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity);
glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId);
glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId, absoluteOffsetX);

absoluteOffsetY = Math.round(
// Top of layout box (includes line height)
Expand All @@ -461,14 +459,13 @@ export class FullFileRenderStrategy extends BaseRenderStrategy {
);

cellIndex = ((y - 1) * FullFileRenderStrategy.maxSupportedColumns + x) * Constants.IndicesPerCell;
cellBuffer[cellIndex + CellBufferInfo.Offset_X] = Math.round(absoluteOffsetX);
cellBuffer[cellIndex + CellBufferInfo.Offset_X] = Math.floor(absoluteOffsetX);
cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = absoluteOffsetY;
cellBuffer[cellIndex + CellBufferInfo.GlyphIndex] = glyph.glyphIndex;
cellBuffer[cellIndex + CellBufferInfo.TextureIndex] = glyph.pageIndex;

// Adjust the x pixel offset for the next character
absoluteOffsetX += charWidth;
localContentWidth = Math.max(localContentWidth, absoluteOffsetX);
}

tokenStartIndex = tokenEndIndex;
Expand Down Expand Up @@ -504,7 +501,9 @@ export class FullFileRenderStrategy extends BaseRenderStrategy {

this._visibleObjectCount = visibleObjectCount;

return { localContentWidth };
return {
localContentWidth: absoluteOffsetX
};
}

draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,6 @@ export class ViewportRenderStrategy extends BaseRenderStrategy {
this._scrollInitialized = true;
}

let localContentWidth = 0;

// Zero out cell buffer or rebuild if needed
if (this._cellBindBufferLineCapacity < viewportData.endLineNumber - viewportData.startLineNumber + 1) {
this._rebuildCellBuffer(viewportData.endLineNumber - viewportData.startLineNumber + 1);
Expand Down Expand Up @@ -348,7 +346,7 @@ export class ViewportRenderStrategy extends BaseRenderStrategy {
}

const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity);
glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId);
glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId, absoluteOffsetX);

absoluteOffsetY = Math.round(
// Top of layout box (includes line height)
Expand All @@ -364,14 +362,13 @@ export class ViewportRenderStrategy extends BaseRenderStrategy {
);

cellIndex = ((y - viewportData.startLineNumber) * ViewportRenderStrategy.maxSupportedColumns + x) * Constants.IndicesPerCell;
cellBuffer[cellIndex + CellBufferInfo.Offset_X] = Math.round(absoluteOffsetX);
cellBuffer[cellIndex + CellBufferInfo.Offset_X] = Math.floor(absoluteOffsetX);
cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = absoluteOffsetY;
cellBuffer[cellIndex + CellBufferInfo.GlyphIndex] = glyph.glyphIndex;
cellBuffer[cellIndex + CellBufferInfo.TextureIndex] = glyph.pageIndex;

// Adjust the x pixel offset for the next character
absoluteOffsetX += charWidth;
localContentWidth = Math.max(localContentWidth, absoluteOffsetX);
}

tokenStartIndex = tokenEndIndex;
Expand All @@ -397,7 +394,9 @@ export class ViewportRenderStrategy extends BaseRenderStrategy {
this._activeDoubleBufferIndex = this._activeDoubleBufferIndex ? 0 : 1;

this._visibleObjectCount = visibleObjectCount;
return { localContentWidth };
return {
localContentWidth: Math.ceil(absoluteOffsetX / dpr)
};
}

draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines {
// Track the largest local content width so far in this session and use it as the scroll
// width. This is how the DOM renderer works as well, so you may not be able to scroll to
// the right in a file with long lines until you scroll down.
this._maxLocalContentWidthSoFar = Math.max(this._maxLocalContentWidthSoFar, localContentWidth);
this._maxLocalContentWidthSoFar = Math.max(this._maxLocalContentWidthSoFar, localContentWidth / this._viewGpuContext.devicePixelRatio.get());
this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLocalContentWidthSoFar);
this._viewGpuContext.scrollWidthElement.setWidth(this._context.viewLayout.getScrollWidth());

Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/contrib/gpu/browser/gpuActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class DebugEditorGpuRendererAction extends EditorAction {
}
const tokenMetadata = 0;
const charMetadata = 0;
const rasterizedGlyph = atlas.getGlyph(rasterizer, chars, tokenMetadata, charMetadata);
const rasterizedGlyph = atlas.getGlyph(rasterizer, chars, tokenMetadata, charMetadata, 0);
if (!rasterizedGlyph) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ const blackInt = 0x000000FF;
const nullCharMetadata = 0x0;

let lastUniqueGlyph: string | undefined;
function getUniqueGlyphId(): [chars: string, tokenMetadata: number, charMetadata: number] {
function getUniqueGlyphId(): [chars: string, tokenMetadata: number, charMetadata: number, x: number] {
if (!lastUniqueGlyph) {
lastUniqueGlyph = 'a';
} else {
lastUniqueGlyph = String.fromCharCode(lastUniqueGlyph.charCodeAt(0) + 1);
}
return [lastUniqueGlyph, blackInt, nullCharMetadata];
return [lastUniqueGlyph, blackInt, nullCharMetadata, 0];
}

class TestGlyphRasterizer implements IGlyphRasterizer {
Expand Down

0 comments on commit 4260196

Please sign in to comment.