/**
* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
**/export const description = `
Tests for depth clipping, depth clamping (at various points in the pipeline), and maybe extended
depth ranges as well.
`;import { makeTestGroup } from '../../../../common/framework/test_group.js';
import {
  canCopyFromAspectOfTextureFormat,
  getBlockInfoForTextureFormat,
  isStencilTextureFormat,
  kDepthTextureFormats } from
'../../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js';
import {
  checkElementsBetween,
  checkElementsPassPredicate } from

'../../../util/check_contents.js';

export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);

g.test('depth_clamp_and_clip').
desc(
  `
Depth written to the depth attachment should always be in the range of the viewport depth,
even if it was written by the fragment shader (using frag_depth). If depth clipping is enabled,
primitives should be clipped to the viewport depth before rasterization; if not, these fragments
should be rasterized, and the fragment shader should receive out-of-viewport position.z values.

To test this, render NxN points, with N vertex depth values, by (if writeDepth=true) N
frag_depth values with the viewport depth set to [0.25,0.75].

While rendering, check the fragment input position.z has the expected value (for all fragments that
were produced by the rasterizer) by writing the diff to a storage buffer, which is later checked to
be all (near) 0.

Then, run another pass (which outputs every point at z=0.5 to avoid clipping) to verify the depth
buffer contents by outputting the expected depth with depthCompare:'not-equal': any fragments that
have unexpected values then get drawn to the color buffer, which is later checked to be empty.`
).
params((u) =>
u //
.combine('format', kDepthTextureFormats).
combine('unclippedDepth', [undefined, false, true]).
combine('writeDepth', [false, true]).
combine('multisampled', [false, true])
).
fn(async (t) => {
  const { format, unclippedDepth, writeDepth, multisampled } = t.params;
  t.skipIfTextureFormatNotSupported(format);
  if (unclippedDepth) {
    t.skipIfDeviceDoesNotHaveFeature('depth-clip-control');
  }

  /** Number of depth values to test for both vertex output and frag_depth output. */
  const kNumDepthValues = 8;
  /** Test every combination of vertex output and frag_depth output. */
  const kNumTestPoints = kNumDepthValues * kNumDepthValues;
  const kViewportMinDepth = 0.25;
  const kViewportMaxDepth = 0.75;

  const shaderSource = `
      // Test depths, with viewport range corresponding to [0,1].
      var<private> kDepths: array<f32, ${kNumDepthValues}> = array<f32, ${kNumDepthValues}>(
          -1.0, -0.5, 0.0, 0.25, 0.75, 1.0, 1.5, 2.0);

      const vpMin: f32 = ${kViewportMinDepth};
      const vpMax: f32 = ${kViewportMaxDepth};

      // Draw the points in a straight horizontal row, one per pixel.
      fn vertexX(idx: u32) -> f32 {
        return (f32(idx) + 0.5) * 2.0 / ${kNumTestPoints}.0 - 1.0;
      }

      // Test vertex shader's position.z output.
      // Here, the viewport range corresponds to position.z in [0,1].
      fn vertexZ(idx: u32) -> f32 {
        return kDepths[idx / ${kNumDepthValues}u];
      }

      // Test fragment shader's expected position.z input.
      // Here, the viewport range corresponds to position.z in [vpMin,vpMax], but
      // unclipped values extend beyond that range.
      fn expectedFragPosZ(idx: u32) -> f32 {
        return vpMin + vertexZ(idx) * (vpMax - vpMin);
      }

      //////// "Test" entry points

      struct VFTest {
        @builtin(position) pos: vec4<f32>,
        @location(0) @interpolate(flat, either) vertexIndex: u32,
      };

      @vertex
      fn vtest(@builtin(vertex_index) idx: u32) -> VFTest {
        var vf: VFTest;
        vf.pos = vec4<f32>(vertexX(idx), 0.0, vertexZ(idx), 1.0);
        vf.vertexIndex = idx;
        return vf;
      }

      struct Output {
        // Each fragment (that didn't get clipped) writes into one element of this output.
        // (Anything that doesn't get written is already zero.)
        fragInputZDiff: array<f32, ${kNumTestPoints}>
      };
      @group(0) @binding(0) var <storage, read_write> output: Output;

      fn checkZ(vf: VFTest) {
        output.fragInputZDiff[vf.vertexIndex] = vf.pos.z - expectedFragPosZ(vf.vertexIndex);
      }

      @fragment
      fn ftest_WriteDepth(vf: VFTest) -> @builtin(frag_depth) f32 {
        checkZ(vf);
        return kDepths[vf.vertexIndex % ${kNumDepthValues}u];
      }

      @fragment
      fn ftest_NoWriteDepth(vf: VFTest) {
        checkZ(vf);
      }

      //////// "Check" entry points

      struct VFCheck {
        @builtin(position) pos: vec4<f32>,
        @location(0) @interpolate(flat, either) vertexIndex: u32,
      };

      @vertex
      fn vcheck(@builtin(vertex_index) idx: u32) -> VFCheck {
        var vf: VFCheck;
        // Depth=0.5 because we want to render every point, not get clipped.
        vf.pos = vec4<f32>(vertexX(idx), 0.0, 0.5, 1.0);
        vf.vertexIndex = idx;
        return vf;
      }

      struct FCheck {
        @location(0) color: f32,
      };

      ${
  multisampled ?
  '@group(0) @binding(0) var depthTex: texture_multisampled_2d<f32>;' :
  '@group(0) @binding(0) var depthTex: texture_2d<f32>;'
  }

      @fragment
      fn fcheck(vf: VFCheck) -> FCheck {
        let vertZ = vertexZ(vf.vertexIndex);
        let outOfRange = vertZ < 0.0 || vertZ > 1.0;
        let expFragPosZ = expectedFragPosZ(vf.vertexIndex);

        let writtenDepth = kDepths[vf.vertexIndex % ${kNumDepthValues}u];

        let expectedDepthWriteInput = ${writeDepth ? 'writtenDepth' : 'expFragPosZ'};
        var expectedDepthBufferValue = clamp(expectedDepthWriteInput, vpMin, vpMax);
        if (${!unclippedDepth} && outOfRange) {
          // Test fragment should have been clipped; expect the depth attachment to
          // have its clear value (0.5).
          expectedDepthBufferValue = 0.5;
        }

        let actualDepthBufferValue = textureLoad(depthTex, vec2u(vf.vertexIndex, 0), 0).r;
        let actualVsExpectedDiff = abs(expectedDepthBufferValue - actualDepthBufferValue);
        var f: FCheck;
        f.color = 1.0; // Color written if the resulting depth is unexpected.
        if (actualVsExpectedDiff < 1e-5) {
          f.color = 0.0;
        }
        return f;
      }
    `;
  const module = t.device.createShaderModule({ code: shaderSource });

  // Draw points at different vertex depths and fragment depths into the depth attachment,
  // with a viewport of [0.25,0.75].
  const testPipeline = t.device.createRenderPipeline({
    label: 'testPipeline',
    layout: 'auto',
    vertex: { module, entryPoint: 'vtest' },
    primitive: {
      topology: 'point-list',
      unclippedDepth
    },
    depthStencil: { format, depthWriteEnabled: true, depthCompare: 'always' },
    multisample: multisampled ? { count: 4 } : undefined,
    fragment: {
      module,
      entryPoint: writeDepth ? 'ftest_WriteDepth' : 'ftest_NoWriteDepth',
      targets: []
    }
  });

  const checkBindGroupLayout = t.device.createBindGroupLayout({
    entries: [
    {
      binding: 0,
      visibility: GPUShaderStage.FRAGMENT,
      texture: {
        sampleType: 'unfilterable-float',
        multisampled
      }
    }]

  });

  const checkPipelineLayout = t.device.createPipelineLayout({
    bindGroupLayouts: [checkBindGroupLayout]
  });

  // Read the depth values and output 0 if they match expected, 1 if they don't
  const checkPipeline = t.device.createRenderPipeline({
    label: 'checkPipeline',
    layout: checkPipelineLayout,
    vertex: { module, entryPoint: 'vcheck' },
    primitive: { topology: 'point-list' },
    multisample: multisampled ? { count: 4 } : undefined,
    fragment: { module, entryPoint: 'fcheck', targets: [{ format: 'r8unorm' }] }
  });

  const dsTexture = t.createTextureTracked({
    format,
    size: [kNumTestPoints],
    usage:
    GPUTextureUsage.RENDER_ATTACHMENT |
    GPUTextureUsage.TEXTURE_BINDING |
    GPUTextureUsage.COPY_SRC,
    sampleCount: multisampled ? 4 : 1
  });
  const dsTextureView = dsTexture.createView();

  const checkTextureDesc = {
    format: 'r8unorm',
    size: [kNumTestPoints],
    usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
  };
  const checkTexture = t.createTextureTracked(checkTextureDesc);
  const checkTextureView = checkTexture.createView();
  const checkTextureMSView = multisampled ?
  t.createTextureTracked({ ...checkTextureDesc, sampleCount: 4 }).createView() :
  undefined;

  const { bytesPerBlock } = getBlockInfoForTextureFormat(format);
  const dsActual =
  canCopyFromAspectOfTextureFormat(format, 'depth-only') && !multisampled && bytesPerBlock ?
  t.createBufferTracked({
    size: kNumTestPoints * bytesPerBlock,
    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
  }) :
  undefined;
  const dsExpected =
  canCopyFromAspectOfTextureFormat(format, 'depth-only') && !multisampled && bytesPerBlock ?
  t.createBufferTracked({
    size: kNumTestPoints * bytesPerBlock,
    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
  }) :
  undefined;
  const checkBuffer = t.createBufferTracked({
    size: kNumTestPoints,
    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
  });

  const fragInputZFailedBuffer = t.createBufferTracked({
    size: 4 * kNumTestPoints,
    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
  });
  const testBindGroup = t.device.createBindGroup({
    layout: testPipeline.getBindGroupLayout(0),
    entries: [{ binding: 0, resource: { buffer: fragInputZFailedBuffer } }]
  });

  const enc = t.device.createCommandEncoder();
  {
    const pass = enc.beginRenderPass({
      colorAttachments: [],
      depthStencilAttachment: {
        view: dsTextureView,
        depthClearValue: 0.5, // Will see this depth value if the fragment was clipped.
        depthLoadOp: 'clear',
        depthStoreOp: 'store',
        ...(isStencilTextureFormat(format) && {
          stencilClearValue: 0,
          stencilLoadOp: 'clear',
          stencilStoreOp: 'discard'
        })
      }
    });
    pass.setPipeline(testPipeline);
    pass.setBindGroup(0, testBindGroup);
    pass.setViewport(0, 0, kNumTestPoints, 1, kViewportMinDepth, kViewportMaxDepth);
    pass.draw(kNumTestPoints);
    pass.end();
  }
  if (dsActual) {
    enc.copyTextureToBuffer({ texture: dsTexture, aspect: 'depth-only' }, { buffer: dsActual }, [
    kNumTestPoints]
    );
  }
  {
    const checkBindGroup = t.device.createBindGroup({
      layout: checkBindGroupLayout,
      entries: [{ binding: 0, resource: dsTexture.createView({ aspect: 'depth-only' }) }]
    });
    const clearValue = [0.5, 0.5, 0.5, 0.5]; // We should only see 0.0 or 1.0
    const pass = enc.beginRenderPass({
      colorAttachments: [
      checkTextureMSView ?
      {
        view: checkTextureMSView,
        resolveTarget: checkTextureView,
        clearValue,
        loadOp: 'clear',
        storeOp: 'discard'
      } :
      { view: checkTextureView, clearValue, loadOp: 'clear', storeOp: 'store' }]

    });
    pass.setPipeline(checkPipeline);
    pass.setBindGroup(0, checkBindGroup);
    pass.setViewport(0, 0, kNumTestPoints, 1, 0.0, 1.0);
    pass.draw(kNumTestPoints);
    pass.end();
  }
  enc.copyTextureToBuffer({ texture: checkTexture }, { buffer: checkBuffer }, [kNumTestPoints]);
  if (dsExpected) {
    enc.copyTextureToBuffer(
      { texture: dsTexture, aspect: 'depth-only' },
      { buffer: dsExpected },
      [kNumTestPoints]
    );
  }
  t.device.queue.submit([enc.finish()]);

  t.expectGPUBufferValuesPassCheck(
    fragInputZFailedBuffer,
    (a) => checkElementsBetween(a, [() => -1e-5, () => 1e-5]),
    { type: Float32Array, typedLength: kNumTestPoints }
  );

  const kCheckPassedValue = 0;
  const predicatePrinter = [
  { leftHeader: 'expected ==', getValueForCell: (_index) => kCheckPassedValue }];

  if (dsActual && dsExpected && format === 'depth32float') {
    await Promise.all([dsActual.mapAsync(GPUMapMode.READ), dsExpected.mapAsync(GPUMapMode.READ)]);
    const act = new Float32Array(dsActual.getMappedRange());
    const exp = new Float32Array(dsExpected.getMappedRange());
    predicatePrinter.push(
      { leftHeader: 'act ==', getValueForCell: (index) => act[index].toFixed(2) },
      { leftHeader: 'exp ==', getValueForCell: (index) => exp[index].toFixed(2) }
    );
  }
  t.expectGPUBufferValuesPassCheck(
    checkBuffer,
    (a) =>
    checkElementsPassPredicate(a, (_index, value) => value === kCheckPassedValue, {
      predicatePrinter
    }),
    { type: Uint8Array, typedLength: kNumTestPoints, method: 'map' }
  );
});

