Path: blob/main/src/vs/editor/contrib/gpu/browser/gpuActions.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 { VSBuffer } from '../../../../base/common/buffer.js';7import { URI } from '../../../../base/common/uri.js';8import { localize, localize2 } from '../../../../nls.js';9import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';10import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';11import { IFileService } from '../../../../platform/files/common/files.js';12import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';13import { ILogService } from '../../../../platform/log/common/log.js';14import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';15import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';16import type { ICodeEditor } from '../../../browser/editorBrowser.js';17import { EditorAction, registerEditorAction, type ServicesAccessor } from '../../../browser/editorExtensions.js';18import { ensureNonNullable } from '../../../browser/gpu/gpuUtils.js';19import { GlyphRasterizer } from '../../../browser/gpu/raster/glyphRasterizer.js';20import { ViewGpuContext } from '../../../browser/gpu/viewGpuContext.js';2122class DebugEditorGpuRendererAction extends EditorAction {2324constructor() {25super({26id: 'editor.action.debugEditorGpuRenderer',27label: localize2('gpuDebug.label', "Developer: Debug Editor GPU Renderer"),28// TODO: Why doesn't `ContextKeyExpr.equals('config:editor.experimentalGpuAcceleration', 'on')` work?29precondition: ContextKeyExpr.true(),30});31}3233async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {34const instantiationService = accessor.get(IInstantiationService);35const quickInputService = accessor.get(IQuickInputService);36const choice = await quickInputService.pick([37{38label: localize('logTextureAtlasStats.label', "Log Texture Atlas Stats"),39id: 'logTextureAtlasStats',40},41{42label: localize('saveTextureAtlas.label', "Save Texture Atlas"),43id: 'saveTextureAtlas',44},45{46label: localize('drawGlyph.label', "Draw Glyph"),47id: 'drawGlyph',48},49], { canPickMany: false });50if (!choice) {51return;52}53switch (choice.id) {54case 'logTextureAtlasStats':55instantiationService.invokeFunction(accessor => {56const logService = accessor.get(ILogService);5758const atlas = ViewGpuContext.atlas;59if (!ViewGpuContext.atlas) {60logService.error('No texture atlas found');61return;62}6364const stats = atlas.getStats();65logService.info(['Texture atlas stats', ...stats].join('\n\n'));66});67break;68case 'saveTextureAtlas':69instantiationService.invokeFunction(async accessor => {70const workspaceContextService = accessor.get(IWorkspaceContextService);71const fileService = accessor.get(IFileService);72const folders = workspaceContextService.getWorkspace().folders;73if (folders.length > 0) {74const atlas = ViewGpuContext.atlas;75const promises = [];76for (const [layerIndex, page] of atlas.pages.entries()) {77promises.push(...[78fileService.writeFile(79URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_actual.png`),80VSBuffer.wrap(new Uint8Array(await (await page.source.convertToBlob()).arrayBuffer()))81),82fileService.writeFile(83URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_usage.png`),84VSBuffer.wrap(new Uint8Array(await (await page.getUsagePreview()).arrayBuffer()))85),86]);87}88await Promise.all(promises);89}90});91break;92case 'drawGlyph':93instantiationService.invokeFunction(async accessor => {94const configurationService = accessor.get(IConfigurationService);95const fileService = accessor.get(IFileService);96const quickInputService = accessor.get(IQuickInputService);97const workspaceContextService = accessor.get(IWorkspaceContextService);9899const folders = workspaceContextService.getWorkspace().folders;100if (folders.length === 0) {101return;102}103104const atlas = ViewGpuContext.atlas;105const fontFamily = configurationService.getValue<string>('editor.fontFamily');106const fontSize = configurationService.getValue<number>('editor.fontSize');107const rasterizer = new GlyphRasterizer(fontSize, fontFamily, getActiveWindow().devicePixelRatio, ViewGpuContext.decorationStyleCache);108let chars = await quickInputService.input({109prompt: 'Enter a character to draw (prefix with 0x for code point))'110});111if (!chars) {112return;113}114const codePoint = chars.match(/0x(?<codePoint>[0-9a-f]+)/i)?.groups?.codePoint;115if (codePoint !== undefined) {116chars = String.fromCodePoint(parseInt(codePoint, 16));117}118const tokenMetadata = 0;119const charMetadata = 0;120const rasterizedGlyph = atlas.getGlyph(rasterizer, chars, tokenMetadata, charMetadata, 0);121if (!rasterizedGlyph) {122return;123}124const imageData = atlas.pages[rasterizedGlyph.pageIndex].source.getContext('2d')?.getImageData(125rasterizedGlyph.x,126rasterizedGlyph.y,127rasterizedGlyph.w,128rasterizedGlyph.h129);130if (!imageData) {131return;132}133const canvas = new OffscreenCanvas(imageData.width, imageData.height);134const ctx = ensureNonNullable(canvas.getContext('2d'));135ctx.putImageData(imageData, 0, 0);136const blob = await canvas.convertToBlob({ type: 'image/png' });137const resource = URI.joinPath(folders[0].uri, `glyph_${chars}_${tokenMetadata}_${fontSize}px_${fontFamily.replaceAll(/[,\\\/\.'\s]/g, '_')}.png`);138await fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(await blob.arrayBuffer())));139});140break;141}142}143}144145registerEditorAction(DebugEditorGpuRendererAction);146147148