[WebGPU] Validation for GPUDevice.createTexture()
authormmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 May 2020 01:48:20 +0000 (01:48 +0000)
committermmaxfield@apple.com <mmaxfield@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 May 2020 01:48:20 +0000 (01:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=211882
<rdar://problem/63215999>

Reviewed by Dean Jackson.

Source/WebCore:

Add lots of validation for texture creation. The logic was gathered by
trial and error.

Before this patch, we didn't have any validation. This is a first pass, as
the validation logic isn't spelled out in the spec. Next, I will make a pull
request to the spec to match this patch.

This patch also updates three pieces of our IDL files to match the spec for
WebGPU: One to remove GPUTextureUsage.NONE which was replaced with just 0,
one to remove GPUTextureDescriptor.arrayLayerCount, which was deleted in
favor of using regular dimension fields, and one to add [EnforceRange] to
various values.

This patch also updates GPUDevice to have a GPUErrorScopes object, which is
required for good error messages.

Test: webgpu/texture-creation.html

* Modules/webgpu/GPUExtent3D.idl:
* Modules/webgpu/GPUTextureDescriptor.idl:
* Modules/webgpu/WebGPUDevice.cpp:
(WebCore::WebGPUDevice::tryCreate):
(WebCore::WebGPUDevice::createTexture const):
* Modules/webgpu/WebGPUDevice.h:
* platform/graphics/gpu/GPUDevice.cpp:
(WebCore::maximumMipLevelCount):
(WebCore::GPUDevice::tryCreateTexture const):
* platform/graphics/gpu/GPUDevice.h:
(WebCore::GPUDevice::setErrorScopes):
* platform/graphics/gpu/GPUExtent3D.h:
* platform/graphics/gpu/GPUObjectBase.h:
(WebCore::GPUObjectBase::errorScopes const):
(WebCore::GPUObjectBase::errorScopes): Deleted.
* platform/graphics/gpu/GPUTexture.h:
* platform/graphics/gpu/GPUTextureDescriptor.h:
* platform/graphics/gpu/cocoa/GPUTextureMetal.mm:
(WebCore::mtlTextureTypeForGPUTextureDescriptor):
(WebCore::mtlTextureUsageForGPUTextureUsageFlags):
(WebCore::tryCreateMtlTextureDescriptor):
(WebCore::GPUTexture::tryCreate):

LayoutTests:

* webgpu/blit-commands-texture-to-texture.html: Updated.
  Rewrote part of the test to avoid hitting https://bugs.webkit.org/show_bug.cgi?id=212013
* webgpu/texture-creation-expected.txt: Added.
* webgpu/texture-creation.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@261843 268f45cc-cd09-0410-ab3c-d52691b4dbfc

20 files changed:
LayoutTests/ChangeLog
LayoutTests/webgpu/blit-commands-texture-to-texture.html
LayoutTests/webgpu/buffer-errors.html
LayoutTests/webgpu/error-scopes-test.html
LayoutTests/webgpu/texture-creation-expected.txt [new file with mode: 0644]
LayoutTests/webgpu/texture-creation.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/webgpu/GPUExtent3D.idl
Source/WebCore/Modules/webgpu/GPUTextureDescriptor.idl
Source/WebCore/Modules/webgpu/GPUTextureUsage.idl
Source/WebCore/Modules/webgpu/WebGPUDevice.cpp
Source/WebCore/platform/graphics/gpu/GPUDevice.cpp
Source/WebCore/platform/graphics/gpu/GPUDevice.h
Source/WebCore/platform/graphics/gpu/GPUExtent3D.h
Source/WebCore/platform/graphics/gpu/GPUObjectBase.h
Source/WebCore/platform/graphics/gpu/GPUTexture.h
Source/WebCore/platform/graphics/gpu/GPUTextureDescriptor.h
Source/WebCore/platform/graphics/gpu/GPUTextureUsage.h
Source/WebCore/platform/graphics/gpu/cocoa/GPUBufferMetal.mm
Source/WebCore/platform/graphics/gpu/cocoa/GPUTextureMetal.mm

index 21965c8..ae63aed 100644 (file)
@@ -1,3 +1,16 @@
+2020-05-18  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [WebGPU] Validation for GPUDevice.createTexture()
+        https://bugs.webkit.org/show_bug.cgi?id=211882
+        <rdar://problem/63215999>
+
+        Reviewed by Dean Jackson.
+
+        * webgpu/blit-commands-texture-to-texture.html: Updated.
+          Rewrote part of the test to avoid hitting https://bugs.webkit.org/show_bug.cgi?id=212013
+        * webgpu/texture-creation-expected.txt: Added.
+        * webgpu/texture-creation.html: Added.
+
 2020-05-18  Simon Fraser  <simon.fraser@apple.com>
 
         Content disappears on CSS parallax example
index 481ff13..d1bf641 100644 (file)
 if (window.testRunner)
     testRunner.waitUntilDone();
 
-function loadTextureFromCanvas2d(device, canvas2d) {
+async function loadTextureFromCanvas2d(device, canvas2d) {
     const textureSize = {
         width:  canvas2d.width,
         height: canvas2d.height,
-        depth:  1
+        depth:  2
     };
 
     // Mipmap count
@@ -25,7 +25,6 @@ function loadTextureFromCanvas2d(device, canvas2d) {
 
     const textureDescriptor = {
         size: textureSize,
-        arrayLayerCount: 2,
         mipLevelCount: mipLevelCount,
         sampleCount: 1,
         dimension: "2d",
@@ -35,6 +34,7 @@ function loadTextureFromCanvas2d(device, canvas2d) {
 
     // Create texture and also add the descriptor
     const texture = device.createTexture(textureDescriptor);
+    textureSize.depth = 1;
     texture.descriptor = textureDescriptor;
 
     // Texture data 
@@ -43,10 +43,11 @@ function loadTextureFromCanvas2d(device, canvas2d) {
 
     const textureDataBufferDescriptor = {
         size: imageData.data.length,
-        usage: GPUBufferUsage.COPY_SRC
+        usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.MAP_WRITE
     };
-    const [textureDataBuffer, textureArrayBuffer] = device.createBufferMapped(textureDataBufferDescriptor);
-    
+    const textureDataBuffer = device.createBuffer(textureDataBufferDescriptor);
+
+    const textureArrayBuffer = await textureDataBuffer.mapWriteAsync();
     const textureWriteArray = new Uint8Array(textureArrayBuffer);
     textureWriteArray.set(imageData.data);
     textureDataBuffer.unmap();
@@ -55,7 +56,7 @@ function loadTextureFromCanvas2d(device, canvas2d) {
         buffer: textureDataBuffer,
         offset: 0,
         rowPitch: canvas2d.width * 4,
-        imageHeight: 0
+        imageHeight: canvas2d.width * canvas2d.height * 4
     };
     const textureCopyView = {
         texture: texture,
@@ -78,12 +79,12 @@ async function test(device) {
     // textureA (layer 0, mip 0): Green
     context2d.fillStyle = 'rgb(0, 255, 0)';
     context2d.fillRect(0, 0, canvas2d.width, canvas2d.height);
-    const textureA = loadTextureFromCanvas2d(device, canvas2d);
+    const textureA = await loadTextureFromCanvas2d(device, canvas2d);
 
     // textureB (layer 0, mip 0): Red
     context2d.fillStyle = 'rgb(255, 0, 0)';
     context2d.fillRect(0, 0, canvas2d.width, canvas2d.height);
-    const textureB = loadTextureFromCanvas2d(device, canvas2d); 
+    const textureB = await loadTextureFromCanvas2d(device, canvas2d);
 
     // Clean canvas to blue
     context2d.fillStyle = 'rgb(0, 0, 255)';
@@ -140,7 +141,7 @@ async function test(device) {
     const bufferCopyView = {
         buffer: bufferA,
         rowPitch: canvas2d.width * 4,
-        imageHeight: 0
+        imageHeight: canvas2d.width * canvas2d.height * 4
     };
     const textureCopyView = {
         texture: textureB
@@ -149,14 +150,13 @@ async function test(device) {
 
     device.getQueue().submit([blitCommandEncoder.finish()]);
 
-    await bufferA.mapReadAsync().then(ab => {
-        const array = new Uint8ClampedArray(ab);
-        const resultImageData = new ImageData(array, canvas2d.width, canvas2d.height);
+    const ab = await bufferA.mapReadAsync()
+    const array = new Uint8ClampedArray(ab);
+    const resultImageData = new ImageData(array, canvas2d.width, canvas2d.height);
 
-        context2d.putImageData(resultImageData, 0, 0);
+    context2d.putImageData(resultImageData, 0, 0);
 
-        bufferA.destroy();
-    });
+    bufferA.destroy();
 }
 
 getBasicDevice().then(function(device) {
index d777c07..0fa1458 100644 (file)
@@ -155,4 +155,4 @@ const assertAllBufferMethodsFail = async (device, buffer) => {
 
 runTestsWithDevice(tests);
 </script>
-</body>
\ No newline at end of file
+</body>
index 856afa8..43eb139 100644 (file)
@@ -97,4 +97,4 @@ const causeValidationError = device => device.createBuffer({ size: 4, usage: GPU
 const causeMemoryError = device => device.createBuffer({ size: 99999999999, usage: GPUBufferUsage.NONE });
 
 </script>
-</body>
\ No newline at end of file
+</body>
diff --git a/LayoutTests/webgpu/texture-creation-expected.txt b/LayoutTests/webgpu/texture-creation-expected.txt
new file mode 100644 (file)
index 0000000..e1abf19
--- /dev/null
@@ -0,0 +1,55 @@
+
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width -1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height -1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth -1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount -1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount -1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage -1. 
+PASS Creating a texture of width 0 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 0 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 0 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 0 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 0 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 10000 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 3000 depth 1 mipLevelCount 1 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 10000 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 10000 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 3000 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 3000 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 3000 depth 1 mipLevelCount 1 sampleCount 1 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 3000 mipLevelCount 1 sampleCount 1 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 1 depth 1 mipLevelCount 2 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 1 mipLevelCount 2 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 1 depth 1 mipLevelCount 2 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 2 mipLevelCount 2 sampleCount 1 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 1 mipLevelCount 3 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 2 mipLevelCount 3 sampleCount 1 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 2 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 2 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 1 mipLevelCount 1 sampleCount 2 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 1 mipLevelCount 2 sampleCount 2 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 2 dimension 3d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 1 mipLevelCount 1 sampleCount 3 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 1 mipLevelCount 1 sampleCount 300 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 2 depth 2 mipLevelCount 1 sampleCount 2 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 2 depth 1 mipLevelCount 1 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 2 mipLevelCount 1 sampleCount 1 dimension 1d format rgba8unorm usage 0. 
+PASS Creating a texture of width 2 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 2 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 2 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 1d format depth32float-stencil8 usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format depth32float-stencil8 usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 2 dimension 2d format depth32float-stencil8 usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension garbage format rgba8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format garbage usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8uint usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format bgra8unorm usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format bgra8unorm-srgb usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba16float usage 0. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 1000. 
+PASS Creating a texture of width 1 height 1 depth 1 mipLevelCount 1 sampleCount 1 dimension 2d format rgba8unorm usage 31. 
+
diff --git a/LayoutTests/webgpu/texture-creation.html b/LayoutTests/webgpu/texture-creation.html
new file mode 100644 (file)
index 0000000..9a1ecb5
--- /dev/null
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Tests for creating texture views.</title>
+<meta name="timeout" content="long">
+<body>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="js/webgpu-functions.js"></script>
+<script>
+async function runTests() {
+    let tests = [
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "3d", format: "rgba8unorm", usage: 0, success: true},
+
+        // Test negative values
+        {width: -1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: -1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: -1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: -1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: -1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: -1, success: false},
+
+        // Test zero values
+        {width: 0, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 0, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 0, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 0, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 0, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+
+        // Test maximum sizes
+        {width: 10000, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 3000, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 10000, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 10000, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 3000, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 3000, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "3d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 3000, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "3d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 3000, mipLevelCount: 1, sampleCount: 1, dimension: "3d", format: "rgba8unorm", usage: 0, success: false},
+
+        // Test mipmapped textures
+        {width: 2, height: 1, depth: 1, mipLevelCount: 2, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 2, height: 2, depth: 1, mipLevelCount: 2, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 2, height: 1, depth: 1, mipLevelCount: 2, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 2, height: 2, depth: 2, mipLevelCount: 2, sampleCount: 1, dimension: "3d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 2, height: 2, depth: 1, mipLevelCount: 3, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 2, height: 2, depth: 2, mipLevelCount: 3, sampleCount: 1, dimension: "3d", format: "rgba8unorm", usage: 0, success: false},
+
+        // Test multisampling
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 2, dimension: "1d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 2, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 2, height: 2, depth: 1, mipLevelCount: 1, sampleCount: 2, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 2, height: 2, depth: 1, mipLevelCount: 2, sampleCount: 2, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 2, dimension: "3d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 2, height: 2, depth: 1, mipLevelCount: 1, sampleCount: 3, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 2, height: 2, depth: 1, mipLevelCount: 1, sampleCount: 300, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 2, height: 2, depth: 2, mipLevelCount: 1, sampleCount: 2, dimension: "2d", format: "rgba8unorm", usage: 0, success: false},
+
+        // Test dimension
+        {width: 2, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 1, height: 2, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 1, height: 1, depth: 2, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "rgba8unorm", usage: 0, success: false},
+        {width: 2, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 1, height: 2, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+        {width: 1, height: 1, depth: 2, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 0, success: true},
+
+        // Test depth textures
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "1d", format: "depth32float-stencil8", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "depth32float-stencil8", usage: 0, success: true},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 2, dimension: "2d", format: "depth32float-stencil8", usage: 0, success: false},
+
+        // Test enums
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "garbage", format: "rgba8unorm", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "garbage", usage: 0, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8uint", usage: 0, success: true},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "bgra8unorm", usage: 0, success: true},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "bgra8unorm-srgb", usage: 0, success: true},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba16float", usage: 0, success: true},
+
+        // Test usage
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: 1000, success: false},
+        {width: 1, height: 1, depth: 1, mipLevelCount: 1, sampleCount: 1, dimension: "2d", format: "rgba8unorm", usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED | GPUTextureUsage.STORAGE | GPUTextureUsage.OUTPUT_ATTACHMENT, success: true},
+    ];
+    for (let test of tests) {
+        promise_test(() => {
+        return getBasicDevice().then(async function(device) {
+            let caught = false;
+            device.pushErrorScope("validation");
+            try {
+                let texture = device.createTexture({size: {width: test.width, height: test.height, depth: test.depth}, mipLevelCount: test.mipLevelCount, sampleCount: test.sampleCount, dimension: test.dimension, format: test.format, usage: test.usage});
+                assert_true(texture instanceof GPUTexture, "Cannot create texture.");
+            } catch {
+                assert_true(!test.success, "Creating texture should " + (test.success ? "not " : "") + "fail.");
+                caught = true;
+            }
+            await device.popErrorScope().then(function(error) {
+                if (test.success) {
+                    let message = "Could not create texture: ";
+                    if (error && error.message)
+                        message += error.message;
+                    assert_equals(error, null, "Could not create texture: " + message);
+                } else if (!caught) {
+                    let message = "Created texture: ";
+                    if (error && error.message)
+                        message += error.message;
+                    assert_true(!!(caught || error), "Created texture when shouldn't have: " + message);
+                }
+            }, function() {
+                assert_true(false, "device lost");
+            });
+        }, function(e) {
+        });
+    }, `Creating a texture of width ${test.width} height ${test.height} depth ${test.depth} mipLevelCount ${test.mipLevelCount} sampleCount ${test.sampleCount} dimension ${test.dimension} format ${test.format} usage ${test.usage}.`);
+    }
+}
+
+runTests();
+</script>
+</body>
index 54b73d6..1d44e0c 100644 (file)
@@ -1,3 +1,52 @@
+2020-05-18  Myles C. Maxfield  <mmaxfield@apple.com>
+
+        [WebGPU] Validation for GPUDevice.createTexture()
+        https://bugs.webkit.org/show_bug.cgi?id=211882
+        <rdar://problem/63215999>
+
+        Reviewed by Dean Jackson.
+
+        Add lots of validation for texture creation. The logic was gathered by
+        trial and error.
+
+        Before this patch, we didn't have any validation. This is a first pass, as
+        the validation logic isn't spelled out in the spec. Next, I will make a pull
+        request to the spec to match this patch.
+
+        This patch also updates three pieces of our IDL files to match the spec for
+        WebGPU: One to remove GPUTextureUsage.NONE which was replaced with just 0,
+        one to remove GPUTextureDescriptor.arrayLayerCount, which was deleted in
+        favor of using regular dimension fields, and one to add [EnforceRange] to
+        various values.
+
+        This patch also updates GPUDevice to have a GPUErrorScopes object, which is
+        required for good error messages.
+
+        Test: webgpu/texture-creation.html
+
+        * Modules/webgpu/GPUExtent3D.idl:
+        * Modules/webgpu/GPUTextureDescriptor.idl:
+        * Modules/webgpu/WebGPUDevice.cpp:
+        (WebCore::WebGPUDevice::tryCreate):
+        (WebCore::WebGPUDevice::createTexture const):
+        * Modules/webgpu/WebGPUDevice.h:
+        * platform/graphics/gpu/GPUDevice.cpp:
+        (WebCore::maximumMipLevelCount):
+        (WebCore::GPUDevice::tryCreateTexture const):
+        * platform/graphics/gpu/GPUDevice.h:
+        (WebCore::GPUDevice::setErrorScopes):
+        * platform/graphics/gpu/GPUExtent3D.h:
+        * platform/graphics/gpu/GPUObjectBase.h:
+        (WebCore::GPUObjectBase::errorScopes const):
+        (WebCore::GPUObjectBase::errorScopes): Deleted.
+        * platform/graphics/gpu/GPUTexture.h:
+        * platform/graphics/gpu/GPUTextureDescriptor.h:
+        * platform/graphics/gpu/cocoa/GPUTextureMetal.mm:
+        (WebCore::mtlTextureTypeForGPUTextureDescriptor):
+        (WebCore::mtlTextureUsageForGPUTextureUsageFlags):
+        (WebCore::tryCreateMtlTextureDescriptor):
+        (WebCore::GPUTexture::tryCreate):
+
 2020-05-18  Oriol Brufau  <obrufau@igalia.com>
 
         [css-grid] Clear the override width for computing percent margins
index 5dfdce3..13a1b44 100644 (file)
  */
 // https://gpuweb.github.io/gpuweb
 
-typedef unsigned long u32;
+typedef [EnforceRange] unsigned long GPUIntegerCoordinate;
 
 [
     Conditional=WEBGPU,
     EnabledAtRuntime=WebGPU
 ] dictionary GPUExtent3D {
-    required u32 width;
-    required u32 height;
-    required u32 depth;
+    required GPUIntegerCoordinate width;
+    required GPUIntegerCoordinate height;
+    required GPUIntegerCoordinate depth;
 };
index 4459538..5d6188f 100644 (file)
@@ -24,8 +24,9 @@
  */
 // https://gpuweb.github.io/gpuweb
 
-typedef unsigned long u32;
-typedef unsigned long GPUTextureUsageFlags;
+typedef [EnforceRange] unsigned long GPUIntegerCoordinate;
+typedef [EnforceRange] unsigned long GPUSize32;
+typedef [EnforceRange] unsigned long GPUTextureUsageFlags;
 
 [
     ImplementedAs=GPUTextureDimension
@@ -40,9 +41,8 @@ typedef unsigned long GPUTextureUsageFlags;
     EnabledAtRuntime=WebGPU
 ] dictionary GPUTextureDescriptor {
     required GPUExtent3D size;
-    u32 arrayLayerCount = 1;
-    u32 mipLevelCount = 1;
-    u32 sampleCount = 1;
+    GPUIntegerCoordinate mipLevelCount = 1;
+    GPUSize32 sampleCount = 1;
     GPUTextureDimension dimension = "2d";
     required GPUTextureFormat format;
     required GPUTextureUsageFlags usage;
index 02e890c..6b30bb0 100644 (file)
@@ -32,7 +32,6 @@ typedef unsigned long u32;
     EnabledAtRuntime=WebGPU,
     ImplementationLacksVTable
 ] interface GPUTextureUsage {
-    const u32 NONE = 0;
     const u32 COPY_SRC = 1;
     const u32 COPY_DST = 2;
     const u32 SAMPLED = 4;
index 80b1e63..ca01b10 100644 (file)
@@ -96,8 +96,11 @@ WTF_MAKE_ISO_ALLOCATED_IMPL(WebGPUDevice);
 
 RefPtr<WebGPUDevice> WebGPUDevice::tryCreate(ScriptExecutionContext& context, Ref<const WebGPUAdapter>&& adapter)
 {
-    if (auto device = GPUDevice::tryCreate(adapter->options()))
-        return adoptRef(new WebGPUDevice(context, WTFMove(adapter), device.releaseNonNull()));
+    if (auto device = GPUDevice::tryCreate(adapter->options())) {
+        auto result = adoptRef(new WebGPUDevice(context, WTFMove(adapter), makeRef(*device)));
+        device->setErrorScopes(result->m_errorScopes.copyRef());
+        return result;
+    }
     return nullptr;
 }
 
@@ -180,6 +183,8 @@ Vector<JSC::JSValue> WebGPUDevice::createBufferMapped(JSC::JSGlobalObject& lexic
 
 Ref<WebGPUTexture> WebGPUDevice::createTexture(const GPUTextureDescriptor& descriptor) const
 {
+    m_errorScopes->setErrorPrefix("GPUDevice.createTexture(): ");
+
     auto texture = m_device->tryCreateTexture(descriptor);
     return WebGPUTexture::create(WTFMove(texture));
 }
index f5e43ce..a34735c 100644 (file)
@@ -52,6 +52,7 @@
 #include "GPUTextureDescriptor.h"
 #include <algorithm>
 #include <wtf/Optional.h>
+#include <wtf/text/StringConcatenate.h>
 
 namespace WebCore {
 
@@ -60,9 +61,130 @@ RefPtr<GPUBuffer> GPUDevice::tryCreateBuffer(const GPUBufferDescriptor& descript
     return GPUBuffer::tryCreate(*this, descriptor, isMapped, errorScopes);
 }
 
+static unsigned maximumMipLevelCount(GPUTextureDimension dimension, GPUExtent3D size)
+{
+    unsigned width = size.width;
+    unsigned height = 1;
+    unsigned depth = 1;
+    switch (dimension) {
+    case GPUTextureDimension::_1d:
+        break;
+    case GPUTextureDimension::_2d:
+        height = size.height;
+        break;
+    case GPUTextureDimension::_3d:
+        height = size.height;
+        depth = size.depth;
+        break;
+    }
+
+    auto maxDimension = std::max(std::max(width, height), depth);
+    int i = sizeof(GPUExtent3D::InnerType) * 8 - 1;
+    while (!(maxDimension & (1 << i)) && i >= 0)
+        --i;
+    ASSERT(i >= 0);
+    return i + 1;
+}
+
 RefPtr<GPUTexture> GPUDevice::tryCreateTexture(const GPUTextureDescriptor& descriptor) const
 {
-    return GPUTexture::tryCreate(*this, descriptor);
+    if (!descriptor.size.width || !descriptor.size.height || !descriptor.size.depth) {
+        m_errorScopes->generatePrefixedError("Textures can't have 0 dimensions.");
+        return { };
+    }
+
+    if (!descriptor.mipLevelCount) {
+        m_errorScopes->generatePrefixedError("Textures can't have 0 miplevels.");
+        return { };
+    }
+
+    if (!descriptor.sampleCount) {
+        m_errorScopes->generatePrefixedError("Textures can't have 0 samples.");
+        return { };
+    }
+
+    // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
+    unsigned maxRegularSize = 8192;
+    unsigned maxDepthSize = 2048;
+    switch (descriptor.dimension) {
+    case GPUTextureDimension::_1d:
+        if (descriptor.size.width > maxRegularSize) {
+            m_errorScopes->generatePrefixedError(makeString("1D texture is too wide: ", descriptor.size.width, " needs to be <= ", maxRegularSize, "."));
+            return { };
+        }
+        if (descriptor.size.height > maxDepthSize) {
+            m_errorScopes->generatePrefixedError(makeString("1D texture array has too many layers: ", descriptor.size.height, " needs to be <= ", maxDepthSize, "."));
+            return { };
+        }
+        break;
+    case GPUTextureDimension::_2d:
+        if (descriptor.size.width > maxRegularSize || descriptor.size.height > maxRegularSize) {
+            m_errorScopes->generatePrefixedError(makeString("2D texture is too large:", descriptor.size.width, " and ", descriptor.size.height, " need to be <= ", maxRegularSize, "."));
+            return { };
+        }
+        if (descriptor.size.depth > maxDepthSize) {
+            m_errorScopes->generatePrefixedError(makeString("2D texture array has too many layers: ", descriptor.size.depth, " needs to be <= ", maxDepthSize, "."));
+            return { };
+        }
+        break;
+    case GPUTextureDimension::_3d:
+        if (descriptor.size.width > maxDepthSize || descriptor.size.height > maxDepthSize || descriptor.size.depth > maxDepthSize) {
+            m_errorScopes->generatePrefixedError(makeString("3D texture is too large: ", descriptor.size.width, ", ", descriptor.size.height, ", and ", descriptor.size.depth, " need to be <= ", maxDepthSize, "."));
+            return { };
+        }
+        break;
+    }
+
+    if (descriptor.dimension == GPUTextureDimension::_1d && descriptor.mipLevelCount != 1) {
+        m_errorScopes->generatePrefixedError("1D textures can't have mipmaps");
+        return { };
+    }
+
+    auto maxLevels = maximumMipLevelCount(descriptor.dimension, descriptor.size);
+    if (descriptor.mipLevelCount > maxLevels) {
+        m_errorScopes->generatePrefixedError(makeString("Too many miplevels: ", descriptor.mipLevelCount, " needs to be <= ", maxLevels, "."));
+        return { };
+    }
+
+    if (descriptor.sampleCount != 1) {
+        if (descriptor.dimension != GPUTextureDimension::_2d) {
+            m_errorScopes->generatePrefixedError("Texture dimension incompatible with multisampling.");
+            return { };
+        }
+        if (descriptor.mipLevelCount != 1) {
+            m_errorScopes->generatePrefixedError("Multisampled textures can't have mipmaps.");
+            return { };
+        }
+        if (descriptor.size.depth != 1) {
+            m_errorScopes->generatePrefixedError("Array textures can't be multisampled.");
+            return { };
+        }
+        // FIXME: Only some pixel formats are capable of MSAA. When we add those new pixel formats, we'll have to add filtering code here.
+    }
+
+    // The height component for 1d textures is the array length.
+    // The depth component for 2d textures is the array length.
+    if (descriptor.dimension == GPUTextureDimension::_1d && descriptor.size.depth != 1) {
+        m_errorScopes->generatePrefixedError("1D textures can't have depth != 1.");
+        return { };
+    }
+
+    if (descriptor.format == GPUTextureFormat::Depth32floatStencil8 && descriptor.dimension != GPUTextureDimension::_2d) {
+        m_errorScopes->generatePrefixedError("Depth/stencil textures must be 2d.");
+        return { };
+    }
+
+    if (descriptor.format == GPUTextureFormat::Depth32floatStencil8 && descriptor.sampleCount != 1) {
+        m_errorScopes->generatePrefixedError("Depth/stencil textures can't be multisampled.");
+        return { };
+    }
+
+    if (static_cast<GPUTextureUsageFlags>(descriptor.usage) >= static_cast<GPUTextureUsageFlags>(GPUTextureUsage::Flags::MaximumValue)) {
+        m_errorScopes->generatePrefixedError("Invalid usage flags.");
+        return { };
+    }
+
+    return GPUTexture::tryCreate(*this, descriptor, makeRef(*m_errorScopes));
 }
 
 RefPtr<GPUSampler> GPUDevice::tryCreateSampler(const GPUSamplerDescriptor& descriptor) const
index 58b1200..f5496cc 100644 (file)
@@ -28,6 +28,7 @@
 #if ENABLE(WEBGPU)
 
 #include "GPUBindGroupAllocator.h"
+#include "GPUErrorScopes.h"
 #include "GPUQueue.h"
 #include "GPUSwapChain.h"
 #include <wtf/Function.h>
@@ -95,6 +96,8 @@ public:
     GPUSwapChain* swapChain() const { return m_swapChain.get(); }
     void setSwapChain(RefPtr<GPUSwapChain>&&);
 
+    void setErrorScopes(Ref<GPUErrorScopes>&& errorScopes) { m_errorScopes = WTFMove(errorScopes); }
+
     static constexpr bool useWHLSL = true;
 
 private:
@@ -104,6 +107,7 @@ private:
     mutable RefPtr<GPUQueue> m_queue;
     RefPtr<GPUSwapChain> m_swapChain;
     mutable RefPtr<GPUBindGroupAllocator> m_bindGroupAllocator;
+    RefPtr<GPUErrorScopes> m_errorScopes;
 };
 
 } // namespace WebCore
index 3195d17..5448265 100644 (file)
 namespace WebCore {
     
 struct GPUExtent3D {
-    unsigned width;
-    unsigned height;
-    unsigned depth;
+    using InnerType = unsigned;
+    InnerType width;
+    InnerType height;
+    InnerType depth;
 };
 
 } // namespace WebCore
index 94c1f52..67b2d91 100644 (file)
@@ -40,7 +40,7 @@ protected:
     GPUObjectBase(Ref<GPUErrorScopes>&& reporter)
         : m_errorScopes(WTFMove(reporter)) { }
 
-    GPUErrorScopes& errorScopes() { return m_errorScopes; }
+    GPUErrorScopes& errorScopes() const { return m_errorScopes; }
 
 private:
     Ref<GPUErrorScopes> m_errorScopes;
index ff7aab0..a3b426b 100644 (file)
@@ -38,6 +38,7 @@ OBJC_PROTOCOL(MTLTexture);
 namespace WebCore {
 
 class GPUDevice;
+class GPUErrorScopes;
 
 struct GPUTextureDescriptor;
 
@@ -46,7 +47,7 @@ using PlatformTextureSmartPtr = RetainPtr<MTLTexture>;
 
 class GPUTexture : public RefCounted<GPUTexture> {
 public:
-    static RefPtr<GPUTexture> tryCreate(const GPUDevice&, const GPUTextureDescriptor&);
+    static RefPtr<GPUTexture> tryCreate(const GPUDevice&, const GPUTextureDescriptor&, GPUErrorScopes&);
     static Ref<GPUTexture> create(PlatformTextureSmartPtr&&, OptionSet<GPUTextureUsage::Flags>);
 
     PlatformTexture *platformTexture() const { return m_platformTexture.get(); }
index 66e1c17..b2a685a 100644 (file)
@@ -41,7 +41,6 @@ enum class GPUTextureDimension {
     
 struct GPUTextureDescriptor {
     GPUExtent3D size;
-    unsigned arrayLayerCount;
     unsigned mipLevelCount;
     unsigned sampleCount;
     GPUTextureDimension dimension;
index 5c0ce61..643e6fb 100644 (file)
@@ -36,12 +36,12 @@ using GPUTextureUsageFlags = unsigned;
 class GPUTextureUsage : public RefCounted<GPUTextureUsage> {
 public:
     enum class Flags : GPUTextureUsageFlags {
-        None = 0,
-        CopySource = 1 << 0,
-        CopyDestination = 1 << 1,
-        Sampled = 1 << 2,
-        Storage = 1 << 3,
+        CopySource       = 1 << 0,
+        CopyDestination  = 1 << 1,
+        Sampled          = 1 << 2,
+        Storage          = 1 << 3,
         OutputAttachment = 1 << 4,
+        MaximumValue     = 1 << 5,
     };
 };
 
index 561ad52..b1c34ef 100644 (file)
@@ -228,7 +228,7 @@ void GPUBuffer::copyStagingBufferToGPU(GPUErrorScopes* errorScopes)
 
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
     // GPUBuffer creation validation ensures m_byteSize fits in NSUInteger.
-    stagingMtlBuffer = adoptNS([m_device->platformDevice() newBufferWithLength:static_cast<NSUInteger>(m_byteLength) options:MTLResourceCPUCacheModeDefaultCache]);
+    stagingMtlBuffer = adoptNS([m_device->platformDevice() newBufferWithBytes:m_stagingBuffer->data() length:static_cast<NSUInteger>(m_byteLength) options:MTLResourceCPUCacheModeDefaultCache]);
     END_BLOCK_OBJC_EXCEPTIONS;
 
     if (!stagingMtlBuffer && errorScopes) {
@@ -236,8 +236,6 @@ void GPUBuffer::copyStagingBufferToGPU(GPUErrorScopes* errorScopes)
         return;
     }
 
-    memcpy(stagingMtlBuffer.get().contents, m_stagingBuffer->data(), m_byteLength);
-
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
     auto commandBuffer = retainPtr([queue commandBuffer]);
index 5db1daa..c29176b 100644 (file)
@@ -42,12 +42,11 @@ static MTLTextureType mtlTextureTypeForGPUTextureDescriptor(const GPUTextureDesc
 {
     switch (descriptor.dimension) {
     case GPUTextureDimension::_1d:
-        return (descriptor.arrayLayerCount == 1) ? MTLTextureType1D : MTLTextureType1DArray;
+        return (descriptor.size.height == 1) ? MTLTextureType1D : MTLTextureType1DArray;
     case GPUTextureDimension::_2d: {
-        if (descriptor.arrayLayerCount == 1)
+        if (descriptor.size.depth == 1)
             return (descriptor.sampleCount == 1) ? MTLTextureType2D : MTLTextureType2DMultisample;
-
-        return MTLTextureType2DArray;
+        return MTLTextureType2DArray; // MTLTextureType2DMultisampleArray is unavailable on iOS.
     }
     case GPUTextureDimension::_3d:
         return MTLTextureType3D;
@@ -65,7 +64,7 @@ static Optional<MTLTextureUsage> mtlTextureUsageForGPUTextureUsageFlags(OptionSe
         return WTF::nullopt;
     }
 
-    MTLTextureUsage result = MTLTextureUsagePixelFormatView;
+    MTLTextureUsage result = 0;
     if (flags.contains(GPUTextureUsage::Flags::OutputAttachment))
         result |= MTLTextureUsageRenderTarget;
     if (flags.containsAny({ GPUTextureUsage::Flags::Storage, GPUTextureUsage::Flags::Sampled }))
@@ -102,7 +101,24 @@ static RetainPtr<MTLTextureDescriptor> tryCreateMtlTextureDescriptor(const char*
         return nullptr;
     }
 
-    // FIXME: Add more validation as constraints are added to spec.
+    unsigned width = descriptor.size.width;
+    unsigned height = 1;
+    unsigned depth = 1;
+    unsigned arrayLength = 1;
+    switch (descriptor.dimension) {
+    case GPUTextureDimension::_1d:
+        arrayLength = descriptor.size.height;
+        break;
+    case GPUTextureDimension::_2d:
+        height = descriptor.size.height;
+        arrayLength = descriptor.size.depth;
+        break;
+    case GPUTextureDimension::_3d:
+        height = descriptor.size.height;
+        depth = descriptor.size.depth;
+        break;
+    }
+
     auto pixelFormat = static_cast<MTLPixelFormat>(platformTextureFormatForGPUTextureFormat(descriptor.format));
 
     auto mtlUsage = mtlTextureUsageForGPUTextureUsageFlags(usage, functionName);
@@ -117,10 +133,10 @@ static RetainPtr<MTLTextureDescriptor> tryCreateMtlTextureDescriptor(const char*
 
     BEGIN_BLOCK_OBJC_EXCEPTIONS;
 
-    [mtlDescriptor setWidth:descriptor.size.width];
-    [mtlDescriptor setHeight:descriptor.size.height];
-    [mtlDescriptor setDepth:descriptor.size.depth];
-    [mtlDescriptor setArrayLength:descriptor.arrayLayerCount];
+    [mtlDescriptor setWidth:width];
+    [mtlDescriptor setHeight:height];
+    [mtlDescriptor setDepth:depth];
+    [mtlDescriptor setArrayLength:arrayLength];
     [mtlDescriptor setMipmapLevelCount:descriptor.mipLevelCount];
     [mtlDescriptor setSampleCount:descriptor.sampleCount];
     [mtlDescriptor setTextureType:mtlTextureTypeForGPUTextureDescriptor(descriptor)];
@@ -134,7 +150,7 @@ static RetainPtr<MTLTextureDescriptor> tryCreateMtlTextureDescriptor(const char*
     return mtlDescriptor;
 }
 
-RefPtr<GPUTexture> GPUTexture::tryCreate(const GPUDevice& device, const GPUTextureDescriptor& descriptor)
+RefPtr<GPUTexture> GPUTexture::tryCreate(const GPUDevice& device, const GPUTextureDescriptor& descriptor, GPUErrorScopes& errorScopes)
 {
     const char* const functionName = "GPUTexture::tryCreate()";
 
@@ -143,6 +159,11 @@ RefPtr<GPUTexture> GPUTexture::tryCreate(const GPUDevice& device, const GPUTextu
         return nullptr;
     }
 
+    if (![device.platformDevice() supportsTextureSampleCount:descriptor.sampleCount]) {
+        errorScopes.generatePrefixedError(makeString("Device does not support ", descriptor.sampleCount, " sample count."));
+        return { };
+    }
+
     auto usage = OptionSet<GPUTextureUsage::Flags>::fromRaw(descriptor.usage);
     auto mtlDescriptor = tryCreateMtlTextureDescriptor(functionName, descriptor, usage);
     if (!mtlDescriptor)