g.test('depth_test_input_clamped').
desc(
  `
Input to the depth test should always be in the range of viewport depth, even if it was written by
the fragment shader (using frag_depth).

To test this, first initialize the depth buffer with N expected values (by writing frag_depth, with
the default viewport). These expected values are clamped by the shader to [0.25, 0.75].

Then, run another pass with the viewport depth set to [0.25,0.75], and output various (unclamped)
frag_depth values from its fragment shader with depthCompare:'not-equal'. These should get clamped;
any fragments that have unexpected values then get drawn to the color buffer, which is later checked
to be empty.`
).
params((u) =>
u //
.combine('format', kDepthTextureFormats).
combine('unclippedDepth', [false, true]).
combine('multisampled', [false, true])
).
fn((t) => {
  const { format, unclippedDepth, multisampled } = t.params;
  t.skipIfTextureFormatNotSupported(format);
  if (unclippedDepth) {
    t.skipIfDeviceDoesNotHaveFeature('depth-clip-control');
  }

  const kNumDepthValues = 8;
  const kViewportMinDepth = 0.25;
  const kViewportMaxDepth = 0.75;

  const shaderSource = `
      // Test depths, with viewport range corresponding to [0,1].
      var<private> kDepths: array<f32, ${kNumDepthValues}> = array<f32, ${kNumDepthValues}>(
          -1.0, -0.5, 0.0, 0.25, 0.75, 1.0, 1.5, 2.0);

      const vpMin: f32 = ${kViewportMinDepth};
      const vpMax: f32 = ${kViewportMaxDepth};

      // Draw the points in a straight horizontal row, one per pixel.
      fn vertexX(idx: u32) -> f32 {
        return (f32(idx) + 0.5) * 2.0 / ${kNumDepthValues}.0 - 1.0;
      }

      struct VF {
        @builtin(position) pos: vec4<f32>,
        @location(0) @interpolate(flat, either) vertexIndex: u32,
      };

      @vertex
      fn vmain(@builtin(vertex_index) idx: u32) -> VF {
        var vf: VF;
        // Depth=0.5 because we want to render every point, not get clipped.
        vf.pos = vec4<f32>(vertexX(idx), 0.0, 0.5, 1.0);
        vf.vertexIndex = idx;
        return vf;
      }

      @fragment
      fn finit(vf: VF) -> @builtin(frag_depth) f32 {
        // Expected values of the ftest pipeline.
        return clamp(kDepths[vf.vertexIndex], vpMin, vpMax);
      }

      struct FTest {
        @builtin(frag_depth) depth: f32,
        @location(0) color: f32,
      };

      @fragment
      fn ftest(vf: VF) -> FTest {
        var f: FTest;
        f.depth = kDepths[vf.vertexIndex]; // Should get clamped to the viewport.
        f.color = 1.0; // Color written if the resulting depth is unexpected.
        return f;
      }
    `;

  const module = t.device.createShaderModule({ code: shaderSource });

  // Initialize depth attachment with expected values, in [0.25,0.75].
  const initPipeline = t.device.createRenderPipeline({
    layout: 'auto',
    vertex: { module, entryPoint: 'vmain' },
    primitive: { topology: 'point-list' },
    depthStencil: { format, depthWriteEnabled: true, depthCompare: 'always' },
    multisample: multisampled ? { count: 4 } : undefined,
    fragment: { module, entryPoint: 'finit', targets: [] }
  });

  // With a viewport set to [0.25,0.75], output values in [0.0,1.0] and check they're clamped
  // before the depth test, regardless of whether unclippedDepth is enabled.
  const testPipeline = t.device.createRenderPipeline({
    layout: 'auto',
    vertex: { module, entryPoint: 'vmain' },
    primitive: {
      topology: 'point-list',
      unclippedDepth
    },
    depthStencil: { format, depthCompare: 'not-equal', depthWriteEnabled: false },
    multisample: multisampled ? { count: 4 } : undefined,
    fragment: { module, entryPoint: 'ftest', targets: [{ format: 'r8unorm' }] }
  });

  const dsTexture = t.createTextureTracked({
    format,
    size: [kNumDepthValues],
    usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
    sampleCount: multisampled ? 4 : 1
  });
  const dsTextureView = dsTexture.createView();

  const testTextureDesc = {
    format: 'r8unorm',
    size: [kNumDepthValues],
    usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
  };
  const testTexture = t.createTextureTracked(testTextureDesc);
  const testTextureView = testTexture.createView();
  const testTextureMSView = multisampled ?
  t.createTextureTracked({ ...testTextureDesc, sampleCount: 4 }).createView() :
  undefined;

  const resultBuffer = t.createBufferTracked({
    size: kNumDepthValues,
    usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
  });

  const enc = t.device.createCommandEncoder();
  {
    const pass = enc.beginRenderPass({
      colorAttachments: [],
      depthStencilAttachment: {
        view: dsTextureView,
        depthClearValue: 1.0,
        depthLoadOp: 'clear',
        depthStoreOp: 'store',
        ...(isStencilTextureFormat(format) && {
          stencilClearValue: 0,
          stencilLoadOp: 'clear',
          stencilStoreOp: 'discard'
        })
      }
    });
    pass.setPipeline(initPipeline);
    pass.draw(kNumDepthValues);
    pass.end();
  }
  {
    const clearValue = [0, 0, 0, 0]; // Will see this color if the test passed.
    const pass = enc.beginRenderPass({
      colorAttachments: [
      testTextureMSView ?
      {
        view: testTextureMSView,
        resolveTarget: testTextureView,
        clearValue,
        loadOp: 'clear',
        storeOp: 'discard'
      } :
      { view: testTextureView, clearValue, loadOp: 'clear', storeOp: 'store' }],

      depthStencilAttachment: {
        view: dsTextureView,
        depthLoadOp: 'load',
        depthStoreOp: 'store',
        ...(isStencilTextureFormat(format) && {
          stencilClearValue: 0,
          stencilLoadOp: 'clear',
          stencilStoreOp: 'discard'
        })
      }
    });
    pass.setPipeline(testPipeline);
    pass.setViewport(0, 0, kNumDepthValues, 1, kViewportMinDepth, kViewportMaxDepth);
    pass.draw(kNumDepthValues);
    pass.end();
  }
  enc.copyTextureToBuffer({ texture: testTexture }, { buffer: resultBuffer }, [kNumDepthValues]);
  t.device.queue.submit([enc.finish()]);

  t.expectGPUBufferValuesEqual(resultBuffer, new Uint8Array(kNumDepthValues), 0, {
    method: 'map'
  });
});