From bcb90fc6f23e750afe3673c28ca8885deea4a3d9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Mar 2024 08:48:08 -0700 Subject: [PATCH] Render characters in approximately the right position --- .../editor/browser/view/gpu/gpuViewLayer.ts | 112 +++++++++++++----- .../editor/browser/view/gpu/textureAtlas.ts | 5 + 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/src/vs/editor/browser/view/gpu/gpuViewLayer.ts b/src/vs/editor/browser/view/gpu/gpuViewLayer.ts index a96cdb33ab31b..b5270ce35cf3d 100644 --- a/src/vs/editor/browser/view/gpu/gpuViewLayer.ts +++ b/src/vs/editor/browser/view/gpu/gpuViewLayer.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getActiveWindow } from 'vs/base/browser/dom'; import { TextureAtlas, type ITextureAtlasGlyph } from 'vs/editor/browser/view/gpu/textureAtlas'; import type { IVisibleLine, IVisibleLinesHost } from 'vs/editor/browser/view/viewLayer'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; @@ -14,7 +15,7 @@ interface IRendererContext { } const enum Constants { - IndicesPerCell = 3 + IndicesPerCell = 6 } const enum BindingId { @@ -47,7 +48,9 @@ struct Vertex { struct DynamicUnitInfo { position: vec2f, + unused1: vec2f, textureId: f32, + unused2: f32 }; struct VSOutput { @@ -70,14 +73,15 @@ struct VSOutput { let spriteInfo = spriteInfo[u32(dynamicUnitInfo.textureId)]; var vsOut: VSOutput; + // Multiple vert.position by 2,-2 to get it into clipspace which ranged from -1 to 1 vsOut.position = vec4f( - (((vert.position * 2 - 1) / uniforms.canvasDimensions)) * spriteInfo.size + dynamicUnitInfo.position, + (((vert.position * vec2f(2, -2)) / uniforms.canvasDimensions)) * spriteInfo.size + dynamicUnitInfo.position, 0.0, 1.0 ); // Textures are flipped from natural direction on the y-axis, so flip it back - vsOut.texcoord = vec2f(vert.position.x, 1.0 - vert.position.y); + vsOut.texcoord = vert.position; vsOut.texcoord = ( // Sprite offset (0-1) (spriteInfo.position / textureInfoUniform.spriteSheetSize) + @@ -92,8 +96,6 @@ struct VSOutput { @group(0) @binding(${BindingId.Texture}) var ourTexture: texture_2d; @fragment fn fs(vsOut: VSOutput) -> @location(0) vec4f { - // var a = textureSample(ourTexture, ourSampler, vsOut.texcoord); - // return vec4f(1.0, 0.0, 0.0, 1.0); return textureSample(ourTexture, ourSampler, vsOut.texcoord); } `; @@ -119,6 +121,8 @@ export class GpuViewLayerRenderer { private _vertexBuffer!: GPUBuffer; private _squareVertices!: { vertexData: Float32Array; numVertices: number }; + private _textureAtlas!: TextureAtlas; + private _initialized = false; constructor(domNode: HTMLCanvasElement, host: IVisibleLinesHost, viewportData: ViewportData) { @@ -211,7 +215,7 @@ export class GpuViewLayerRenderer { // Create texture atlas - const textureAtlas = new TextureAtlas(this.domNode, this._device.limits.maxTextureDimension2D); + const textureAtlas = this._textureAtlas = new TextureAtlas(this.domNode, this._device.limits.maxTextureDimension2D); @@ -290,7 +294,8 @@ export class GpuViewLayerRenderer { - const cellCount = 2; + // TODO: Grow/shrink buffer size dynamically + const cellCount = 10000; const bufferSize = cellCount * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT; this._dataBindBuffer = this._device.createBuffer({ label: 'Entity dynamic info buffer', @@ -380,11 +385,11 @@ export class GpuViewLayerRenderer { } private _renderWebgpu(ctx: IRendererContext, startLineNumber: number, stopLineNumber: number, deltaTop: number[]): IRendererContext { - - const visibleObjectCount = this._updateDataBuffer(); + // TODO: Improve "data" name + const dataBuffer = new Float32Array(this._dataValueBuffers[this._dataValuesBufferActiveIndex]); + const visibleObjectCount = this._updateDataBuffer(dataBuffer, ctx, startLineNumber, stopLineNumber, deltaTop); // Write buffer and swap it out to unblock writes - const dataBuffer = new Float32Array(this._dataValueBuffers[this._dataValuesBufferActiveIndex]); this._device.queue.writeBuffer(this._dataBindBuffer, 0, dataBuffer, 0, visibleObjectCount * Constants.IndicesPerCell); this._dataValuesBufferActiveIndex = (this._dataValuesBufferActiveIndex + 1) % 2; @@ -409,7 +414,9 @@ export class GpuViewLayerRenderer { return ctx; } - private _updateDataBuffer() { + // TODO: This update could be moved to an arbitrary task thread if expensive? + private _updateDataBuffer(dataBuffer: Float32Array, ctx: IRendererContext, startLineNumber: number, stopLineNumber: number, deltaTop: number[]): number { + let chars: string = ''; let screenAbsoluteX: number = 0; let screenAbsoluteY: number = 0; let zeroToOneX: number = 0; @@ -417,25 +424,68 @@ export class GpuViewLayerRenderer { let wgslX: number = 0; let wgslY: number = 0; - screenAbsoluteX = 100; - screenAbsoluteY = 100; - - screenAbsoluteX = Math.round(screenAbsoluteX); - screenAbsoluteY = Math.round(screenAbsoluteY); - zeroToOneX = screenAbsoluteX / this.domNode.width; - zeroToOneY = screenAbsoluteY / this.domNode.height; - wgslX = zeroToOneX * 2 - 1; - wgslY = zeroToOneY * 2 - 1; - - const offset = 0; - const objectCount = 1; - const data = new Float32Array(objectCount * Constants.IndicesPerCell); - data[offset] = wgslX; // x - data[offset + 1] = -wgslY; // y - data[offset + 2] = 1; // textureIndex - - const storageValues = new Float32Array(this._dataValueBuffers[this._dataValuesBufferActiveIndex]); - storageValues.set(data); - return objectCount; + const activeWindow = getActiveWindow(); + let charCount = 0; + let scrollTop = parseInt(this.domNode.parentElement!.getAttribute('data-adjusted-scroll-top')!); + if (Number.isNaN(scrollTop)) { + scrollTop = 0; + } + for (let lineNumber = startLineNumber; lineNumber <= stopLineNumber; lineNumber++) { + const y = Math.round((-scrollTop + deltaTop[lineNumber - startLineNumber])); + // Offscreen + if (y < 0) { + continue; + } + const content = this.viewportData.getViewLineRenderingData(lineNumber).content; + // console.log(content, 0, y); + for (let x = 0; x < content.length; x++) { + if (content.charAt(x) === ' ') { + continue; + } + // TODO: Handle tab + + chars = content[x]; + // TODO: Get glyph + + // TODO: Move math to gpu + // TODO: Render using a line offset for partial line scrolling + // TODO: Sub-pixel rendering + screenAbsoluteX = x * 7 * activeWindow.devicePixelRatio; + // TODO: This +10 is because the glyph is being rendered in the wrong position + screenAbsoluteY = Math.round(y * activeWindow.devicePixelRatio); + zeroToOneX = screenAbsoluteX / this.domNode.width; + zeroToOneY = screenAbsoluteY / this.domNode.height; + wgslX = zeroToOneX * 2 - 1; + wgslY = zeroToOneY * 2 - 1; + + dataBuffer[charCount * Constants.IndicesPerCell + 0] = wgslX; // x + dataBuffer[charCount * Constants.IndicesPerCell + 1] = -wgslY; // y + dataBuffer[charCount * Constants.IndicesPerCell + 2] = 0; + dataBuffer[charCount * Constants.IndicesPerCell + 3] = 0; + dataBuffer[charCount * Constants.IndicesPerCell + 4] = 1; // textureIndex + dataBuffer[charCount * Constants.IndicesPerCell + 5] = 0; + + charCount++; + } + } + // console.log('charCount: ' + charCount); + return charCount; + + + + + // screenAbsoluteX = 100; + // screenAbsoluteY = 100; + + + // const offset = 0; + // const objectCount = 1; + // const data = new Float32Array(objectCount * Constants.IndicesPerCell); + // data[offset] = wgslX; // x + // data[offset + 1] = -wgslY; // y + // data[offset + 2] = 1; // textureIndex + + // storageValues.set(data); + // return objectCount; } } diff --git a/src/vs/editor/browser/view/gpu/textureAtlas.ts b/src/vs/editor/browser/view/gpu/textureAtlas.ts index e52bd360d41d6..aee130db4cc3f 100644 --- a/src/vs/editor/browser/view/gpu/textureAtlas.ts +++ b/src/vs/editor/browser/view/gpu/textureAtlas.ts @@ -15,6 +15,8 @@ export class TextureAtlas extends Disposable { private _glyphRasterizer: GlyphRasterizer; + private _nextId = 0; + public get source(): OffscreenCanvas { return this._canvas; } @@ -63,6 +65,7 @@ export class TextureAtlas extends Disposable { } // TODO: Implement allocation glyph = { + id: this._nextId++, x: 0, y: 0, w: rasterizedGlyph.boundingBox.right - rasterizedGlyph.boundingBox.left, @@ -108,6 +111,7 @@ class GlyphRasterizer extends Disposable { return result; } + // TODO: Pass back origin offset private _findGlyphBoundingBox(imageData: ImageData): IBoundingBox { // TODO: Hot path: Reuse object const boundingBox = { @@ -183,6 +187,7 @@ class GlyphRasterizer extends Disposable { } export interface ITextureAtlasGlyph { + id: number; x: number; y: number; w: number;