diff options
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts')
-rw-r--r-- | dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts new file mode 100644 index 0000000000..853de3ee6a --- /dev/null +++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/rendering/depth.spec.ts @@ -0,0 +1,549 @@ +export const description = ` +Test related to depth buffer, depth op, compare func, etc. +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { TypedArrayBufferView } from '../../../../common/util/util.js'; +import { kDepthStencilFormats, kTextureFormatInfo } from '../../../capability_info.js'; +import { GPUTest } from '../../../gpu_test.js'; +import { TexelView } from '../../../util/texture/texel_view.js'; +import { textureContentIsOKByT2B } from '../../../util/texture/texture_ok.js'; + +const backgroundColor = [0x00, 0x00, 0x00, 0xff]; +const triangleColor = [0xff, 0xff, 0xff, 0xff]; + +const kBaseColor = new Float32Array([1.0, 1.0, 1.0, 1.0]); +const kRedStencilColor = new Float32Array([1.0, 0.0, 0.0, 1.0]); +const kGreenStencilColor = new Float32Array([0.0, 1.0, 0.0, 1.0]); + +type TestStates = { + state: GPUDepthStencilState; + color: Float32Array; + depth: number; +}; + +class DepthTest extends GPUTest { + runDepthStateTest(testStates: TestStates[], expectedColor: Float32Array) { + const renderTargetFormat = 'rgba8unorm'; + + const renderTarget = this.device.createTexture({ + format: renderTargetFormat, + size: { width: 1, height: 1, depthOrArrayLayers: 1 }, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const depthStencilFormat: GPUTextureFormat = 'depth24plus-stencil8'; + const depthTexture = this.device.createTexture({ + size: { width: 1, height: 1, depthOrArrayLayers: 1 }, + format: depthStencilFormat, + sampleCount: 1, + mipLevelCount: 1, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST, + }); + + const depthStencilAttachment: GPURenderPassDepthStencilAttachment = { + view: depthTexture.createView(), + depthLoadOp: 'load', + depthStoreOp: 'store', + stencilLoadOp: 'load', + stencilStoreOp: 'store', + }; + + const encoder = this.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: renderTarget.createView(), + storeOp: 'store', + loadOp: 'load', + }, + ], + depthStencilAttachment, + }); + + // Draw a triangle with the given depth state, color, and depth. + for (const test of testStates) { + const testPipeline = this.createRenderPipelineForTest(test.state, test.depth); + pass.setPipeline(testPipeline); + pass.setBindGroup( + 0, + this.createBindGroupForTest(testPipeline.getBindGroupLayout(0), test.color) + ); + pass.draw(1); + } + + pass.end(); + this.device.queue.submit([encoder.finish()]); + + const expColor = { + R: expectedColor[0], + G: expectedColor[1], + B: expectedColor[2], + A: expectedColor[3], + }; + const expTexelView = TexelView.fromTexelsAsColors(renderTargetFormat, coords => expColor); + + const result = textureContentIsOKByT2B( + this, + { texture: renderTarget }, + [1, 1], + { expTexelView }, + { maxDiffULPsForNormFormat: 1 } + ); + this.eventualExpectOK(result); + this.trackForCleanup(renderTarget); + } + + createRenderPipelineForTest( + depthStencil: GPUDepthStencilState, + depth: number + ): GPURenderPipeline { + return this.device.createRenderPipeline({ + layout: 'auto', + vertex: { + module: this.device.createShaderModule({ + code: ` + @vertex + fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { + return vec4<f32>(0.0, 0.0, ${depth}, 1.0); + } + `, + }), + entryPoint: 'main', + }, + fragment: { + targets: [{ format: 'rgba8unorm' }], + module: this.device.createShaderModule({ + code: ` + struct Params { + color : vec4<f32> + } + @group(0) @binding(0) var<uniform> params : Params; + + @fragment fn main() -> @location(0) vec4<f32> { + return vec4<f32>(params.color); + }`, + }), + entryPoint: 'main', + }, + primitive: { topology: 'point-list' }, + depthStencil, + }); + } + + createBindGroupForTest(layout: GPUBindGroupLayout, data: TypedArrayBufferView): GPUBindGroup { + return this.device.createBindGroup({ + layout, + entries: [ + { + binding: 0, + resource: { + buffer: this.makeBufferWithContents(data, GPUBufferUsage.UNIFORM), + }, + }, + ], + }); + } +} + +export const g = makeTestGroup(DepthTest); + +g.test('depth_disabled') + .desc('Tests render results with depth test disabled.') + .fn(async t => { + const depthSpencilFormat: GPUTextureFormat = 'depth24plus-stencil8'; + const state = { format: depthSpencilFormat }; + + const testStates = [ + { state, color: kBaseColor, depth: 0.0 }, + { state, color: kRedStencilColor, depth: 0.5 }, + { state, color: kGreenStencilColor, depth: 1.0 }, + ]; + + // Test that for all combinations and ensure the last triangle drawn is the one visible + // regardless of depth testing. + for (let last = 0; last < 3; ++last) { + const i = (last + 1) % 3; + const j = (last + 2) % 3; + + t.runDepthStateTest([testStates[i], testStates[j], testStates[last]], testStates[last].color); + t.runDepthStateTest([testStates[j], testStates[i], testStates[last]], testStates[last].color); + } + }); + +g.test('depth_write_disabled') + .desc( + ` + Test that depthWriteEnabled behaves as expected. + If enabled, a depth value of 0.0 is written. + If disabled, it's not written, so it keeps the previous value of 1.0. + Use a depthCompare: 'equal' check at the end to check the value. + ` + ) + .params(u => + u // + .combineWithParams([ + { depthWriteEnabled: false, lastDepth: 0.0, _expectedColor: kRedStencilColor }, + { depthWriteEnabled: true, lastDepth: 0.0, _expectedColor: kGreenStencilColor }, + { depthWriteEnabled: false, lastDepth: 1.0, _expectedColor: kGreenStencilColor }, + { depthWriteEnabled: true, lastDepth: 1.0, _expectedColor: kRedStencilColor }, + ]) + ) + .fn(async t => { + const { depthWriteEnabled, lastDepth, _expectedColor } = t.params; + + const depthSpencilFormat: GPUTextureFormat = 'depth24plus-stencil8'; + + const stencilState = { + compare: 'always', + failOp: 'keep', + depthFailOp: 'keep', + passOp: 'keep', + } as const; + + const baseState = { + format: depthSpencilFormat, + depthWriteEnabled: true, + depthCompare: 'always', + stencilFront: stencilState, + stencilBack: stencilState, + stencilReadMask: 0xff, + stencilWriteMask: 0xff, + } as const; + + const depthWriteState = { + format: depthSpencilFormat, + depthWriteEnabled, + depthCompare: 'always', + stencilFront: stencilState, + stencilBack: stencilState, + stencilReadMask: 0xff, + stencilWriteMask: 0xff, + } as const; + + const checkState = { + format: depthSpencilFormat, + depthWriteEnabled: false, + depthCompare: 'equal', + stencilFront: stencilState, + stencilBack: stencilState, + stencilReadMask: 0xff, + stencilWriteMask: 0xff, + } as const; + + const testStates = [ + // Draw a base point with depth write enabled. + { state: baseState, color: kBaseColor, depth: 1.0 }, + // Draw a second point without depth write enabled. + { state: depthWriteState, color: kRedStencilColor, depth: 0.0 }, + // Draw a third point which should occlude the second even though it is behind it. + { state: checkState, color: kGreenStencilColor, depth: lastDepth }, + ]; + + t.runDepthStateTest(testStates, _expectedColor); + }); + +g.test('depth_test_fail') + .desc( + ` + Test that render results on depth test failure cases with 'less' depthCompare operation and + depthWriteEnabled is true. + ` + ) + .params(u => + u // + .combineWithParams([ + { secondDepth: 1.0, lastDepth: 2.0, _expectedColor: kBaseColor }, // fail -> fail. + { secondDepth: 0.0, lastDepth: 2.0, _expectedColor: kRedStencilColor }, // pass -> fail. + { secondDepth: 2.0, lastDepth: 0.9, _expectedColor: kGreenStencilColor }, // fail -> pass. + ] as const) + ) + .fn(async t => { + const { secondDepth, lastDepth, _expectedColor } = t.params; + + const depthSpencilFormat: GPUTextureFormat = 'depth24plus-stencil8'; + + const baseState = { + format: depthSpencilFormat, + depthWriteEnabled: true, + depthCompare: 'always', + stencilReadMask: 0xff, + stencilWriteMask: 0xff, + } as const; + + const depthTestState = { + format: depthSpencilFormat, + depthWriteEnabled: true, + depthCompare: 'less', + stencilReadMask: 0xff, + stencilWriteMask: 0xff, + } as const; + + const testStates = [ + { state: baseState, color: kBaseColor, depth: 1.0 }, + { state: depthTestState, color: kRedStencilColor, depth: secondDepth }, + { state: depthTestState, color: kGreenStencilColor, depth: lastDepth }, + ]; + + t.runDepthStateTest(testStates, _expectedColor); + }); + +// Use a depth value that's not exactly 0.5 because it is exactly between two depth16unorm value and +// can get rounded either way (and a different way between shaders and clearDepthValue). +const kMiddleDepthValue = 0.5001; + +g.test('depth_compare_func') + .desc( + `Tests each depth compare function works properly. Clears the depth attachment to various values, and renders a point at depth 0.5 with various depthCompare modes.` + ) + .params(u => + u + .combine( + 'format', + kDepthStencilFormats.filter(format => kTextureFormatInfo[format].depth) + ) + .combineWithParams([ + { depthCompare: 'never', depthClearValue: 1.0, _expected: backgroundColor }, + { depthCompare: 'never', depthClearValue: kMiddleDepthValue, _expected: backgroundColor }, + { depthCompare: 'never', depthClearValue: 0.0, _expected: backgroundColor }, + { depthCompare: 'less', depthClearValue: 1.0, _expected: triangleColor }, + { depthCompare: 'less', depthClearValue: kMiddleDepthValue, _expected: backgroundColor }, + { depthCompare: 'less', depthClearValue: 0.0, _expected: backgroundColor }, + { depthCompare: 'less-equal', depthClearValue: 1.0, _expected: triangleColor }, + { + depthCompare: 'less-equal', + depthClearValue: kMiddleDepthValue, + _expected: triangleColor, + }, + { depthCompare: 'less-equal', depthClearValue: 0.0, _expected: backgroundColor }, + { depthCompare: 'equal', depthClearValue: 1.0, _expected: backgroundColor }, + { depthCompare: 'equal', depthClearValue: kMiddleDepthValue, _expected: triangleColor }, + { depthCompare: 'equal', depthClearValue: 0.0, _expected: backgroundColor }, + { depthCompare: 'not-equal', depthClearValue: 1.0, _expected: triangleColor }, + { + depthCompare: 'not-equal', + depthClearValue: kMiddleDepthValue, + _expected: backgroundColor, + }, + { depthCompare: 'not-equal', depthClearValue: 0.0, _expected: triangleColor }, + { depthCompare: 'greater-equal', depthClearValue: 1.0, _expected: backgroundColor }, + { + depthCompare: 'greater-equal', + depthClearValue: kMiddleDepthValue, + _expected: triangleColor, + }, + { depthCompare: 'greater-equal', depthClearValue: 0.0, _expected: triangleColor }, + { depthCompare: 'greater', depthClearValue: 1.0, _expected: backgroundColor }, + { depthCompare: 'greater', depthClearValue: kMiddleDepthValue, _expected: backgroundColor }, + { depthCompare: 'greater', depthClearValue: 0.0, _expected: triangleColor }, + { depthCompare: 'always', depthClearValue: 1.0, _expected: triangleColor }, + { depthCompare: 'always', depthClearValue: kMiddleDepthValue, _expected: triangleColor }, + { depthCompare: 'always', depthClearValue: 0.0, _expected: triangleColor }, + ] as const) + ) + .beforeAllSubcases(t => { + t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format); + }) + .fn(async t => { + const { depthCompare, depthClearValue, _expected, format } = t.params; + + const colorAttachmentFormat = 'rgba8unorm'; + const colorAttachment = t.device.createTexture({ + format: colorAttachmentFormat, + size: { width: 1, height: 1, depthOrArrayLayers: 1 }, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + const colorAttachmentView = colorAttachment.createView(); + + const depthTexture = t.device.createTexture({ + size: { width: 1, height: 1 }, + format, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + }); + const depthTextureView = depthTexture.createView(); + + const pipelineDescriptor: GPURenderPipelineDescriptor = { + layout: 'auto', + vertex: { + module: t.device.createShaderModule({ + code: ` + @vertex fn main( + @builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> { + return vec4<f32>(0.5, 0.5, ${kMiddleDepthValue}, 1.0); + } + `, + }), + entryPoint: 'main', + }, + fragment: { + module: t.device.createShaderModule({ + code: ` + @fragment fn main() -> @location(0) vec4<f32> { + return vec4<f32>(1.0, 1.0, 1.0, 1.0); + } + `, + }), + entryPoint: 'main', + targets: [{ format: colorAttachmentFormat }], + }, + primitive: { topology: 'point-list' }, + depthStencil: { + depthWriteEnabled: true, + depthCompare, + format, + }, + }; + const pipeline = t.device.createRenderPipeline(pipelineDescriptor); + + const encoder = t.device.createCommandEncoder(); + const depthStencilAttachment: GPURenderPassDepthStencilAttachment = { + view: depthTextureView, + depthClearValue, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }; + if (kTextureFormatInfo[format].stencil) { + depthStencilAttachment.stencilClearValue = 0; + depthStencilAttachment.stencilLoadOp = 'clear'; + depthStencilAttachment.stencilStoreOp = 'store'; + } + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: colorAttachmentView, + storeOp: 'store', + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: 'clear', + }, + ], + depthStencilAttachment, + }); + pass.setPipeline(pipeline); + pass.draw(1); + pass.end(); + t.device.queue.submit([encoder.finish()]); + + t.expectSinglePixelIn2DTexture( + colorAttachment, + colorAttachmentFormat, + { x: 0, y: 0 }, + { exp: new Uint8Array(_expected) } + ); + }); + +g.test('reverse_depth') + .desc( + `Tests simple rendering with reversed depth buffer, ensures depth test works properly: fragments are in correct order and out of range fragments are clipped. + Note that in real use case the depth range remapping is done by the modified projection matrix. +(see https://developer.nvidia.com/content/depth-precision-visualized).` + ) + .params(u => u.combine('reversed', [false, true])) + .fn(async t => { + const colorAttachmentFormat = 'rgba8unorm'; + const colorAttachment = t.device.createTexture({ + format: colorAttachmentFormat, + size: { width: 1, height: 1, depthOrArrayLayers: 1 }, + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + const colorAttachmentView = colorAttachment.createView(); + + const depthBufferFormat = 'depth32float'; + const depthTexture = t.device.createTexture({ + size: { width: 1, height: 1 }, + format: depthBufferFormat, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, + }); + const depthTextureView = depthTexture.createView(); + + const pipelineDescriptor: GPURenderPipelineDescriptor = { + layout: 'auto', + vertex: { + module: t.device.createShaderModule({ + code: ` + struct Output { + @builtin(position) Position : vec4<f32>, + @location(0) color : vec4<f32>, + }; + + @vertex fn main( + @builtin(vertex_index) VertexIndex : u32, + @builtin(instance_index) InstanceIndex : u32) -> Output { + // TODO: remove workaround for Tint unary array access broke + var zv : array<vec2<f32>, 4> = array<vec2<f32>, 4>( + vec2<f32>(0.2, 0.2), + vec2<f32>(0.3, 0.3), + vec2<f32>(-0.1, -0.1), + vec2<f32>(1.1, 1.1)); + let z : f32 = zv[InstanceIndex].x; + + var output : Output; + output.Position = vec4<f32>(0.5, 0.5, z, 1.0); + var colors : array<vec4<f32>, 4> = array<vec4<f32>, 4>( + vec4<f32>(1.0, 0.0, 0.0, 1.0), + vec4<f32>(0.0, 1.0, 0.0, 1.0), + vec4<f32>(0.0, 0.0, 1.0, 1.0), + vec4<f32>(1.0, 1.0, 1.0, 1.0) + ); + output.color = colors[InstanceIndex]; + return output; + } + `, + }), + entryPoint: 'main', + }, + fragment: { + module: t.device.createShaderModule({ + code: ` + @fragment fn main( + @location(0) color : vec4<f32> + ) -> @location(0) vec4<f32> { + return color; + } + `, + }), + entryPoint: 'main', + targets: [{ format: colorAttachmentFormat }], + }, + primitive: { topology: 'point-list' }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: t.params.reversed ? 'greater' : 'less', + format: depthBufferFormat, + }, + }; + const pipeline = t.device.createRenderPipeline(pipelineDescriptor); + + const encoder = t.device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: colorAttachmentView, + storeOp: 'store', + clearValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 }, + loadOp: 'clear', + }, + ], + depthStencilAttachment: { + view: depthTextureView, + + depthClearValue: t.params.reversed ? 0.0 : 1.0, + depthLoadOp: 'clear', + depthStoreOp: 'store', + }, + }); + pass.setPipeline(pipeline); + pass.draw(1, 4); + pass.end(); + t.device.queue.submit([encoder.finish()]); + + t.expectSinglePixelIn2DTexture( + colorAttachment, + colorAttachmentFormat, + { x: 0, y: 0 }, + { + exp: new Uint8Array( + t.params.reversed ? [0x00, 0xff, 0x00, 0xff] : [0xff, 0x00, 0x00, 0xff] + ), + } + ); + }); |