export const description = `
Tests for capability checking for features enabling optional texture formats.
`;

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { getGPU } from '../../../../../common/util/navigator_gpu.js';
import { assert } from '../../../../../common/util/util.js';
import { kAllTextureFormats, kTextureFormatInfo } from '../../../../format_info.js';
import { kAllCanvasTypes, createCanvas } from '../../../../util/create_elements.js';
import { ValidationTest } from '../../validation_test.js';

export const g = makeTestGroup(ValidationTest);

const kOptionalTextureFormats = kAllTextureFormats.filter(
  t => kTextureFormatInfo[t].feature !== undefined
);

g.test('texture_descriptor')
  .desc(
    `
  Test creating a texture with an optional texture format will fail if the required optional feature
  is not enabled.
  `
  )
  .params(u =>
    u.combine('format', kOptionalTextureFormats).combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
      t.device.createTexture({
        format,
        size: [formatInfo.blockWidth, formatInfo.blockHeight, 1] as const,
        usage: GPUTextureUsage.TEXTURE_BINDING,
      });
    });
  });

g.test('texture_descriptor_view_formats')
  .desc(
    `
  Test creating a texture with view formats that have an optional texture format will fail if the
  required optional feature is not enabled.
  `
  )
  .params(u =>
    u.combine('format', kOptionalTextureFormats).combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
      t.device.createTexture({
        format,
        size: [formatInfo.blockWidth, formatInfo.blockHeight, 1] as const,
        usage: GPUTextureUsage.TEXTURE_BINDING,
        viewFormats: [format],
      });
    });
  });

g.test('texture_view_descriptor')
  .desc(
    `
  Test creating a texture view with all texture formats will fail if the required optional feature
  is not enabled.
  `
  )
  .params(u =>
    u.combine('format', kOptionalTextureFormats).combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, enable_required_feature } = t.params;

    // If the required feature isn't enabled then the texture will fail to create and we won't be
    // able to test createView, so pick and alternate guaranteed format instead. This will almost
    // certainly not be view-compatible with the format being tested, but that doesn't matter since
    // createView should throw an exception due to the format feature not being enabled before it
    // has a chance to validate that the view and texture formats aren't compatible.
    const textureFormat = enable_required_feature ? format : 'rgba8unorm';

    const formatInfo = kTextureFormatInfo[format];
    const testTexture = t.device.createTexture({
      format: textureFormat,
      size: [formatInfo.blockWidth, formatInfo.blockHeight, 1] as const,
      usage: GPUTextureUsage.TEXTURE_BINDING,
    });
    const testViewDesc: GPUTextureViewDescriptor = {
      format,
      dimension: '2d',
      aspect: 'all',
      arrayLayerCount: 1,
      baseMipLevel: 0,
      mipLevelCount: 1,
      baseArrayLayer: 0,
    };
    t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
      testTexture.createView(testViewDesc);
    });
  });

g.test('canvas_configuration')
  .desc(
    `
  Test configuring a canvas with optional texture formats will throw an exception if the required
  optional feature is not enabled. Otherwise, a validation error should be generated instead of
  throwing an exception.
  `
  )
  .params(u =>
    u
      .combine('format', kOptionalTextureFormats)
      .combine('canvasType', kAllCanvasTypes)
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, canvasType, enable_required_feature } = t.params;

    const canvas = createCanvas(t, canvasType, 2, 2);
    const ctx = canvas.getContext('webgpu');
    assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');

    const canvasConf = {
      device: t.device,
      format,
      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
    };

    if (enable_required_feature) {
      t.expectValidationError(() => {
        ctx.configure(canvasConf);
      });
    } else {
      t.shouldThrow('TypeError', () => {
        ctx.configure(canvasConf);
      });
    }
  });

g.test('canvas_configuration_view_formats')
  .desc(
    `
  Test that configuring a canvas with view formats throws an exception if the required optional
  feature is not enabled. Otherwise, a validation error should be generated instead of throwing an
  exception.
  `
  )
  .params(u =>
    u
      .combine('viewFormats', [
        ...kOptionalTextureFormats.map(format => [format]),
        ['bgra8unorm', 'bc1-rgba-unorm'],
        ['bc1-rgba-unorm', 'bgra8unorm'],
      ])
      .combine('canvasType', kAllCanvasTypes)
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { viewFormats, enable_required_feature } = t.params;

    if (enable_required_feature) {
      t.selectDeviceForTextureFormatOrSkipTestCase(viewFormats as GPUTextureFormat[]);
    }
  })
  .fn(t => {
    const { viewFormats, canvasType, enable_required_feature } = t.params;

    const canvas = createCanvas(t, canvasType, 2, 2);
    const ctx = canvas.getContext('webgpu');
    assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');

    const canvasConf = {
      device: t.device,
      format: 'bgra8unorm' as const,
      usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
      viewFormats: viewFormats as GPUTextureFormat[],
    };

    if (enable_required_feature) {
      t.expectValidationError(() => {
        ctx.configure(canvasConf);
      });
    } else {
      t.shouldThrow('TypeError', () => {
        ctx.configure(canvasConf);
      });
    }
  });

