| // Copyright 2021 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include <algorithm> | |
| #include <string> | |
| #include <vector> | |
| #include "src/dawn/common/Enumerator.h" | |
| #include "src/dawn/common/ExternalTextureParams.h" | |
| #include "src/dawn/tests/DawnTest.h" | |
| #include "src/dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "src/dawn/utils/WGPUHelpers.h" | |
| namespace dawn { | |
| namespace { | |
| wgpu::Texture Create2DTexture(wgpu::Device device, | |
| uint32_t width, | |
| uint32_t height, | |
| wgpu::TextureFormat format, | |
| wgpu::TextureUsage usage) { | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.size.width = width; | |
| descriptor.size.height = height; | |
| descriptor.size.depthOrArrayLayers = 1; | |
| descriptor.sampleCount = 1; | |
| descriptor.format = format; | |
| descriptor.mipLevelCount = 1; | |
| descriptor.usage = usage; | |
| return device.CreateTexture(&descriptor); | |
| } | |
| struct YUVTestData { | |
| float y; | |
| float u; | |
| float v; | |
| std::array<float, 4> rgbaFloats; | |
| utils::RGBA8 rgba; | |
| }; | |
| static const YUVTestData kBlack = { | |
| 0.0, 0.5, 0.5, {0.0, 0.0, 0.0, 1.0}, utils::RGBA8::kBlack, | |
| }; | |
| static const YUVTestData kRed = { | |
| 0.2126, 0.4172, 1.0, {1.0, 0.0, 0.0, 1.0}, utils::RGBA8::kRed, | |
| }; | |
| static const YUVTestData kGreen = { | |
| 0.7152, 0.1402, 0.0175, {0.0, 1.0, 0.0, 1.0}, utils::RGBA8::kGreen, | |
| }; | |
| static const YUVTestData kBlue = { | |
| 0.0722, 1.0, 0.4937, {0.0, 0.0, 1.0, 1.0}, utils::RGBA8::kBlue, | |
| }; | |
| static const YUVTestData kColor1 = {0.6402, | |
| 0.3214, | |
| 0.6624, | |
| {246 / 255.0, 169 / 255.0, 89 / 255.0, 1}, | |
| {246, 169, 89, 255}}; | |
| template <typename Parent> | |
| class ExternalTextureTestsBase : public Parent { | |
| protected: | |
| void SetUp() override { | |
| Parent::SetUp(); | |
| vsModule = utils::CreateShaderModule(this->device, R"( | |
| @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| var positions = array( | |
| vec4f(-3, -1, 0, 1), | |
| vec4f( 3, -1, 0, 1), | |
| vec4f( 0, 2, 0, 1), | |
| ); | |
| return positions[VertexIndex]; | |
| })"); | |
| fsSampleExternalTextureModule = utils::CreateShaderModule(this->device, R"( | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var t : texture_external; | |
| @fragment fn main(@builtin(position) FragCoord : vec4f) | |
| -> @location(0) vec4f { | |
| return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2f(4.0, 4.0)); | |
| })"); | |
| // Set up the BT709 to sRGB color space conversion. | |
| wgpu::Status status = ComputeExternalTextureParams( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec709, | |
| .transfer = wgpu::ColorSpaceTransferDawn::SMPTE_170M, | |
| .yCbCrRange = wgpu::ColorSpaceYCbCrRangeDawn::Narrow, | |
| .yCbCrMatrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec709, | |
| }, | |
| wgpu::PredefinedColorSpace::SRGB, &bt709ColorSpaceParams); | |
| DAWN_ASSERT(status == wgpu::Status::Success); | |
| } | |
| wgpu::ExternalTextureDescriptor InitExternalTextureDescriptor(wgpu::Texture plane0, | |
| wgpu::Texture plane1 = nullptr) { | |
| wgpu::ExternalTextureDescriptor desc; | |
| desc.plane0 = plane0.CreateView(); | |
| desc.plane1 = plane1 != nullptr ? plane1.CreateView() : nullptr; | |
| const auto& params = plane1 == nullptr ? noopRGBColorSpaceParams : bt709ColorSpaceParams; | |
| desc.yuvToRgbConversionMatrix = params.yuvToRgbConversionMatrix.data(); | |
| desc.gamutConversionMatrix = params.gamutConversionMatrix.data(); | |
| desc.srcTransferFunctionParameters = params.srcTransferFunction.data(); | |
| desc.dstTransferFunctionParameters = params.dstTransferFunction.data(); | |
| desc.cropOrigin = {0, 0}; | |
| desc.cropSize = {plane0.GetWidth(), plane0.GetHeight()}; | |
| desc.apparentSize = {plane0.GetWidth(), plane0.GetHeight()}; | |
| return desc; | |
| } | |
| // Helper function to render a quad of data in a texture with a different color for each | |
| // corner as well as an optional color for the outside of the quad. (the quad can be scaled | |
| // to make it fill only parts of the texture). | |
| struct QuadData { | |
| std::array<float, 4> upperLeft; | |
| std::array<float, 4> upperRight; | |
| std::array<float, 4> lowerLeft; | |
| std::array<float, 4> lowerRight; | |
| std::array<float, 4> outsideData = {}; | |
| float scale = 1.0; | |
| std::array<float, 3> padding = {}; | |
| }; | |
| void RenderQuad(const wgpu::Texture& texture, const QuadData& quad) { | |
| // Make the pipeline drawing a quad in the texture, taking the colors as a uniform buffer. | |
| wgpu::ShaderModule module = utils::CreateShaderModule(this->device, R"( | |
| struct VsOut { | |
| @builtin(position) pos : vec4f, | |
| @interpolate(perspective) @location(0) ndc : vec4f, | |
| } | |
| @vertex fn vs(@builtin(vertex_index) VertexIndex : u32) -> VsOut { | |
| var pos = array( | |
| vec4f(-3, -1, 0, 1), | |
| vec4f( 3, -1, 0, 1), | |
| vec4f( 0, 2, 0, 1), | |
| ); | |
| return VsOut(pos[VertexIndex], pos[VertexIndex]); | |
| } | |
| struct QuadData { | |
| upperLeft : vec4f, | |
| upperRight : vec4f, | |
| lowerLeft : vec4f, | |
| lowerRight : vec4f, | |
| outside : vec4f, | |
| scale : f32, | |
| } | |
| @group(0) @binding(0) var<uniform> quad : QuadData; | |
| @fragment fn fs(@interpolate(perspective) @location(0) ndc : vec4f) | |
| -> @location(0) vec4f { | |
| if abs(ndc.x) > quad.scale || abs(ndc.y) > quad.scale { | |
| return quad.outside; | |
| } | |
| if ndc.x <= 0 && ndc.y >= 0 { | |
| return quad.upperLeft; | |
| } else if ndc.x >= 0 && ndc.y >= 0 { | |
| return quad.upperRight; | |
| } else if ndc.x <= 0 && ndc.y <= 0 { | |
| return quad.lowerLeft; | |
| } else { | |
| return quad.lowerRight; | |
| } | |
| })"); | |
| utils::ComboRenderPipelineDescriptor pDesc; | |
| pDesc.vertex.module = module; | |
| pDesc.cFragment.module = module; | |
| pDesc.cTargets[0].format = texture.GetFormat(); | |
| wgpu::RenderPipeline quadPipeline = this->device.CreateRenderPipeline(&pDesc); | |
| // Make the storage buffer and the bind group containing it. | |
| wgpu::Buffer quadData = utils::CreateBufferFromData(this->device, &quad, sizeof(quad), | |
| wgpu::BufferUsage::Uniform); | |
| wgpu::BindGroup bg = | |
| utils::MakeBindGroup(this->device, quadPipeline.GetBindGroupLayout(0), {{0, quadData}}); | |
| // Do the render. | |
| wgpu::CommandEncoder encoder = this->device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor renderPass({texture.CreateView()}); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.SetPipeline(quadPipeline); | |
| pass.SetBindGroup(0, bg); | |
| pass.Draw(3); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| this->queue.Submit(1, &commands); | |
| } | |
| wgpu::Texture MakeTestTexture(wgpu::TextureFormat format, | |
| uint32_t width, | |
| uint32_t height, | |
| wgpu::Color color) { | |
| wgpu::TextureDescriptor tDesc = { | |
| .usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| .size = {width, height}, | |
| .format = format, | |
| }; | |
| wgpu::Texture texture = this->device.CreateTexture(&tDesc); | |
| // Use a render pass clear to set the value for the texture so that it works whatever the | |
| // format. | |
| wgpu::RenderPassColorAttachment attachment = { | |
| .view = texture.CreateView(), | |
| .loadOp = wgpu::LoadOp::Clear, | |
| .storeOp = wgpu::StoreOp::Store, | |
| .clearValue = color, | |
| }; | |
| wgpu::RenderPassDescriptor rpDesc = { | |
| .colorAttachmentCount = 1, | |
| .colorAttachments = &attachment, | |
| }; | |
| wgpu::CommandEncoder encoder = this->device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rpDesc); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| this->queue.Submit(1, &commands); | |
| return texture; | |
| } | |
| void CheckColorSpaceConversion(wgpu::ColorSpaceDawn srcColorSpace, | |
| wgpu::PredefinedColorSpace dstColorSpace, | |
| wgpu::Color srcColor, | |
| std::array<float, 3> expectedColor) { | |
| // Create the external texture from the color spaces and srcColor. | |
| wgpu::Texture srcTexture = | |
| MakeTestTexture(wgpu::TextureFormat::RGBA16Float, 1, 1, srcColor); | |
| ExternalTextureColorSpaceParams colorSpaceParams; | |
| wgpu::Status status = | |
| ComputeExternalTextureParams(srcColorSpace, dstColorSpace, &colorSpaceParams); | |
| DAWN_ASSERT(status == wgpu::Status::Success); | |
| wgpu::ExternalTextureDescriptor etDesc; | |
| etDesc.plane0 = srcTexture.CreateView(); | |
| etDesc.yuvToRgbConversionMatrix = colorSpaceParams.yuvToRgbConversionMatrix.data(); | |
| etDesc.gamutConversionMatrix = colorSpaceParams.gamutConversionMatrix.data(); | |
| etDesc.srcTransferFunctionParameters = colorSpaceParams.srcTransferFunction.data(); | |
| etDesc.dstTransferFunctionParameters = colorSpaceParams.dstTransferFunction.data(); | |
| etDesc.cropOrigin = {0, 0}; | |
| etDesc.cropSize = {srcTexture.GetWidth(), srcTexture.GetHeight()}; | |
| etDesc.apparentSize = {srcTexture.GetWidth(), srcTexture.GetHeight()}; | |
| wgpu::ExternalTexture externalTexture = this->device.CreateExternalTexture(&etDesc); | |
| // Create the test pipeline that will just load the single texel of the ExternalTexture. | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(this->device, R"( | |
| @group(0) @binding(0) var t : texture_external; | |
| @group(0) @binding(1) var<storage, read_write> results : vec4f; | |
| @compute @workgroup_size(1) fn main() { | |
| results = textureLoad(t, vec2(0)); | |
| } | |
| )"); | |
| wgpu::ComputePipeline pipeline = this->device.CreateComputePipeline(&csDesc); | |
| // Run the pipeline and store results in a buffer. | |
| wgpu::BufferDescriptor resultDesc = { | |
| .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, | |
| .size = sizeof(uint32_t) + 256, | |
| }; | |
| wgpu::Buffer resultBuffer = this->device.CreateBuffer(&resultDesc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(this->device, pipeline.GetBindGroupLayout(0), | |
| {{0, externalTexture}, {1, resultBuffer}}); | |
| wgpu::CommandEncoder encoder = this->device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| this->queue.Submit(1, &commands); | |
| // Check that we got the expected values. Make the tolerance 1% of the maximum expected | |
| // value as some of them can get somewhat large. (so a hardcoded .01 is too small). | |
| float tolerance = 0; | |
| for (auto [_, value] : Enumerate(expectedColor)) { | |
| tolerance = std::max(tolerance, std::abs(value) * 0.01f); | |
| } | |
| EXPECT_BUFFER_FLOAT_RANGE_TOLERANCE_EQ(expectedColor.data(), resultBuffer, 0, 3, tolerance); | |
| EXPECT_BUFFER_FLOAT_EQ(1.0, resultBuffer, 12); | |
| } | |
| static constexpr uint32_t kWidth = 4; | |
| static constexpr uint32_t kHeight = 4; | |
| static constexpr wgpu::TextureFormat kFormat = wgpu::TextureFormat::RGBA8Unorm; | |
| static constexpr wgpu::TextureUsage kSampledUsage = wgpu::TextureUsage::TextureBinding; | |
| wgpu::ShaderModule vsModule; | |
| wgpu::ShaderModule fsSampleExternalTextureModule; | |
| ExternalTextureColorSpaceParams noopRGBColorSpaceParams = GetNoopColorSpaceParams(); | |
| ExternalTextureColorSpaceParams bt709ColorSpaceParams; | |
| }; | |
| class ExternalTextureTests : public ExternalTextureTestsBase<DawnTest> { | |
| protected: | |
| std::vector<wgpu::FeatureName> GetRequiredFeatures() override { | |
| std::vector<wgpu::FeatureName> requiredFeatures = {}; | |
| if (SupportsFeatures({wgpu::FeatureName::Unorm16TextureFormats})) { | |
| mIsUnorm16TextureFormatsSupported = true; | |
| requiredFeatures.push_back(wgpu::FeatureName::Unorm16TextureFormats); | |
| } | |
| return requiredFeatures; | |
| } | |
| bool IsUnorm16TextureFormatsSupported() { return mIsUnorm16TextureFormatsSupported; } | |
| bool mIsUnorm16TextureFormatsSupported = false; | |
| }; | |
| TEST_P(ExternalTextureTests, CreateExternalTextureSuccess) { | |
| wgpu::Texture texture = Create2DTexture(device, kWidth, kHeight, kFormat, kSampledUsage); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(texture); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| ASSERT_NE(externalTexture.Get(), nullptr); | |
| } | |
| TEST_P(ExternalTextureTests, SampleExternalTexture) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sampledTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Initialize texture with green to ensure it is sampled from later. | |
| { | |
| utils::ComboRenderPassDescriptor renderPass({sampledTexture.CreateView()}, nullptr); | |
| renderPass.cColorAttachments[0].clearValue = {0.0f, 1.0f, 0.0f, 1.0f}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sampledTexture); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into the | |
| // upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderTexture, 0, 0); | |
| } | |
| // Tests that a texture view can be used for an externalTexture binding. | |
| TEST_P(ExternalTextureTests, SampleTextureView) { | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sampledTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Initialize texture with green to ensure it is sampled from later. | |
| { | |
| utils::ComboRenderPassDescriptor renderPass({sampledTexture.CreateView()}, nullptr); | |
| renderPass.cColorAttachments[0].clearValue = {0.0f, 1.0f, 0.0f, 1.0f}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| wgpu::TextureView textureView = sampledTexture.CreateView(); | |
| // Create a sampler and bind group that uses a TextureView for the external_texture in WGSL | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, textureView}}); | |
| // Run the shader, which should sample from the texture view and draw a triangle into the | |
| // upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderTexture, 0, 0); | |
| } | |
| // Tests that textureDimensions WGSL built-in function works when a texture view is used for an | |
| // externalTexture binding. | |
| TEST_P(ExternalTextureTests, TextureDimensionsWithTextureView) { | |
| DAWN_SUPPRESS_TEST_IF(IsWARP()); // Flaky on WARP | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && (IsVulkan() || IsOpenGLES())); | |
| wgpu::TextureDescriptor descriptor; | |
| descriptor.size = {kWidth, kHeight, 1}; | |
| descriptor.mipLevelCount = 2; | |
| descriptor.dimension = wgpu::TextureDimension::e2D; | |
| descriptor.format = wgpu::TextureFormat::RGBA8Unorm; | |
| descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment; | |
| wgpu::Texture texture = device.CreateTexture(&descriptor); | |
| for (const auto& mipLevel : {0, 1}) { | |
| wgpu::TextureViewDescriptor textureViewDesc; | |
| textureViewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| textureViewDesc.mipLevelCount = 1; | |
| textureViewDesc.baseMipLevel = mipLevel; | |
| wgpu::TextureView textureView = texture.CreateView(&textureViewDesc); | |
| // Create buffer that will store texture dimensions | |
| std::vector<uint32_t> data(2); | |
| std::vector<uint32_t> expected(2); | |
| if (mipLevel == 0) { | |
| expected = {kWidth, kHeight}; | |
| } else { | |
| expected = {kWidth / 2, kHeight / 2}; | |
| } | |
| uint64_t bufferSize = static_cast<uint64_t>(data.size() * sizeof(uint32_t)); | |
| wgpu::Buffer buffer = | |
| utils::CreateBufferFromData(device, data.data(), bufferSize, | |
| wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var texture : texture_external; | |
| @group(0) @binding(1) var<storage, read_write> buffer: vec2u; | |
| @compute @workgroup_size(1) fn main() { | |
| buffer = textureDimensions(texture); | |
| })"); | |
| // Pipeline Creation | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| // Set up bind group that uses a TextureView for the external_texture in WGSL | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup( | |
| device, pipeline.GetBindGroupLayout(0), {{0, textureView}, {1, buffer, 0, bufferSize}}); | |
| // Issue dispatch | |
| wgpu::CommandBuffer commands; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_U32_EQ(expected[0], buffer, 0); | |
| EXPECT_BUFFER_U32_EQ(expected[1], buffer, 4); | |
| } | |
| } | |
| // Tests that textureLoad WGSL built-in function works when a texture view is used for an | |
| // externalTexture binding. | |
| TEST_P(ExternalTextureTests, TextureLoadWithTextureView) { | |
| wgpu::Texture texture = | |
| MakeTestTexture(kFormat, kWidth, kHeight, {1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 1.0}); | |
| // Create buffer that will store textureLoad result | |
| uint32_t kZero = 0; | |
| wgpu::Buffer buffer = utils::CreateBufferFromData( | |
| device, &kZero, sizeof(uint32_t), wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc); | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var texture : texture_external; | |
| @group(0) @binding(1) var<storage, read_write> buffer: u32; | |
| @compute @workgroup_size(1) fn main() { | |
| buffer = pack4x8unorm(textureLoad(texture, vec2(0, 0))); | |
| })"); | |
| // Pipeline Creation | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| // Set up bind group that uses a TextureView for the external_texture in WGSL | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView()}, {1, buffer}}); | |
| // Issue dispatch | |
| wgpu::CommandBuffer commands; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_RGBA8_EQ(utils::RGBA8(1, 2, 3, 255), buffer, 0); | |
| } | |
| // https://crbug.com/1515439 | |
| TEST_P(ExternalTextureTests, SampleExternalTextureDifferingGroup) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sampledTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Initialize texture with green to ensure it is sampled from later. | |
| { | |
| utils::ComboRenderPassDescriptor renderPass({sampledTexture.CreateView()}, nullptr); | |
| renderPass.cColorAttachments[0].clearValue = {0.0f, 1.0f, 0.0f, 1.0f}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var s : sampler; | |
| @group(1) @binding(0) var t : texture_external; | |
| @fragment fn main(@builtin(position) FragCoord : vec4f) | |
| -> @location(0) vec4f { | |
| return textureSampleBaseClampToEdge(t, s, FragCoord.xy / vec2f(4.0, 4.0)); | |
| })"); | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sampledTexture); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup samplerBindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, sampler}}); | |
| wgpu::BindGroup texBindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(1), {{0, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into the | |
| // upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, samplerBindGroup); | |
| pass.SetBindGroup(1, texBindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderTexture, 0, 0); | |
| } | |
| TEST_P(ExternalTextureTests, SampleMultiplanarExternalTexture) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(https://crbug.com/468988322): Fails because of precision issues on Mac AMD. | |
| DAWN_SUPPRESS_TEST_IF(IsMetal() && IsAMD()); | |
| // TODO(crbug.com/500766620): Fails on Windows 11/AMD RX 5500 XT. | |
| DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sampledTexturePlane0 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture sampledTexturePlane1 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RG8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Create a texture view for the external texture | |
| wgpu::TextureView externalViewPlane0 = sampledTexturePlane0.CreateView(); | |
| wgpu::TextureView externalViewPlane1 = sampledTexturePlane1.CreateView(); | |
| for (const auto& expectation : {kBlack, kRed, kGreen, kBlue, kColor1}) { | |
| // Initialize the texture planes with YUV data | |
| { | |
| utils::ComboRenderPassDescriptor renderPass({externalViewPlane0, externalViewPlane1}, | |
| nullptr); | |
| renderPass.cColorAttachments[0].clearValue = {expectation.y, 0.0f, 0.0f, 0.0f}; | |
| renderPass.cColorAttachments[1].clearValue = {expectation.u, expectation.v, 0.0f, 0.0f}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| // descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl); | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = | |
| InitExternalTextureDescriptor(sampledTexturePlane0, sampledTexturePlane1); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(expectation.rgba, renderTexture, 0, 0); | |
| } | |
| } | |
| TEST_P(ExternalTextureTests, SampleMultiplanarExternalTextureNorm16) { | |
| DAWN_TEST_UNSUPPORTED_IF(!IsUnorm16TextureFormatsSupported()); | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sampledTexturePlane0 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::R16Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture sampledTexturePlane1 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RG16Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Create a texture view for the external texture | |
| wgpu::TextureView externalViewPlane0 = sampledTexturePlane0.CreateView(); | |
| wgpu::TextureView externalViewPlane1 = sampledTexturePlane1.CreateView(); | |
| for (const auto& expectation : {kBlack, kRed, kGreen, kBlue, kColor1}) { | |
| // Initialize the texture planes with YUV data | |
| { | |
| utils::ComboRenderPassDescriptor renderPass({externalViewPlane0, externalViewPlane1}, | |
| nullptr); | |
| renderPass.cColorAttachments[0].clearValue = {expectation.y, 0.0f, 0.0f, 0.0f}; | |
| renderPass.cColorAttachments[1].clearValue = {expectation.u, expectation.v, 0.0f, 0.0f}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = | |
| InitExternalTextureDescriptor(sampledTexturePlane0, sampledTexturePlane1); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(expectation.rgba, renderTexture, 0, 0); | |
| } | |
| } | |
| // Test draws a green square in the upper left quadrant, a black square in the upper right, a red | |
| // square in the lower left and a blue square in the lower right. The image is then sampled as an | |
| // external texture and rotated 0, 90, 180, and 270 degrees with and without the y-axis flipped. | |
| TEST_P(ExternalTextureTests, RotateAndOrFlipSampleSinglePlane) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sourceTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexture, | |
| {kGreen.rgbaFloats, kBlack.rgbaFloats, kRed.rgbaFloats, kBlue.rgbaFloats}); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Control case to verify mirrored and rotation defaults | |
| { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sourceTexture); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlack, renderTexture, 3, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderTexture, 0, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, renderTexture, 3, 3); | |
| } | |
| struct RotationExpectation { | |
| wgpu::ExternalTextureRotation rotation; | |
| bool mirrored; | |
| utils::RGBA8 upperLeftColor; | |
| utils::RGBA8 upperRightColor; | |
| utils::RGBA8 lowerLeftColor; | |
| utils::RGBA8 lowerRightColor; | |
| }; | |
| std::array<RotationExpectation, 8> expectations = { | |
| {{wgpu::ExternalTextureRotation::Rotate0Degrees, false, utils::RGBA8::kGreen, | |
| utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kBlue}, | |
| {wgpu::ExternalTextureRotation::Rotate90Degrees, false, utils::RGBA8::kRed, | |
| utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlack}, | |
| {wgpu::ExternalTextureRotation::Rotate180Degrees, false, utils::RGBA8::kBlue, | |
| utils::RGBA8::kRed, utils::RGBA8::kBlack, utils::RGBA8::kGreen}, | |
| {wgpu::ExternalTextureRotation::Rotate270Degrees, false, utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlue, utils::RGBA8::kGreen, utils::RGBA8::kRed}, | |
| {wgpu::ExternalTextureRotation::Rotate0Degrees, true, utils::RGBA8::kBlack, | |
| utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kRed}, | |
| {wgpu::ExternalTextureRotation::Rotate90Degrees, true, utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed, utils::RGBA8::kBlack, utils::RGBA8::kBlue}, | |
| {wgpu::ExternalTextureRotation::Rotate180Degrees, true, utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue, utils::RGBA8::kGreen, utils::RGBA8::kBlack}, | |
| {wgpu::ExternalTextureRotation::Rotate270Degrees, true, utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kGreen}}}; | |
| for (const RotationExpectation& exp : expectations) { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sourceTexture); | |
| externalDesc.rotation = exp.rotation; | |
| externalDesc.mirrored = exp.mirrored; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 1, 1); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 3, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 2, 1); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 0, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 1, 2); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 3, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 2, 2); | |
| } | |
| } | |
| // Test for a bug found during review where the cropSize was not correctly rotated during the | |
| // initialization of the ExternalTexture, which could lead to incorrect textureLoad operations when | |
| // rotating 90 and 270 degrees. | |
| TEST_P(ExternalTextureTests, RotateAndOrFlipTextureLoadSinglePlaneNotSquare) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sourceTexture = | |
| Create2DTexture(device, 2, 16, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexture, | |
| {kGreen.rgbaFloats, kBlack.rgbaFloats, kRed.rgbaFloats, kBlue.rgbaFloats}); | |
| wgpu::Texture renderTexture = Create2DTexture( | |
| device, 2, 2, kFormat, wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture dimensionTexture = | |
| Create2DTexture(device, 2, 2, wgpu::TextureFormat::RG32Uint, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| struct RotationExpectation { | |
| wgpu::ExternalTextureRotation rotation; | |
| bool mirrored; | |
| utils::RGBA8 upperLeftColor; | |
| utils::RGBA8 upperRightColor; | |
| utils::RGBA8 lowerLeftColor; | |
| utils::RGBA8 lowerRightColor; | |
| }; | |
| std::array<RotationExpectation, 8> expectations = { | |
| {{wgpu::ExternalTextureRotation::Rotate0Degrees, false, utils::RGBA8::kGreen, | |
| utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kBlue}, | |
| {wgpu::ExternalTextureRotation::Rotate90Degrees, false, utils::RGBA8::kRed, | |
| utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlack}, | |
| {wgpu::ExternalTextureRotation::Rotate180Degrees, false, utils::RGBA8::kBlue, | |
| utils::RGBA8::kRed, utils::RGBA8::kBlack, utils::RGBA8::kGreen}, | |
| {wgpu::ExternalTextureRotation::Rotate270Degrees, false, utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlue, utils::RGBA8::kGreen, utils::RGBA8::kRed}, | |
| {wgpu::ExternalTextureRotation::Rotate0Degrees, true, utils::RGBA8::kBlack, | |
| utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kRed}, | |
| {wgpu::ExternalTextureRotation::Rotate90Degrees, true, utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed, utils::RGBA8::kBlack, utils::RGBA8::kBlue}, | |
| {wgpu::ExternalTextureRotation::Rotate180Degrees, true, utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue, utils::RGBA8::kGreen, utils::RGBA8::kBlack}, | |
| {wgpu::ExternalTextureRotation::Rotate270Degrees, true, utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kGreen}}}; | |
| wgpu::ShaderModule loadModule = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var t : texture_external; | |
| struct FragOut { | |
| @location(0) color: vec4f, | |
| @location(1) dimension: vec2u, | |
| }; | |
| @fragment fn main(@builtin(position) FragCoord : vec4f) | |
| -> FragOut { | |
| let dimension = textureDimensions(t); | |
| let coords = textureDimensions(t) / 2 + vec2u(FragCoord.xy) - vec2(1, 1); | |
| return FragOut(textureLoad(t, coords), dimension); | |
| })"); | |
| wgpu::BufferDescriptor dimensionBufferDesc; | |
| dimensionBufferDesc.size = 256; | |
| dimensionBufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc; | |
| wgpu::Buffer dimensionBuffer = device.CreateBuffer(&dimensionBufferDesc); | |
| for (const RotationExpectation& exp : expectations) { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = loadModule; | |
| descriptor.cFragment.targetCount = 2; | |
| descriptor.cTargets[0].format = kFormat; | |
| descriptor.cTargets[1].format = wgpu::TextureFormat::RG32Uint; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture and make the bindgroup. | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sourceTexture); | |
| externalDesc.rotation = exp.rotation; | |
| externalDesc.mirrored = exp.mirrored; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| utils::ComboRenderPassDescriptor renderPass( | |
| {renderTexture.CreateView(), dimensionTexture.CreateView()}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| { | |
| wgpu::TexelCopyTextureInfo texelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(dimensionTexture, 0, {0, 0, 0}); | |
| wgpu::TexelCopyBufferInfo texelCopyBufferInfo = | |
| utils::CreateTexelCopyBufferInfo(dimensionBuffer, 0, 256, 1); | |
| wgpu::Extent3D size = {1, 1, 1}; | |
| encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &size); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 1, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 0, 1); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 1, 1); | |
| if (exp.rotation == wgpu::ExternalTextureRotation::Rotate90Degrees || | |
| exp.rotation == wgpu::ExternalTextureRotation::Rotate270Degrees) { | |
| EXPECT_BUFFER_U32_EQ(sourceTexture.GetHeight(), dimensionBuffer, 0); | |
| EXPECT_BUFFER_U32_EQ(sourceTexture.GetWidth(), dimensionBuffer, 4); | |
| } else { | |
| EXPECT_BUFFER_U32_EQ(sourceTexture.GetWidth(), dimensionBuffer, 0); | |
| EXPECT_BUFFER_U32_EQ(sourceTexture.GetHeight(), dimensionBuffer, 4); | |
| } | |
| } | |
| } | |
| // Test draws a green square in the upper left quadrant, a black square in the upper right, a red | |
| // square in the lower left and a blue square in the lower right. The image is then sampled as an | |
| // external texture and rotated 0, 90, 180, and 270 degrees with and without the y-axis flipped. | |
| TEST_P(ExternalTextureTests, RotateAndOrFlipSampleMultiplanar) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sourceTexturePlane0 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture sourceTexturePlane1 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RG8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexturePlane0, {{kGreen.y}, {kBlack.y}, {kRed.y}, {kBlue.y}}); | |
| RenderQuad(sourceTexturePlane1, | |
| {{kGreen.u, kGreen.v}, {kBlack.u, kBlack.v}, {kRed.u, kRed.v}, {kBlue.u, kBlue.v}}); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // Control case to verify mirrored and rotation defaults | |
| { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Create an ExternalTextureDescriptor from the texture view | |
| wgpu::ExternalTextureDescriptor externalDesc = | |
| InitExternalTextureDescriptor(sourceTexturePlane0, sourceTexturePlane1); | |
| // Import the external texture | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlack, renderTexture, 3, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kRed, renderTexture, 0, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlue, renderTexture, 3, 3); | |
| } | |
| struct RotationExpectation { | |
| wgpu::ExternalTextureRotation rotation; | |
| bool mirrored; | |
| utils::RGBA8 upperLeftColor; | |
| utils::RGBA8 upperRightColor; | |
| utils::RGBA8 lowerLeftColor; | |
| utils::RGBA8 lowerRightColor; | |
| }; | |
| std::array<RotationExpectation, 8> expectations = { | |
| {{wgpu::ExternalTextureRotation::Rotate0Degrees, false, utils::RGBA8::kGreen, | |
| utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kBlue}, | |
| {wgpu::ExternalTextureRotation::Rotate90Degrees, false, utils::RGBA8::kRed, | |
| utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kBlack}, | |
| {wgpu::ExternalTextureRotation::Rotate180Degrees, false, utils::RGBA8::kBlue, | |
| utils::RGBA8::kRed, utils::RGBA8::kBlack, utils::RGBA8::kGreen}, | |
| {wgpu::ExternalTextureRotation::Rotate270Degrees, false, utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlue, utils::RGBA8::kGreen, utils::RGBA8::kRed}, | |
| {wgpu::ExternalTextureRotation::Rotate0Degrees, true, utils::RGBA8::kBlack, | |
| utils::RGBA8::kGreen, utils::RGBA8::kBlue, utils::RGBA8::kRed}, | |
| {wgpu::ExternalTextureRotation::Rotate90Degrees, true, utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed, utils::RGBA8::kBlack, utils::RGBA8::kBlue}, | |
| {wgpu::ExternalTextureRotation::Rotate180Degrees, true, utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue, utils::RGBA8::kGreen, utils::RGBA8::kBlack}, | |
| {wgpu::ExternalTextureRotation::Rotate270Degrees, true, utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlack, utils::RGBA8::kRed, utils::RGBA8::kGreen}}}; | |
| for (const RotationExpectation& exp : expectations) { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = | |
| InitExternalTextureDescriptor(sourceTexturePlane0, sourceTexturePlane1); | |
| externalDesc.rotation = exp.rotation; | |
| externalDesc.mirrored = exp.mirrored; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 1, 1); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 3, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 2, 1); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 0, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 1, 2); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 3, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 2, 2); | |
| } | |
| } | |
| // This test draws a 2x2 multi-colored square surrounded by a 1px black border. We test the external | |
| // texture crop functionality by cropping to specific ranges inside the texture. | |
| TEST_P(ExternalTextureTests, CropSinglePlane) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sourceTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexture, {kGreen.rgbaFloats, kColor1.rgbaFloats, kRed.rgbaFloats, | |
| kBlue.rgbaFloats, kBlack.rgbaFloats, 0.5}); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| struct CropExpectation { | |
| wgpu::Origin2D cropOrigin; | |
| wgpu::Extent2D cropSize; | |
| wgpu::ExternalTextureRotation rotation; | |
| utils::RGBA8 upperLeftColor; | |
| utils::RGBA8 upperRightColor; | |
| utils::RGBA8 lowerLeftColor; | |
| utils::RGBA8 lowerRightColor; | |
| }; | |
| std::array<CropExpectation, 9> expectations = {{ | |
| {{0, 0}, | |
| {kWidth, kHeight}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlack}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kGreen}, | |
| {{kWidth / 2, kHeight / 4}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| kColor1.rgba, | |
| kColor1.rgba, | |
| kColor1.rgba, | |
| kColor1.rgba}, | |
| {{kWidth / 4, kHeight / 2}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kRed}, | |
| {{kWidth / 2, kHeight / 2}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlue}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kGreen, | |
| kColor1.rgba, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate90Degrees, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kBlue, | |
| kColor1.rgba}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate180Degrees, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kRed, | |
| kColor1.rgba, | |
| utils::RGBA8::kGreen}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate270Degrees, | |
| kColor1.rgba, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed}, | |
| }}; | |
| for (const CropExpectation& exp : expectations) { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sourceTexture); | |
| externalDesc.rotation = exp.rotation; | |
| externalDesc.cropOrigin = exp.cropOrigin; | |
| externalDesc.cropSize = exp.cropSize; | |
| externalDesc.apparentSize = exp.cropSize; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 3, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 0, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 3, 3); | |
| } | |
| } | |
| // Test that the apparentSize takes effect by using it to scale a texture and "blitting" it. | |
| TEST_P(ExternalTextureTests, ApparentSizeEffect) { | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // Create the test pipeline | |
| wgpu::ShaderModule blitAndOutputSize = utils::CreateShaderModule(this->device, R"( | |
| @group(0) @binding(0) var t : texture_external; | |
| struct FragOut { | |
| @location(0) color: vec4f, | |
| @location(1) dimension: vec2u, | |
| }; | |
| @fragment fn main(@builtin(position) FragCoord : vec4f) | |
| -> FragOut { | |
| let dimensions = textureDimensions(t); | |
| return FragOut(textureLoad(t, vec2u(FragCoord.xy)), dimensions); | |
| })"); | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = blitAndOutputSize; | |
| descriptor.cFragment.targetCount = 2; | |
| descriptor.cTargets[0].format = kFormat; | |
| descriptor.cTargets[1].format = wgpu::TextureFormat::RG32Uint; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // The source texture will contain a quad and have a larger apparent size. | |
| wgpu::Texture sourceTexture = | |
| Create2DTexture(device, 2, 2, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexture, | |
| {kGreen.rgbaFloats, kColor1.rgbaFloats, kRed.rgbaFloats, kBlue.rgbaFloats}); | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sourceTexture); | |
| externalDesc.apparentSize = {8, 16}; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // The texture that will receive the blit operation, uses apparentSize | |
| wgpu::Texture renderTexture = Create2DTexture( | |
| device, externalDesc.apparentSize.width, externalDesc.apparentSize.height, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture dimensionTexture = | |
| Create2DTexture(device, externalDesc.apparentSize.width, externalDesc.apparentSize.height, | |
| wgpu::TextureFormat::RG32Uint, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| // The buffer that will receive the result of `textureDimensions` | |
| wgpu::BufferDescriptor bufDesc; | |
| bufDesc.size = 256; | |
| bufDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc; | |
| wgpu::Buffer dimensionBuffer = device.CreateBuffer(&bufDesc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, externalTexture}}); | |
| // Do the blit | |
| utils::ComboRenderPassDescriptor renderPass( | |
| {renderTexture.CreateView(), dimensionTexture.CreateView()}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| { | |
| wgpu::TexelCopyTextureInfo texelCopyTextureInfo = | |
| utils::CreateTexelCopyTextureInfo(dimensionTexture, 0, {0, 0, 0}); | |
| wgpu::TexelCopyBufferInfo texelCopyBufferInfo = | |
| utils::CreateTexelCopyBufferInfo(dimensionBuffer, 0, 256, 1); | |
| wgpu::Extent3D size = {1, 1, 1}; | |
| encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &size); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| // Check that the apparentSize is correct and that the center of the quad has moved due to the | |
| // scaling with apparentSize. | |
| EXPECT_BUFFER_U32_EQ(externalDesc.apparentSize.width, dimensionBuffer, 0); | |
| EXPECT_BUFFER_U32_EQ(externalDesc.apparentSize.height, dimensionBuffer, 4); | |
| EXPECT_PIXEL_RGBA8_EQ(kGreen.rgba, renderTexture, 3, 7); | |
| EXPECT_PIXEL_RGBA8_EQ(kColor1.rgba, renderTexture, 4, 7); | |
| EXPECT_PIXEL_RGBA8_EQ(kRed.rgba, renderTexture, 3, 8); | |
| EXPECT_PIXEL_RGBA8_EQ(kBlue.rgba, renderTexture, 4, 8); | |
| } | |
| // This test draws a 2x2 multi-colored square surrounded by a 1px black border. We test the external | |
| // texture crop functionality by cropping to specific ranges inside the texture. | |
| TEST_P(ExternalTextureTests, CropMultiplanar) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(https://crbug.com/468988322): Fails because of precision issues on Mac AMD. | |
| DAWN_SUPPRESS_TEST_IF(IsMetal() && IsAMD()); | |
| // TODO(crbug.com/500766620): Fails on Windows 11/AMD RX 5500 XT. | |
| DAWN_SUPPRESS_TEST_IF(IsWindows11() && IsAMD()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sourceTexturePlane0 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture sourceTexturePlane1 = | |
| Create2DTexture(device, kWidth, kHeight, wgpu::TextureFormat::RG8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexturePlane0, | |
| {{kGreen.y}, {kColor1.y}, {kRed.y}, {kBlue.y}, {kBlack.y}, 0.5}); | |
| RenderQuad(sourceTexturePlane1, {{kGreen.u, kGreen.v}, | |
| {kColor1.u, kColor1.v}, | |
| {kRed.u, kRed.v}, | |
| {kBlue.u, kBlue.v}, | |
| {kBlack.u, kBlack.v}, | |
| 0.5}); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| struct CropExpectation { | |
| wgpu::Origin2D cropOrigin; | |
| wgpu::Extent2D cropSize; | |
| wgpu::ExternalTextureRotation rotation; | |
| utils::RGBA8 upperLeftColor; | |
| utils::RGBA8 upperRightColor; | |
| utils::RGBA8 lowerLeftColor; | |
| utils::RGBA8 lowerRightColor; | |
| }; | |
| std::array<CropExpectation, 9> expectations = { | |
| {{{0, 0}, | |
| {kWidth, kHeight}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kBlack}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kGreen}, | |
| {{kWidth / 2, kHeight / 4}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| kColor1.rgba, | |
| kColor1.rgba, | |
| kColor1.rgba, | |
| kColor1.rgba}, | |
| {{kWidth / 4, kHeight / 2}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kRed}, | |
| {{kWidth / 2, kHeight / 2}, | |
| {kWidth / 4, kHeight / 4}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kBlue}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| utils::RGBA8::kGreen, | |
| kColor1.rgba, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate90Degrees, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kBlue, | |
| kColor1.rgba}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate180Degrees, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kRed, | |
| kColor1.rgba, | |
| utils::RGBA8::kGreen}, | |
| {{kWidth / 4, kHeight / 4}, | |
| {kWidth / 2, kHeight / 2}, | |
| wgpu::ExternalTextureRotation::Rotate270Degrees, | |
| kColor1.rgba, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed}}}; | |
| for (const CropExpectation& exp : expectations) { | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = | |
| InitExternalTextureDescriptor(sourceTexturePlane0, sourceTexturePlane1); | |
| externalDesc.rotation = exp.rotation; | |
| externalDesc.cropOrigin = exp.cropOrigin; | |
| externalDesc.cropSize = exp.cropSize; | |
| externalDesc.apparentSize = exp.cropSize; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperLeftColor, renderTexture, 0, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.upperRightColor, renderTexture, 3, 0); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerLeftColor, renderTexture, 0, 3); | |
| EXPECT_PIXEL_RGBA8_EQ(exp.lowerRightColor, renderTexture, 3, 3); | |
| } | |
| } | |
| // Test that sampling an external texture with non-one alpha preserves the alpha channel. | |
| TEST_P(ExternalTextureTests, SampleExternalTextureAlpha) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| // TODO(crbug.com/522868202): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| wgpu::Texture sampledTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| wgpu::Texture renderTexture = | |
| Create2DTexture(device, kWidth, kHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| utils::RGBA8 kColor = {255, 255, 255, 128}; | |
| // Initialize texture with green to ensure it is sampled from later. | |
| { | |
| utils::ComboRenderPassDescriptor renderPass({sampledTexture.CreateView()}, nullptr); | |
| renderPass.cColorAttachments[0].clearValue = {kColor.r / 255.0f, kColor.g / 255.0f, | |
| kColor.b / 255.0f, kColor.a / 255.0f}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| } | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = vsModule; | |
| descriptor.cFragment.module = fsSampleExternalTextureModule; | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = InitExternalTextureDescriptor(sampledTexture); | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into the | |
| // upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_PIXEL_RGBA8_EQ(kColor, renderTexture, 0, 0); | |
| } | |
| // Test for crbug.com/dawn/2472 | |
| TEST_P(ExternalTextureTests, RemappingBugDawn2472) { | |
| auto wgslModule = utils::CreateShaderModule(device, R"( | |
| @vertex | |
| fn vertexMain() -> @builtin(position) vec4f { | |
| return vec4f(1); | |
| } | |
| @group(0) @binding(0) var myTexture: texture_external; | |
| @fragment | |
| fn fragmentMain() -> @location(0) vec4f { | |
| let result = textureLoad(myTexture, vec2u(1, 1)); | |
| return vec4f(1); | |
| })"); | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = wgslModule; | |
| descriptor.cFragment.module = wgslModule; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| ASSERT_NE(pipeline.Get(), nullptr); | |
| } | |
| // Regression test for issue 346174896. | |
| TEST_P(ExternalTextureTests, Regression346174896) { | |
| auto wgslModule = utils::CreateShaderModule(device, R"( | |
| @vertex fn vertexMain() -> @builtin(position) vec4f { | |
| return vec4f(1); | |
| } | |
| @group(0) @binding(1) var<uniform> dimension : vec2u; | |
| @group(0) @binding(0) var t : texture_external; | |
| @fragment fn main(@builtin(position) FragCoord : vec4f) -> @location(0) vec4f { | |
| _ = dimension; | |
| return textureLoad(t, vec2u(0, 0)); | |
| })"); | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = wgslModule; | |
| descriptor.cFragment.module = wgslModule; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| ASSERT_NE(pipeline.Get(), nullptr); | |
| } | |
| TEST_P(ExternalTextureTests, MultipleBindings) { | |
| auto wgslModule = utils::CreateShaderModule(device, R"( | |
| @vertex | |
| fn vertexMain() -> @builtin(position) vec4f { | |
| return vec4f(1); | |
| } | |
| @group(0) @binding(0) var<uniform> u : f32; | |
| @group(0) @binding(1) var s : sampler; | |
| @group(0) @binding(2) var et : texture_external; | |
| @fragment | |
| fn main() -> @location(0) vec4f { | |
| return textureSampleBaseClampToEdge(et, s, vec2f(u)); | |
| })"); | |
| // Pipeline Creation | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = wgslModule; | |
| descriptor.cFragment.module = wgslModule; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| ASSERT_NE(pipeline.Get(), nullptr); | |
| } | |
| // Tests using multiple textures in the same pipeline and checks that sampling them returns the | |
| // expected result. It rolls the RGBA and multiplanar external textures to check that the Vulkan | |
| // pipeline specialization works correctly (and doesn't reuse when it shouldn't). | |
| // | |
| // Case with all in the same bindgroup layout. | |
| TEST_P(ExternalTextureTests, SampleDifferentKindsSameBindGroup) { | |
| DAWN_SUPPRESS_TEST_IF(IsWARP()); | |
| // Create our three test external of different kinds as well as the expected data. | |
| std::vector<wgpu::ExternalTexture> externalTextures; | |
| std::vector<utils::RGBA8> colors; | |
| // The RGBA external textures. | |
| externalTextures.push_back(utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 4.0 / 255.0}))); | |
| colors.push_back(utils::RGBA8(1, 2, 3, 4)); | |
| externalTextures.push_back(utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {2.0 / 255.0, 4.0 / 255.0, 6.0 / 255.0, 8.0 / 255.0}))); | |
| colors.push_back(utils::RGBA8(2, 4, 6, 8)); | |
| // The multiplanar external texture. | |
| externalTextures.push_back(utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::R8Unorm, 2, 2, {10.0 / 255.0, 0, 0, 0}), | |
| MakeTestTexture(wgpu::TextureFormat::RG8Unorm, 1, 1, {20.0 / 255.0, 30.0 / 255.0, 0, 0}))); | |
| colors.push_back(utils::RGBA8(10, 20, 30, 255)); | |
| // Create the pipeline that copies from the three external texture to a buffer. | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var<storage, read_write> results : array<u32, 3>; | |
| @group(0) @binding(2) var t0 : texture_external; | |
| @group(0) @binding(3) var t1 : texture_external; | |
| @group(0) @binding(4) var t2 : texture_external; | |
| @compute @workgroup_size(1) fn main() { | |
| results[0] = pack4x8unorm(textureSampleBaseClampToEdge(t0, s, vec2(0))); | |
| results[1] = pack4x8unorm(textureSampleBaseClampToEdge(t1, s, vec2(0))); | |
| results[2] = pack4x8unorm(textureSampleBaseClampToEdge(t2, s, vec2(0))); | |
| } | |
| )"); | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BufferDescriptor resultDesc = { | |
| .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, | |
| .size = 3 * sizeof(uint32_t), | |
| }; | |
| wgpu::Buffer resultBuffer = device.CreateBuffer(&resultDesc); | |
| // Run the pipeline, rolling the external textures in the bindgroup such that a new | |
| // specialization of the shader should be created each time. | |
| for (size_t roll = 0; roll < externalTextures.size(); roll++) { | |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, device.CreateSampler()}, | |
| {1, resultBuffer}, | |
| {2, externalTextures[(0 + roll) % 3]}, | |
| {3, externalTextures[(1 + roll) % 3]}, | |
| {4, externalTextures[(2 + roll) % 3]}, | |
| }); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bg); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_RGBA8_EQ(colors[(0 + roll) % 3], resultBuffer, 0); | |
| EXPECT_BUFFER_RGBA8_EQ(colors[(1 + roll) % 3], resultBuffer, 4); | |
| EXPECT_BUFFER_RGBA8_EQ(colors[(2 + roll) % 3], resultBuffer, 8); | |
| } | |
| } | |
| // Case with all in different bind group layouts. | |
| TEST_P(ExternalTextureTests, SampleDifferentKindsDifferentBindGroups) { | |
| DAWN_SUPPRESS_TEST_IF(IsWARP()); | |
| // Create our three test external of different kinds as well as the expected data. | |
| std::vector<wgpu::ExternalTexture> externalTextures; | |
| std::vector<utils::RGBA8> colors; | |
| // The RGBA external textures. | |
| externalTextures.push_back(utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 4.0 / 255.0}))); | |
| colors.push_back(utils::RGBA8(1, 2, 3, 4)); | |
| externalTextures.push_back(utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {2.0 / 255.0, 4.0 / 255.0, 6.0 / 255.0, 8.0 / 255.0}))); | |
| colors.push_back(utils::RGBA8(2, 4, 6, 8)); | |
| // The multiplanar external texture. | |
| externalTextures.push_back(utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::R8Unorm, 2, 2, {10.0 / 255.0, 0, 0, 0}), | |
| MakeTestTexture(wgpu::TextureFormat::RG8Unorm, 1, 1, {20.0 / 255.0, 30.0 / 255.0, 0, 0}))); | |
| colors.push_back(utils::RGBA8(10, 20, 30, 255)); | |
| // Create the pipeline that copies from the three external texture to a buffer. | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var<storage, read_write> results : array<u32, 3>; | |
| @group(1) @binding(0) var t0 : texture_external; | |
| @group(2) @binding(0) var t1 : texture_external; | |
| @group(3) @binding(0) var t2 : texture_external; | |
| @compute @workgroup_size(1) fn main() { | |
| results[0] = pack4x8unorm(textureSampleBaseClampToEdge(t0, s, vec2(0))); | |
| results[1] = pack4x8unorm(textureSampleBaseClampToEdge(t1, s, vec2(0))); | |
| results[2] = pack4x8unorm(textureSampleBaseClampToEdge(t2, s, vec2(0))); | |
| } | |
| )"); | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| wgpu::BufferDescriptor resultDesc = { | |
| .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, | |
| .size = 3 * sizeof(uint32_t), | |
| }; | |
| wgpu::Buffer resultBuffer = device.CreateBuffer(&resultDesc); | |
| // Run the pipeline, rolling the external textures in the bindgroup such that a new | |
| // specialization of the shader should be created each time. | |
| for (size_t roll = 0; roll < externalTextures.size(); roll++) { | |
| wgpu::BindGroup bg0 = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, device.CreateSampler()}, | |
| {1, resultBuffer}, | |
| }); | |
| wgpu::BindGroup bg1 = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(1), | |
| {{0, externalTextures[(0 + roll) % 3]}}); | |
| wgpu::BindGroup bg2 = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(2), | |
| {{0, externalTextures[(1 + roll) % 3]}}); | |
| wgpu::BindGroup bg3 = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(3), | |
| {{0, externalTextures[(2 + roll) % 3]}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bg0); | |
| pass.SetBindGroup(1, bg1); | |
| pass.SetBindGroup(2, bg2); | |
| pass.SetBindGroup(3, bg3); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_RGBA8_EQ(colors[(0 + roll) % 3], resultBuffer, 0); | |
| EXPECT_BUFFER_RGBA8_EQ(colors[(1 + roll) % 3], resultBuffer, 4); | |
| EXPECT_BUFFER_RGBA8_EQ(colors[(2 + roll) % 3], resultBuffer, 8); | |
| } | |
| } | |
| // Test using a pipeline with two external textures in the layout, one of which is unused. | |
| TEST_P(ExternalTextureTests, LayoutWithUnusedExternalTexture) { | |
| // Make the two external textures. | |
| wgpu::ExternalTexture usedET = utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 4.0 / 255.0})); | |
| wgpu::ExternalTexture unusedET = utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {2.0 / 255.0, 4.0 / 255.0, 6.0 / 255.0, 8.0 / 255.0})); | |
| // Create the layout using both external textures. | |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( | |
| device, { | |
| {0, wgpu::ShaderStage::Compute, wgpu::SamplerBindingType::Filtering}, | |
| {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}, | |
| {2, wgpu::ShaderStage::Compute, &utils::kExternalTextureBindingLayout}, | |
| {3, wgpu::ShaderStage::Compute, &utils::kExternalTextureBindingLayout}, | |
| }); | |
| wgpu::PipelineLayout pl = utils::MakePipelineLayout(device, {bgl}); | |
| // Create the pipeline that samples only one of the external textures that's in the layout. | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var<storage, read_write> results : u32; | |
| @group(0) @binding(2) var t : texture_external; | |
| @compute @workgroup_size(1) fn main() { | |
| results = pack4x8unorm(textureSampleBaseClampToEdge(t, s, vec2(0))); | |
| } | |
| )"); | |
| csDesc.layout = pl; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| // Run the pipeline and check the results. | |
| wgpu::BufferDescriptor resultDesc = { | |
| .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, | |
| .size = sizeof(uint32_t), | |
| }; | |
| wgpu::Buffer resultBuffer = device.CreateBuffer(&resultDesc); | |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, device.CreateSampler()}, | |
| {1, resultBuffer}, | |
| {2, usedET}, | |
| {3, unusedET}, | |
| }); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bg); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), resultBuffer, 0); | |
| } | |
| // Test that using a dynamic offset works when there is an external texture in the same | |
| // BindGroupLayout | |
| TEST_P(ExternalTextureTests, UseExternalTextureWithDynamicOffset) { | |
| // TODO(42242119): fail on Qualcomm Adreno X1. | |
| DAWN_SUPPRESS_TEST_IF(IsD3D11() && IsQualcomm()); | |
| // Make the two external textures. | |
| wgpu::ExternalTexture et = utils::MakePassthroughExternalTexture( | |
| device, MakeTestTexture(wgpu::TextureFormat::RGBA8Unorm, 1, 1, | |
| {1.0 / 255.0, 2.0 / 255.0, 3.0 / 255.0, 4.0 / 255.0})); | |
| // Create the layout using both external textures. | |
| wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( | |
| device, { | |
| {0, wgpu::ShaderStage::Compute, wgpu::SamplerBindingType::Filtering}, | |
| {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage, true}, | |
| {2, wgpu::ShaderStage::Compute, &utils::kExternalTextureBindingLayout}, | |
| }); | |
| wgpu::PipelineLayout pl = utils::MakePipelineLayout(device, {bgl}); | |
| // Create the pipeline that samples only one of the external textures that's in the layout. | |
| wgpu::ComputePipelineDescriptor csDesc; | |
| csDesc.compute.module = utils::CreateShaderModule(device, R"( | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var<storage, read_write> results : u32; | |
| @group(0) @binding(2) var t : texture_external; | |
| @compute @workgroup_size(1) fn main() { | |
| results = pack4x8unorm(textureSampleBaseClampToEdge(t, s, vec2(0))); | |
| } | |
| )"); | |
| csDesc.layout = pl; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&csDesc); | |
| for (uint32_t offset : {0, 256}) { | |
| // Run the pipeline and check the results. | |
| wgpu::BufferDescriptor resultDesc = { | |
| .usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc, | |
| .size = sizeof(uint32_t) + 256, | |
| }; | |
| wgpu::Buffer resultBuffer = device.CreateBuffer(&resultDesc); | |
| wgpu::BindGroup bg = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| { | |
| {0, device.CreateSampler()}, | |
| {1, resultBuffer, 0, 4}, | |
| {2, et}, | |
| }); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetBindGroup(0, bg, 1, &offset); | |
| pass.SetPipeline(pipeline); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| EXPECT_BUFFER_RGBA8_EQ(utils::RGBA8(1, 2, 3, 4), resultBuffer, offset); | |
| EXPECT_BUFFER_RGBA8_EQ(utils::RGBA8(0, 0, 0, 0), resultBuffer, 256 - offset); | |
| } | |
| } | |
| // Test that the color conversion from PQ to sRGBLinear works as expected. | |
| TEST_P(ExternalTextureTests, ColorSpaceConversion_PQ_sRGBLinear) { | |
| // https://apps.colorjs.io/convert/?color=color(rec2100-pq%200.4%200.2%200.1)&precision=5 | |
| CheckColorSpaceConversion( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec2020, | |
| .transfer = wgpu::ColorSpaceTransferDawn::PQ, | |
| }, | |
| wgpu::PredefinedColorSpace::SRGBLinear, {0.4, 0.2, 0.1, 1}, | |
| {0.25826747, -0.00636454, -0.00231619}); | |
| // https://apps.colorjs.io/convert/?color=color(rec2100-pq%200.5%200.6%200.9)&precision=5 | |
| CheckColorSpaceConversion( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec2020, | |
| .transfer = wgpu::ColorSpaceTransferDawn::PQ, | |
| }, | |
| wgpu::PredefinedColorSpace::SRGBLinear, {0.5, 0.6, 0.9, 1}, {-1.3534, 1.1445, 21.395}); | |
| // Same test as above but using a different reference white luminance. | |
| { | |
| float factor = 203.0 / 100.0; | |
| CheckColorSpaceConversion( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec2020, | |
| .transfer = wgpu::ColorSpaceTransferDawn::PQ, | |
| .hdrReferenceWhiteLuminance = 100, | |
| }, | |
| wgpu::PredefinedColorSpace::SRGBLinear, {0.5, 0.6, 0.9, 1}, | |
| {-1.3534f * factor, 1.1445f * factor, 21.395f * factor}); | |
| } | |
| } | |
| // Test that the color conversion from HLG to Rec2020Linear works as expected. | |
| TEST_P(ExternalTextureTests, ColorSpaceConversion_HLG_Rec2020Linear) { | |
| struct TestVector { | |
| wgpu::Color hlg; | |
| std::array<float, 3> rec2020; | |
| }; | |
| // Test vector were generated using Skia to convert between two SkColorSpaces. | |
| auto testVectors = std::array<TestVector, 8>({ | |
| {{1.00, 1.00, 1.00, 1.0}, {4.926219, 4.926220, 4.926220}}, | |
| {{0.75, 0.75, 0.75, 1.0}, {1.000729, 1.000729, 1.000729}}, | |
| {{0.00, 0.00, 0.75, 1.0}, {0.000000, 0.000000, 0.568750}}, | |
| {{0.00, 0.75, 0.00, 1.0}, {0.000000, 0.925928, 0.000000}}, | |
| {{0.75, 0.00, 0.00, 1.0}, {0.765934, 0.000000, 0.000000}}, | |
| {{0.25, 0.50, 0.75, 1.0}, {0.061563, 0.246247, 0.782958}}, | |
| {{0.50, 0.75, 0.25, 1.0}, {0.298348, 0.948618, 0.074588}}, | |
| {{0.75, 0.25, 0.50, 1.0}, {0.803970, 0.063215, 0.252855}}, | |
| }); | |
| for (const auto& testVector : testVectors) { | |
| CheckColorSpaceConversion( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec2020, | |
| .transfer = wgpu::ColorSpaceTransferDawn::HLG, | |
| }, | |
| wgpu::PredefinedColorSpace::Rec2020Linear, testVector.hlg, testVector.rec2020); | |
| } | |
| } | |
| // Test that the color conversion from HLG to SRGB (non-linear) works as expected. | |
| TEST_P(ExternalTextureTests, ColorSpaceConversion_HLG_SRGB) { | |
| struct TestVector { | |
| wgpu::Color hlg; | |
| std::array<float, 3> srgb; | |
| }; | |
| // Test vector were generated using Skia to convert between two SkColorSpaces. | |
| auto testVectors = std::array<TestVector, 8>({ | |
| {{1.00, 1.00, 1.00, 1.0}, {1.994927, 1.995262, 1.995293}}, | |
| {{0.75, 0.75, 0.75, 1.0}, {1.000107, 1.000290, 1.000305}}, | |
| {{0.00, 0.00, 0.75, 1.0}, {-0.224957, -0.058485, 0.818867}}, | |
| {{0.00, 0.75, 0.00, 1.0}, {-0.763752, 1.021187, -0.337370}}, | |
| {{0.75, 0.00, 0.00, 1.0}, {1.111084, -0.341290, -0.122559}}, | |
| {{0.25, 0.50, 0.75, 1.0}, {-0.348418, 0.551422, 0.930966}}, | |
| {{0.50, 0.75, 0.25, 1.0}, {-0.288479, 1.016007, -0.139914}}, | |
| {{0.75, 0.25, 0.50, 1.0}, {1.113976, -0.191790, 0.548725}}, | |
| }); | |
| for (const auto& testVector : testVectors) { | |
| CheckColorSpaceConversion( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec2020, | |
| .transfer = wgpu::ColorSpaceTransferDawn::HLG, | |
| }, | |
| wgpu::PredefinedColorSpace::SRGB, testVector.hlg, testVector.srgb); | |
| } | |
| } | |
| // Test that PredefinedColorSpace::Rec2020Linear is the same color space as using primaries of | |
| // Rec2020 and an identity transfer function. (the Rec2020 primaries are tested as part of the PQ | |
| // and HLG tests above). | |
| TEST_P(ExternalTextureTests, ColorSpaceConversion_Rec2020_Rec2020Linear) { | |
| CheckColorSpaceConversion( | |
| { | |
| .primaries = wgpu::ColorSpacePrimariesDawn::Rec2020, | |
| }, | |
| wgpu::PredefinedColorSpace::Rec2020Linear, {0.4, 0.2, 0.1, 1}, {0.4, 0.2, 0.1}); | |
| } | |
| // TODO(https://crbug.com/468988322): Add tests of ExternalTextures being used with a resource table | |
| // and with Vulkan's extended dynamic state as these are other interactions that could easily break. | |
| DAWN_INSTANTIATE_TEST(ExternalTextureTests, | |
| D3D11Backend(), | |
| D3D12Backend(), | |
| D3D12Backend({}, {"d3d12_use_root_signature_version_1_1"}), | |
| MetalBackend(), | |
| OpenGLBackend(), | |
| OpenGLESBackend(), | |
| VulkanBackend(), | |
| VulkanBackend({"vulkan_force_static_samplers_for_external_textures"}), | |
| WebGPUBackend()); | |
| enum class AccessMode { Sample, Load }; | |
| enum class OOBAccess { BelowBottomLeft, OverTopRight }; | |
| enum class Flip { None, Mirrored }; | |
| using Rotation = wgpu::ExternalTextureRotation; | |
| std::ostream& operator<<(std::ostream& o, Flip flip) { | |
| switch (flip) { | |
| case Flip::None: | |
| o << "no flip "; | |
| break; | |
| case Flip::Mirrored: | |
| o << "mirrored "; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| break; | |
| } | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, AccessMode mode) { | |
| switch (mode) { | |
| case AccessMode::Sample: | |
| o << "sample "; | |
| break; | |
| case AccessMode::Load: | |
| o << "load "; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| break; | |
| } | |
| return o; | |
| } | |
| std::ostream& operator<<(std::ostream& o, OOBAccess rect) { | |
| switch (rect) { | |
| case OOBAccess::BelowBottomLeft: | |
| o << "below bottom left "; | |
| break; | |
| case OOBAccess::OverTopRight: | |
| o << "over top right"; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| break; | |
| } | |
| return o; | |
| } | |
| DAWN_TEST_PARAM_STRUCT(OOBTestParams, Rotation, Flip, AccessMode, OOBAccess); | |
| class ExternalTextureOOBTests : public ExternalTextureTestsBase<DawnTestWithParams<OOBTestParams>> { | |
| protected: | |
| void SetUp() override { | |
| ExternalTextureTestsBase<DawnTestWithParams<OOBTestParams>>::SetUp(); | |
| // TODO(crbug.com/522872460): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| sourceTexturePlane0 = Create2DTexture( | |
| device, kPlaneWidth, kPlaneHeight, wgpu::TextureFormat::R8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| sourceTexturePlane1 = Create2DTexture( | |
| device, kPlaneWidth, kPlaneHeight, wgpu::TextureFormat::RG8Unorm, | |
| wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment); | |
| RenderQuad(sourceTexturePlane0, | |
| {{kGreen.y}, {kColor1.y}, {kRed.y}, {kBlue.y}, {kBlack.y}, 0.5}); | |
| RenderQuad(sourceTexturePlane1, {{kGreen.u, kGreen.v}, | |
| {kColor1.u, kColor1.v}, | |
| {kRed.u, kRed.v}, | |
| {kBlue.u, kBlue.v}, | |
| {kBlack.u, kBlack.v}, | |
| 0.5}); | |
| renderTexture = | |
| Create2DTexture(device, kPlaneWidth, kPlaneHeight, kFormat, | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment); | |
| oobTestShaderModule = utils::CreateShaderModule(device, R"( | |
| @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| var positions = array( | |
| vec4f(-3, -1, 0, 1), | |
| vec4f( 3, -1, 0, 1), | |
| vec4f( 0, 2, 0, 1), | |
| ); | |
| return positions[VertexIndex]; | |
| } | |
| @group(0) @binding(0) var s : sampler; | |
| @group(0) @binding(1) var t : texture_external; | |
| @fragment fn sampleOverTopRight() -> @location(0) vec4f { | |
| return textureSampleBaseClampToEdge(t, s, vec2f(1.1, 1.1)); | |
| } | |
| @fragment fn sampleBelowBottomLeft() -> @location(0) vec4f { | |
| return textureSampleBaseClampToEdge(t, s, vec2f(-0.1, -0.1)); | |
| } | |
| @fragment fn loadOverTopRight() -> @location(0) vec4f { | |
| _ = textureSampleBaseClampToEdge(t, s, vec2f(0.0, 0.0)); | |
| return textureLoad(t, vec2<i32>(5, 5)); | |
| } | |
| @fragment fn loadBelowBottomLeft() -> @location(0) vec4f { | |
| _ = textureSampleBaseClampToEdge(t, s, vec2f(0.0, 0.0)); | |
| return textureLoad(t, vec2<i32>(-1, -1)); | |
| } | |
| )"); | |
| } | |
| std::string GetEntryPoint(AccessMode mode, OOBAccess access) { | |
| switch (mode) { | |
| case AccessMode::Sample: | |
| switch (access) { | |
| case OOBAccess::BelowBottomLeft: | |
| return "sampleBelowBottomLeft"; | |
| case OOBAccess::OverTopRight: | |
| return "sampleOverTopRight"; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| break; | |
| case AccessMode::Load: | |
| switch (access) { | |
| case OOBAccess::BelowBottomLeft: | |
| return "loadBelowBottomLeft"; | |
| case OOBAccess::OverTopRight: | |
| return "loadOverTopRight"; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| void DoTest() { | |
| wgpu::ExternalTextureRotation rotation = GetParam().mRotation; | |
| OOBAccess oobAccess = GetParam().mOOBAccess; | |
| AccessMode accessMode = GetParam().mAccessMode; | |
| Flip flip = GetParam().mFlip; | |
| utils::ComboRenderPipelineDescriptor descriptor; | |
| descriptor.vertex.module = oobTestShaderModule; | |
| descriptor.vertex.entryPoint = "main"; | |
| descriptor.cFragment.module = oobTestShaderModule; | |
| std::string fragmentEntryPoint = GetEntryPoint(accessMode, oobAccess); | |
| descriptor.cFragment.entryPoint = fragmentEntryPoint.c_str(); | |
| descriptor.cTargets[0].format = kFormat; | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&descriptor); | |
| // Import the external texture | |
| wgpu::ExternalTextureDescriptor externalDesc = | |
| InitExternalTextureDescriptor(sourceTexturePlane0, sourceTexturePlane1); | |
| externalDesc.rotation = rotation; | |
| externalDesc.mirrored = flip == Flip::Mirrored; | |
| wgpu::ExternalTexture externalTexture = device.CreateExternalTexture(&externalDesc); | |
| // Create a sampler and bind group | |
| wgpu::Sampler sampler = device.CreateSampler(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, sampler}, {1, externalTexture}}); | |
| // Run the shader, which should sample from the external texture and draw a triangle into | |
| // the upper left corner of the render texture. | |
| wgpu::TextureView renderView = renderTexture.CreateView(); | |
| utils::ComboRenderPassDescriptor renderPass({renderView}, nullptr); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); | |
| { | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(3); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| // Border color is black. | |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kBlack, renderTexture, 0, 0); | |
| } | |
| static constexpr uint32_t kPlaneWidth = 5; | |
| static constexpr uint32_t kPlaneHeight = 5; | |
| wgpu::ShaderModule oobTestShaderModule; | |
| wgpu::Texture sourceTexturePlane0; | |
| wgpu::Texture sourceTexturePlane1; | |
| wgpu::Texture renderTexture; | |
| }; | |
| // Test for OOB access | |
| TEST_P(ExternalTextureOOBTests, ExternalTextureOOB) { | |
| // TODO(crbug.com/dawn/2295): diagnose this failure on Pixel 4 OpenGLES | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsAndroid() && IsQualcomm()); | |
| DoTest(); | |
| } | |
| DAWN_INSTANTIATE_TEST_P( | |
| ExternalTextureOOBTests, | |
| {D3D11Backend(), D3D12Backend(), D3D12Backend({}, {"d3d12_use_root_signature_version_1_1"}), | |
| MetalBackend(), OpenGLBackend(), OpenGLESBackend(), VulkanBackend(), | |
| VulkanBackend({"vulkan_force_static_samplers_for_external_textures"}), WebGPUBackend()}, | |
| std::vector<wgpu::ExternalTextureRotation>({wgpu::ExternalTextureRotation::Rotate0Degrees, | |
| wgpu::ExternalTextureRotation::Rotate90Degrees, | |
| wgpu::ExternalTextureRotation::Rotate180Degrees, | |
| wgpu::ExternalTextureRotation::Rotate270Degrees}), | |
| std::vector<Flip>({Flip::None, Flip::Mirrored}), | |
| std::vector<AccessMode>({AccessMode::Sample, AccessMode::Load}), | |
| std::vector<OOBAccess>({OOBAccess::BelowBottomLeft, OOBAccess::OverTopRight})); | |
| } // anonymous namespace | |
| } // namespace dawn |