1 // Copyright 2019 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "tests/DawnTest.h"
16
17 #include "common/Assert.h"
18 #include "common/Constants.h"
19 #include "common/Math.h"
20 #include "utils/ComboRenderPipelineDescriptor.h"
21 #include "utils/DawnHelpers.h"
22
23 // Create a 2D texture for sampling in the tests.
Create2DTexture(dawn::Device device,dawn::TextureFormat format,uint32_t width,uint32_t height,uint32_t arrayLayerCount=1,uint32_t mipLevelCount=1,dawn::TextureUsageBit usage=dawn::TextureUsageBit::Sampled|dawn::TextureUsageBit::CopyDst)24 dawn::Texture Create2DTexture(dawn::Device device,
25 dawn::TextureFormat format,
26 uint32_t width,
27 uint32_t height,
28 uint32_t arrayLayerCount = 1,
29 uint32_t mipLevelCount = 1,
30 dawn::TextureUsageBit usage = dawn::TextureUsageBit::Sampled |
31 dawn::TextureUsageBit::CopyDst) {
32 dawn::TextureDescriptor descriptor;
33 descriptor.dimension = dawn::TextureDimension::e2D;
34 descriptor.format = format;
35 descriptor.size.width = width;
36 descriptor.size.height = height;
37 descriptor.size.depth = 1;
38 descriptor.arrayLayerCount = arrayLayerCount;
39 descriptor.sampleCount = 1;
40 descriptor.mipLevelCount = mipLevelCount;
41 descriptor.usage = usage;
42 return device.CreateTexture(&descriptor);
43 }
44
45 // The helper struct to configure the copies between buffers and textures.
46 struct CopyConfig {
47 dawn::TextureFormat format;
48 uint32_t textureWidthLevel0;
49 uint32_t textureHeightLevel0;
50 dawn::Extent3D copyExtent3D;
51 dawn::Origin3D copyOrigin3D = {0, 0, 0};
52 uint32_t arrayLayerCount = 1;
53 uint32_t mipmapLevelCount = 1;
54 uint32_t baseMipmapLevel = 0;
55 uint32_t baseArrayLayer = 0;
56 uint32_t bufferOffset = 0;
57 uint32_t rowPitchAlignment = kTextureRowPitchAlignment;
58 uint32_t imageHeight = 0;
59 };
60
61 class CompressedTextureBCFormatTest : public DawnTest {
62 protected:
SetUp()63 void SetUp() override {
64 DawnTest::SetUp();
65 mBindGroupLayout = utils::MakeBindGroupLayout(
66 device, {{0, dawn::ShaderStageBit::Fragment, dawn::BindingType::Sampler},
67 {1, dawn::ShaderStageBit::Fragment, dawn::BindingType::SampledTexture}});
68 }
69
70 // Copy the compressed texture data into the destination texture as is specified in copyConfig.
CopyDataIntoCompressedTexture(dawn::Texture bcCompressedTexture,const CopyConfig & copyConfig)71 void CopyDataIntoCompressedTexture(dawn::Texture bcCompressedTexture,
72 const CopyConfig& copyConfig) {
73 // Compute the upload buffer size with rowPitchAlignment and the copy region.
74 uint32_t actualWidthAtLevel = copyConfig.textureWidthLevel0 >> copyConfig.baseMipmapLevel;
75 uint32_t actualHeightAtLevel = copyConfig.textureHeightLevel0 >> copyConfig.baseMipmapLevel;
76 uint32_t copyWidthInBlockAtLevel =
77 (actualWidthAtLevel + kBCBlockWidthInTexels - 1) / kBCBlockWidthInTexels;
78 uint32_t copyHeightInBlockAtLevel =
79 (actualHeightAtLevel + kBCBlockHeightInTexels - 1) / kBCBlockHeightInTexels;
80 uint32_t bufferRowPitchInBytes = 0;
81 if (copyConfig.rowPitchAlignment != 0) {
82 bufferRowPitchInBytes = copyConfig.rowPitchAlignment;
83 } else {
84 bufferRowPitchInBytes =
85 copyWidthInBlockAtLevel * CompressedFormatBlockSizeInBytes(copyConfig.format);
86 }
87 uint32_t uploadBufferSize =
88 copyConfig.bufferOffset + bufferRowPitchInBytes * copyHeightInBlockAtLevel;
89
90 // Fill uploadData with the pre-prepared one-block compressed texture data.
91 std::vector<uint8_t> uploadData(uploadBufferSize, 0);
92 std::vector<uint8_t> oneBlockCompressedTextureData =
93 GetOneBlockBCFormatTextureData(copyConfig.format);
94 for (uint32_t h = 0; h < copyHeightInBlockAtLevel; ++h) {
95 for (uint32_t w = 0; w < copyWidthInBlockAtLevel; ++w) {
96 uint32_t uploadBufferOffset = copyConfig.bufferOffset + bufferRowPitchInBytes * h +
97 oneBlockCompressedTextureData.size() * w;
98 std::memcpy(&uploadData[uploadBufferOffset], oneBlockCompressedTextureData.data(),
99 oneBlockCompressedTextureData.size() * sizeof(uint8_t));
100 }
101 }
102
103 // Copy texture data from a staging buffer to the destination texture.
104 dawn::Buffer stagingBuffer = utils::CreateBufferFromData(
105 device, uploadData.data(), uploadBufferSize, dawn::BufferUsageBit::CopySrc);
106 dawn::BufferCopyView bufferCopyView =
107 utils::CreateBufferCopyView(stagingBuffer, copyConfig.bufferOffset,
108 copyConfig.rowPitchAlignment, copyConfig.imageHeight);
109 dawn::TextureCopyView textureCopyView =
110 utils::CreateTextureCopyView(bcCompressedTexture, copyConfig.baseMipmapLevel,
111 copyConfig.baseArrayLayer, copyConfig.copyOrigin3D);
112
113 dawn::CommandEncoder encoder = device.CreateCommandEncoder();
114 encoder.CopyBufferToTexture(&bufferCopyView, &textureCopyView, ©Config.copyExtent3D);
115 dawn::CommandBuffer copy = encoder.Finish();
116 queue.Submit(1, ©);
117 }
118
119 // Create the bind group that includes a BC texture and a sampler.
CreateBindGroupForTest(dawn::Texture bcCompressedTexture,dawn::TextureFormat bcFormat,uint32_t baseArrayLayer=0,uint32_t baseMipLevel=0)120 dawn::BindGroup CreateBindGroupForTest(dawn::Texture bcCompressedTexture,
121 dawn::TextureFormat bcFormat,
122 uint32_t baseArrayLayer = 0,
123 uint32_t baseMipLevel = 0) {
124 dawn::SamplerDescriptor samplerDesc = utils::GetDefaultSamplerDescriptor();
125 samplerDesc.minFilter = dawn::FilterMode::Nearest;
126 samplerDesc.magFilter = dawn::FilterMode::Nearest;
127 dawn::Sampler sampler = device.CreateSampler(&samplerDesc);
128
129 dawn::TextureViewDescriptor textureViewDescriptor;
130 textureViewDescriptor.format = bcFormat;
131 textureViewDescriptor.dimension = dawn::TextureViewDimension::e2D;
132 textureViewDescriptor.baseMipLevel = baseMipLevel;
133 textureViewDescriptor.baseArrayLayer = baseArrayLayer;
134 textureViewDescriptor.arrayLayerCount = 1;
135 textureViewDescriptor.mipLevelCount = 1;
136 dawn::TextureView bcTextureView = bcCompressedTexture.CreateView(&textureViewDescriptor);
137
138 return utils::MakeBindGroup(device, mBindGroupLayout, {{0, sampler}, {1, bcTextureView}});
139 }
140
141 // Create a render pipeline for sampling from a BC texture and rendering into the render target.
CreateRenderPipelineForTest()142 dawn::RenderPipeline CreateRenderPipelineForTest() {
143 dawn::PipelineLayout pipelineLayout =
144 utils::MakeBasicPipelineLayout(device, &mBindGroupLayout);
145
146 utils::ComboRenderPipelineDescriptor renderPipelineDescriptor(device);
147 dawn::ShaderModule vsModule =
148 utils::CreateShaderModule(device, utils::ShaderStage::Vertex, R"(
149 #version 450
150 layout(location=0) out vec2 texCoord;
151 void main() {
152 const vec2 pos[3] = vec2[3](
153 vec2(-3.0f, -1.0f),
154 vec2( 3.0f, -1.0f),
155 vec2( 0.0f, 2.0f)
156 );
157 gl_Position = vec4(pos[gl_VertexIndex], 0.0f, 1.0f);
158 texCoord = gl_Position.xy / 2.0f + vec2(0.5f);
159 })");
160 dawn::ShaderModule fsModule =
161 utils::CreateShaderModule(device, utils::ShaderStage::Fragment, R"(
162 #version 450
163 layout(set = 0, binding = 0) uniform sampler sampler0;
164 layout(set = 0, binding = 1) uniform texture2D texture0;
165 layout(location = 0) in vec2 texCoord;
166 layout(location = 0) out vec4 fragColor;
167
168 void main() {
169 fragColor = texture(sampler2D(texture0, sampler0), texCoord);
170 })");
171 renderPipelineDescriptor.cVertexStage.module = vsModule;
172 renderPipelineDescriptor.cFragmentStage.module = fsModule;
173 renderPipelineDescriptor.layout = pipelineLayout;
174 renderPipelineDescriptor.cColorStates[0]->format =
175 utils::BasicRenderPass::kDefaultColorFormat;
176 return device.CreateRenderPipeline(&renderPipelineDescriptor);
177 }
178
179 // Run the given render pipeline and bind group and verify the pixels in the render target.
VerifyCompressedTexturePixelValues(dawn::RenderPipeline renderPipeline,dawn::BindGroup bindGroup,const dawn::Extent3D & renderTargetSize,const dawn::Origin3D & expectedOrigin,const dawn::Extent3D & expectedExtent,const std::vector<RGBA8> & expected)180 void VerifyCompressedTexturePixelValues(dawn::RenderPipeline renderPipeline,
181 dawn::BindGroup bindGroup,
182 const dawn::Extent3D& renderTargetSize,
183 const dawn::Origin3D& expectedOrigin,
184 const dawn::Extent3D& expectedExtent,
185 const std::vector<RGBA8>& expected) {
186 ASSERT(expected.size() == renderTargetSize.width * renderTargetSize.height);
187 utils::BasicRenderPass renderPass =
188 utils::CreateBasicRenderPass(device, renderTargetSize.width, renderTargetSize.height);
189
190 dawn::CommandEncoder encoder = device.CreateCommandEncoder();
191 {
192 dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
193 pass.SetPipeline(renderPipeline);
194 pass.SetBindGroup(0, bindGroup, 0, nullptr);
195 pass.Draw(6, 1, 0, 0);
196 pass.EndPass();
197 }
198
199 dawn::CommandBuffer commands = encoder.Finish();
200 queue.Submit(1, &commands);
201
202 EXPECT_TEXTURE_RGBA8_EQ(expected.data(), renderPass.color, expectedOrigin.x,
203 expectedOrigin.y, expectedExtent.width, expectedExtent.height, 0,
204 0);
205 }
206
207 // Run the tests that copies pre-prepared BC format data into a BC texture and verifies if we
208 // can render correctly with the pixel values sampled from the BC texture.
TestCopyRegionIntoBCFormatTextures(const CopyConfig & config)209 void TestCopyRegionIntoBCFormatTextures(const CopyConfig& config) {
210 dawn::Texture bcTexture = Create2DTexture(device, config.format, config.textureWidthLevel0,
211 config.textureHeightLevel0,
212 config.arrayLayerCount, config.mipmapLevelCount);
213 CopyDataIntoCompressedTexture(bcTexture, config);
214
215 dawn::BindGroup bindGroup = CreateBindGroupForTest(
216 bcTexture, config.format, config.baseArrayLayer, config.baseMipmapLevel);
217 dawn::RenderPipeline renderPipeline = CreateRenderPipelineForTest();
218
219 dawn::Extent3D virtualSizeAtLevel = GetVirtualSizeAtLevel(config);
220
221 // The copy region may exceed the subresource size because of the required paddings for BC
222 // blocks, so we should limit the size of the expectedData to make it match the real size
223 // of the render target.
224 dawn::Extent3D noPaddingExtent3D = config.copyExtent3D;
225 if (config.copyOrigin3D.x + config.copyExtent3D.width > virtualSizeAtLevel.width) {
226 noPaddingExtent3D.width = virtualSizeAtLevel.width - config.copyOrigin3D.x;
227 }
228 if (config.copyOrigin3D.y + config.copyExtent3D.height > virtualSizeAtLevel.height) {
229 noPaddingExtent3D.height = virtualSizeAtLevel.height - config.copyOrigin3D.y;
230 }
231
232 std::vector<RGBA8> expectedData = GetExpectedData(config.format, virtualSizeAtLevel);
233 VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, virtualSizeAtLevel,
234 config.copyOrigin3D, noPaddingExtent3D, expectedData);
235 }
236
237 // Return the BC block size in bytes.
CompressedFormatBlockSizeInBytes(dawn::TextureFormat format)238 static uint32_t CompressedFormatBlockSizeInBytes(dawn::TextureFormat format) {
239 switch (format) {
240 case dawn::TextureFormat::BC1RGBAUnorm:
241 case dawn::TextureFormat::BC1RGBAUnormSrgb:
242 case dawn::TextureFormat::BC4RSnorm:
243 case dawn::TextureFormat::BC4RUnorm:
244 return 8;
245 case dawn::TextureFormat::BC2RGBAUnorm:
246 case dawn::TextureFormat::BC2RGBAUnormSrgb:
247 case dawn::TextureFormat::BC3RGBAUnorm:
248 case dawn::TextureFormat::BC3RGBAUnormSrgb:
249 case dawn::TextureFormat::BC5RGSnorm:
250 case dawn::TextureFormat::BC5RGUnorm:
251 case dawn::TextureFormat::BC6HRGBSfloat:
252 case dawn::TextureFormat::BC6HRGBUfloat:
253 case dawn::TextureFormat::BC7RGBAUnorm:
254 case dawn::TextureFormat::BC7RGBAUnormSrgb:
255 return 16;
256 default:
257 UNREACHABLE();
258 return 0;
259 }
260 }
261
262 // Return the pre-prepared one-block BC texture data.
GetOneBlockBCFormatTextureData(dawn::TextureFormat bcFormat)263 static std::vector<uint8_t> GetOneBlockBCFormatTextureData(dawn::TextureFormat bcFormat) {
264 switch (bcFormat) {
265 // The expected data represents 4x4 pixel images with the left side dark red and the
266 // right side dark green. We specify the same compressed data in both sRGB and non-sRGB
267 // tests, but the rendering result should be different because for sRGB formats, the
268 // red, green, and blue components are converted from an sRGB color space to a linear
269 // color space as part of filtering.
270 case dawn::TextureFormat::BC1RGBAUnorm:
271 case dawn::TextureFormat::BC1RGBAUnormSrgb:
272 return {0x0, 0xC0, 0x60, 0x6, 0x50, 0x50, 0x50, 0x50};
273 case dawn::TextureFormat::BC7RGBAUnorm:
274 case dawn::TextureFormat::BC7RGBAUnormSrgb:
275 return {0x50, 0x18, 0xfc, 0xf, 0x0, 0x30, 0xe3, 0xe1,
276 0xe1, 0xe1, 0xc1, 0xf, 0xfc, 0xc0, 0xf, 0xfc};
277
278 // The expected data represents 4x4 pixel images with the left side dark red and the
279 // right side dark green. The pixels in the left side of the block all have an alpha
280 // value equal to 0x88. We specify the same compressed data in both sRGB and non-sRGB
281 // tests, but the rendering result should be different because for sRGB formats, the
282 // red, green, and blue components are converted from an sRGB color space to a linear
283 // color space as part of filtering, and any alpha component is left unchanged.
284 case dawn::TextureFormat::BC2RGBAUnorm:
285 case dawn::TextureFormat::BC2RGBAUnormSrgb:
286 return {0x88, 0xFF, 0x88, 0xFF, 0x88, 0xFF, 0x88, 0xFF,
287 0x0, 0xC0, 0x60, 0x6, 0x50, 0x50, 0x50, 0x50};
288 case dawn::TextureFormat::BC3RGBAUnorm:
289 case dawn::TextureFormat::BC3RGBAUnormSrgb:
290 return {0x88, 0xFF, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24,
291 0x0, 0xC0, 0x60, 0x6, 0x50, 0x50, 0x50, 0x50};
292
293 // The expected data represents 4x4 pixel images with the left side red and the
294 // right side black.
295 case dawn::TextureFormat::BC4RSnorm:
296 return {0x7F, 0x0, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24};
297 case dawn::TextureFormat::BC4RUnorm:
298 return {0xFF, 0x0, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24};
299
300 // The expected data represents 4x4 pixel images with the left side red and the right
301 // side green and was encoded with DirectXTex from Microsoft.
302 case dawn::TextureFormat::BC5RGSnorm:
303 return {0x7f, 0x81, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24,
304 0x7f, 0x81, 0x9, 0x90, 0x0, 0x9, 0x90, 0x0};
305 case dawn::TextureFormat::BC5RGUnorm:
306 return {0xff, 0x0, 0x40, 0x2, 0x24, 0x40, 0x2, 0x24,
307 0xff, 0x0, 0x9, 0x90, 0x0, 0x9, 0x90, 0x0};
308 case dawn::TextureFormat::BC6HRGBSfloat:
309 return {0xe3, 0x1f, 0x0, 0x0, 0x0, 0xe0, 0x1f, 0x0,
310 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff};
311 case dawn::TextureFormat::BC6HRGBUfloat:
312 return {0xe3, 0x3d, 0x0, 0x0, 0x0, 0xe0, 0x3d, 0x0,
313 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff};
314
315 default:
316 UNREACHABLE();
317 return {};
318 }
319 }
320
321 // Return the texture data that is decoded from the result of GetOneBlockBCFormatTextureData in
322 // RGBA8 formats.
GetExpectedData(dawn::TextureFormat bcFormat,const dawn::Extent3D & testRegion)323 static std::vector<RGBA8> GetExpectedData(dawn::TextureFormat bcFormat,
324 const dawn::Extent3D& testRegion) {
325 constexpr RGBA8 kRed(255, 0, 0, 255);
326 constexpr RGBA8 kGreen(0, 255, 0, 255);
327 constexpr RGBA8 kBlack(0, 0, 0, 255);
328 constexpr RGBA8 kDarkRed(198, 0, 0, 255);
329 constexpr RGBA8 kDarkGreen(0, 207, 0, 255);
330 constexpr RGBA8 kDarkRedSRGB(144, 0, 0, 255);
331 constexpr RGBA8 kDarkGreenSRGB(0, 159, 0, 255);
332
333 constexpr uint8_t kLeftAlpha = 0x88;
334 constexpr uint8_t kRightAlpha = 0xFF;
335
336 switch (bcFormat) {
337 case dawn::TextureFormat::BC1RGBAUnorm:
338 case dawn::TextureFormat::BC7RGBAUnorm:
339 return FillExpectedData(testRegion, kDarkRed, kDarkGreen);
340
341 case dawn::TextureFormat::BC2RGBAUnorm:
342 case dawn::TextureFormat::BC3RGBAUnorm: {
343 constexpr RGBA8 kLeftColor = RGBA8(kDarkRed.r, 0, 0, kLeftAlpha);
344 constexpr RGBA8 kRightColor = RGBA8(0, kDarkGreen.g, 0, kRightAlpha);
345 return FillExpectedData(testRegion, kLeftColor, kRightColor);
346 }
347
348 case dawn::TextureFormat::BC1RGBAUnormSrgb:
349 case dawn::TextureFormat::BC7RGBAUnormSrgb:
350 return FillExpectedData(testRegion, kDarkRedSRGB, kDarkGreenSRGB);
351
352 case dawn::TextureFormat::BC2RGBAUnormSrgb:
353 case dawn::TextureFormat::BC3RGBAUnormSrgb: {
354 constexpr RGBA8 kLeftColor = RGBA8(kDarkRedSRGB.r, 0, 0, kLeftAlpha);
355 constexpr RGBA8 kRightColor = RGBA8(0, kDarkGreenSRGB.g, 0, kRightAlpha);
356 return FillExpectedData(testRegion, kLeftColor, kRightColor);
357 }
358
359 case dawn::TextureFormat::BC4RSnorm:
360 case dawn::TextureFormat::BC4RUnorm:
361 return FillExpectedData(testRegion, kRed, kBlack);
362
363 case dawn::TextureFormat::BC5RGSnorm:
364 case dawn::TextureFormat::BC5RGUnorm:
365 case dawn::TextureFormat::BC6HRGBSfloat:
366 case dawn::TextureFormat::BC6HRGBUfloat:
367 return FillExpectedData(testRegion, kRed, kGreen);
368
369 default:
370 UNREACHABLE();
371 return {};
372 }
373 }
374
FillExpectedData(const dawn::Extent3D & testRegion,RGBA8 leftColorInBlock,RGBA8 rightColorInBlock)375 static std::vector<RGBA8> FillExpectedData(const dawn::Extent3D& testRegion,
376 RGBA8 leftColorInBlock,
377 RGBA8 rightColorInBlock) {
378 ASSERT(testRegion.depth == 1);
379
380 std::vector<RGBA8> expectedData(testRegion.width * testRegion.height, leftColorInBlock);
381 for (uint32_t y = 0; y < testRegion.height; ++y) {
382 for (uint32_t x = 0; x < testRegion.width; ++x) {
383 if (x % kBCBlockWidthInTexels >= kBCBlockWidthInTexels / 2) {
384 expectedData[testRegion.width * y + x] = rightColorInBlock;
385 }
386 }
387 }
388 return expectedData;
389 }
390
GetVirtualSizeAtLevel(const CopyConfig & config)391 static dawn::Extent3D GetVirtualSizeAtLevel(const CopyConfig& config) {
392 return {config.textureWidthLevel0 >> config.baseMipmapLevel,
393 config.textureHeightLevel0 >> config.baseMipmapLevel, 1};
394 }
395
GetPhysicalSizeAtLevel(const CopyConfig & config)396 static dawn::Extent3D GetPhysicalSizeAtLevel(const CopyConfig& config) {
397 dawn::Extent3D sizeAtLevel = GetVirtualSizeAtLevel(config);
398 sizeAtLevel.width = (sizeAtLevel.width + kBCBlockWidthInTexels - 1) /
399 kBCBlockWidthInTexels * kBCBlockWidthInTexels;
400 sizeAtLevel.height = (sizeAtLevel.height + kBCBlockHeightInTexels - 1) /
401 kBCBlockHeightInTexels * kBCBlockHeightInTexels;
402 return sizeAtLevel;
403 }
404
405 const std::array<dawn::TextureFormat, 14> kBCFormats = {
406 dawn::TextureFormat::BC1RGBAUnorm, dawn::TextureFormat::BC1RGBAUnormSrgb,
407 dawn::TextureFormat::BC2RGBAUnorm, dawn::TextureFormat::BC2RGBAUnormSrgb,
408 dawn::TextureFormat::BC3RGBAUnorm, dawn::TextureFormat::BC3RGBAUnormSrgb,
409 dawn::TextureFormat::BC4RSnorm, dawn::TextureFormat::BC4RUnorm,
410 dawn::TextureFormat::BC5RGSnorm, dawn::TextureFormat::BC5RGUnorm,
411 dawn::TextureFormat::BC6HRGBSfloat, dawn::TextureFormat::BC6HRGBUfloat,
412 dawn::TextureFormat::BC7RGBAUnorm, dawn::TextureFormat::BC7RGBAUnormSrgb};
413
414 // Tthe block width and height in texels are 4 for all BC formats.
415 static constexpr uint32_t kBCBlockWidthInTexels = 4;
416 static constexpr uint32_t kBCBlockHeightInTexels = 4;
417
418 dawn::BindGroupLayout mBindGroupLayout;
419 };
420
421 // Test copying into the whole BC texture with 2x2 blocks and sampling from it.
TEST_P(CompressedTextureBCFormatTest,Basic)422 TEST_P(CompressedTextureBCFormatTest, Basic) {
423 CopyConfig config;
424 config.textureWidthLevel0 = 8;
425 config.textureHeightLevel0 = 8;
426 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
427
428 for (dawn::TextureFormat format : kBCFormats) {
429 config.format = format;
430 TestCopyRegionIntoBCFormatTextures(config);
431 }
432 }
433
434 // Test copying into a sub-region of a texture with BC formats works correctly.
TEST_P(CompressedTextureBCFormatTest,CopyIntoSubRegion)435 TEST_P(CompressedTextureBCFormatTest, CopyIntoSubRegion) {
436 CopyConfig config;
437 config.textureHeightLevel0 = 8;
438 config.textureWidthLevel0 = 8;
439 config.rowPitchAlignment = kTextureRowPitchAlignment;
440
441 const dawn::Origin3D kOrigin = {4, 4, 0};
442 const dawn::Extent3D kExtent3D = {4, 4, 1};
443 config.copyOrigin3D = kOrigin;
444 config.copyExtent3D = kExtent3D;
445
446 for (dawn::TextureFormat format : kBCFormats) {
447 config.format = format;
448 TestCopyRegionIntoBCFormatTextures(config);
449 }
450 }
451
452 // Test using rowPitch == 0 in the copies with BC formats works correctly.
TEST_P(CompressedTextureBCFormatTest,CopyWithZeroRowPitch)453 TEST_P(CompressedTextureBCFormatTest, CopyWithZeroRowPitch) {
454 CopyConfig config;
455 config.textureHeightLevel0 = 8;
456
457 config.rowPitchAlignment = 0;
458
459 for (dawn::TextureFormat format : kBCFormats) {
460 config.format = format;
461 config.textureWidthLevel0 = kTextureRowPitchAlignment /
462 CompressedFormatBlockSizeInBytes(config.format) *
463 kBCBlockWidthInTexels;
464 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
465 TestCopyRegionIntoBCFormatTextures(config);
466 }
467 }
468
469 // Test copying into the non-zero layer of a 2D array texture with BC formats works correctly.
TEST_P(CompressedTextureBCFormatTest,CopyIntoNonZeroArrayLayer)470 TEST_P(CompressedTextureBCFormatTest, CopyIntoNonZeroArrayLayer) {
471 CopyConfig config;
472 config.textureHeightLevel0 = 8;
473 config.textureWidthLevel0 = 8;
474 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
475 config.rowPitchAlignment = kTextureRowPitchAlignment;
476
477 constexpr uint32_t kArrayLayerCount = 3;
478 config.arrayLayerCount = kArrayLayerCount;
479 config.baseArrayLayer = kArrayLayerCount - 1;
480
481 for (dawn::TextureFormat format : kBCFormats) {
482 config.format = format;
483 TestCopyRegionIntoBCFormatTextures(config);
484 }
485 }
486
487 // Test copying into a non-zero mipmap level of a texture with BC texture formats.
TEST_P(CompressedTextureBCFormatTest,CopyBufferIntoNonZeroMipmapLevel)488 TEST_P(CompressedTextureBCFormatTest, CopyBufferIntoNonZeroMipmapLevel) {
489 CopyConfig config;
490 config.textureHeightLevel0 = 60;
491 config.textureWidthLevel0 = 60;
492 config.rowPitchAlignment = kTextureRowPitchAlignment;
493
494 constexpr uint32_t kMipmapLevelCount = 3;
495 config.mipmapLevelCount = kMipmapLevelCount;
496 config.baseMipmapLevel = kMipmapLevelCount - 1;
497
498 // The actual size of the texture at mipmap level == 2 is not a multiple of 4, paddings are
499 // required in the copies.
500 const uint32_t kActualWidthAtLevel = config.textureWidthLevel0 >> config.baseMipmapLevel;
501 const uint32_t kActualHeightAtLevel = config.textureHeightLevel0 >> config.baseMipmapLevel;
502 ASSERT(kActualWidthAtLevel % kBCBlockWidthInTexels != 0);
503 ASSERT(kActualHeightAtLevel % kBCBlockHeightInTexels != 0);
504
505 const uint32_t kCopyWidthAtLevel = (kActualWidthAtLevel + kBCBlockWidthInTexels - 1) /
506 kBCBlockWidthInTexels * kBCBlockWidthInTexels;
507 const uint32_t kCopyHeightAtLevel = (kActualHeightAtLevel + kBCBlockHeightInTexels - 1) /
508 kBCBlockHeightInTexels * kBCBlockHeightInTexels;
509
510 config.copyExtent3D = {kCopyWidthAtLevel, kCopyHeightAtLevel, 1};
511
512 for (dawn::TextureFormat format : kBCFormats) {
513 config.format = format;
514 TestCopyRegionIntoBCFormatTextures(config);
515 }
516 }
517
518 // Test texture-to-texture whole-size copies with BC formats.
TEST_P(CompressedTextureBCFormatTest,CopyWholeTextureSubResourceIntoNonZeroMipmapLevel)519 TEST_P(CompressedTextureBCFormatTest, CopyWholeTextureSubResourceIntoNonZeroMipmapLevel) {
520 // TODO(cwallez@chromium.org): This consistently fails on with the 12th pixel being opaque black
521 // instead of opaque red on Win10 FYI Release (NVIDIA GeForce GTX 1660). See
522 // https://bugs.chromium.org/p/chromium/issues/detail?id=981393
523 DAWN_SKIP_TEST_IF(IsWindows() && IsVulkan() && IsNvidia());
524
525 CopyConfig config;
526 config.textureHeightLevel0 = 60;
527 config.textureWidthLevel0 = 60;
528 config.rowPitchAlignment = kTextureRowPitchAlignment;
529
530 constexpr uint32_t kMipmapLevelCount = 3;
531 config.mipmapLevelCount = kMipmapLevelCount;
532 config.baseMipmapLevel = kMipmapLevelCount - 1;
533
534 // The actual size of the texture at mipmap level == 2 is not a multiple of 4, paddings are
535 // required in the copies.
536 const dawn::Extent3D kVirtualSize = GetVirtualSizeAtLevel(config);
537 const dawn::Extent3D kPhysicalSize = GetPhysicalSizeAtLevel(config);
538 ASSERT(kVirtualSize.width % kBCBlockWidthInTexels != 0);
539 ASSERT(kVirtualSize.height % kBCBlockHeightInTexels != 0);
540
541 config.copyExtent3D = kPhysicalSize;
542 for (dawn::TextureFormat format : kBCFormats) {
543 // Create bcTextureSrc as the source texture and initialize it with pre-prepared BC
544 // compressed data.
545 config.format = format;
546 dawn::Texture bcTextureSrc = Create2DTexture(
547 device, config.format, config.textureWidthLevel0, config.textureHeightLevel0,
548 config.arrayLayerCount, config.mipmapLevelCount,
549 dawn::TextureUsageBit::CopySrc | dawn::TextureUsageBit::CopyDst);
550 CopyDataIntoCompressedTexture(bcTextureSrc, config);
551
552 // Create bcTexture and copy from the content in bcTextureSrc into it.
553 dawn::Texture bcTexture = Create2DTexture(device, config.format, config.textureWidthLevel0,
554 config.textureHeightLevel0,
555 config.arrayLayerCount, config.mipmapLevelCount);
556 dawn::TextureCopyView textureCopyViewSrc = utils::CreateTextureCopyView(
557 bcTextureSrc, config.baseMipmapLevel, config.baseArrayLayer, config.copyOrigin3D);
558 dawn::TextureCopyView textureCopyViewDst = utils::CreateTextureCopyView(
559 bcTexture, config.baseMipmapLevel, config.baseArrayLayer, config.copyOrigin3D);
560 dawn::CommandEncoder encoder = device.CreateCommandEncoder();
561 encoder.CopyTextureToTexture(&textureCopyViewSrc, &textureCopyViewDst,
562 &config.copyExtent3D);
563 dawn::CommandBuffer copy = encoder.Finish();
564 queue.Submit(1, ©);
565
566 // Verify if we can use bcTexture as sampled textures correctly.
567 dawn::BindGroup bindGroup = CreateBindGroupForTest(
568 bcTexture, config.format, config.baseArrayLayer, config.baseMipmapLevel);
569 dawn::RenderPipeline renderPipeline = CreateRenderPipelineForTest();
570
571 std::vector<RGBA8> expectedData = GetExpectedData(config.format, kVirtualSize);
572 VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kVirtualSize,
573 config.copyOrigin3D, kVirtualSize, expectedData);
574 }
575 }
576
577 // Test BC format texture-to-texture partial copies.
TEST_P(CompressedTextureBCFormatTest,CopyPartofTextureSubResourceIntoNonZeroMipmapLevel)578 TEST_P(CompressedTextureBCFormatTest, CopyPartofTextureSubResourceIntoNonZeroMipmapLevel) {
579 // TODO(jiawei.shao@intel.com): add workaround on the T2T copies where Extent3D fits in one
580 // subresource and does not fit in another one on Vulkan. Currently this test causes an error if
581 // Vulkan validation layer is enabled.
582 DAWN_SKIP_TEST_IF(IsVulkan());
583
584 CopyConfig srcConfig;
585 srcConfig.textureHeightLevel0 = 60;
586 srcConfig.textureWidthLevel0 = 60;
587 srcConfig.rowPitchAlignment = kTextureRowPitchAlignment;
588 srcConfig.mipmapLevelCount = 1;
589 srcConfig.baseMipmapLevel = srcConfig.mipmapLevelCount - 1;
590
591 const dawn::Extent3D kSrcVirtualSize = GetVirtualSizeAtLevel(srcConfig);
592
593 CopyConfig dstConfig;
594 dstConfig.textureHeightLevel0 = 60;
595 dstConfig.textureWidthLevel0 = 60;
596 dstConfig.rowPitchAlignment = kTextureRowPitchAlignment;
597
598 constexpr uint32_t kMipmapLevelCount = 3;
599 dstConfig.mipmapLevelCount = kMipmapLevelCount;
600 dstConfig.baseMipmapLevel = kMipmapLevelCount - 1;
601
602 // The actual size of the texture at mipmap level == 2 is not a multiple of 4, paddings are
603 // required in the copies.
604 const dawn::Extent3D kDstVirtualSize = GetVirtualSizeAtLevel(dstConfig);
605 ASSERT(kDstVirtualSize.width % kBCBlockWidthInTexels != 0);
606 ASSERT(kDstVirtualSize.height % kBCBlockHeightInTexels != 0);
607
608 const dawn::Extent3D kDstPhysicalSize = GetPhysicalSizeAtLevel(dstConfig);
609
610 srcConfig.copyExtent3D = dstConfig.copyExtent3D = kDstPhysicalSize;
611 ASSERT(srcConfig.copyOrigin3D.x + srcConfig.copyExtent3D.width < kSrcVirtualSize.width);
612 ASSERT(srcConfig.copyOrigin3D.y + srcConfig.copyExtent3D.height < kSrcVirtualSize.height);
613
614 for (dawn::TextureFormat format : kBCFormats) {
615 // Create bcTextureSrc as the source texture and initialize it with pre-prepared BC
616 // compressed data.
617 srcConfig.format = format;
618 dawn::Texture bcTextureSrc = Create2DTexture(
619 device, srcConfig.format, srcConfig.textureWidthLevel0, srcConfig.textureHeightLevel0,
620 srcConfig.arrayLayerCount, srcConfig.mipmapLevelCount,
621 dawn::TextureUsageBit::CopySrc | dawn::TextureUsageBit::CopyDst);
622 CopyDataIntoCompressedTexture(bcTextureSrc, srcConfig);
623 dawn::TextureCopyView textureCopyViewSrc =
624 utils::CreateTextureCopyView(bcTextureSrc, srcConfig.baseMipmapLevel,
625 srcConfig.baseArrayLayer, srcConfig.copyOrigin3D);
626
627 // Create bcTexture and copy from the content in bcTextureSrc into it.
628 dstConfig.format = format;
629 dawn::Texture bcTexture = Create2DTexture(
630 device, dstConfig.format, dstConfig.textureWidthLevel0, dstConfig.textureHeightLevel0,
631 dstConfig.arrayLayerCount, dstConfig.mipmapLevelCount);
632 dawn::TextureCopyView textureCopyViewDst = utils::CreateTextureCopyView(
633 bcTexture, dstConfig.baseMipmapLevel, dstConfig.baseArrayLayer, dstConfig.copyOrigin3D);
634
635 dawn::CommandEncoder encoder = device.CreateCommandEncoder();
636 encoder.CopyTextureToTexture(&textureCopyViewSrc, &textureCopyViewDst,
637 &dstConfig.copyExtent3D);
638 dawn::CommandBuffer copy = encoder.Finish();
639 queue.Submit(1, ©);
640
641 // Verify if we can use bcTexture as sampled textures correctly.
642 dawn::BindGroup bindGroup = CreateBindGroupForTest(
643 bcTexture, dstConfig.format, dstConfig.baseArrayLayer, dstConfig.baseMipmapLevel);
644 dawn::RenderPipeline renderPipeline = CreateRenderPipelineForTest();
645
646 std::vector<RGBA8> expectedData = GetExpectedData(dstConfig.format, kDstVirtualSize);
647 VerifyCompressedTexturePixelValues(renderPipeline, bindGroup, kDstVirtualSize,
648 dstConfig.copyOrigin3D, kDstVirtualSize, expectedData);
649 }
650 }
651
652 // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture
653 // extent exactly fit the RowPitch.
TEST_P(CompressedTextureBCFormatTest,BufferOffsetAndExtentFitRowPitch)654 TEST_P(CompressedTextureBCFormatTest, BufferOffsetAndExtentFitRowPitch) {
655 CopyConfig config;
656 config.textureWidthLevel0 = 8;
657 config.textureHeightLevel0 = 8;
658 config.rowPitchAlignment = kTextureRowPitchAlignment;
659 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
660
661 const uint32_t blockCountPerRow = config.textureWidthLevel0 / kBCBlockWidthInTexels;
662
663 for (dawn::TextureFormat format : kBCFormats) {
664 config.format = format;
665
666 const uint32_t blockSizeInBytes = CompressedFormatBlockSizeInBytes(format);
667 const uint32_t blockCountPerRowPitch = config.rowPitchAlignment / blockSizeInBytes;
668
669 config.bufferOffset = (blockCountPerRowPitch - blockCountPerRow) * blockSizeInBytes;
670
671 TestCopyRegionIntoBCFormatTextures(config);
672 }
673 }
674
675 // Test the special case of the B2T copies on the D3D12 backend that the buffer offset exceeds the
676 // slice pitch (slicePitch = rowPitch * (imageHeightInTexels / blockHeightInTexels)). On D3D12
677 // backend the texelOffset.y will be greater than 0 after calcuting the texelOffset in the function
678 // ComputeTexelOffsets().
TEST_P(CompressedTextureBCFormatTest,BufferOffsetExceedsSlicePitch)679 TEST_P(CompressedTextureBCFormatTest, BufferOffsetExceedsSlicePitch) {
680 CopyConfig config;
681 config.textureWidthLevel0 = 8;
682 config.textureHeightLevel0 = 8;
683 config.rowPitchAlignment = kTextureRowPitchAlignment;
684 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
685
686 const uint32_t blockCountPerRow = config.textureWidthLevel0 / kBCBlockWidthInTexels;
687 const uint32_t slicePitchInBytes =
688 config.rowPitchAlignment * (config.textureHeightLevel0 / kBCBlockHeightInTexels);
689
690 for (dawn::TextureFormat format : kBCFormats) {
691 config.format = format;
692
693 const uint32_t blockSizeInBytes = CompressedFormatBlockSizeInBytes(format);
694 const uint32_t blockCountPerRowPitch = config.rowPitchAlignment / blockSizeInBytes;
695
696 config.bufferOffset = (blockCountPerRowPitch - blockCountPerRow) * blockSizeInBytes +
697 config.rowPitchAlignment + slicePitchInBytes;
698
699 TestCopyRegionIntoBCFormatTextures(config);
700 }
701 }
702
703 // Test the special case of the B2T copies on the D3D12 backend that the buffer offset and texture
704 // extent exceed the RowPitch. On D3D12 backend two copies are required for this case.
TEST_P(CompressedTextureBCFormatTest,CopyWithBufferOffsetAndExtentExceedRowPitch)705 TEST_P(CompressedTextureBCFormatTest, CopyWithBufferOffsetAndExtentExceedRowPitch) {
706 CopyConfig config;
707 config.textureWidthLevel0 = 8;
708 config.textureHeightLevel0 = 8;
709 config.rowPitchAlignment = kTextureRowPitchAlignment;
710 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
711
712 const uint32_t blockCountPerRow = config.textureWidthLevel0 / kBCBlockWidthInTexels;
713
714 constexpr uint32_t kExceedRowBlockCount = 1;
715
716 for (dawn::TextureFormat format : kBCFormats) {
717 config.format = format;
718
719 const uint32_t blockSizeInBytes = CompressedFormatBlockSizeInBytes(format);
720 const uint32_t blockCountPerRowPitch = config.rowPitchAlignment / blockSizeInBytes;
721 config.bufferOffset =
722 (blockCountPerRowPitch - blockCountPerRow + kExceedRowBlockCount) * blockSizeInBytes;
723
724 TestCopyRegionIntoBCFormatTextures(config);
725 }
726 }
727
728 // Test the special case of the B2T copies on the D3D12 backend that the slicePitch is equal to the
729 // rowPitch. On D3D12 backend the texelOffset.z will be greater than 0 after calcuting the
730 // texelOffset in the function ComputeTexelOffsets().
TEST_P(CompressedTextureBCFormatTest,RowPitchEqualToSlicePitch)731 TEST_P(CompressedTextureBCFormatTest, RowPitchEqualToSlicePitch) {
732 CopyConfig config;
733 config.textureWidthLevel0 = 8;
734 config.textureHeightLevel0 = kBCBlockHeightInTexels;
735 config.rowPitchAlignment = kTextureRowPitchAlignment;
736 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
737
738 const uint32_t blockCountPerRow = config.textureWidthLevel0 / kBCBlockWidthInTexels;
739 const uint32_t slicePitchInBytes = config.rowPitchAlignment;
740
741 for (dawn::TextureFormat format : kBCFormats) {
742 config.format = format;
743
744 const uint32_t blockSizeInBytes = CompressedFormatBlockSizeInBytes(format);
745 const uint32_t blockCountPerRowPitch = config.rowPitchAlignment / blockSizeInBytes;
746
747 config.bufferOffset =
748 (blockCountPerRowPitch - blockCountPerRow) * blockSizeInBytes + slicePitchInBytes;
749
750 TestCopyRegionIntoBCFormatTextures(config);
751 }
752 }
753
754 // Test the workaround in the B2T copies when (bufferSize - bufferOffset < bytesPerImage *
755 // copyExtent.depth) on Metal backends. As copyExtent.depth can only be 1 for BC formats, on Metal
756 // backend we will use two copies to implement such copy.
TEST_P(CompressedTextureBCFormatTest,LargeImageHeight)757 TEST_P(CompressedTextureBCFormatTest, LargeImageHeight) {
758 CopyConfig config;
759 config.textureWidthLevel0 = 8;
760 config.textureHeightLevel0 = 8;
761 config.copyExtent3D = {config.textureWidthLevel0, config.textureHeightLevel0, 1};
762
763 config.imageHeight = config.textureHeightLevel0 * 2;
764
765 for (dawn::TextureFormat format : kBCFormats) {
766 config.format = format;
767 TestCopyRegionIntoBCFormatTextures(config);
768 }
769 }
770
771 // Test the workaround in the B2T copies when (bufferSize - bufferOffset < bytesPerImage *
772 // copyExtent.depth) and copyExtent needs to be clamped.
TEST_P(CompressedTextureBCFormatTest,LargeImageHeightAndClampedCopyExtent)773 TEST_P(CompressedTextureBCFormatTest, LargeImageHeightAndClampedCopyExtent) {
774 CopyConfig config;
775 config.textureHeightLevel0 = 56;
776 config.textureWidthLevel0 = 56;
777 config.rowPitchAlignment = kTextureRowPitchAlignment;
778
779 constexpr uint32_t kMipmapLevelCount = 3;
780 config.mipmapLevelCount = kMipmapLevelCount;
781 config.baseMipmapLevel = kMipmapLevelCount - 1;
782
783 // The actual size of the texture at mipmap level == 2 is not a multiple of 4, paddings are
784 // required in the copies.
785 const uint32_t kActualWidthAtLevel = config.textureWidthLevel0 >> config.baseMipmapLevel;
786 const uint32_t kActualHeightAtLevel = config.textureHeightLevel0 >> config.baseMipmapLevel;
787 ASSERT(kActualWidthAtLevel % kBCBlockWidthInTexels != 0);
788 ASSERT(kActualHeightAtLevel % kBCBlockHeightInTexels != 0);
789
790 const uint32_t kCopyWidthAtLevel = (kActualWidthAtLevel + kBCBlockWidthInTexels - 1) /
791 kBCBlockWidthInTexels * kBCBlockWidthInTexels;
792 const uint32_t kCopyHeightAtLevel = (kActualHeightAtLevel + kBCBlockHeightInTexels - 1) /
793 kBCBlockHeightInTexels * kBCBlockHeightInTexels;
794
795 config.copyExtent3D = {kCopyWidthAtLevel, kCopyHeightAtLevel, 1};
796
797 config.imageHeight = kCopyHeightAtLevel * 2;
798
799 for (dawn::TextureFormat format : kBCFormats) {
800 config.format = format;
801 TestCopyRegionIntoBCFormatTextures(config);
802 }
803 }
804
805 // TODO(jiawei.shao@intel.com): support BC formats on OpenGL backend
806 DAWN_INSTANTIATE_TEST(CompressedTextureBCFormatTest, D3D12Backend, MetalBackend, VulkanBackend);
807