export const description = `
Tests using a destroyed buffer on a queue.
`;

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);

g.test('writeBuffer')
  .desc(
    `
Tests that using a destroyed buffer in writeBuffer fails.
- x= {destroyed, not destroyed (control case)}
  `
  )
  .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const))
  .fn(t => {
    const { destroyed } = t.params;
    const buffer = t.createBufferTracked({
      size: 4,
      usage: GPUBufferUsage.COPY_DST,
    });

    if (destroyed) {
      buffer.destroy();
    }

    t.expectValidationError(() => t.queue.writeBuffer(buffer, 0, new Uint8Array(4)), destroyed);
  });

g.test('copyBufferToBuffer')
  .desc(
    `
Tests that using a destroyed buffer in copyBufferToBuffer fails.
- x= {not destroyed (control case), src destroyed, dst destroyed}
  `
  )
  .paramsSubcasesOnly(u => u.combine('destroyed', ['none', 'src', 'dst', 'both'] as const))
  .fn(t => {
    const src = t.createBufferTracked({ size: 4, usage: GPUBufferUsage.COPY_SRC });
    const dst = t.createBufferTracked({ size: 4, usage: GPUBufferUsage.COPY_DST });

    const encoder = t.device.createCommandEncoder();
    encoder.copyBufferToBuffer(src, 0, dst, 0, dst.size);
    const commandBuffer = encoder.finish();

    let shouldError = true;
    switch (t.params.destroyed) {
      case 'none':
        shouldError = false;
        break;
      case 'src':
        src.destroy();
        break;
      case 'dst':
        dst.destroy();
        break;
      case 'both':
        src.destroy();
        dst.destroy();
        break;
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, shouldError);
  });

g.test('copyBufferToTexture')
  .desc(
    `
Tests that using a destroyed buffer in copyBufferToTexture fails.
- x= {not destroyed (control case), src destroyed}
  `
  )
  .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const))
  .fn(t => {
    const { destroyed } = t.params;
    const buffer = t.createBufferTracked({ size: 4, usage: GPUBufferUsage.COPY_SRC });
    const texture = t.createTextureTracked({
      size: [1, 1, 1],
      format: 'rgba8unorm',
      usage: GPUTextureUsage.COPY_DST,
    });

    const encoder = t.device.createCommandEncoder();
    encoder.copyBufferToTexture({ buffer }, { texture }, [1, 1, 1]);
    const commandBuffer = encoder.finish();

    if (destroyed) {
      buffer.destroy();
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, destroyed);
  });

g.test('copyTextureToBuffer')
  .desc(
    `
Tests that using a destroyed buffer in copyTextureToBuffer fails.
- x= {not destroyed (control case), dst destroyed}
  `
  )
  .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const))
  .fn(t => {
    const { destroyed } = t.params;
    const texture = t.createTextureTracked({
      size: [1, 1, 1],
      format: 'rgba8unorm',
      usage: GPUTextureUsage.COPY_SRC,
    });
    const buffer = t.createBufferTracked({ size: 4, usage: GPUBufferUsage.COPY_DST });

    const encoder = t.device.createCommandEncoder();
    encoder.copyTextureToBuffer({ texture }, { buffer }, [1, 1, 1]);
    const commandBuffer = encoder.finish();

    if (destroyed) {
      buffer.destroy();
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, destroyed);
  });

g.test('setBindGroup')
  .desc(
    `
Tests that using a destroyed buffer referenced by a bindGroup set with setBindGroup fails
- x= {not destroyed (control case), destroyed}
    `
  )
  .paramsSubcasesOnly(u =>
    u
      .combine('destroyed', [false, true] as const)
      .combine('encoderType', ['compute pass', 'render pass', 'render bundle'] as const)
  )
  .fn(t => {
    const { destroyed, encoderType } = t.params;
    const { device } = t;
    const buffer = t.createBufferTracked({
      size: 4,
      usage: GPUBufferUsage.UNIFORM,
    });

    const layout = device.createBindGroupLayout({
      entries: [
        {
          binding: 0,
          visibility: GPUShaderStage.COMPUTE | GPUShaderStage.VERTEX,
          buffer: {},
        },
      ],
    });

    const bindGroup = device.createBindGroup({
      layout,
      entries: [{ binding: 0, resource: { buffer } }],
    });

    const { encoder, finish } = t.createEncoder(encoderType);
    encoder.setBindGroup(0, bindGroup);
    const commandBuffer = finish();

    if (destroyed) {
      buffer.destroy();
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, destroyed);
  });

g.test('setVertexBuffer')
  .desc(
    `
Tests that using a destroyed buffer referenced in a render pass fails
- x= {not destroyed (control case), destroyed}
    `
  )
  .paramsSubcasesOnly(u =>
    u
      .combine('destroyed', [false, true] as const)
      .combine('encoderType', ['render pass', 'render bundle'] as const)
  )
  .fn(t => {
    const { destroyed, encoderType } = t.params;
    const vertexBuffer = t.createBufferTracked({
      size: 4,
      usage: GPUBufferUsage.VERTEX,
    });

    const { encoder, finish } = t.createEncoder(encoderType);
    encoder.setVertexBuffer(0, vertexBuffer);
    const commandBuffer = finish();

    if (destroyed) {
      vertexBuffer.destroy();
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, destroyed);
  });

g.test('setIndexBuffer')
  .desc(
    `
Tests that using a destroyed buffer referenced in a render pass fails
- x= {not destroyed (control case), destroyed}
    `
  )
  .paramsSubcasesOnly(u =>
    u
      .combine('destroyed', [false, true] as const)
      .combine('encoderType', ['render pass', 'render bundle'] as const)
  )
  .fn(t => {
    const { destroyed, encoderType } = t.params;
    const indexBuffer = t.createBufferTracked({
      size: 4,
      usage: GPUBufferUsage.INDEX,
    });

    const { encoder, finish } = t.createEncoder(encoderType);
    encoder.setIndexBuffer(indexBuffer, 'uint16');
    const commandBuffer = finish();

    if (destroyed) {
      indexBuffer.destroy();
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, destroyed);
  });

g.test('resolveQuerySet')
  .desc(
    `
Tests that using a destroyed buffer referenced via resolveQuerySet fails
- x= {not destroyed (control case), destroyed}
    `
  )
  .paramsSubcasesOnly(u => u.combine('destroyed', [false, true] as const))
  .fn(t => {
    const { destroyed } = t.params;
    const querySet = t.createQuerySetTracked({
      type: 'occlusion',
      count: 1,
    });
    const querySetBuffer = t.createBufferTracked({
      size: 8,
      usage: GPUBufferUsage.QUERY_RESOLVE,
    });

    const encoder = t.device.createCommandEncoder();
    encoder.resolveQuerySet(querySet, 0, 1, querySetBuffer, 0);
    const commandBuffer = encoder.finish();

    if (destroyed) {
      querySetBuffer.destroy();
    }

    t.expectValidationError(() => {
      t.queue.submit([commandBuffer]);
    }, destroyed);
  });
