Skip to content

Commit

Permalink
Move all gpu actions into a single action with a picker
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar committed Aug 23, 2024
1 parent c60f142 commit bd21f3c
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 116 deletions.
1 change: 1 addition & 0 deletions src/vs/editor/browser/gpu/atlas/textureAtlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class TextureAtlas extends Disposable {

// IMPORTANT: The first glyph on the first page must be an empty glyph such that zeroed out
// cells end up rendering nothing
// TODO: This currently means the first slab is for 0x0 glyphs and is wasted
firstPage.getGlyph(new GlyphRasterizer(1, ''), '', 0);

this._register(toDisposable(() => dispose(this._pages)));
Expand Down
233 changes: 117 additions & 116 deletions src/vs/editor/contrib/gpu/browser/gpuActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,137 +14,138 @@ import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';

const precondition = ContextKeyExpr.equals('config:editor.experimentalGpuAcceleration', 'on');

class LogTextureAtlasStatsAction extends EditorAction {

constructor() {
super({
id: 'editor.action.logTextureAtlasStats',
label: localize('logTextureAtlasStats.label', "Log Texture Atlas States"),
alias: 'Log Texture Atlas States',
precondition,
});
}

async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const logService = accessor.get(ILogService);

const atlas = ViewLinesGpu.atlas;
if (!ViewLinesGpu.atlas) {
logService.error('No texture atlas found');
return;
}

const stats = atlas.getStats();
logService.info(['Texture atlas stats', ...stats].join('\n\n'));
}
}

class SaveTextureAtlasAction extends EditorAction {

constructor() {
super({
id: 'editor.action.saveTextureAtlas',
label: localize('saveTextureAtlas.label', "Save Texture Atlas"),
alias: 'Save Texture Atlas',
precondition,
});
}

async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const workspaceContextService = accessor.get(IWorkspaceContextService);
const fileService = accessor.get(IFileService);
const folders = workspaceContextService.getWorkspace().folders;
if (folders.length > 0) {
const atlas = ViewLinesGpu.atlas;
const promises = [];
for (const [layerIndex, page] of atlas.pages.entries()) {
promises.push(...[
fileService.writeFile(
URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_actual.png`),
VSBuffer.wrap(new Uint8Array(await (await page.source.convertToBlob()).arrayBuffer()))
),
fileService.writeFile(
URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_usage.png`),
VSBuffer.wrap(new Uint8Array(await (await page.getUsagePreview()).arrayBuffer()))
),
]);
}
await promises;
}
}
}

