Path: blob/main/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { getActiveWindow } from '../../../../base/browser/dom.js';6import { BugIndicatingError } from '../../../../base/common/errors.js';7import { autorun, runOnChange } from '../../../../base/common/observable.js';8import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';9import { ILogService } from '../../../../platform/log/common/log.js';10import { EditorOption } from '../../../common/config/editorOptions.js';11import { Position } from '../../../common/core/position.js';12import { Range } from '../../../common/core/range.js';13import type { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js';14import type { ViewContext } from '../../../common/viewModel/viewContext.js';15import { TextureAtlasPage } from '../../gpu/atlas/textureAtlasPage.js';16import { BindingId, type IGpuRenderStrategy } from '../../gpu/gpu.js';17import { GPULifecycle } from '../../gpu/gpuDisposable.js';18import { quadVertices } from '../../gpu/gpuUtils.js';19import { ViewGpuContext } from '../../gpu/viewGpuContext.js';20import { FloatHorizontalRange, HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js';21import { ViewPart } from '../../view/viewPart.js';22import { ViewLineOptions } from '../viewLines/viewLineOptions.js';23import type * as viewEvents from '../../../common/viewEvents.js';24import { CursorColumns } from '../../../common/core/cursorColumns.js';25import { TextureAtlas } from '../../gpu/atlas/textureAtlas.js';26import { createContentSegmenter, type IContentSegmenter } from '../../gpu/contentSegmenter.js';27import { ViewportRenderStrategy } from '../../gpu/renderStrategy/viewportRenderStrategy.js';28import { FullFileRenderStrategy } from '../../gpu/renderStrategy/fullFileRenderStrategy.js';29import { MutableDisposable } from '../../../../base/common/lifecycle.js';30import type { ViewLineRenderingData } from '../../../common/viewModel.js';31import { GlyphRasterizer } from '../../gpu/raster/glyphRasterizer.js';3233const enum GlyphStorageBufferInfo {34FloatsPerEntry = 2 + 2 + 2,35BytesPerEntry = GlyphStorageBufferInfo.FloatsPerEntry * 4,36Offset_TexturePosition = 0,37Offset_TextureSize = 2,38Offset_OriginPosition = 4,39}4041/**42* The GPU implementation of the ViewLines part.43*/44export class ViewLinesGpu extends ViewPart implements IViewLines {4546private readonly canvas: HTMLCanvasElement;4748private _initViewportData?: ViewportData[];49private _lastViewportData?: ViewportData;50private _lastViewLineOptions?: ViewLineOptions;5152private _device!: GPUDevice;53private _renderPassDescriptor!: GPURenderPassDescriptor;54private _renderPassColorAttachment!: GPURenderPassColorAttachment;55private _bindGroup!: GPUBindGroup;56private _pipeline!: GPURenderPipeline;5758private _vertexBuffer!: GPUBuffer;5960private _glyphStorageBuffer!: GPUBuffer;61private _atlasGpuTexture!: GPUTexture;62private readonly _atlasGpuTextureVersions: number[] = [];6364private _initialized = false;6566private readonly _glyphRasterizer: MutableDisposable<GlyphRasterizer> = this._register(new MutableDisposable());67private readonly _renderStrategy: MutableDisposable<IGpuRenderStrategy> = this._register(new MutableDisposable());68private _rebuildBindGroup?: () => void;6970constructor(71context: ViewContext,72private readonly _viewGpuContext: ViewGpuContext,73@IInstantiationService private readonly _instantiationService: IInstantiationService,74@ILogService private readonly _logService: ILogService,75) {76super(context);7778this.canvas = this._viewGpuContext.canvas.domNode;7980// Re-render the following frame after canvas device pixel dimensions change, provided a81// new render does not occur.82this._register(autorun(reader => {83this._viewGpuContext.canvasDevicePixelDimensions.read(reader);84const lastViewportData = this._lastViewportData;85if (lastViewportData) {86setTimeout(() => {87if (lastViewportData === this._lastViewportData) {88this.renderText(lastViewportData);89}90});91}92}));9394this.initWebgpu();95}9697async initWebgpu() {98// #region General99100this._device = ViewGpuContext.deviceSync || await ViewGpuContext.device;101102if (this._store.isDisposed) {103return;104}105106const atlas = ViewGpuContext.atlas;107108// Rerender when the texture atlas deletes glyphs109this._register(atlas.onDidDeleteGlyphs(() => {110this._atlasGpuTextureVersions.length = 0;111this._atlasGpuTextureVersions[0] = 0;112this._atlasGpuTextureVersions[1] = 0;113this._renderStrategy.value!.reset();114}));115116const presentationFormat = navigator.gpu.getPreferredCanvasFormat();117this._viewGpuContext.ctx.configure({118device: this._device,119format: presentationFormat,120alphaMode: 'premultiplied',121});122123this._renderPassColorAttachment = {124view: null!, // Will be filled at render time125loadOp: 'load',126storeOp: 'store',127};128this._renderPassDescriptor = {129label: 'Monaco render pass',130colorAttachments: [this._renderPassColorAttachment],131};132133// #endregion General134135// #region Uniforms136137let layoutInfoUniformBuffer: GPUBuffer;138{139const enum Info {140FloatsPerEntry = 6,141BytesPerEntry = Info.FloatsPerEntry * 4,142Offset_CanvasWidth____ = 0,143Offset_CanvasHeight___ = 1,144Offset_ViewportOffsetX = 2,145Offset_ViewportOffsetY = 3,146Offset_ViewportWidth__ = 4,147Offset_ViewportHeight_ = 5,148}149const bufferValues = new Float32Array(Info.FloatsPerEntry);150const updateBufferValues = (canvasDevicePixelWidth: number = this.canvas.width, canvasDevicePixelHeight: number = this.canvas.height) => {151bufferValues[Info.Offset_CanvasWidth____] = canvasDevicePixelWidth;152bufferValues[Info.Offset_CanvasHeight___] = canvasDevicePixelHeight;153bufferValues[Info.Offset_ViewportOffsetX] = Math.ceil(this._context.configuration.options.get(EditorOption.layoutInfo).contentLeft * getActiveWindow().devicePixelRatio);154bufferValues[Info.Offset_ViewportOffsetY] = 0;155bufferValues[Info.Offset_ViewportWidth__] = bufferValues[Info.Offset_CanvasWidth____] - bufferValues[Info.Offset_ViewportOffsetX];156bufferValues[Info.Offset_ViewportHeight_] = bufferValues[Info.Offset_CanvasHeight___] - bufferValues[Info.Offset_ViewportOffsetY];157return bufferValues;158};159layoutInfoUniformBuffer = this._register(GPULifecycle.createBuffer(this._device, {160label: 'Monaco uniform buffer',161size: Info.BytesPerEntry,162usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,163}, () => updateBufferValues())).object;164this._register(runOnChange(this._viewGpuContext.canvasDevicePixelDimensions, ({ width, height }) => {165this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues(width, height));166}));167this._register(runOnChange(this._viewGpuContext.contentLeft, () => {168this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues());169}));170}171172let atlasInfoUniformBuffer: GPUBuffer;173{174const enum Info {175FloatsPerEntry = 2,176BytesPerEntry = Info.FloatsPerEntry * 4,177Offset_Width_ = 0,178Offset_Height = 1,179}180atlasInfoUniformBuffer = this._register(GPULifecycle.createBuffer(this._device, {181label: 'Monaco atlas info uniform buffer',182size: Info.BytesPerEntry,183usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,184}, () => {185const values = new Float32Array(Info.FloatsPerEntry);186values[Info.Offset_Width_] = atlas.pageSize;187values[Info.Offset_Height] = atlas.pageSize;188return values;189})).object;190}191192// #endregion Uniforms193194// #region Storage buffers195196const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily);197const fontSize = this._context.configuration.options.get(EditorOption.fontSize);198this._glyphRasterizer.value = this._register(new GlyphRasterizer(fontSize, fontFamily, this._viewGpuContext.devicePixelRatio.get(), ViewGpuContext.decorationStyleCache));199this._register(runOnChange(this._viewGpuContext.devicePixelRatio, () => {200this._refreshGlyphRasterizer();201}));202203204this._renderStrategy.value = this._instantiationService.createInstance(FullFileRenderStrategy, this._context, this._viewGpuContext, this._device, this._glyphRasterizer as { value: GlyphRasterizer });205// this._renderStrategy.value = this._instantiationService.createInstance(ViewportRenderStrategy, this._context, this._viewGpuContext, this._device);206207this._glyphStorageBuffer = this._register(GPULifecycle.createBuffer(this._device, {208label: 'Monaco glyph storage buffer',209size: TextureAtlas.maximumPageCount * (TextureAtlasPage.maximumGlyphCount * GlyphStorageBufferInfo.BytesPerEntry),210usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,211})).object;212this._atlasGpuTextureVersions[0] = 0;213this._atlasGpuTextureVersions[1] = 0;214this._atlasGpuTexture = this._register(GPULifecycle.createTexture(this._device, {215label: 'Monaco atlas texture',216format: 'rgba8unorm',217size: { width: atlas.pageSize, height: atlas.pageSize, depthOrArrayLayers: TextureAtlas.maximumPageCount },218dimension: '2d',219usage: GPUTextureUsage.TEXTURE_BINDING |220GPUTextureUsage.COPY_DST |221GPUTextureUsage.RENDER_ATTACHMENT,222})).object;223224this._updateAtlasStorageBufferAndTexture();225226// #endregion Storage buffers227228// #region Vertex buffer229230this._vertexBuffer = this._register(GPULifecycle.createBuffer(this._device, {231label: 'Monaco vertex buffer',232size: quadVertices.byteLength,233usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,234}, quadVertices)).object;235236// #endregion Vertex buffer237238// #region Shader module239240const module = this._device.createShaderModule({241label: 'Monaco shader module',242code: this._renderStrategy.value!.wgsl,243});244245// #endregion Shader module246247// #region Pipeline248249this._pipeline = this._device.createRenderPipeline({250label: 'Monaco render pipeline',251layout: 'auto',252vertex: {253module,254buffers: [255{256arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, // 2 floats, 4 bytes each257attributes: [258{ shaderLocation: 0, offset: 0, format: 'float32x2' }, // position259],260}261]262},263fragment: {264module,265targets: [266{267format: presentationFormat,268blend: {269color: {270srcFactor: 'src-alpha',271dstFactor: 'one-minus-src-alpha'272},273alpha: {274srcFactor: 'src-alpha',275dstFactor: 'one-minus-src-alpha'276},277},278}279],280},281});282283// #endregion Pipeline284285// #region Bind group286287this._rebuildBindGroup = () => {288this._bindGroup = this._device.createBindGroup({289label: 'Monaco bind group',290layout: this._pipeline.getBindGroupLayout(0),291entries: [292// TODO: Pass in generically as array?293{ binding: BindingId.GlyphInfo, resource: { buffer: this._glyphStorageBuffer } },294{295binding: BindingId.TextureSampler, resource: this._device.createSampler({296label: 'Monaco atlas sampler',297magFilter: 'nearest',298minFilter: 'nearest',299})300},301{ binding: BindingId.Texture, resource: this._atlasGpuTexture.createView() },302{ binding: BindingId.LayoutInfoUniform, resource: { buffer: layoutInfoUniformBuffer } },303{ binding: BindingId.AtlasDimensionsUniform, resource: { buffer: atlasInfoUniformBuffer } },304...this._renderStrategy.value!.bindGroupEntries305],306});307};308this._rebuildBindGroup();309310// endregion Bind group311312this._initialized = true;313314// Render the initial viewport immediately after initialization315if (this._initViewportData) {316// HACK: Rendering multiple times in the same frame like this isn't ideal, but there317// isn't an easy way to merge viewport data318for (const viewportData of this._initViewportData) {319this.renderText(viewportData);320}321this._initViewportData = undefined;322}323}324325private _refreshRenderStrategy(viewportData: ViewportData) {326if (this._renderStrategy.value?.type === 'viewport') {327return;328}329if (viewportData.endLineNumber < FullFileRenderStrategy.maxSupportedLines && this._viewportMaxColumn(viewportData) < FullFileRenderStrategy.maxSupportedColumns) {330return;331}332this._logService.trace(`File is larger than ${FullFileRenderStrategy.maxSupportedLines} lines or ${FullFileRenderStrategy.maxSupportedColumns} columns, switching to viewport render strategy`);333const viewportRenderStrategy = this._instantiationService.createInstance(ViewportRenderStrategy, this._context, this._viewGpuContext, this._device, this._glyphRasterizer as { value: GlyphRasterizer });334this._renderStrategy.value = viewportRenderStrategy;335this._register(viewportRenderStrategy.onDidChangeBindGroupEntries(() => this._rebuildBindGroup?.()));336this._rebuildBindGroup?.();337}338339private _viewportMaxColumn(viewportData: ViewportData): number {340let maxColumn = 0;341let lineData: ViewLineRenderingData;342for (let i = viewportData.startLineNumber; i <= viewportData.endLineNumber; i++) {343lineData = viewportData.getViewLineRenderingData(i);344maxColumn = Math.max(maxColumn, lineData.maxColumn);345}346return maxColumn;347}348349private _updateAtlasStorageBufferAndTexture() {350for (const [layerIndex, page] of ViewGpuContext.atlas.pages.entries()) {351if (layerIndex >= TextureAtlas.maximumPageCount) {352console.log(`Attempt to upload atlas page [${layerIndex}], only ${TextureAtlas.maximumPageCount} are supported currently`);353continue;354}355356// Skip the update if it's already the latest version357if (page.version === this._atlasGpuTextureVersions[layerIndex]) {358continue;359}360361this._logService.trace('Updating atlas page[', layerIndex, '] from version ', this._atlasGpuTextureVersions[layerIndex], ' to version ', page.version);362363const entryCount = GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount;364const values = new Float32Array(entryCount);365let entryOffset = 0;366for (const glyph of page.glyphs) {367values[entryOffset + GlyphStorageBufferInfo.Offset_TexturePosition] = glyph.x;368values[entryOffset + GlyphStorageBufferInfo.Offset_TexturePosition + 1] = glyph.y;369values[entryOffset + GlyphStorageBufferInfo.Offset_TextureSize] = glyph.w;370values[entryOffset + GlyphStorageBufferInfo.Offset_TextureSize + 1] = glyph.h;371values[entryOffset + GlyphStorageBufferInfo.Offset_OriginPosition] = glyph.originOffsetX;372values[entryOffset + GlyphStorageBufferInfo.Offset_OriginPosition + 1] = glyph.originOffsetY;373entryOffset += GlyphStorageBufferInfo.FloatsPerEntry;374}375if (entryOffset / GlyphStorageBufferInfo.FloatsPerEntry > TextureAtlasPage.maximumGlyphCount) {376throw new Error(`Attempting to write more glyphs (${entryOffset / GlyphStorageBufferInfo.FloatsPerEntry}) than the GPUBuffer can hold (${TextureAtlasPage.maximumGlyphCount})`);377}378this._device.queue.writeBuffer(379this._glyphStorageBuffer,380layerIndex * GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount * Float32Array.BYTES_PER_ELEMENT,381values,3820,383GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount384);385if (page.usedArea.right - page.usedArea.left > 0 && page.usedArea.bottom - page.usedArea.top > 0) {386this._device.queue.copyExternalImageToTexture(387{ source: page.source },388{389texture: this._atlasGpuTexture,390origin: {391x: page.usedArea.left,392y: page.usedArea.top,393z: layerIndex394}395},396{397width: page.usedArea.right - page.usedArea.left + 1,398height: page.usedArea.bottom - page.usedArea.top + 1399},400);401}402this._atlasGpuTextureVersions[layerIndex] = page.version;403}404}405406public prepareRender(ctx: RenderingContext): void {407throw new BugIndicatingError('Should not be called');408}409410public override render(ctx: RestrictedRenderingContext): void {411throw new BugIndicatingError('Should not be called');412}413414// #region Event handlers415416// Since ViewLinesGpu currently coordinates rendering to the canvas, it must listen to all417// changed events that any GPU part listens to. This is because any drawing to the canvas will418// clear it for that frame, so all parts must be rendered every time.419//420// Additionally, since this is intrinsically linked to ViewLines, it must also listen to events421// from that side. Luckily rendering is cheap, it's only when uploaded data changes does it422// start to cost.423424override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {425this._refreshGlyphRasterizer();426return true;427}428override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return true; }429override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; }430override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; }431432override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; }433override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; }434override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; }435override onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean { return true; }436override onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean { return true; }437override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return true; }438override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { return true; }439override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return true; }440441// #endregion442443private _refreshGlyphRasterizer() {444const glyphRasterizer = this._glyphRasterizer.value;445if (!glyphRasterizer) {446return;447}448const fontFamily = this._context.configuration.options.get(EditorOption.fontFamily);449const fontSize = this._context.configuration.options.get(EditorOption.fontSize);450const devicePixelRatio = this._viewGpuContext.devicePixelRatio.get();451if (452glyphRasterizer.fontFamily !== fontFamily ||453glyphRasterizer.fontSize !== fontSize ||454glyphRasterizer.devicePixelRatio !== devicePixelRatio455) {456this._glyphRasterizer.value = new GlyphRasterizer(fontSize, fontFamily, devicePixelRatio, ViewGpuContext.decorationStyleCache);457}458}459460public renderText(viewportData: ViewportData): void {461if (this._initialized) {462this._refreshRenderStrategy(viewportData);463return this._renderText(viewportData);464} else {465this._initViewportData = this._initViewportData ?? [];466this._initViewportData.push(viewportData);467}468}469470private _renderText(viewportData: ViewportData): void {471this._viewGpuContext.rectangleRenderer.draw(viewportData);472473const options = new ViewLineOptions(this._context.configuration, this._context.theme.type);474475this._renderStrategy.value!.update(viewportData, options);476477this._updateAtlasStorageBufferAndTexture();478479const encoder = this._device.createCommandEncoder({ label: 'Monaco command encoder' });480481this._renderPassColorAttachment.view = this._viewGpuContext.ctx.getCurrentTexture().createView({ label: 'Monaco canvas texture view' });482const pass = encoder.beginRenderPass(this._renderPassDescriptor);483pass.setPipeline(this._pipeline);484pass.setVertexBuffer(0, this._vertexBuffer);485486// Only draw the content area487const contentLeft = Math.ceil(this._viewGpuContext.contentLeft.get() * this._viewGpuContext.devicePixelRatio.get());488pass.setScissorRect(contentLeft, 0, this.canvas.width - contentLeft, this.canvas.height);489490pass.setBindGroup(0, this._bindGroup);491492this._renderStrategy.value!.draw(pass, viewportData);493494pass.end();495496const commandBuffer = encoder.finish();497498this._device.queue.submit([commandBuffer]);499500this._lastViewportData = viewportData;501this._lastViewLineOptions = options;502}503504linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null {505if (!this._lastViewportData) {506return null;507}508const originalEndLineNumber = _range.endLineNumber;509const range = Range.intersectRanges(_range, this._lastViewportData.visibleRange);510if (!range) {511return null;512}513514const rendStartLineNumber = this._lastViewportData.startLineNumber;515const rendEndLineNumber = this._lastViewportData.endLineNumber;516517const viewportData = this._lastViewportData;518const viewLineOptions = this._lastViewLineOptions;519520if (!viewportData || !viewLineOptions) {521return null;522}523524const visibleRanges: LineVisibleRanges[] = [];525526let nextLineModelLineNumber: number = 0;527if (includeNewLines) {528nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;529}530531for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {532533if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) {534continue;535}536const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;537const continuesInNextLine = lineNumber !== originalEndLineNumber;538const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn;539540const visibleRangesForLine = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn);541542if (!visibleRangesForLine) {543continue;544}545546if (includeNewLines && lineNumber < originalEndLineNumber) {547const currentLineModelLineNumber = nextLineModelLineNumber;548nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber;549550if (currentLineModelLineNumber !== nextLineModelLineNumber) {551visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += viewLineOptions.spaceWidth;552}553}554555visibleRanges.push(new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine));556}557558if (visibleRanges.length === 0) {559return null;560}561562return visibleRanges;563}564565private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null {566if (this.shouldRender()) {567// Cannot read from the DOM because it is dirty568// i.e. the model & the dom are out of sync, so I'd be reading something stale569return null;570}571572const viewportData = this._lastViewportData;573const viewLineOptions = this._lastViewLineOptions;574575if (!viewportData || !viewLineOptions || lineNumber < viewportData.startLineNumber || lineNumber > viewportData.endLineNumber) {576return null;577}578579// Resolve tab widths for this line580const lineData = viewportData.getViewLineRenderingData(lineNumber);581const content = lineData.content;582583let contentSegmenter: IContentSegmenter | undefined;584if (!(lineData.isBasicASCII && viewLineOptions.useMonospaceOptimizations)) {585contentSegmenter = createContentSegmenter(lineData, viewLineOptions);586}587588let chars: string | undefined = '';589590let resolvedStartColumn = 0;591let resolvedStartCssPixelOffset = 0;592for (let x = 0; x < startColumn - 1; x++) {593if (lineData.isBasicASCII && viewLineOptions.useMonospaceOptimizations) {594chars = content.charAt(x);595} else {596chars = contentSegmenter!.getSegmentAtIndex(x);597if (chars === undefined) {598continue;599}600resolvedStartCssPixelOffset += (this._renderStrategy.value!.glyphRasterizer.getTextMetrics(chars).width / getActiveWindow().devicePixelRatio) - viewLineOptions.spaceWidth;601}602if (chars === '\t') {603resolvedStartColumn = CursorColumns.nextRenderTabStop(resolvedStartColumn, lineData.tabSize);604} else {605resolvedStartColumn++;606}607}608let resolvedEndColumn = resolvedStartColumn;609let resolvedEndCssPixelOffset = 0;610for (let x = startColumn - 1; x < endColumn - 1; x++) {611if (lineData.isBasicASCII && viewLineOptions.useMonospaceOptimizations) {612chars = content.charAt(x);613} else {614chars = contentSegmenter!.getSegmentAtIndex(x);615if (chars === undefined) {616continue;617}618resolvedEndCssPixelOffset += (this._renderStrategy.value!.glyphRasterizer.getTextMetrics(chars).width / getActiveWindow().devicePixelRatio) - viewLineOptions.spaceWidth;619}620if (chars === '\t') {621resolvedEndColumn = CursorColumns.nextRenderTabStop(resolvedEndColumn, lineData.tabSize);622} else {623resolvedEndColumn++;624}625}626627// Visible horizontal range in _scaled_ pixels628const result = new VisibleRanges(false, [new FloatHorizontalRange(629resolvedStartColumn * viewLineOptions.spaceWidth + resolvedStartCssPixelOffset,630(resolvedEndColumn - resolvedStartColumn) * viewLineOptions.spaceWidth + resolvedEndCssPixelOffset)631]);632633return result;634}635636visibleRangeForPosition(position: Position): HorizontalPosition | null {637const visibleRanges = this._visibleRangesForLineRange(position.lineNumber, position.column, position.column);638if (!visibleRanges) {639return null;640}641return new HorizontalPosition(visibleRanges.outsideRenderedLine, visibleRanges.ranges[0].left);642}643644getLineWidth(lineNumber: number): number | undefined {645if (!this._lastViewportData || !this._lastViewLineOptions) {646return undefined;647}648if (!this._viewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) {649return undefined;650}651652const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber);653const lineRange = this._visibleRangesForLineRange(lineNumber, 1, lineData.maxColumn);654const lastRange = lineRange?.ranges.at(-1);655if (lastRange) {656return lastRange.width;657}658659return undefined;660}661662getPositionAtCoordinate(lineNumber: number, mouseContentHorizontalOffset: number): Position | undefined {663if (!this._lastViewportData || !this._lastViewLineOptions) {664return undefined;665}666if (!this._viewGpuContext.canRender(this._lastViewLineOptions, this._lastViewportData, lineNumber)) {667return undefined;668}669const lineData = this._lastViewportData.getViewLineRenderingData(lineNumber);670const content = lineData.content;671const dpr = getActiveWindow().devicePixelRatio;672const mouseContentHorizontalOffsetDevicePixels = mouseContentHorizontalOffset * dpr;673const spaceWidthDevicePixels = this._lastViewLineOptions.spaceWidth * dpr;674const contentSegmenter = createContentSegmenter(lineData, this._lastViewLineOptions);675676let widthSoFar = 0;677let charWidth = 0;678let tabXOffset = 0;679let column = 0;680for (let x = 0; x < content.length; x++) {681const chars = contentSegmenter.getSegmentAtIndex(x);682683// Part of an earlier segment684if (chars === undefined) {685column++;686continue;687}688689// Get the width of the character690if (chars === '\t') {691// Find the pixel offset between the current position and the next tab stop692const offsetBefore = x + tabXOffset;693tabXOffset = CursorColumns.nextRenderTabStop(x + tabXOffset, lineData.tabSize);694charWidth = spaceWidthDevicePixels * (tabXOffset - offsetBefore);695// Convert back to offset excluding x and the current character696tabXOffset -= x + 1;697} else if (lineData.isBasicASCII && this._lastViewLineOptions.useMonospaceOptimizations) {698charWidth = spaceWidthDevicePixels;699} else {700charWidth = this._renderStrategy.value!.glyphRasterizer.getTextMetrics(chars).width;701}702703if (mouseContentHorizontalOffsetDevicePixels < widthSoFar + charWidth / 2) {704break;705}706707widthSoFar += charWidth;708column++;709}710711return new Position(lineNumber, column + 1);712}713}714715716