2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2023-12-08 19:19:16 -05:00
2024-01-21 15:51:45 -05:00
import { assert , assertEquals , assertThrows } from "./test_util.ts" ;
2023-12-08 19:19:16 -05:00
let isCI : boolean ;
try {
isCI = ( Deno . env . get ( "CI" ) ? . length ? ? 0 ) > 0 ;
} catch {
isCI = true ;
}
// Skip these tests on linux CI, because the vulkan emulator is not good enough
2024-09-18 08:08:08 -04:00
// yet, and skip on macOS x86 CI because these do not have virtual GPUs.
const isCIWithoutGPU = ( Deno . build . os === "linux" ||
( Deno . build . os === "darwin" && Deno . build . arch === "x86_64" ) ) && isCI ;
2023-12-08 19:19:16 -05:00
// Skip these tests in WSL because it doesn't have good GPU support.
const isWsl = await checkIsWsl ( ) ;
Deno . test ( {
permissions : { read : true , env : true } ,
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2023-12-08 19:19:16 -05:00
} , async function webgpuComputePass() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const numbers = [ 1 , 4 , 3 , 295 ] ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
const shaderCode = await Deno . readTextFile (
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
"tests/testdata/webgpu/computepass_shader.wgsl" ,
2023-12-08 19:19:16 -05:00
) ;
const shaderModule = device . createShaderModule ( {
code : shaderCode ,
} ) ;
const size = new Uint32Array ( numbers ) . byteLength ;
const stagingBuffer = device . createBuffer ( {
size : size ,
usage : GPUBufferUsage.MAP_READ | GPUBufferUsage . COPY_DST ,
} ) ;
const storageBuffer = device . createBuffer ( {
label : "Storage Buffer" ,
size : size ,
usage : GPUBufferUsage.STORAGE | GPUBufferUsage . COPY_DST |
GPUBufferUsage . COPY_SRC ,
mappedAtCreation : true ,
} ) ;
const buf = new Uint32Array ( storageBuffer . getMappedRange ( ) ) ;
buf . set ( numbers ) ;
storageBuffer . unmap ( ) ;
const computePipeline = device . createComputePipeline ( {
layout : "auto" ,
compute : {
module : shaderModule ,
entryPoint : "main" ,
} ,
} ) ;
const bindGroupLayout = computePipeline . getBindGroupLayout ( 0 ) ;
const bindGroup = device . createBindGroup ( {
layout : bindGroupLayout ,
entries : [
{
binding : 0 ,
resource : {
buffer : storageBuffer ,
} ,
} ,
] ,
} ) ;
const encoder = device . createCommandEncoder ( ) ;
const computePass = encoder . beginComputePass ( ) ;
computePass . setPipeline ( computePipeline ) ;
computePass . setBindGroup ( 0 , bindGroup ) ;
computePass . insertDebugMarker ( "compute collatz iterations" ) ;
computePass . dispatchWorkgroups ( numbers . length ) ;
computePass . end ( ) ;
encoder . copyBufferToBuffer ( storageBuffer , 0 , stagingBuffer , 0 , size ) ;
device . queue . submit ( [ encoder . finish ( ) ] ) ;
await stagingBuffer . mapAsync ( 1 ) ;
const data = stagingBuffer . getMappedRange ( ) ;
assertEquals ( new Uint32Array ( data ) , new Uint32Array ( [ 0 , 2 , 7 , 55 ] ) ) ;
stagingBuffer . unmap ( ) ;
device . destroy ( ) ;
} ) ;
Deno . test ( {
permissions : { read : true , env : true } ,
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2023-12-08 19:19:16 -05:00
} , async function webgpuHelloTriangle() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
const shaderCode = await Deno . readTextFile (
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
"tests/testdata/webgpu/hellotriangle_shader.wgsl" ,
2023-12-08 19:19:16 -05:00
) ;
const shaderModule = device . createShaderModule ( {
code : shaderCode ,
} ) ;
const pipelineLayout = device . createPipelineLayout ( {
bindGroupLayouts : [ ] ,
} ) ;
const renderPipeline = device . createRenderPipeline ( {
layout : pipelineLayout ,
vertex : {
module : shaderModule ,
entryPoint : "vs_main" ,
2024-08-01 14:19:25 -04:00
// only test purpose
constants : {
value : 0.5 ,
} ,
2023-12-08 19:19:16 -05:00
} ,
fragment : {
module : shaderModule ,
entryPoint : "fs_main" ,
2024-08-01 14:19:25 -04:00
// only test purpose
constants : {
value : 0.5 ,
} ,
2023-12-08 19:19:16 -05:00
targets : [
{
format : "rgba8unorm-srgb" ,
} ,
] ,
} ,
} ) ;
const dimensions = {
width : 200 ,
height : 200 ,
} ;
const unpaddedBytesPerRow = dimensions . width * 4 ;
const align = 256 ;
const paddedBytesPerRowPadding = ( align - unpaddedBytesPerRow % align ) %
align ;
const paddedBytesPerRow = unpaddedBytesPerRow + paddedBytesPerRowPadding ;
const outputBuffer = device . createBuffer ( {
label : "Capture" ,
size : paddedBytesPerRow * dimensions . height ,
usage : GPUBufferUsage.MAP_READ | GPUBufferUsage . COPY_DST ,
} ) ;
const texture = device . createTexture ( {
label : "Capture" ,
size : dimensions ,
format : "rgba8unorm-srgb" ,
usage : GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage . COPY_SRC ,
} ) ;
const encoder = device . createCommandEncoder ( ) ;
const view = texture . createView ( ) ;
const renderPass = encoder . beginRenderPass ( {
colorAttachments : [
{
view ,
storeOp : "store" ,
loadOp : "clear" ,
clearValue : [ 0 , 1 , 0 , 1 ] ,
} ,
] ,
} ) ;
renderPass . setPipeline ( renderPipeline ) ;
renderPass . draw ( 3 , 1 ) ;
renderPass . end ( ) ;
encoder . copyTextureToBuffer (
{
texture ,
} ,
{
buffer : outputBuffer ,
bytesPerRow : paddedBytesPerRow ,
rowsPerImage : 0 ,
} ,
dimensions ,
) ;
const bundle = encoder . finish ( ) ;
device . queue . submit ( [ bundle ] ) ;
await outputBuffer . mapAsync ( 1 ) ;
const data = new Uint8Array ( outputBuffer . getMappedRange ( ) ) ;
assertEquals (
data ,
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
await Deno . readFile ( "tests/testdata/webgpu/hellotriangle.out" ) ,
2023-12-08 19:19:16 -05:00
) ;
outputBuffer . unmap ( ) ;
device . destroy ( ) ;
} ) ;
Deno . test ( {
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2023-12-08 19:19:16 -05:00
} , async function webgpuAdapterHasFeatures() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
assert ( adapter . features ) ;
2024-05-10 07:10:22 -04:00
const device = await adapter . requestDevice ( ) ;
device . destroy ( ) ;
2023-12-08 19:19:16 -05:00
} ) ;
2024-01-23 09:15:40 -05:00
Deno . test ( {
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2024-01-23 09:15:40 -05:00
} , async function webgpuNullWindowSurfaceThrows() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
2024-01-21 15:51:45 -05:00
assertThrows (
( ) = > {
new Deno . UnsafeWindowSurface ( "cocoa" , null , null ) ;
} ,
) ;
2024-01-23 09:15:40 -05:00
device . destroy ( ) ;
2024-01-21 15:51:45 -05:00
} ) ;
2024-01-27 12:40:09 -05:00
Deno . test ( function getPreferredCanvasFormat() {
const preferredFormat = navigator . gpu . getPreferredCanvasFormat ( ) ;
assert ( preferredFormat === "bgra8unorm" || preferredFormat === "rgba8unorm" ) ;
} ) ;
2024-05-06 09:26:25 -04:00
Deno . test ( {
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2024-05-06 09:26:25 -04:00
} , async function validateGPUColor() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
const format = "rgba8unorm-srgb" ;
const encoder = device . createCommandEncoder ( ) ;
const texture = device . createTexture ( {
size : [ 256 , 256 ] ,
format ,
usage : GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage . COPY_SRC ,
} ) ;
const view = texture . createView ( ) ;
const storeOp = "store" ;
const loadOp = "clear" ;
// values for validating GPUColor
const invalidSize = [ 0 , 0 , 0 ] ;
const msgIncludes =
2024-09-19 03:14:54 -04:00
"A sequence of number used as a GPUColor must have exactly 4 elements, received 3 elements" ;
2024-05-06 09:26:25 -04:00
// validate the argument of descriptor.colorAttachments[@@iterator].clearValue property's length of GPUCommandEncoder.beginRenderPass when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-beginrenderpass
assertThrows (
( ) = >
encoder . beginRenderPass ( {
colorAttachments : [
{
view ,
storeOp ,
loadOp ,
clearValue : invalidSize ,
} ,
] ,
} ) ,
TypeError ,
msgIncludes ,
) ;
const renderPass = encoder . beginRenderPass ( {
colorAttachments : [
{
view ,
storeOp ,
loadOp ,
clearValue : [ 0 , 0 , 0 , 1 ] ,
} ,
] ,
} ) ;
// validate the argument of color length of GPURenderPassEncoder.setBlendConstant when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpurenderpassencoder-setblendconstant
assertThrows (
( ) = > renderPass . setBlendConstant ( invalidSize ) ,
TypeError ,
msgIncludes ,
) ;
device . destroy ( ) ;
} ) ;
Deno . test ( {
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2024-05-06 09:26:25 -04:00
} , async function validateGPUExtent3D() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
const format = "rgba8unorm-srgb" ;
const encoder = device . createCommandEncoder ( ) ;
const buffer = device . createBuffer ( {
size : new Uint32Array ( [ 1 , 4 , 3 , 295 ] ) . byteLength ,
usage : GPUBufferUsage.MAP_READ | GPUBufferUsage . COPY_DST ,
} ) ;
const usage = GPUTextureUsage . RENDER_ATTACHMENT | GPUTextureUsage . COPY_SRC ;
const texture = device . createTexture ( {
size : [ 256 , 256 ] ,
format ,
usage ,
} ) ;
// values for validating GPUExtent3D
const belowSize : Array < number > = [ ] ;
const overSize = [ 256 , 256 , 1 , 1 ] ;
const msgIncludes =
2024-09-19 03:14:54 -04:00
"A sequence of number used as a GPUExtent3D must have between 1 and 3 elements" ;
2024-05-06 09:26:25 -04:00
// validate the argument of descriptor.size property's length of GPUDevice.createTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpudevice-createtexture
assertThrows (
( ) = > device . createTexture ( { size : belowSize , format , usage } ) ,
TypeError ,
msgIncludes ,
) ;
assertThrows (
( ) = > device . createTexture ( { size : overSize , format , usage } ) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of copySize property's length of GPUCommandEncoder.copyBufferToTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copybuffertotexture
assertThrows (
( ) = > encoder . copyBufferToTexture ( { buffer } , { texture } , belowSize ) ,
TypeError ,
msgIncludes ,
) ;
assertThrows (
( ) = > encoder . copyBufferToTexture ( { buffer } , { texture } , overSize ) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of copySize property's length of GPUCommandEncoder.copyTextureToBuffer when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetobuffer
assertThrows (
( ) = > encoder . copyTextureToBuffer ( { texture } , { buffer } , belowSize ) ,
TypeError ,
msgIncludes ,
) ;
assertThrows (
( ) = > encoder . copyTextureToBuffer ( { texture } , { buffer } , overSize ) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of copySize property's length of GPUCommandEncoder.copyTextureToTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetotexture
assertThrows (
( ) = > encoder . copyTextureToTexture ( { texture } , { texture } , belowSize ) ,
TypeError ,
msgIncludes ,
) ;
assertThrows (
( ) = > encoder . copyTextureToTexture ( { texture } , { texture } , overSize ) ,
TypeError ,
msgIncludes ,
) ;
const data = new Uint8Array ( [ 1 * 255 , 1 * 255 , 1 * 255 , 1 * 255 ] ) ;
// validate the argument of size property's length of GPUQueue.writeTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpuqueue-writetexture
assertThrows (
( ) = > device . queue . writeTexture ( { texture } , data , { } , belowSize ) ,
TypeError ,
msgIncludes ,
) ;
assertThrows (
( ) = > device . queue . writeTexture ( { texture } , data , { } , overSize ) ,
TypeError ,
msgIncludes ,
) ;
// NOTE: GPUQueue.copyExternalImageToTexture needs to be validated the argument of copySize property's length when its a sequence, but it is not implemented yet
device . destroy ( ) ;
} ) ;
Deno . test ( {
ignore : true ,
} , async function validateGPUOrigin2D() {
// NOTE: GPUQueue.copyExternalImageToTexture needs to be validated the argument of source.origin property's length when its a sequence, but it is not implemented yet
} ) ;
Deno . test ( {
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2024-05-06 09:26:25 -04:00
} , async function validateGPUOrigin3D() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
const format = "rgba8unorm-srgb" ;
const encoder = device . createCommandEncoder ( ) ;
const buffer = device . createBuffer ( {
size : new Uint32Array ( [ 1 , 4 , 3 , 295 ] ) . byteLength ,
usage : GPUBufferUsage.MAP_READ | GPUBufferUsage . COPY_DST ,
} ) ;
const usage = GPUTextureUsage . RENDER_ATTACHMENT | GPUTextureUsage . COPY_SRC ;
const size = [ 256 , 256 , 1 ] ;
const texture = device . createTexture ( {
size ,
format ,
usage ,
} ) ;
// value for validating GPUOrigin3D
const overSize = [ 256 , 256 , 1 , 1 ] ;
const msgIncludes =
2024-09-19 03:14:54 -04:00
"A sequence of number used as a GPUOrigin3D must have at most 3 elements, received 4 elements" ;
2024-05-06 09:26:25 -04:00
// validate the argument of destination.origin property's length of GPUCommandEncoder.copyBufferToTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copybuffertotexture
assertThrows (
( ) = >
encoder . copyBufferToTexture (
{ buffer } ,
{ texture , origin : overSize } ,
size ,
) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of source.origin property's length of GPUCommandEncoder.copyTextureToBuffer when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetobuffer
assertThrows (
( ) = >
encoder . copyTextureToBuffer (
{ texture , origin : overSize } ,
{ buffer } ,
size ,
) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of source.origin property's length of GPUCommandEncoder.copyTextureToTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpucommandencoder-copytexturetotexture
assertThrows (
( ) = >
encoder . copyTextureToTexture (
{ texture , origin : overSize } ,
{ texture } ,
size ,
) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of destination.origin property's length of GPUCommandEncoder.copyTextureToTexture when its a sequence
assertThrows (
( ) = >
encoder . copyTextureToTexture (
{ texture } ,
{ texture , origin : overSize } ,
size ,
) ,
TypeError ,
msgIncludes ,
) ;
// validate the argument of destination.origin property's length of GPUQueue.writeTexture when its a sequence
// https://www.w3.org/TR/2024/WD-webgpu-20240409/#dom-gpuqueue-writetexture
assertThrows (
( ) = >
device . queue . writeTexture (
{ texture , origin : overSize } ,
new Uint8Array ( [ 1 * 255 , 1 * 255 , 1 * 255 , 1 * 255 ] ) ,
{ } ,
size ,
) ,
TypeError ,
msgIncludes ,
) ;
// NOTE: GPUQueue.copyExternalImageToTexture needs to be validated the argument of destination.origin property's length when its a sequence, but it is not implemented yet
device . destroy ( ) ;
} ) ;
2024-05-20 16:47:04 -04:00
Deno . test ( {
2024-09-18 08:08:08 -04:00
ignore : isWsl || isCIWithoutGPU ,
2024-05-20 16:47:04 -04:00
} , async function beginRenderPassWithoutDepthClearValue() {
const adapter = await navigator . gpu . requestAdapter ( ) ;
assert ( adapter ) ;
const device = await adapter . requestDevice ( ) ;
assert ( device ) ;
const encoder = device . createCommandEncoder ( ) ;
const depthTexture = device . createTexture ( {
size : [ 256 , 256 ] ,
format : "depth32float" ,
usage : GPUTextureUsage.RENDER_ATTACHMENT ,
} ) ;
const depthView = depthTexture . createView ( ) ;
const renderPass = encoder . beginRenderPass ( {
colorAttachments : [ ] ,
depthStencilAttachment : {
view : depthView ,
depthLoadOp : "load" ,
} ,
} ) ;
assert ( renderPass ) ;
device . destroy ( ) ;
} ) ;
2023-12-08 19:19:16 -05:00
async function checkIsWsl() {
return Deno . build . os === "linux" && await hasMicrosoftProcVersion ( ) ;
async function hasMicrosoftProcVersion() {
// https://github.com/microsoft/WSL/issues/423#issuecomment-221627364
try {
const procVersion = await Deno . readTextFile ( "/proc/version" ) ;
return /microsoft/i . test ( procVersion ) ;
} catch {
return false ;
}
}
}