From 42e648c1cfdbe08b3c38679e7e36423bb96b1d6b Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Fri, 4 Feb 2022 08:34:54 +0000 Subject: [PATCH] Implement creating and using 1D texture views. - Adds a test to sample a 1D texture. - Adds a test writing to a 1D texture as a storage texture. - Reworks some of the StorageTextureTests helper code to allow passing custom sizes (since 1D textures must have height=1). - Deletes some dead code leftover from readonly storage textures. - Adds validation tests for 1D texture view creation. Bug: dawn:814 Change-Id: I279856569f4fc6c9a7a5023a42bfa50d444158ea Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/79106 Reviewed-by: Austin Eng Commit-Queue: Corentin Wallez --- src/dawn_native/Texture.cpp | 21 +- src/dawn_native/d3d12/TextureD3D12.cpp | 15 +- src/dawn_native/metal/TextureMTL.mm | 3 +- src/dawn_native/vulkan/TextureVk.cpp | 6 +- src/tests/end2end/StorageTextureTests.cpp | 249 +++++++----------- src/tests/end2end/TextureViewTests.cpp | 68 +++++ .../validation/TextureViewValidationTests.cpp | 73 ++++- 7 files changed, 250 insertions(+), 185 deletions(-) diff --git a/src/dawn_native/Texture.cpp b/src/dawn_native/Texture.cpp index 9465992ec3..b187177391 100644 --- a/src/dawn_native/Texture.cpp +++ b/src/dawn_native/Texture.cpp @@ -47,7 +47,6 @@ namespace dawn::native { return {}; } - // TODO(crbug.com/dawn/814): Implement for 1D texture. bool IsTextureViewDimensionCompatibleWithTextureDimension( wgpu::TextureViewDimension textureViewDimension, wgpu::TextureDimension textureDimension) { @@ -62,13 +61,13 @@ namespace dawn::native { return textureDimension == wgpu::TextureDimension::e3D; case wgpu::TextureViewDimension::e1D: + return textureDimension == wgpu::TextureDimension::e1D; + case wgpu::TextureViewDimension::Undefined: - break; + UNREACHABLE(); } - UNREACHABLE(); } - // TODO(crbug.com/dawn/814): Implement for 1D texture. bool IsArrayLayerValidForTextureViewDimension( wgpu::TextureViewDimension textureViewDimension, uint32_t textureViewArrayLayer) { @@ -82,12 +81,12 @@ namespace dawn::native { return textureViewArrayLayer == 6u; case wgpu::TextureViewDimension::CubeArray: return textureViewArrayLayer % 6 == 0; - case wgpu::TextureViewDimension::e1D: + return textureViewArrayLayer == 1u; + case wgpu::TextureViewDimension::Undefined: - break; + UNREACHABLE(); } - UNREACHABLE(); } MaybeError ValidateSampleCount(const TextureDescriptor* descriptor, @@ -159,15 +158,14 @@ namespace dawn::native { texture->GetSize().height); break; + case wgpu::TextureViewDimension::e1D: case wgpu::TextureViewDimension::e2D: case wgpu::TextureViewDimension::e2DArray: case wgpu::TextureViewDimension::e3D: break; - case wgpu::TextureViewDimension::e1D: case wgpu::TextureViewDimension::Undefined: UNREACHABLE(); - break; } return {}; @@ -362,12 +360,9 @@ namespace dawn::native { ASSERT(!texture->IsError()); DAWN_TRY(ValidateTextureViewDimension(descriptor->dimension)); - DAWN_INVALID_IF(descriptor->dimension == wgpu::TextureViewDimension::e1D, - "1D texture views aren't supported (yet)."); - DAWN_TRY(ValidateTextureFormat(descriptor->format)); - DAWN_TRY(ValidateTextureAspect(descriptor->aspect)); + DAWN_INVALID_IF( SelectFormatAspects(texture->GetFormat(), descriptor->aspect) == Aspect::None, "Texture format (%s) does not have the texture view's selected aspect (%s).", diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp index 511892ef04..9db78be147 100644 --- a/src/dawn_native/d3d12/TextureD3D12.cpp +++ b/src/dawn_native/d3d12/TextureD3D12.cpp @@ -1264,7 +1264,6 @@ namespace dawn::native::d3d12 { // D3D12_SRV_DIMENSION_TEXTURE2DMS. // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_srv // https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_tex2d_array_srv - // TODO(crbug.com/dawn/814): support 1D textures. if (GetTexture()->IsMultisampledTexture()) { switch (descriptor->dimension) { case wgpu::TextureViewDimension::e2DArray: @@ -1280,6 +1279,13 @@ namespace dawn::native::d3d12 { } } else { switch (descriptor->dimension) { + case wgpu::TextureViewDimension::e1D: + mSrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D; + mSrvDesc.Texture1D.MipLevels = descriptor->mipLevelCount; + mSrvDesc.Texture1D.MostDetailedMip = descriptor->baseMipLevel; + mSrvDesc.Texture1D.ResourceMinLODClamp = 0; + break; + case wgpu::TextureViewDimension::e2D: case wgpu::TextureViewDimension::e2DArray: ASSERT(texture->GetDimension() == wgpu::TextureDimension::e2D); @@ -1310,7 +1316,6 @@ namespace dawn::native::d3d12 { mSrvDesc.Texture3D.ResourceMinLODClamp = 0; break; - case wgpu::TextureViewDimension::e1D: case wgpu::TextureViewDimension::Undefined: UNREACHABLE(); } @@ -1345,6 +1350,10 @@ namespace dawn::native::d3d12 { ASSERT(!GetTexture()->IsMultisampledTexture()); switch (GetDimension()) { + case wgpu::TextureViewDimension::e1D: + uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D; + uavDesc.Texture1D.MipSlice = GetBaseMipLevel(); + break; case wgpu::TextureViewDimension::e2D: case wgpu::TextureViewDimension::e2DArray: uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; @@ -1359,8 +1368,6 @@ namespace dawn::native::d3d12 { uavDesc.Texture3D.WSize = GetTexture()->GetDepth() >> GetBaseMipLevel(); uavDesc.Texture3D.MipSlice = GetBaseMipLevel(); break; - // TODO(crbug.com/dawn/814): support 1D textures. - case wgpu::TextureViewDimension::e1D: // Cube and Cubemap can't be used as storage texture. So there is no need to create UAV // descriptor for them. case wgpu::TextureViewDimension::Cube: diff --git a/src/dawn_native/metal/TextureMTL.mm b/src/dawn_native/metal/TextureMTL.mm index 5230423f13..7f2db5c4cb 100644 --- a/src/dawn_native/metal/TextureMTL.mm +++ b/src/dawn_native/metal/TextureMTL.mm @@ -66,6 +66,8 @@ namespace dawn::native::metal { MTLTextureType MetalTextureViewType(wgpu::TextureViewDimension dimension, unsigned int sampleCount) { switch (dimension) { + case wgpu::TextureViewDimension::e1D: + return MTLTextureType1D; case wgpu::TextureViewDimension::e2D: return (sampleCount > 1) ? MTLTextureType2DMultisample : MTLTextureType2D; case wgpu::TextureViewDimension::e2DArray: @@ -77,7 +79,6 @@ namespace dawn::native::metal { case wgpu::TextureViewDimension::e3D: return MTLTextureType3D; - case wgpu::TextureViewDimension::e1D: case wgpu::TextureViewDimension::Undefined: UNREACHABLE(); } diff --git a/src/dawn_native/vulkan/TextureVk.cpp b/src/dawn_native/vulkan/TextureVk.cpp index 309722d64c..65b7da76c4 100644 --- a/src/dawn_native/vulkan/TextureVk.cpp +++ b/src/dawn_native/vulkan/TextureVk.cpp @@ -38,6 +38,8 @@ namespace dawn::native::vulkan { // Contrary to image types, image view types include arrayness and cubemapness VkImageViewType VulkanImageViewType(wgpu::TextureViewDimension dimension) { switch (dimension) { + case wgpu::TextureViewDimension::e1D: + return VK_IMAGE_VIEW_TYPE_1D; case wgpu::TextureViewDimension::e2D: return VK_IMAGE_VIEW_TYPE_2D; case wgpu::TextureViewDimension::e2DArray: @@ -49,11 +51,9 @@ namespace dawn::native::vulkan { case wgpu::TextureViewDimension::e3D: return VK_IMAGE_VIEW_TYPE_3D; - case wgpu::TextureViewDimension::e1D: case wgpu::TextureViewDimension::Undefined: - break; + UNREACHABLE(); } - UNREACHABLE(); } // Computes which vulkan access type could be required for the given Dawn usage. diff --git a/src/tests/end2end/StorageTextureTests.cpp b/src/tests/end2end/StorageTextureTests.cpp index 211b16a6d0..88c66c5d32 100644 --- a/src/tests/end2end/StorageTextureTests.cpp +++ b/src/tests/end2end/StorageTextureTests.cpp @@ -18,6 +18,7 @@ #include "common/Constants.h" #include "common/Math.h" #include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/TestUtils.h" #include "utils/TextureUtils.h" #include "utils/WGPUHelpers.h" @@ -168,6 +169,9 @@ class StorageTextureTests : public DawnTest { ostream << "@group(0) @binding(" << binding << ") " << "var storageImage" << binding << " : "; switch (dimension) { + case wgpu::TextureViewDimension::e1D: + ostream << "texture_storage_1d"; + break; case wgpu::TextureViewDimension::e2D: ostream << "texture_storage_2d"; break; @@ -295,56 +299,6 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { return ""; } - std::string CommonReadOnlyTestCode( - wgpu::TextureFormat format, - wgpu::TextureViewDimension dimension = wgpu::TextureViewDimension::e2D) { - std::string componentFmt = utils::GetWGSLColorTextureComponentType(format); - auto texelType = "vec4<" + componentFmt + ">"; - std::string sliceCount; - std::string textureLoad; - switch (dimension) { - case wgpu::TextureViewDimension::e2D: - sliceCount = "1"; - textureLoad = "textureLoad(storageImage0, vec2(x, y))"; - break; - case wgpu::TextureViewDimension::e2DArray: - sliceCount = "textureNumLayers(storageImage0)"; - textureLoad = "textureLoad(storageImage0, vec2(x, y), i32(slice))"; - break; - case wgpu::TextureViewDimension::e3D: - sliceCount = "textureDimensions(storageImage0).z"; - textureLoad = "textureLoad(storageImage0, vec3(x, y, slice))"; - break; - default: - UNREACHABLE(); - break; - } - - std::ostringstream ostream; - ostream << GetImageDeclaration(format, "read", dimension, 0) << "\n" - << GetComparisonFunction(format) << "\n"; - ostream << "fn doTest() -> bool {\n"; - ostream << " var size : vec2 = textureDimensions(storageImage0).xy;\n"; - ostream << " let sliceCount : i32 = " << sliceCount << ";\n"; - ostream << " for (var slice : i32 = 0; slice < sliceCount; slice = slice + 1) {\n"; - ostream << " for (var y : i32 = 0; y < size.y; y = y + 1) {\n"; - ostream << " for (var x : i32 = 0; x < size.x; x = x + 1) {\n"; - ostream << " var value : i32 = " << kComputeExpectedValue << ";\n"; - ostream << " var expected : " << texelType << " = " << GetExpectedPixelValue(format) - << ";\n"; - ostream << " var pixel : " << texelType << " = " << textureLoad << ";\n"; - ostream << " if (!IsEqualTo(pixel, expected)) {\n"; - ostream << " return false;\n"; - ostream << " }\n"; - ostream << " }\n"; - ostream << " }\n"; - ostream << " }\n"; - ostream << " return true;\n"; - ostream << "}\n"; - - return ostream.str(); - } - std::string CommonWriteOnlyTestCode( const char* stage, wgpu::TextureFormat format, @@ -353,7 +307,13 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { auto texelType = "vec4<" + componentFmt + ">"; std::string sliceCount; std::string textureStore; + std::string textureSize = "textureDimensions(storageImage0).xy"; switch (dimension) { + case wgpu::TextureViewDimension::e1D: + sliceCount = "1"; + textureStore = "textureStore(storageImage0, x, expected)"; + textureSize = "vec2(textureDimensions(storageImage0), 1)"; + break; case wgpu::TextureViewDimension::e2D: sliceCount = "1"; textureStore = "textureStore(storageImage0, vec2(x, y), expected)"; @@ -381,7 +341,7 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { ostream << "-> @location(0) vec4 "; } ostream << "{\n"; - ostream << " let size : vec2 = textureDimensions(storageImage0).xy;\n"; + ostream << " let size : vec2 = " << textureSize << ";\n"; ostream << " let sliceCount : i32 = " << sliceCount << ";\n"; ostream << " for (var slice : i32 = 0; slice < sliceCount; slice = slice + 1) {\n"; ostream << " for (var y : i32 = 0; y < size.y; y = y + 1) {\n"; @@ -401,53 +361,6 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { return ostream.str(); } - std::string CommonReadWriteTestCode( - wgpu::TextureFormat format, - wgpu::TextureViewDimension dimension = wgpu::TextureViewDimension::e2D) { - std::string sliceCount; - std::string textureStore; - switch (dimension) { - case wgpu::TextureViewDimension::e2D: - sliceCount = "1"; - textureStore = - "textureStore(storageImage0, texcoord, " - "textureLoad(storageImage1, texcoord))"; - break; - case wgpu::TextureViewDimension::e2DArray: - sliceCount = "textureNumLayers(storageImage0)"; - textureStore = - "textureStore(storageImage0, texcoord, slice, " - "textureLoad(storageImage1, texcoord, slice))"; - break; - case wgpu::TextureViewDimension::e3D: - sliceCount = "textureDimensions(storageImage0).z"; - textureStore = - "textureStore(storageImage0, vec3(texcoord, slice), " - "textureLoad(storageImage1, vec3(texcoord, slice)))"; - break; - default: - UNREACHABLE(); - break; - } - - std::ostringstream ostream; - ostream << GetImageDeclaration(format, "write", dimension, 0) << "\n"; - ostream << GetImageDeclaration(format, "read", dimension, 1) << "\n"; - ostream << "@stage(compute) @workgroup_size(1) fn main() {\n"; - ostream << " let size : vec2 = textureDimensions(storageImage0).xy;\n"; - ostream << " let sliceCount : i32 = " << sliceCount << ";\n"; - ostream << " for (var slice : i32 = 0; slice < sliceCount; slice = slice + 1) {\n"; - ostream << " for (var y : i32 = 0; y < size.y; y = y + 1) {\n"; - ostream << " for (var x : i32 = 0; x < size.x; x = x + 1) {\n"; - ostream << " var texcoord : vec2 = vec2(x, y);\n"; - ostream << " " << textureStore << ";\n"; - ostream << " }\n"; - ostream << " }\n"; - ostream << " }\n"; - ostream << "}\n"; - return ostream.str(); - } - static std::vector GetExpectedData(wgpu::TextureFormat format, uint32_t sliceCount = 1) { const uint32_t texelSizeInBytes = utils::GetTexelBlockSizeInBytes(format); @@ -467,28 +380,16 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { wgpu::Texture CreateTexture(wgpu::TextureFormat format, wgpu::TextureUsage usage, - uint32_t width = kWidth, - uint32_t height = kHeight, - uint32_t sliceCount = 1, + const wgpu::Extent3D& size, wgpu::TextureDimension dimension = wgpu::TextureDimension::e2D) { wgpu::TextureDescriptor descriptor; - descriptor.size = {width, height, sliceCount}; + descriptor.size = size; descriptor.dimension = dimension; descriptor.format = format; descriptor.usage = usage; return device.CreateTexture(&descriptor); } - wgpu::Buffer CreateEmptyBufferForTextureCopy(uint32_t texelSize, uint32_t sliceCount = 1) { - ASSERT(kWidth * texelSize <= kTextureBytesPerRowAlignment); - const size_t uploadBufferSize = - kTextureBytesPerRowAlignment * (kHeight * sliceCount - 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, @@ -520,8 +421,8 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); wgpu::Texture outputTexture = CreateTexture( - format, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopyDst, kWidth, - kHeight, sliceCount, utils::ViewDimensionToTextureDimension(dimension)); + format, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopyDst, + {kWidth, kHeight, sliceCount}, utils::ViewDimensionToTextureDimension(dimension)); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); @@ -568,9 +469,9 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { device, pipeline.GetBindGroupLayout(0), {{0, readonlyStorageTexture.CreateView()}}); // Clear the render attachment to red at the beginning of the render pass. - wgpu::Texture outputTexture = - CreateTexture(kRenderAttachmentFormat, - wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, 1, 1); + wgpu::Texture outputTexture = CreateTexture( + kRenderAttachmentFormat, + wgpu::TextureUsage::RenderAttachment | 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}; @@ -634,9 +535,9 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::Texture dummyOutputTexture = - CreateTexture(kRenderAttachmentFormat, - wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, 1, 1); + wgpu::Texture dummyOutputTexture = CreateTexture( + kRenderAttachmentFormat, + wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, {1, 1}); utils::ComboRenderPassDescriptor renderPassDescriptor({dummyOutputTexture.CreateView()}); wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassDescriptor); renderPassEncoder.SetBindGroup(0, bindGroup); @@ -695,40 +596,45 @@ fn IsEqualTo(pixel : vec4, expected : vec4) -> bool { void CheckOutputStorageTexture(wgpu::Texture writeonlyStorageTexture, wgpu::TextureFormat format, - uint32_t sliceCount = 1) { - const uint32_t texelSize = utils::GetTexelBlockSizeInBytes(format); - const std::vector& expectedData = GetExpectedData(format, sliceCount); - CheckOutputStorageTexture(writeonlyStorageTexture, texelSize, expectedData); + const wgpu::Extent3D& size) { + const std::vector& expectedData = GetExpectedData(format, size.depthOrArrayLayers); + CheckOutputStorageTexture(writeonlyStorageTexture, format, size, expectedData); } void CheckOutputStorageTexture(wgpu::Texture writeonlyStorageTexture, - uint32_t texelSize, + wgpu::TextureFormat format, + const wgpu::Extent3D& size, const std::vector& expectedData) { // Copy the content from the write-only storage texture to the result buffer. - const uint32_t sliceCount = - static_cast(expectedData.size() / texelSize / (kWidth * kHeight)); - wgpu::Buffer resultBuffer = CreateEmptyBufferForTextureCopy(texelSize, sliceCount); + wgpu::BufferDescriptor descriptor; + descriptor.size = + utils::RequiredBytesInCopy(kTextureBytesPerRowAlignment, size.height, size, format); + descriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; + wgpu::Buffer resultBuffer = device.CreateBuffer(&descriptor); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - - const wgpu::Extent3D copyExtent = {kWidth, kHeight, sliceCount}; - wgpu::ImageCopyTexture imageCopyTexture = - utils::CreateImageCopyTexture(writeonlyStorageTexture, 0, {0, 0, 0}); - wgpu::ImageCopyBuffer imageCopyBuffer = - utils::CreateImageCopyBuffer(resultBuffer, 0, kTextureBytesPerRowAlignment, kHeight); - encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, ©Extent); + { + wgpu::ImageCopyTexture imageCopyTexture = + utils::CreateImageCopyTexture(writeonlyStorageTexture, 0, {0, 0, 0}); + wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer( + resultBuffer, 0, kTextureBytesPerRowAlignment, size.height); + encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &size); + } wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); // Check if the contents in the result buffer are what we expect. - for (size_t slice = 0; slice < sliceCount; ++slice) { - for (size_t y = 0; y < kHeight; ++y) { + uint32_t texelSize = utils::GetTexelBlockSizeInBytes(format); + ASSERT(size.width * texelSize <= kTextureBytesPerRowAlignment); + + for (size_t z = 0; z < size.depthOrArrayLayers; ++z) { + for (size_t y = 0; y < size.height; ++y) { const size_t resultBufferOffset = - kTextureBytesPerRowAlignment * (kHeight * slice + y); - const size_t expectedDataOffset = texelSize * kWidth * (kHeight * slice + y); + kTextureBytesPerRowAlignment * (size.height * z + y); + const size_t expectedDataOffset = texelSize * size.width * (size.height * z + y); EXPECT_BUFFER_U32_RANGE_EQ( reinterpret_cast(expectedData.data() + expectedDataOffset), - resultBuffer, resultBufferOffset, kWidth); + resultBuffer, resultBufferOffset, texelSize); } } } @@ -769,14 +675,15 @@ TEST_P(StorageTextureTests, WriteonlyStorageTextureInComputeShader) { // Prepare the write-only storage texture. wgpu::Texture writeonlyStorageTexture = - CreateTexture(format, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc); + CreateTexture(format, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, + {kWidth, kHeight}); // Write the expected pixel values into the write-only storage texture. const std::string computeShader = CommonWriteOnlyTestCode("compute", format); WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, computeShader.c_str()); // Verify the pixel data in the write-only storage texture is expected. - CheckOutputStorageTexture(writeonlyStorageTexture, format); + CheckOutputStorageTexture(writeonlyStorageTexture, format, {kWidth, kHeight}); } } @@ -807,7 +714,8 @@ TEST_P(StorageTextureTests, WriteonlyStorageTextureInFragmentShader) { // Prepare the write-only storage texture. wgpu::Texture writeonlyStorageTexture = - CreateTexture(format, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc); + CreateTexture(format, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, + {kWidth, kHeight}); // Write the expected pixel values into the write-only storage texture. const std::string fragmentShader = CommonWriteOnlyTestCode("fragment", format); @@ -815,7 +723,7 @@ TEST_P(StorageTextureTests, WriteonlyStorageTextureInFragmentShader) { fragmentShader.c_str()); // Verify the pixel data in the write-only storage texture is expected. - CheckOutputStorageTexture(writeonlyStorageTexture, format); + CheckOutputStorageTexture(writeonlyStorageTexture, format, {kWidth, kHeight}); } } @@ -837,7 +745,7 @@ TEST_P(StorageTextureTests, Writeonly2DArrayOr3DStorageTexture) { for (wgpu::TextureViewDimension dimension : dimensions) { wgpu::Texture writeonlyStorageTexture = CreateTexture( kTextureFormat, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, - kWidth, kHeight, kSliceCount, utils::ViewDimensionToTextureDimension(dimension)); + {kWidth, kHeight, kSliceCount}, utils::ViewDimensionToTextureDimension(dimension)); // Write the expected pixel values into the write-only storage texture. const std::string computeShader = @@ -846,10 +754,33 @@ TEST_P(StorageTextureTests, Writeonly2DArrayOr3DStorageTexture) { dimension); // Verify the pixel data in the write-only storage texture is expected. - CheckOutputStorageTexture(writeonlyStorageTexture, kTextureFormat, kSliceCount); + CheckOutputStorageTexture(writeonlyStorageTexture, kTextureFormat, + {kWidth, kHeight, kSliceCount}); } } +// Verify 1D write-only storage textures work correctly. +TEST_P(StorageTextureTests, Writeonly1DStorageTexture) { + // TODO(crbug.com/dawn/547): implement 1D storage texture on OpenGL and OpenGLES. + DAWN_TEST_UNSUPPORTED_IF(IsOpenGL() || IsOpenGLES()); + + constexpr wgpu::TextureFormat kTextureFormat = wgpu::TextureFormat::R32Uint; + + // Prepare the write-only storage texture. + wgpu::Texture writeonlyStorageTexture = CreateTexture( + kTextureFormat, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, + {kWidth, 1, 1}, wgpu::TextureDimension::e1D); + + // Write the expected pixel values into the write-only storage texture. + const std::string computeShader = + CommonWriteOnlyTestCode("compute", kTextureFormat, wgpu::TextureViewDimension::e1D); + WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, computeShader.c_str(), + wgpu::TextureViewDimension::e1D); + + // Verify the pixel data in the write-only storage texture is expected. + CheckOutputStorageTexture(writeonlyStorageTexture, kTextureFormat, {kWidth, 1, 1}); +} + // Test that multiple dispatches to increment values by ping-ponging between a sampled texture and // a write-only storage texture are synchronized in one pass. TEST_P(StorageTextureTests, SampledAndWriteonlyStorageTexturePingPong) { @@ -858,10 +789,10 @@ TEST_P(StorageTextureTests, SampledAndWriteonlyStorageTexturePingPong) { CreateTexture(kTextureFormat, wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, - 1u, 1u); + {1u, 1u}); wgpu::Texture storageTexture2 = CreateTexture( - kTextureFormat, wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding, 1u, - 1u); + kTextureFormat, wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::StorageBinding, + {1u, 1u}); wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( @group(0) @binding(0) var Src : texture_2d; @group(0) @binding(1) var Dst : texture_storage_2d; @@ -980,28 +911,28 @@ fn doTest() -> bool { // storage texture in a render pass. TEST_P(StorageTextureZeroInitTests, WriteonlyStorageTextureClearsToZeroInRenderPass) { // Prepare the write-only storage texture. - constexpr uint32_t kTexelSizeR32Uint = 4u; - wgpu::Texture writeonlyStorageTexture = - CreateTexture(wgpu::TextureFormat::R32Uint, - wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc); + wgpu::Texture writeonlyStorageTexture = CreateTexture( + wgpu::TextureFormat::R32Uint, + wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, {kWidth, kHeight}); WriteIntoStorageTextureInRenderPass(writeonlyStorageTexture, kSimpleVertexShader, kCommonWriteOnlyZeroInitTestCodeFragment); - CheckOutputStorageTexture(writeonlyStorageTexture, kTexelSizeR32Uint, GetExpectedData()); + CheckOutputStorageTexture(writeonlyStorageTexture, wgpu::TextureFormat::R32Uint, + {kWidth, kHeight}, 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) { // Prepare the write-only storage texture. - constexpr uint32_t kTexelSizeR32Uint = 4u; - wgpu::Texture writeonlyStorageTexture = - CreateTexture(wgpu::TextureFormat::R32Uint, - wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc); + wgpu::Texture writeonlyStorageTexture = CreateTexture( + wgpu::TextureFormat::R32Uint, + wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::CopySrc, {kWidth, kHeight}); WriteIntoStorageTextureInComputePass(writeonlyStorageTexture, kCommonWriteOnlyZeroInitTestCodeCompute); - CheckOutputStorageTexture(writeonlyStorageTexture, kTexelSizeR32Uint, GetExpectedData()); + CheckOutputStorageTexture(writeonlyStorageTexture, wgpu::TextureFormat::R32Uint, + {kWidth, kHeight}, GetExpectedData()); } DAWN_INSTANTIATE_TEST(StorageTextureZeroInitTests, diff --git a/src/tests/end2end/TextureViewTests.cpp b/src/tests/end2end/TextureViewTests.cpp index 731fde476a..431d6a8306 100644 --- a/src/tests/end2end/TextureViewTests.cpp +++ b/src/tests/end2end/TextureViewTests.cpp @@ -718,3 +718,71 @@ DAWN_INSTANTIATE_TEST(TextureView3DTest, OpenGLBackend(), OpenGLESBackend(), VulkanBackend()); + +class TextureView1DTest : public DawnTest {}; + +// Test that it is possible to create a 1D texture view and sample from it. +TEST_P(TextureView1DTest, Sampling) { + // Create a 1D texture and fill it with some data. + wgpu::TextureDescriptor texDesc; + texDesc.dimension = wgpu::TextureDimension::e1D; + texDesc.format = wgpu::TextureFormat::RGBA8Unorm; + texDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; + texDesc.size = {4, 1, 1}; + wgpu::Texture tex = device.CreateTexture(&texDesc); + + std::array data = {RGBA8::kGreen, RGBA8::kRed, RGBA8::kBlue, RGBA8::kWhite}; + wgpu::ImageCopyTexture target = utils::CreateImageCopyTexture(tex, 0, {}); + wgpu::TextureDataLayout layout = utils::CreateTextureDataLayout(0, wgpu::kCopyStrideUndefined); + queue.WriteTexture(&target, &data, sizeof(data), &layout, &texDesc.size); + + // Create a pipeline that will sample from the 1D texture and output to an attachment. + wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( + @stage(vertex) + fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec4( 0., 2., 0., 1.), + vec4(-3., -1., 0., 1.), + vec4( 3., -1., 0., 1.)); + return pos[VertexIndex]; + } + + @group(0) @binding(0) var tex : texture_1d; + @group(0) @binding(1) var samp : sampler; + @stage(fragment) + fn fs(@builtin(position) pos: vec4) -> @location(0) vec4 { + return textureSample(tex, samp, pos.x / 4.0); + } + )"); + utils::ComboRenderPipelineDescriptor pDesc; + pDesc.vertex.module = module; + pDesc.vertex.entryPoint = "vs"; + pDesc.cFragment.module = module; + pDesc.cFragment.entryPoint = "fs"; + pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc); + + // Do the sample + rendering. + wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), + {{0, tex.CreateView()}, {1, device.CreateSampler()}}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + + utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bg); + pass.Draw(3); + pass.EndPass(); + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + // Check texels got sampled correctly. + EXPECT_PIXEL_RGBA8_EQ(data[0], rp.color, 0, 0); + EXPECT_PIXEL_RGBA8_EQ(data[1], rp.color, 1, 0); + EXPECT_PIXEL_RGBA8_EQ(data[2], rp.color, 2, 0); + EXPECT_PIXEL_RGBA8_EQ(data[3], rp.color, 3, 0); +} + +DAWN_INSTANTIATE_TEST(TextureView1DTest, D3D12Backend(), MetalBackend(), VulkanBackend()); diff --git a/src/tests/unittests/validation/TextureViewValidationTests.cpp b/src/tests/unittests/validation/TextureViewValidationTests.cpp index 2bae28d1c2..f4d950235a 100644 --- a/src/tests/unittests/validation/TextureViewValidationTests.cpp +++ b/src/tests/unittests/validation/TextureViewValidationTests.cpp @@ -54,6 +54,15 @@ namespace { return device.CreateTexture(&descriptor); } + wgpu::Texture Create1DTexture(wgpu::Device& device) { + wgpu::TextureDescriptor descriptor; + descriptor.dimension = wgpu::TextureDimension::e1D; + descriptor.size = {kWidth, 1, 1}; + descriptor.format = kDefaultTextureFormat; + descriptor.usage = wgpu::TextureUsage::TextureBinding; + return device.CreateTexture(&descriptor); + } + wgpu::Texture CreateDepthStencilTexture(wgpu::Device& device, wgpu::TextureFormat format) { wgpu::TextureDescriptor descriptor = {}; descriptor.size = {kWidth, kHeight, kDepth}; @@ -69,7 +78,9 @@ namespace { descriptor.format = kDefaultTextureFormat; descriptor.dimension = dimension; descriptor.baseMipLevel = 0; - descriptor.mipLevelCount = kDefaultMipLevels; + if (dimension != wgpu::TextureViewDimension::e1D) { + descriptor.mipLevelCount = kDefaultMipLevels; + } descriptor.baseArrayLayer = 0; descriptor.arrayLayerCount = 1; return descriptor; @@ -208,6 +219,14 @@ namespace { ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor)); } + // It is an error to create a 1D texture view on a 2D array texture. + { + wgpu::TextureViewDescriptor descriptor = base2DArrayTextureViewDescriptor; + descriptor.dimension = wgpu::TextureViewDimension::e1D; + descriptor.arrayLayerCount = 1; + ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor)); + } + // baseArrayLayer == k && arrayLayerCount == wgpu::kArrayLayerCountUndefined means to use // layers k..end. { @@ -269,12 +288,11 @@ namespace { texture.CreateView(&descriptor); } - // It is an error to create a 2D/2DArray/Cube/CubeArray texture view on a 3D texture. + // It is an error to create a 1D/2D/2DArray/Cube/CubeArray texture view on a 3D texture. { wgpu::TextureViewDimension invalidDimensions[] = { - wgpu::TextureViewDimension::e2D, - wgpu::TextureViewDimension::e2DArray, - wgpu::TextureViewDimension::Cube, + wgpu::TextureViewDimension::e1D, wgpu::TextureViewDimension::e2D, + wgpu::TextureViewDimension::e2DArray, wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::CubeArray, }; for (wgpu::TextureViewDimension dimension : invalidDimensions) { @@ -342,6 +360,51 @@ namespace { } } + // Test creating texture view on a 1D texture + TEST_F(TextureViewValidationTest, CreateTextureViewOnTexture1D) { + wgpu::Texture texture = Create1DTexture(device); + + wgpu::TextureViewDescriptor base1DTextureViewDescriptor = + CreateDefaultViewDescriptor(wgpu::TextureViewDimension::e1D); + + // It is an error to create a view with zero 'arrayLayerCount'. + { + wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor; + descriptor.arrayLayerCount = 0; + ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor)); + } + + // It is an error to create a view with zero 'mipLevelCount'. + { + wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor; + descriptor.mipLevelCount = 0; + ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor)); + } + + // It is OK to create a 1D texture view on a 1D texture. + { + wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor; + texture.CreateView(&descriptor); + } + + // It is an error to create a 2D/2DArray/Cube/CubeArray/3D texture view on a 1D texture. + { + wgpu::TextureViewDimension invalidDimensions[] = { + wgpu::TextureViewDimension::e2D, wgpu::TextureViewDimension::e2DArray, + wgpu::TextureViewDimension::Cube, wgpu::TextureViewDimension::CubeArray, + wgpu::TextureViewDimension::e3D, + }; + for (wgpu::TextureViewDimension dimension : invalidDimensions) { + wgpu::TextureViewDescriptor descriptor = base1DTextureViewDescriptor; + descriptor.dimension = dimension; + ASSERT_DEVICE_ERROR(texture.CreateView(&descriptor)); + } + } + + // No tests for setting mip levels / array layer ranges because 1D textures can only have + // a single mip and layer. + } + // Using the "none" ("default") values validates the same as explicitly // specifying the values they're supposed to default to. // Variant for a 2D texture with more than 1 array layer.