Skip to content

Commit 299d0b8

Browse files
feat: Support other console functionalities (#1855)
1 parent 12b2d38 commit 299d0b8

11 files changed

Lines changed: 438 additions & 51 deletions

File tree

apps/typegpu-docs/src/content/docs/fundamentals/utils.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,12 @@ When using `console.log`, atomic operations are injected into the WGSL code to s
191191
This synchronization can introduce overhead and significantly impact shader performance.
192192
:::
193193

194+
Other supported `console` functionalities include `console.debug`, `console.info`, `console.warn`, `console.error` and `console.clear`.
195+
194196
There are some limitations (some of which we intend to alleviate in the future):
195197

196198
- `console.log` only works when used in TGSL, when calling or resolving a TypeGPU pipeline.
197199
Otherwise, for example when using `tgpu.resolve` on a WGSL template, logs are ignored.
198200
- `console.log` only works in fragment and compute shaders.
199201
This is due to a [WebGPU limitation](https://www.w3.org/TR/WGSL/#address-space) that does not allow modifying buffers during the vertex shader stage.
200-
- `console.log` currently does not support template literals and string substitutions.
201-
- Other `console` methods like `clear` or `warn` are not yet supported.
202+
- `console.log` currently does not support template literals (but you can use [string substitutions](https://developer.mozilla.org/en-US/docs/Web/API/console#using_string_substitutions), or just pass multiple arguments instead).

apps/typegpu-docs/src/examples/tests/log-test/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,37 @@ export const controls = {
182182
test.dispatchThreads();
183183
},
184184
},
185+
'String interpolation': {
186+
onButtonClick: async () =>
187+
root['~unstable'].createGuardedComputePipeline(() => {
188+
'use gpu';
189+
console.log(
190+
'The values %d, %f and %s were interpolated in this message.',
191+
987,
192+
1.26,
193+
d.vec4f(1, 2, 3, 4),
194+
);
195+
console.log(
196+
'TypeGPU resources can be used as strings (%s) or objects (%o).',
197+
d.vec3f(1, 2, 3),
198+
d.vec3f(1, 2, 3),
199+
'This sentence is the fourth argument of the log.',
200+
);
201+
}).dispatchThreads(),
202+
},
203+
'Different log functionalities': {
204+
onButtonClick: async () =>
205+
root['~unstable'].createGuardedComputePipeline(() => {
206+
'use gpu';
207+
console.log('This message should be cleared.');
208+
console.clear();
209+
console.log('This is a log message.', 'Index:', 1);
210+
console.debug('This is a debug message.', 'Index:', 2);
211+
console.info('This is an info message.', 'Index:', 3);
212+
console.warn('This is a warn message.', 'Index:', 4);
213+
console.error('This is an error message.', 'Index:', 5);
214+
}).dispatchThreads(),
215+
},
185216
'Render pipeline': {
186217
onButtonClick: () => {
187218
const context = canvas.getContext('webgpu') as GPUCanvasContext;

packages/typegpu/src/data/dataTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export class MatrixColumnsAccess {
259259

260260
export class ConsoleLog {
261261
[$internal] = true;
262-
constructor() {
262+
constructor(readonly op: string) {
263263
setName(this, 'consoleLog');
264264
}
265265
}

packages/typegpu/src/resolutionCtx.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,8 @@ export class ResolutionCtxImpl implements ResolutionCtx {
436436
this._itemStateStack.popBlockScope();
437437
}
438438

439-
generateLog(args: Snippet[]): Snippet {
440-
return this.#logGenerator.generateLog(this, args);
439+
generateLog(op: string, args: Snippet[]): Snippet {
440+
return this.#logGenerator.generateLog(this, op, args);
441441
}
442442

443443
get logResources(): LogResources | undefined {

packages/typegpu/src/tgsl/consoleLog/deserializers.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import type { Infer } from '../../shared/repr.ts';
2727
import { niceStringify } from '../../shared/stringify.ts';
2828
import { bitcastU32toF32, bitcastU32toI32 } from '../../std/bitcast.ts';
2929
import { unpack2x16float } from '../../std/packing.ts';
30-
import type { LogResources } from './types.ts';
30+
import type { LogMeta, LogResources } from './types.ts';
3131

3232
const toF = (n: number | undefined) => bitcastU32toF32(n ?? 0);
3333
const toI = (n: number | undefined) => bitcastU32toI32(n ?? 0);
@@ -159,10 +159,9 @@ function deserializeCompound(
159159
export function deserializeAndStringify(
160160
serializedData: Uint32Array,
161161
argTypes: (AnyWgslData | string)[],
162-
): string {
162+
): string[] {
163163
return deserializeCompound(serializedData, argTypes)
164-
.map(niceStringify)
165-
.join(' ');
164+
.map(niceStringify);
166165
}
167166

168167
/**
@@ -175,22 +174,26 @@ export function deserializeAndStringify(
175174
* - After processing, the index buffer and the data buffer are cleared.
176175
*/
177176
export function logDataFromGPU(resources: LogResources) {
178-
const { indexBuffer, dataBuffer, logIdToArgTypes, options } = resources;
177+
const { indexBuffer, dataBuffer, logIdToMeta, options } = resources;
179178

180179
dataBuffer.read().then((data) => {
181180
data
182181
.filter((e) => e.id)
183182
.forEach(({ id, serializedData }) => {
184-
const argTypes = logIdToArgTypes.get(id) as (AnyWgslData | string)[];
185-
const result = deserializeAndStringify(
183+
const { argTypes, op } = logIdToMeta.get(id) as LogMeta;
184+
const results = deserializeAndStringify(
186185
new Uint32Array(serializedData),
187186
argTypes,
188187
);
189-
console.log(
190-
`${options.messagePrefix}${result}`,
188+
if (results.length === 0) {
189+
results.push('');
190+
}
191+
console[op](...[
192+
`%c${options.messagePrefix}%c ${results[0]}`,
191193
'background: #936ff5; color: white;',
192-
'',
193-
);
194+
'color: inherit; background: none',
195+
...results.slice(1),
196+
]);
194197
});
195198
});
196199

packages/typegpu/src/tgsl/consoleLog/logGenerator.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,24 @@ import {
2020
type GenerationCtx,
2121
} from '../generationHelpers.ts';
2222
import { createLoggingFunction } from './serializers.ts';
23-
import type {
24-
LogGenerator,
25-
LogGeneratorOptions,
26-
LogResources,
27-
SerializedLogCallData,
23+
import {
24+
type LogGenerator,
25+
type LogGeneratorOptions,
26+
type LogMeta,
27+
type LogResources,
28+
type SerializedLogCallData,
29+
type SupportedLogOps,
30+
supportedLogOps,
2831
} from './types.ts';
2932

3033
const defaultOptions: Required<LogGeneratorOptions> = {
3134
logCountLimit: 64,
3235
logSizeLimit: 252,
33-
messagePrefix: '%c GPU %c ',
36+
messagePrefix: ' GPU ',
3437
};
3538

39+
const fallbackSnippet = snip('/* console.log() */', Void);
40+
3641
export class LogGeneratorNullImpl implements LogGenerator {
3742
get logResources(): undefined {
3843
return undefined;
@@ -41,20 +46,20 @@ export class LogGeneratorNullImpl implements LogGenerator {
4146
console.warn(
4247
"'console.log' is currently only supported in compute pipelines.",
4348
);
44-
return snip('/* console.log() */', Void);
49+
return fallbackSnippet;
4550
}
4651
}
4752

4853
export class LogGeneratorImpl implements LogGenerator {
4954
#options: Required<LogGeneratorOptions>;
50-
#logIdToArgTypes: Map<number, (string | AnyWgslData)[]>;
55+
#logIdToMeta: Map<number, LogMeta>;
5156
#firstUnusedId = 1;
5257
#indexBuffer: TgpuMutable<Atomic<U32>>;
5358
#dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>;
5459

5560
constructor(root: TgpuRoot) {
5661
this.#options = { ...defaultOptions, ...root[$internal].logOptions };
57-
this.#logIdToArgTypes = new Map();
62+
this.#logIdToMeta = new Map();
5863

5964
const SerializedLogData = struct({
6065
id: u32,
@@ -77,7 +82,16 @@ export class LogGeneratorImpl implements LogGenerator {
7782
* @param args Argument snippets. Snippets of UnknownType will be treated as string literals.
7883
* @returns A snippet containing the call to the logging function.
7984
*/
80-
generateLog(ctx: GenerationCtx, args: Snippet[]): Snippet {
85+
generateLog(
86+
ctx: GenerationCtx,
87+
op: string,
88+
args: Snippet[],
89+
): Snippet {
90+
if (!supportedLogOps.includes(op as SupportedLogOps)) {
91+
console.warn(`Unsupported log method '${op}' was used in TGSL.`);
92+
return fallbackSnippet;
93+
}
94+
8195
const concreteArgs = concretizeSnippets(args);
8296
const id = this.#firstUnusedId++;
8397

@@ -92,15 +106,14 @@ export class LogGeneratorImpl implements LogGenerator {
92106
this.#options,
93107
);
94108

95-
this.#logIdToArgTypes.set(
96-
id,
97-
concreteArgs.map((e) =>
98-
e.dataType === UnknownData
99-
? (e.value as string)
100-
: e.dataType as AnyWgslData
101-
),
109+
const argTypes = concreteArgs.map((e) =>
110+
e.dataType === UnknownData
111+
? (e.value as string)
112+
: e.dataType as AnyWgslData
102113
);
103114

115+
this.#logIdToMeta.set(id, { op: op as SupportedLogOps, argTypes });
116+
104117
return snip(stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, Void);
105118
}
106119

@@ -109,7 +122,7 @@ export class LogGeneratorImpl implements LogGenerator {
109122
dataBuffer: this.#dataBuffer,
110123
indexBuffer: this.#indexBuffer,
111124
options: this.#options,
112-
logIdToArgTypes: this.#logIdToArgTypes,
125+
logIdToMeta: this.#logIdToMeta,
113126
};
114127
}
115128
}

packages/typegpu/src/tgsl/consoleLog/types.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface LogGeneratorOptions {
2727
logSizeLimit?: number;
2828
/**
2929
* The prefix attached to each log call.
30-
* @default "%c GPU %c "
30+
* @default ' GPU '
3131
*/
3232
messagePrefix?: string;
3333
}
@@ -37,22 +37,42 @@ export type SerializedLogCallData = WgslStruct<{
3737
serializedData: WgslArray<U32>;
3838
}>;
3939

40+
export interface LogMeta {
41+
op: SupportedLogOps;
42+
argTypes: (string | AnyWgslData)[];
43+
}
44+
4045
/**
4146
* The resources required for logging within the TGSL console.
4247
*
4348
* @property indexBuffer - A buffer used for indexing log entries. Needs to be cleared after each dispatch/draw.
4449
* @property dataBuffer - A buffer containing an array of serialized log call data.
4550
* @property options - The configuration options for the LogGenerator.
46-
* @property logIdToArgTypes - A mapping from log identifiers to their corresponding argument types.
51+
* @property logIdToMeta - A mapping from log identifiers to an object containing the corresponding log op and argument types.
4752
*/
4853
export interface LogResources {
4954
indexBuffer: TgpuMutable<Atomic<U32>>;
5055
dataBuffer: TgpuMutable<WgslArray<SerializedLogCallData>>;
5156
options: Required<LogGeneratorOptions>;
52-
logIdToArgTypes: Map<number, (string | AnyWgslData)[]>;
57+
logIdToMeta: Map<number, LogMeta>;
5358
}
5459

5560
export interface LogGenerator {
56-
generateLog(ctx: GenerationCtx, args: Snippet[]): Snippet;
61+
generateLog(
62+
ctx: GenerationCtx,
63+
op: string,
64+
args: Snippet[],
65+
): Snippet;
5766
get logResources(): LogResources | undefined;
5867
}
68+
69+
export const supportedLogOps = [
70+
'log',
71+
'debug',
72+
'info',
73+
'warn',
74+
'error',
75+
'clear',
76+
] as const;
77+
78+
export type SupportedLogOps = (typeof supportedLogOps)[number];

packages/typegpu/src/tgsl/generationHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export type GenerationCtx = ResolutionCtx & {
219219
dedent(): string;
220220
pushBlockScope(): void;
221221
popBlockScope(): void;
222-
generateLog(args: Snippet[]): Snippet;
222+
generateLog(op: string, args: Snippet[]): Snippet;
223223
getById(id: string): Snippet | null;
224224
defineVariable(id: string, snippet: Snippet): void;
225225

packages/typegpu/src/tgsl/wgslGenerator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,10 @@ ${this.ctx.pre}}`;
274274
const target = this.expression(targetNode);
275275

276276
if (target.value === console) {
277-
return snip(new ConsoleLog(), UnknownData);
277+
return snip(
278+
new ConsoleLog(property),
279+
UnknownData,
280+
);
278281
}
279282

280283
if (
@@ -508,7 +511,7 @@ ${this.ctx.pre}}`;
508511
}
509512

510513
if (callee.value instanceof ConsoleLog) {
511-
return this.ctx.generateLog(convertedArguments);
514+
return this.ctx.generateLog(callee.value.op, convertedArguments);
512515
}
513516

514517
// Assuming that `callee` is callable

0 commit comments

Comments
 (0)