From f7c6a764342b72117f0e2a131cfdcff9c4595668 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:52:18 +0200 Subject: [PATCH 01/21] Replace warn with throw --- .../tests/individual-example-tests/utils/testUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/typegpu-docs/tests/individual-example-tests/utils/testUtils.ts b/apps/typegpu-docs/tests/individual-example-tests/utils/testUtils.ts index 0a0e9e0658..65b73a450b 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/utils/testUtils.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/utils/testUtils.ts @@ -61,7 +61,7 @@ export function extractShaderCodes( [GPUShaderModuleDescriptor, { label?: string }] >; if (expectedCalls !== undefined && calls.length !== expectedCalls) { - console.warn( + throw new Error( `Expected ${expectedCalls} shader module creation calls, but got ${calls.length}.`, ); } From e2ce177151f54ddc8c9cb2d6d1f14219f90fffa6 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:28:13 +0200 Subject: [PATCH 02/21] Add logger, replace console.warns with logger warns --- .../src/core/pipeline/computePipeline.ts | 4 ++- .../src/core/pipeline/limitsOverflow.ts | 7 ++-- .../src/core/pipeline/pipelineUtils.ts | 4 ++- .../src/core/pipeline/renderPipeline.ts | 7 ++-- .../typegpu/src/core/resolve/externals.ts | 4 ++- .../typegpu/src/core/resolve/tgpuResolve.ts | 4 ++- packages/typegpu/src/core/root/init.ts | 8 +++-- packages/typegpu/src/core/texture/texture.ts | 7 ++-- packages/typegpu/src/data/compiledIO.ts | 9 +++-- packages/typegpu/src/data/dataIO.ts | 7 ++-- packages/typegpu/src/indexNamedExports.ts | 1 + packages/typegpu/src/tgpuLogger.ts | 36 +++++++++++++++++++ .../src/tgsl/consoleLog/deserializers.ts | 4 ++- .../src/tgsl/consoleLog/logGenerator.ts | 8 +++-- packages/typegpu/src/tgsl/conversion.ts | 7 ++-- .../typegpu/src/tgsl/generationHelpers.ts | 4 ++- .../typegpu/tests/tgsl/assignment.test.ts | 4 +-- 17 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 packages/typegpu/src/tgpuLogger.ts diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index 36e80ddca7..614f1b3f12 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -42,6 +42,7 @@ import { PerformanceTrackerImpl, type PerformanceTracker, } from './performanceTracker.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; interface ComputePipelineInternals { readonly rawPipeline: GPUComputePipeline; @@ -227,7 +228,8 @@ class TgpuComputePipelineImpl implements TgpuComputePipeline { const querySet = this.#core.performanceCallbackQuerySet; if (!querySet) { - console.warn( + tgpuLogger.warn( + 'missing-webgpu-feature', 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', ); return this; diff --git a/packages/typegpu/src/core/pipeline/limitsOverflow.ts b/packages/typegpu/src/core/pipeline/limitsOverflow.ts index 81d2883644..96f1ebb926 100644 --- a/packages/typegpu/src/core/pipeline/limitsOverflow.ts +++ b/packages/typegpu/src/core/pipeline/limitsOverflow.ts @@ -1,4 +1,5 @@ import type { TgpuBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSupportedLimits) { const entries = Object.values(layouts) @@ -9,7 +10,8 @@ export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSuppor const storage = entries.filter((entry) => 'storage' in entry).length; if (uniform > limits.maxUniformBuffersPerShaderStage) { - console.warn( + tgpuLogger.warn( + 'webgpu-limits-exceeded', `Total number of uniform buffers (${uniform}) exceeds maxUniformBuffersPerShaderStage (${limits.maxUniformBuffersPerShaderStage}). Consider: 1. Grouping some of the uniforms into one using 'd.struct', 2. Increasing the limit when requesting a device or creating a root.`, @@ -17,7 +19,8 @@ export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSuppor } if (storage > limits.maxStorageBuffersPerShaderStage) { - console.warn( + tgpuLogger.warn( + 'webgpu-limits-exceeded', `Total number of storage buffers (${storage}) exceeds maxStorageBuffersPerShaderStage (${limits.maxStorageBuffersPerShaderStage}).`, ); } diff --git a/packages/typegpu/src/core/pipeline/pipelineUtils.ts b/packages/typegpu/src/core/pipeline/pipelineUtils.ts index 80027b7386..ba80eee30d 100644 --- a/packages/typegpu/src/core/pipeline/pipelineUtils.ts +++ b/packages/typegpu/src/core/pipeline/pipelineUtils.ts @@ -3,6 +3,7 @@ import { memoryLayoutOf, type PrimitiveOffsetInfo } from '../../data/offsetUtils import { sizeOf } from '../../data/sizeOf.ts'; import type { BaseData } from '../../data/wgslTypes.ts'; import { isGPUBuffer } from '../../types.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; type IndirectOperation = 'dispatchWorkgroupsIndirect' | 'drawIndirect' | 'drawIndexedIndirect'; const IndirectOperationToRequiredData = { @@ -51,7 +52,8 @@ export function resolveIndirectOffset( validateIndirectBufferSize(sizeOf(indirectBuffer.dataType), offset, requiredSize, operation); if (contiguous < requiredSize) { - console.warn( + tgpuLogger.warn( + 'non-contiguous-memory', `${operation}: Starting at offset ${offset}, only ${contiguous} contiguous bytes are available before padding. '${operation}' requires ${requiredSize} bytes (${IndirectOperationToRequiredData[operation]}). Reading across padding may result in undefined behavior.`, ); } diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index 64d53cdc4a..ed68e15882 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -88,6 +88,7 @@ import { PerformanceTrackerImpl, type PerformanceTracker, } from './performanceTracker.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; const DRAW_INDIRECT_SIZE = 16; // 4 x 4 const DRAW_INDEXED_INDIRECT_SIZE = 20; // 5 x 4 @@ -601,7 +602,8 @@ class TgpuRenderPipelineImpl implements TgpuRenderPipeline { const querySet = internals.core.performanceCallbackQuerySet; if (!querySet) { - console.warn( + tgpuLogger.warn( + 'missing-webgpu-feature', 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', ); return this; @@ -1288,7 +1290,8 @@ export function matchUpVaryingLocations( if (locations[key] === undefined) { saveLocation(key, customLocation); } else if (locations[key] !== customLocation) { - console.warn( + tgpuLogger.warn( + 'mismatched-locations', `Mismatched location between vertexFn (${vertexFnName}) output (${ locations[key] }) and fragmentFn (${fragmentFnName}) input (${customLocation}) for the key "${key}", using the location set on vertex output.`, diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 856f3cbb8b..808db00ea8 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -1,6 +1,7 @@ import { isLooseData } from '../../data/dataTypes.ts'; import { isWgslStruct } from '../../data/wgslTypes.ts'; import { getName, hasTinyestMetadata, isNamable, setName } from '../../shared/meta.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; import { isWgsl, type ResolutionCtx } from '../../types.ts'; import type { FnExternals } from '../function/fnCore.ts'; @@ -124,7 +125,8 @@ export function replaceExternalsInWgsl( } if (typeof currentItem !== 'object' || currentItem === null || i === chain.length - 1) { - console.warn( + tgpuLogger.warn( + 'omitted-external', `During resolution, the external '${chain.slice(0, i + 1).join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, ); return match; diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index ecd6b42df4..75708e3d44 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -3,6 +3,7 @@ import { Void } from '../../data/wgslTypes.ts'; import { type ResolutionResult, resolve as resolveImpl } from '../../resolutionCtx.ts'; import { $internal, $resolve } from '../../shared/symbols.ts'; import { isBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; import type { ShaderGenerator } from '../../tgsl/shaderGenerator.ts'; import type { ResolvableObject, SelfResolvable, Wgsl } from '../../types.ts'; import type { WgslEnableExtension } from '../../wgslExtensions.ts'; @@ -190,7 +191,8 @@ function resolveFromTemplate(options: TgpuExtendedResolveOptions): ResolutionRes } = options; if (!template) { - console.warn( + tgpuLogger.warn( + 'deprecated-method-called', "Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.", ); } diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 504f0dc2dd..3e9029f212 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -81,6 +81,7 @@ import { u32 } from '../../data/numeric.ts'; import { ceil } from '../../std/numeric.ts'; import { allEq } from '../../std/boolean.ts'; import { getName, setName } from '../../shared/meta.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; /** * Changes the given array to a vec of 3 numbers, filling missing values with 1. @@ -659,7 +660,7 @@ class TgpuRootImpl extends WithBindingImpl implements TgpuRoot, ExperimentalTgpu } flush() { - console.warn('flush() has been deprecated, and has no effect.'); + tgpuLogger.warn('deprecated-method-called', 'flush() has been deprecated, and has no effect.'); } } @@ -741,7 +742,10 @@ export async function init(options?: InitOptions): Promise { if (adapter.features.has(feature)) { availableFeatures.push(feature); } else { - console.warn(`Optional feature "${feature}" is not supported by the adapter.`); + tgpuLogger.warn( + 'missing-webgpu-feature', + `Optional feature "${feature}" is not supported by the adapter.`, + ); } } diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 5103ec081c..94d5b6a429 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -33,6 +33,7 @@ import type { SampledFlag, } from './usageExtension.ts'; import { generateTextureMipmaps, getImageSourceDimensions, resampleImage } from './textureUtils.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; export type TextureInternals = { unwrap(): GPUTexture; @@ -394,7 +395,8 @@ class TgpuTextureImpl implements TgpuTexture implements TgpuTexture layerCount) { - console.warn( + tgpuLogger.warn( + 'suspicious-call', `Too many image sources provided. Expected ${layerCount} layers, got ${source.length}. Extra sources will be ignored.`, ); } diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts index 9ba97f39c3..81e6aafc61 100644 --- a/packages/typegpu/src/data/compiledIO.ts +++ b/packages/typegpu/src/data/compiledIO.ts @@ -1,4 +1,5 @@ import { roundUp } from '../mathUtils.ts'; +import { tgpuLogger } from '../tgpuLogger.ts'; import { alignmentOf } from './alignmentOf.ts'; import { isDisarray, isUnstruct } from './dataTypes.ts'; import { offsetsForProps } from './offsets.ts'; @@ -289,7 +290,10 @@ export function buildWriter( export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undefined { if (!EVAL_ALLOWED_IN_ENV) { - console.warn('This environment does not allow eval - using default writer as fallback'); + tgpuLogger.warn( + 'eval-not-supported-in-env', + 'This environment does not allow eval - using default writer as fallback', + ); return undefined; } @@ -316,7 +320,8 @@ export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undef compiledWriters.set(schema, fn); return fn; } catch (error) { - console.warn( + tgpuLogger.warn( + 'failed-to-compile-writer', `Failed to compile writer for schema: ${schema}\nReason: ${ error instanceof Error ? error.message : String(error) }\nFalling back to default writer`, diff --git a/packages/typegpu/src/data/dataIO.ts b/packages/typegpu/src/data/dataIO.ts index f41ee091ad..101274440f 100644 --- a/packages/typegpu/src/data/dataIO.ts +++ b/packages/typegpu/src/data/dataIO.ts @@ -31,6 +31,7 @@ import type { BufferWriteOptions } from '../core/buffer/buffer.ts'; import { getCompiledWriter } from './compiledIO.ts'; import { getName } from '../shared/meta.ts'; import { roundUp } from '../mathUtils.ts'; +import { tgpuLogger } from '../tgpuLogger.ts'; type DataWriter = ( output: ISerialOutput, @@ -455,7 +456,8 @@ export function writeData( const src = value as ArrayBufferView; const expected = sizeOf(schema); if (src.byteLength !== expected) { - console.warn( + tgpuLogger.warn( + 'suspicious-call', `TypedArray size mismatch: schema expects ${expected} bytes, got ${src.byteLength}. ` + (src.byteLength < expected ? 'Data truncated.' : 'Excess ignored.'), ); @@ -841,7 +843,8 @@ export function writeToArrayBuffer( : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); const regionSize = endOffset - startOffset; if (src.byteLength !== regionSize) { - console.warn( + tgpuLogger.warn( + 'suspicious-call', `Buffer size mismatch: expected ${regionSize} bytes, got ${src.byteLength}. ` + (src.byteLength < regionSize ? 'Data truncated.' : 'Excess ignored.'), ); diff --git a/packages/typegpu/src/indexNamedExports.ts b/packages/typegpu/src/indexNamedExports.ts index 12dc70eee1..d1005be2ff 100644 --- a/packages/typegpu/src/indexNamedExports.ts +++ b/packages/typegpu/src/indexNamedExports.ts @@ -42,6 +42,7 @@ export { } from './tgsl/wgslGenerator.ts'; export { readFromArrayBuffer, writeToArrayBuffer } from './data/dataIO.ts'; export { patchArrayBuffer } from './data/partialIO.ts'; +export { tgpuLogger } from './tgpuLogger.ts'; // types diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts new file mode 100644 index 0000000000..100de1d058 --- /dev/null +++ b/packages/typegpu/src/tgpuLogger.ts @@ -0,0 +1,36 @@ +const warningTypes = [ + 'implicit-conversion', + 'missing-webgpu-feature', + 'non-contiguous-memory', + 'webgpu-limits-exceeded', + 'mismatched-locations', + 'omitted-external', + 'deprecated-method-called', + 'suspicious-call', + 'eval-not-supported-in-env', + 'not-supported', + 'precision-loss', + 'log-limit-exceeded', + 'failed-to-compile-writer', +] as const; +type WarningType = (typeof warningTypes)[number]; + +class TgpuLogger { + #enabledWarnings: Set = new Set(warningTypes); + + disableWarn(type: WarningType) { + this.#enabledWarnings.delete(type); + } + + enableWarn(type: WarningType) { + this.#enabledWarnings.add(type); + } + + warn(type: (typeof warningTypes)[number], ...args: unknown[]) { + if (this.#enabledWarnings.has(type)) { + console.warn(...args); + } + } +} + +export const tgpuLogger = new TgpuLogger(); diff --git a/packages/typegpu/src/tgsl/consoleLog/deserializers.ts b/packages/typegpu/src/tgsl/consoleLog/deserializers.ts index bc89a0eec5..bd763f262c 100644 --- a/packages/typegpu/src/tgsl/consoleLog/deserializers.ts +++ b/packages/typegpu/src/tgsl/consoleLog/deserializers.ts @@ -29,6 +29,7 @@ import type { Infer } from '../../shared/repr.ts'; import { niceStringify } from '../../shared/stringify.ts'; import { bitcast } from '../../std/bitcast.ts'; import { unpack2x16float } from '../../std/packing.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; import type { LogMeta, LogResources } from './types.ts'; const toF = (n: number | undefined) => bitcast(u32, f32)(n ?? 0); @@ -186,7 +187,8 @@ export function logDataFromGPU(resources: LogResources) { void indexBuffer.read().then((totalCalls) => { if (totalCalls > options.logCountLimit) { - console.warn( + tgpuLogger.warn( + 'log-limit-exceeded', `Log count limit per dispatch (${options.logCountLimit}) exceeded by ${ totalCalls - options.logCountLimit } calls. Consider increasing the limit by passing appropriate options to tgpu.init().`, diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index ebcf984717..830fe00b6f 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -17,6 +17,7 @@ import { } from '../../data/wgslTypes.ts'; import { invariant } from '../../errors.ts'; import { $internal } from '../../shared/symbols.ts'; +import { tgpuLogger } from '../../tgpuLogger.ts'; import { convertToCommonType } from '../conversion.ts'; import { concretizeSnippet, type GenerationCtx } from '../generationHelpers.ts'; import { createLoggingFunction } from './serializers.ts'; @@ -42,7 +43,7 @@ export class LogGeneratorNullImpl implements LogGenerator { return undefined; } generateLog(): Snippet { - console.warn("'console.log' is only supported when resolving pipelines."); + tgpuLogger.warn('not-supported', "'console.log' is only supported when resolving pipelines."); return fallbackSnippet; } } @@ -79,7 +80,10 @@ export class LogGeneratorImpl implements LogGenerator { */ generateLog(ctx: GenerationCtx, op: SupportedLogOp, args: Snippet[]): Snippet { if (shaderStageSlot.$ === 'vertex') { - console.warn(`'console' operations are not supported in vertex shaders.`); + tgpuLogger.warn( + 'suspicious-call', + `'console' operations are not supported in vertex shaders.`, + ); return fallbackSnippet; } diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index f64492ccb9..3f2f22d50d 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -23,6 +23,7 @@ import { DEV, TEST } from '../shared/env.ts'; import { getName } from '../shared/meta.ts'; import { safeStringify } from '../shared/stringify.ts'; import { assertExhaustive } from '../shared/utilityTypes.ts'; +import { tgpuLogger } from '../tgpuLogger.ts'; import type { ResolutionCtx } from '../types.ts'; import { accessStructProp } from './accessStructProp.ts'; @@ -363,6 +364,7 @@ export function convertToCommonType( return undefined; } + // TODO: investigate if (DEV && Array.isArray(restrictTo) && restrictTo.length === 0) { console.warn( 'convertToCommonType was called with an empty restrictTo array, which prevents any conversions from being made. If you intend to allow all conversions, pass undefined instead. If this was intended call the function conditionally since the result will always be undefined.', @@ -374,8 +376,9 @@ export function convertToCommonType( return undefined; } - if ((TEST || DEV) && verbose && conversion.hasImplicitConversions) { - console.warn( + if (conversion.hasImplicitConversions) { + tgpuLogger.warn( + 'implicit-conversion', `Implicit conversions from [\n${values .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`) .join(',\n')}\n] to ${conversion.targetType.type} are supported, but not recommended. diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 3eb4d37b56..a53e90aaa9 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -24,6 +24,7 @@ import type { ShelllessRepository } from './shellless.ts'; import { WgslTypeError } from '../errors.ts'; import { $internal, $resolve } from '../shared/symbols.ts'; import type { SupportedLogOp } from './consoleLog/types.ts'; +import { tgpuLogger } from '../tgpuLogger.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -33,7 +34,8 @@ export function numericLiteralToSnippet(value: number): Snippet { // Warn when values exceed this range to prevent precision loss. if (Number.isInteger(value)) { if (!Number.isSafeInteger(value)) { - console.warn( + tgpuLogger.warn( + 'precision-loss', `The integer ${value} exceeds the safe integer range and may have lost precision.`, ); } diff --git a/packages/typegpu/tests/tgsl/assignment.test.ts b/packages/typegpu/tests/tgsl/assignment.test.ts index 48b8206528..2aaa0b25e9 100644 --- a/packages/typegpu/tests/tgsl/assignment.test.ts +++ b/packages/typegpu/tests/tgsl/assignment.test.ts @@ -1,8 +1,8 @@ import { beforeEach, expect, type MockInstance, vi } from 'vitest'; import { it } from 'typegpu-testing-utility'; -import { tgpu, d } from 'typegpu'; +import { tgpu, d, tgpuLogger } from 'typegpu'; -let warnSpy: MockInstance; +let warnSpy: MockInstance; beforeEach(() => { warnSpy = vi.spyOn(console, 'warn'); From c0a42d07914b5161717876fa2d331198431a1582 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:54:40 +0200 Subject: [PATCH 03/21] Rename warning types --- .../typegpu/src/core/pipeline/pipelineUtils.ts | 2 +- packages/typegpu/src/core/resolve/tgpuResolve.ts | 2 +- packages/typegpu/src/core/root/init.ts | 2 +- packages/typegpu/src/core/texture/texture.ts | 4 ++-- packages/typegpu/src/data/compiledIO.ts | 4 ++-- packages/typegpu/src/data/dataIO.ts | 4 ++-- packages/typegpu/src/tgpuLogger.ts | 15 +++++++-------- .../typegpu/src/tgsl/consoleLog/logGenerator.ts | 4 ++-- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/typegpu/src/core/pipeline/pipelineUtils.ts b/packages/typegpu/src/core/pipeline/pipelineUtils.ts index ba80eee30d..2f7a2b3aa3 100644 --- a/packages/typegpu/src/core/pipeline/pipelineUtils.ts +++ b/packages/typegpu/src/core/pipeline/pipelineUtils.ts @@ -53,7 +53,7 @@ export function resolveIndirectOffset( if (contiguous < requiredSize) { tgpuLogger.warn( - 'non-contiguous-memory', + 'suspicious', `${operation}: Starting at offset ${offset}, only ${contiguous} contiguous bytes are available before padding. '${operation}' requires ${requiredSize} bytes (${IndirectOperationToRequiredData[operation]}). Reading across padding may result in undefined behavior.`, ); } diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index 75708e3d44..4211b1037a 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -192,7 +192,7 @@ function resolveFromTemplate(options: TgpuExtendedResolveOptions): ResolutionRes if (!template) { tgpuLogger.warn( - 'deprecated-method-called', + 'deprecated', "Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.", ); } diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 3e9029f212..91d42c5807 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -660,7 +660,7 @@ class TgpuRootImpl extends WithBindingImpl implements TgpuRoot, ExperimentalTgpu } flush() { - tgpuLogger.warn('deprecated-method-called', 'flush() has been deprecated, and has no effect.'); + tgpuLogger.warn('deprecated', 'flush() has been deprecated, and has no effect.'); } } diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 94d5b6a429..99963078bd 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -396,7 +396,7 @@ class TgpuTextureImpl implements TgpuTexture implements TgpuTexture layerCount) { tgpuLogger.warn( - 'suspicious-call', + 'suspicious', `Too many image sources provided. Expected ${layerCount} layers, got ${source.length}. Extra sources will be ignored.`, ); } diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts index 81e6aafc61..795d922227 100644 --- a/packages/typegpu/src/data/compiledIO.ts +++ b/packages/typegpu/src/data/compiledIO.ts @@ -291,7 +291,7 @@ export function buildWriter( export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undefined { if (!EVAL_ALLOWED_IN_ENV) { tgpuLogger.warn( - 'eval-not-supported-in-env', + 'fallback', 'This environment does not allow eval - using default writer as fallback', ); return undefined; @@ -321,7 +321,7 @@ export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undef return fn; } catch (error) { tgpuLogger.warn( - 'failed-to-compile-writer', + 'fallback', `Failed to compile writer for schema: ${schema}\nReason: ${ error instanceof Error ? error.message : String(error) }\nFalling back to default writer`, diff --git a/packages/typegpu/src/data/dataIO.ts b/packages/typegpu/src/data/dataIO.ts index 101274440f..b2d2db9555 100644 --- a/packages/typegpu/src/data/dataIO.ts +++ b/packages/typegpu/src/data/dataIO.ts @@ -457,7 +457,7 @@ export function writeData( const expected = sizeOf(schema); if (src.byteLength !== expected) { tgpuLogger.warn( - 'suspicious-call', + 'suspicious', `TypedArray size mismatch: schema expects ${expected} bytes, got ${src.byteLength}. ` + (src.byteLength < expected ? 'Data truncated.' : 'Excess ignored.'), ); @@ -844,7 +844,7 @@ export function writeToArrayBuffer( const regionSize = endOffset - startOffset; if (src.byteLength !== regionSize) { tgpuLogger.warn( - 'suspicious-call', + 'suspicious', `Buffer size mismatch: expected ${regionSize} bytes, got ${src.byteLength}. ` + (src.byteLength < regionSize ? 'Data truncated.' : 'Excess ignored.'), ); diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index 100de1d058..ee701dba3d 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -1,17 +1,16 @@ const warningTypes = [ - 'implicit-conversion', + 'deprecated', + 'suspicious', + 'fallback', + 'missing-webgpu-feature', - 'non-contiguous-memory', 'webgpu-limits-exceeded', + + 'implicit-conversion', 'mismatched-locations', + 'log-limit-exceeded', 'omitted-external', - 'deprecated-method-called', - 'suspicious-call', - 'eval-not-supported-in-env', - 'not-supported', 'precision-loss', - 'log-limit-exceeded', - 'failed-to-compile-writer', ] as const; type WarningType = (typeof warningTypes)[number]; diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index 830fe00b6f..9ef77747ff 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -43,7 +43,7 @@ export class LogGeneratorNullImpl implements LogGenerator { return undefined; } generateLog(): Snippet { - tgpuLogger.warn('not-supported', "'console.log' is only supported when resolving pipelines."); + tgpuLogger.warn('fallback', "'console.log' is only supported when resolving pipelines."); return fallbackSnippet; } } @@ -81,7 +81,7 @@ export class LogGeneratorImpl implements LogGenerator { generateLog(ctx: GenerationCtx, op: SupportedLogOp, args: Snippet[]): Snippet { if (shaderStageSlot.$ === 'vertex') { tgpuLogger.warn( - 'suspicious-call', + 'suspicious', `'console' operations are not supported in vertex shaders.`, ); return fallbackSnippet; From 1e567ecd28e81d03249fcdf3cba10136bfc3fbd4 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:37:20 +0200 Subject: [PATCH 04/21] Split apis into two --- .../src/examples/simple/triangle/index.ts | 14 +++++++++++ .../src/core/pipeline/computePipeline.ts | 4 ++-- .../src/core/pipeline/limitsOverflow.ts | 6 ++--- .../src/core/pipeline/pipelineUtils.ts | 4 ++-- .../src/core/pipeline/renderPipeline.ts | 6 ++--- .../typegpu/src/core/resolve/externals.ts | 4 ++-- .../typegpu/src/core/resolve/tgpuResolve.ts | 4 ++-- packages/typegpu/src/core/root/init.ts | 6 ++--- packages/typegpu/src/core/texture/texture.ts | 6 ++--- packages/typegpu/src/data/compiledIO.ts | 6 ++--- packages/typegpu/src/data/dataIO.ts | 6 ++--- packages/typegpu/src/indexNamedExports.ts | 2 +- packages/typegpu/src/tgpuLogger.ts | 24 +++++++++++++++---- .../src/tgsl/consoleLog/deserializers.ts | 4 ++-- .../src/tgsl/consoleLog/logGenerator.ts | 9 +++---- packages/typegpu/src/tgsl/conversion.ts | 4 ++-- .../typegpu/src/tgsl/generationHelpers.ts | 4 ++-- 17 files changed, 69 insertions(+), 44 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simple/triangle/index.ts b/apps/typegpu-docs/src/examples/simple/triangle/index.ts index f785d16d01..81f34c5646 100644 --- a/apps/typegpu-docs/src/examples/simple/triangle/index.ts +++ b/apps/typegpu-docs/src/examples/simple/triangle/index.ts @@ -55,3 +55,17 @@ export function onCleanup() { } // #endregion + +// import { tgpu, d, tgpuLogger } from 'typegpu'; + +// const fn = tgpu.fn( +// [d.u32, d.f32], +// d.u32, +// )((a, b) => { +// 'use gpu'; +// return a / b; +// }); + +// tgpuLogger.disableWarn('implicit-conversion'); + +// console.log(tgpu.resolve([fn])); diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index 614f1b3f12..8803d277e6 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -42,7 +42,7 @@ import { PerformanceTrackerImpl, type PerformanceTracker, } from './performanceTracker.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; interface ComputePipelineInternals { readonly rawPipeline: GPUComputePipeline; @@ -228,7 +228,7 @@ class TgpuComputePipelineImpl implements TgpuComputePipeline { const querySet = this.#core.performanceCallbackQuerySet; if (!querySet) { - tgpuLogger.warn( + logger.warn( 'missing-webgpu-feature', 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', ); diff --git a/packages/typegpu/src/core/pipeline/limitsOverflow.ts b/packages/typegpu/src/core/pipeline/limitsOverflow.ts index 96f1ebb926..0675a232dd 100644 --- a/packages/typegpu/src/core/pipeline/limitsOverflow.ts +++ b/packages/typegpu/src/core/pipeline/limitsOverflow.ts @@ -1,5 +1,5 @@ import type { TgpuBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSupportedLimits) { const entries = Object.values(layouts) @@ -10,7 +10,7 @@ export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSuppor const storage = entries.filter((entry) => 'storage' in entry).length; if (uniform > limits.maxUniformBuffersPerShaderStage) { - tgpuLogger.warn( + logger.warn( 'webgpu-limits-exceeded', `Total number of uniform buffers (${uniform}) exceeds maxUniformBuffersPerShaderStage (${limits.maxUniformBuffersPerShaderStage}). Consider: 1. Grouping some of the uniforms into one using 'd.struct', @@ -19,7 +19,7 @@ export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSuppor } if (storage > limits.maxStorageBuffersPerShaderStage) { - tgpuLogger.warn( + logger.warn( 'webgpu-limits-exceeded', `Total number of storage buffers (${storage}) exceeds maxStorageBuffersPerShaderStage (${limits.maxStorageBuffersPerShaderStage}).`, ); diff --git a/packages/typegpu/src/core/pipeline/pipelineUtils.ts b/packages/typegpu/src/core/pipeline/pipelineUtils.ts index 2f7a2b3aa3..0870f31f46 100644 --- a/packages/typegpu/src/core/pipeline/pipelineUtils.ts +++ b/packages/typegpu/src/core/pipeline/pipelineUtils.ts @@ -3,7 +3,7 @@ import { memoryLayoutOf, type PrimitiveOffsetInfo } from '../../data/offsetUtils import { sizeOf } from '../../data/sizeOf.ts'; import type { BaseData } from '../../data/wgslTypes.ts'; import { isGPUBuffer } from '../../types.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; type IndirectOperation = 'dispatchWorkgroupsIndirect' | 'drawIndirect' | 'drawIndexedIndirect'; const IndirectOperationToRequiredData = { @@ -52,7 +52,7 @@ export function resolveIndirectOffset( validateIndirectBufferSize(sizeOf(indirectBuffer.dataType), offset, requiredSize, operation); if (contiguous < requiredSize) { - tgpuLogger.warn( + logger.warn( 'suspicious', `${operation}: Starting at offset ${offset}, only ${contiguous} contiguous bytes are available before padding. '${operation}' requires ${requiredSize} bytes (${IndirectOperationToRequiredData[operation]}). Reading across padding may result in undefined behavior.`, ); diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index ed68e15882..4602a9c4e4 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -88,7 +88,7 @@ import { PerformanceTrackerImpl, type PerformanceTracker, } from './performanceTracker.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; const DRAW_INDIRECT_SIZE = 16; // 4 x 4 const DRAW_INDEXED_INDIRECT_SIZE = 20; // 5 x 4 @@ -602,7 +602,7 @@ class TgpuRenderPipelineImpl implements TgpuRenderPipeline { const querySet = internals.core.performanceCallbackQuerySet; if (!querySet) { - tgpuLogger.warn( + logger.warn( 'missing-webgpu-feature', 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', ); @@ -1290,7 +1290,7 @@ export function matchUpVaryingLocations( if (locations[key] === undefined) { saveLocation(key, customLocation); } else if (locations[key] !== customLocation) { - tgpuLogger.warn( + logger.warn( 'mismatched-locations', `Mismatched location between vertexFn (${vertexFnName}) output (${ locations[key] diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index 808db00ea8..bf929c2a69 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -1,7 +1,7 @@ import { isLooseData } from '../../data/dataTypes.ts'; import { isWgslStruct } from '../../data/wgslTypes.ts'; import { getName, hasTinyestMetadata, isNamable, setName } from '../../shared/meta.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; import { isWgsl, type ResolutionCtx } from '../../types.ts'; import type { FnExternals } from '../function/fnCore.ts'; @@ -125,7 +125,7 @@ export function replaceExternalsInWgsl( } if (typeof currentItem !== 'object' || currentItem === null || i === chain.length - 1) { - tgpuLogger.warn( + logger.warn( 'omitted-external', `During resolution, the external '${chain.slice(0, i + 1).join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, ); diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index 4211b1037a..a0b07a3475 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -3,7 +3,7 @@ import { Void } from '../../data/wgslTypes.ts'; import { type ResolutionResult, resolve as resolveImpl } from '../../resolutionCtx.ts'; import { $internal, $resolve } from '../../shared/symbols.ts'; import { isBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; import type { ShaderGenerator } from '../../tgsl/shaderGenerator.ts'; import type { ResolvableObject, SelfResolvable, Wgsl } from '../../types.ts'; import type { WgslEnableExtension } from '../../wgslExtensions.ts'; @@ -191,7 +191,7 @@ function resolveFromTemplate(options: TgpuExtendedResolveOptions): ResolutionRes } = options; if (!template) { - tgpuLogger.warn( + logger.warn( 'deprecated', "Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.", ); diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 91d42c5807..0a0804028b 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -81,7 +81,7 @@ import { u32 } from '../../data/numeric.ts'; import { ceil } from '../../std/numeric.ts'; import { allEq } from '../../std/boolean.ts'; import { getName, setName } from '../../shared/meta.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; /** * Changes the given array to a vec of 3 numbers, filling missing values with 1. @@ -660,7 +660,7 @@ class TgpuRootImpl extends WithBindingImpl implements TgpuRoot, ExperimentalTgpu } flush() { - tgpuLogger.warn('deprecated', 'flush() has been deprecated, and has no effect.'); + logger.warn('deprecated', 'flush() has been deprecated, and has no effect.'); } } @@ -742,7 +742,7 @@ export async function init(options?: InitOptions): Promise { if (adapter.features.has(feature)) { availableFeatures.push(feature); } else { - tgpuLogger.warn( + logger.warn( 'missing-webgpu-feature', `Optional feature "${feature}" is not supported by the adapter.`, ); diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 99963078bd..f496b7d34a 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -33,7 +33,7 @@ import type { SampledFlag, } from './usageExtension.ts'; import { generateTextureMipmaps, getImageSourceDimensions, resampleImage } from './textureUtils.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; export type TextureInternals = { unwrap(): GPUTexture; @@ -395,7 +395,7 @@ class TgpuTextureImpl implements TgpuTexture implements TgpuTexture layerCount) { - tgpuLogger.warn( + logger.warn( 'suspicious', `Too many image sources provided. Expected ${layerCount} layers, got ${source.length}. Extra sources will be ignored.`, ); diff --git a/packages/typegpu/src/data/compiledIO.ts b/packages/typegpu/src/data/compiledIO.ts index 795d922227..4c00c4d2ad 100644 --- a/packages/typegpu/src/data/compiledIO.ts +++ b/packages/typegpu/src/data/compiledIO.ts @@ -1,5 +1,5 @@ import { roundUp } from '../mathUtils.ts'; -import { tgpuLogger } from '../tgpuLogger.ts'; +import { logger } from '../tgpuLogger.ts'; import { alignmentOf } from './alignmentOf.ts'; import { isDisarray, isUnstruct } from './dataTypes.ts'; import { offsetsForProps } from './offsets.ts'; @@ -290,7 +290,7 @@ export function buildWriter( export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undefined { if (!EVAL_ALLOWED_IN_ENV) { - tgpuLogger.warn( + logger.warn( 'fallback', 'This environment does not allow eval - using default writer as fallback', ); @@ -320,7 +320,7 @@ export function getCompiledWriter(schema: wgsl.BaseData): CompiledWriter | undef compiledWriters.set(schema, fn); return fn; } catch (error) { - tgpuLogger.warn( + logger.warn( 'fallback', `Failed to compile writer for schema: ${schema}\nReason: ${ error instanceof Error ? error.message : String(error) diff --git a/packages/typegpu/src/data/dataIO.ts b/packages/typegpu/src/data/dataIO.ts index b2d2db9555..a798b20a6b 100644 --- a/packages/typegpu/src/data/dataIO.ts +++ b/packages/typegpu/src/data/dataIO.ts @@ -31,7 +31,7 @@ import type { BufferWriteOptions } from '../core/buffer/buffer.ts'; import { getCompiledWriter } from './compiledIO.ts'; import { getName } from '../shared/meta.ts'; import { roundUp } from '../mathUtils.ts'; -import { tgpuLogger } from '../tgpuLogger.ts'; +import { logger } from '../tgpuLogger.ts'; type DataWriter = ( output: ISerialOutput, @@ -456,7 +456,7 @@ export function writeData( const src = value as ArrayBufferView; const expected = sizeOf(schema); if (src.byteLength !== expected) { - tgpuLogger.warn( + logger.warn( 'suspicious', `TypedArray size mismatch: schema expects ${expected} bytes, got ${src.byteLength}. ` + (src.byteLength < expected ? 'Data truncated.' : 'Excess ignored.'), @@ -843,7 +843,7 @@ export function writeToArrayBuffer( : new Uint8Array(data.buffer, data.byteOffset, data.byteLength); const regionSize = endOffset - startOffset; if (src.byteLength !== regionSize) { - tgpuLogger.warn( + logger.warn( 'suspicious', `Buffer size mismatch: expected ${regionSize} bytes, got ${src.byteLength}. ` + (src.byteLength < regionSize ? 'Data truncated.' : 'Excess ignored.'), diff --git a/packages/typegpu/src/indexNamedExports.ts b/packages/typegpu/src/indexNamedExports.ts index d1005be2ff..809af0e2bd 100644 --- a/packages/typegpu/src/indexNamedExports.ts +++ b/packages/typegpu/src/indexNamedExports.ts @@ -42,7 +42,7 @@ export { } from './tgsl/wgslGenerator.ts'; export { readFromArrayBuffer, writeToArrayBuffer } from './data/dataIO.ts'; export { patchArrayBuffer } from './data/partialIO.ts'; -export { tgpuLogger } from './tgpuLogger.ts'; +export { warn } from './tgpuLogger.ts'; // types diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index ee701dba3d..ab70f13e3d 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -14,22 +14,36 @@ const warningTypes = [ ] as const; type WarningType = (typeof warningTypes)[number]; -class TgpuLogger { +// internal-facing API +interface Logger { + warn(type: (typeof warningTypes)[number], ...args: unknown[]): void; +} + +// user-facing API +interface Warn { + disable(type: WarningType): void; + enable(type: WarningType): void; +} + +class TgpuLogger implements Logger, Warn { #enabledWarnings: Set = new Set(warningTypes); - disableWarn(type: WarningType) { + disable(type: WarningType) { this.#enabledWarnings.delete(type); } - enableWarn(type: WarningType) { + enable(type: WarningType) { this.#enabledWarnings.add(type); } - warn(type: (typeof warningTypes)[number], ...args: unknown[]) { + warn(type: WarningType, ...args: unknown[]) { if (this.#enabledWarnings.has(type)) { console.warn(...args); + // console.warn(`⚠️ [${type}}] `, ...args); } } } -export const tgpuLogger = new TgpuLogger(); +const tgpuLogger = new TgpuLogger(); +export const logger: Logger = tgpuLogger; +export const warn: Warn = tgpuLogger; diff --git a/packages/typegpu/src/tgsl/consoleLog/deserializers.ts b/packages/typegpu/src/tgsl/consoleLog/deserializers.ts index bd763f262c..42d06a18d7 100644 --- a/packages/typegpu/src/tgsl/consoleLog/deserializers.ts +++ b/packages/typegpu/src/tgsl/consoleLog/deserializers.ts @@ -29,7 +29,7 @@ import type { Infer } from '../../shared/repr.ts'; import { niceStringify } from '../../shared/stringify.ts'; import { bitcast } from '../../std/bitcast.ts'; import { unpack2x16float } from '../../std/packing.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; import type { LogMeta, LogResources } from './types.ts'; const toF = (n: number | undefined) => bitcast(u32, f32)(n ?? 0); @@ -187,7 +187,7 @@ export function logDataFromGPU(resources: LogResources) { void indexBuffer.read().then((totalCalls) => { if (totalCalls > options.logCountLimit) { - tgpuLogger.warn( + logger.warn( 'log-limit-exceeded', `Log count limit per dispatch (${options.logCountLimit}) exceeded by ${ totalCalls - options.logCountLimit diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index 9ef77747ff..41182d4330 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -17,7 +17,7 @@ import { } from '../../data/wgslTypes.ts'; import { invariant } from '../../errors.ts'; import { $internal } from '../../shared/symbols.ts'; -import { tgpuLogger } from '../../tgpuLogger.ts'; +import { logger } from '../../tgpuLogger.ts'; import { convertToCommonType } from '../conversion.ts'; import { concretizeSnippet, type GenerationCtx } from '../generationHelpers.ts'; import { createLoggingFunction } from './serializers.ts'; @@ -43,7 +43,7 @@ export class LogGeneratorNullImpl implements LogGenerator { return undefined; } generateLog(): Snippet { - tgpuLogger.warn('fallback', "'console.log' is only supported when resolving pipelines."); + logger.warn('fallback', "'console.log' is only supported when resolving pipelines."); return fallbackSnippet; } } @@ -80,10 +80,7 @@ export class LogGeneratorImpl implements LogGenerator { */ generateLog(ctx: GenerationCtx, op: SupportedLogOp, args: Snippet[]): Snippet { if (shaderStageSlot.$ === 'vertex') { - tgpuLogger.warn( - 'suspicious', - `'console' operations are not supported in vertex shaders.`, - ); + logger.warn('suspicious', `'console' operations are not supported in vertex shaders.`); return fallbackSnippet; } diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 3f2f22d50d..e72ab856ad 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -23,7 +23,7 @@ import { DEV, TEST } from '../shared/env.ts'; import { getName } from '../shared/meta.ts'; import { safeStringify } from '../shared/stringify.ts'; import { assertExhaustive } from '../shared/utilityTypes.ts'; -import { tgpuLogger } from '../tgpuLogger.ts'; +import { logger } from '../tgpuLogger.ts'; import type { ResolutionCtx } from '../types.ts'; import { accessStructProp } from './accessStructProp.ts'; @@ -377,7 +377,7 @@ export function convertToCommonType( } if (conversion.hasImplicitConversions) { - tgpuLogger.warn( + logger.warn( 'implicit-conversion', `Implicit conversions from [\n${values .map((v) => ` ${ctx.resolveSnippet(v).value}: ${safeStringify(v.dataType)}`) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index a53e90aaa9..f35794db33 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -24,7 +24,7 @@ import type { ShelllessRepository } from './shellless.ts'; import { WgslTypeError } from '../errors.ts'; import { $internal, $resolve } from '../shared/symbols.ts'; import type { SupportedLogOp } from './consoleLog/types.ts'; -import { tgpuLogger } from '../tgpuLogger.ts'; +import { logger } from '../tgpuLogger.ts'; export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { @@ -34,7 +34,7 @@ export function numericLiteralToSnippet(value: number): Snippet { // Warn when values exceed this range to prevent precision loss. if (Number.isInteger(value)) { if (!Number.isSafeInteger(value)) { - tgpuLogger.warn( + logger.warn( 'precision-loss', `The integer ${value} exceeds the safe integer range and may have lost precision.`, ); From b82d259306ae7c1abac9503095ad66914d82ae12 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:54:16 +0200 Subject: [PATCH 05/21] Rename warning types again --- .../typegpu/src/core/pipeline/computePipeline.ts | 2 +- packages/typegpu/src/core/pipeline/renderPipeline.ts | 4 ++-- packages/typegpu/src/core/resolve/externals.ts | 2 +- packages/typegpu/src/core/root/init.ts | 2 +- packages/typegpu/src/tgpuLogger.ts | 12 ++++++------ 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index 8803d277e6..fef3a07b8e 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -229,7 +229,7 @@ class TgpuComputePipelineImpl implements TgpuComputePipeline { const querySet = this.#core.performanceCallbackQuerySet; if (!querySet) { logger.warn( - 'missing-webgpu-feature', + 'webgpu-feature-missing', 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', ); return this; diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index 4602a9c4e4..194ff7b6a9 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -603,7 +603,7 @@ class TgpuRenderPipelineImpl implements TgpuRenderPipeline { const querySet = internals.core.performanceCallbackQuerySet; if (!querySet) { logger.warn( - 'missing-webgpu-feature', + 'webgpu-feature-missing', 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', ); return this; @@ -1291,7 +1291,7 @@ export function matchUpVaryingLocations( saveLocation(key, customLocation); } else if (locations[key] !== customLocation) { logger.warn( - 'mismatched-locations', + 'locations-mismatched', `Mismatched location between vertexFn (${vertexFnName}) output (${ locations[key] }) and fragmentFn (${fragmentFnName}) input (${customLocation}) for the key "${key}", using the location set on vertex output.`, diff --git a/packages/typegpu/src/core/resolve/externals.ts b/packages/typegpu/src/core/resolve/externals.ts index bf929c2a69..4d25e11068 100644 --- a/packages/typegpu/src/core/resolve/externals.ts +++ b/packages/typegpu/src/core/resolve/externals.ts @@ -126,7 +126,7 @@ export function replaceExternalsInWgsl( if (typeof currentItem !== 'object' || currentItem === null || i === chain.length - 1) { logger.warn( - 'omitted-external', + 'external-omitted', `During resolution, the external '${chain.slice(0, i + 1).join('.')}' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.`, ); return match; diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 0a0804028b..d5439452cb 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -743,7 +743,7 @@ export async function init(options?: InitOptions): Promise { availableFeatures.push(feature); } else { logger.warn( - 'missing-webgpu-feature', + 'webgpu-feature-missing', `Optional feature "${feature}" is not supported by the adapter.`, ); } diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index ab70f13e3d..ca79259735 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -3,14 +3,14 @@ const warningTypes = [ 'suspicious', 'fallback', - 'missing-webgpu-feature', - 'webgpu-limits-exceeded', - + 'precision-loss', 'implicit-conversion', - 'mismatched-locations', + + 'webgpu-feature-missing', + 'webgpu-limits-exceeded', + 'locations-mismatched', 'log-limit-exceeded', - 'omitted-external', - 'precision-loss', + 'external-omitted', ] as const; type WarningType = (typeof warningTypes)[number]; From 5cae02c0a4bbd9fbcad05180c42c3e8e60235d18 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:29:35 +0200 Subject: [PATCH 06/21] Add custom warn message, change log tests to snapshots --- packages/typegpu/src/tgpuLogger.ts | 3 +- .../typegpu/tests/computePipeline.test.ts | 58 ++++++++++++------- .../tests/internal/limitsOverflow.test.ts | 35 ++++++----- packages/typegpu/tests/renderPipeline.test.ts | 58 ++++++++++++------- packages/typegpu/tests/resolve.test.ts | 27 ++++++--- packages/typegpu/tests/texture.test.ts | 18 ++++-- .../typegpu/tests/tgsl/assignment.test.ts | 18 ++++-- .../typegpu/tests/tgsl/consoleLog.test.ts | 18 ++++-- .../typegpu/tests/tgsl/conversion.test.ts | 3 + .../typegpu/tests/tgsl/multiplication.test.ts | 7 +++ .../typegpu/tests/tgsl/typeInference.test.ts | 14 ++++- 11 files changed, 174 insertions(+), 85 deletions(-) diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index ca79259735..c55a995482 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -38,8 +38,7 @@ class TgpuLogger implements Logger, Warn { warn(type: WarningType, ...args: unknown[]) { if (this.#enabledWarnings.has(type)) { - console.warn(...args); - // console.warn(`⚠️ [${type}}] `, ...args); + console.warn(`⚠️ [${type}}] `, ...args); } } } diff --git a/packages/typegpu/tests/computePipeline.test.ts b/packages/typegpu/tests/computePipeline.test.ts index e39927a5a7..b8b4caf18d 100644 --- a/packages/typegpu/tests/computePipeline.test.ts +++ b/packages/typegpu/tests/computePipeline.test.ts @@ -98,9 +98,12 @@ describe('TgpuComputePipeline', () => { // no-op expect(after).toBe(before); }).not.toThrow(); - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-feature-missing}] ", + "Performance callback cannot be used because the timestamp-query feature is not enabled on the root.", + ] + `); }); it('should setup timestamp writes in compute pass descriptor', ({ root, commandEncoder }) => { @@ -329,14 +332,20 @@ describe('TgpuComputePipeline', () => { pipeline.dispatchThreads(); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of uniform buffers (14) exceeds maxUniformBuffersPerShaderStage (12). Consider: -1. Grouping some of the uniforms into one using 'd.struct', -2. Increasing the limit when requesting a device or creating a root.`, - ); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of storage buffers (9) exceeds maxStorageBuffersPerShaderStage (8).`, - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of uniform buffers (14) exceeds maxUniformBuffersPerShaderStage (12). Consider: + 1. Grouping some of the uniforms into one using 'd.struct', + 2. Increasing the limit when requesting a device or creating a root.", + ] + `); + expect(consoleWarnSpy.mock.calls[1]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of storage buffers (9) exceeds maxStorageBuffersPerShaderStage (8).", + ] + `); }); describe('dispatchWorkgroupsIndirect', () => { @@ -413,9 +422,12 @@ describe('TgpuComputePipeline', () => { d.memoryLayoutOf(PaddedStruct, (s) => s.a), ); - expect(warnSpy.mock.calls[0]![0]).toMatchInlineSnapshot( - `"dispatchWorkgroupsIndirect: Starting at offset 0, only 4 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior."`, - ); + expect(warnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "dispatchWorkgroupsIndirect: Starting at offset 0, only 4 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior.", + ] + `); const deepBuffer = root.createBuffer(DeepStruct).$usage('indirect'); pipeline.dispatchWorkgroupsIndirect( @@ -423,18 +435,24 @@ describe('TgpuComputePipeline', () => { d.memoryLayoutOf(DeepStruct, (s) => s.someData[11]), ); - expect(warnSpy.mock.calls[1]![0]).toMatchInlineSnapshot( - `"dispatchWorkgroupsIndirect: Starting at offset 44, only 8 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior."`, - ); + expect(warnSpy.mock.calls[1]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "dispatchWorkgroupsIndirect: Starting at offset 44, only 8 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior.", + ] + `); pipeline.dispatchWorkgroupsIndirect( deepBuffer, d.memoryLayoutOf(DeepStruct, (s) => s.nested.innerNested[0]?.yy), ); - expect(warnSpy.mock.calls[2]![0]).toMatchInlineSnapshot( - `"dispatchWorkgroupsIndirect: Starting at offset 84, only 8 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior."`, - ); + expect(warnSpy.mock.calls[2]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "dispatchWorkgroupsIndirect: Starting at offset 84, only 8 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior.", + ] + `); }); it('does not warn when dispatch has sufficient contiguous data', ({ root }) => { diff --git a/packages/typegpu/tests/internal/limitsOverflow.test.ts b/packages/typegpu/tests/internal/limitsOverflow.test.ts index 83da7a4b30..0d41cfa3fd 100644 --- a/packages/typegpu/tests/internal/limitsOverflow.test.ts +++ b/packages/typegpu/tests/internal/limitsOverflow.test.ts @@ -35,11 +35,14 @@ describe('warnIfOverflow', () => { warnIfOverflow([layout], limits); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of uniform buffers (3) exceeds maxUniformBuffersPerShaderStage (2). Consider: -1. Grouping some of the uniforms into one using 'd.struct', -2. Increasing the limit when requesting a device or creating a root.`, - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of uniform buffers (3) exceeds maxUniformBuffersPerShaderStage (2). Consider: + 1. Grouping some of the uniforms into one using 'd.struct', + 2. Increasing the limit when requesting a device or creating a root.", + ] + `); }); it('warns for storages', () => { @@ -52,9 +55,12 @@ describe('warnIfOverflow', () => { warnIfOverflow([layout], limits); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of storage buffers (2) exceeds maxStorageBuffersPerShaderStage (1).`, - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of storage buffers (2) exceeds maxStorageBuffersPerShaderStage (1).", + ] + `); }); it('warns when resources are split among layouts', () => { @@ -74,10 +80,13 @@ describe('warnIfOverflow', () => { warnIfOverflow([layout1, layout2, layout3], limits); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of uniform buffers (3) exceeds maxUniformBuffersPerShaderStage (2). Consider: -1. Grouping some of the uniforms into one using 'd.struct', -2. Increasing the limit when requesting a device or creating a root.`, - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of uniform buffers (3) exceeds maxUniformBuffersPerShaderStage (2). Consider: + 1. Grouping some of the uniforms into one using 'd.struct', + 2. Increasing the limit when requesting a device or creating a root.", + ] + `); }); }); diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 2ad3410849..89ac000af4 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -291,9 +291,12 @@ describe('render pipeline behavior', () => { }); tgpu.resolve([pipeline]); - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Mismatched location between vertexFn (vertexMain) output (0) and fragmentFn (fragmentMain) input (1) for the key "bar", using the location set on vertex output.', - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [locations-mismatched}] ", + "Mismatched location between vertexFn (vertexMain) output (0) and fragmentFn (fragmentMain) input (1) for the key "bar", using the location set on vertex output.", + ] + `); }); it('does not log warning when resolving pipeline having vertex and fragment functions with non-conflicting user-defined locations', ({ @@ -355,9 +358,12 @@ describe('render pipeline behavior', () => { // no-op expect(after).toBe(before); }).not.toThrow(); - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Performance callback cannot be used because the timestamp-query feature is not enabled on the root.', - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-feature-missing}] ", + "Performance callback cannot be used because the timestamp-query feature is not enabled on the root.", + ] + `); }); it("should not throw 'A color target was not provided to the shader'", ({ root }) => { @@ -597,14 +603,20 @@ describe('render pipeline behavior', () => { }) .draw(3); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of uniform buffers (13) exceeds maxUniformBuffersPerShaderStage (12). Consider: -1. Grouping some of the uniforms into one using 'd.struct', -2. Increasing the limit when requesting a device or creating a root.`, - ); - expect(consoleWarnSpy).toHaveBeenCalledWith( - `Total number of storage buffers (9) exceeds maxStorageBuffersPerShaderStage (8).`, - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of uniform buffers (13) exceeds maxUniformBuffersPerShaderStage (12). Consider: + 1. Grouping some of the uniforms into one using 'd.struct', + 2. Increasing the limit when requesting a device or creating a root.", + ] + `); + expect(consoleWarnSpy.mock.calls[1]).toMatchInlineSnapshot(` + [ + "⚠️ [webgpu-limits-exceeded}] ", + "Total number of storage buffers (9) exceeds maxStorageBuffersPerShaderStage (8).", + ] + `); }); }); @@ -1577,9 +1589,12 @@ describe('drawIndirect / drawIndexedIndirect buffer and offset validation', () = d.memoryLayoutOf(DeepStruct, (s) => s.someData[10]), ); - expect(warnSpy.mock.calls[0]![0]).toMatchInlineSnapshot( - `"drawIndirect: Starting at offset 40, only 12 contiguous bytes are available before padding. 'drawIndirect' requires 16 bytes (4 x u32). Reading across padding may result in undefined behavior."`, - ); + expect(warnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "drawIndirect: Starting at offset 40, only 12 contiguous bytes are available before padding. 'drawIndirect' requires 16 bytes (4 x u32). Reading across padding may result in undefined behavior.", + ] + `); }); it('does not warn when draw has sufficient contiguous data', ({ root }) => { @@ -1648,9 +1663,12 @@ describe('drawIndirect / drawIndexedIndirect buffer and offset validation', () = d.memoryLayoutOf(DeepStruct, (s) => s.someData[9]), ); - expect(warnSpy.mock.calls[0]![0]).toMatchInlineSnapshot( - `"drawIndexedIndirect: Starting at offset 36, only 16 contiguous bytes are available before padding. 'drawIndexedIndirect' requires 20 bytes (3 x u32, i32, u32). Reading across padding may result in undefined behavior."`, - ); + expect(warnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "drawIndexedIndirect: Starting at offset 36, only 16 contiguous bytes are available before padding. 'drawIndexedIndirect' requires 20 bytes (3 x u32, i32, u32). Reading across padding may result in undefined behavior.", + ] + `); }); it('does not warn when drawIndexed has sufficient contiguous data', ({ root }) => { diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index e388823b3c..1f99389734 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -456,9 +456,12 @@ describe('tgpu resolveWithContext', () => { externals: { identity: (a: number) => a }, }); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "During resolution, the external 'identity' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [external-omitted}] ", + "During resolution, the external 'identity' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", + ] + `); }); it('should warn when the end of external chain was reached without a resolvable', () => { @@ -474,9 +477,12 @@ describe('tgpu resolveWithContext', () => { }" `); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "During resolution, the external 'EXT.p.q' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [external-omitted}] ", + "During resolution, the external 'EXT.p.q' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", + ] + `); }); it('should not warn when In/Out are unused', () => { @@ -511,9 +517,12 @@ describe('resolve without template', () => { tgpu.resolve({ externals: { Boid }, template: '' }); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.", - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [deprecated}] ", + "Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.", + ] + `); }); it('resolves one item', () => { diff --git a/packages/typegpu/tests/texture.test.ts b/packages/typegpu/tests/texture.test.ts index 6573f38c15..9ab8f1f915 100644 --- a/packages/typegpu/tests/texture.test.ts +++ b/packages/typegpu/tests/texture.test.ts @@ -743,9 +743,12 @@ Overload 3 of 4, '(schema: "(Error) Texture not usable as storage, call $usage(' // Base mip level 3 would result in 0 mip levels to generate, so it should return early expect(() => texture.generateMipmaps(3)).not.toThrow(); - expect(consoleSpy).toHaveBeenCalledWith( - 'generateMipmaps is a no-op: would generate 0 mip levels (base: 3, total: 3)', - ); + expect(consoleSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "generateMipmaps is a no-op: would generate 0 mip levels (base: 3, total: 3)", + ] + `); consoleSpy.mockRestore(); }); @@ -764,9 +767,12 @@ Overload 3 of 4, '(schema: "(Error) Texture not usable as storage, call $usage(' // Base mip level 2 would result in 1 mip level to generate (3-2=1), so it should warn and return early expect(() => texture.generateMipmaps(2)).not.toThrow(); - expect(consoleSpy).toHaveBeenCalledWith( - 'generateMipmaps is a no-op: would generate 1 mip levels (base: 2, total: 3)', - ); + expect(consoleSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "generateMipmaps is a no-op: would generate 1 mip levels (base: 2, total: 3)", + ] + `); consoleSpy.mockRestore(); }); diff --git a/packages/typegpu/tests/tgsl/assignment.test.ts b/packages/typegpu/tests/tgsl/assignment.test.ts index 2aaa0b25e9..5b367d6a7b 100644 --- a/packages/typegpu/tests/tgsl/assignment.test.ts +++ b/packages/typegpu/tests/tgsl/assignment.test.ts @@ -26,10 +26,16 @@ it('implicitly casts right-hand side, with a warning', () => { }" `); - expect(warnSpy).toHaveBeenCalledExactlyOnceWith(`\ -Implicit conversions from [ - a: i32, - arg: f32 -] to i32 are supported, but not recommended. -Consider using explicit conversions instead.`); + expect(warnSpy.mock.calls).toMatchInlineSnapshot(` + [ + [ + "⚠️ [implicit-conversion}] ", + "Implicit conversions from [ + a: i32, + arg: f32 + ] to i32 are supported, but not recommended. + Consider using explicit conversions instead.", + ], + ] + `); }); diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index 1093d7a92c..cdc5ac8342 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -16,9 +16,12 @@ describe('wgslGenerator with console.log', () => { }" `); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "'console.log' is only supported when resolving pipelines.", - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [fallback}] ", + "'console.log' is only supported when resolving pipelines.", + ] + `); expect(consoleWarnSpy).toHaveBeenCalledTimes(1); }); @@ -218,9 +221,12 @@ describe('wgslGenerator with console.log', () => { }" `); - expect(consoleWarnSpy).toHaveBeenCalledWith( - "'console' operations are not supported in vertex shaders.", - ); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "'console' operations are not supported in vertex shaders.", + ] + `); expect(consoleWarnSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index 37f095c8ec..70268a6432 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -38,6 +38,7 @@ describe('convertToCommonType', () => { expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` [ [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 1i: i32, 2f: f32 @@ -170,12 +171,14 @@ describe('convertToCommonType', () => { expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` [ [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 1i: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 2u: u32 ] to i32 are supported, but not recommended. diff --git a/packages/typegpu/tests/tgsl/multiplication.test.ts b/packages/typegpu/tests/tgsl/multiplication.test.ts index 5d416adeb4..d73cf6cb05 100644 --- a/packages/typegpu/tests/tgsl/multiplication.test.ts +++ b/packages/typegpu/tests/tgsl/multiplication.test.ts @@ -30,18 +30,21 @@ test('multiplying i32 with a float literal should implicitly convert to an f32', expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 1i: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ int: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 1i: i32 ] to f32 are supported, but not recommended. @@ -77,18 +80,21 @@ test('multiplying u32 with a float literal should implicitly convert to an f32', expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 10u: u32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ int: u32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ 1u: u32 ] to f32 are supported, but not recommended. @@ -119,6 +125,7 @@ test('multiplying u32 with an i32 should implicitly convert to an i32', () => { expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ + "⚠️ [implicit-conversion}] ", "Implicit conversions from [ uint: u32 ] to i32 are supported, but not recommended. diff --git a/packages/typegpu/tests/tgsl/typeInference.test.ts b/packages/typegpu/tests/tgsl/typeInference.test.ts index 63508d2bd8..769c8712ab 100644 --- a/packages/typegpu/tests/tgsl/typeInference.test.ts +++ b/packages/typegpu/tests/tgsl/typeInference.test.ts @@ -234,9 +234,17 @@ describe('wgsl generator type inference', () => { }" `); - expect(warnSpy).toHaveBeenCalledExactlyOnceWith( - 'Implicit conversions from [\n 1.1: abstractFloat\n] to u32 are supported, but not recommended.\nConsider using explicit conversions instead.', - ); + expect(warnSpy.mock.calls).toMatchInlineSnapshot(` + [ + [ + "⚠️ [implicit-conversion}] ", + "Implicit conversions from [ + 1.1: abstractFloat + ] to u32 are supported, but not recommended. + Consider using explicit conversions instead.", + ], + ] + `); }); it('throws when no info about what to coerce to', () => { From 8a8d1ed4321b72c30ffead6a72fa43f8d1727ca9 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:43:23 +0200 Subject: [PATCH 07/21] Change `enable` to `reset`, add tests --- packages/typegpu/src/tgpuLogger.ts | 8 +- .../typegpu/tests/internal/tgpuLogger.test.ts | 79 +++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 packages/typegpu/tests/internal/tgpuLogger.test.ts diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index c55a995482..4994bc40c3 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -22,18 +22,18 @@ interface Logger { // user-facing API interface Warn { disable(type: WarningType): void; - enable(type: WarningType): void; + reset(): void; } -class TgpuLogger implements Logger, Warn { +export class TgpuLogger implements Logger, Warn { #enabledWarnings: Set = new Set(warningTypes); disable(type: WarningType) { this.#enabledWarnings.delete(type); } - enable(type: WarningType) { - this.#enabledWarnings.add(type); + reset() { + this.#enabledWarnings = new Set(warningTypes); } warn(type: WarningType, ...args: unknown[]) { diff --git a/packages/typegpu/tests/internal/tgpuLogger.test.ts b/packages/typegpu/tests/internal/tgpuLogger.test.ts new file mode 100644 index 0000000000..4b5e0fc283 --- /dev/null +++ b/packages/typegpu/tests/internal/tgpuLogger.test.ts @@ -0,0 +1,79 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { logger, TgpuLogger, warn } from '../../src/tgpuLogger.ts'; + +describe('tgpuLogger', () => { + it('warns through console.warn', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(); + + logger.warn('deprecated', 'this is deprecated'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [deprecated}] ", + "this is deprecated", + ] + `); + }); + + it('does not warn after disabling', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(); + + logger.disable('deprecated'); + logger.warn('deprecated', 'this is deprecated'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + }); + + it('starts warning after reset', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(); + + logger.disable('deprecated'); + logger.reset(); + logger.warn('deprecated', 'this is deprecated'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [deprecated}] ", + "this is deprecated", + ] + `); + }); + + it('works with multiple arguments', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + logger.warn('suspicious', 'there is an impostor among us', 42, { prop: 1 }); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "there is an impostor among us", + 42, + { + "prop": 1, + }, + ] + `); + }); + + it('only silences the disabled type', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + warn.disable('deprecated'); + logger.warn('suspicious', 'still warns'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [suspicious}] ", + "still warns", + ] + `); + }); +}); From 7e1368be95ffea9e494da99a86af8caba9cb115a Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:45:20 +0200 Subject: [PATCH 08/21] Pass prod mode to logger --- packages/typegpu/src/tgpuLogger.ts | 38 +++++++++++++++++-- .../typegpu/tests/internal/tgpuLogger.test.ts | 23 ++++++++--- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index 4994bc40c3..227ab6fc67 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -1,3 +1,5 @@ +import { DEV, TEST } from './shared/env.ts'; + const warningTypes = [ 'deprecated', 'suspicious', @@ -19,21 +21,49 @@ interface Logger { warn(type: (typeof warningTypes)[number], ...args: unknown[]): void; } -// user-facing API +/** + * Use this object to globally disable TypeGPU warnings. + * All warnings are better addressed than silenced, only use this when absolutely necessary. + * + * By default, lesser warnings are already silenced in production environment. + */ interface Warn { + /** + * Globally disables one kind of warnings. + * Do not use unless absolutely necessary. + */ disable(type: WarningType): void; + /** + * Restores the initial state. + */ reset(): void; } export class TgpuLogger implements Logger, Warn { - #enabledWarnings: Set = new Set(warningTypes); + #initialEnabledWarnings: readonly WarningType[]; + #enabledWarnings: Set; + + constructor(prod: boolean) { + if (prod) { + this.#initialEnabledWarnings = [ + 'webgpu-feature-missing', + 'webgpu-limits-exceeded', + 'locations-mismatched', + 'log-limit-exceeded', + 'external-omitted', + ]; + } else { + this.#initialEnabledWarnings = warningTypes; + } + this.#enabledWarnings = new Set(this.#initialEnabledWarnings); + } disable(type: WarningType) { this.#enabledWarnings.delete(type); } reset() { - this.#enabledWarnings = new Set(warningTypes); + this.#enabledWarnings = new Set(this.#initialEnabledWarnings); } warn(type: WarningType, ...args: unknown[]) { @@ -43,6 +73,6 @@ export class TgpuLogger implements Logger, Warn { } } -const tgpuLogger = new TgpuLogger(); +const tgpuLogger = new TgpuLogger(!(DEV || TEST)); export const logger: Logger = tgpuLogger; export const warn: Warn = tgpuLogger; diff --git a/packages/typegpu/tests/internal/tgpuLogger.test.ts b/packages/typegpu/tests/internal/tgpuLogger.test.ts index 4b5e0fc283..28dc33d9b3 100644 --- a/packages/typegpu/tests/internal/tgpuLogger.test.ts +++ b/packages/typegpu/tests/internal/tgpuLogger.test.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { logger, TgpuLogger, warn } from '../../src/tgpuLogger.ts'; +import { TgpuLogger } from '../../src/tgpuLogger.ts'; describe('tgpuLogger', () => { it('warns through console.warn', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const logger = new TgpuLogger(); + const logger = new TgpuLogger(false); logger.warn('deprecated', 'this is deprecated'); @@ -19,7 +19,7 @@ describe('tgpuLogger', () => { it('does not warn after disabling', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const logger = new TgpuLogger(); + const logger = new TgpuLogger(false); logger.disable('deprecated'); logger.warn('deprecated', 'this is deprecated'); @@ -29,7 +29,7 @@ describe('tgpuLogger', () => { it('starts warning after reset', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const logger = new TgpuLogger(); + const logger = new TgpuLogger(false); logger.disable('deprecated'); logger.reset(); @@ -46,6 +46,7 @@ describe('tgpuLogger', () => { it('works with multiple arguments', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(false); logger.warn('suspicious', 'there is an impostor among us', 42, { prop: 1 }); @@ -64,8 +65,9 @@ describe('tgpuLogger', () => { it('only silences the disabled type', () => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(false); - warn.disable('deprecated'); + logger.disable('deprecated'); logger.warn('suspicious', 'still warns'); expect(consoleWarnSpy).toHaveBeenCalledTimes(1); @@ -76,4 +78,15 @@ describe('tgpuLogger', () => { ] `); }); + + it('has stricter rules in prod mode', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(true); + + logger.warn('suspicious', '...'); + logger.warn('deprecated', '...'); + logger.warn('fallback', '...'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + }); }); From 1c4bde956bf3684c690ecbf8ea0abb53fa084f35 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:09:07 +0200 Subject: [PATCH 09/21] Change warn to invariant --- packages/typegpu/src/tgsl/conversion.ts | 12 +++++------- packages/typegpu/tests/tgsl/assignment.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index e72ab856ad..0889b37893 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -364,19 +364,17 @@ export function convertToCommonType( return undefined; } - // TODO: investigate - if (DEV && Array.isArray(restrictTo) && restrictTo.length === 0) { - console.warn( - 'convertToCommonType was called with an empty restrictTo array, which prevents any conversions from being made. If you intend to allow all conversions, pass undefined instead. If this was intended call the function conditionally since the result will always be undefined.', - ); - } + invariant( + !(Array.isArray(restrictTo) && restrictTo.length === 0), + "Internal error, expected 'restrictTo' to not be an empty array.", + ); const conversion = getBestConversion(types as BaseData[], restrictTo); if (!conversion) { return undefined; } - if (conversion.hasImplicitConversions) { + if (verbose && conversion.hasImplicitConversions) { logger.warn( 'implicit-conversion', `Implicit conversions from [\n${values diff --git a/packages/typegpu/tests/tgsl/assignment.test.ts b/packages/typegpu/tests/tgsl/assignment.test.ts index 5b367d6a7b..24659dec1c 100644 --- a/packages/typegpu/tests/tgsl/assignment.test.ts +++ b/packages/typegpu/tests/tgsl/assignment.test.ts @@ -1,8 +1,8 @@ import { beforeEach, expect, type MockInstance, vi } from 'vitest'; import { it } from 'typegpu-testing-utility'; -import { tgpu, d, tgpuLogger } from 'typegpu'; +import { tgpu, d } from 'typegpu'; -let warnSpy: MockInstance; +let warnSpy: MockInstance; beforeEach(() => { warnSpy = vi.spyOn(console, 'warn'); From b4b2ba7ce440d7abc68f1b41c8effd0551f54ab5 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:20:13 +0200 Subject: [PATCH 10/21] Change remaining calls to warns --- packages/typegpu/src/data/dataIO.ts | 3 ++- packages/typegpu/src/data/ref.ts | 1 - packages/typegpu/src/shared/stringify.ts | 3 ++- packages/typegpu/src/tgpuLogger.ts | 2 +- packages/typegpu/tests/internal/tgpuLogger.test.ts | 12 ++++++++++++ 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/typegpu/src/data/dataIO.ts b/packages/typegpu/src/data/dataIO.ts index a798b20a6b..c0b8bfdc25 100644 --- a/packages/typegpu/src/data/dataIO.ts +++ b/packages/typegpu/src/data/dataIO.ts @@ -864,7 +864,8 @@ export function writeToArrayBuffer( compiledWriter(dataView, startOffset, data, isLittleEndian, endOffset); return; } catch (error) { - console.error( + logger.warn( + 'fallback', `Error when using compiled writer for data type '${ schema.type }' (${getName(schema) ?? 'unnamed'}) - this is likely a bug, please submit an issue at https://github.com/software-mansion/TypeGPU/issues\nUsing fallback writer instead.`, diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 77dac4395a..7598ca5f94 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -148,7 +148,6 @@ export function INTERNAL_createRef(value: T): ref { return false; } if (prop === '$') { - console.log('Setting ref value:', propValue); return Reflect.set(target, prop, propValue); } return Reflect.set(value as object, prop, propValue); diff --git a/packages/typegpu/src/shared/stringify.ts b/packages/typegpu/src/shared/stringify.ts index 7af99b8e0e..57c32a7269 100644 --- a/packages/typegpu/src/shared/stringify.ts +++ b/packages/typegpu/src/shared/stringify.ts @@ -1,4 +1,5 @@ import { isMatInstance, isVecInstance } from '../data/wgslTypes.ts'; +import { logger } from '../tgpuLogger.ts'; export function safeStringify(item: unknown): string { if (Array.isArray(item)) { @@ -13,7 +14,7 @@ export function safeStringify(item: unknown): string { try { return JSON.stringify(item); } catch (error) { - console.error('Error parsing JSON:', error); + logger.warn('suspicious', 'Error parsing JSON:', error); return ''; } } diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index 227ab6fc67..cbc9143c4a 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -16,7 +16,7 @@ const warningTypes = [ ] as const; type WarningType = (typeof warningTypes)[number]; -// internal-facing API +// internal API interface Logger { warn(type: (typeof warningTypes)[number], ...args: unknown[]): void; } diff --git a/packages/typegpu/tests/internal/tgpuLogger.test.ts b/packages/typegpu/tests/internal/tgpuLogger.test.ts index 28dc33d9b3..5d56593070 100644 --- a/packages/typegpu/tests/internal/tgpuLogger.test.ts +++ b/packages/typegpu/tests/internal/tgpuLogger.test.ts @@ -89,4 +89,16 @@ describe('tgpuLogger', () => { expect(consoleWarnSpy).toHaveBeenCalledTimes(0); }); + + it('correctly resets to initial state', () => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const logger = new TgpuLogger(true); + + logger.reset(); + logger.warn('suspicious', '...'); + logger.warn('deprecated', '...'); + logger.warn('fallback', '...'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + }); }); From 593242923b9d42e6419b9a8166f824bfc3d8de9c Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:40:31 +0200 Subject: [PATCH 11/21] Add docs --- .../src/content/docs/apis/utils.mdx | 20 ++++++++++++++++++- .../src/examples/simple/triangle/index.ts | 14 ------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/apps/typegpu-docs/src/content/docs/apis/utils.mdx b/apps/typegpu-docs/src/content/docs/apis/utils.mdx index 819069d345..059773d8d8 100644 --- a/apps/typegpu-docs/src/content/docs/apis/utils.mdx +++ b/apps/typegpu-docs/src/content/docs/apis/utils.mdx @@ -548,7 +548,7 @@ If what the expression is a direct reference to an existing value (e.g. a unifor storage binding, ...), then choose from `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'` or `'handle'` depending on the address space of the referred value. -### `possibleSideEffects` +### *possibleSideEffects* The fourth optional parameter `possibleSideEffects` indicates, whether generating this snippet may produce a WGSL expression with observable side-effects (e.g. calling a barrier, discarding a fragment, or writing to memory). @@ -557,3 +557,21 @@ Snippets with `possibleSideEffects: true` cannot appear in ternary branches that get compiled to `select()`, because `select()` evaluates both branches unconditionally - a side-effect meant to be conditional would execute regardless of the condition. + +## *warn* + +TypeGPU warns you about a lot of potential problems or API misuses. +Although it is almost always better addressed properly, the `warn` object let's you silence specific warning types. + +```ts twoslash +import { tgpu, d, warn } from 'typegpu'; + +const fn = tgpu.fn([], d.u32)(() => { + 'use gpu'; + return 1.5; // <- AbstractFloat needs an implicit cast to u32 +}); + +warn.disable('implicit-conversion'); +console.log(tgpu.resolve([fn])); // resolves without warning about the cast +warn.reset(); +``` diff --git a/apps/typegpu-docs/src/examples/simple/triangle/index.ts b/apps/typegpu-docs/src/examples/simple/triangle/index.ts index 81f34c5646..f785d16d01 100644 --- a/apps/typegpu-docs/src/examples/simple/triangle/index.ts +++ b/apps/typegpu-docs/src/examples/simple/triangle/index.ts @@ -55,17 +55,3 @@ export function onCleanup() { } // #endregion - -// import { tgpu, d, tgpuLogger } from 'typegpu'; - -// const fn = tgpu.fn( -// [d.u32, d.f32], -// d.u32, -// )((a, b) => { -// 'use gpu'; -// return a / b; -// }); - -// tgpuLogger.disableWarn('implicit-conversion'); - -// console.log(tgpu.resolve([fn])); From 24dfc22ffb554b2a0109480ca7c14526d2062f89 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:45:03 +0200 Subject: [PATCH 12/21] nr fix --- packages/typegpu/src/tgsl/conversion.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 0889b37893..8d83f8c7aa 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -19,7 +19,6 @@ import { type WgslStruct, } from '../data/wgslTypes.ts'; import { invariant, WgslTypeError } from '../errors.ts'; -import { DEV, TEST } from '../shared/env.ts'; import { getName } from '../shared/meta.ts'; import { safeStringify } from '../shared/stringify.ts'; import { assertExhaustive } from '../shared/utilityTypes.ts'; From 46fc32e511a1056d455a6fba067a6f636ab184c0 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:03:32 +0200 Subject: [PATCH 13/21] Add tests --- packages/typegpu/tests/buffer.test.ts | 80 +++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/packages/typegpu/tests/buffer.test.ts b/packages/typegpu/tests/buffer.test.ts index 3473bb79ae..c3170651d1 100644 --- a/packages/typegpu/tests/buffer.test.ts +++ b/packages/typegpu/tests/buffer.test.ts @@ -1732,3 +1732,83 @@ describe('ValidateBufferSchema', () => { ); }); }); + +describe('Uniform alignment', () => { + it('does not report legit schemas', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform(d.u32); + root.createUniform(d.struct({ p: d.u32 })); + root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.vec4f })); + root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.align(16, d.u32) })); + root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.align(32, d.u32) })); + root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.vec3f })); + root.createUniform(d.struct({ p: d.size(16, d.struct({ p: d.u32 })), q: d.u32 })); + root.createUniform(d.struct({ p: d.size(32, d.struct({ p: d.u32 })), q: d.u32 })); + root.createUniform(d.arrayOf(d.vec4f, 3)); + root.createUniform(d.arrayOf(d.vec3f, 3)); + root.createUniform(d.arrayOf(d.align(16, d.u32), 3)); + root.createUniform(d.arrayOf(d.struct({ p: d.vec3f }), 3)); + + expect(consoleWarnSpy).not.toHaveBeenCalled(); + }); + + it('reports unaligned props in structs', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.u32 })); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); + + it('reports further unaligned props in structs', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.vec4f, r: d.u32 })); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); + + it('reports nested unaligned props in structs', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform( + d.struct({ p: d.vec4f, q: d.struct({ p: d.struct({ p: d.u32 }), q: d.u32 }) }), + ); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); + + it('reports unaligned arrays', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform(d.arrayOf(d.u32, 3)); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); + + it('reports nested unaligned arrays', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform(d.arrayOf(d.arrayOf(d.u32, 4), 4)); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); + + it('reports when giving usage', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createBuffer(d.arrayOf(d.u32, 2)).$usage('uniform'); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); + + it('does not report twice', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createBuffer(d.arrayOf(d.u32, 2)).$usage('uniform').as('uniform'); + + expect(consoleWarnSpy).toHaveBeenCalledTimes(1); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + }); +}); From bf5b386e37de3e45b88e47f61f8bd6fddedec695 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:37:46 +0200 Subject: [PATCH 14/21] Finish warn implementation --- .../src/core/pipeline/applyPipelineState.ts | 2 +- .../src/core/pipeline/limitsOverflow.ts | 27 ----- .../src/core/pipeline/webgpuLimitations.ts | 111 ++++++++++++++++++ packages/typegpu/src/tgpuLogger.ts | 1 + packages/typegpu/tests/buffer.test.ts | 62 +++++++++- .../tests/internal/limitsOverflow.test.ts | 2 +- 6 files changed, 170 insertions(+), 35 deletions(-) delete mode 100644 packages/typegpu/src/core/pipeline/limitsOverflow.ts create mode 100644 packages/typegpu/src/core/pipeline/webgpuLimitations.ts diff --git a/packages/typegpu/src/core/pipeline/applyPipelineState.ts b/packages/typegpu/src/core/pipeline/applyPipelineState.ts index df2e708ddc..7f5fb466ba 100644 --- a/packages/typegpu/src/core/pipeline/applyPipelineState.ts +++ b/packages/typegpu/src/core/pipeline/applyPipelineState.ts @@ -10,7 +10,7 @@ import type { BaseData } from '../../data/wgslTypes.ts'; import type { TgpuVertexLayout } from '../vertexLayout/vertexLayout.ts'; import type { ExperimentalTgpuRoot } from '../root/rootTypes.ts'; -import { warnIfOverflow } from './limitsOverflow.ts'; +import { warnIfOverflow } from './webgpuLimitations.ts'; // ----------------------------------------------- // shared helpers for applying pipeline state to render/compute pass encoders diff --git a/packages/typegpu/src/core/pipeline/limitsOverflow.ts b/packages/typegpu/src/core/pipeline/limitsOverflow.ts deleted file mode 100644 index 0675a232dd..0000000000 --- a/packages/typegpu/src/core/pipeline/limitsOverflow.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { TgpuBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; -import { logger } from '../../tgpuLogger.ts'; - -export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSupportedLimits) { - const entries = Object.values(layouts) - .flatMap((layout) => Object.values(layout.entries)) - .filter((entry) => entry !== null); - - const uniform = entries.filter((entry) => 'uniform' in entry).length; - const storage = entries.filter((entry) => 'storage' in entry).length; - - if (uniform > limits.maxUniformBuffersPerShaderStage) { - logger.warn( - 'webgpu-limits-exceeded', - `Total number of uniform buffers (${uniform}) exceeds maxUniformBuffersPerShaderStage (${limits.maxUniformBuffersPerShaderStage}). Consider: -1. Grouping some of the uniforms into one using 'd.struct', -2. Increasing the limit when requesting a device or creating a root.`, - ); - } - - if (storage > limits.maxStorageBuffersPerShaderStage) { - logger.warn( - 'webgpu-limits-exceeded', - `Total number of storage buffers (${storage}) exceeds maxStorageBuffersPerShaderStage (${limits.maxStorageBuffersPerShaderStage}).`, - ); - } -} diff --git a/packages/typegpu/src/core/pipeline/webgpuLimitations.ts b/packages/typegpu/src/core/pipeline/webgpuLimitations.ts new file mode 100644 index 0000000000..cfc4509266 --- /dev/null +++ b/packages/typegpu/src/core/pipeline/webgpuLimitations.ts @@ -0,0 +1,111 @@ +import { alignmentOf } from '../../data/alignmentOf.ts'; +import { memoryLayoutOf } from '../../data/offsetUtils.ts'; +import { sizeOf } from '../../data/sizeOf.ts'; +import { isWgslArray, isWgslStruct, type BaseData } from '../../data/wgslTypes.ts'; +import { invariant } from '../../errors.ts'; +import { getName } from '../../internal.ts'; +import { roundUp } from '../../mathUtils.ts'; +import type { TgpuBindGroupLayout } from '../../tgpuBindGroupLayout.ts'; +import { logger } from '../../tgpuLogger.ts'; + +/** + * Warns if layout exceeds supported buffer count limits. + */ +export function warnIfOverflow(layouts: TgpuBindGroupLayout[], limits: GPUSupportedLimits) { + const entries = Object.values(layouts) + .flatMap((layout) => Object.values(layout.entries)) + .filter((entry) => entry !== null); + + const uniform = entries.filter((entry) => 'uniform' in entry).length; + const storage = entries.filter((entry) => 'storage' in entry).length; + + if (uniform > limits.maxUniformBuffersPerShaderStage) { + logger.warn( + 'webgpu-limits-exceeded', + `Total number of uniform buffers (${uniform}) exceeds maxUniformBuffersPerShaderStage (${limits.maxUniformBuffersPerShaderStage}). Consider: +1. Grouping some of the uniforms into one using 'd.struct', +2. Increasing the limit when requesting a device or creating a root.`, + ); + } + + if (storage > limits.maxStorageBuffersPerShaderStage) { + logger.warn( + 'webgpu-limits-exceeded', + `Total number of storage buffers (${storage}) exceeds maxStorageBuffersPerShaderStage (${limits.maxStorageBuffersPerShaderStage}).`, + ); + } +} + +function requiredAlignOf(schema: BaseData) { + if (isWgslStruct(schema) || isWgslArray(schema)) { + return roundUp(alignmentOf(schema), 16); + } + return alignmentOf(schema); +} + +/** + * See https://www.w3.org/TR/WGSL/#address-space-layout-constraints + */ +export function warnIfNotUniformAligned(schema: BaseData) { + if (isWgslArray(schema)) { + warnIfNotUniformAligned(schema.elementType); + + const stride = roundUp(sizeOf(schema.elementType), alignmentOf(schema.elementType)); + if (stride % 16) { + logger.warn( + 'uniform-schema-misaligned', + `\ +Schema '${getName(schema) ?? ''}' is used in an array in an uniform buffer, and its stride (${stride}) is not a multiple of 16. +This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. +To address this, wrap the element in 'd.align(16, ...)'.`, + ); + } + } + if (isWgslStruct(schema)) { + Object.values(schema.propTypes).forEach(warnIfNotUniformAligned); + + Object.entries(schema.propTypes).forEach(([key, value]) => { + const offset = memoryLayoutOf(schema, (schema) => schema[key]).offset; + const requiredAlignment = requiredAlignOf(value); + + if (offset % requiredAlignment) { + logger.warn( + 'uniform-schema-misaligned', + `\ +Schema '${getName(schema) ?? ''}' is used in an uniform buffer, and its property ${key} does not meet required alignment (offset is ${offset}, required alignment is ${requiredAlignment}). +This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. +To address this, wrap the property in 'd.align(${requiredAlignment}, ...)'.`, + ); + } + }); + + const keys = Object.keys(schema.propTypes); + for (let i = 0; i < keys.length - 1; i++) { + const thisKey = keys[i]; + const nextKey = keys[i + 1]; + invariant(thisKey && nextKey); + + const thisValue = schema.propTypes[thisKey]; + invariant(thisValue); + + if (!isWgslStruct(thisValue)) { + continue; + } + + const minimumDifference = roundUp(16, sizeOf(thisValue)); + const thisKeyOffset = memoryLayoutOf(schema, (schema) => schema[thisKey]).offset; + const nextKeyOffset = memoryLayoutOf(schema, (schema) => schema[nextKey]).offset; + const difference = nextKeyOffset - thisKeyOffset; + + if (minimumDifference > difference) { + logger.warn( + 'uniform-schema-misaligned', + `\ +Schema '${getName(schema) ?? ''}' is used in an uniform buffer, and the difference between memory offsets of '${thisKey}' and '${nextKey}' props (${difference}) is less than recommended (${minimumDifference}). +This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. +To address this, wrap the '${thisKey}' prop in 'd.size(${minimumDifference}, ...)'.`, + ); + } + } + } +} diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index cbc9143c4a..14194b91ab 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -13,6 +13,7 @@ const warningTypes = [ 'locations-mismatched', 'log-limit-exceeded', 'external-omitted', + 'uniform-schema-misaligned', ] as const; type WarningType = (typeof warningTypes)[number]; diff --git a/packages/typegpu/tests/buffer.test.ts b/packages/typegpu/tests/buffer.test.ts index c3170651d1..d47dacd83b 100644 --- a/packages/typegpu/tests/buffer.test.ts +++ b/packages/typegpu/tests/buffer.test.ts @@ -1753,20 +1753,49 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy).not.toHaveBeenCalled(); }); + it('reports props not meeting requiredAlignOf in structs', ({ root }) => { + using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + root.createUniform(d.struct({ q: d.u32, p: d.struct({ p: d.u32 }) })); + + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an array in an uniform buffer, and its property p does not meet its required alignment (offset is 4, required alignment is 16). + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the property in 'd.align(16, ...)'.", + ] + `); + }); + it('reports unaligned props in structs', ({ root }) => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.u32 })); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (4) is less than recommended (16). + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the 'p' prop in 'd.size(16, ...)'.", + ] + `); }); it('reports further unaligned props in structs', ({ root }) => { using consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - root.createUniform(d.struct({ p: d.struct({ p: d.u32 }), q: d.vec4f, r: d.u32 })); + root.createUniform(d.struct({ p: d.vec4f, q: d.struct({ p: d.u32 }), r: d.u32 })); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an uniform buffer, and the difference between memory offsets of 'q' and 'r' props (4) is less than recommended (16). + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the 'q' prop in 'd.size(16, ...)'.", + ] + `); }); it('reports nested unaligned props in structs', ({ root }) => { @@ -1776,7 +1805,14 @@ describe('Uniform alignment', () => { d.struct({ p: d.vec4f, q: d.struct({ p: d.struct({ p: d.u32 }), q: d.u32 }) }), ); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema 'q' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (4) is less than recommended (16). + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the 'p' prop in 'd.size(16, ...)'.", + ] + `); }); it('reports unaligned arrays', ({ root }) => { @@ -1784,7 +1820,14 @@ describe('Uniform alignment', () => { root.createUniform(d.arrayOf(d.u32, 3)); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the element in 'd.align(16, ...)'.", + ] + `); }); it('reports nested unaligned arrays', ({ root }) => { @@ -1792,7 +1835,14 @@ describe('Uniform alignment', () => { root.createUniform(d.arrayOf(d.arrayOf(d.u32, 4), 4)); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the element in 'd.align(16, ...)'.", + ] + `); }); it('reports when giving usage', ({ root }) => { diff --git a/packages/typegpu/tests/internal/limitsOverflow.test.ts b/packages/typegpu/tests/internal/limitsOverflow.test.ts index 0d41cfa3fd..5f024ade46 100644 --- a/packages/typegpu/tests/internal/limitsOverflow.test.ts +++ b/packages/typegpu/tests/internal/limitsOverflow.test.ts @@ -1,7 +1,7 @@ import { describe, expect, vi } from 'vitest'; import { it } from 'typegpu-testing-utility'; import { tgpu, d } from 'typegpu'; -import { warnIfOverflow } from '../../src/core/pipeline/limitsOverflow.ts'; +import { warnIfOverflow } from '../../src/core/pipeline/webgpuLimitations.ts'; describe('warnIfOverflow', () => { const limits = { From 90ff2ec15e79b54d9fd882a66dbebc581b94b9a2 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:40:39 +0200 Subject: [PATCH 15/21] Report when giving usage --- packages/typegpu/src/core/buffer/buffer.ts | 5 +++++ packages/typegpu/tests/buffer.test.ts | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/typegpu/src/core/buffer/buffer.ts b/packages/typegpu/src/core/buffer/buffer.ts index cab7ab00af..77ba5f1264 100644 --- a/packages/typegpu/src/core/buffer/buffer.ts +++ b/packages/typegpu/src/core/buffer/buffer.ts @@ -32,6 +32,7 @@ import { type TgpuReadonly, type TgpuUniform, } from './bufferBinding.ts'; +import { warnIfNotUniformAligned } from '../pipeline/webgpuLimitations.ts'; // ---------- // Public API @@ -292,6 +293,10 @@ class TgpuBufferImpl implements TgpuBuffer { throw new Error(`Buffer of type ${this.dataType.type} cannot be used as ${usage}`); } + if (usage === 'uniform') { + warnIfNotUniformAligned(this.dataType); + } + this.flags |= usage === 'uniform' ? GPUBufferUsage.UNIFORM : 0; this.flags |= usage === 'storage' ? GPUBufferUsage.STORAGE : 0; this.flags |= usage === 'vertex' ? GPUBufferUsage.VERTEX : 0; diff --git a/packages/typegpu/tests/buffer.test.ts b/packages/typegpu/tests/buffer.test.ts index d47dacd83b..0cfb99b515 100644 --- a/packages/typegpu/tests/buffer.test.ts +++ b/packages/typegpu/tests/buffer.test.ts @@ -1761,7 +1761,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ "⚠️ [uniform-schema-misaligned}] ", - "Schema '' is used in an array in an uniform buffer, and its property p does not meet its required alignment (offset is 4, required alignment is 16). + "Schema '' is used in an uniform buffer, and its property p does not meet required alignment (offset is 4, required alignment is 16). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the property in 'd.align(16, ...)'.", ] @@ -1850,7 +1850,14 @@ describe('Uniform alignment', () => { root.createBuffer(d.arrayOf(d.u32, 2)).$usage('uniform'); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the element in 'd.align(16, ...)'.", + ] + `); }); it('does not report twice', ({ root }) => { @@ -1859,6 +1866,13 @@ describe('Uniform alignment', () => { root.createBuffer(d.arrayOf(d.u32, 2)).$usage('uniform').as('uniform'); expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(`undefined`); + expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` + [ + "⚠️ [uniform-schema-misaligned}] ", + "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. + To address this, wrap the element in 'd.align(16, ...)'.", + ] + `); }); }); From 664d90fc9e502af2a9e9c2ea7589c77f18a4255b Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:05:31 +0200 Subject: [PATCH 16/21] Remove 'uniform' usage from confetti examples --- apps/typegpu-docs/src/examples/react/confetti/index.tsx | 2 +- apps/typegpu-docs/src/examples/simulation/confetti/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/typegpu-docs/src/examples/react/confetti/index.tsx b/apps/typegpu-docs/src/examples/react/confetti/index.tsx index 2a7fb460bf..476bfdab19 100644 --- a/apps/typegpu-docs/src/examples/react/confetti/index.tsx +++ b/apps/typegpu-docs/src/examples/react/confetti/index.tsx @@ -127,7 +127,7 @@ function App() { const particleDataBuffer = useBuffer(d.arrayOf(ParticleData, PARTICLE_AMOUNT), { initial: writeRandomPositions, - }).$usage('storage', 'uniform', 'vertex'); + }).$usage('storage', 'vertex'); const aspectRatio = useUniform(d.f32, { initial: 1 }); const deltaTime = useUniform(d.f32); diff --git a/apps/typegpu-docs/src/examples/simulation/confetti/index.ts b/apps/typegpu-docs/src/examples/simulation/confetti/index.ts index 97a4d007ea..6655c8690e 100644 --- a/apps/typegpu-docs/src/examples/simulation/confetti/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/confetti/index.ts @@ -48,7 +48,7 @@ const particleGeometryBuffer = root const particleDataBuffer = root .createBuffer(d.arrayOf(ParticleData, PARTICLE_AMOUNT)) - .$usage('storage', 'uniform', 'vertex'); + .$usage('storage', 'vertex'); let elapsedTime = 0; const aspectRatio = root.createUniform(d.f32, canvas.width / canvas.height); From 742c6956979de46ed32dfa02ce027ffc002cd2fc Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:14:22 +0200 Subject: [PATCH 17/21] Self review --- .../typegpu/src/core/pipeline/webgpuLimitations.ts | 6 +++--- packages/typegpu/tests/buffer.test.ts | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/typegpu/src/core/pipeline/webgpuLimitations.ts b/packages/typegpu/src/core/pipeline/webgpuLimitations.ts index cfc4509266..60132e566a 100644 --- a/packages/typegpu/src/core/pipeline/webgpuLimitations.ts +++ b/packages/typegpu/src/core/pipeline/webgpuLimitations.ts @@ -55,7 +55,7 @@ export function warnIfNotUniformAligned(schema: BaseData) { logger.warn( 'uniform-schema-misaligned', `\ -Schema '${getName(schema) ?? ''}' is used in an array in an uniform buffer, and its stride (${stride}) is not a multiple of 16. +Schema '${getName(schema.elementType) ?? ''}' is used in an array in an uniform buffer, and its stride (${stride}) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.`, ); @@ -72,9 +72,9 @@ To address this, wrap the element in 'd.align(16, ...)'.`, logger.warn( 'uniform-schema-misaligned', `\ -Schema '${getName(schema) ?? ''}' is used in an uniform buffer, and its property ${key} does not meet required alignment (offset is ${offset}, required alignment is ${requiredAlignment}). +Schema '${getName(schema) ?? ''}' is used in an uniform buffer, and its property '${key}' does not meet required alignment (offset is ${offset}, required alignment is ${requiredAlignment}). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. -To address this, wrap the property in 'd.align(${requiredAlignment}, ...)'.`, +To address this, wrap the property '${key}' in 'd.align(${requiredAlignment}, ...)'.`, ); } }); diff --git a/packages/typegpu/tests/buffer.test.ts b/packages/typegpu/tests/buffer.test.ts index 0cfb99b515..d95e63e47f 100644 --- a/packages/typegpu/tests/buffer.test.ts +++ b/packages/typegpu/tests/buffer.test.ts @@ -1761,9 +1761,9 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ "⚠️ [uniform-schema-misaligned}] ", - "Schema '' is used in an uniform buffer, and its property p does not meet required alignment (offset is 4, required alignment is 16). + "Schema '' is used in an uniform buffer, and its property 'p' does not meet required alignment (offset is 4, required alignment is 16). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. - To address this, wrap the property in 'd.align(16, ...)'.", + To address this, wrap the property 'p' in 'd.align(16, ...)'.", ] `); }); @@ -1823,7 +1823,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ "⚠️ [uniform-schema-misaligned}] ", - "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", ] @@ -1838,7 +1838,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ "⚠️ [uniform-schema-misaligned}] ", - "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", ] @@ -1853,7 +1853,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ "⚠️ [uniform-schema-misaligned}] ", - "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", ] @@ -1869,7 +1869,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ "⚠️ [uniform-schema-misaligned}] ", - "Schema '' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. + "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", ] From 7357da47fcf44c05dd510cab291e601cf0749979 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:23:05 +0200 Subject: [PATCH 18/21] Review fixes --- apps/typegpu-docs/src/content/docs/apis/utils.mdx | 2 +- packages/typegpu/src/tgpuLogger.ts | 2 +- packages/typegpu/tests/computePipeline.test.ts | 12 ++++++------ .../typegpu/tests/internal/limitsOverflow.test.ts | 6 +++--- packages/typegpu/tests/internal/tgpuLogger.test.ts | 8 ++++---- packages/typegpu/tests/renderPipeline.test.ts | 12 ++++++------ packages/typegpu/tests/resolve.test.ts | 6 +++--- packages/typegpu/tests/texture.test.ts | 4 ++-- packages/typegpu/tests/tgsl/assignment.test.ts | 2 +- packages/typegpu/tests/tgsl/consoleLog.test.ts | 4 ++-- packages/typegpu/tests/tgsl/conversion.test.ts | 6 +++--- packages/typegpu/tests/tgsl/multiplication.test.ts | 14 +++++++------- packages/typegpu/tests/tgsl/typeInference.test.ts | 2 +- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/apps/typegpu-docs/src/content/docs/apis/utils.mdx b/apps/typegpu-docs/src/content/docs/apis/utils.mdx index 059773d8d8..8b5a73930e 100644 --- a/apps/typegpu-docs/src/content/docs/apis/utils.mdx +++ b/apps/typegpu-docs/src/content/docs/apis/utils.mdx @@ -561,7 +561,7 @@ would execute regardless of the condition. ## *warn* TypeGPU warns you about a lot of potential problems or API misuses. -Although it is almost always better addressed properly, the `warn` object let's you silence specific warning types. +Although it is almost always better addressed properly, the `warn` object lets you silence specific warning types. ```ts twoslash import { tgpu, d, warn } from 'typegpu'; diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index cbc9143c4a..1a532c3675 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -68,7 +68,7 @@ export class TgpuLogger implements Logger, Warn { warn(type: WarningType, ...args: unknown[]) { if (this.#enabledWarnings.has(type)) { - console.warn(`⚠️ [${type}}] `, ...args); + console.warn(`⚠️ [${type}] `, ...args); } } } diff --git a/packages/typegpu/tests/computePipeline.test.ts b/packages/typegpu/tests/computePipeline.test.ts index b8b4caf18d..92b10789a8 100644 --- a/packages/typegpu/tests/computePipeline.test.ts +++ b/packages/typegpu/tests/computePipeline.test.ts @@ -100,7 +100,7 @@ describe('TgpuComputePipeline', () => { }).not.toThrow(); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-feature-missing}] ", + "⚠️ [webgpu-feature-missing] ", "Performance callback cannot be used because the timestamp-query feature is not enabled on the root.", ] `); @@ -334,7 +334,7 @@ describe('TgpuComputePipeline', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of uniform buffers (14) exceeds maxUniformBuffersPerShaderStage (12). Consider: 1. Grouping some of the uniforms into one using 'd.struct', 2. Increasing the limit when requesting a device or creating a root.", @@ -342,7 +342,7 @@ describe('TgpuComputePipeline', () => { `); expect(consoleWarnSpy.mock.calls[1]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of storage buffers (9) exceeds maxStorageBuffersPerShaderStage (8).", ] `); @@ -424,7 +424,7 @@ describe('TgpuComputePipeline', () => { expect(warnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "dispatchWorkgroupsIndirect: Starting at offset 0, only 4 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior.", ] `); @@ -437,7 +437,7 @@ describe('TgpuComputePipeline', () => { expect(warnSpy.mock.calls[1]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "dispatchWorkgroupsIndirect: Starting at offset 44, only 8 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior.", ] `); @@ -449,7 +449,7 @@ describe('TgpuComputePipeline', () => { expect(warnSpy.mock.calls[2]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "dispatchWorkgroupsIndirect: Starting at offset 84, only 8 contiguous bytes are available before padding. 'dispatchWorkgroupsIndirect' requires 12 bytes (3 x u32). Reading across padding may result in undefined behavior.", ] `); diff --git a/packages/typegpu/tests/internal/limitsOverflow.test.ts b/packages/typegpu/tests/internal/limitsOverflow.test.ts index 0d41cfa3fd..4253742e23 100644 --- a/packages/typegpu/tests/internal/limitsOverflow.test.ts +++ b/packages/typegpu/tests/internal/limitsOverflow.test.ts @@ -37,7 +37,7 @@ describe('warnIfOverflow', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of uniform buffers (3) exceeds maxUniformBuffersPerShaderStage (2). Consider: 1. Grouping some of the uniforms into one using 'd.struct', 2. Increasing the limit when requesting a device or creating a root.", @@ -57,7 +57,7 @@ describe('warnIfOverflow', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of storage buffers (2) exceeds maxStorageBuffersPerShaderStage (1).", ] `); @@ -82,7 +82,7 @@ describe('warnIfOverflow', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of uniform buffers (3) exceeds maxUniformBuffersPerShaderStage (2). Consider: 1. Grouping some of the uniforms into one using 'd.struct', 2. Increasing the limit when requesting a device or creating a root.", diff --git a/packages/typegpu/tests/internal/tgpuLogger.test.ts b/packages/typegpu/tests/internal/tgpuLogger.test.ts index 5d56593070..d1e44baed2 100644 --- a/packages/typegpu/tests/internal/tgpuLogger.test.ts +++ b/packages/typegpu/tests/internal/tgpuLogger.test.ts @@ -11,7 +11,7 @@ describe('tgpuLogger', () => { expect(consoleWarnSpy).toHaveBeenCalledTimes(1); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [deprecated}] ", + "⚠️ [deprecated] ", "this is deprecated", ] `); @@ -38,7 +38,7 @@ describe('tgpuLogger', () => { expect(consoleWarnSpy).toHaveBeenCalledTimes(1); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [deprecated}] ", + "⚠️ [deprecated] ", "this is deprecated", ] `); @@ -53,7 +53,7 @@ describe('tgpuLogger', () => { expect(consoleWarnSpy).toHaveBeenCalledTimes(1); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "there is an impostor among us", 42, { @@ -73,7 +73,7 @@ describe('tgpuLogger', () => { expect(consoleWarnSpy).toHaveBeenCalledTimes(1); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "still warns", ] `); diff --git a/packages/typegpu/tests/renderPipeline.test.ts b/packages/typegpu/tests/renderPipeline.test.ts index 89ac000af4..58a1569af6 100644 --- a/packages/typegpu/tests/renderPipeline.test.ts +++ b/packages/typegpu/tests/renderPipeline.test.ts @@ -293,7 +293,7 @@ describe('render pipeline behavior', () => { tgpu.resolve([pipeline]); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [locations-mismatched}] ", + "⚠️ [locations-mismatched] ", "Mismatched location between vertexFn (vertexMain) output (0) and fragmentFn (fragmentMain) input (1) for the key "bar", using the location set on vertex output.", ] `); @@ -360,7 +360,7 @@ describe('render pipeline behavior', () => { }).not.toThrow(); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-feature-missing}] ", + "⚠️ [webgpu-feature-missing] ", "Performance callback cannot be used because the timestamp-query feature is not enabled on the root.", ] `); @@ -605,7 +605,7 @@ describe('render pipeline behavior', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of uniform buffers (13) exceeds maxUniformBuffersPerShaderStage (12). Consider: 1. Grouping some of the uniforms into one using 'd.struct', 2. Increasing the limit when requesting a device or creating a root.", @@ -613,7 +613,7 @@ describe('render pipeline behavior', () => { `); expect(consoleWarnSpy.mock.calls[1]).toMatchInlineSnapshot(` [ - "⚠️ [webgpu-limits-exceeded}] ", + "⚠️ [webgpu-limits-exceeded] ", "Total number of storage buffers (9) exceeds maxStorageBuffersPerShaderStage (8).", ] `); @@ -1591,7 +1591,7 @@ describe('drawIndirect / drawIndexedIndirect buffer and offset validation', () = expect(warnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "drawIndirect: Starting at offset 40, only 12 contiguous bytes are available before padding. 'drawIndirect' requires 16 bytes (4 x u32). Reading across padding may result in undefined behavior.", ] `); @@ -1665,7 +1665,7 @@ describe('drawIndirect / drawIndexedIndirect buffer and offset validation', () = expect(warnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "drawIndexedIndirect: Starting at offset 36, only 16 contiguous bytes are available before padding. 'drawIndexedIndirect' requires 20 bytes (3 x u32, i32, u32). Reading across padding may result in undefined behavior.", ] `); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 1f99389734..532b5766d6 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -458,7 +458,7 @@ describe('tgpu resolveWithContext', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [external-omitted}] ", + "⚠️ [external-omitted] ", "During resolution, the external 'identity' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", ] `); @@ -479,7 +479,7 @@ describe('tgpu resolveWithContext', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [external-omitted}] ", + "⚠️ [external-omitted] ", "During resolution, the external 'EXT.p.q' has been omitted. Only TGPU resources, 'use gpu' functions, primitives, and plain JS objects can be used as externals.", ] `); @@ -519,7 +519,7 @@ describe('resolve without template', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [deprecated}] ", + "⚠️ [deprecated] ", "Calling resolve with an empty template is deprecated and will soon return an empty string. Consider using the 'tgpu.resolve(resolvableArray, options)' API instead.", ] `); diff --git a/packages/typegpu/tests/texture.test.ts b/packages/typegpu/tests/texture.test.ts index 9ab8f1f915..f5440c32ca 100644 --- a/packages/typegpu/tests/texture.test.ts +++ b/packages/typegpu/tests/texture.test.ts @@ -745,7 +745,7 @@ Overload 3 of 4, '(schema: "(Error) Texture not usable as storage, call $usage(' expect(consoleSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "generateMipmaps is a no-op: would generate 0 mip levels (base: 3, total: 3)", ] `); @@ -769,7 +769,7 @@ Overload 3 of 4, '(schema: "(Error) Texture not usable as storage, call $usage(' expect(consoleSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "generateMipmaps is a no-op: would generate 1 mip levels (base: 2, total: 3)", ] `); diff --git a/packages/typegpu/tests/tgsl/assignment.test.ts b/packages/typegpu/tests/tgsl/assignment.test.ts index 24659dec1c..f45d7bd6e5 100644 --- a/packages/typegpu/tests/tgsl/assignment.test.ts +++ b/packages/typegpu/tests/tgsl/assignment.test.ts @@ -29,7 +29,7 @@ it('implicitly casts right-hand side, with a warning', () => { expect(warnSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ a: i32, arg: f32 diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index cdc5ac8342..df5233582c 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -18,7 +18,7 @@ describe('wgslGenerator with console.log', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [fallback}] ", + "⚠️ [fallback] ", "'console.log' is only supported when resolving pipelines.", ] `); @@ -223,7 +223,7 @@ describe('wgslGenerator with console.log', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [suspicious}] ", + "⚠️ [suspicious] ", "'console' operations are not supported in vertex shaders.", ] `); diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index 70268a6432..9b7243a565 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -38,7 +38,7 @@ describe('convertToCommonType', () => { expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 1i: i32, 2f: f32 @@ -171,14 +171,14 @@ describe('convertToCommonType', () => { expect(consoleSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 1i: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 2u: u32 ] to i32 are supported, but not recommended. diff --git a/packages/typegpu/tests/tgsl/multiplication.test.ts b/packages/typegpu/tests/tgsl/multiplication.test.ts index d73cf6cb05..94459f32df 100644 --- a/packages/typegpu/tests/tgsl/multiplication.test.ts +++ b/packages/typegpu/tests/tgsl/multiplication.test.ts @@ -30,21 +30,21 @@ test('multiplying i32 with a float literal should implicitly convert to an f32', expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 1i: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ int: i32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 1i: i32 ] to f32 are supported, but not recommended. @@ -80,21 +80,21 @@ test('multiplying u32 with a float literal should implicitly convert to an f32', expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 10u: u32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ int: u32 ] to f32 are supported, but not recommended. Consider using explicit conversions instead.", ], [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 1u: u32 ] to f32 are supported, but not recommended. @@ -125,7 +125,7 @@ test('multiplying u32 with an i32 should implicitly convert to an i32', () => { expect(consoleWarnSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ uint: u32 ] to i32 are supported, but not recommended. diff --git a/packages/typegpu/tests/tgsl/typeInference.test.ts b/packages/typegpu/tests/tgsl/typeInference.test.ts index 769c8712ab..5cba8fc88f 100644 --- a/packages/typegpu/tests/tgsl/typeInference.test.ts +++ b/packages/typegpu/tests/tgsl/typeInference.test.ts @@ -237,7 +237,7 @@ describe('wgsl generator type inference', () => { expect(warnSpy.mock.calls).toMatchInlineSnapshot(` [ [ - "⚠️ [implicit-conversion}] ", + "⚠️ [implicit-conversion] ", "Implicit conversions from [ 1.1: abstractFloat ] to u32 are supported, but not recommended. From 521ca90cabcb2c0757b0d296d305d9a4ff3725b8 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:23:32 +0200 Subject: [PATCH 19/21] Unused import --- packages/typegpu/tests/internal/tgpuLogger.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/tests/internal/tgpuLogger.test.ts b/packages/typegpu/tests/internal/tgpuLogger.test.ts index d1e44baed2..0bcdd64083 100644 --- a/packages/typegpu/tests/internal/tgpuLogger.test.ts +++ b/packages/typegpu/tests/internal/tgpuLogger.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { TgpuLogger } from '../../src/tgpuLogger.ts'; describe('tgpuLogger', () => { From 7b8f4d22e93dda39683f35cd9efd82aef7cf3cc6 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:49:33 +0200 Subject: [PATCH 20/21] Review fixes --- packages/typegpu/src/tgpuLogger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/src/tgpuLogger.ts b/packages/typegpu/src/tgpuLogger.ts index 1a532c3675..240b183b5c 100644 --- a/packages/typegpu/src/tgpuLogger.ts +++ b/packages/typegpu/src/tgpuLogger.ts @@ -18,7 +18,7 @@ type WarningType = (typeof warningTypes)[number]; // internal API interface Logger { - warn(type: (typeof warningTypes)[number], ...args: unknown[]): void; + warn(type: WarningType, ...args: unknown[]): void; } /** From 7d87abc251799c9df9ed1ee6a9e50e37dc182451 Mon Sep 17 00:00:00 2001 From: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:03:07 +0200 Subject: [PATCH 21/21] Update snapshots --- packages/typegpu/tests/buffer.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/typegpu/tests/buffer.test.ts b/packages/typegpu/tests/buffer.test.ts index d95e63e47f..b8f8dd3c17 100644 --- a/packages/typegpu/tests/buffer.test.ts +++ b/packages/typegpu/tests/buffer.test.ts @@ -1760,7 +1760,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema '' is used in an uniform buffer, and its property 'p' does not meet required alignment (offset is 4, required alignment is 16). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the property 'p' in 'd.align(16, ...)'.", @@ -1775,7 +1775,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema '' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (4) is less than recommended (16). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the 'p' prop in 'd.size(16, ...)'.", @@ -1790,7 +1790,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema '' is used in an uniform buffer, and the difference between memory offsets of 'q' and 'r' props (4) is less than recommended (16). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the 'q' prop in 'd.size(16, ...)'.", @@ -1807,7 +1807,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema 'q' is used in an uniform buffer, and the difference between memory offsets of 'p' and 'q' props (4) is less than recommended (16). This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the 'p' prop in 'd.size(16, ...)'.", @@ -1822,7 +1822,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", @@ -1837,7 +1837,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", @@ -1852,7 +1852,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.", @@ -1868,7 +1868,7 @@ describe('Uniform alignment', () => { expect(consoleWarnSpy).toHaveBeenCalledTimes(1); expect(consoleWarnSpy.mock.calls[0]).toMatchInlineSnapshot(` [ - "⚠️ [uniform-schema-misaligned}] ", + "⚠️ [uniform-schema-misaligned] ", "Schema 'u32' is used in an array in an uniform buffer, and its stride (4) is not a multiple of 16. This is not portable (see https://www.w3.org/TR/WGSL/#address-space-layout-constraints), and will break on some devices. To address this, wrap the element in 'd.align(16, ...)'.",