class DrawGlyphAction extends EditorAction {
class DebugEditorGpuRendererAction extends EditorAction {

constructor() {
super({
id: 'editor.action.drawGlyph',
label: localize('drawGlyph.label', "Draw Glyph"),
alias: 'Draw Glyph',
precondition,
id: 'editor.action.debugEditorGpuRenderer',
label: localize('gpuDebug.label', "Developer: Debug Editor GPU Renderer"),
alias: 'Developer: Debug Editor GPU Renderer',
// TODO: Why doesn't `ContextKeyExpr.equals('config:editor.experimentalGpuAcceleration', 'on')` work?
precondition: ContextKeyExpr.true(),
});
}

async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
const fileService = accessor.get(IFileService);
const logService = accessor.get(ILogService);
const instantiationService = accessor.get(IInstantiationService);
const quickInputService = accessor.get(IQuickInputService);
const workspaceContextService = accessor.get(IWorkspaceContextService);

const folders = workspaceContextService.getWorkspace().folders;
if (folders.length === 0) {
const choice = await quickInputService.pick([
{
label: localize('logTextureAtlasStats.label', "Log Texture Atlas Stats"),
id: 'logTextureAtlasStats',
},
{
label: localize('saveTextureAtlas.label', "Save Texture Atlas"),
id: 'saveTextureAtlas',
},
{
label: localize('drawGlyph.label', "Draw Glyph"),
id: 'drawGlyph',
},
], { canPickMany: false });
if (!choice) {
return;
}

const atlas = ViewLinesGpu.atlas;
if (!ViewLinesGpu.atlas) {
logService.error('No texture atlas found');
return;
}

const fontFamily = configurationService.getValue<string>('editor.fontFamily');
const fontSize = configurationService.getValue<number>('editor.fontSize');
const rasterizer = new GlyphRasterizer(fontSize, fontFamily);
let chars = await quickInputService.input({
prompt: 'Enter a character to draw (prefix with 0x for code point))'
});
if (!chars) {
return;
}
const codePoint = chars.match(/0x(?<codePoint>[0-9a-f]+)/i)?.groups?.codePoint;
if (codePoint !== undefined) {
chars = String.fromCodePoint(parseInt(codePoint, 16));
}
const metadata = 0;
const rasterizedGlyph = atlas.getGlyph(rasterizer, chars, metadata);
if (!rasterizedGlyph) {
return;
}
const imageData = atlas.pages[rasterizedGlyph.pageIndex].source.getContext('2d')?.getImageData(
rasterizedGlyph.x,
rasterizedGlyph.y,
rasterizedGlyph.w,
rasterizedGlyph.h
);
if (!imageData) {
return;
switch (choice.id) {
case 'logTextureAtlasStats':
instantiationService.invokeFunction(accessor => {
const logService = accessor.get(ILogService);

const atlas = ViewLinesGpu.atlas;
if (!ViewLinesGpu.atlas) {
logService.error('No texture atlas found');
return;
}

const stats = atlas.getStats();
logService.info(['Texture atlas stats', ...stats].join('\n\n'));
});
break;
case 'saveTextureAtlas':
instantiationService.invokeFunction(async accessor => {
const workspaceContextService = accessor.get(IWorkspaceContextService);
const fileService = accessor.get(IFileService);
const folders = workspaceContextService.getWorkspace().folders;
if (folders.length > 0) {
const atlas = ViewLinesGpu.atlas;
const promises = [];
for (const [layerIndex, page] of atlas.pages.entries()) {
promises.push(...[
fileService.writeFile(
URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_actual.png`),
VSBuffer.wrap(new Uint8Array(await (await page.source.convertToBlob()).arrayBuffer()))
),
fileService.writeFile(
URI.joinPath(folders[0].uri, `textureAtlasPage${layerIndex}_usage.png`),
VSBuffer.wrap(new Uint8Array(await (await page.getUsagePreview()).arrayBuffer()))
),
]);
}
await Promise.all(promises);
}
});
break;
case 'drawGlyph':
instantiationService.invokeFunction(async accessor => {
const configurationService = accessor.get(IConfigurationService);
const fileService = accessor.get(IFileService);
const logService = accessor.get(ILogService);
const quickInputService = accessor.get(IQuickInputService);
const workspaceContextService = accessor.get(IWorkspaceContextService);

const folders = workspaceContextService.getWorkspace().folders;
if (folders.length === 0) {
return;
}

const atlas = ViewLinesGpu.atlas;
if (!ViewLinesGpu.atlas) {
logService.error('No texture atlas found');
return;
}

const fontFamily = configurationService.getValue<string>('editor.fontFamily');
const fontSize = configurationService.getValue<number>('editor.fontSize');
const rasterizer = new GlyphRasterizer(fontSize, fontFamily);
let chars = await quickInputService.input({
prompt: 'Enter a character to draw (prefix with 0x for code point))'
});
if (!chars) {
return;
}
const codePoint = chars.match(/0x(?<codePoint>[0-9a-f]+)/i)?.groups?.codePoint;
if (codePoint !== undefined) {
chars = String.fromCodePoint(parseInt(codePoint, 16));
}
const metadata = 0;
const rasterizedGlyph = atlas.getGlyph(rasterizer, chars, metadata);
if (!rasterizedGlyph) {
return;
}
const imageData = atlas.pages[rasterizedGlyph.pageIndex].source.getContext('2d')?.getImageData(
rasterizedGlyph.x,
rasterizedGlyph.y,
rasterizedGlyph.w,
rasterizedGlyph.h
);
if (!imageData) {
return;
}
const canvas = new OffscreenCanvas(imageData.width, imageData.height);
const ctx = ensureNonNullable(canvas.getContext('2d'));
ctx.putImageData(imageData, 0, 0);
const blob = await canvas.convertToBlob({ type: 'image/png' });
const resource = URI.joinPath(folders[0].uri, `glyph_${chars}_${metadata}_${fontSize}px_${fontFamily.replaceAll(/[,\\\/\.'\s]/g, '_')}.png`);
await fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(await blob.arrayBuffer())));
});
break;
}
const canvas = new OffscreenCanvas(imageData.width, imageData.height);
const ctx = ensureNonNullable(canvas.getContext('2d'));
ctx.putImageData(imageData, 0, 0);
const blob = await canvas.convertToBlob({ type: 'image/png' });
const resource = URI.joinPath(folders[0].uri, `glyph_${chars}_${metadata}_${fontSize}px_${fontFamily.replaceAll(/[,\\\/\.'\s]/g, '_')}.png`);
await fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(await blob.arrayBuffer())));
}
}

registerEditorAction(LogTextureAtlasStatsAction);
registerEditorAction(SaveTextureAtlasAction);
registerEditorAction(DrawGlyphAction);
registerEditorAction(DebugEditorGpuRendererAction);

0 comments on commit bd21f3c

Please sign in to comment.