export const description = `Test vertex shader builtin variables

* test builtin(clip_distances)
`;

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

class VertexBuiltinTest extends AllFeaturesMaxLimitsGPUTest {}

export const g = makeTestGroup(VertexBuiltinTest);

g.test('outputs,clip_distances')
  .desc(
    `
    Test vertex shader builtin(clip_distances) values.

    In the tests, we draw a square with two triangles (top-right and bottom left), whose vertices
    have different clip distances values. (Top Left: -1, Bottom Right: 1 Top Right & Bottom Left: 0)
    1. The clip distances values of the pixels in the top-left region should be less than 0 so these
       pixels will all be invisible
    2. The clip distances values of the pixels on the top-right-to-bottom-left diagonal line should
       be equal to 0
    3. The clip distances values of the pixels in the bottom-right region should be greater than 0

    -1 - - - - - 0
     | \\      x x
     |   \\  x x x
     |    \\ x x x
     |   x x\\ x x
     | x x x x\\ x
     0 x x x x x 1
  `
  )
  .params(u => u.combine('clipDistances', [1, 2, 3, 4, 5, 6, 7, 8] as const))
  .fn(t => {
    t.skipIfDeviceDoesNotHaveFeature('clip-distances');
    const { clipDistances } = t.params;

    // Draw two triangles (top-right and bottom left) into Red, whose vertices have different clip
    // distances values. (Top Left: -1, Bottom Right: 1 Top Right & Bottom Left: 0)
    const code = `
    enable clip_distances;
    const kClipDistancesSize = ${clipDistances};
    struct VertexOutputs {
        @builtin(position) position : vec4f,
        @builtin(clip_distances) clipDistances : array<f32, kClipDistancesSize>,
    }
    @vertex
    fn vsMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutputs {
          var posAndClipDistances = array(
              vec3f(-1.0,  1.0, -1.0),
              vec3f( 1.0, -1.0,  1.0),
              vec3f( 1.0,  1.0,  0.0),
              vec3f(-1.0, -1.0,  0.0),
              vec3f( 1.0, -1.0,  1.0),
              vec3f(-1.0,  1.0, -1.0));
          var vertexOutput : VertexOutputs;
          vertexOutput.position = vec4f(posAndClipDistances[vertexIndex].xy, 0.0, 1.0);
          vertexOutput.clipDistances[kClipDistancesSize - 1] = posAndClipDistances[vertexIndex].z;
          return vertexOutput;
    }
    @fragment
    fn fsMain() -> @location(0) vec4f {
        return vec4f(1.0, 0.0, 0.0, 1.0);
    }`;
    const module = t.device.createShaderModule({ code });
    const renderPipeline = t.device.createRenderPipeline({
      layout: 'auto',
      vertex: {
        module,
      },
      fragment: {
        module,
        targets: [
          {
            format: 'rgba8unorm',
          },
        ],
      },
    });

    const kSize = 7;
    const outputTexture = t.createTextureTracked({
      format: 'rgba8unorm',
      size: [kSize, kSize, 1] as const,
      usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
    });

    // Clear outputTexture to Green
    const commandEncoder = t.device.createCommandEncoder();
    const renderPassEncoder = commandEncoder.beginRenderPass({
      colorAttachments: [
        {
          view: outputTexture.createView(),
          loadOp: 'clear',
          clearValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
          storeOp: 'store',
        },
      ],
    });
    renderPassEncoder.setPipeline(renderPipeline);
    renderPassEncoder.draw(6);
    renderPassEncoder.end();

    const kBytesPerRow = 256;
    const kBytesPerPixel = 4;
    const outputDataSize = kBytesPerRow * (kSize - 1) + kSize * kBytesPerPixel;
    const outputBuffer = t.createBufferTracked({
      size: outputDataSize,
      usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
    });

    commandEncoder.copyTextureToBuffer(
      {
        texture: outputTexture,
      },
      {
        buffer: outputBuffer,
        bytesPerRow: kBytesPerRow,
        rowsPerImage: kSize,
      },
      [kSize, kSize, 1]
    );
    t.queue.submit([commandEncoder.finish()]);

    // The top-left part should be Green and the bottom-right part should be Red
    const expectedData = new Uint8Array(outputDataSize);
    for (let y = 0; y < kSize; ++y) {
      const baseOffset = kBytesPerRow * y;
      for (let x = 0; x < kSize; ++x) {
        const lastRed = kSize - y - 1;
        for (let i = 0; i < lastRed; ++i) {
          expectedData[baseOffset + i * 4] = 0;
          expectedData[baseOffset + i * 4 + 1] = 255;
          expectedData[baseOffset + i * 4 + 2] = 0;
          expectedData[baseOffset + i * 4 + 3] = 255;
        }
        for (let j = lastRed; j < kSize; ++j) {
          expectedData[baseOffset + j * 4] = 255;
          expectedData[baseOffset + j * 4 + 1] = 0;
          expectedData[baseOffset + j * 4 + 2] = 0;
          expectedData[baseOffset + j * 4 + 3] = 255;
        }
      }
    }
    t.expectGPUBufferValuesEqual(outputBuffer, expectedData);
  });
