// Copyright 2020 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "tests/DawnTest.h" #include "common/Assert.h" #include "common/Constants.h" #include "common/Math.h" #include "utils/ComboRenderPipelineDescriptor.h" #include "utils/TextureFormatUtils.h" #include "utils/WGPUHelpers.h" class StorageTextureTests : public DawnTest { public: static void FillExpectedData(void* pixelValuePtr, wgpu::TextureFormat format, uint32_t x, uint32_t y, uint32_t arrayLayer) { const uint32_t pixelValue = 1 + x + kWidth * (y + kHeight * arrayLayer); ASSERT(pixelValue <= 255u / 4); switch (format) { // 32-bit unsigned integer formats case wgpu::TextureFormat::R32Uint: { uint32_t* valuePtr = static_cast(pixelValuePtr); *valuePtr = pixelValue; break; } case wgpu::TextureFormat::RG32Uint: { uint32_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = pixelValue; valuePtr[1] = pixelValue * 2; break; } case wgpu::TextureFormat::RGBA32Uint: { uint32_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = pixelValue; valuePtr[1] = pixelValue * 2; valuePtr[2] = pixelValue * 3; valuePtr[3] = pixelValue * 4; break; } // 32-bit signed integer formats case wgpu::TextureFormat::R32Sint: { int32_t* valuePtr = static_cast(pixelValuePtr); *valuePtr = static_cast(pixelValue); break; } case wgpu::TextureFormat::RG32Sint: { int32_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue); valuePtr[1] = -static_cast(pixelValue); break; } case wgpu::TextureFormat::RGBA32Sint: { int32_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue); valuePtr[1] = -static_cast(pixelValue); valuePtr[2] = static_cast(pixelValue * 2); valuePtr[3] = -static_cast(pixelValue * 2); break; } // 32-bit float formats case wgpu::TextureFormat::R32Float: { float_t* valuePtr = static_cast(pixelValuePtr); *valuePtr = static_cast(pixelValue * 1.1f); break; } case wgpu::TextureFormat::RG32Float: { float_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue * 1.1f); valuePtr[1] = -static_cast(pixelValue * 2.2f); break; } case wgpu::TextureFormat::RGBA32Float: { float_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue * 1.1f); valuePtr[1] = -static_cast(pixelValue * 1.1f); valuePtr[2] = static_cast(pixelValue * 2.2f); valuePtr[3] = -static_cast(pixelValue * 2.2f); break; } // 16-bit (unsigned integer, signed integer and float) 4-component formats case wgpu::TextureFormat::RGBA16Uint: { uint16_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue); valuePtr[1] = static_cast(pixelValue * 2); valuePtr[2] = static_cast(pixelValue * 3); valuePtr[3] = static_cast(pixelValue * 4); break; } case wgpu::TextureFormat::RGBA16Sint: { int16_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue); valuePtr[1] = -static_cast(pixelValue); valuePtr[2] = static_cast(pixelValue * 2); valuePtr[3] = -static_cast(pixelValue * 2); break; } case wgpu::TextureFormat::RGBA16Float: { uint16_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = Float32ToFloat16(static_cast(pixelValue)); valuePtr[1] = Float32ToFloat16(-static_cast(pixelValue)); valuePtr[2] = Float32ToFloat16(static_cast(pixelValue * 2)); valuePtr[3] = Float32ToFloat16(-static_cast(pixelValue * 2)); break; } // 8-bit (normalized/non-normalized signed/unsigned integer) 4-component formats case wgpu::TextureFormat::RGBA8Unorm: case wgpu::TextureFormat::RGBA8Uint: { RGBA8* valuePtr = static_cast(pixelValuePtr); *valuePtr = RGBA8(pixelValue, pixelValue * 2, pixelValue * 3, pixelValue * 4); break; } case wgpu::TextureFormat::RGBA8Snorm: case wgpu::TextureFormat::RGBA8Sint: { int8_t* valuePtr = static_cast(pixelValuePtr); valuePtr[0] = static_cast(pixelValue); valuePtr[1] = -static_cast(pixelValue); valuePtr[2] = static_cast(pixelValue) * 2; valuePtr[3] = -static_cast(pixelValue) * 2; break; } default: UNREACHABLE(); break; } } std::string GetGLSLImageDeclaration(wgpu::TextureFormat format, std::string accessQualifier, bool is2DArray, uint32_t binding) { std::ostringstream ostream; ostream << "layout(set = 0, binding = " << binding << ", " << utils::GetGLSLImageFormatQualifier(format) << ") uniform " << accessQualifier << " " << utils::GetColorTextureComponentTypePrefix(format) << "image2D"; if (is2DArray) { ostream << "Array"; } ostream << " storageImage" << binding << ";"; return ostream.str(); } const char* GetExpectedPixelValue(wgpu::TextureFormat format) { switch (format) { // non-normalized unsigned integer formats case wgpu::TextureFormat::R32Uint: return "uvec4(value, 0, 0, 1u)"; case wgpu::TextureFormat::RG32Uint: return "uvec4(value, value * 2, 0, 1);"; case wgpu::TextureFormat::RGBA8Uint: case wgpu::TextureFormat::RGBA16Uint: case wgpu::TextureFormat::RGBA32Uint: return "uvec4(value, value * 2, value * 3, value * 4);"; // non-normalized signed integer formats case wgpu::TextureFormat::R32Sint: return "ivec4(value, 0, 0, 1)"; case wgpu::TextureFormat::RG32Sint: return "ivec4(value, -value, 0, 1);"; case wgpu::TextureFormat::RGBA8Sint: case wgpu::TextureFormat::RGBA16Sint: case wgpu::TextureFormat::RGBA32Sint: return "ivec4(value, -value, value * 2, -value * 2);"; // float formats case wgpu::TextureFormat::R32Float: return "vec4(value * 1.1f, 0, 0, 1);"; case wgpu::TextureFormat::RG32Float: return "vec4(value * 1.1f, -(value * 2.2f), 0, 1);"; case wgpu::TextureFormat::RGBA16Float: return "vec4(value, -float(value), float(value * 2), -float(value * 2));"; case wgpu::TextureFormat::RGBA32Float: return "vec4(value * 1.1f, -(value * 1.1f), value * 2.2f, -(value * 2.2f));"; // normalized signed/unsigned integer formats case wgpu::TextureFormat::RGBA8Unorm: return "vec4(value / 255.0, value / 255.0 * 2, value / 255.0 * 3, value / 255.0 * " "4);"; case wgpu::TextureFormat::RGBA8Snorm: return "vec4(value / 127.0, -(value / 127.0), (value * 2 / 127.0), -(value * 2 / " "127.0));"; default: UNREACHABLE(); break; } } const char* GetGLSLComparisonFunction(wgpu::TextureFormat format) { switch (format) { // non-normalized unsigned integer formats case wgpu::TextureFormat::R32Uint: case wgpu::TextureFormat::RG32Uint: case wgpu::TextureFormat::RGBA8Uint: case wgpu::TextureFormat::RGBA16Uint: case wgpu::TextureFormat::RGBA32Uint: return R"(bool IsEqualTo(uvec4 pixel, uvec4 expected) { return pixel == expected; })"; // non-normalized signed integer formats case wgpu::TextureFormat::R32Sint: case wgpu::TextureFormat::RG32Sint: case wgpu::TextureFormat::RGBA8Sint: case wgpu::TextureFormat::RGBA16Sint: case wgpu::TextureFormat::RGBA32Sint: return R"(bool IsEqualTo(ivec4 pixel, ivec4 expected) { return pixel == expected; })"; // float formats case wgpu::TextureFormat::R32Float: case wgpu::TextureFormat::RG32Float: case wgpu::TextureFormat::RGBA16Float: case wgpu::TextureFormat::RGBA32Float: return R"(bool IsEqualTo(vec4 pixel, vec4 expected) { return pixel == expected; })"; // normalized signed/unsigned integer formats case wgpu::TextureFormat::RGBA8Unorm: case wgpu::TextureFormat::RGBA8Snorm: // On Windows Intel drivers the tests will fail if tolerance <= 0.00000001f. return R"(bool IsEqualTo(vec4 pixel, vec4 expected) { const float tolerance = 0.0000001f; return all(lessThan(abs(pixel - expected), vec4(tolerance))); })"; default: UNREACHABLE(); break; } return ""; } std::string CommonReadOnlyTestCode(wgpu::TextureFormat format, bool is2DArray = false) { std::ostringstream ostream; const char* prefix = utils::GetColorTextureComponentTypePrefix(format); ostream << GetGLSLImageDeclaration(format, "readonly", is2DArray, 0) << "\n" << GetGLSLComparisonFunction(format) << "bool doTest() {\n"; if (is2DArray) { ostream << R"(ivec3 size = imageSize(storageImage0); const uint layerCount = size.z;)"; } else { ostream << R"(ivec2 size = imageSize(storageImage0); const uint layerCount = 1;)"; } ostream << R"(for (uint layer = 0; layer < layerCount; ++layer) { for (uint y = 0; y < size.y; ++y) { for (uint x = 0; x < size.x; ++x) { uint value = )" << kComputeExpectedValueGLSL << ";\n" << prefix << "vec4 expected = " << GetExpectedPixelValue(format) << ";\n" << prefix << R"(vec4 pixel = imageLoad(storageImage0, )"; if (is2DArray) { ostream << "ivec3(x, y, layer));"; } else { ostream << "ivec2(x, y));"; } ostream << R"( if (!IsEqualTo(pixel, expected)) { return false; } } } } return true; })"; return ostream.str(); } std::string CommonWriteOnlyTestCode(wgpu::TextureFormat format, bool is2DArray = false) { std::ostringstream ostream; const char* prefix = utils::GetColorTextureComponentTypePrefix(format); ostream << R"( #version 450 )" << GetGLSLImageDeclaration(format, "writeonly", is2DArray, 0) << R"( void main() { )"; if (is2DArray) { ostream << R"(ivec3 size = imageSize(storageImage0); const uint layerCount = size.z; )"; } else { ostream << R"(ivec2 size = imageSize(storageImage0); const uint layerCount = 1; )"; } ostream << R"(for (uint layer = 0; layer < layerCount; ++layer) { for (uint y = 0; y < size.y; ++y) { for (uint x = 0; x < size.x; ++x) { uint value = )" << kComputeExpectedValueGLSL << ";\n" << prefix << "vec4 expected = " << GetExpectedPixelValue(format) << ";\n"; if (is2DArray) { ostream << "ivec3 texcoord = ivec3(x, y, layer);\n"; } else { ostream << "ivec2 texcoord = ivec2(x, y);\n"; } ostream << R"( imageStore(storageImage0, texcoord, expected); } } } })"; return ostream.str(); } std::string CommonReadWriteTestCode(wgpu::TextureFormat format, bool is2DArray = false) { std::ostringstream ostream; ostream << R"( #version 450 )" << GetGLSLImageDeclaration(format, "writeonly", is2DArray, 0) << GetGLSLImageDeclaration(format, "readonly", is2DArray, 1) << R"( void main() { )"; if (is2DArray) { ostream << R"(ivec3 size = imageSize(storageImage0); const uint layerCount = size.z; )"; } else { ostream << R"(ivec2 size = imageSize(storageImage0); const uint layerCount = 1; )"; } ostream << R"(for (uint layer = 0; layer < layerCount; ++layer) { for (uint y = 0; y < size.y; ++y) { for (uint x = 0; x < size.x; ++x) {)" "\n"; if (is2DArray) { ostream << "ivec3 texcoord = ivec3(x, y, layer);\n"; } else { ostream << "ivec2 texcoord = ivec2(x, y);\n"; } ostream << R"( imageStore(storageImage0, texcoord, imageLoad(storageImage1, texcoord)); } } } })"; return ostream.str(); } static std::vector GetExpectedData(wgpu::TextureFormat format, uint32_t arrayLayerCount = 1) { const uint32_t texelSizeInBytes = utils::GetTexelBlockSizeInBytes(format); std::vector outputData(texelSizeInBytes * kWidth * kHeight * arrayLayerCount); for (uint32_t i = 0; i < outputData.size() / texelSizeInBytes; ++i) { uint8_t* pixelValuePtr = &outputData[i * texelSizeInBytes]; const uint32_t x = i % kWidth; const uint32_t y = (i % (kWidth * kHeight)) / kWidth; const uint32_t arrayLayer = i / (kWidth * kHeight); FillExpectedData(pixelValuePtr, format, x, y, arrayLayer); } return outputData; } wgpu::Texture CreateTexture(wgpu::TextureFormat format, wgpu::TextureUsage usage, uint32_t width = kWidth, uint32_t height = kHeight, uint32_t arrayLayerCount = 1) { wgpu::TextureDescriptor descriptor; descriptor.size = {width, height, 1}; descriptor.format = format; descriptor.usage = usage; descriptor.arrayLayerCount = arrayLayerCount; return device.CreateTexture(&descriptor); } wgpu::Buffer CreateEmptyBufferForTextureCopy(uint32_t texelSize, uint32_t arrayLayerCount = 1) { ASSERT(kWidth * texelSize <= kTextureBytesPerRowAlignment); const size_t uploadBufferSize = kTextureBytesPerRowAlignment * (kHeight * arrayLayerCount - 1) + kWidth * texelSize; wgpu::BufferDescriptor descriptor; descriptor.size = uploadBufferSize; descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; return device.CreateBuffer(&descriptor); } wgpu::Texture CreateTextureWithTestData(const std::vector& initialTextureData, wgpu::TextureFormat format) { uint32_t texelSize = utils::GetTexelBlockSizeInBytes(format); ASSERT(kWidth * texelSize <= kTextureBytesPerRowAlignment); const uint32_t bytesPerTextureRow = texelSize * kWidth; const uint32_t arrayLayerCount = static_cast(initialTextureData.size() / texelSize / (kWidth * kHeight)); const size_t uploadBufferSize = kTextureBytesPerRowAlignment * (kHeight * arrayLayerCount - 1) + kWidth * bytesPerTextureRow; std::vector uploadBufferData(uploadBufferSize); for (uint32_t layer = 0; layer < arrayLayerCount; ++layer) { const size_t initialDataOffset = bytesPerTextureRow * kHeight * layer; for (size_t y = 0; y < kHeight; ++y) { for (size_t x = 0; x < bytesPerTextureRow; ++x) { uint8_t data = initialTextureData[initialDataOffset + bytesPerTextureRow * y + x]; size_t indexInUploadBuffer = (kHeight * layer + y) * kTextureBytesPerRowAlignment + x; uploadBufferData[indexInUploadBuffer] = data; } } } wgpu::Buffer uploadBuffer = utils::CreateBufferFromData(device, uploadBufferData.data(), uploadBufferSize, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); wgpu::Texture outputTexture = CreateTexture(format, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopyDst, kWidth, kHeight, arrayLayerCount); const wgpu::Extent3D copyExtent = {kWidth, kHeight, 1}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); // TODO(jiawei.shao@intel.com): copy multiple array layers in one CopyBufferToTexture() when // it is supported. for (uint32_t layer = 0; layer < arrayLayerCount; ++layer) { wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView( uploadBuffer, kTextureBytesPerRowAlignment * kHeight * layer, kTextureBytesPerRowAlignment, 0); wgpu::TextureCopyView textureCopyView; textureCopyView.texture = outputTexture; textureCopyView.arrayLayer = layer; encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, ©Extent); } wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); return outputTexture; } wgpu::ComputePipeline CreateComputePipeline(const char* computeShader) { wgpu::ShaderModule csModule = utils::CreateShaderModule(device, utils::SingleShaderStage::Compute, computeShader); wgpu::ComputePipelineDescriptor computeDescriptor; computeDescriptor.layout = nullptr; computeDescriptor.computeStage.module = csModule; computeDescriptor.computeStage.entryPoint = "main"; return device.CreateComputePipeline(&computeDescriptor); } wgpu::RenderPipeline CreateRenderPipeline(const char* vertexShader, const char* fragmentShader) { wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vertexShader); wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fragmentShader); utils::ComboRenderPipelineDescriptor desc(device); desc.vertexStage.module = vsModule; desc.cFragmentStage.module = fsModule; desc.cColorStates[0].format = kOutputAttachmentFormat; desc.primitiveTopology = wgpu::PrimitiveTopology::PointList; return device.CreateRenderPipeline(&desc); } void CheckDrawsGreen(const char* vertexShader, const char* fragmentShader, wgpu::Texture readonlyStorageTexture) { wgpu::RenderPipeline pipeline = CreateRenderPipeline(vertexShader, fragmentShader); wgpu::BindGroup bindGroup = utils::MakeBindGroup( device, pipeline.GetBindGroupLayout(0), {{0, readonlyStorageTexture.CreateView()}}); // Clear the output attachment to red at the beginning of the render pass. wgpu::Texture outputTexture = CreateTexture(kOutputAttachmentFormat, wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc, 1, 1); utils::ComboRenderPassDescriptor renderPassDescriptor({outputTexture.CreateView()}); renderPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; renderPassDescriptor.cColorAttachments[0].clearColor = {1.f, 0.f, 0.f, 1.f}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); renderPassEncoder.SetBindGroup(0, bindGroup); renderPassEncoder.SetPipeline(pipeline); renderPassEncoder.Draw(1); renderPassEncoder.EndPass(); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); // Check if the contents in the output texture are all as expected (green). EXPECT_PIXEL_RGBA8_EQ(RGBA8::kGreen, outputTexture, 0, 0); } void CheckResultInStorageBuffer(wgpu::Texture readonlyStorageTexture, const std::string& computeShader) { wgpu::ComputePipeline pipeline = CreateComputePipeline(computeShader.c_str()); // Clear the content of the result buffer into 0. constexpr uint32_t kInitialValue = 0; wgpu::Buffer resultBuffer = utils::CreateBufferFromData(device, &kInitialValue, sizeof(kInitialValue), wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, readonlyStorageTexture.CreateView()}, {1, resultBuffer}}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computeEncoder = encoder.BeginComputePass(); computeEncoder.SetBindGroup(0, bindGroup); computeEncoder.SetPipeline(pipeline); computeEncoder.Dispatch(1); computeEncoder.EndPass(); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); // Check if the contents in the result buffer are what we expect. constexpr uint32_t kExpectedValue = 1u; EXPECT_BUFFER_U32_RANGE_EQ(&kExpectedValue, resultBuffer, 0, 1u); } void WriteIntoStorageTextureInRenderPass(wgpu::Texture writeonlyStorageTexture, const char* kVertexShader, const char* kFragmentShader) { // Create a render pipeline that writes the expected pixel values into the storage texture // without fragment shader outputs. wgpu::RenderPipeline pipeline = CreateRenderPipeline(kVertexShader, kFragmentShader); wgpu::BindGroup bindGroup = utils::MakeBindGroup( device, pipeline.GetBindGroupLayout(0), {{0, writeonlyStorageTexture.CreateView()}}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); // TODO(jiawei.shao@intel.com): remove the output attachment when Dawn supports beginning a // render pass with no attachments. wgpu::Texture dummyOutputTexture = CreateTexture(kOutputAttachmentFormat, wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc, 1, 1); utils::ComboRenderPassDescriptor renderPassDescriptor({dummyOutputTexture.CreateView()}); wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); renderPassEncoder.SetBindGroup(0, bindGroup); renderPassEncoder.SetPipeline(pipeline); renderPassEncoder.Draw(1); renderPassEncoder.EndPass(); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); } void WriteIntoStorageTextureInComputePass(wgpu::Texture writeonlyStorageTexture, const char* computeShader) { // Create a compute pipeline that writes the expected pixel values into the storage texture. wgpu::ComputePipeline pipeline = CreateComputePipeline(computeShader); wgpu::BindGroup bindGroup = utils::MakeBindGroup( device, pipeline.GetBindGroupLayout(0), {{0, writeonlyStorageTexture.CreateView()}}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass(); computePassEncoder.SetBindGroup(0, bindGroup); computePassEncoder.SetPipeline(pipeline); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); } void ReadWriteIntoStorageTextureInComputePass(wgpu::Texture readonlyStorageTexture, wgpu::Texture writeonlyStorageTexture, const char* computeShader) { // Create a compute pipeline that writes the expected pixel values into the storage texture. wgpu::ComputePipeline pipeline = CreateComputePipeline(computeShader); wgpu::BindGroup bindGroup = utils::MakeBindGroup( device, pipeline.GetBindGroupLayout(0), {{0, writeonlyStorageTexture.CreateView()}, {1, readonlyStorageTexture.CreateView()}}); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); wgpu::ComputePassEncoder computePassEncoder = encoder.BeginComputePass(); computePassEncoder.SetBindGroup(0, bindGroup); computePassEncoder.SetPipeline(pipeline); computePassEncoder.Dispatch(1); computePassEncoder.EndPass(); wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); } void CheckOutputStorageTexture(wgpu::Texture writeonlyStorageTexture, wgpu::TextureFormat format, uint32_t arrayLayerCount = 1) { const uint32_t texelSize = utils::GetTexelBlockSizeInBytes(format); const std::vector& expectedData = GetExpectedData(format, arrayLayerCount); CheckOutputStorageTexture(writeonlyStorageTexture, texelSize, expectedData); } void CheckOutputStorageTexture(wgpu::Texture writeonlyStorageTexture, uint32_t texelSize, const std::vector& expectedData) { // Copy the content from the write-only storage texture to the result buffer. const uint32_t arrayLayerCount = static_cast(expectedData.size() / texelSize / (kWidth * kHeight)); wgpu::Buffer resultBuffer = CreateEmptyBufferForTextureCopy(texelSize, arrayLayerCount); const wgpu::Extent3D copyExtent = {kWidth, kHeight, 1}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); // TODO(jiawei.shao@intel.com): copy multiple array layers in one CopyTextureToBuffer() when // it is supported. for (uint32_t layer = 0; layer < arrayLayerCount; ++layer) { wgpu::TextureCopyView textureCopyView; textureCopyView.texture = writeonlyStorageTexture; textureCopyView.arrayLayer = layer; const uint64_t bufferOffset = kTextureBytesPerRowAlignment * kHeight * layer; wgpu::BufferCopyView bufferCopyView = utils::CreateBufferCopyView( resultBuffer, bufferOffset, kTextureBytesPerRowAlignment, 0); encoder.CopyTextureToBuffer(&textureCopyView, &bufferCopyView, ©Extent); } wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); // Check if the contents in the result buffer are what we expect. for (size_t layer = 0; layer < arrayLayerCount; ++layer) { for (size_t y = 0; y < kHeight; ++y) { const size_t resultBufferOffset = kTextureBytesPerRowAlignment * (kHeight * layer + y); const size_t expectedDataOffset = texelSize * kWidth * (kHeight * layer + y); EXPECT_BUFFER_U32_RANGE_EQ( reinterpret_cast(expectedData.data() + expectedDataOffset), resultBuffer, resultBufferOffset, kWidth); } } } static constexpr size_t kWidth = 4u; static constexpr size_t kHeight = 4u; static constexpr wgpu::TextureFormat kOutputAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm; const char* kSimpleVertexShader = R"( #version 450 void main() { gl_Position = vec4(0.f, 0.f, 0.f, 1.f); gl_PointSize = 1.0f; })"; const char* kComputeExpectedValueGLSL = "1 + x + size.x * (y + size.y * layer)"; }; // Test that using read-only storage texture and write-only storage texture in BindGroupLayout is // valid on all backends. This test is a regression test for chromium:1061156 and passes by not // asserting or crashing. TEST_P(StorageTextureTests, BindGroupLayoutWithStorageTextureBindingType) { // wgpu::BindingType::ReadonlyStorageTexture is a valid binding type to create a bind group // layout. { wgpu::BindGroupLayoutEntry entry = {0, wgpu::ShaderStage::Compute, wgpu::BindingType::ReadonlyStorageTexture}; entry.storageTextureFormat = wgpu::TextureFormat::R32Float; wgpu::BindGroupLayoutDescriptor descriptor; descriptor.entryCount = 1; descriptor.entries = &entry; device.CreateBindGroupLayout(&descriptor); } // wgpu::BindingType::WriteonlyStorageTexture is a valid binding type to create a bind group // layout. { wgpu::BindGroupLayoutEntry entry = {0, wgpu::ShaderStage::Compute, wgpu::BindingType::WriteonlyStorageTexture}; entry.storageTextureFormat = wgpu::TextureFormat::R32Float; wgpu::BindGroupLayoutDescriptor descriptor; descriptor.entryCount = 1; descriptor.entries = &entry; device.CreateBindGroupLayout(&descriptor); } } // Test that read-only storage textures are supported in compute shader. TEST_P(StorageTextureTests, ReadonlyStorageTextureInComputeShader) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); for (wgpu::TextureFormat format : utils::kAllTextureFormats) { if (!utils::TextureFormatSupportsStorageTexture(format)) { continue; } // Prepare the read-only storage texture and fill it with the expected data. const std::vector kInitialTextureData = GetExpectedData(format); wgpu::Texture readonlyStorageTexture = CreateTextureWithTestData(kInitialTextureData, format); // Create a compute shader that reads the pixels from the read-only storage texture and // writes 1 to DstBuffer if they all have to expected value. std::ostringstream csStream; csStream << R"( #version 450 layout(set = 0, binding = 1, std430) buffer DstBuffer { uint result; } dstBuffer; )" << CommonReadOnlyTestCode(format) << R"( void main() { if (doTest()) { dstBuffer.result = 1; } else { dstBuffer.result = 0; } })"; CheckResultInStorageBuffer(readonlyStorageTexture, csStream.str()); } } // Test that read-only storage textures are supported in vertex shader. TEST_P(StorageTextureTests, ReadonlyStorageTextureInVertexShader) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsSpvcParserBeingUsed()); for (wgpu::TextureFormat format : utils::kAllTextureFormats) { if (!utils::TextureFormatSupportsStorageTexture(format)) { continue; } // Prepare the read-only storage texture and fill it with the expected data. const std::vector kInitialTextureData = GetExpectedData(format); wgpu::Texture readonlyStorageTexture = CreateTextureWithTestData(kInitialTextureData, format); // Create a rendering pipeline that reads the pixels from the read-only storage texture and // uses green as the output color, otherwise uses red instead. std::ostringstream vsStream; vsStream << R"( #version 450 layout(location = 0) out vec4 o_color; )" << CommonReadOnlyTestCode(format) << R"( void main() { gl_Position = vec4(0.f, 0.f, 0.f, 1.f); if (doTest()) { o_color = vec4(0.f, 1.f, 0.f, 1.f); } else { o_color = vec4(1.f, 0.f, 0.f, 1.f); } gl_PointSize = 1.0f; })"; const char* kFragmentShader = R"( #version 450 layout(location = 0) in vec4 o_color; layout(location = 0) out vec4 fragColor; void main() { fragColor = o_color; })"; CheckDrawsGreen(vsStream.str().c_str(), kFragmentShader, readonlyStorageTexture); } } // Test that read-only storage textures are supported in fragment shader. TEST_P(StorageTextureTests, ReadonlyStorageTextureInFragmentShader) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsSpvcParserBeingUsed()); for (wgpu::TextureFormat format : utils::kAllTextureFormats) { if (!utils::TextureFormatSupportsStorageTexture(format)) { continue; } // Prepare the read-only storage texture and fill it with the expected data. const std::vector kInitialTextureData = GetExpectedData(format); wgpu::Texture readonlyStorageTexture = CreateTextureWithTestData(kInitialTextureData, format); // Create a rendering pipeline that reads the pixels from the read-only storage texture and // uses green as the output color if the pixel value is expected, otherwise uses red // instead. std::ostringstream fsStream; fsStream << R"( #version 450 layout(location = 0) out vec4 o_color; )" << CommonReadOnlyTestCode(format) << R"( void main() { if (doTest()) { o_color = vec4(0.f, 1.f, 0.f, 1.f); } else { o_color = vec4(1.f, 0.f, 0.f, 1.f); } })"; CheckDrawsGreen(kSimpleVertexShader, fsStream.str().c_str(), readonlyStorageTexture); } } // Test that write-only storage textures are supported in compute shader. TEST_P(StorageTextureTests, WriteonlyStorageTextureInComputeShader) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); for (wgpu::TextureFormat format : utils::kAllTextureFormats) { if (!utils::TextureFormatSupportsStorageTexture(format)) { continue; } // TODO(jiawei.shao@intel.com): investigate why this test fails with RGBA8Snorm on Linux // Intel OpenGL driver. if (format == wgpu::TextureFormat::RGBA8Snorm && IsIntel() && IsOpenGL() && IsLinux()) { continue; } // Prepare the write-only storage texture. wgpu::Texture writeonlyStorageTexture = CreateTexture(format, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopySrc); // Write the expected pixel values into the write-only storage texture. const std::string computeShader = CommonWriteOnlyTestCode(format); WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, computeShader.c_str()); // Verify the pixel data in the write-only storage texture is expected. CheckOutputStorageTexture(writeonlyStorageTexture, format); } } // Test that reading from one read-only storage texture then writing into another write-only storage // texture in one dispatch are supported in compute shader. TEST_P(StorageTextureTests, ReadWriteDifferentStorageTextureInOneDispatchInComputeShader) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); for (wgpu::TextureFormat format : utils::kAllTextureFormats) { if (!utils::TextureFormatSupportsStorageTexture(format)) { continue; } // TODO(jiawei.shao@intel.com): investigate why this test fails with RGBA8Snorm on Linux // Intel OpenGL driver. if (format == wgpu::TextureFormat::RGBA8Snorm && IsIntel() && IsOpenGL() && IsLinux()) { continue; } // Prepare the read-only storage texture. const std::vector kInitialTextureData = GetExpectedData(format); wgpu::Texture readonlyStorageTexture = CreateTextureWithTestData(kInitialTextureData, format); // Prepare the write-only storage texture. wgpu::Texture writeonlyStorageTexture = CreateTexture(format, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopySrc); // Write the expected pixel values into the write-only storage texture. const std::string computeShader = CommonReadWriteTestCode(format); ReadWriteIntoStorageTextureInComputePass(readonlyStorageTexture, writeonlyStorageTexture, computeShader.c_str()); // Verify the pixel data in the write-only storage texture is expected. CheckOutputStorageTexture(writeonlyStorageTexture, format); } } // Test that write-only storage textures are supported in fragment shader. TEST_P(StorageTextureTests, WriteonlyStorageTextureInFragmentShader) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); for (wgpu::TextureFormat format : utils::kAllTextureFormats) { if (!utils::TextureFormatSupportsStorageTexture(format)) { continue; } // TODO(jiawei.shao@intel.com): investigate why this test fails with RGBA8Snorm on Linux // Intel OpenGL driver. if (format == wgpu::TextureFormat::RGBA8Snorm && IsIntel() && IsOpenGL() && IsLinux()) { continue; } // Prepare the write-only storage texture. wgpu::Texture writeonlyStorageTexture = CreateTexture(format, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopySrc); // Write the expected pixel values into the write-only storage texture. const std::string fragmentShader = CommonWriteOnlyTestCode(format); WriteIntoStorageTextureInRenderPass(writeonlyStorageTexture, kSimpleVertexShader, fragmentShader.c_str()); // Verify the pixel data in the write-only storage texture is expected. CheckOutputStorageTexture(writeonlyStorageTexture, format); } } // Verify 2D array read-only storage texture works correctly. TEST_P(StorageTextureTests, Readonly2DArrayStorageTexture) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsSpvcParserBeingUsed()); constexpr uint32_t kArrayLayerCount = 3u; constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::R32Uint; const std::vector initialTextureData = GetExpectedData(kTextureFormat, kArrayLayerCount); wgpu::Texture readonlyStorageTexture = CreateTextureWithTestData(initialTextureData, kTextureFormat); // Create a compute shader that reads the pixels from the read-only storage texture and writes 1 // to DstBuffer if they all have to expected value. std::ostringstream csStream; csStream << R"( #version 450 layout (set = 0, binding = 1, std430) buffer DstBuffer { uint result; } dstBuffer; )" << CommonReadOnlyTestCode(kTextureFormat, true) << R"( void main() { if (doTest()) { dstBuffer.result = 1; } else { dstBuffer.result = 0; } })"; CheckResultInStorageBuffer(readonlyStorageTexture, csStream.str()); } // Verify 2D array write-only storage texture works correctly. TEST_P(StorageTextureTests, Writeonly2DArrayStorageTexture) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); constexpr uint32_t kArrayLayerCount = 3u; constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::R32Uint; // Prepare the write-only storage texture. wgpu::Texture writeonlyStorageTexture = CreateTexture(kTextureFormat, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopySrc, kWidth, kHeight, kArrayLayerCount); // Write the expected pixel values into the write-only storage texture. const std::string computeShader = CommonWriteOnlyTestCode(kTextureFormat, true); WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, computeShader.c_str()); // Verify the pixel data in the write-only storage texture is expected. CheckOutputStorageTexture(writeonlyStorageTexture, kTextureFormat, kArrayLayerCount); } DAWN_INSTANTIATE_TEST(StorageTextureTests, D3D12Backend(), MetalBackend(), OpenGLBackend(), VulkanBackend()); class StorageTextureZeroInitTests : public StorageTextureTests { public: static std::vector GetExpectedData() { constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::R32Uint; const uint32_t texelSizeInBytes = utils::GetTexelBlockSizeInBytes(kTextureFormat); const size_t kDataCount = texelSizeInBytes * kWidth * kHeight; std::vector outputData(kDataCount, 0); uint32_t* outputDataPtr = reinterpret_cast(&outputData[0]); *outputDataPtr = 1u; return outputData; } const char* kCommonReadOnlyZeroInitTestCode = R"( bool doTest() { for (uint y = 0; y < 4; ++y) { for (uint x = 0; x < 4; ++x) { uvec4 pixel = imageLoad(srcImage, ivec2(x, y)); if (pixel != uvec4(0, 0, 0, 1u)) { return false; } } } return true; })"; const char* kCommonWriteOnlyZeroInitTestCode = R"( #version 450 layout(set = 0, binding = 0, r32ui) uniform writeonly uimage2D dstImage; void main() { imageStore(dstImage, ivec2(0, 0), uvec4(1u, 0, 0, 1u)); })"; }; // Verify that the texture is correctly cleared to 0 before its first usage as a read-only storage // texture in a render pass. TEST_P(StorageTextureZeroInitTests, ReadonlyStorageTextureClearsToZeroInRenderPass) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsSpvcParserBeingUsed()); wgpu::Texture readonlyStorageTexture = CreateTexture(wgpu::TextureFormat::R32Uint, wgpu::TextureUsage::Storage); // Create a rendering pipeline that reads the pixels from the read-only storage texture and uses // green as the output color, otherwise uses red instead. const char* kVertexShader = kSimpleVertexShader; const std::string kFragmentShader = std::string(R"( #version 450 layout(set = 0, binding = 0, r32ui) uniform readonly uimage2D srcImage; layout(location = 0) out vec4 o_color;)") + kCommonReadOnlyZeroInitTestCode + R"( void main() { if (doTest()) { o_color = vec4(0.f, 1.f, 0.f, 1.f); } else { o_color = vec4(1.f, 0.f, 0.f, 1.f); } })"; CheckDrawsGreen(kVertexShader, kFragmentShader.c_str(), readonlyStorageTexture); } // Verify that the texture is correctly cleared to 0 before its first usage as a read-only storage // texture in a compute pass. TEST_P(StorageTextureZeroInitTests, ReadonlyStorageTextureClearsToZeroInComputePass) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsSpvcParserBeingUsed()); wgpu::Texture readonlyStorageTexture = CreateTexture(wgpu::TextureFormat::R32Uint, wgpu::TextureUsage::Storage); // Create a compute shader that reads the pixels from the read-only storage texture and writes 1 // to DstBuffer if they all have to expected value. const std::string kComputeShader = std::string(R"( #version 450 layout (set = 0, binding = 0, r32ui) uniform readonly uimage2D srcImage; layout (set = 0, binding = 1, std430) buffer DstBuffer { uint result; } dstBuffer;)") + kCommonReadOnlyZeroInitTestCode + R"( void main() { if (doTest()) { dstBuffer.result = 1; } else { dstBuffer.result = 0; } })"; CheckResultInStorageBuffer(readonlyStorageTexture, kComputeShader); } // Verify that the texture is correctly cleared to 0 before its first usage as a write-only storage // storage texture in a render pass. TEST_P(StorageTextureZeroInitTests, WriteonlyStorageTextureClearsToZeroInRenderPass) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); // Prepare the write-only storage texture. constexpr uint32_t kTexelSizeR32Uint = 4u; wgpu::Texture writeonlyStorageTexture = CreateTexture( wgpu::TextureFormat::R32Uint, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopySrc); WriteIntoStorageTextureInRenderPass(writeonlyStorageTexture, kSimpleVertexShader, kCommonWriteOnlyZeroInitTestCode); CheckOutputStorageTexture(writeonlyStorageTexture, kTexelSizeR32Uint, GetExpectedData()); } // Verify that the texture is correctly cleared to 0 before its first usage as a write-only storage // texture in a compute pass. TEST_P(StorageTextureZeroInitTests, WriteonlyStorageTextureClearsToZeroInComputePass) { // When we run dawn_end2end_tests with "--use-spvc-parser", extracting the binding type of a // read-only image will always return shaderc_spvc_binding_type_writeonly_storage_texture. // TODO(jiawei.shao@intel.com): enable this test when we specify "--use-spvc-parser" after the // bug in spvc parser is fixed. DAWN_SKIP_TEST_IF(IsD3D12() && IsSpvcParserBeingUsed()); // Prepare the write-only storage texture. constexpr uint32_t kTexelSizeR32Uint = 4u; wgpu::Texture writeonlyStorageTexture = CreateTexture( wgpu::TextureFormat::R32Uint, wgpu::TextureUsage::Storage | wgpu::TextureUsage::CopySrc); WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, kCommonWriteOnlyZeroInitTestCode); CheckOutputStorageTexture(writeonlyStorageTexture, kTexelSizeR32Uint, GetExpectedData()); } DAWN_INSTANTIATE_TEST(StorageTextureZeroInitTests, D3D12Backend({"nonzero_clear_resources_on_creation_for_testing"}), OpenGLBackend({"nonzero_clear_resources_on_creation_for_testing"}), MetalBackend({"nonzero_clear_resources_on_creation_for_testing"}), VulkanBackend({"nonzero_clear_resources_on_creation_for_testing"}));