export const description = `createTexture validation tests.`;

import { AllFeaturesMaxLimitsGPUTest } from '../.././gpu_test.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert, makeValueTestVariant } from '../../../common/util/util.js';
import { kTextureDimensions, kTextureUsages } from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
  kAllTextureFormats,
  kCompressedTextureFormats,
  kUncompressedTextureFormats,
  kRegularTextureFormats,
  kFeaturesForFormats,
  filterFormatsByFeature,
  textureFormatAndDimensionPossiblyCompatible,
  getBlockInfoForTextureFormat,
  isTextureFormatMultisampled,
  isTextureFormatColorRenderable,
  isTextureFormatPossiblyUsableAsColorRenderAttachment,
  isTextureFormatPossiblyStorageReadable,
  isColorTextureFormat,
  textureFormatsAreViewCompatible,
  textureDimensionAndFormatCompatibleForDevice,
  getMaxValidTextureSizeForFormatAndDimension,
  isTextureFormatUsableWithStorageAccessMode,
} from '../../format_info.js';
import { maxMipLevelCount } from '../../util/texture/base.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);

g.test('zero_size_and_usage')
  .desc(
    `Test texture creation with zero or nonzero size of
    width, height, depthOrArrayLayers and mipLevelCount, usage for every dimension, and
    representative formats.
  `
  )
  .params(u =>
    u
      .combine('dimension', [undefined, ...kTextureDimensions])
      .combine('format', [
        'rgba8unorm',
        'rgb10a2unorm',
        'bc1-rgba-unorm',
        'depth24plus-stencil8',
      ] as const)
      .beginSubcases()
      .combine('zeroArgument', [
        'none',
        'width',
        'height',
        'depthOrArrayLayers',
        'mipLevelCount',
        'usage',
      ] as const)
      // Filter out incompatible dimension type and format combinations.
      .filter(({ dimension, format }) =>
        textureFormatAndDimensionPossiblyCompatible(dimension, format)
      )
  )
  .fn(t => {
    const { dimension, zeroArgument, format } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);

    const info = getBlockInfoForTextureFormat(format);

    const size = [info.blockWidth, info.blockHeight, 1];
    let mipLevelCount = 1;
    let usage = GPUTextureUsage.TEXTURE_BINDING;

    switch (zeroArgument) {
      case 'width':
        size[0] = 0;
        break;
      case 'height':
        size[1] = 0;
        break;
      case 'depthOrArrayLayers':
        size[2] = 0;
        break;
      case 'mipLevelCount':
        mipLevelCount = 0;
        break;
      case 'usage':
        usage = 0;
        break;
      default:
        break;
    }

    const descriptor = {
      size,
      mipLevelCount,
      dimension,
      format,
      usage,
    };

    const success = zeroArgument === 'none';

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('dimension_type_and_format_compatibility')
  .desc(
    `Test every dimension type on every format. Note that compressed formats and depth/stencil formats are not valid
    for 1D dimension types while it depends on the format for 3D types.`
  )
  .params(u =>
    u //
      .combine('dimension', [undefined, ...kTextureDimensions])
      .combine('format', kAllTextureFormats)
  )
  .fn(t => {
    const { dimension, format } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    const info = getBlockInfoForTextureFormat(format);

    const descriptor: GPUTextureDescriptor = {
      size: [info.blockWidth, info.blockHeight, 1],
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    t.expectValidationError(
      () => {
        t.createTextureTracked(descriptor);
      },
      !textureDimensionAndFormatCompatibleForDevice(t.device, dimension, format)
    );
  });

g.test('mipLevelCount,format')
  .desc(
    `Test texture creation with no mipmap chain, partial mipmap chain, full mipmap chain, out-of-bounds mipmap chain
    for every format with different texture dimension types.`
  )
  .params(u =>
    u
      .combine('dimension', [undefined, ...kTextureDimensions])
      .combine('format', kAllTextureFormats)
      .beginSubcases()
      .combine('mipLevelCount', [1, 2, 3, 6, 7])
      // Filter out incompatible dimension type and format combinations.
      .filter(({ dimension, format }) =>
        textureFormatAndDimensionPossiblyCompatible(dimension, format)
      )
      .combine('largestDimension', [0, 1, 2])
      .unless(({ dimension, largestDimension }) => dimension === '1d' && largestDimension > 0)
  )
  .fn(t => {
    const { dimension, format, mipLevelCount, largestDimension } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
    const info = getBlockInfoForTextureFormat(format);

    // Compute dimensions such that the dimensions are in range [17, 32] and aligned with the
    // format block size so that there will be exactly 6 mip levels.
    const kTargetMipLevelCount = 5;
    const kTargetLargeSize = (1 << kTargetMipLevelCount) - 1;
    const largeSize = [
      Math.floor(kTargetLargeSize / info.blockWidth) * info.blockWidth,
      Math.floor(kTargetLargeSize / info.blockHeight) * info.blockHeight,
      kTargetLargeSize,
    ];
    assert(17 <= largeSize[0] && largeSize[0] <= 32);
    assert(17 <= largeSize[1] && largeSize[1] <= 32);

    // Note that compressed formats are not valid for 1D. They have already been filtered out for 1D
    // in this test. So there is no dilemma about size.width equals 1 vs
    // size.width % info.blockHeight equals 0 for 1D compressed formats.
    const size = [info.blockWidth, info.blockHeight, 1];
    size[largestDimension] = largeSize[largestDimension];

    const descriptor = {
      size,
      mipLevelCount,
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const success = mipLevelCount <= maxMipLevelCount(descriptor);

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('mipLevelCount,bound_check')
  .desc(
    `Test mip level count bound check upon different texture size and different texture dimension types.
    The cases below test: 1) there must be no mip levels after a 1 level (1D texture), or 1x1 level (2D texture), or 1x1x1 level (3D texture), 2) array layers are not mip-mapped, 3) power-of-two, non-power-of-two, and non-square sizes.`
  )
  .params(u =>
    u //
      .combine('format', ['rgba8unorm', 'bc1-rgba-unorm'] as const)
      .beginSubcases()
      .combineWithParams([
        { size: [32, 32] }, // Mip level sizes: 32x32, 16x16, 8x8, 4x4, 2x2, 1x1
        { size: [31, 32] }, // Mip level sizes: 31x32, 15x16, 7x8, 3x4, 1x2, 1x1
        { size: [28, 32] }, // Mip level sizes: 28x32, 14x16, 7x8, 3x4, 1x2, 1x1
        { size: [32, 31] }, // Mip level sizes: 32x31, 16x15, 8x7, 4x3, 2x1, 1x1
        { size: [32, 28] }, // Mip level sizes: 32x28, 16x14, 8x7, 4x3, 2x1, 1x1
        { size: [31, 31] }, // Mip level sizes: 31x31, 15x15, 7x7, 3x3, 1x1
        { size: [32], dimension: '1d' as const }, // Mip level sizes: 32, 16, 8, 4, 2, 1
        { size: [31], dimension: '1d' as const }, // Mip level sizes: 31, 15, 7, 3, 1
        { size: [32, 32, 32], dimension: '3d' as const }, // Mip level sizes: 32x32x32, 16x16x16, 8x8x8, 4x4x4, 2x2x2, 1x1x1
        { size: [32, 31, 31], dimension: '3d' as const }, // Mip level sizes: 32x31x31, 16x15x15, 8x7x7, 4x3x3, 2x1x1, 1x1x1
        { size: [31, 32, 31], dimension: '3d' as const }, // Mip level sizes: 31x32x31, 15x16x15, 7x8x7, 3x4x3, 1x2x1, 1x1x1
        { size: [31, 31, 32], dimension: '3d' as const }, // Mip level sizes: 31x31x32, 15x15x16, 7x7x8, 3x3x4, 1x1x2, 1x1x1
        { size: [31, 31, 31], dimension: '3d' as const }, // Mip level sizes: 31x31x31, 15x15x15, 7x7x7, 3x3x3, 1x1x1
        { size: [32, 8] }, // Mip levels: 32x8, 16x4, 8x2, 4x1, 2x1, 1x1
        { size: [32, 32, 64] }, // Mip levels: 32x32x64, 16x16x64, 8x8x64, 4x4x64, 2x2x64, 1x1x64
        { size: [32, 32, 64], dimension: '3d' as const }, // Mip levels: 32x32x64, 16x16x32, 8x8x16, 4x4x8, 2x2x4, 1x1x2, 1x1x1
      ])
      .unless(
        ({ format, size, dimension }) =>
          format === 'bc1-rgba-unorm' &&
          (dimension === '1d' ||
            dimension === '3d' ||
            size[0] % getBlockInfoForTextureFormat(format).blockWidth !== 0 ||
            size[1] % getBlockInfoForTextureFormat(format).blockHeight !== 0)
      )
  )
  .fn(t => {
    const { format, size, dimension } = t.params;
    t.skipIfTextureFormatNotSupported(format);

    const descriptor = {
      size,
      mipLevelCount: 0,
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const mipLevelCount = maxMipLevelCount(descriptor);
    descriptor.mipLevelCount = mipLevelCount;
    t.createTextureTracked(descriptor);

    descriptor.mipLevelCount = mipLevelCount + 1;
    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    });
  });

g.test('mipLevelCount,bound_check,bigger_than_integer_bit_width')
  .desc(`Test mip level count bound check when mipLevelCount is bigger than integer bit width`)
  .fn(t => {
    const descriptor = {
      size: [32, 32],
      mipLevelCount: 100,
      format: 'rgba8unorm' as const,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    });
  });

g.test('sampleCount,various_sampleCount_with_all_formats')
  .desc(
    `Test texture creation with various (valid or invalid) sample count and all formats. Note that 1D and 3D textures can't support multisample.`
  )
  .params(u =>
    u
      .combine('dimension', [undefined, '2d'] as const)
      .combine('format', kAllTextureFormats)
      .beginSubcases()
      .combine('sampleCount', [0, 1, 2, 4, 8, 16, 32, 256])
  )
  .fn(t => {
    const { dimension, sampleCount, format } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    const info = getBlockInfoForTextureFormat(format);

    const usage =
      sampleCount > 1
        ? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT
        : GPUTextureUsage.TEXTURE_BINDING;
    const descriptor = {
      size: [32 * info.blockWidth, 32 * info.blockHeight, 1],
      sampleCount,
      dimension,
      format,
      usage,
    };

    const success =
      sampleCount === 1 || (sampleCount === 4 && isTextureFormatMultisampled(t.device, format));

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('sampleCount,valid_sampleCount_with_other_parameter_varies')
  .desc(
    `Test texture creation with valid sample count when dimensions, arrayLayerCount, mipLevelCount,
     format, and usage varies. Texture can be single sample (sampleCount is 1) or multi-sample
     (sampleCount is 4). Multisample texture requires that
     1) its dimension is 2d or undefined,
     2) its format supports multisample,
     3) its mipLevelCount and arrayLayerCount are 1,
     4) its usage doesn't include STORAGE_BINDING,
     5) its usage includes RENDER_ATTACHMENT.`
  )
  .params(u =>
    u
      .combine('dimension', [undefined, ...kTextureDimensions])
      .combine('format', kAllTextureFormats)
      .beginSubcases()
      .combine('sampleCount', [1, 4])
      .combine('arrayLayerCount', [1, 2])
      .unless(
        ({ dimension, arrayLayerCount }) =>
          arrayLayerCount === 2 && dimension !== '2d' && dimension !== undefined
      )
      .combine('mipLevelCount', [1, 2])
      .expand('usage', () => {
        const usageSet = new Set<number>();
        for (const usage0 of kTextureUsages) {
          for (const usage1 of kTextureUsages) {
            usageSet.add(usage0 | usage1);
          }
        }
        return usageSet;
      })
      // Filter out incompatible dimension type and format combinations.
      .filter(({ dimension, format }) =>
        textureFormatAndDimensionPossiblyCompatible(dimension, format)
      )
      .unless(({ usage, format, mipLevelCount, dimension }) => {
        return (
          ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
            (!isTextureFormatPossiblyUsableAsColorRenderAttachment(format) ||
              dimension !== '2d')) ||
          ((usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 &&
            !isTextureFormatPossiblyStorageReadable(format)) ||
          (mipLevelCount !== 1 && dimension === '1d')
        );
      })
  )
  .fn(t => {
    const { dimension, sampleCount, format, mipLevelCount, arrayLayerCount, usage } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
    if ((usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0) {
      t.skipIfTextureFormatNotUsableAsRenderAttachment(format);
    }
    const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);

    const size =
      dimension === '1d'
        ? [32 * blockWidth, 1 * blockHeight, 1]
        : dimension === '2d' || dimension === undefined
        ? [32 * blockWidth, 32 * blockHeight, arrayLayerCount]
        : [32 * blockWidth, 32 * blockHeight, 32];
    const descriptor = {
      size,
      mipLevelCount,
      sampleCount,
      dimension,
      format,
      usage,
    };

    const satisfyWithStorageUsageRequirement =
      (usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0 ||
      isTextureFormatUsableWithStorageAccessMode(t.device, format, 'write-only');

    const success =
      (sampleCount === 1 && satisfyWithStorageUsageRequirement) ||
      (sampleCount === 4 &&
        isTextureFormatMultisampled(t.device, format) &&
        (dimension === '2d' || dimension === undefined) &&
        mipLevelCount === 1 &&
        arrayLayerCount === 1 &&
        (usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
        (usage & GPUConst.TextureUsage.STORAGE_BINDING) === 0);

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('sample_count,1d_2d_array_3d')
  .desc(`Test that you can not create 1d, 2d_array, and 3d multisampled textures`)
  .params(u =>
    u.combineWithParams([
      { dimension: '2d', size: [4, 4, 1], shouldError: false },
      { dimension: '1d', size: [4, 1, 1], shouldError: true },
      { dimension: '2d', size: [4, 4, 4], shouldError: true },
      { dimension: '2d', size: [4, 4, 6], shouldError: true },
      { dimension: '3d', size: [4, 4, 4], shouldError: true },
    ] as const)
  )
  .fn(t => {
    const { dimension, size, shouldError } = t.params;

    t.expectValidationError(() => {
      t.createTextureTracked({
        size,
        dimension,
        sampleCount: 4,
        format: 'rgba8unorm',
        usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
      });
    }, shouldError);
  });

g.test('texture_size,default_value_and_smallest_size,uncompressed_format')
  .desc(
    `Test default values for height and depthOrArrayLayers for every dimension type and every uncompressed format.
    It also tests smallest size (lower bound) for every dimension type and every uncompressed format, while other texture_size tests are testing the upper bound.`
  )
  .params(u =>
    u
      .combine('dimension', [undefined, ...kTextureDimensions])
      .combine('format', kUncompressedTextureFormats)
      .beginSubcases()
      .combine('size', [[1], [1, 1], [1, 1, 1]])
      // Filter out incompatible dimension type and format combinations.
      .filter(({ dimension, format }) =>
        textureFormatAndDimensionPossiblyCompatible(dimension, format)
      )
  )
  .fn(t => {
    const { dimension, format, size } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);

    const descriptor: GPUTextureDescriptor = {
      size,
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    t.createTextureTracked(descriptor);
  });

g.test('texture_size,default_value_and_smallest_size,compressed_format')
  .desc(
    `Test default values for height and depthOrArrayLayers for every dimension type and every compressed format.
    It also tests smallest size (lower bound) for every dimension type and every compressed format, while other texture_size tests are testing the upper bound.`
  )
  .params(u =>
    u
      // Compressed formats are invalid for 1D.
      .combine('dimension', [undefined, '2d', '3d'] as const)
      .combine('format', kCompressedTextureFormats)
      .filter(({ dimension, format }) =>
        textureFormatAndDimensionPossiblyCompatible(dimension, format)
      )
      .beginSubcases()
      .expandWithParams(p => {
        const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(p.format);
        return [
          { size: [1], _success: false },
          { size: [blockWidth], _success: false },
          { size: [1, 1], _success: false },
          { size: [blockWidth, blockHeight], _success: true },
          { size: [1, 1, 1], _success: false },
          { size: [blockWidth, blockHeight, 1], _success: true },
        ];
      })
  )
  .fn(t => {
    const { dimension, format, size, _success } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);

    const descriptor: GPUTextureDescriptor = {
      size,
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !_success);
  });

g.test('texture_size,1d_texture')
  .desc(`Test texture size requirement for 1D texture`)
  .params(u =>
    u //
      // Compressed and depth-stencil textures are invalid for 1D.
      .combine('format', kRegularTextureFormats)
      .beginSubcases()
      .combine('widthVariant', [
        { mult: 1, add: -1 },
        { mult: 1, add: 0 },
        { mult: 1, add: 1 },
      ])
      .combine('height', [1, 2])
      .combine('depthOrArrayLayers', [1, 2])
  )
  .fn(t => {
    const { format, widthVariant, height, depthOrArrayLayers } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    const width = t.makeLimitVariant('maxTextureDimension1D', widthVariant);

    const descriptor: GPUTextureDescriptor = {
      size: [width, height, depthOrArrayLayers],
      dimension: '1d' as const,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const success =
      width <= t.device.limits.maxTextureDimension1D && height === 1 && depthOrArrayLayers === 1;

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('texture_size,2d_texture,uncompressed_format')
  .desc(`Test texture size requirement for 2D texture with uncompressed format.`)
  .params(u =>
    u
      .combine('dimension', [undefined, '2d'] as const)
      .combine('format', kUncompressedTextureFormats)
      .combine(
        'sizeVariant',
        /* prettier-ignore */ [
          // Test the bound of width
          [{ mult: 1, add: -1 }, { mult: 0, add:  1 }, { mult: 0, add:  1 }],
          [{ mult: 1, add:  0 }, { mult: 0, add:  1 }, { mult: 0, add:  1 }],
          [{ mult: 1, add:  1 }, { mult: 0, add:  1 }, { mult: 0, add:  1 }],
          // Test the bound of height
          [{ mult: 0, add:  1 }, { mult: 1, add: -1 }, { mult: 0, add:  1 }],
          [{ mult: 0, add:  1 }, { mult: 1, add:  0 }, { mult: 0, add:  1 }],
          [{ mult: 0, add:  1 }, { mult: 1, add:  1 }, { mult: 0, add:  1 }],
          // Test the bound of array layers
          [{ mult: 0, add:  1 }, { mult: 0, add:  1 }, { mult: 1, add: -1 }],
          [{ mult: 0, add:  1 }, { mult: 0, add:  1 }, { mult: 1, add:  0 }],
          [{ mult: 0, add:  1 }, { mult: 0, add:  1 }, { mult: 1, add:  1 }],
        ]
      )
  )
  .fn(t => {
    const { dimension, format, sizeVariant } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    const size = [
      t.device.limits.maxTextureDimension2D,
      t.device.limits.maxTextureDimension2D,
      t.device.limits.maxTextureArrayLayers,
    ].map((limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx]));

    const descriptor: GPUTextureDescriptor = {
      size,
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const success =
      size[0] <= t.device.limits.maxTextureDimension2D &&
      size[1] <= t.device.limits.maxTextureDimension2D &&
      size[2] <= t.device.limits.maxTextureArrayLayers;

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('texture_size,2d_texture,compressed_format')
  .desc(`Test texture size requirement for 2D texture with compressed format.`)
  .params(u =>
    u
      .combine('dimension', [undefined, '2d'] as const)
      .combine('format', kCompressedTextureFormats)
      .beginSubcases()
      .expand('sizeVariant', p => {
        const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(p.format);
        return [
          // Test the bound of width
          [
            { mult: 1, add: -1 },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: -blockWidth },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: -blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: 0 },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: 0 },
            { mult: 0, add: blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: 1 },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 0, add: 1 },
          ],
          // Test the bound of height
          [
            { mult: 0, add: 1 },
            { mult: 1, add: -1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: -blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 1, add: -blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: 0 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 1, add: 0 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: +1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: +blockWidth },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 1, add: +blockHeight },
            { mult: 0, add: 1 },
          ],
          // Test the bound of array layers
          [
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: blockHeight },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: blockHeight },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
            { mult: 1, add: +1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 1, add: +1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: blockHeight },
            { mult: 1, add: +1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 1, add: +1 },
          ],
        ];
      })
  )
  .fn(t => {
    const { dimension, format, sizeVariant } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    const info = getBlockInfoForTextureFormat(format);
    const size = getMaxValidTextureSizeForFormatAndDimension(t.device, format, '2d').map(
      (limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx])
    );

    const descriptor: GPUTextureDescriptor = {
      size,
      dimension,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const success =
      size[0] % info.blockWidth === 0 &&
      size[1] % info.blockHeight === 0 &&
      size[0] <= t.device.limits.maxTextureDimension2D &&
      size[1] <= t.device.limits.maxTextureDimension2D &&
      size[2] <= t.device.limits.maxTextureArrayLayers;

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('texture_size,3d_texture,uncompressed_format')
  .desc(
    `Test texture size requirement for 3D texture with uncompressed format. Note that depth/stencil formats are invalid for 3D textures, so we only test regular formats.`
  )
  .params(u =>
    u //
      .combine('format', kRegularTextureFormats)
      .beginSubcases()
      .combine(
        'sizeVariant',
        /* prettier-ignore */ [
          // Test the bound of width
          [{ mult: 1, add: -1 }, { mult: 0, add:  1 }, { mult: 0, add:  1 }],
          [{ mult: 1, add:  0 }, { mult: 0, add:  1 }, { mult: 0, add:  1 }],
          [{ mult: 1, add: +1 }, { mult: 0, add:  1 }, { mult: 0, add:  1 }],
          // Test the bound of height
          [{ mult: 0, add:  1 }, { mult: 1, add: -1 }, { mult: 0, add:  1 }],
          [{ mult: 0, add:  1 }, { mult: 1, add:  0 }, { mult: 0, add:  1 }],
          [{ mult: 0, add:  1 }, { mult: 1, add: +1 }, { mult: 0, add:  1 }],
          // Test the bound of depth
          [{ mult: 0, add:  1 }, { mult: 0, add:  1 }, { mult: 1, add: -1 }],
          [{ mult: 0, add:  1 }, { mult: 0, add:  1 }, { mult: 1, add:  0 }],
          [{ mult: 0, add:  1 }, { mult: 0, add:  1 }, { mult: 1, add: +1 }],
        ]
      )
  )
  .fn(t => {
    const { format, sizeVariant } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, '3d');
    const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
    const size = sizeVariant.map(variant => t.makeLimitVariant('maxTextureDimension3D', variant));

    const descriptor: GPUTextureDescriptor = {
      size,
      dimension: '3d' as const,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const success =
      size[0] <= maxTextureDimension3D &&
      size[1] <= maxTextureDimension3D &&
      size[2] <= maxTextureDimension3D;

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('texture_size,3d_texture,compressed_format')
  .desc(`Test texture size requirement for 3D texture with compressed format.`)
  .params(u =>
    u //
      .combine('format', kCompressedTextureFormats)
      .beginSubcases()
      .expand('sizeVariant', p => {
        const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(p.format);
        return [
          // Test the bound of width
          [
            { mult: 1, add: -1 },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: -blockWidth },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: -blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: 0 },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: 0 },
            { mult: 0, add: blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: +1 },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: +blockWidth },
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 1, add: +blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 0, add: 1 },
          ],
          // Test the bound of height
          [
            { mult: 0, add: 1 },
            { mult: 1, add: -1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: -blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 1, add: -blockHeight },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: 0 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 1, add: 0 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: +1 },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 1, add: +blockWidth },
            { mult: 0, add: 1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 1, add: +blockHeight },
            { mult: 0, add: 1 },
          ],
          // Test the bound of depth
          [
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: blockHeight },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 1, add: -1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: blockHeight },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 1, add: 0 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: 1 },
            { mult: 1, add: +1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: 1 },
            { mult: 1, add: +1 },
          ],
          [
            { mult: 0, add: 1 },
            { mult: 0, add: blockHeight },
            { mult: 1, add: +1 },
          ],
          [
            { mult: 0, add: blockWidth },
            { mult: 0, add: blockHeight },
            { mult: 1, add: +1 },
          ],
        ];
      })
  )
  .fn(t => {
    const { format, sizeVariant } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, '3d');
    const maxTextureDimension3D = t.device.limits.maxTextureDimension3D;
    const info = getBlockInfoForTextureFormat(format);
    const size = getMaxValidTextureSizeForFormatAndDimension(t.device, format, '3d').map(
      (limit, ndx) => makeValueTestVariant(limit, sizeVariant[ndx])
    );

    const descriptor: GPUTextureDescriptor = {
      size,
      dimension: '3d' as const,
      format,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    };

    const success =
      size[0] % info.blockWidth === 0 &&
      size[1] % info.blockHeight === 0 &&
      size[0] <= maxTextureDimension3D &&
      size[1] <= maxTextureDimension3D &&
      size[2] <= maxTextureDimension3D &&
      textureDimensionAndFormatCompatibleForDevice(t.device, '3d', format);

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('texture_usage')
  .desc(
    `Test texture usage (single usage or combined usages) for every texture format and every dimension type`
  )
  .params(u =>
    u
      .combine('dimension', [undefined, ...kTextureDimensions])
      .combine('format', kAllTextureFormats)
      .beginSubcases()
      // If usage0 and usage1 are the same, then the usage being test is a single usage. Otherwise, it is a combined usage.
      .combine('usage0', kTextureUsages)
      .combine('usage1', kTextureUsages)
      // Filter out incompatible dimension type and format combinations.
      .filter(({ dimension, format }) =>
        textureFormatAndDimensionPossiblyCompatible(dimension, format)
      )
  )
  .fn(t => {
    const { dimension, format, usage0, usage1 } = t.params;
    t.skipIfTextureFormatNotSupported(format);
    t.skipIfTextureFormatAndDimensionNotCompatible(format, dimension);
    const info = getBlockInfoForTextureFormat(format);

    const size = [info.blockWidth, info.blockHeight, 1];
    const usage = usage0 | usage1;
    const descriptor = {
      size,
      dimension,
      format,
      usage,
    };

    let success = true;
    const appliedDimension = dimension ?? '2d';
    // Note that we unconditionally test copy usages for all formats and
    // expect failure if copying from or to is not supported.
    if (usage & GPUTextureUsage.STORAGE_BINDING) {
      if (!isTextureFormatUsableWithStorageAccessMode(t.device, format, 'write-only'))
        success = false;
    }
    if (usage & GPUTextureUsage.RENDER_ATTACHMENT) {
      if (appliedDimension === '1d') success = false;
      if (isColorTextureFormat(format) && !isTextureFormatColorRenderable(t.device, format))
        success = false;
    }

    t.expectValidationError(() => {
      t.createTextureTracked(descriptor);
    }, !success);
  });

g.test('viewFormats')
  .desc(
    `Test creating a texture with viewFormats list for all {texture format}x{view format}. Only compatible view formats should be valid.`
  )
  .params(u =>
    u
      .combine('formatFeature', kFeaturesForFormats)
      .combine('viewFormatFeature', kFeaturesForFormats)
      .beginSubcases()
      .expand('format', ({ formatFeature }) =>
        filterFormatsByFeature(formatFeature, kAllTextureFormats)
      )
      .expand('viewFormat', ({ viewFormatFeature }) =>
        filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
      )
  )
  .fn(t => {
    const { format, viewFormat } = t.params;
    t.skipIfTextureFormatNotSupported(format, viewFormat);

    const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
    const compatible = textureFormatsAreViewCompatible(t.device, format, viewFormat);

    // Test the viewFormat in the list.
    t.expectValidationError(() => {
      t.createTextureTracked({
        format,
        size: [blockWidth, blockHeight],
        usage: GPUTextureUsage.TEXTURE_BINDING,
        viewFormats: [viewFormat],
      });
    }, !compatible);

    // Test the viewFormat and the texture format in the list.
    t.expectValidationError(() => {
      t.createTextureTracked({
        format,
        size: [blockWidth, blockHeight],
        usage: GPUTextureUsage.TEXTURE_BINDING,
        viewFormats: [viewFormat, format],
      });
    }, !compatible);

    // Test the viewFormat multiple times in the list.
    t.expectValidationError(() => {
      t.createTextureTracked({
        format,
        size: [blockWidth, blockHeight],
        usage: GPUTextureUsage.TEXTURE_BINDING,
        viewFormats: [viewFormat, viewFormat],
      });
    }, !compatible);
  });
