diff --git a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts index 2d4365da93bbb..852e879621691 100644 --- a/src/vs/editor/browser/gpu/atlas/textureAtlas.ts +++ b/src/vs/editor/browser/gpu/atlas/textureAtlas.ts @@ -28,6 +28,12 @@ export class TextureAtlas extends Disposable { private readonly _warmedUpRasterizers = new Set(); private readonly _allocatorType: AllocatorType; + /** + * The maximum number of texture atlas pages. This is currently a hard static cap that must not + * be reached. + */ + static readonly maximumPageCount = 16; + /** * The main texture atlas pages which are both larger textures and more efficiently packed * relative to the scratch page. The idea is the main pages are drawn to and uploaded to the GPU @@ -131,7 +137,9 @@ export class TextureAtlas extends Disposable { } private _getGlyphFromNewPage(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, charMetadata: number): Readonly { - // TODO: Support more than 2 pages and the GPU texture layer limit + if (this._pages.length >= TextureAtlas.maximumPageCount) { + throw new Error(`Attempt to create a texture atlas page past the limit ${TextureAtlas.maximumPageCount}`); + } this._pages.push(this._instantiationService.createInstance(TextureAtlasPage, this._pages.length, this.pageSize, this._allocatorType)); this._glyphPageIndex.set(chars, tokenMetadata, charMetadata, rasterizer.cacheKey, this._pages.length - 1); return this._pages[this._pages.length - 1].getGlyph(rasterizer, chars, tokenMetadata, charMetadata)!; diff --git a/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts b/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts index 531986de396f9..7aab399457b34 100644 --- a/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts +++ b/src/vs/editor/browser/gpu/fullFileRenderStrategy.wgsl.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { TextureAtlas } from './atlas/textureAtlas.js'; +import { TextureAtlasPage } from './atlas/textureAtlasPage.js'; import { BindingId } from './gpu.js'; export const fullFileRenderStrategyWgsl = /*wgsl*/ ` @@ -45,8 +47,7 @@ struct VSOutput { @group(0) @binding(${BindingId.ScrollOffset}) var scrollOffset: ScrollOffset; // Storage buffers -@group(0) @binding(${BindingId.GlyphInfo0}) var glyphInfo0: array; -@group(0) @binding(${BindingId.GlyphInfo1}) var glyphInfo1: array; +@group(0) @binding(${BindingId.GlyphInfo}) var glyphInfo: array, ${TextureAtlas.maximumPageCount}>; @group(0) @binding(${BindingId.Cells}) var cells: array; @vertex fn vs( @@ -55,14 +56,7 @@ struct VSOutput { @builtin(vertex_index) vertexIndex : u32 ) -> VSOutput { let cell = cells[instanceIndex]; - // TODO: Is there a nicer way to init this? - var glyph = glyphInfo0[0]; - let glyphIndex = u32(cell.glyphIndex); - if (u32(cell.textureIndex) == 0) { - glyph = glyphInfo0[glyphIndex]; - } else { - glyph = glyphInfo1[glyphIndex]; - } + var glyph = glyphInfo[u32(cell.textureIndex)][u32(cell.glyphIndex)]; var vsOut: VSOutput; // Multiple vert.position by 2,-2 to get it into clipspace which ranged from -1 to 1 diff --git a/src/vs/editor/browser/gpu/gpu.ts b/src/vs/editor/browser/gpu/gpu.ts index 8d965fcc5aab8..cbd292a1b3d3c 100644 --- a/src/vs/editor/browser/gpu/gpu.ts +++ b/src/vs/editor/browser/gpu/gpu.ts @@ -8,8 +8,7 @@ import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js'; export const enum BindingId { - GlyphInfo0, - GlyphInfo1, + GlyphInfo, Cells, TextureSampler, Texture, diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index 45c0050f2080f..5f3fa9a7064cc 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -24,6 +24,7 @@ import { ViewPart } from '../../view/viewPart.js'; import { ViewLineOptions } from '../viewLines/viewLineOptions.js'; import type * as viewEvents from '../../../common/viewEvents.js'; import { CursorColumns } from '../../../common/core/cursorColumns.js'; +import { TextureAtlas } from '../../gpu/atlas/textureAtlas.js'; const enum GlyphStorageBufferInfo { FloatsPerEntry = 2 + 2 + 2, @@ -52,7 +53,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _vertexBuffer!: GPUBuffer; - private readonly _glyphStorageBuffer: GPUBuffer[] = []; + private _glyphStorageBuffer!: GPUBuffer; private _atlasGpuTexture!: GPUTexture; private readonly _atlasGpuTextureVersions: number[] = []; @@ -190,14 +191,9 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { this._renderStrategy = this._register(this._instantiationService.createInstance(FullFileRenderStrategy, this._context, this._viewGpuContext, this._device)); - this._glyphStorageBuffer[0] = this._register(GPULifecycle.createBuffer(this._device, { - label: 'Monaco glyph storage buffer [0]', - size: GlyphStorageBufferInfo.BytesPerEntry * TextureAtlasPage.maximumGlyphCount, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, - })).object; - this._glyphStorageBuffer[1] = this._register(GPULifecycle.createBuffer(this._device, { - label: 'Monaco glyph storage buffer [1]', - size: GlyphStorageBufferInfo.BytesPerEntry * TextureAtlasPage.maximumGlyphCount, + this._glyphStorageBuffer = this._register(GPULifecycle.createBuffer(this._device, { + label: 'Monaco glyph storage buffer', + size: TextureAtlas.maximumPageCount * (TextureAtlasPage.maximumGlyphCount * GlyphStorageBufferInfo.BytesPerEntry), usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, })).object; this._atlasGpuTextureVersions[0] = 0; @@ -205,8 +201,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { this._atlasGpuTexture = this._register(GPULifecycle.createTexture(this._device, { label: 'Monaco atlas texture', format: 'rgba8unorm', - // TODO: Dynamically grow/shrink layer count - size: { width: atlas.pageSize, height: atlas.pageSize, depthOrArrayLayers: 2 }, + size: { width: atlas.pageSize, height: atlas.pageSize, depthOrArrayLayers: TextureAtlas.maximumPageCount }, dimension: '2d', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | @@ -281,8 +276,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { layout: this._pipeline.getBindGroupLayout(0), entries: [ // TODO: Pass in generically as array? - { binding: BindingId.GlyphInfo0, resource: { buffer: this._glyphStorageBuffer[0] } }, - { binding: BindingId.GlyphInfo1, resource: { buffer: this._glyphStorageBuffer[1] } }, + { binding: BindingId.GlyphInfo, resource: { buffer: this._glyphStorageBuffer } }, { binding: BindingId.TextureSampler, resource: this._device.createSampler({ label: 'Monaco atlas sampler', @@ -314,9 +308,8 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _updateAtlasStorageBufferAndTexture() { for (const [layerIndex, page] of ViewGpuContext.atlas.pages.entries()) { - if (layerIndex >= 2) { - // TODO: Support arbitrary number of layers - console.log(`Attempt to upload atlas page [${layerIndex}], only 2 are supported currently`); + if (layerIndex >= TextureAtlas.maximumPageCount) { + console.log(`Attempt to upload atlas page [${layerIndex}], only ${TextureAtlas.maximumPageCount} are supported currently`); continue; } @@ -329,7 +322,8 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { // TODO: Reuse buffer instead of reconstructing each time // TODO: Dynamically set buffer size - const values = new Float32Array(GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount); + const entryCount = GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount; + const values = new Float32Array(entryCount); let entryOffset = 0; for (const glyph of page.glyphs) { values[entryOffset + GlyphStorageBufferInfo.Offset_TexturePosition] = glyph.x; @@ -343,7 +337,13 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { if (entryOffset / GlyphStorageBufferInfo.FloatsPerEntry > TextureAtlasPage.maximumGlyphCount) { throw new Error(`Attempting to write more glyphs (${entryOffset / GlyphStorageBufferInfo.FloatsPerEntry}) than the GPUBuffer can hold (${TextureAtlasPage.maximumGlyphCount})`); } - this._device.queue.writeBuffer(this._glyphStorageBuffer[layerIndex], 0, values); + this._device.queue.writeBuffer( + this._glyphStorageBuffer, + layerIndex * GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount * Float32Array.BYTES_PER_ELEMENT, + values, + 0, + GlyphStorageBufferInfo.FloatsPerEntry * TextureAtlasPage.maximumGlyphCount + ); if (page.usedArea.right - page.usedArea.left > 0 && page.usedArea.bottom - page.usedArea.top > 0) { this._device.queue.copyExternalImageToTexture( { source: page.source },