diff --git a/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts b/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts index eb44a19266..c8c0e5e0de 100644 --- a/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts +++ b/apps/typegpu-docs/tests/individual-example-tests/function-visualizer.test.ts @@ -173,9 +173,9 @@ describe('function visualizer example', () => { return vec2f(0, 1); } let lineVertices = (&lineVertices_1); - let previous = (&(*lineVertices)[u32((index - 1f))]); - let current = (&(*lineVertices)[u32(index)]); - let next = (&(*lineVertices)[u32((index + 1f))]); + let previous = (&(*lineVertices)[i32((index - 1f))]); + let current = (&(*lineVertices)[i32(index)]); + let next = (&(*lineVertices)[i32((index + 1f))]); let n1 = orthonormalForLine((*previous), (*current)); let n2 = orthonormalForLine((*current), (*next)); let avg = ((n1 + n2) / 2f); @@ -196,7 +196,7 @@ describe('function visualizer example', () => { let rightTop = ((*properties).transformation * vec4f(1, 1, 0, 1)); let canvasRatio = ((rightTop.x - leftBot.x) / (rightTop.y - leftBot.y)); let adjustedOffset = vec2f((offset.x / canvasRatio), offset.y); - return vertex_Output(vec4f(((*lineVertices)[u32(currentVertex)] + adjustedOffset), 0f, 1f)); + return vertex_Output(vec4f(((*lineVertices)[i32(currentVertex)] + adjustedOffset), 0f, 1f)); } @group(1) @binding(1) var color: vec4f; diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 70f54b1580..026725f6a6 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -83,6 +83,19 @@ function getAutoConversionRank(src: BaseData, dest: BaseData): ConversionRankInf return INFINITE_RANK; } +const primitivePreference = { + f32: 0, + f16: 1, + i32: 2, + u32: 3, + bool: 4, +} as const; +type PrimitiveType = keyof typeof primitivePreference; + +function getPreference(type: BaseData): number { + return primitivePreference[undecorate(type).type as PrimitiveType] ?? Number.POSITIVE_INFINITY; +} + function getImplicitConversionRank(src: BaseData, dest: BaseData): ConversionRankInfo { const trueSrc = undecorate(src); const trueDst = undecorate(dest); @@ -103,15 +116,6 @@ function getImplicitConversionRank(src: BaseData, dest: BaseData): ConversionRan return { rank: 1, action: 'ref' }; } - const primitivePreference = { - f32: 0, - f16: 1, - i32: 2, - u32: 3, - bool: 4, - } as const; - type PrimitiveType = keyof typeof primitivePreference; - if (trueSrc.type in primitivePreference && trueDst.type in primitivePreference) { const srcType = trueSrc.type as PrimitiveType; const destType = trueDst.type as PrimitiveType; @@ -176,7 +180,9 @@ function findBestType( uniqueTypes: BaseData[], allowImplicit: boolean, ): ConversionResult | undefined { - let bestResult: { type: BaseData; details: ConversionRankInfo[]; sum: number } | undefined; + let bestResult: + | { type: BaseData; details: ConversionRankInfo[]; sum: number; preference: number } + | undefined; for (const targetType of uniqueTypes) { /** @@ -196,8 +202,12 @@ function findBestType( destType = conversion.targetType; } } - if (sum < (bestResult?.sum ?? Number.POSITIVE_INFINITY)) { - bestResult = { type: destType, details, sum }; + const preference = getPreference(destType); + if ( + sum < (bestResult?.sum ?? Number.POSITIVE_INFINITY) || + (bestResult !== undefined && sum === bestResult.sum && preference < bestResult.preference) + ) { + bestResult = { type: destType, details, sum, preference }; } } if (!bestResult) { diff --git a/packages/typegpu/tests/internal/getBestConversion.test.ts b/packages/typegpu/tests/internal/getBestConversion.test.ts index 99b9af7be5..4b538f62a2 100644 --- a/packages/typegpu/tests/internal/getBestConversion.test.ts +++ b/packages/typegpu/tests/internal/getBestConversion.test.ts @@ -1,6 +1,7 @@ import { describe, expect } from 'vitest'; import { abstractFloat, abstractInt } from '../../src/data/numeric.ts'; import { getBestConversion } from '../../src/tgsl/conversion.ts'; +import type { BaseData } from '../../src/data/wgslTypes.ts'; import { it } from 'typegpu-testing-utility'; import { INTERNAL_createPtr } from '../../src/data/ptr.ts'; import { d } from '../../src/index.js'; @@ -67,22 +68,25 @@ describe('getBestConversion', () => { // Potential targets (from input): u32, f16, i32 // Preference: f32(0) > f16(1) > i32(2) > u32(3) // + // Rank of a cast is `destPref < srcPref ? 10 : 20`, summed over all sources. + // Equal sums are broken by preferring the lower destPref. + // // Target f16 (pref 1): - // u32 (3) -> f16 (1): dest < src => rank 10 + // u32 (3) -> f16 (1): destPref < srcPref => rank 10 // f16 (1) -> f16 (1): rank 0 - // i32 (2) -> f16 (1): dest < src => rank 10 + // i32 (2) -> f16 (1): destPref < srcPref => rank 10 // Total Rank = 10 + 0 + 10 = 20 // // Target i32 (pref 2): - // u32 (3) -> i32 (2): dest < src => rank 10 - // f16 (1) -> i32 (2): dest >= src => rank 20 + // u32 (3) -> i32 (2): destPref < srcPref => rank 10 + // f16 (1) -> i32 (2): destPref >= srcPref => rank 20 // i32 (2) -> i32 (2): rank 0 // Total Rank = 10 + 20 + 0 = 30 // // Target u32 (pref 3): // u32 (3) -> u32 (3): rank 0 - // f16 (1) -> u32 (3): dest >= src => rank 20 - // i32 (2) -> u32 (3): dest >= src => rank 20 + // f16 (1) -> u32 (3): destPref >= srcPref => rank 20 + // i32 (2) -> u32 (3): destPref >= srcPref => rank 20 // Total Rank = 0 + 20 + 20 = 40 // // Lowest rank is 20 for target f16. @@ -156,6 +160,40 @@ describe('getBestConversion', () => { expect(res?.hasImplicitConversions).toBe(true); }); + it('does not depend on the order of targetTypes', () => { + const cases: { sources: BaseData[]; candidates: BaseData[]; expected: BaseData }[] = [ + { sources: [d.f32, d.f32], candidates: [d.i32, d.u32], expected: d.i32 }, + { sources: [d.f16], candidates: [d.i32, d.u32], expected: d.i32 }, + { sources: [d.bool], candidates: [d.i32, d.u32], expected: d.i32 }, + { sources: [d.u32, d.i32], candidates: [d.f32, d.f16], expected: d.f32 }, + ]; + + for (const { sources, candidates, expected } of cases) { + expect(getBestConversion(sources, candidates)?.targetType).toBe(expected); + expect(getBestConversion(sources, candidates.toReversed())?.targetType).toBe(expected); + } + }); + + it('does not depend on the order of sources', () => { + const cases: { sources: BaseData[]; expected: BaseData }[] = [ + { sources: [d.i32, d.u32, d.u32], expected: d.i32 }, + { sources: [d.f16, d.i32, d.i32], expected: d.f16 }, + { sources: [d.u32, d.f16, d.i32], expected: d.f16 }, + ]; + + for (const { sources, expected } of cases) { + expect(getBestConversion(sources)?.targetType).toBe(expected); + expect(getBestConversion(sources.toReversed())?.targetType).toBe(expected); + } + }); + + it('does not let preference outweigh the ranking as sources pile up', () => { + // u32: 0 + 4*10, f32: 10 + 4*10 + expect( + getBestConversion([d.u32, d.bool, d.bool, d.bool, d.bool], [d.f32, d.u32])?.targetType, + ).toBe(d.u32); + }); + it('handles void gracefully', () => { const resFail = getBestConversion([d.f32, d.Void]); expect(resFail).toBeUndefined(); diff --git a/packages/typegpu/tests/std/texture/textureGather.test.ts b/packages/typegpu/tests/std/texture/textureGather.test.ts index 087a76a3d2..99684a0176 100644 --- a/packages/typegpu/tests/std/texture/textureGather.test.ts +++ b/packages/typegpu/tests/std/texture/textureGather.test.ts @@ -85,10 +85,10 @@ describe('textureGather', () => { const component = 0i; let gather2d = textureGather(component, tex2d, sampler_1, uv2d); let gather2d_u32 = textureGather(component, tex2d_u32, sampler_1, uv2d); - let gather2d_array = textureGather(component, tex2d_array, sampler_1, uv2d, u32(idx)); - let gathercube_array = textureGather(component, texcube_array, sampler_1, uv3d, u32(idx)); + let gather2d_array = textureGather(component, tex2d_array, sampler_1, uv2d, i32(idx)); + let gathercube_array = textureGather(component, texcube_array, sampler_1, uv3d, i32(idx)); let gatherdepth2d = textureGather(texdepth2d, sampler_1, uv2d); - let gatherdepth2d_array = textureGather(texdepth2d_array, sampler_1, uv2d, u32(idx)); + let gatherdepth2d_array = textureGather(texdepth2d_array, sampler_1, uv2d, i32(idx)); }" `); }); diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index 9b7243a565..ad1323d133 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -218,8 +218,8 @@ describe('convertToCommonType', () => { expect(tgpu.resolve([fn])).toMatchInlineSnapshot(` "fn fn_1() { let arr = array(1, 2, 3, 4); - let x = arr[1u]; - let y = arr[1u]; + let x = arr[1i]; + let y = arr[1i]; let z = arr[1i]; let t = arr[1u]; }"