// Copyright 2019 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 "dawn/tests/DawnTest.h" #include "dawn/common/Assert.h" #include "dawn/common/Constants.h" #include "dawn/common/Math.h" #include "dawn/utils/ComboRenderPipelineDescriptor.h" #include "dawn/utils/TestUtils.h" #include "dawn/utils/TextureUtils.h" #include "dawn/utils/WGPUHelpers.h" // The helper struct to configure the copies between buffers and textures. struct CopyConfig { wgpu::TextureDescriptor textureDescriptor; wgpu::Extent3D copyExtent3D; wgpu::Origin3D copyOrigin3D = {0, 0, 0}; uint32_t viewMipmapLevel = 0; uint32_t bufferOffset = 0; uint32_t bytesPerRowAlignment = kTextureBytesPerRowAlignment; uint32_t rowsPerImage = wgpu::kCopyStrideUndefined; }; namespace { using TextureFormat = wgpu::TextureFormat; DAWN_TEST_PARAM_STRUCT(CompressedTextureFormatTestParams, TextureFormat); } // namespace class CompressedTextureFormatTest : public DawnTestWithParams { protected: std::vector GetRequiredFeatures() override { const wgpu::TextureFormat format = GetParam().mTextureFormat; if (utils::IsBCTextureFormat(format) && SupportsFeatures({wgpu::FeatureName::TextureCompressionBC})) { mIsFormatSupported = true; return {wgpu::FeatureName::TextureCompressionBC}; } if (utils::IsETC2TextureFormat(format) && SupportsFeatures({wgpu::FeatureName::TextureCompressionETC2})) { mIsFormatSupported = true; return {wgpu::FeatureName::TextureCompressionETC2}; } if (utils::IsASTCTextureFormat(format) && SupportsFeatures({wgpu::FeatureName::TextureCompressionASTC})) { mIsFormatSupported = true; return {wgpu::FeatureName::TextureCompressionASTC}; } return {}; } bool IsFormatSupported() const { return mIsFormatSupported; } uint32_t BlockWidthInTexels() const { ASSERT(IsFormatSupported()); return utils::GetTextureFormatBlockWidth(GetParam().mTextureFormat); } uint32_t BlockHeightInTexels() const { ASSERT(IsFormatSupported()); return utils::GetTextureFormatBlockHeight(GetParam().mTextureFormat); } // Compute the upload data for the copyConfig. std::vector UploadData(const CopyConfig& copyConfig) { uint32_t copyWidthInBlock = copyConfig.copyExtent3D.width / BlockWidthInTexels(); uint32_t copyHeightInBlock = copyConfig.copyExtent3D.height / BlockHeightInTexels(); uint32_t copyBytesPerRow = 0; if (copyConfig.bytesPerRowAlignment != 0) { copyBytesPerRow = copyConfig.bytesPerRowAlignment; } else { copyBytesPerRow = copyWidthInBlock * utils::GetTexelBlockSizeInBytes(copyConfig.textureDescriptor.format); } uint32_t copyRowsPerImage = copyConfig.rowsPerImage; if (copyRowsPerImage == wgpu::kCopyStrideUndefined) { copyRowsPerImage = copyHeightInBlock; } uint32_t copyBytesPerImage = copyBytesPerRow * copyRowsPerImage; uint32_t uploadBufferSize = copyConfig.bufferOffset + copyBytesPerImage * copyConfig.copyExtent3D.depthOrArrayLayers; // Fill data with the pre-prepared one-block compressed texture data. std::vector data(uploadBufferSize, 0); std::vector oneBlockCompressedTextureData = GetOneBlockFormatTextureData(); for (uint32_t layer = 0; layer < copyConfig.copyExtent3D.depthOrArrayLayers; ++layer) { for (uint32_t h = 0; h < copyHeightInBlock; ++h) { for (uint32_t w = 0; w < copyWidthInBlock; ++w) { uint32_t uploadBufferOffset = copyConfig.bufferOffset + copyBytesPerImage * layer + copyBytesPerRow * h + oneBlockCompressedTextureData.size() * w; std::memcpy(&data[uploadBufferOffset], oneBlockCompressedTextureData.data(), oneBlockCompressedTextureData.size() * sizeof(uint8_t)); } } } return data; } // Copy the compressed texture data into the destination texture as is specified in // copyConfig. void InitializeDataInCompressedTexture(wgpu::Texture compressedTexture, const CopyConfig& copyConfig) { ASSERT(IsFormatSupported()); std::vector data = UploadData(copyConfig); // Copy texture data from a staging buffer to the destination texture. wgpu::Buffer stagingBuffer = utils::CreateBufferFromData(device, data.data(), data.size(), wgpu::BufferUsage::CopySrc); wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(stagingBuffer, copyConfig.bufferOffset, copyConfig.bytesPerRowAlignment, copyConfig.rowsPerImage); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( compressedTexture, copyConfig.viewMipmapLevel, copyConfig.copyOrigin3D); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyBufferToTexture(&imageCopyBuffer, &imageCopyTexture, ©Config.copyExtent3D); wgpu::CommandBuffer copy = encoder.Finish(); queue.Submit(1, ©); } // Create the bind group that includes a texture and a sampler. wgpu::BindGroup CreateBindGroupForTest(wgpu::BindGroupLayout bindGroupLayout, wgpu::Texture compressedTexture, uint32_t baseArrayLayer = 0, uint32_t baseMipLevel = 0) { ASSERT(IsFormatSupported()); wgpu::SamplerDescriptor samplerDesc; samplerDesc.minFilter = wgpu::FilterMode::Nearest; samplerDesc.magFilter = wgpu::FilterMode::Nearest; wgpu::Sampler sampler = device.CreateSampler(&samplerDesc); wgpu::TextureViewDescriptor textureViewDescriptor; textureViewDescriptor.format = GetParam().mTextureFormat; textureViewDescriptor.dimension = wgpu::TextureViewDimension::e2D; textureViewDescriptor.baseMipLevel = baseMipLevel; textureViewDescriptor.baseArrayLayer = baseArrayLayer; textureViewDescriptor.arrayLayerCount = 1; textureViewDescriptor.mipLevelCount = 1; wgpu::TextureView textureView = compressedTexture.CreateView(&textureViewDescriptor); return utils::MakeBindGroup(device, bindGroupLayout, {{0, sampler}, {1, textureView}}); } // Create a render pipeline for sampling from a texture and rendering into the render target. wgpu::RenderPipeline CreateRenderPipelineForTest() { ASSERT(IsFormatSupported()); utils::ComboRenderPipelineDescriptor renderPipelineDescriptor; wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"( struct VertexOut { @location(0) texCoord : vec2 , @builtin(position) position : vec4, } @stage(vertex) fn main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut { var pos = array, 3>( vec2(-3.0, 1.0), vec2( 3.0, 1.0), vec2( 0.0, -2.0) ); var output : VertexOut; output.position = vec4(pos[VertexIndex], 0.0, 1.0); output.texCoord = vec2(output.position.x / 2.0, -output.position.y / 2.0) + vec2(0.5, 0.5); return output; })"); wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( @group(0) @binding(0) var sampler0 : sampler; @group(0) @binding(1) var texture0 : texture_2d; @stage(fragment) fn main(@location(0) texCoord : vec2) -> @location(0) vec4 { return textureSample(texture0, sampler0, texCoord); })"); renderPipelineDescriptor.vertex.module = vsModule; renderPipelineDescriptor.cFragment.module = fsModule; renderPipelineDescriptor.cTargets[0].format = utils::BasicRenderPass::kDefaultColorFormat; return device.CreateRenderPipeline(&renderPipelineDescriptor); } // Run the given render pipeline and bind group and verify the pixels in the render target. void VerifyCompressedTexturePixelValues(wgpu::RenderPipeline renderPipeline, wgpu::BindGroup bindGroup, const wgpu::Extent3D& renderTargetSize, const wgpu::Origin3D& expectedOrigin, const wgpu::Extent3D& expectedExtent, const std::vector& expected) { ASSERT(IsFormatSupported()); utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, renderTargetSize.width, renderTargetSize.height); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); { wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); pass.SetPipeline(renderPipeline); pass.SetBindGroup(0, bindGroup); pass.Draw(6); pass.End(); } wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); EXPECT_TEXTURE_EQ(expected.data(), renderPass.color, {expectedOrigin.x, expectedOrigin.y}, {expectedExtent.width, expectedExtent.height}); } // Run the tests that copies pre-prepared format data into a texture and verifies if we can // render correctly with the pixel values sampled from the texture. void TestCopyRegionIntoFormatTextures(const CopyConfig& config) { ASSERT(IsFormatSupported()); wgpu::Texture texture = CreateTextureWithCompressedData(config); VerifyTexture(config, texture); } void VerifyTexture(const CopyConfig& config, wgpu::Texture texture) { wgpu::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); wgpu::Extent3D virtualSizeAtLevel = GetVirtualSizeAtLevel(config); // The copy region may exceed the subresource size because of the required paddings, so we // should limit the size of the expectedData to make it match the real size of the render // target. wgpu::Extent3D noPaddingExtent3D = config.copyExtent3D; if (config.copyOrigin3D.x + config.copyExtent3D.width > virtualSizeAtLevel.width) { noPaddingExtent3D.width = virtualSizeAtLevel.width - config.copyOrigin3D.x; } if (config.copyOrigin3D.y + config.copyExtent3D.height > virtualSizeAtLevel.height) { noPaddingExtent3D.height = virtualSizeAtLevel.height - config.copyOrigin3D.y; } noPaddingExtent3D.depthOrArrayLayers = 1u; std::vector expectedData = GetExpectedData(noPaddingExtent3D); wgpu::Origin3D firstLayerCopyOrigin = {config.copyOrigin3D.x, config.copyOrigin3D.y, 0}; for (uint32_t layer = config.copyOrigin3D.z; layer < config.copyOrigin3D.z + config.copyExtent3D.depthOrArrayLayers; ++layer) { wgpu::BindGroup bindGroup = CreateBindGroupForTest( renderPipeline.GetBindGroupLayout(0), texture, layer, config.viewMipmapLevel); VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, virtualSizeAtLevel, firstLayerCopyOrigin, noPaddingExtent3D, expectedData); } } // Create a texture and initialize it with the pre-prepared compressed texture data. wgpu::Texture CreateTextureWithCompressedData(CopyConfig config) { wgpu::Texture texture = device.CreateTexture(&config.textureDescriptor); InitializeDataInCompressedTexture(texture, config); return texture; } // Record a texture-to-texture copy command into command encoder without finishing the // encoding. void RecordTextureToTextureCopy(wgpu::CommandEncoder encoder, wgpu::Texture srcTexture, wgpu::Texture dstTexture, CopyConfig srcConfig, CopyConfig dstConfig) { wgpu::ImageCopyTexture imageCopyTextureSrc = utils::CreateImageCopyTexture( srcTexture, srcConfig.viewMipmapLevel, srcConfig.copyOrigin3D); wgpu::ImageCopyTexture imageCopyTextureDst = utils::CreateImageCopyTexture( dstTexture, dstConfig.viewMipmapLevel, dstConfig.copyOrigin3D); encoder.CopyTextureToTexture(&imageCopyTextureSrc, &imageCopyTextureDst, &dstConfig.copyExtent3D); } wgpu::Texture CreateTextureFromTexture(wgpu::Texture srcTexture, CopyConfig srcConfig, CopyConfig dstConfig) { wgpu::Texture dstTexture = device.CreateTexture(&dstConfig.textureDescriptor); wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); RecordTextureToTextureCopy(encoder, srcTexture, dstTexture, srcConfig, dstConfig); wgpu::CommandBuffer copy = encoder.Finish(); queue.Submit(1, ©); return dstTexture; } // Return the pre-prepared one-block texture data. std::vector GetOneBlockFormatTextureData() { switch (GetParam().mTextureFormat) { // The expected data represents 4x4 pixel images with the left side dark red and the // right side dark green. We specify the same compressed data in both sRGB and // non-sRGB tests, but the rendering result should be different because for sRGB // formats, the red, green, and blue components are converted from an sRGB color // space to a linear color space as part of filtering. case wgpu::TextureFormat::BC1RGBAUnorm: case wgpu::TextureFormat::BC1RGBAUnormSrgb: return {0x0, 0xC0, 0x60, 0x6, 0x50, 0x50, 0x50, 0x50}; case wgpu::TextureFormat::BC7RGBAUnorm: case wgpu::TextureFormat::BC7RGBAUnormSrgb: return {0x50, 0x18, 0xfc, 0xf, 0x0, 0x30, 0xe3, 0xe1, 0xe1, 0xe1, 0xc1, 0xf, 0xfc, 0xc0, 0xf, 0xfc}; // The expected data represents 4x4 pixel images with the left side dark red and the // right side dark green. The pixels in the left side of the block all have an alpha // value equal to 0x88. We specify the same compressed data in both sRGB and // non-sRGB tests, but the rendering result should be different because for sRGB // formats, the red, green, and blue components are converted from an sRGB color // space to a linear color space as part of filtering, and any alpha component is // left unchanged. case wgpu::TextureFormat::BC2RGBAUnorm: case wgpu::TextureFormat::BC2RGBAUnormSrgb: return {0x88, 0xFF, 0x88, 0xFF, 0x88, 0xFF, 0x88, 0xFF, 0x0, 0xC0, 0x60, 0x6, 0x50, 0x50, 0x50, 0x50}; case wgpu::TextureFormat::BC3RGBAUnorm: case wgpu::TextureFormat::BC3RGBAUnormSrgb: return {0x88, 0xFF, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24, 0x0, 0xC0, 0x60, 0x6, 0x50, 0x50, 0x50, 0x50}; // The expected data represents 4x4 pixel images with the left side red and the // right side black. case wgpu::TextureFormat::BC4RSnorm: return {0x7F, 0x0, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24}; case wgpu::TextureFormat::BC4RUnorm: return {0xFF, 0x0, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24}; // The expected data represents 4x4 pixel images with the left side red and the // right side green and was encoded with DirectXTex from Microsoft. case wgpu::TextureFormat::BC5RGSnorm: return {0x7f, 0x81, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24, 0x7f, 0x81, 0x9, 0x90, 0x0, 0x9, 0x90, 0x0}; case wgpu::TextureFormat::BC5RGUnorm: return {0xff, 0x0, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24, 0xff, 0x0, 0x9, 0x90, 0x0, 0x9, 0x90, 0x0}; case wgpu::TextureFormat::BC6HRGBFloat: return {0xe3, 0x1f, 0x0, 0x0, 0x0, 0xe0, 0x1f, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff}; case wgpu::TextureFormat::BC6HRGBUfloat: return {0xe3, 0x3d, 0x0, 0x0, 0x0, 0xe0, 0x3d, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff}; // The expected data represents 4x4 pixel images with the left side dark red and the // right side dark green. We specify the same compressed data in both sRGB and // non-sRGB tests, but the rendering result should be different because for sRGB // formats, the red, green, and blue components are converted from an sRGB color // space to a linear color space as part of filtering. case wgpu::TextureFormat::ETC2RGB8Unorm: case wgpu::TextureFormat::ETC2RGB8UnormSrgb: case wgpu::TextureFormat::ETC2RGB8A1Unorm: case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return {0x4, 0xc0, 0xc0, 0x2, 0x0, 0xff, 0x0, 0x0}; // The expected data represents 4x4 pixel images with the left side dark red and the // right side dark green. The pixels in the left side of the block all have an alpha // value equal to 0x88. We specify the same compressed data in both sRGB and // non-sRGB tests, but the rendering result should be different because for sRGB // formats, the red, green, and blue components are converted from an sRGB color // space to a linear color space as part of filtering, and any alpha component is // left unchanged. case wgpu::TextureFormat::ETC2RGBA8Unorm: case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return {0xc0, 0x78, 0x49, 0x24, 0x92, 0xff, 0xff, 0xff, 0x4, 0xc0, 0xc0, 0x2, 0x0, 0xff, 0x0, 0x0}; // The expected data represents 4x4 pixel image with the left side red and the right // side black. case wgpu::TextureFormat::EACR11Unorm: return {0x84, 0x90, 0xff, 0xff, 0xff, 0x6d, 0xb6, 0xdb}; case wgpu::TextureFormat::EACR11Snorm: return {0x2, 0x90, 0xff, 0xff, 0xff, 0x6d, 0xb6, 0xdb}; // The expected data represents 4x4 pixel image with the left side red and the right // side green. case wgpu::TextureFormat::EACRG11Unorm: return {0x84, 0x90, 0xff, 0xff, 0xff, 0x6d, 0xb6, 0xdb, 0x84, 0x90, 0x6d, 0xb6, 0xdb, 0xff, 0xff, 0xff}; case wgpu::TextureFormat::EACRG11Snorm: return {0x2, 0x90, 0xff, 0xff, 0xff, 0x6d, 0xb6, 0xdb, 0x2, 0x90, 0x6d, 0xb6, 0xdb, 0xff, 0xff, 0xff}; // The expected data is a texel block of the corresponding size where the left width / 2 // pixels are dark red with an alpha of 0x80 and the remaining (width - width / 2) // pixels are dark green. We specify the same compressed data in both sRGB and non-sRGB // tests, but the rendering result should be different because for sRGB formats, the // red, green, and blue components are converted from an sRGB color space to a linear // color space as part of filtering, and any alpha component is left unchanged. case wgpu::TextureFormat::ASTC4x4Unorm: case wgpu::TextureFormat::ASTC4x4UnormSrgb: return {0x13, 0x80, 0xe9, 0x1, 0x0, 0xe8, 0x1, 0x0, 0x0, 0xff, 0x1, 0x0, 0x0, 0x3f, 0xf0, 0x3}; case wgpu::TextureFormat::ASTC5x4Unorm: case wgpu::TextureFormat::ASTC5x4UnormSrgb: case wgpu::TextureFormat::ASTC5x5Unorm: case wgpu::TextureFormat::ASTC5x5UnormSrgb: return {0x83, 0x80, 0xe9, 0x1, 0x0, 0xe8, 0x1, 0x0, 0x0, 0xff, 0x1, 0x0, 0x80, 0x14, 0x90, 0x2}; case wgpu::TextureFormat::ASTC6x5Unorm: case wgpu::TextureFormat::ASTC6x5UnormSrgb: case wgpu::TextureFormat::ASTC6x6Unorm: case wgpu::TextureFormat::ASTC6x6UnormSrgb: return {0x2, 0x81, 0xe9, 0x1, 0x0, 0xe8, 0x1, 0x0, 0x0, 0xff, 0x1, 0x0, 0x0, 0x3f, 0xf0, 0x3}; case wgpu::TextureFormat::ASTC8x5Unorm: case wgpu::TextureFormat::ASTC8x5UnormSrgb: case wgpu::TextureFormat::ASTC8x6Unorm: case wgpu::TextureFormat::ASTC8x6UnormSrgb: case wgpu::TextureFormat::ASTC8x8Unorm: case wgpu::TextureFormat::ASTC8x8UnormSrgb: return {0x6, 0x80, 0xe9, 0x1, 0x0, 0xe8, 0x1, 0x0, 0x0, 0xff, 0x1, 0x0, 0xff, 0x0, 0xff, 0x0}; case wgpu::TextureFormat::ASTC10x5Unorm: case wgpu::TextureFormat::ASTC10x5UnormSrgb: case wgpu::TextureFormat::ASTC10x6Unorm: case wgpu::TextureFormat::ASTC10x6UnormSrgb: case wgpu::TextureFormat::ASTC10x8Unorm: case wgpu::TextureFormat::ASTC10x8UnormSrgb: case wgpu::TextureFormat::ASTC10x10Unorm: case wgpu::TextureFormat::ASTC10x10UnormSrgb: return {0x6, 0x81, 0xe9, 0x1, 0x0, 0xe8, 0x1, 0x0, 0x0, 0xff, 0x1, 0xff, 0x3, 0xf0, 0x3f, 0x0}; case wgpu::TextureFormat::ASTC12x10Unorm: case wgpu::TextureFormat::ASTC12x10UnormSrgb: case wgpu::TextureFormat::ASTC12x12Unorm: case wgpu::TextureFormat::ASTC12x12UnormSrgb: return {0x4, 0x80, 0xe9, 0x1, 0x0, 0xe8, 0x1, 0x0, 0x0, 0xff, 0x1, 0x0, 0x0, 0x3f, 0xf0, 0x3}; default: UNREACHABLE(); return {}; } } // Return the texture data that is decoded from the result of GetOneBlockFormatTextureData // in RGBA8 formats. Since some compression methods may be lossy, we may use different colors // to test different formats. std::vector GetExpectedData(const wgpu::Extent3D& testRegion) { constexpr uint8_t kLeftAlpha = 0x88; constexpr uint8_t kRightAlpha = 0xFF; constexpr RGBA8 kBCDarkRed(198, 0, 0, 255); constexpr RGBA8 kBCDarkGreen(0, 207, 0, 255); constexpr RGBA8 kBCDarkRedSRGB(144, 0, 0, 255); constexpr RGBA8 kBCDarkGreenSRGB(0, 159, 0, 255); constexpr RGBA8 kETC2DarkRed(204, 0, 0, 255); constexpr RGBA8 kETC2DarkGreen(0, 204, 0, 255); constexpr RGBA8 kETC2DarkRedSRGB(154, 0, 0, 255); constexpr RGBA8 kETC2DarkGreenSRGB(0, 154, 0, 255); constexpr RGBA8 kASTCDarkRed(244, 0, 0, 128); constexpr RGBA8 kASTCDarkGreen(0, 244, 0, 255); constexpr RGBA8 kASTCDarkRedSRGB(231, 0, 0, 128); constexpr RGBA8 kASTCDarkGreenSRGB(0, 231, 0, 255); switch (GetParam().mTextureFormat) { case wgpu::TextureFormat::BC1RGBAUnorm: case wgpu::TextureFormat::BC7RGBAUnorm: return FillExpectedData(testRegion, kBCDarkRed, kBCDarkGreen); case wgpu::TextureFormat::BC2RGBAUnorm: case wgpu::TextureFormat::BC3RGBAUnorm: { constexpr RGBA8 kLeftColor = RGBA8(kBCDarkRed.r, 0, 0, kLeftAlpha); constexpr RGBA8 kRightColor = RGBA8(0, kBCDarkGreen.g, 0, kRightAlpha); return FillExpectedData(testRegion, kLeftColor, kRightColor); } case wgpu::TextureFormat::BC1RGBAUnormSrgb: case wgpu::TextureFormat::BC7RGBAUnormSrgb: return FillExpectedData(testRegion, kBCDarkRedSRGB, kBCDarkGreenSRGB); case wgpu::TextureFormat::BC2RGBAUnormSrgb: case wgpu::TextureFormat::BC3RGBAUnormSrgb: { constexpr RGBA8 kLeftColor = RGBA8(kBCDarkRedSRGB.r, 0, 0, kLeftAlpha); constexpr RGBA8 kRightColor = RGBA8(0, kBCDarkGreenSRGB.g, 0, kRightAlpha); return FillExpectedData(testRegion, kLeftColor, kRightColor); } case wgpu::TextureFormat::BC4RSnorm: case wgpu::TextureFormat::BC4RUnorm: return FillExpectedData(testRegion, RGBA8::kRed, RGBA8::kBlack); case wgpu::TextureFormat::BC5RGSnorm: case wgpu::TextureFormat::BC5RGUnorm: case wgpu::TextureFormat::BC6HRGBFloat: case wgpu::TextureFormat::BC6HRGBUfloat: return FillExpectedData(testRegion, RGBA8::kRed, RGBA8::kGreen); case wgpu::TextureFormat::ETC2RGB8Unorm: case wgpu::TextureFormat::ETC2RGB8A1Unorm: return FillExpectedData(testRegion, kETC2DarkRed, kETC2DarkGreen); case wgpu::TextureFormat::ETC2RGB8UnormSrgb: case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return FillExpectedData(testRegion, kETC2DarkRedSRGB, kETC2DarkGreenSRGB); case wgpu::TextureFormat::ETC2RGBA8Unorm: { constexpr RGBA8 kLeftColor = RGBA8(kETC2DarkRed.r, 0, 0, kLeftAlpha); constexpr RGBA8 kRightColor = RGBA8(0, kETC2DarkGreen.g, 0, kRightAlpha); return FillExpectedData(testRegion, kLeftColor, kRightColor); } case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: { constexpr RGBA8 kLeftColor = RGBA8(kETC2DarkRedSRGB.r, 0, 0, kLeftAlpha); constexpr RGBA8 kRightColor = RGBA8(0, kETC2DarkGreenSRGB.g, 0, kRightAlpha); return FillExpectedData(testRegion, kLeftColor, kRightColor); } case wgpu::TextureFormat::EACR11Unorm: case wgpu::TextureFormat::EACR11Snorm: return FillExpectedData(testRegion, RGBA8::kRed, RGBA8::kBlack); case wgpu::TextureFormat::EACRG11Unorm: case wgpu::TextureFormat::EACRG11Snorm: return FillExpectedData(testRegion, RGBA8::kRed, RGBA8::kGreen); case wgpu::TextureFormat::ASTC4x4Unorm: case wgpu::TextureFormat::ASTC5x4Unorm: case wgpu::TextureFormat::ASTC5x5Unorm: case wgpu::TextureFormat::ASTC6x5Unorm: case wgpu::TextureFormat::ASTC6x6Unorm: case wgpu::TextureFormat::ASTC8x5Unorm: case wgpu::TextureFormat::ASTC8x6Unorm: case wgpu::TextureFormat::ASTC8x8Unorm: case wgpu::TextureFormat::ASTC10x5Unorm: case wgpu::TextureFormat::ASTC10x6Unorm: case wgpu::TextureFormat::ASTC10x8Unorm: case wgpu::TextureFormat::ASTC10x10Unorm: case wgpu::TextureFormat::ASTC12x10Unorm: case wgpu::TextureFormat::ASTC12x12Unorm: return FillExpectedData(testRegion, kASTCDarkRed, kASTCDarkGreen); case wgpu::TextureFormat::ASTC4x4UnormSrgb: case wgpu::TextureFormat::ASTC5x4UnormSrgb: case wgpu::TextureFormat::ASTC5x5UnormSrgb: case wgpu::TextureFormat::ASTC6x5UnormSrgb: case wgpu::TextureFormat::ASTC6x6UnormSrgb: case wgpu::TextureFormat::ASTC8x5UnormSrgb: case wgpu::TextureFormat::ASTC8x6UnormSrgb: case wgpu::TextureFormat::ASTC8x8UnormSrgb: case wgpu::TextureFormat::ASTC10x5UnormSrgb: case wgpu::TextureFormat::ASTC10x6UnormSrgb: case wgpu::TextureFormat::ASTC10x8UnormSrgb: case wgpu::TextureFormat::ASTC10x10UnormSrgb: case wgpu::TextureFormat::ASTC12x10UnormSrgb: case wgpu::TextureFormat::ASTC12x12UnormSrgb: return FillExpectedData(testRegion, kASTCDarkRedSRGB, kASTCDarkGreenSRGB); default: UNREACHABLE(); return {}; } } std::vector FillExpectedData(const wgpu::Extent3D& testRegion, RGBA8 leftColorInBlock, RGBA8 rightColorInBlock) { ASSERT(testRegion.depthOrArrayLayers == 1); std::vector expectedData(testRegion.width * testRegion.height, leftColorInBlock); for (uint32_t y = 0; y < testRegion.height; ++y) { for (uint32_t x = 0; x < testRegion.width; ++x) { if (x % BlockWidthInTexels() >= BlockWidthInTexels() / 2) { expectedData[testRegion.width * y + x] = rightColorInBlock; } } } return expectedData; } // Returns a texture size given the number of texel blocks that should be tiled. For example, // if a texel block size is 5x4, then GetTextureSizeFromBlocks(2, 2) -> {10, 8, 1}. wgpu::Extent3D GetTextureSizeWithNumBlocks(uint32_t numBlockWidth, uint32_t numBlockHeight, uint32_t depthOrArrayLayers = 1) const { return {numBlockWidth * BlockWidthInTexels(), numBlockHeight * BlockHeightInTexels(), depthOrArrayLayers}; } CopyConfig GetDefaultFullConfig(uint32_t depthOrArrayLayers = 1) const { ASSERT(IsFormatSupported()); CopyConfig config; config.textureDescriptor.format = GetParam().mTextureFormat; config.textureDescriptor.usage = kDefaultFormatTextureUsage; config.textureDescriptor.size = GetTextureSizeWithNumBlocks( kUnalignedBlockSize, kUnalignedBlockSize, depthOrArrayLayers); config.textureDescriptor.mipLevelCount = kMipmapLevelCount; config.viewMipmapLevel = kMipmapLevelCount - 1; const wgpu::Extent3D virtualSize = GetVirtualSizeAtLevel(config); ASSERT(virtualSize.width % BlockWidthInTexels() != 0u); ASSERT(virtualSize.height % BlockHeightInTexels() != 0u); return config; } CopyConfig GetDefaultSmallConfig(uint32_t depthOrArrayLayers = 1) const { ASSERT(IsFormatSupported()); CopyConfig config; config.textureDescriptor.format = GetParam().mTextureFormat; config.textureDescriptor.usage = kDefaultFormatTextureUsage; config.textureDescriptor.size = GetTextureSizeWithNumBlocks(2, 2, depthOrArrayLayers); return config; } CopyConfig GetDefaultSubresourceConfig(uint32_t depthOrArrayLayers = 1) const { ASSERT(IsFormatSupported()); CopyConfig config; config.textureDescriptor.format = GetParam().mTextureFormat; config.textureDescriptor.usage = kDefaultFormatTextureUsage; config.textureDescriptor.size = GetPhysicalSizeAtLevel(GetDefaultFullConfig(depthOrArrayLayers)); config.viewMipmapLevel = config.textureDescriptor.mipLevelCount - 1; return config; } // Note: Compressed formats are only valid with 2D (array) textures. static wgpu::Extent3D GetVirtualSizeAtLevel(const CopyConfig& config) { return {config.textureDescriptor.size.width >> config.viewMipmapLevel, config.textureDescriptor.size.height >> config.viewMipmapLevel, config.textureDescriptor.size.depthOrArrayLayers}; } wgpu::Extent3D GetPhysicalSizeAtLevel(const CopyConfig& config) const { wgpu::Extent3D sizeAtLevel = GetVirtualSizeAtLevel(config); sizeAtLevel.width = (sizeAtLevel.width + BlockWidthInTexels() - 1) / BlockWidthInTexels() * BlockWidthInTexels(); sizeAtLevel.height = (sizeAtLevel.height + BlockHeightInTexels() - 1) / BlockHeightInTexels() * BlockHeightInTexels(); return sizeAtLevel; } static constexpr wgpu::TextureUsage kDefaultFormatTextureUsage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst; // We choose a prime that is greater than the current max texel dimension size as a multiplier // to compute test texture sizes so that we can be certain that its level 2 mipmap (x4) // cannot be a multiple of the dimension. This is useful for testing padding at the edges of // the mipmaps. static constexpr uint32_t kUnalignedBlockSize = 13; static constexpr uint32_t kMipmapLevelCount = 3; bool mIsFormatSupported = false; }; // Test copying into the whole texture with 2x2 blocks and sampling from it. TEST_P(CompressedTextureFormatTest, Basic) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); CopyConfig config = GetDefaultSmallConfig(); config.copyExtent3D = config.textureDescriptor.size; TestCopyRegionIntoFormatTextures(config); } // Test copying into a sub-region of a texture works correctly. TEST_P(CompressedTextureFormatTest, CopyIntoSubRegion) { // TODO(crbug.com/dawn/976): Failing on Linux Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); CopyConfig config = GetDefaultSmallConfig(); config.copyOrigin3D = {BlockWidthInTexels(), BlockHeightInTexels(), 0}; config.copyExtent3D = {BlockWidthInTexels(), BlockHeightInTexels(), 1}; TestCopyRegionIntoFormatTextures(config); } // Test copying into the non-zero layer of a 2D array texture works correctly. TEST_P(CompressedTextureFormatTest, CopyIntoNonZeroArrayLayer) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // This test uses glTextureView() which is not supported in OpenGL ES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); constexpr uint32_t kArrayLayerCount = 3; CopyConfig config = GetDefaultSmallConfig(kArrayLayerCount); config.copyExtent3D = config.textureDescriptor.size; config.copyExtent3D.depthOrArrayLayers = 1; config.copyOrigin3D.z = kArrayLayerCount - 1; TestCopyRegionIntoFormatTextures(config); } // Test copying into a non-zero mipmap level of a texture. TEST_P(CompressedTextureFormatTest, CopyBufferIntoNonZeroMipmapLevel) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // This test uses glTextureView() which is not supported in OpenGL ES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); CopyConfig config = GetDefaultFullConfig(); // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. config.copyExtent3D = GetPhysicalSizeAtLevel(config); TestCopyRegionIntoFormatTextures(config); } // Test texture-to-texture whole-size copies. TEST_P(CompressedTextureFormatTest, CopyWholeTextureSubResourceIntoNonZeroMipmapLevel) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // This test uses glTextureView() which is not supported in OpenGL ES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); // TODO(crbug.com/dawn/816): This consistently fails on with the 12th pixel being opaque // black instead of opaque red on Win10 FYI Release (NVIDIA GeForce GTX 1660). DAWN_SUPPRESS_TEST_IF(IsWindows() && IsVulkan() && IsNvidia()); CopyConfig config = GetDefaultFullConfig(); // Add the usage bit for both source and destination textures so that we don't need to // create two copy configs. config.textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding; // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. const wgpu::Extent3D kVirtualSize = GetVirtualSizeAtLevel(config); config.copyExtent3D = GetPhysicalSizeAtLevel(config); wgpu::Texture textureSrc = CreateTextureWithCompressedData(config); // Create textureDst and copy from the content in textureSrc into it. wgpu::Texture textureDst = CreateTextureFromTexture(textureSrc, config, config); // Verify if we can use texture as sampled textures correctly. wgpu::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); wgpu::BindGroup bindGroup = CreateBindGroupForTest(renderPipeline.GetBindGroupLayout(0), textureDst, config.copyOrigin3D.z, config.viewMipmapLevel); std::vector expectedData = GetExpectedData(kVirtualSize); VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kVirtualSize, config.copyOrigin3D, kVirtualSize, expectedData); } // Test texture-to-texture partial copies where the physical size of the destination subresource is // different from its virtual size. TEST_P(CompressedTextureFormatTest, CopyIntoSubresourceWithPhysicalSizeNotEqualToVirtualSize) { DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // TODO(crbug.com/dawn/817): add workaround on the T2T copies where Extent3D fits in one // subresource and does not fit in another one on OpenGL. DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); CopyConfig srcConfig = GetDefaultSubresourceConfig(); srcConfig.textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; CopyConfig dstConfig = GetDefaultFullConfig(); // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. const wgpu::Extent3D kDstVirtualSize = GetVirtualSizeAtLevel(dstConfig); const wgpu::Extent3D kDstPhysicalSize = GetPhysicalSizeAtLevel(dstConfig); srcConfig.copyExtent3D = dstConfig.copyExtent3D = kDstPhysicalSize; // Create textureSrc as the source texture and initialize it with pre-prepared compressed // data. wgpu::Texture textureSrc = CreateTextureWithCompressedData(srcConfig); wgpu::ImageCopyTexture imageCopyTextureSrc = utils::CreateImageCopyTexture( textureSrc, srcConfig.viewMipmapLevel, srcConfig.copyOrigin3D); // Create textureDst and copy from the content in textureSrc into it. wgpu::Texture textureDst = CreateTextureFromTexture(textureSrc, srcConfig, dstConfig); // Verify if we can use texture as sampled textures correctly. wgpu::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); wgpu::BindGroup bindGroup = CreateBindGroupForTest(renderPipeline.GetBindGroupLayout(0), textureDst, dstConfig.copyOrigin3D.z, dstConfig.viewMipmapLevel); std::vector expectedData = GetExpectedData(kDstVirtualSize); VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kDstVirtualSize, dstConfig.copyOrigin3D, kDstVirtualSize, expectedData); } // Test texture-to-texture partial copies where the physical size of the source subresource is // different from its virtual size. TEST_P(CompressedTextureFormatTest, CopyFromSubresourceWithPhysicalSizeNotEqualToVirtualSize) { DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // TODO(crbug.com/dawn/817): add workaround on the T2T copies where Extent3D fits in one // subresource and does not fit in another one on OpenGL. DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); CopyConfig srcConfig = GetDefaultFullConfig(); srcConfig.textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; CopyConfig dstConfig = GetDefaultSubresourceConfig(); // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. const wgpu::Extent3D kSrcVirtualSize = GetVirtualSizeAtLevel(srcConfig); const wgpu::Extent3D kDstVirtualSize = GetVirtualSizeAtLevel(dstConfig); srcConfig.copyExtent3D = dstConfig.copyExtent3D = kDstVirtualSize; ASSERT_GT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width, kSrcVirtualSize.width); ASSERT_GT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height, kSrcVirtualSize.height); // Create textureSrc as the source texture and initialize it with pre-prepared compressed // data. wgpu::Texture textureSrc = CreateTextureWithCompressedData(srcConfig); // Create textureDst and copy from the content in textureSrc into it. wgpu::Texture textureDst = CreateTextureFromTexture(textureSrc, srcConfig, dstConfig); // Verify if we can use texture as sampled textures correctly. wgpu::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); wgpu::BindGroup bindGroup = CreateBindGroupForTest(renderPipeline.GetBindGroupLayout(0), textureDst, dstConfig.copyOrigin3D.z, dstConfig.viewMipmapLevel); std::vector expectedData = GetExpectedData(kDstVirtualSize); VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kDstVirtualSize, dstConfig.copyOrigin3D, kDstVirtualSize, expectedData); } // Test recording two texture-to-texture partial copies where the physical size of the source // subresource is different from its virtual size into one command buffer. TEST_P(CompressedTextureFormatTest, MultipleCopiesWithPhysicalSizeNotEqualToVirtualSize) { DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // TODO(crbug.com/dawn/817): add workaround on the T2T copies where Extent3D fits in one // subresource and does not fit in another one on OpenGL. DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); constexpr uint32_t kTotalCopyCount = 2; std::array srcConfigs; std::array dstConfigs; srcConfigs[0] = GetDefaultFullConfig(); srcConfigs[0].textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; dstConfigs[0] = GetDefaultSubresourceConfig(); srcConfigs[0].copyExtent3D = dstConfigs[0].copyExtent3D = GetVirtualSizeAtLevel(dstConfigs[0]); srcConfigs[1] = GetDefaultSubresourceConfig(); srcConfigs[1].textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; dstConfigs[1] = GetDefaultFullConfig(); srcConfigs[1].copyExtent3D = dstConfigs[1].copyExtent3D = GetVirtualSizeAtLevel(srcConfigs[1]); std::array dstVirtualSizes; for (uint32_t i = 0; i < kTotalCopyCount; ++i) { dstVirtualSizes[i] = GetVirtualSizeAtLevel(dstConfigs[i]); } std::array srcTextures; std::array dstTextures; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); for (uint32_t i = 0; i < kTotalCopyCount; ++i) { // Create srcTextures as the source textures and initialize them with pre-prepared // compressed data. srcTextures[i] = CreateTextureWithCompressedData(srcConfigs[i]); dstTextures[i] = device.CreateTexture(&dstConfigs[i].textureDescriptor); RecordTextureToTextureCopy(encoder, srcTextures[i], dstTextures[i], srcConfigs[i], dstConfigs[i]); } wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); wgpu::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); for (uint32_t i = 0; i < kTotalCopyCount; ++i) { // Verify if we can use dstTextures as sampled textures correctly. wgpu::BindGroup bindGroup0 = CreateBindGroupForTest(renderPipeline.GetBindGroupLayout(0), dstTextures[i], dstConfigs[i].copyOrigin3D.z, dstConfigs[i].viewMipmapLevel); std::vector expectedData = GetExpectedData(dstVirtualSizes[i]); VerifyCompressedTexturePixelValues(renderPipeline, bindGroup0, dstVirtualSizes[i], dstConfigs[i].copyOrigin3D, dstVirtualSizes[i], expectedData); } } // A regression test for a bug for the toggle UseTemporaryBufferInCompressedTextureToTextureCopy on // Vulkan backend: test texture-to-texture partial copies with multiple array layers where the // physical size of the source subresource is different from its virtual size. TEST_P(CompressedTextureFormatTest, CopyWithMultipleLayerAndPhysicalSizeNotEqualToVirtualSize) { DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // TODO(crbug.com/dawn/817): add workaround on the T2T copies where Extent3D fits in one // subresource and does not fit in another one on OpenGL. DAWN_SUPPRESS_TEST_IF(IsOpenGL() || IsOpenGLES()); constexpr uint32_t kArrayLayerCount = 5; CopyConfig srcConfig = GetDefaultFullConfig(kArrayLayerCount); srcConfig.textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst; CopyConfig dstConfig = GetDefaultSubresourceConfig(kArrayLayerCount); // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. const wgpu::Extent3D kSrcVirtualSize = GetVirtualSizeAtLevel(srcConfig); const wgpu::Extent3D kDstVirtualSize = GetVirtualSizeAtLevel(dstConfig); srcConfig.copyExtent3D = dstConfig.copyExtent3D = kDstVirtualSize; srcConfig.rowsPerImage = srcConfig.copyExtent3D.height / BlockHeightInTexels(); ASSERT_GT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width, kSrcVirtualSize.width); ASSERT_GT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height, kSrcVirtualSize.height); const wgpu::TextureFormat format = GetParam().mTextureFormat; srcConfig.textureDescriptor.format = dstConfig.textureDescriptor.format = format; srcConfig.bytesPerRowAlignment = Align(srcConfig.copyExtent3D.width / BlockWidthInTexels() * utils::GetTexelBlockSizeInBytes(format), kTextureBytesPerRowAlignment); dstConfig.textureDescriptor.usage = kDefaultFormatTextureUsage; // Create textureSrc as the source texture and initialize it with pre-prepared compressed // data. wgpu::Texture textureSrc = CreateTextureWithCompressedData(srcConfig); // Create textureDst and copy from the content in textureSrc into it. wgpu::Texture textureDst = CreateTextureFromTexture(textureSrc, srcConfig, dstConfig); // We use the render pipeline to test if each layer can be correctly sampled with the // expected data. wgpu::RenderPipeline renderPipeline = CreateRenderPipelineForTest(); const wgpu::Extent3D kExpectedDataRegionPerLayer = {kDstVirtualSize.width, kDstVirtualSize.height, 1u}; std::vector kExpectedDataPerLayer = GetExpectedData(kExpectedDataRegionPerLayer); const wgpu::Origin3D kCopyOriginPerLayer = {dstConfig.copyOrigin3D.x, dstConfig.copyOrigin3D.y, 0}; for (uint32_t copyLayer = 0; copyLayer < kArrayLayerCount; ++copyLayer) { wgpu::BindGroup bindGroup = CreateBindGroupForTest(renderPipeline.GetBindGroupLayout(0), textureDst, dstConfig.copyOrigin3D.z + copyLayer, dstConfig.viewMipmapLevel); VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kExpectedDataRegionPerLayer, kCopyOriginPerLayer, kExpectedDataRegionPerLayer, kExpectedDataPerLayer); } } // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture // extent exactly fit the RowPitch. TEST_P(CompressedTextureFormatTest, BufferOffsetAndExtentFitRowPitch) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); CopyConfig config = GetDefaultSmallConfig(); config.copyExtent3D = config.textureDescriptor.size; const wgpu::TextureFormat format = GetParam().mTextureFormat; const uint32_t blockCountPerRow = config.textureDescriptor.size.width / BlockWidthInTexels(); const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format); const uint32_t blockCountPerRowPitch = config.bytesPerRowAlignment / blockSizeInBytes; config.bufferOffset = (blockCountPerRowPitch - blockCountPerRow) * blockSizeInBytes; TestCopyRegionIntoFormatTextures(config); } // Test the special case of the B2T copies on the D3D12 backend that the buffer offset exceeds the // slice pitch (slicePitch = bytesPerRow * (rowsPerImage / blockHeightInTexels)). On D3D12 // backend the texelOffset.y will be greater than 0 after calcuting the texelOffset in the function // ComputeTexelOffsets(). TEST_P(CompressedTextureFormatTest, BufferOffsetExceedsSlicePitch) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); CopyConfig config = GetDefaultSmallConfig(); config.copyExtent3D = config.textureDescriptor.size; const wgpu::TextureFormat format = GetParam().mTextureFormat; const wgpu::Extent3D textureSizeLevel = config.textureDescriptor.size; const uint32_t blockCountPerRow = textureSizeLevel.width / BlockWidthInTexels(); const uint32_t slicePitchInBytes = config.bytesPerRowAlignment * (textureSizeLevel.height / BlockHeightInTexels()); const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format); const uint32_t blockCountPerRowPitch = config.bytesPerRowAlignment / blockSizeInBytes; config.bufferOffset = (blockCountPerRowPitch - blockCountPerRow) * blockSizeInBytes + config.bytesPerRowAlignment + slicePitchInBytes; TestCopyRegionIntoFormatTextures(config); } // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture // extent exceed the RowPitch. On D3D12 backend two copies are required for this case. TEST_P(CompressedTextureFormatTest, CopyWithBufferOffsetAndExtentExceedRowPitch) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); constexpr uint32_t kExceedRowBlockCount = 1; CopyConfig config = GetDefaultSmallConfig(); config.copyExtent3D = config.textureDescriptor.size; const wgpu::TextureFormat format = GetParam().mTextureFormat; const uint32_t blockCountPerRow = config.textureDescriptor.size.width / BlockWidthInTexels(); const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format); const uint32_t blockCountPerRowPitch = config.bytesPerRowAlignment / blockSizeInBytes; config.bufferOffset = (blockCountPerRowPitch - blockCountPerRow + kExceedRowBlockCount) * blockSizeInBytes; TestCopyRegionIntoFormatTextures(config); } // Test the special case of the B2T copies on the D3D12 backend that the slicePitch is equal to the // bytesPerRow. On D3D12 backend the texelOffset.z will be greater than 0 after calcuting the // texelOffset in the function ComputeTexelOffsets(). TEST_P(CompressedTextureFormatTest, RowPitchEqualToSlicePitch) { DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); CopyConfig config = GetDefaultSmallConfig(); config.textureDescriptor.size = GetTextureSizeWithNumBlocks(2, 1); config.copyExtent3D = config.textureDescriptor.size; const wgpu::TextureFormat format = GetParam().mTextureFormat; const uint32_t blockCountPerRow = config.textureDescriptor.size.width / BlockWidthInTexels(); const uint32_t slicePitchInBytes = config.bytesPerRowAlignment; const uint32_t blockSizeInBytes = utils::GetTexelBlockSizeInBytes(format); const uint32_t blockCountPerRowPitch = config.bytesPerRowAlignment / blockSizeInBytes; config.bufferOffset = (blockCountPerRowPitch - blockCountPerRow) * blockSizeInBytes + slicePitchInBytes; TestCopyRegionIntoFormatTextures(config); } // Test the workaround in the B2T copies when (bufferSize - bufferOffset < bytesPerImage * // copyExtent.depthOrArrayLayers) on Metal backends. As copyExtent.depthOrArrayLayers can only be 1 // for compressed formats, on Metal backend we will use two copies to implement such copy. TEST_P(CompressedTextureFormatTest, LargeImageHeight) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); CopyConfig config = GetDefaultSmallConfig(); config.copyExtent3D = config.textureDescriptor.size; config.rowsPerImage = config.textureDescriptor.size.height * 2 / BlockHeightInTexels(); TestCopyRegionIntoFormatTextures(config); } // Test the workaround in the B2T copies when (bufferSize - bufferOffset < bytesPerImage * // copyExtent.depthOrArrayLayers) and copyExtent needs to be clamped. TEST_P(CompressedTextureFormatTest, LargeImageHeightAndClampedCopyExtent) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // This test uses glTextureView() which is not supported in OpenGL ES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); CopyConfig config = GetDefaultFullConfig(); // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. const wgpu::Extent3D kPhysicalSize = GetPhysicalSizeAtLevel(config); config.copyExtent3D = kPhysicalSize; config.rowsPerImage = kPhysicalSize.height * 2 / BlockHeightInTexels(); TestCopyRegionIntoFormatTextures(config); } // Test copying a whole 2D array texture with array layer count > 1 in one copy command works with // compressed formats. TEST_P(CompressedTextureFormatTest, CopyWhole2DArrayTexture) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // This test uses glTextureView() which is not supported in OpenGL ES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); constexpr uint32_t kArrayLayerCount = 3; CopyConfig config = GetDefaultSmallConfig(kArrayLayerCount); config.rowsPerImage = 8; config.copyExtent3D = config.textureDescriptor.size; config.copyExtent3D.depthOrArrayLayers = kArrayLayerCount; TestCopyRegionIntoFormatTextures(config); } // Test copying a multiple 2D texture array layers in one copy command works. TEST_P(CompressedTextureFormatTest, CopyMultiple2DArrayLayers) { // TODO(crbug.com/dawn/815): find out why this test fails on Windows Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsWindows()); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); // This test uses glTextureView() which is not supported in OpenGL ES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); constexpr uint32_t kArrayLayerCount = 3; CopyConfig config = GetDefaultSmallConfig(kArrayLayerCount); config.rowsPerImage = 8; constexpr uint32_t kCopyBaseArrayLayer = 1; constexpr uint32_t kCopyLayerCount = 2; config.copyOrigin3D = {0, 0, kCopyBaseArrayLayer}; config.copyExtent3D = config.textureDescriptor.size; config.copyExtent3D.depthOrArrayLayers = kCopyLayerCount; TestCopyRegionIntoFormatTextures(config); } DAWN_INSTANTIATE_TEST_P(CompressedTextureFormatTest, {D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend(), VulkanBackend({"use_temporary_buffer_in_texture_to_texture_copy"})}, std::vector(utils::kCompressedFormats.begin(), utils::kCompressedFormats.end())); // Suite of regression tests that target specific compression types. class CompressedTextureFormatSpecificTest : public DawnTest { protected: std::vector GetRequiredFeatures() override { mIsBCFormatSupported = SupportsFeatures({wgpu::FeatureName::TextureCompressionBC}); std::vector features; if (mIsBCFormatSupported) { features.emplace_back(wgpu::FeatureName::TextureCompressionBC); } return features; } bool IsBCFormatSupported() const { return mIsBCFormatSupported; } bool mIsBCFormatSupported = false; }; // Testing a special code path: clearing a non-renderable texture when DynamicUploader // is unaligned doesn't throw validation errors. TEST_P(CompressedTextureFormatSpecificTest, BC1RGBAUnorm_UnalignedDynamicUploader) { // CopyT2B for compressed texture formats is unimplemented on OpenGL. DAWN_TEST_UNSUPPORTED_IF(IsOpenGL()); DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); DAWN_TEST_UNSUPPORTED_IF(!IsBCFormatSupported()); utils::UnalignDynamicUploader(device); wgpu::TextureDescriptor textureDescriptor = {}; textureDescriptor.size = {4, 4, 1}; textureDescriptor.format = wgpu::TextureFormat::BC1RGBAUnorm; textureDescriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::CopySrc; wgpu::Texture texture = device.CreateTexture(&textureDescriptor); wgpu::BufferDescriptor bufferDescriptor; bufferDescriptor.size = 8; bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst; wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture(texture, 0, {0, 0, 0}); wgpu::ImageCopyBuffer imageCopyBuffer = utils::CreateImageCopyBuffer(buffer, 0, 256); wgpu::Extent3D copyExtent = {4, 4, 1}; wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, ©Extent); wgpu::CommandBuffer commands = encoder.Finish(); queue.Submit(1, &commands); } DAWN_INSTANTIATE_TEST(CompressedTextureFormatSpecificTest, D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend(), VulkanBackend({"use_temporary_buffer_in_texture_to_texture_copy"})); class CompressedTextureWriteTextureTest : public CompressedTextureFormatTest { protected: void SetUp() override { CompressedTextureFormatTest::SetUp(); DAWN_TEST_UNSUPPORTED_IF(!IsFormatSupported()); } // Write the compressed texture data into the destination texture as is specified in // copyConfig. void WriteToCompressedTexture(wgpu::Texture compressedTexture, const CopyConfig& copyConfig) { ASSERT(IsFormatSupported()); std::vector data = UploadData(copyConfig); wgpu::TextureDataLayout textureDataLayout = utils::CreateTextureDataLayout( copyConfig.bufferOffset, copyConfig.bytesPerRowAlignment, copyConfig.rowsPerImage); wgpu::ImageCopyTexture imageCopyTexture = utils::CreateImageCopyTexture( compressedTexture, copyConfig.viewMipmapLevel, copyConfig.copyOrigin3D); queue.WriteTexture(&imageCopyTexture, data.data(), data.size(), &textureDataLayout, ©Config.copyExtent3D); } // Run the tests that write pre-prepared format data into a texture and verifies if we can // render correctly with the pixel values sampled from the texture. void TestWriteRegionIntoFormatTextures(const CopyConfig& config) { ASSERT(IsFormatSupported()); wgpu::Texture texture = device.CreateTexture(&config.textureDescriptor); WriteToCompressedTexture(texture, config); VerifyTexture(config, texture); } }; // Test WriteTexture to a 2D texture with all parameters non-defaults. TEST_P(CompressedTextureWriteTextureTest, Basic) { // TODO(crbug.com/dawn/976): Failing on Linux Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux()); constexpr uint32_t kSizeWidthMultiplier = 5; constexpr uint32_t kSizeHeightMultiplier = 6; constexpr uint32_t kOriginWidthMultiplier = 1; constexpr uint32_t kOriginHeightMultiplier = 2; constexpr uint32_t kExtentWidthMultiplier = 3; constexpr uint32_t kExtentHeightMultiplier = 4; CopyConfig config; config.textureDescriptor.usage = kDefaultFormatTextureUsage; config.textureDescriptor.size = {BlockWidthInTexels() * kSizeWidthMultiplier, BlockHeightInTexels() * kSizeHeightMultiplier, 1}; config.copyOrigin3D = {BlockWidthInTexels() * kOriginWidthMultiplier, BlockHeightInTexels() * kOriginHeightMultiplier, 0}; config.copyExtent3D = {BlockWidthInTexels() * kExtentWidthMultiplier, BlockHeightInTexels() * kExtentHeightMultiplier, 1}; config.bytesPerRowAlignment = 511; config.rowsPerImage = 5; config.textureDescriptor.format = GetParam().mTextureFormat; TestWriteRegionIntoFormatTextures(config); } // Test writing to multiple 2D texture array layers. TEST_P(CompressedTextureWriteTextureTest, WriteMultiple2DArrayLayers) { // TODO(crbug.com/dawn/976): Failing on Linux Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux()); // TODO(crbug.com/dawn/593): This test uses glTextureView() which is not supported on OpenGLES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); // TODO(b/198674734): Width multiplier set to 7 because 5 results in square size for ASTC6x5. constexpr uint32_t kSizeWidthMultiplier = 7; constexpr uint32_t kSizeHeightMultiplier = 6; constexpr uint32_t kOriginWidthMultiplier = 1; constexpr uint32_t kOriginHeightMultiplier = 2; constexpr uint32_t kExtentWidthMultiplier = 3; constexpr uint32_t kExtentHeightMultiplier = 4; CopyConfig config; config.textureDescriptor.usage = kDefaultFormatTextureUsage; config.textureDescriptor.size = {BlockWidthInTexels() * kSizeWidthMultiplier, BlockHeightInTexels() * kSizeHeightMultiplier, 9}; config.copyOrigin3D = {BlockWidthInTexels() * kOriginWidthMultiplier, BlockHeightInTexels() * kOriginHeightMultiplier, 3}; config.copyExtent3D = {BlockWidthInTexels() * kExtentWidthMultiplier, BlockHeightInTexels() * kExtentHeightMultiplier, 6}; config.bytesPerRowAlignment = 511; config.rowsPerImage = 5; config.textureDescriptor.format = GetParam().mTextureFormat; TestWriteRegionIntoFormatTextures(config); } // Test writing textures where the physical size of the destination subresource is different from // its virtual size. TEST_P(CompressedTextureWriteTextureTest, WriteIntoSubresourceWithPhysicalSizeNotEqualToVirtualSize) { // TODO(crbug.com/dawn/976): Failing on Linux Intel OpenGL drivers. DAWN_SUPPRESS_TEST_IF(IsIntel() && IsOpenGL() && IsLinux()); // TODO(crbug.com/dawn/593): This test uses glTextureView() which is not supported on OpenGLES. DAWN_TEST_UNSUPPORTED_IF(IsOpenGLES()); CopyConfig config = GetDefaultFullConfig(); // The virtual size of the texture at mipmap level == 2 is not a multiple of the texel // dimensions so paddings are required in the copies. We then test against the expected // physical size and a valid size smaller than the physical size for verification. const wgpu::Extent3D kPhysicalSize = GetPhysicalSizeAtLevel(config); for (unsigned int w : {kPhysicalSize.width - BlockWidthInTexels(), kPhysicalSize.width}) { for (unsigned int h : {kPhysicalSize.height - BlockHeightInTexels(), kPhysicalSize.height}) { config.copyExtent3D = {w, h, 1}; TestWriteRegionIntoFormatTextures(config); } } } DAWN_INSTANTIATE_TEST_P(CompressedTextureWriteTextureTest, {D3D12Backend(), MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend()}, std::vector(utils::kCompressedFormats.begin(), utils::kCompressedFormats.end()));