+2019-09-01 Myles C. Maxfield <mmaxfield@apple.com>
+
+ [WHLSL] Resources don't work when only a subset of a bind group is referenced by a shader
+ https://bugs.webkit.org/show_bug.cgi?id=201383
+
+ Reviewed by Dean Jackson.
+
+ * webgpu/whlsl/compute.html:
+ * webgpu/whlsl/sparse-bind-group-2-expected.txt: Added.
+ * webgpu/whlsl/sparse-bind-group-2.html: Added.
+ * webgpu/whlsl/sparse-bind-group-3-expected.txt: Added.
+ * webgpu/whlsl/sparse-bind-group-3.html: Added.
+ * webgpu/whlsl/sparse-bind-group-expected.txt: Added.
+ * webgpu/whlsl/sparse-bind-group.html: Added.
+
2019-09-01 Wenson Hsieh <wenson_hsieh@apple.com>
Long presses that interrupt accelerated scrolling dispatch clicks on apps linked against iOS 12 or earlier
testFailed("");
resultsBuffer.unmap();
}
+
window.jsTestIsAsync = true;
getBasicDevice().then(function(device) {
start(device).then(function() {
--- /dev/null
+PASS
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../js/webgpu-functions.js"></script>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+const shaderSource = `
+[numthreads(1, 1, 1)]
+compute void computeShader(device float[] buffer : register(u1)) {
+ buffer[0] = 17;
+}
+`;
+
+async function start(device) {
+ device.pushErrorScope("validation");
+ const shaderModule = device.createShaderModule({code: shaderSource});
+ const computeStage = {module: shaderModule, entryPoint: "computeShader"};
+
+ const bindGroupLayoutDescriptor = {bindings: [{binding: 0, visibility: 7, type: "storage-buffer"}, {binding: 1, visibility: 7, type: "storage-buffer"}]};
+ const bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDescriptor);
+ const pipelineLayoutDescriptor = {bindGroupLayouts: [bindGroupLayout]};
+ const pipelineLayout = device.createPipelineLayout(pipelineLayoutDescriptor);
+
+ const computePipelineDescriptor = {computeStage, layout: pipelineLayout};
+ const computePipeline = device.createComputePipeline(computePipelineDescriptor);
+ device.popErrorScope().then(function(e) {
+ alert(e.message);
+ });
+
+ const size = Float32Array.BYTES_PER_ELEMENT * 1;
+
+ const buffer1 = device.createBuffer({size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ});
+ const buffer2 = device.createBuffer({size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ});
+
+ const bindGroup = device.createBindGroup({layout: bindGroupLayout, bindings: [{binding: 0, resource: {buffer: buffer1, size}}, {binding: 1, resource: {buffer: buffer2, size}}]});
+
+ const commandEncoder = device.createCommandEncoder(); // {}
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.dispatch(1, 1, 1);
+ computePassEncoder.endPass();
+ const commandBuffer = commandEncoder.finish();
+ device.getQueue().submit([commandBuffer]);
+
+ const resultsArrayBuffer = await buffer2.mapReadAsync();
+ const resultsFloat32Array = new Float32Array(resultsArrayBuffer);
+ if (resultsFloat32Array[0] == 17)
+ testPassed("");
+ else
+ testFailed("");
+ buffer2.unmap();
+}
+
+window.jsTestIsAsync = true;
+getBasicDevice().then(function(device) {
+ start(device).then(function() {
+ finishJSTest();
+ }, function() {
+ testFailed("");
+ finishJSTest();
+ });
+}, function() {
+ testPassed("");
+ finishJSTest();
+});
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
--- /dev/null
+PASS
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../js/webgpu-functions.js"></script>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+const shaderSource = `
+[numthreads(1, 1, 1)]
+compute void computeShader(device float[] buffer : register(u1)) {
+ buffer[0] = 17;
+}
+`;
+
+async function start(device) {
+ device.pushErrorScope("validation");
+ const shaderModule = device.createShaderModule({code: shaderSource});
+ const computeStage = {module: shaderModule, entryPoint: "computeShader"};
+
+ const bindGroupLayoutDescriptor = {bindings: [{binding: 0, visibility: 0, type: "storage-buffer"}, {binding: 1, visibility: 7, type: "storage-buffer"}]};
+ const bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDescriptor);
+ const pipelineLayoutDescriptor = {bindGroupLayouts: [bindGroupLayout]};
+ const pipelineLayout = device.createPipelineLayout(pipelineLayoutDescriptor);
+
+ const computePipelineDescriptor = {computeStage, layout: pipelineLayout};
+ const computePipeline = device.createComputePipeline(computePipelineDescriptor);
+ device.popErrorScope().then(function(e) {
+ alert(e.message);
+ });
+
+ const size = Float32Array.BYTES_PER_ELEMENT * 1;
+
+ const buffer1 = device.createBuffer({size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ});
+ const buffer2 = device.createBuffer({size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ});
+
+ const bindGroup = device.createBindGroup({layout: bindGroupLayout, bindings: [{binding: 0, resource: {buffer: buffer1, size}}, {binding: 1, resource: {buffer: buffer2, size}}]});
+
+ const commandEncoder = device.createCommandEncoder(); // {}
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup);
+ computePassEncoder.dispatch(1, 1, 1);
+ computePassEncoder.endPass();
+ const commandBuffer = commandEncoder.finish();
+ device.getQueue().submit([commandBuffer]);
+
+ const resultsArrayBuffer = await buffer2.mapReadAsync();
+ const resultsFloat32Array = new Float32Array(resultsArrayBuffer);
+ if (resultsFloat32Array[0] == 17)
+ testPassed("");
+ else
+ testFailed("");
+ buffer2.unmap();
+}
+
+window.jsTestIsAsync = true;
+getBasicDevice().then(function(device) {
+ start(device).then(function() {
+ finishJSTest();
+ }, function() {
+ testFailed("");
+ finishJSTest();
+ });
+}, function() {
+ testPassed("");
+ finishJSTest();
+});
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
--- /dev/null
+PASS
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../js/webgpu-functions.js"></script>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script>
+const shaderSource = `
+[numthreads(1, 1, 1)]
+compute void computeShader(device float[] buffer : register(u0, space1)) {
+ buffer[0] = 17;
+}
+`;
+
+async function start(device) {
+ const shaderModule = device.createShaderModule({code: shaderSource});
+ const computeStage = {module: shaderModule, entryPoint: "computeShader"};
+
+ const bindGroupLayoutDescriptor = {bindings: [{binding: 0, visibility: 7, type: "storage-buffer"}]};
+ const bindGroupLayout = device.createBindGroupLayout(bindGroupLayoutDescriptor);
+ const pipelineLayoutDescriptor = {bindGroupLayouts: [bindGroupLayout, bindGroupLayout]};
+ const pipelineLayout = device.createPipelineLayout(pipelineLayoutDescriptor);
+
+ const computePipelineDescriptor = {computeStage, layout: pipelineLayout};
+ const computePipeline = device.createComputePipeline(computePipelineDescriptor);
+
+ const size = Float32Array.BYTES_PER_ELEMENT * 1;
+
+ const buffer1 = device.createBuffer({size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ});
+ const buffer2 = device.createBuffer({size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ});
+
+ const bindGroup1 = device.createBindGroup({layout: bindGroupLayout, bindings: [{binding: 0, resource: {buffer: buffer1, size}}]});
+ const bindGroup2 = device.createBindGroup({layout: bindGroupLayout, bindings: [{binding: 0, resource: {buffer: buffer2, size}}]});
+
+ const commandEncoder = device.createCommandEncoder(); // {}
+ const computePassEncoder = commandEncoder.beginComputePass();
+ computePassEncoder.setPipeline(computePipeline);
+ computePassEncoder.setBindGroup(0, bindGroup1);
+ computePassEncoder.setBindGroup(1, bindGroup2);
+ computePassEncoder.dispatch(1, 1, 1);
+ computePassEncoder.endPass();
+ const commandBuffer = commandEncoder.finish();
+ device.getQueue().submit([commandBuffer]);
+
+ const resultsArrayBuffer = await buffer2.mapReadAsync();
+ const resultsFloat32Array = new Float32Array(resultsArrayBuffer);
+ if (resultsFloat32Array[0] == 17)
+ testPassed("");
+ else
+ testFailed("");
+ buffer2.unmap();
+}
+
+window.jsTestIsAsync = true;
+getBasicDevice().then(function(device) {
+ start(device).then(function() {
+ finishJSTest();
+ }, function() {
+ testFailed("");
+ finishJSTest();
+ });
+}, function() {
+ testPassed("");
+ finishJSTest();
+});
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
+2019-09-01 Myles C. Maxfield <mmaxfield@apple.com>
+
+ [WHLSL] Resources don't work when only a subset of a bind group is referenced by a shader
+ https://bugs.webkit.org/show_bug.cgi?id=201383
+
+ Reviewed by Dean Jackson.
+
+ Bind groups correspond to argument buffers in Metal. Both the Metal API and Metal Shading Language
+ have to agree on the layout of exactly which resources lie at which byte offsets within an argument
+ buffer.
+
+ Before this patch, we only emitted code for the items in the argument buffer that were actually
+ referenced by the shader source code. However, because these items are held inside a struct, if
+ we omit one item from the middle of the struct, the byte offets of all the successive items would
+ be wrong. This means that the Metal API and the shader would disagree about how to access these
+ resources, making the resources inaccessible (and causing security problems).
+
+ Tests: webgpu/whlsl/sparse-bind-group-2.html
+ webgpu/whlsl/sparse-bind-group-3.html
+ webgpu/whlsl/sparse-bind-group.html
+
+ * Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.cpp:
+ (WebCore::WHLSL::Metal::EntryPointScaffolding::emitResourceHelperTypes):
+ (WebCore::WHLSL::Metal::VertexEntryPointScaffolding::emitHelperTypes):
+ (WebCore::WHLSL::Metal::FragmentEntryPointScaffolding::emitHelperTypes):
+ (WebCore::WHLSL::Metal::ComputeEntryPointScaffolding::emitHelperTypes):
+ * Modules/webgpu/WHLSL/Metal/WHLSLEntryPointScaffolding.h:
+ * Modules/webgpu/WHLSL/WHLSLSemanticMatcher.cpp:
+ (WebCore::WHLSL::matchResources):
+ (WebCore::WHLSL::matchVertexAttributes):
+ (WebCore::WHLSL::matchColorAttachments):
+
2019-09-01 Said Abou-Hallawa <sabouhallawa@apple.com>
HTMLImageElement::decode() should return a resolved promise for decoding non bitmap images
m_parameterVariables.uncheckedAppend(m_generateNextVariableName());
}
-void EntryPointScaffolding::emitResourceHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent)
+void EntryPointScaffolding::emitResourceHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent, ShaderStage shaderStage)
{
for (size_t i = 0; i < m_layout.size(); ++i) {
stringBuilder.append(indent, "struct ", m_namedBindGroups[i].structName, " {\n");
IndentationScope scope(indent);
Vector<std::pair<unsigned, String>> structItems;
for (size_t j = 0; j < m_layout[i].bindings.size(); ++j) {
- auto iterator = m_resourceMap.find(&m_layout[i].bindings[j]);
- if (iterator == m_resourceMap.end())
+ auto& binding = m_layout[i].bindings[j];
+ if (!binding.visibility.contains(shaderStage))
continue;
- auto& type = m_entryPointItems.inputs[iterator->value].unnamedType->unifyNode();
- if (is<AST::UnnamedType>(type) && is<AST::ReferenceType>(downcast<AST::UnnamedType>(type))) {
- auto& referenceType = downcast<AST::ReferenceType>(downcast<AST::UnnamedType>(type));
- auto mangledTypeName = m_typeNamer.mangledNameForType(referenceType.elementType());
- auto addressSpace = toString(referenceType.addressSpace());
- auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
- auto index = m_namedBindGroups[i].namedBindings[j].index;
- structItems.append(std::make_pair(index, makeString(addressSpace, " ", mangledTypeName, "* ", elementName, " [[id(", index, ")]];")));
- if (auto lengthInformation = m_namedBindGroups[i].namedBindings[j].lengthInformation)
- structItems.append(std::make_pair(lengthInformation->index, makeString("uint2 ", lengthInformation->elementName, " [[id(", lengthInformation->index, ")]];")));
- } else if (is<AST::NamedType>(type) && is<AST::NativeTypeDeclaration>(downcast<AST::NamedType>(type))) {
- auto& namedType = downcast<AST::NativeTypeDeclaration>(downcast<AST::NamedType>(type));
- auto mangledTypeName = m_typeNamer.mangledNameForType(namedType);
- auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
- auto index = m_namedBindGroups[i].namedBindings[j].index;
- structItems.append(std::make_pair(index, makeString(mangledTypeName, ' ', elementName, " [[id(", index, ")]];")));
+
+ auto elementName = m_namedBindGroups[i].namedBindings[j].elementName;
+ auto index = m_namedBindGroups[i].namedBindings[j].index;
+ if (auto lengthInformation = m_namedBindGroups[i].namedBindings[j].lengthInformation)
+ structItems.append(std::make_pair(lengthInformation->index, makeString("uint2 ", lengthInformation->elementName, " [[id(", lengthInformation->index, ")]];")));
+
+ auto iterator = m_resourceMap.find(&m_layout[i].bindings[j]);
+ if (iterator != m_resourceMap.end()) {
+ auto& type = m_entryPointItems.inputs[iterator->value].unnamedType->unifyNode();
+ if (is<AST::UnnamedType>(type) && is<AST::ReferenceType>(downcast<AST::UnnamedType>(type))) {
+ auto& referenceType = downcast<AST::ReferenceType>(downcast<AST::UnnamedType>(type));
+ auto mangledTypeName = m_typeNamer.mangledNameForType(referenceType.elementType());
+ auto addressSpace = toString(referenceType.addressSpace());
+ structItems.append(std::make_pair(index, makeString(addressSpace, " ", mangledTypeName, "* ", elementName, " [[id(", index, ")]];")));
+ } else if (is<AST::NamedType>(type) && is<AST::NativeTypeDeclaration>(downcast<AST::NamedType>(type))) {
+ auto& namedType = downcast<AST::NativeTypeDeclaration>(downcast<AST::NamedType>(type));
+ auto mangledTypeName = m_typeNamer.mangledNameForType(namedType);
+ structItems.append(std::make_pair(index, makeString(mangledTypeName, ' ', elementName, " [[id(", index, ")]];")));
+ }
+ } else {
+ // The binding doesn't appear in the shader source.
+ // However, we must still emit a placeholder, so successive items in the argument buffer struct have the correct offset.
+ // Because the binding doesn't appear in the shader source, we don't know which exact type the bind point should have.
+ // Therefore, we must synthesize a type out of thin air.
+ WTF::visit(WTF::makeVisitor([&](UniformBufferBinding) {
+ structItems.append(std::make_pair(index, makeString("constant void* ", elementName, " [[id(", index, ")]];")));
+ }, [&](SamplerBinding) {
+ structItems.append(std::make_pair(index, makeString("sampler ", elementName, " [[id(", index, ")]];")));
+ }, [&](TextureBinding) {
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=201384 We don't know which texture type the binding represents. This is no good very bad.
+ structItems.append(std::make_pair(index, makeString("texture2d<float4> ", elementName, " [[id(", index, ")]];")));
+ }, [&](StorageBufferBinding) {
+ structItems.append(std::make_pair(index, makeString("device void* ", elementName, " [[id(", index, ")]];")));
+ }), binding.binding);
}
}
std::sort(structItems.begin(), structItems.end(), [](const std::pair<unsigned, String>& left, const std::pair<unsigned, String>& right) {
}
stringBuilder.append(indent, "};\n\n");
- emitResourceHelperTypes(stringBuilder, indent);
+ emitResourceHelperTypes(stringBuilder, indent, ShaderStage::Vertex);
}
void VertexEntryPointScaffolding::emitSignature(StringBuilder& stringBuilder, MangledFunctionName functionName, Indentation<4> indent)
}
stringBuilder.append(indent, "};\n\n");
- emitResourceHelperTypes(stringBuilder, indent);
+ emitResourceHelperTypes(stringBuilder, indent, ShaderStage::Fragment);
}
void FragmentEntryPointScaffolding::emitSignature(StringBuilder& stringBuilder, MangledFunctionName functionName, Indentation<4> indent)
void ComputeEntryPointScaffolding::emitHelperTypes(StringBuilder& stringBuilder, Indentation<4> indent)
{
- emitResourceHelperTypes(stringBuilder, indent);
+ emitResourceHelperTypes(stringBuilder, indent, ShaderStage::Compute);
}
void ComputeEntryPointScaffolding::emitSignature(StringBuilder& stringBuilder, MangledFunctionName functionName, Indentation<4> indent)
protected:
EntryPointScaffolding(AST::FunctionDefinition&, Intrinsics&, TypeNamer&, EntryPointItems&, HashMap<Binding*, size_t>& resourceMap, Layout&, std::function<MangledVariableName()>&& generateNextVariableName);
- void emitResourceHelperTypes(StringBuilder&, Indentation<4>);
+ void emitResourceHelperTypes(StringBuilder&, Indentation<4>, ShaderStage);
enum class IncludePrecedingComma {
Yes,
static Optional<HashMap<Binding*, size_t>> matchResources(Vector<EntryPointItem>& entryPointItems, Layout& layout, ShaderStage shaderStage)
{
HashMap<Binding*, size_t> result;
- HashSet<size_t> itemIndices;
- if (entryPointItems.size() == std::numeric_limits<size_t>::max())
- return WTF::nullopt; // Work around the fact that HashSet's keys are restricted.
+ HashSet<size_t, DefaultHash<size_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<size_t>> itemIndices;
for (auto& bindGroup : layout) {
auto space = bindGroup.name;
for (auto& binding : bindGroup.bindings) {
if (space != resourceSemantic.space())
continue;
result.add(&binding, i);
- itemIndices.add(i + 1); // Work around the fact that HashSet's keys are restricted.
+ itemIndices.add(i);
}
}
}
auto& semantic = *item.semantic;
if (!WTF::holds_alternative<AST::ResourceSemantic>(semantic))
continue;
- if (!itemIndices.contains(i + 1))
+ if (!itemIndices.contains(i))
return WTF::nullopt;
}
static Optional<HashMap<VertexAttribute*, size_t>> matchVertexAttributes(Vector<EntryPointItem>& vertexInputs, VertexAttributes& vertexAttributes, Intrinsics& intrinsics)
{
HashMap<VertexAttribute*, size_t> result;
- HashSet<size_t> itemIndices;
- if (vertexInputs.size() == std::numeric_limits<size_t>::max())
- return WTF::nullopt; // Work around the fact that HashSet's keys are restricted.
+ HashSet<size_t, DefaultHash<size_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<size_t>> itemIndices;
for (auto& vertexAttribute : vertexAttributes) {
for (size_t i = 0; i < vertexInputs.size(); ++i) {
auto& item = vertexInputs[i];
if (!isAcceptableFormat(vertexAttribute.vertexFormat, *item.unnamedType, intrinsics))
return WTF::nullopt;
result.add(&vertexAttribute, i);
- itemIndices.add(i + 1); // Work around the fact that HashSet's keys are restricted.
+ itemIndices.add(i);
}
}
auto& semantic = *item.semantic;
if (!WTF::holds_alternative<AST::StageInOutSemantic>(semantic))
continue;
- if (!itemIndices.contains(i + 1))
+ if (!itemIndices.contains(i))
return WTF::nullopt;
}
static Optional<HashMap<AttachmentDescriptor*, size_t>> matchColorAttachments(Vector<EntryPointItem>& fragmentOutputs, Vector<AttachmentDescriptor>& attachmentDescriptors, Intrinsics& intrinsics)
{
HashMap<AttachmentDescriptor*, size_t> result;
- HashSet<size_t> itemIndices;
- if (attachmentDescriptors.size() == std::numeric_limits<size_t>::max())
- return WTF::nullopt; // Work around the fact that HashSet's keys are restricted.
+ HashSet<size_t, DefaultHash<size_t>::Hash, WTF::UnsignedWithZeroKeyHashTraits<size_t>> itemIndices;
for (auto& attachmentDescriptor : attachmentDescriptors) {
for (size_t i = 0; i < fragmentOutputs.size(); ++i) {
auto& item = fragmentOutputs[i];
if (!isAcceptableFormat(attachmentDescriptor.textureFormat, *item.unnamedType, intrinsics, true))
return WTF::nullopt;
result.add(&attachmentDescriptor, i);
- itemIndices.add(i + 1); // Work around the fact that HashSet's keys are restricted.
+ itemIndices.add(i);
}
}
auto& semantic = *item.semantic;
if (!WTF::holds_alternative<AST::StageInOutSemantic>(semantic))
continue;
- if (!itemIndices.contains(i + 1))
+ if (!itemIndices.contains(i))
return WTF::nullopt;
}
mtlArgument = adoptNS([MTLArgumentDescriptor new]);
END_BLOCK_OBJC_EXCEPTIONS;
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=201384 This needs to set the "textureType" field
[mtlArgument setDataType:dataType];
[mtlArgument setIndex:index];
return mtlArgument;