diff --git a/src/vs/editor/browser/gpu/rectangleRenderer.ts b/src/vs/editor/browser/gpu/rectangleRenderer.ts index 0859281f48b31..54ac68f9b98fb 100644 --- a/src/vs/editor/browser/gpu/rectangleRenderer.ts +++ b/src/vs/editor/browser/gpu/rectangleRenderer.ts @@ -8,7 +8,7 @@ import { Event } from '../../../base/common/event.js'; import { IReference, MutableDisposable } from '../../../base/common/lifecycle.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { ViewEventHandler } from '../../common/viewEventHandler.js'; -import type { ViewScrollChangedEvent } from '../../common/viewEvents.js'; +import type { ViewCursorStateChangedEvent, ViewScrollChangedEvent } from '../../common/viewEvents.js'; import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js'; import type { ViewContext } from '../../common/viewModel/viewContext.js'; import { GPULifecycle } from './gpuDisposable.js'; @@ -42,7 +42,6 @@ export class RectangleRenderer extends ViewEventHandler { private _scrollOffsetValueBuffer!: Float32Array; private _initialized: boolean = false; - private _scrollChanged: boolean = true; private readonly _shapeCollection: IObjectCollectionBuffer = this._register(createObjectCollectionBuffer([ { name: 'x' }, @@ -242,29 +241,33 @@ export class RectangleRenderer extends ViewEventHandler { return this._shapeCollection.createEntry({ x, y, width, height, red, green, blue, alpha }); } - // --- begin event handlers + // #region Event handlers public override onScrollChanged(e: ViewScrollChangedEvent): boolean { - this._scrollChanged = true; - return super.onScrollChanged(e); + return true; } - // --- end event handlers + public override onCursorStateChanged(e: ViewCursorStateChangedEvent): boolean { + if (this._device) { + const dpr = getActiveWindow().devicePixelRatio; + this._scrollOffsetValueBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr; + this._scrollOffsetValueBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr; + this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer); + } + return true; + } + + // #endregion private _update() { + if (!this._device) { + return; + } const shapes = this._shapeCollection; if (shapes.dirtyTracker.isDirty) { this._device.queue.writeBuffer(this._shapeBindBuffer.value!.object, 0, shapes.buffer, shapes.dirtyTracker.dataOffset, shapes.dirtyTracker.dirtySize! * shapes.view.BYTES_PER_ELEMENT); shapes.dirtyTracker.clear(); } - - // Update scroll offset - if (this._scrollChanged) { - const dpr = getActiveWindow().devicePixelRatio; - this._scrollOffsetValueBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr; - this._scrollOffsetValueBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr; - this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer); - } } draw(viewportData: ViewportData) { diff --git a/src/vs/editor/browser/view/renderingContext.ts b/src/vs/editor/browser/view/renderingContext.ts index 178f546082e82..f0d5b5357c14c 100644 --- a/src/vs/editor/browser/view/renderingContext.ts +++ b/src/vs/editor/browser/view/renderingContext.ts @@ -80,7 +80,22 @@ export class RenderingContext extends RestrictedRenderingContext { } public linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { - return this._viewLines.linesVisibleRangesForRange(range, includeNewLines) ?? this._viewLinesGpu?.linesVisibleRangesForRange(range, includeNewLines) ?? null; + const domRanges = this._viewLines.linesVisibleRangesForRange(range, includeNewLines); + if (!this._viewLinesGpu) { + return domRanges ?? null; + } + const gpuRanges = this._viewLinesGpu.linesVisibleRangesForRange(range, includeNewLines); + if (!domRanges && !gpuRanges) { + return null; + } + const ranges = []; + if (domRanges) { + ranges.push(...domRanges); + } + if (gpuRanges) { + ranges.push(...gpuRanges); + } + return ranges; } public visibleRangeForPosition(position: Position): HorizontalPosition | null { diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index ac22b39a6d633..04b2d0ae22934 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -9,9 +9,9 @@ import { autorun } from '../../../../base/common/observable.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; -import type { Position } from '../../../common/core/position.js'; -import type { Range } from '../../../common/core/range.js'; -import type { ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewScrollChangedEvent } from '../../../common/viewEvents.js'; +import { Position } from '../../../common/core/position.js'; +import { Range } from '../../../common/core/range.js'; +import type { ViewConfigurationChangedEvent, ViewCursorStateChangedEvent, ViewLinesChangedEvent, ViewLinesDeletedEvent, ViewScrollChangedEvent } from '../../../common/viewEvents.js'; import type { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js'; import type { ViewContext } from '../../../common/viewModel/viewContext.js'; import { TextureAtlasPage } from '../../gpu/atlas/textureAtlasPage.js'; @@ -20,7 +20,7 @@ import { BindingId, type IGpuRenderStrategy } from '../../gpu/gpu.js'; import { GPULifecycle } from '../../gpu/gpuDisposable.js'; import { observeDevicePixelDimensions, quadVertices } from '../../gpu/gpuUtils.js'; import { ViewGpuContext } from '../../gpu/viewGpuContext.js'; -import { FloatHorizontalRange, HorizontalPosition, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js'; +import { FloatHorizontalRange, HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js'; import { ViewPart } from '../../view/viewPart.js'; import { ViewLineOptions } from '../viewLines/viewLineOptions.js'; @@ -368,6 +368,16 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { throw new BugIndicatingError('Should not be called'); } + // #region Event handlers + + // Since ViewLinesGpu currently coordinates rendering to the canvas, it must listen to all + // changed events that any GPU part listens to. This is because any drawing to the canvas will + // clear it for that frame, so all parts must be rendered every time. + + override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean { + return true; + } + override onLinesChanged(e: ViewLinesChangedEvent): boolean { return true; } @@ -381,7 +391,11 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { return true; } - // subscribe to more events + override onCursorStateChanged(e: ViewCursorStateChangedEvent): boolean { + return true; + } + + // #endregion public renderText(viewportData: ViewportData): void { if (this._initialized) { @@ -427,8 +441,65 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { this._lastViewLineOptions = options; } - linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { - return null; + linesVisibleRangesForRange(_range: Range, includeNewLines: boolean): LineVisibleRanges[] | null { + if (!this._lastViewportData) { + return null; + } + const originalEndLineNumber = _range.endLineNumber; + const range = Range.intersectRanges(_range, this._lastViewportData.visibleRange); + if (!range) { + return null; + } + + const rendStartLineNumber = this._lastViewportData.startLineNumber; + const rendEndLineNumber = this._lastViewportData.endLineNumber; + + const viewportData = this._lastViewportData; + const viewLineOptions = this._lastViewLineOptions; + + if (!viewportData || !viewLineOptions) { + return null; + } + + const visibleRanges: LineVisibleRanges[] = []; + + let nextLineModelLineNumber: number = 0; + if (includeNewLines) { + nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; + } + + for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { + + if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { + continue; + } + const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; + const continuesInNextLine = lineNumber !== range.endLineNumber; + const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn; + + const visibleRangesForLine = this._visibleRangesForLineRange(lineNumber, startColumn, endColumn); + + if (!visibleRangesForLine) { + continue; + } + + if (includeNewLines && lineNumber < originalEndLineNumber) { + const currentLineModelLineNumber = nextLineModelLineNumber; + nextLineModelLineNumber = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber; + + if (currentLineModelLineNumber !== nextLineModelLineNumber) { + visibleRangesForLine.ranges[visibleRangesForLine.ranges.length - 1].width += viewLineOptions.spaceWidth; + } + } + + visibleRanges.push(new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine)); + } + + if (visibleRanges.length === 0) { + return null; + } + + return visibleRanges; } private _visibleRangesForLineRange(lineNumber: number, startColumn: number, endColumn: number): VisibleRanges | null { @@ -448,20 +519,19 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // Resolve tab widths for this line const lineData = viewportData.getViewLineRenderingData(lineNumber); const content = lineData.content; - let startColumnResolvedTabWidth = 0; - let endColumnResolvedTabWidth = 0; + let resolvedStartColumnLeft = 0; for (let x = 0; x < startColumn - 1; x++) { - startColumnResolvedTabWidth += content[x] === '\t' ? lineData.tabSize : 1; + resolvedStartColumnLeft += content[x] === '\t' ? lineData.tabSize : 1; } - endColumnResolvedTabWidth = startColumnResolvedTabWidth; + let resolvedRangeWidth = 0; for (let x = startColumn - 1; x < endColumn - 1; x++) { - endColumnResolvedTabWidth += content[x] === '\t' ? lineData.tabSize : 1; + resolvedRangeWidth += content[x] === '\t' ? lineData.tabSize : 1; } // Visible horizontal range in _scaled_ pixels const result = new VisibleRanges(false, [new FloatHorizontalRange( - startColumnResolvedTabWidth * viewLineOptions.spaceWidth, - endColumnResolvedTabWidth * viewLineOptions.spaceWidth) + resolvedStartColumnLeft * viewLineOptions.spaceWidth, + resolvedRangeWidth * viewLineOptions.spaceWidth) ]); return result;