diff --git a/modules/core/src/utils/texture.ts b/modules/core/src/utils/texture.ts index 3a9ba8f918f..2dbacb34a2c 100644 --- a/modules/core/src/utils/texture.ts +++ b/modules/core/src/utils/texture.ts @@ -47,7 +47,10 @@ export function createTexture( }; } - const {width, height} = image.data; + // Browser objects are wrapped as `{data: browserImage}` above, so their dimensions live on + // `data`. The plain object form `{data: , width, height}` carries them on the object + // itself - reading `image.data` there yields `undefined` and an invalid `mipLevels` count. + const {width, height} = image.data.width === undefined ? image : image.data; const texture = device.createTexture({ ...image, sampler: { diff --git a/test/modules/core/utils/index.ts b/test/modules/core/utils/index.ts index c252bb2cbd1..2318f58fa5d 100644 --- a/test/modules/core/utils/index.ts +++ b/test/modules/core/utils/index.ts @@ -15,3 +15,4 @@ import './math-utils.spec'; import './shader.spec'; import './typed-array-manager.spec'; import './apply-styles.spec'; +import './texture.spec'; diff --git a/test/modules/core/utils/texture.spec.ts b/test/modules/core/utils/texture.spec.ts new file mode 100644 index 00000000000..81b39d48a8e --- /dev/null +++ b/test/modules/core/utils/texture.spec.ts @@ -0,0 +1,48 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +import {test, expect} from 'vitest'; + +import {createTexture, destroyTexture} from '@deck.gl/core/utils/texture'; +import {device} from '@deck.gl/test-utils/vitest'; + +const SIZE = 64; +// 1 + floor(log2(64)) +const EXPECTED_MIP_LEVELS = 7; + +test('createTexture#plain object image', () => { + // The documented `{data, width, height}` form carries its dimensions on the object itself, not on + // `data`. Reading them from the wrong level produced `mipLevels: NaN`, which rendered as opaque + // black. See issue #10371 + const texture = createTexture( + 'test-owner', + device, + {data: new Uint8Array(SIZE * SIZE * 4).fill(200), width: SIZE, height: SIZE}, + {} + ); + + expect(texture, 'creates a texture').toBeTruthy(); + expect(texture!.width, 'reads width from the plain object').toBe(SIZE); + expect(texture!.height, 'reads height from the plain object').toBe(SIZE); + expect(texture!.mipLevels, 'derives a valid mip level count').toBe(EXPECTED_MIP_LEVELS); + + destroyTexture('test-owner', texture!); +}); + +test('createTexture#browser image object', () => { + // Browser objects are wrapped as `{data: browserImage}`, so their dimensions live on `data` + const texture = createTexture( + 'test-owner', + device, + new ImageData(new Uint8ClampedArray(SIZE * SIZE * 4).fill(200), SIZE, SIZE), + {} + ); + + expect(texture, 'creates a texture').toBeTruthy(); + expect(texture!.width, 'reads width from the wrapped image').toBe(SIZE); + expect(texture!.height, 'reads height from the wrapped image').toBe(SIZE); + expect(texture!.mipLevels, 'derives a valid mip level count').toBe(EXPECTED_MIP_LEVELS); + + destroyTexture('test-owner', texture!); +});