g.test('storage_texture_binding_layout')
  .desc(
    `
  Test creating a GPUStorageTextureBindingLayout with an optional texture format will fail if the
  required optional feature are not enabled.

  Note: This test has no cases if there are no optional texture formats supporting storage.
  `
  )
  .params(u =>
    u
      .combine('format', kOptionalTextureFormats)
      .filter(t => !!kTextureFormatInfo[t.format].color?.storage)
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, enable_required_feature } = t.params;

    t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
      t.device.createBindGroupLayout({
        entries: [
          {
            binding: 0,
            visibility: GPUShaderStage.COMPUTE,
            storageTexture: {
              format,
            },
          },
        ],
      });
    });
  });

g.test('color_target_state')
  .desc(
    `
  Test creating a render pipeline with an optional texture format set in GPUColorTargetState will
  fail if the required optional feature is not enabled.

  Note: This test has no cases if there are no optional texture formats supporting color rendering.
  `
  )
  .params(u =>
    u
      .combine('isAsync', [false, true])
      .combine('format', kOptionalTextureFormats)
      .filter(t => !!kTextureFormatInfo[t.format].colorRender)
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { isAsync, format, enable_required_feature } = t.params;

    t.doCreateRenderPipelineTest(
      isAsync,
      enable_required_feature,
      {
        layout: 'auto',
        vertex: {
          module: t.device.createShaderModule({
            code: `
              @vertex
              fn main()-> @builtin(position) vec4<f32> {
                return vec4<f32>(0.0, 0.0, 0.0, 1.0);
              }`,
          }),
          entryPoint: 'main',
        },
        fragment: {
          module: t.device.createShaderModule({
            code: `
              @fragment
              fn main() -> @location(0) vec4<f32> {
                return vec4<f32>(0.0, 1.0, 0.0, 1.0);
              }`,
          }),
          entryPoint: 'main',
          targets: [{ format }],
        },
      },
      'TypeError'
    );
  });

g.test('depth_stencil_state')
  .desc(
    `
  Test creating a render pipeline with an optional texture format set in GPUColorTargetState will
  fail if the required optional feature is not enabled.
  `
  )
  .params(u =>
    u
      .combine('isAsync', [false, true])
      .combine('format', kOptionalTextureFormats)
      .filter(t => !!(kTextureFormatInfo[t.format].depth || kTextureFormatInfo[t.format].stencil))
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { isAsync, format, enable_required_feature } = t.params;

    t.doCreateRenderPipelineTest(
      isAsync,
      enable_required_feature,
      {
        layout: 'auto',
        vertex: {
          module: t.device.createShaderModule({
            code: `
              @vertex
              fn main()-> @builtin(position) vec4<f32> {
                return vec4<f32>(0.0, 0.0, 0.0, 1.0);
              }`,
          }),
          entryPoint: 'main',
        },
        depthStencil: {
          format,
          depthCompare: 'always',
          depthWriteEnabled: false,
        },
        fragment: {
          module: t.device.createShaderModule({
            code: `
              @fragment
              fn main() -> @location(0) vec4<f32> {
                return vec4<f32>(0.0, 1.0, 0.0, 1.0);
              }`,
          }),
          entryPoint: 'main',
          targets: [{ format: 'rgba8unorm' }],
        },
      },
      'TypeError'
    );
  });

g.test('render_bundle_encoder_descriptor_color_format')
  .desc(
    `
  Test creating a render bundle encoder with an optional texture format set as one of the color
  format will fail if the required optional feature is not enabled.

  Note: This test has no cases if there are no optional texture formats supporting color rendering.
  `
  )
  .params(u =>
    u
      .combine('format', kOptionalTextureFormats)
      .filter(t => !!kTextureFormatInfo[t.format].colorRender)
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, enable_required_feature } = t.params;

    t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
      t.device.createRenderBundleEncoder({
        colorFormats: [format],
      });
    });
  });

g.test('render_bundle_encoder_descriptor_depth_stencil_format')
  .desc(
    `
  Test creating a render bundle encoder with an optional texture format set as the depth stencil
  format will fail if the required optional feature is not enabled.
  `
  )
  .params(u =>
    u
      .combine('format', kOptionalTextureFormats)
      .filter(t => !!(kTextureFormatInfo[t.format].depth || kTextureFormatInfo[t.format].stencil))
      .combine('enable_required_feature', [true, false])
  )
  .beforeAllSubcases(t => {
    const { format, enable_required_feature } = t.params;

    const formatInfo = kTextureFormatInfo[format];
    if (enable_required_feature) {
      t.selectDeviceOrSkipTestCase(formatInfo.feature);
    }
  })
  .fn(t => {
    const { format, enable_required_feature } = t.params;

    t.shouldThrow(enable_required_feature ? false : 'TypeError', () => {
      t.device.createRenderBundleEncoder({
        colorFormats: ['rgba8unorm'],
        depthStencilFormat: format,
      });
    });
  });

g.test('check_capability_guarantees')
  .desc(
    `check "texture-compression-bc" is supported or both "texture-compression-etc2" and "texture-compression-astc" are supported.`
  )
  .fn(async t => {
    const adapter = await getGPU(t.rec).requestAdapter();
    assert(adapter !== null);

    const features = adapter.features;
    t.expect(
      features.has('texture-compression-bc') ||
        (features.has('texture-compression-etc2') && features.has('texture-compression-astc'))
    );
  });
