| // Copyright 2023 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. | |
| #ifdef UNSAFE_BUFFERS_BUILD | |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. | |
| #pragma allow_unsafe_buffers | |
| #endif | |
| #include "src/dawn/tests/white_box/SharedTextureMemoryTests.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/dawn/tests/MockCallback.h" | |
| #include "src/dawn/tests/StringViewMatchers.h" | |
| #include "src/dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "src/dawn/utils/TextureUtils.h" | |
| #include "src/dawn/utils/WGPUHelpers.h" | |
| using testing::SizedStringMatches; | |
| namespace dawn { | |
| namespace { | |
| struct BackendBeginStateVk : public SharedTextureMemoryTestBackend::BackendBeginState { | |
| wgpu::SharedTextureMemoryVkImageLayoutBeginState imageLayouts{}; | |
| }; | |
| struct BackendEndStateVk : public SharedTextureMemoryTestBackend::BackendEndState { | |
| wgpu::SharedTextureMemoryVkImageLayoutEndState imageLayouts{}; | |
| }; | |
| // TODO(crbug.com/535646877): Move this utility to shared header and use in more places | |
| bool HasAlphaChannel(wgpu::TextureFormat format) { | |
| switch (format) { | |
| case wgpu::TextureFormat::RGBA8Unorm: | |
| case wgpu::TextureFormat::RGBA8UnormSrgb: | |
| case wgpu::TextureFormat::BGRA8Unorm: | |
| case wgpu::TextureFormat::BGRA8UnormSrgb: | |
| case wgpu::TextureFormat::RGBA8Snorm: | |
| case wgpu::TextureFormat::RGBA8Uint: | |
| case wgpu::TextureFormat::RGBA8Sint: | |
| case wgpu::TextureFormat::RGBA16Unorm: | |
| case wgpu::TextureFormat::RGBA16Snorm: | |
| case wgpu::TextureFormat::RGBA16Uint: | |
| case wgpu::TextureFormat::RGBA16Sint: | |
| case wgpu::TextureFormat::RGBA16Float: | |
| case wgpu::TextureFormat::RGBA32Uint: | |
| case wgpu::TextureFormat::RGBA32Sint: | |
| case wgpu::TextureFormat::RGBA32Float: | |
| case wgpu::TextureFormat::RGB10A2Unorm: | |
| case wgpu::TextureFormat::RGB10A2Uint: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| } // anonymous namespace | |
| std::unique_ptr<SharedTextureMemoryTestBackend::BackendBeginState> | |
| SharedTextureMemoryTestVulkanBackend::ChainInitialBeginState( | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor* beginDesc) { | |
| auto state = std::make_unique<BackendBeginStateVk>(); | |
| beginDesc->nextInChain = &state->imageLayouts; | |
| return state; | |
| } | |
| std::unique_ptr<SharedTextureMemoryTestBackend::BackendEndState> | |
| SharedTextureMemoryTestVulkanBackend::ChainEndState( | |
| wgpu::SharedTextureMemoryEndAccessState* endState) { | |
| auto state = std::make_unique<BackendEndStateVk>(); | |
| endState->nextInChain = &state->imageLayouts; | |
| return state; | |
| } | |
| std::unique_ptr<SharedTextureMemoryTestBackend::BackendBeginState> | |
| SharedTextureMemoryTestVulkanBackend::ChainBeginState( | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor* beginDesc, | |
| const wgpu::SharedTextureMemoryEndAccessState& endState) { | |
| DAWN_ASSERT(endState.nextInChain != nullptr); | |
| DAWN_ASSERT(endState.nextInChain->sType == | |
| wgpu::SType::SharedTextureMemoryVkImageLayoutEndState); | |
| auto* vkEndState = | |
| static_cast<wgpu::SharedTextureMemoryVkImageLayoutEndState*>(endState.nextInChain); | |
| auto state = std::make_unique<BackendBeginStateVk>(); | |
| state->imageLayouts.oldLayout = vkEndState->oldLayout; | |
| state->imageLayouts.newLayout = vkEndState->newLayout; | |
| beginDesc->nextInChain = &state->imageLayouts; | |
| return state; | |
| } | |
| void SharedTextureMemoryNoFeatureTests::SetUp() { | |
| DAWN_TEST_UNSUPPORTED_IF(UsesWire()); | |
| DawnTestWithParams<SharedTextureMemoryTestParams>::SetUp(); | |
| // TODO(crbug.com/483147423): Implement Capture. | |
| DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled()); | |
| GetParam().mBackend->SetUp(device); | |
| } | |
| std::vector<wgpu::FeatureName> SharedTextureMemoryTests::GetRequiredFeatures() { | |
| auto features = GetParam().mBackend->RequiredFeatures(GetAdapter().Get()); | |
| if (!SupportsFeatures(features)) { | |
| return {}; | |
| } | |
| const wgpu::FeatureName kOptionalFeatures[] = { | |
| wgpu::FeatureName::MultiPlanarFormatExtendedUsages, | |
| wgpu::FeatureName::MultiPlanarRenderTargets, | |
| wgpu::FeatureName::TextureFormatsTier1, | |
| wgpu::FeatureName::BGRA8UnormStorage, | |
| wgpu::FeatureName::FlexibleTextureViews, | |
| wgpu::FeatureName::MSAARenderToSingleSampled, | |
| wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable, | |
| }; | |
| for (auto feature : kOptionalFeatures) { | |
| if (SupportsFeatures({feature})) { | |
| features.push_back(feature); | |
| } | |
| } | |
| return features; | |
| } // namespace dawn | |
| void SharedTextureMemoryTests::SetUp() { | |
| DAWN_TEST_UNSUPPORTED_IF(UsesWire()); | |
| DawnTestWithParams<SharedTextureMemoryTestParams>::SetUp(); | |
| DAWN_TEST_UNSUPPORTED_IF( | |
| !SupportsFeatures(GetParam().mBackend->RequiredFeatures(GetAdapter().Get()))); | |
| // TODO(crbug.com/342213634): Crashes on ChromeOS volteer devices. | |
| // TODO(crbug.com/407561933): Triggers dawn validation errors | |
| DAWN_SUPPRESS_TEST_IF(IsChromeOS() && IsVulkan() && IsIntel()); | |
| // TODO(crbug.com/483147423): Implement Capture. | |
| DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled()); | |
| // Compat cannot create 2D texture view from a 2D array texture. | |
| DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode() && | |
| !SupportsFeatures({wgpu::FeatureName::FlexibleTextureViews}) && | |
| GetParam().mLayerCount > 1); | |
| GetParam().mBackend->SetUp(device); | |
| } | |
| void SharedTextureMemoryNoFeatureTests::TearDown() { | |
| DawnTestWithParams<SharedTextureMemoryTestParams>::TearDown(); | |
| GetParam().mBackend->TearDown(); | |
| } | |
| void SharedTextureMemoryTests::TearDown() { | |
| DawnTestWithParams<SharedTextureMemoryTestParams>::TearDown(); | |
| GetParam().mBackend->TearDown(); | |
| } | |
| std::vector<wgpu::SharedTextureMemory> SharedTextureMemoryTestBackend::CreateSharedTextureMemories( | |
| wgpu::Device& device, | |
| int layerCount) { | |
| std::vector<wgpu::SharedTextureMemory> memories; | |
| for (auto& memory : CreatePerDeviceSharedTextureMemories({device}, layerCount)) { | |
| DAWN_ASSERT(memory.size() == 1u); | |
| memories.push_back(std::move(memory[0])); | |
| } | |
| // There should be at least one memory to test. | |
| DAWN_ASSERT(!memories.empty()); | |
| return memories; | |
| } | |
| wgpu::Texture CreateWriteTexture(wgpu::SharedTextureMemory memory) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor writeTextureDesc = {}; | |
| writeTextureDesc.format = properties.format; | |
| writeTextureDesc.size = properties.size; | |
| writeTextureDesc.usage = wgpu::TextureUsage::RenderAttachment; | |
| writeTextureDesc.label = "write texture"; | |
| return memory.CreateTexture(&writeTextureDesc); | |
| } | |
| wgpu::Texture CreateReadTexture(wgpu::SharedTextureMemory memory) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor readTextureDesc = {}; | |
| readTextureDesc.format = properties.format; | |
| readTextureDesc.size = properties.size; | |
| readTextureDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| readTextureDesc.label = "read texture"; | |
| return memory.CreateTexture(&readTextureDesc); | |
| } | |
| std::vector<wgpu::SharedTextureMemory> | |
| SharedTextureMemoryTestBackend::CreateSinglePlanarSharedTextureMemories(wgpu::Device& device, | |
| int layerCount) { | |
| std::vector<wgpu::SharedTextureMemory> out; | |
| for (auto& memory : CreateSharedTextureMemories(device, layerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| if (utils::IsMultiPlanarFormat(properties.format)) { | |
| continue; | |
| } | |
| out.push_back(std::move(memory)); | |
| } | |
| return out; | |
| } | |
| std::vector<std::vector<wgpu::SharedTextureMemory>> | |
| SharedTextureMemoryTestBackend::CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| const std::vector<wgpu::Device>& devices, | |
| wgpu::TextureUsage requiredUsage, | |
| int layerCount) { | |
| std::vector<std::vector<wgpu::SharedTextureMemory>> out; | |
| for (auto& memories : CreatePerDeviceSharedTextureMemories(devices, layerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memories[0].GetProperties(&properties); | |
| if ((properties.usage & requiredUsage) == requiredUsage) { | |
| // Tests using RenderAttachment will get a TextureView from the | |
| // texture. This currently doesn't work with multiplanar textures. The | |
| // superficial problem is that the plane would need to be passed for | |
| // multiplanar formats, and the deep problem is that the tests fail to | |
| // create valid backing textures for some multiplanar formats (e.g., | |
| // on Apple), which results in a crash when accessing plane 0. | |
| // TODO(crbug.com/dawn/2263): Fix this and remove this short-circuit. | |
| if (utils::IsMultiPlanarFormat(properties.format) && | |
| (requiredUsage & | |
| (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding))) { | |
| continue; | |
| } | |
| out.push_back(std::move(memories)); | |
| } | |
| } | |
| return out; | |
| } | |
| wgpu::Device SharedTextureMemoryTests::CreateDevice() { | |
| if (GetParam().mBackend->UseSameDevice()) { | |
| return device; | |
| } | |
| return DawnTestBase::CreateDevice(); | |
| } | |
| void SharedTextureMemoryTests::UseInRenderPass(wgpu::Device& deviceObj, wgpu::Texture& texture) { | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| for (int layer = 0; layer < GetParam().mLayerCount; ++layer) { | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = layer; | |
| desc.arrayLayerCount = 1; | |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView(&desc)}); | |
| passDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Load; | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.End(); | |
| } | |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); | |
| deviceObj.GetQueue().Submit(1, &commandBuffer); | |
| } | |
| void SharedTextureMemoryTests::UseInCopy(wgpu::Device& deviceObj, wgpu::Texture& texture) { | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| wgpu::TexelCopyTextureInfo source; | |
| source.texture = texture; | |
| // Create a destination buffer, large enough for 1 texel of any format. | |
| wgpu::BufferDescriptor bufferDesc; | |
| bufferDesc.size = 128; | |
| bufferDesc.usage = wgpu::BufferUsage::CopyDst; | |
| wgpu::TexelCopyBufferInfo destination; | |
| destination.buffer = deviceObj.CreateBuffer(&bufferDesc); | |
| wgpu::Extent3D size = {1, 1, 1}; | |
| for (int layer = 0; layer < GetParam().mLayerCount; ++layer) { | |
| source.origin.z = layer; | |
| encoder.CopyTextureToBuffer(&source, &destination, &size); | |
| } | |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); | |
| deviceObj.GetQueue().Submit(1, &commandBuffer); | |
| } | |
| // Make a command buffer that clears the texture to four different colors in each quadrant. | |
| wgpu::CommandBuffer SharedTextureMemoryTests::MakeFourColorsClearCommandBuffer( | |
| wgpu::Device& deviceObj, | |
| wgpu::Texture& texture) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(deviceObj, R"( | |
| struct VertexOut { | |
| @builtin(position) position : vec4f, | |
| @location(0) uv : vec2f, | |
| } | |
| struct FragmentIn { | |
| @location(0) uv : vec2f, | |
| } | |
| @vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> VertexOut { | |
| let pos = array( | |
| vec2( 1.0, 1.0), | |
| vec2( 1.0, -1.0), | |
| vec2(-1.0, -1.0), | |
| vec2( 1.0, 1.0), | |
| vec2(-1.0, -1.0), | |
| vec2(-1.0, 1.0), | |
| ); | |
| let uv = array( | |
| vec2(1.0, 0.0), | |
| vec2(1.0, 1.0), | |
| vec2(0.0, 1.0), | |
| vec2(1.0, 0.0), | |
| vec2(0.0, 1.0), | |
| vec2(0.0, 0.0), | |
| ); | |
| return VertexOut(vec4f(pos[VertexIndex], 0.0, 1.0), uv[VertexIndex]); | |
| } | |
| @fragment fn frag_main(in: FragmentIn) -> @location(0) vec4f { | |
| if (in.uv.x < 0.5) { | |
| if (in.uv.y < 0.5) { | |
| return vec4f(0.0, 1.0, 0.0, 0.501); | |
| } else { | |
| return vec4f(1.0, 0.0, 0.0, 0.501); | |
| } | |
| } else { | |
| if (in.uv.y < 0.5) { | |
| return vec4f(0.0, 0.0, 1.0, 0.501); | |
| } else { | |
| return vec4f(1.0, 1.0, 0.0, 0.501); | |
| } | |
| } | |
| } | |
| )"); | |
| utils::ComboRenderPipelineDescriptor pipelineDesc; | |
| pipelineDesc.vertex.module = module; | |
| pipelineDesc.cFragment.module = module; | |
| pipelineDesc.cTargets[0].format = texture.GetFormat(); | |
| wgpu::RenderPipeline pipeline = deviceObj.CreateRenderPipeline(&pipelineDesc); | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| wgpu::TextureViewDescriptor viewDesc; | |
| viewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| viewDesc.baseArrayLayer = layer; | |
| viewDesc.arrayLayerCount = 1; | |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView(&viewDesc)}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.Draw(6); | |
| pass.End(); | |
| } | |
| return encoder.Finish(); | |
| } | |
| // Make a command buffer that clears the texture to four different colors in each quadrant. | |
| wgpu::CommandBuffer SharedTextureMemoryTests::MakeFourColorsComputeCommandBuffer( | |
| wgpu::Device& deviceObj, | |
| wgpu::Texture& texture) { | |
| std::string wgslFormat = utils::GetWGSLImageFormatQualifier(texture.GetFormat()); | |
| std::string shader = R"( | |
| @group(0) @binding(0) var storageImage : texture_storage_2d<)" + | |
| wgslFormat + R"(, write>; | |
| @workgroup_size(1) | |
| @compute fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { | |
| let dims = textureDimensions(storageImage); | |
| if (global_id.x < dims.x / 2) { | |
| if (global_id.y < dims.y / 2) { | |
| textureStore(storageImage, global_id.xy, vec4f(0.0, 1.0, 0.0, 0.501)); | |
| } else { | |
| textureStore(storageImage, global_id.xy, vec4f(1.0, 0.0, 0.0, 0.501)); | |
| } | |
| } else { | |
| if (global_id.y < dims.y / 2) { | |
| textureStore(storageImage, global_id.xy, vec4f(0.0, 0.0, 1.0, 0.501)); | |
| } else { | |
| textureStore(storageImage, global_id.xy, vec4f(1.0, 1.0, 0.0, 0.501)); | |
| } | |
| } | |
| } | |
| )"; | |
| wgpu::ComputePipelineDescriptor pipelineDesc; | |
| pipelineDesc.compute.module = utils::CreateShaderModule(deviceObj, shader.c_str()); | |
| wgpu::ComputePipeline pipeline = deviceObj.CreateComputePipeline(&pipelineDesc); | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetPipeline(pipeline); | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = layer; | |
| desc.arrayLayerCount = 1; | |
| pass.SetBindGroup(0, utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&desc)}})); | |
| pass.DispatchWorkgroups(texture.GetWidth(), texture.GetHeight()); | |
| } | |
| pass.End(); | |
| return encoder.Finish(); | |
| } | |
| // Use queue.writeTexture to write four different colors in each quadrant to the texture. | |
| void SharedTextureMemoryTests::WriteFourColorsToRGBA8Texture(wgpu::Device& deviceObj, | |
| wgpu::Texture& texture) { | |
| DAWN_ASSERT(texture.GetFormat() == wgpu::TextureFormat::RGBA8Unorm); | |
| uint32_t width = texture.GetWidth(); | |
| uint32_t height = texture.GetHeight(); | |
| uint32_t bytesPerBlock = utils::GetTexelBlockSizeInBytes(texture.GetFormat()); | |
| uint32_t bytesPerRow = width * bytesPerBlock; | |
| uint32_t size = bytesPerRow * height; | |
| std::vector<uint8_t> pixels(size); | |
| constexpr utils::RGBA8 kTopLeft(0, 0xFF, 0, 0x80); | |
| constexpr utils::RGBA8 kBottomLeft(0xFF, 0, 0, 0x80); | |
| constexpr utils::RGBA8 kTopRight(0, 0, 0xFF, 0x80); | |
| constexpr utils::RGBA8 kBottomRight(0xFF, 0xFF, 0, 0x80); | |
| for (uint32_t y = 0; y < height; y++) { | |
| for (uint32_t x = 0; x < width; x++) { | |
| utils::RGBA8* pixel = | |
| reinterpret_cast<utils::RGBA8*>(&pixels[y * bytesPerRow + x * bytesPerBlock]); | |
| if (x < width / 2) { | |
| if (y < height / 2) { | |
| *pixel = kTopLeft; | |
| } else { | |
| *pixel = kBottomLeft; | |
| } | |
| } else { | |
| if (y < height / 2) { | |
| *pixel = kTopRight; | |
| } else { | |
| *pixel = kBottomRight; | |
| } | |
| } | |
| } | |
| } | |
| wgpu::Extent3D writeSize = {width, height, 1}; | |
| wgpu::TexelCopyTextureInfo dest; | |
| dest.texture = texture; | |
| wgpu::TexelCopyBufferLayout dataLayout = { | |
| .offset = 0, .bytesPerRow = bytesPerRow, .rowsPerImage = height}; | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| dest.origin.z = layer; | |
| device.GetQueue().WriteTexture(&dest, pixels.data(), pixels.size(), &dataLayout, | |
| &writeSize); | |
| } | |
| } | |
| // Make a command buffer that samples the contents of the input texture into an RGBA8Unorm texture. | |
| std::pair<wgpu::CommandBuffer, wgpu::Texture> | |
| SharedTextureMemoryTests::MakeCheckBySamplingCommandBuffer(wgpu::Device& deviceObj, | |
| wgpu::Texture& texture) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(deviceObj, R"( | |
| @vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| let pos = array( | |
| vec2( 1.0, 1.0), | |
| vec2( 1.0, -1.0), | |
| vec2(-1.0, -1.0), | |
| vec2( 1.0, 1.0), | |
| vec2(-1.0, -1.0), | |
| vec2(-1.0, 1.0), | |
| ); | |
| return vec4f(pos[VertexIndex], 0.0, 1.0); | |
| } | |
| @group(0) @binding(0) var t: texture_2d<f32>; | |
| @fragment fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4f { | |
| return textureLoad(t, vec2u(coord_in.xy), 0); | |
| } | |
| )"); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.dimension = wgpu::TextureDimension::e2D; | |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; | |
| textureDesc.size = {texture.GetWidth(), texture.GetHeight(), 1}; | |
| textureDesc.label = "intermediate check texture"; | |
| wgpu::Texture colorTarget = deviceObj.CreateTexture(&textureDesc); | |
| utils::ComboRenderPipelineDescriptor pipelineDesc; | |
| pipelineDesc.vertex.module = module; | |
| pipelineDesc.cFragment.module = module; | |
| pipelineDesc.cTargets[0].format = colorTarget.GetFormat(); | |
| wgpu::RenderPipeline pipeline = deviceObj.CreateRenderPipeline(&pipelineDesc); | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = 0; | |
| desc.arrayLayerCount = 1; | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&desc)}}); | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({colorTarget.CreateView()}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(6); | |
| pass.End(); | |
| return {encoder.Finish(), colorTarget}; | |
| } | |
| // Make a command buffer that samples the contents of the input texture into an RGBA8Unorm texture. | |
| std::pair<wgpu::CommandBuffer, wgpu::Texture> | |
| SharedTextureMemoryTests::MakeCheckBySamplingTwoTexturesCommandBuffer(wgpu::Texture& texture0, | |
| wgpu::Texture& texture1) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, R"( | |
| @vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| let pos = array( | |
| vec2( 1.0, 1.0), | |
| vec2( 1.0, -1.0), | |
| vec2(-1.0, -1.0), | |
| vec2( 1.0, 1.0), | |
| vec2(-1.0, -1.0), | |
| vec2(-1.0, 1.0), | |
| ); | |
| return vec4f(pos[VertexIndex], 0.0, 1.0); | |
| } | |
| @group(0) @binding(0) var t0: texture_2d<f32>; | |
| @group(0) @binding(1) var t1: texture_2d<f32>; | |
| @fragment fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4f { | |
| return (textureLoad(t0, vec2u(coord_in.xy), 0) / 2) + | |
| (textureLoad(t1, vec2u(coord_in.xy), 0) / 2); | |
| } | |
| )"); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; | |
| textureDesc.size = {texture0.GetWidth(), texture0.GetHeight(), 1}; | |
| textureDesc.label = "intermediate check texture"; | |
| wgpu::Texture colorTarget = device.CreateTexture(&textureDesc); | |
| utils::ComboRenderPipelineDescriptor pipelineDesc; | |
| pipelineDesc.vertex.module = module; | |
| pipelineDesc.cFragment.module = module; | |
| pipelineDesc.cTargets[0].format = colorTarget.GetFormat(); | |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = 0; | |
| desc.arrayLayerCount = 1; | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), | |
| {{0, texture0.CreateView(&desc)}, {1, texture1.CreateView(&desc)}}); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({colorTarget.CreateView()}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(6); | |
| pass.End(); | |
| return {encoder.Finish(), colorTarget}; | |
| } | |
| // Make a command buffer that samples the contents of the input texture 2d array into an RGBA8Unorm | |
| // texture. | |
| std::pair<wgpu::CommandBuffer, wgpu::Texture> | |
| SharedTextureMemoryTests::MakeCheckBySamplingTexture2DArrayCommandBuffer(wgpu::Device& deviceObj, | |
| wgpu::Texture& texture) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(deviceObj, R"( | |
| @vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| let pos = array( | |
| vec2( 1.0, 1.0), | |
| vec2( 1.0, -1.0), | |
| vec2(-1.0, -1.0), | |
| vec2( 1.0, 1.0), | |
| vec2(-1.0, -1.0), | |
| vec2(-1.0, 1.0), | |
| ); | |
| return vec4f(pos[VertexIndex], 0.0, 1.0); | |
| } | |
| @group(0) @binding(0) var t: texture_2d_array<f32>; | |
| @fragment fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4f { | |
| return (textureLoad(t, vec2u(coord_in.xy), 0, 0) / 2) + | |
| (textureLoad(t, vec2u(coord_in.xy), 1, 0) / 2); | |
| } | |
| )"); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; | |
| textureDesc.size = {texture.GetWidth(), texture.GetHeight(), 1}; | |
| textureDesc.label = "intermediate check texture"; | |
| wgpu::Texture colorTarget = deviceObj.CreateTexture(&textureDesc); | |
| utils::ComboRenderPipelineDescriptor pipelineDesc; | |
| pipelineDesc.vertex.module = module; | |
| pipelineDesc.cFragment.module = module; | |
| pipelineDesc.cTargets[0].format = colorTarget.GetFormat(); | |
| wgpu::RenderPipeline pipeline = deviceObj.CreateRenderPipeline(&pipelineDesc); | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2DArray; | |
| desc.baseArrayLayer = 0; | |
| desc.arrayLayerCount = texture.GetDepthOrArrayLayers(); | |
| wgpu::BindGroup bindGroup = utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0), | |
| {{0, texture.CreateView(&desc)}}); | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({colorTarget.CreateView()}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.Draw(6); | |
| pass.End(); | |
| return {encoder.Finish(), colorTarget}; | |
| } | |
| // Check that the contents of colorTarget are RGBA8Unorm texels that match those written by | |
| // MakeFourColorsClearCommandBuffer. | |
| void SharedTextureMemoryTests::CheckFourColors(wgpu::Device& deviceObj, | |
| wgpu::TextureFormat format, | |
| wgpu::Texture& colorTarget) { | |
| wgpu::Origin3D tl = {colorTarget.GetWidth() / 4, colorTarget.GetHeight() / 4}; | |
| wgpu::Origin3D bl = {colorTarget.GetWidth() / 4, 3 * colorTarget.GetHeight() / 4}; | |
| wgpu::Origin3D tr = {3 * colorTarget.GetWidth() / 4, colorTarget.GetHeight() / 4}; | |
| wgpu::Origin3D br = {3 * colorTarget.GetWidth() / 4, 3 * colorTarget.GetHeight() / 4}; | |
| std::array<utils::RGBA8, 4> expectedColors; | |
| uint8_t expectedAlpha; | |
| switch (format) { | |
| case wgpu::TextureFormat::RGB10A2Unorm: | |
| expectedColors = { | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kYellow, | |
| }; | |
| expectedAlpha = 0xAA; | |
| break; | |
| case wgpu::TextureFormat::RGBA8Unorm: | |
| case wgpu::TextureFormat::BGRA8Unorm: | |
| case wgpu::TextureFormat::RGBA16Float: | |
| expectedColors = { | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kBlue, | |
| utils::RGBA8::kYellow, | |
| }; | |
| expectedAlpha = 0x80; | |
| break; | |
| case wgpu::TextureFormat::RG16Float: | |
| case wgpu::TextureFormat::RG16Unorm: | |
| case wgpu::TextureFormat::RG8Unorm: | |
| expectedColors = { | |
| utils::RGBA8::kGreen, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kYellow, | |
| }; | |
| expectedAlpha = 0xFF; | |
| break; | |
| case wgpu::TextureFormat::R32Float: | |
| case wgpu::TextureFormat::R16Float: | |
| case wgpu::TextureFormat::R16Unorm: | |
| case wgpu::TextureFormat::R8Unorm: | |
| expectedColors = { | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kRed, | |
| utils::RGBA8::kBlack, | |
| utils::RGBA8::kRed, | |
| }; | |
| expectedAlpha = 0xFF; | |
| break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| expectedColors[0].a = expectedAlpha; | |
| expectedColors[1].a = expectedAlpha; | |
| expectedColors[2].a = expectedAlpha; | |
| expectedColors[3].a = expectedAlpha; | |
| EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[0], colorTarget, tl, {1, 1}); | |
| EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[1], colorTarget, bl, {1, 1}); | |
| EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[2], colorTarget, tr, {1, 1}); | |
| EXPECT_TEXTURE_EQ(deviceObj, &expectedColors[3], colorTarget, br, {1, 1}); | |
| } | |
| // Allow tests to be uninstantiated since it's possible no backends are available. | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SharedTextureMemoryNoFeatureTests); | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SharedTextureMemoryTests); | |
| wgpu::ResourceTable SharedTextureMemoryTests::MakeResourceTable( | |
| wgpu::Device& deviceObj, | |
| uint32_t size, | |
| std::vector<std::pair<uint32_t, wgpu::BindingResource>> resources) { | |
| wgpu::ResourceTableDescriptor desc; | |
| desc.size = size; | |
| wgpu::ResourceTable table = deviceObj.CreateResourceTable(&desc); | |
| for (auto& [slot, resource] : resources) { | |
| EXPECT_EQ(wgpu::Status::Success, table.Update(slot, &resource)); | |
| } | |
| return table; | |
| } | |
| wgpu::ResourceTable SharedTextureMemoryTests::MakeResourceTable( | |
| uint32_t size, | |
| std::vector<std::pair<uint32_t, wgpu::BindingResource>> resources) { | |
| return MakeResourceTable(device, size, resources); | |
| } | |
| // Make a command buffer that samples the contents of the input texture via a ResourceTable into an | |
| // RGBA8Unorm texture. | |
| std::pair<wgpu::CommandBuffer, wgpu::Texture> | |
| SharedTextureMemoryTests::MakeCheckBySamplingResourceTableCommandBuffer(wgpu::Device& deviceObj, | |
| wgpu::ResourceTable table, | |
| wgpu::Texture& texture) { | |
| wgpu::ShaderModule module = utils::CreateShaderModule(deviceObj, R"( | |
| enable chromium_experimental_resource_table; | |
| @vertex fn vert_main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { | |
| let pos = array( | |
| vec2( 1.0, 1.0), | |
| vec2( 1.0, -1.0), | |
| vec2(-1.0, -1.0), | |
| vec2( 1.0, 1.0), | |
| vec2(-1.0, -1.0), | |
| vec2(-1.0, 1.0), | |
| ); | |
| return vec4f(pos[VertexIndex], 0.0, 1.0); | |
| } | |
| @fragment fn frag_main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4f { | |
| let t = getResource<texture_2d<f32>>(0); | |
| return textureLoad(t, vec2u(coord_in.xy), 0); | |
| } | |
| )"); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = wgpu::TextureFormat::RGBA8Unorm; | |
| textureDesc.dimension = wgpu::TextureDimension::e2D; | |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; | |
| textureDesc.size = {texture.GetWidth(), texture.GetHeight(), 1}; | |
| textureDesc.label = "intermediate check texture"; | |
| wgpu::Texture colorTarget = deviceObj.CreateTexture(&textureDesc); | |
| utils::ComboRenderPipelineDescriptor pipelineDesc; | |
| pipelineDesc.vertex.module = module; | |
| pipelineDesc.cFragment.module = module; | |
| pipelineDesc.cTargets[0].format = colorTarget.GetFormat(); | |
| wgpu::RenderPipeline pipeline = deviceObj.CreateRenderPipeline(&pipelineDesc); | |
| wgpu::CommandEncoder encoder = deviceObj.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor passDescriptor({colorTarget.CreateView()}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.SetResourceTable(table); | |
| pass.SetPipeline(pipeline); | |
| pass.Draw(6); | |
| pass.End(); | |
| return {encoder.Finish(), colorTarget}; | |
| } | |
| wgpu::TextureView SharedTextureMemoryTests::CreateViewForTable(const wgpu::Texture& tex) { | |
| wgpu::TextureViewDescriptor desc; | |
| desc.usage = wgpu::TextureUsage::TextureBinding; | |
| return tex.CreateView(&desc); | |
| } | |
| // Test that the `table` has resources of `wgslType` in the `expected` slots. | |
| void SharedTextureMemoryTests::TestTableHasResource(wgpu::ResourceTable table, | |
| std::vector<bool> expected, | |
| std::string wgslType) { | |
| ASSERT_EQ(table.GetSize(), expected.size()); | |
| std::string shader = R"( | |
| enable chromium_experimental_resource_table; | |
| @group(0) @binding(0) var<storage, read_write> results : array<u32>; | |
| var<immediate> resourceCount : u32; | |
| @compute @workgroup_size(1) fn main() { | |
| for (var i = 0u; i < resourceCount; i++) { | |
| results[i] = u32(hasResource<)" + | |
| wgslType + R"(>(i)); | |
| } | |
| } | |
| )"; | |
| wgpu::ShaderModule module = utils::CreateShaderModule(device, shader.c_str()); | |
| wgpu::ComputePipelineDescriptor cpDesc; | |
| cpDesc.compute.module = module; | |
| wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&cpDesc); | |
| wgpu::BufferDescriptor bufDesc; | |
| bufDesc.size = expected.size() * sizeof(uint32_t); | |
| bufDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc; | |
| wgpu::Buffer resultBuffer = device.CreateBuffer(&bufDesc); | |
| wgpu::BindGroup bindGroup = | |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, resultBuffer}}); | |
| uint32_t count = static_cast<uint32_t>(expected.size()); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); | |
| pass.SetResourceTable(table); | |
| pass.SetPipeline(pipeline); | |
| pass.SetBindGroup(0, bindGroup); | |
| pass.SetImmediates(0, &count, sizeof(count)); | |
| pass.DispatchWorkgroups(1); | |
| pass.End(); | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| queue.Submit(1, &commands); | |
| std::vector<uint32_t> expectedU32(expected.size()); | |
| for (size_t i = 0; i < expected.size(); i++) { | |
| expectedU32[i] = expected[i] ? 1 : 0; | |
| } | |
| EXPECT_BUFFER_U32_RANGE_EQ(expectedU32.data(), resultBuffer, 0, expected.size()); | |
| } | |
| namespace { | |
| using testing::HasSubstr; | |
| using testing::MockCppCallback; | |
| template <typename T> | |
| T& AsNonConst(const T& rhs) { | |
| return const_cast<T&>(rhs); | |
| } | |
| // Test that creating shared texture memory without the required features is an error. | |
| // Using the memory thereafter produces errors. | |
| TEST_P(SharedTextureMemoryNoFeatureTests, CreationWithoutFeature) { | |
| // TODO(crbug.com/555876250): "Failed to create GBM buffer object" crashes the tests. | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsVulkan() && IsNvidia() && | |
| GetParam().mBackend->Name().find("dma buf") != std::string::npos); | |
| // Create external texture memories with an error filter. | |
| // We should see a message that the feature is not enabled. | |
| device.PushErrorScope(wgpu::ErrorFilter::Validation); | |
| const auto& memories = | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount); | |
| MockCppCallback<void (*)(wgpu::PopErrorScopeStatus, wgpu::ErrorType, wgpu::StringView)> | |
| popErrorScopeCallback; | |
| EXPECT_CALL(popErrorScopeCallback, | |
| Call(wgpu::PopErrorScopeStatus::Success, wgpu::ErrorType::Validation, | |
| SizedStringMatches(HasSubstr("is not enabled")))); | |
| device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents, popErrorScopeCallback.Callback()); | |
| for (wgpu::SharedTextureMemory memory : memories) { | |
| ASSERT_DEVICE_ERROR_MSG(wgpu::Texture texture = memory.CreateTexture(), | |
| HasSubstr("is invalid")); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is invalid")); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.EndAccess(texture, &endState), wgpu::Status::Success), | |
| HasSubstr("is invalid")); | |
| } | |
| } | |
| // Test that it is an error to import a shared texture memory with no chained struct. | |
| TEST_P(SharedTextureMemoryTests, ImportSharedTextureMemoryNoChain) { | |
| wgpu::SharedTextureMemoryDescriptor desc; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc), | |
| HasSubstr("chain")); | |
| } | |
| // Test that it is an error to import a shared fence with no chained struct. | |
| // Also test that ExportInfo reports an Undefined type for the error fence. | |
| TEST_P(SharedTextureMemoryTests, ImportSharedFenceNoChain) { | |
| wgpu::SharedFenceDescriptor desc; | |
| ASSERT_DEVICE_ERROR_MSG(wgpu::SharedFence fence = device.ImportSharedFence(&desc), | |
| HasSubstr("chain")); | |
| wgpu::SharedFenceExportInfo exportInfo; | |
| exportInfo.type = static_cast<wgpu::SharedFenceType>(1234); // should be overrwritten | |
| // Expect that exporting the fence info writes Undefined, and generates an error. | |
| ASSERT_DEVICE_ERROR(fence.ExportInfo(&exportInfo)); | |
| EXPECT_EQ(exportInfo.type, wgpu::SharedFenceType(0)); | |
| } | |
| // Test importing a shared texture memory when the device is destroyed | |
| TEST_P(SharedTextureMemoryTests, ImportSharedTextureMemoryDeviceDestroyed) { | |
| device.Destroy(); | |
| wgpu::SharedTextureMemory memory; | |
| if (GetParam().mBackend->Name().rfind("OpaqueFD", 0) == 0) { | |
| // The OpaqueFD backend for `CreateSharedTextureMemory` uses several | |
| // Vulkan device internals before and after the actual call to | |
| // ImportSharedTextureMemory. We can't easily make it import with | |
| // a destroyed device, so create the SharedTextureMemory with | |
| // an invalid descriptor instead. This still tests that an uncaptured | |
| // error is not generated on the import call when the device is lost. | |
| wgpu::SharedTextureMemoryDescriptor desc; | |
| memory = device.ImportSharedTextureMemory(&desc); | |
| } else { | |
| memory = GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| } | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| // That the begin access does not succeed since the device is destroyed. | |
| EXPECT_NE(memory.BeginAccess(memory.CreateTexture(), &beginDesc), wgpu::Status::Success); | |
| } | |
| // Test that SharedTextureMemory::IsDeviceLost() returns the expected value before and | |
| // after destroying the device. | |
| TEST_P(SharedTextureMemoryTests, CheckIsDeviceLostBeforeAndAfterDestroyingDevice) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| EXPECT_FALSE(memory.IsDeviceLost()); | |
| device.Destroy(); | |
| EXPECT_TRUE(memory.IsDeviceLost()); | |
| } | |
| // Test that SharedTextureMemory::IsDeviceLost() returns the expected value before and | |
| // after losing the device. | |
| TEST_P(SharedTextureMemoryTests, CheckIsDeviceLostBeforeAndAfterLosingDevice) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| EXPECT_FALSE(memory.IsDeviceLost()); | |
| LoseDeviceForTesting(device); | |
| EXPECT_TRUE(memory.IsDeviceLost()); | |
| } | |
| // Test importing a shared fence when the device is destroyed | |
| TEST_P(SharedTextureMemoryTests, ImportSharedFenceDeviceDestroyed) { | |
| // Create a shared texture memory and texture | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| // Begin access to use the texture | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success); | |
| // Use the texture so there is a fence to export on end access. | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| if (properties.usage & wgpu::TextureUsage::RenderAttachment) { | |
| UseInRenderPass(device, texture); | |
| } else if (!utils::IsMultiPlanarFormat(properties.format)) { | |
| if (properties.usage & wgpu::TextureUsage::CopySrc) { | |
| UseInCopy(device, texture); | |
| } else if (properties.usage & wgpu::TextureUsage::CopyDst) { | |
| wgpu::Extent3D writeSize = {1, 1, 1}; | |
| wgpu::TexelCopyTextureInfo dest = {}; | |
| dest.texture = texture; | |
| wgpu::TexelCopyBufferLayout dataLayout = {}; | |
| uint64_t data[2]; | |
| device.GetQueue().WriteTexture(&dest, &data, sizeof(data), &dataLayout, &writeSize); | |
| } | |
| } | |
| // End access to export a fence. | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| EXPECT_EQ(memory.EndAccess(texture, &endState), wgpu::Status::Success); | |
| // Destroy the device. | |
| device.Destroy(); | |
| // Import the shared fence to the destroyed device. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(device, endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| // Begin access should fail. | |
| EXPECT_NE(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success); | |
| } | |
| // Test calling GetProperties with an error memory. The properties are filled with 0/None/Undefined. | |
| TEST_P(SharedTextureMemoryTests, GetPropertiesErrorMemory) { | |
| wgpu::SharedTextureMemoryDescriptor desc; | |
| ASSERT_DEVICE_ERROR(wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc)); | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| EXPECT_EQ(properties.usage, wgpu::TextureUsage::None); | |
| EXPECT_EQ(properties.size.width, 0u); | |
| EXPECT_EQ(properties.size.height, 0u); | |
| EXPECT_EQ(properties.size.depthOrArrayLayers, 0u); | |
| EXPECT_EQ(properties.format, wgpu::TextureFormat::Undefined); | |
| } | |
| // Tests that a SharedTextureMemory supports expected texture usages. | |
| TEST_P(SharedTextureMemoryTests, TextureUsages) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| // CopySrc and TextureBinding should always be supported. | |
| // TODO(crbug.com/dawn/2262): TextureBinding support on D3D11/D3D12/Vulkan is actually | |
| // dependent on the flags passed to the underlying texture (the relevant | |
| // flag is currently always passed in the test context). Add tests where | |
| // the D3D/Vulkan texture is not created with the relevant flag. | |
| wgpu::TextureUsage expectedUsage = | |
| wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding; | |
| bool isSinglePlanar = !utils::IsMultiPlanarFormat(properties.format); | |
| if (isSinglePlanar || | |
| device.HasFeature(wgpu::FeatureName::MultiPlanarFormatExtendedUsages)) { | |
| expectedUsage |= wgpu::TextureUsage::CopyDst; | |
| } | |
| // TODO(crbug.com/dawn/2262): RenderAttachment support on D3D11/D3D12/Vulkan is | |
| // additionally dependent on the flags passed to the underlying | |
| // texture (the relevant flag is currently always passed in the test | |
| // context). Add tests where the D3D/Vulkan texture is not created with the | |
| // relevant flag. | |
| if ((isSinglePlanar || device.HasFeature(wgpu::FeatureName::MultiPlanarRenderTargets)) && | |
| utils::IsRenderableFormat(device, properties.format)) { | |
| expectedUsage |= wgpu::TextureUsage::RenderAttachment; | |
| } | |
| // TODO(crbug.com/dawn/2262): StorageBinding support on D3D11/D3D12/Vulkan is | |
| // additionally dependent on the flags passed to the underlying | |
| // texture (the relevant flag is currently always passed in the test | |
| // context). Add tests where the D3D/Vulkan texture is not created with the | |
| // relevant flag. | |
| #if !DAWN_PLATFORM_IS(ANDROID) | |
| if (isSinglePlanar && | |
| utils::TextureFormatSupportsStorageTexture(device, properties.format)) { | |
| expectedUsage |= wgpu::TextureUsage::StorageBinding; | |
| } | |
| #endif | |
| EXPECT_EQ(properties.usage, expectedUsage) << properties.format; | |
| } | |
| } | |
| // Test calling GetProperties with an invalid chained struct. An error is | |
| // generated, but the properties are still populated. | |
| TEST_P(SharedTextureMemoryTests, GetPropertiesInvalidChain) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::ChainedStructOut otherStruct; | |
| wgpu::SharedTextureMemoryProperties properties1; | |
| properties1.nextInChain = &otherStruct; | |
| ASSERT_DEVICE_ERROR(memory.GetProperties(&properties1)); | |
| wgpu::SharedTextureMemoryProperties properties2; | |
| memory.GetProperties(&properties2); | |
| EXPECT_EQ(properties1.usage, properties2.usage); | |
| EXPECT_EQ(properties1.size.width, properties2.size.width); | |
| EXPECT_EQ(properties1.size.height, properties2.size.height); | |
| EXPECT_EQ(properties1.size.depthOrArrayLayers, properties2.size.depthOrArrayLayers); | |
| EXPECT_EQ(properties1.format, properties2.format); | |
| } | |
| // Test that calling GetProperties with a chained | |
| // SharedTextureMemoryAHardwareBufferProperties struct will generate an error | |
| // unless the required feature is present. In either case, the base properties | |
| // should still be populated. | |
| TEST_P(SharedTextureMemoryTests, GetPropertiesAHardwareBufferPropertiesRequiresAHBFeature) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::SharedTextureMemoryAHardwareBufferProperties aHBProps; | |
| wgpu::SharedTextureMemoryProperties properties1; | |
| properties1.nextInChain = &aHBProps; | |
| if (device.HasFeature(wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer)) { | |
| memory.GetProperties(&properties1); | |
| } else { | |
| ASSERT_DEVICE_ERROR(memory.GetProperties(&properties1)); | |
| } | |
| wgpu::SharedTextureMemoryProperties properties2; | |
| memory.GetProperties(&properties2); | |
| EXPECT_EQ(properties1.usage, properties2.usage); | |
| EXPECT_EQ(properties1.size.width, properties2.size.width); | |
| EXPECT_EQ(properties1.size.height, properties2.size.height); | |
| EXPECT_EQ(properties1.size.depthOrArrayLayers, properties2.size.depthOrArrayLayers); | |
| EXPECT_EQ(properties1.format, properties2.format); | |
| } | |
| // Test that texture usages must be a subset of the shared texture memory's usage. | |
| TEST_P(SharedTextureMemoryTests, UsageValidation) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| // SharedTextureMemory should never support TransientAttachment. | |
| ASSERT_EQ(properties.usage & wgpu::TextureUsage::TransientAttachment, 0); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format; | |
| textureDesc.size = properties.size; | |
| for (wgpu::TextureUsage usage : { | |
| wgpu::TextureUsage::CopySrc, | |
| wgpu::TextureUsage::CopyDst, | |
| wgpu::TextureUsage::TextureBinding, | |
| wgpu::TextureUsage::StorageBinding, | |
| wgpu::TextureUsage::RenderAttachment, | |
| }) { | |
| textureDesc.usage = usage; | |
| // `usage` is valid if it is in the shared texture memory properties. | |
| if (usage & properties.usage) { | |
| wgpu::Texture t = memory.CreateTexture(&textureDesc); | |
| EXPECT_EQ(t.GetUsage(), usage); | |
| } else { | |
| ASSERT_DEVICE_ERROR(memory.CreateTexture(&textureDesc)); | |
| } | |
| } | |
| } | |
| } | |
| // Test that it is an error if the texture format doesn't match the shared texture memory. | |
| TEST_P(SharedTextureMemoryTests, FormatValidation) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format != wgpu::TextureFormat::RGBA8Unorm | |
| ? wgpu::TextureFormat::RGBA8Unorm | |
| : wgpu::TextureFormat::RGBA16Float; | |
| textureDesc.size = properties.size; | |
| textureDesc.usage = properties.usage; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), | |
| HasSubstr("doesn't match descriptor format")); | |
| } | |
| } | |
| // Test that it is an error if the texture size doesn't match the shared texture memory. | |
| TEST_P(SharedTextureMemoryTests, SizeValidation) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format; | |
| textureDesc.usage = properties.usage; | |
| textureDesc.size = {properties.size.width + 1, properties.size.height, | |
| properties.size.depthOrArrayLayers}; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), | |
| HasSubstr("doesn't match descriptor size")); | |
| textureDesc.size = {properties.size.width, properties.size.height + 1, | |
| properties.size.depthOrArrayLayers}; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), | |
| HasSubstr("doesn't match descriptor size")); | |
| textureDesc.size = {properties.size.width, properties.size.height, | |
| properties.size.depthOrArrayLayers + 1}; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), | |
| HasSubstr("doesn't match descriptor size")); | |
| } | |
| } | |
| // Test that it is an error if the texture mip level count is not 1. | |
| TEST_P(SharedTextureMemoryTests, MipLevelValidation) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format; | |
| textureDesc.usage = properties.usage; | |
| textureDesc.size = properties.size; | |
| textureDesc.mipLevelCount = 1u; | |
| memory.CreateTexture(&textureDesc); | |
| textureDesc.mipLevelCount = 2u; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), HasSubstr("(2) is not 1")); | |
| } | |
| } | |
| // Test that it is an error if the texture sample count is not 1. | |
| TEST_P(SharedTextureMemoryTests, SampleCountValidation) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format; | |
| textureDesc.usage = properties.usage; | |
| textureDesc.size = properties.size; | |
| textureDesc.sampleCount = 1u; | |
| memory.CreateTexture(&textureDesc); | |
| textureDesc.sampleCount = 4u; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), HasSubstr("(4) is not 1")); | |
| } | |
| } | |
| // Test that it is an error if the texture dimension is not 2D. | |
| TEST_P(SharedTextureMemoryTests, DimensionValidation) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format; | |
| textureDesc.usage = properties.usage; | |
| textureDesc.size = properties.size; | |
| textureDesc.dimension = wgpu::TextureDimension::e1D; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), | |
| HasSubstr("is not TextureDimension::e2D")); | |
| textureDesc.dimension = wgpu::TextureDimension::e3D; | |
| ASSERT_DEVICE_ERROR_MSG(memory.CreateTexture(&textureDesc), | |
| HasSubstr("is not TextureDimension::e2D")); | |
| } | |
| } | |
| // Test that it is an error to call BeginAccess twice in a row on the same texture and memory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccess) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| // It should be an error to BeginAccess twice in a row. | |
| EXPECT_EQ(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success); | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is already used to access")); | |
| } | |
| // Test that it is an error to call BeginAccess with fenceCount != signaledValueCount | |
| TEST_P(SharedTextureMemoryTests, FenceCountMatchesSignaledValueCount) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| uint64_t signalValue = 0; | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| beginDesc.fenceCount = 0; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| // Error case, fenceCount != signaledValueCount | |
| beginDesc.signaledValueCount = 1; | |
| beginDesc.signaledValues = &signalValue; | |
| ASSERT_DEVICE_ERROR(EXPECT_NE(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success)); | |
| // Success case, fenceCount == signaledValueCount | |
| beginDesc.signaledValueCount = 0; | |
| EXPECT_EQ(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success); | |
| } | |
| // Test that it is an error to call BeginAccess concurrently on a write texture | |
| // followed by a read texture on a single SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesWriteRead) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture writeTexture = CreateWriteTexture(memory); | |
| wgpu::Texture readTexture = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(writeTexture, &beginDesc), wgpu::Status::Success); | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(readTexture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for writing")); | |
| } | |
| // Test that it is an error to call BeginAccess concurrently on a write texture | |
| // followed by a read texture on a single SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesWriteConcurrentRead) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture writeTexture = CreateWriteTexture(memory); | |
| wgpu::Texture readTexture = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(writeTexture, &beginDesc), wgpu::Status::Success); | |
| beginDesc.concurrentRead = true; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(readTexture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for writing")); | |
| } | |
| // Test that it is an error to call BeginAccess concurrently on a read texture | |
| // followed by a write texture on a single SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesReadWrite) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture writeTexture = CreateWriteTexture(memory); | |
| wgpu::Texture readTexture = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(readTexture, &beginDesc), wgpu::Status::Success); | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(writeTexture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for exclusive reading")); | |
| } | |
| // Test that it is an error to call BeginAccess concurrently on a read texture | |
| // followed by a write texture on a single SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesConcurrentReadWrite) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture writeTexture = CreateWriteTexture(memory); | |
| wgpu::Texture readTexture = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = true; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(readTexture, &beginDesc), wgpu::Status::Success); | |
| beginDesc.concurrentRead = false; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(writeTexture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for reading.")); | |
| } | |
| // Test that it is an error to call BeginAccess concurrently on two write textures on a single | |
| // SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesWriteWrite) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture writeTexture1 = CreateWriteTexture(memory); | |
| wgpu::Texture writeTexture2 = CreateWriteTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(writeTexture1, &beginDesc), wgpu::Status::Success); | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(writeTexture2, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for writing")); | |
| } | |
| // Test that it is valid to call BeginAccess concurrently on two read textures on a single | |
| // SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesReadRead) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture readTexture1 = CreateReadTexture(memory); | |
| wgpu::Texture readTexture2 = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(readTexture1, &beginDesc), wgpu::Status::Success); | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(readTexture2, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for exclusive reading")); | |
| } | |
| // Test that it is valid to call BeginAccess concurrently on two read textures on a single | |
| // SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesConcurrentReadConcurrentRead) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture readTexture1 = CreateReadTexture(memory); | |
| wgpu::Texture readTexture2 = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = true; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(readTexture1, &beginDesc), wgpu::Status::Success); | |
| EXPECT_EQ(memory.BeginAccess(readTexture2, &beginDesc), wgpu::Status::Success); | |
| wgpu::SharedTextureMemoryEndAccessState endState1 = {}; | |
| EXPECT_EQ(memory.EndAccess(readTexture1, &endState1), wgpu::Status::Success); | |
| wgpu::SharedTextureMemoryEndAccessState endState2 = {}; | |
| EXPECT_EQ(memory.EndAccess(readTexture2, &endState2), wgpu::Status::Success); | |
| } | |
| // Test that it is valid to call BeginAccess concurrently on read textures on a single | |
| // SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesConcurrentReadRead) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture readTexture1 = CreateReadTexture(memory); | |
| wgpu::Texture readTexture2 = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| beginDesc.concurrentRead = true; | |
| EXPECT_EQ(memory.BeginAccess(readTexture1, &beginDesc), wgpu::Status::Success); | |
| beginDesc.concurrentRead = false; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(readTexture2, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for reading.")); | |
| } | |
| // Test that it is valid to call BeginAccess concurrently on read textures on a single | |
| // SharedTextureMemory. | |
| TEST_P(SharedTextureMemoryTests, DoubleBeginAccessSeparateTexturesReadConcurrentRead) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture readTexture1 = CreateReadTexture(memory); | |
| wgpu::Texture readTexture2 = CreateReadTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| beginDesc.concurrentRead = false; | |
| EXPECT_EQ(memory.BeginAccess(readTexture1, &beginDesc), wgpu::Status::Success); | |
| beginDesc.concurrentRead = true; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(readTexture2, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("is currently accessed for exclusive reading.")); | |
| } | |
| // Test that it is valid to call BeginAccess concurrently on write textures with concurrentRead is | |
| // true. | |
| TEST_P(SharedTextureMemoryTests, ConcurrentWrite) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture writeTexture = CreateWriteTexture(memory); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| beginDesc.concurrentRead = true; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| EXPECT_NE(memory.BeginAccess(writeTexture, &beginDesc), wgpu::Status::Success), | |
| HasSubstr("Concurrent reading read-write")); | |
| } | |
| // Test that it is an error to call EndAccess twice in a row on the same memory. | |
| TEST_P(SharedTextureMemoryTests, DoubleEndAccess) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(texture, &beginDesc), wgpu::Status::Success); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| EXPECT_EQ(memory.EndAccess(texture, &endState), wgpu::Status::Success); | |
| // Invalid to end access a second time. | |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_NE(memory.EndAccess(texture, &endState), wgpu::Status::Success), | |
| HasSubstr("is not currently being accessed")); | |
| } | |
| // Test that it is an error to call EndAccess on a texture that was not the one BeginAccess was | |
| // called on. | |
| TEST_P(SharedTextureMemoryTests, BeginThenEndOnDifferentTexture) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture1 = memory.CreateTexture(); | |
| wgpu::Texture texture2 = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| EXPECT_EQ(memory.BeginAccess(texture1, &beginDesc), wgpu::Status::Success); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_NE(memory.EndAccess(texture2, &endState), wgpu::Status::Success), | |
| HasSubstr("is not currently being accessed")); | |
| } | |
| // Test that it is an error to call EndAccess without a preceding BeginAccess. | |
| TEST_P(SharedTextureMemoryTests, EndAccessWithoutBegin) { | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_NE(memory.EndAccess(texture, &endState), wgpu::Status::Success), | |
| HasSubstr("is not currently being accessed")); | |
| } | |
| // Test that it is an error to use the texture on the queue without a preceding BeginAccess. | |
| TEST_P(SharedTextureMemoryTests, UseWithoutBegin) { | |
| DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("skip_validation")); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| if (properties.usage & wgpu::TextureUsage::RenderAttachment) { | |
| ASSERT_DEVICE_ERROR_MSG(UseInRenderPass(device, texture), | |
| HasSubstr("without current access")); | |
| } else if (!utils::IsMultiPlanarFormat(properties.format)) { | |
| if (properties.usage & wgpu::TextureUsage::CopySrc) { | |
| ASSERT_DEVICE_ERROR_MSG(UseInCopy(device, texture), | |
| HasSubstr("without current access")); | |
| } | |
| if (properties.usage & wgpu::TextureUsage::CopyDst) { | |
| wgpu::Extent3D writeSize = {1, 1, 1}; | |
| wgpu::TexelCopyTextureInfo dest = {}; | |
| dest.texture = texture; | |
| wgpu::TexelCopyBufferLayout dataLayout = {}; | |
| uint64_t data[2]; | |
| ASSERT_DEVICE_ERROR_MSG( | |
| device.GetQueue().WriteTexture(&dest, &data, sizeof(data), &dataLayout, &writeSize), | |
| HasSubstr("without current access")); | |
| } | |
| } | |
| } | |
| // Test that it is valid (does not crash) if the memory is dropped while a texture access has begun. | |
| TEST_P(SharedTextureMemoryTests, TextureAccessOutlivesMemory) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| // NOTE: UseInRenderPass()/UseInCopy() do not currently support multiplanar | |
| // formats. | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSinglePlanarSharedTextureMemories(device, | |
| GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| // Begin access on a texture, and drop the memory. | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| memory.BeginAccess(texture, &beginDesc); | |
| memory = nullptr; | |
| // Use the texture on the GPU; it should not crash. | |
| if (properties.usage & wgpu::TextureUsage::RenderAttachment) { | |
| UseInRenderPass(device, texture); | |
| } else { | |
| DAWN_ASSERT(properties.usage & wgpu::TextureUsage::CopySrc); | |
| UseInCopy(device, texture); | |
| } | |
| } | |
| } | |
| // Test that if the texture is uninitialized, it is cleared on first use. | |
| TEST_P(SharedTextureMemoryTests, UninitializedTextureIsCleared) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| // Skipped for multiplanar formats because those must be initialized on import. | |
| // We also need render attachment usage to initially populate the texture. | |
| if (utils::IsMultiPlanarFormat(properties.format) || | |
| !(properties.usage & wgpu::TextureUsage::RenderAttachment)) { | |
| continue; | |
| } | |
| // Helper function to test that unintialized textures are lazy cleared upon use. | |
| auto DoTest = [&](auto UseTexture) { | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| // First fill the texture with data, so we can check that using it uninitialized | |
| // makes it black. | |
| { | |
| wgpu::CommandBuffer commandBuffer = | |
| MakeFourColorsClearCommandBuffer(device, texture); | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| memory.BeginAccess(texture, &beginDesc); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| memory.EndAccess(texture, &endState); | |
| } | |
| // Now, BeginAccess on the texture as uninitialized. | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = endState.fences; | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memory.BeginAccess(texture, &beginDesc); | |
| // Use the texture on the GPU which should lazy clear it. | |
| UseTexture(texture); | |
| AsNonConst(endState.initialized) = false; // should be overrwritten | |
| memory.EndAccess(texture, &endState); | |
| // The texture should be initialized now. | |
| EXPECT_TRUE(endState.initialized); | |
| // Begin access again - and check that the texture contents are zero. | |
| { | |
| auto [commandBuffer, colorTarget] = | |
| MakeCheckBySamplingCommandBuffer(device, texture); | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = endState.fences; | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memory.BeginAccess(texture, &beginDesc); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| memory.EndAccess(texture, &endState); | |
| uint8_t alphaVal; | |
| switch (properties.format) { | |
| case wgpu::TextureFormat::RGBA8Unorm: | |
| case wgpu::TextureFormat::BGRA8Unorm: | |
| case wgpu::TextureFormat::RGB10A2Unorm: | |
| case wgpu::TextureFormat::RGBA16Float: | |
| alphaVal = 0; | |
| break; | |
| default: | |
| // The test checks by sampling. Formats that don't | |
| // have alpha return 1 for alpha when sampled in a shader. | |
| alphaVal = 255; | |
| break; | |
| } | |
| std::vector<utils::RGBA8> expected( | |
| static_cast<size_t>(texture.GetWidth()) * texture.GetHeight(), | |
| utils::RGBA8{0, 0, 0, alphaVal}); | |
| EXPECT_TEXTURE_EQ(device, expected.data(), colorTarget, {0, 0}, | |
| {colorTarget.GetWidth(), colorTarget.GetHeight()}) | |
| << "format: " << static_cast<uint32_t>(properties.format); | |
| } | |
| }; | |
| // Test that using a texture in a render pass lazy clears it. | |
| if (properties.usage & wgpu::TextureUsage::RenderAttachment) { | |
| DoTest([&](wgpu::Texture& texture) { UseInRenderPass(device, texture); }); | |
| } | |
| // Teset that using a texture in a copy lazy clears it. | |
| if (properties.usage & wgpu::TextureUsage::CopySrc) { | |
| DoTest([&](wgpu::Texture& texture) { UseInCopy(device, texture); }); | |
| } | |
| } | |
| } | |
| // Test that if the texture is uninitialized, EndAccess writes the state out as uninitialized. | |
| TEST_P(SharedTextureMemoryTests, UninitializedOnEndAccess) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| // It is not possible to run these tests for multiplanar formats for | |
| // multiple reasons: | |
| // * Test basic begin+end access exports the state as uninitialized | |
| // if it starts as uninitialized. Multiplanar formats must be initialized on import. | |
| // * RenderAttachment gets a TextureView from the texture. This has a | |
| // superficial problem and a deep problem: The superficial problem is that | |
| // the plane would need to be passed for multiplanar formats, and the deep | |
| // problem is that the tests fail to create valid backing textures for some multiplanar | |
| // formats (e.g., on Apple), which results in a crash when accessing plane | |
| // 0. | |
| // TODO(crbug.com/dawn/2263): Fix this and change the below to | |
| // CreateSharedTextureMemories(). | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSinglePlanarSharedTextureMemories(device, | |
| GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| { | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| memory.BeginAccess(texture, &beginDesc); | |
| AsNonConst(endState.initialized) = true; // should be overrwritten | |
| memory.EndAccess(texture, &endState); | |
| EXPECT_FALSE(endState.initialized); | |
| } | |
| // Test begin access as initialized, then uninitializing the texture | |
| // exports the state as uninitialized on end access. Requires render | |
| // attachment usage to uninitialize. | |
| if (properties.usage & wgpu::TextureUsage::RenderAttachment) { | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memory.BeginAccess(texture, &beginDesc); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = 0; | |
| desc.arrayLayerCount = 1; | |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView(&desc)}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Discard; | |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDescriptor); | |
| pass.End(); | |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| endState = {}; | |
| AsNonConst(endState.initialized) = true; // should be overrwritten | |
| memory.EndAccess(texture, &endState); | |
| EXPECT_FALSE(endState.initialized); | |
| } | |
| } | |
| } | |
| // Test copying to texture memory on one device, then sampling it using another device. | |
| TEST_P(SharedTextureMemoryTests, CopyToTextureThenSample) { | |
| // TODO(crbug.com/40238674): Fails on Pixel 10 gles. | |
| DAWN_SUPPRESS_TEST_IF(IsImgTec()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::TextureBinding, GetParam().mLayerCount)) { | |
| wgpu::Texture texture = memories[0].CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(texture, &beginDesc); | |
| // Create a texture of the same size to use as the source content. | |
| wgpu::TextureDescriptor texDesc; | |
| texDesc.format = texture.GetFormat(); | |
| texDesc.size = {texture.GetWidth(), texture.GetHeight()}; | |
| texDesc.usage = | |
| texture.GetUsage() | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; | |
| wgpu::Texture srcTex = devices[0].CreateTexture(&texDesc); | |
| // Populate the source texture. | |
| wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], srcTex); | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| // Copy from the source texture into `texture`. | |
| { | |
| wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder(); | |
| auto src = utils::CreateTexelCopyTextureInfo(srcTex); | |
| auto dst = utils::CreateTexelCopyTextureInfo(texture); | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| dst.origin.z = layer; | |
| encoder.CopyTextureToTexture(&src, &dst, &texDesc.size); | |
| } | |
| commandBuffer = encoder.Finish(); | |
| } | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(texture, &endState); | |
| // Sample from the texture | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| texture = memories[1].CreateTexture(); | |
| memories[1].BeginAccess(texture, &beginDesc); | |
| wgpu::Texture colorTarget; | |
| std::tie(commandBuffer, colorTarget) = | |
| MakeCheckBySamplingCommandBuffer(devices[1], texture); | |
| devices[1].GetQueue().Submit(1, &commandBuffer); | |
| memories[1].EndAccess(texture, &endState); | |
| CheckFourColors(devices[1], texture.GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test that BeginAccess without waiting on anything, followed by EndAccess | |
| // without using the texture, does not export any fences. | |
| TEST_P(SharedTextureMemoryTests, EndWithoutUse) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (const auto& memory : | |
| GetParam().mBackend->CreateSharedTextureMemories(device, GetParam().mLayerCount)) { | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(texture, &beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(texture, &endState); | |
| EXPECT_EQ(endState.fenceCount, 0u); | |
| EXPECT_EQ(endState.signaledValueCount, 0u); | |
| } | |
| } | |
| // Test that BeginAccess, waiting on previous work, followed by EndAccess when the | |
| // texture isn't used at all, doesn't export any new fences from Dawn. It exports the | |
| // old fences. | |
| // If concurrent read is supported, use two read textures. The first EndAccess should | |
| // see no fences. The second should then export all the unacquired fences. | |
| TEST_P(SharedTextureMemoryTests, BeginEndWithoutUse) { | |
| // TODO(crbug.com/40238674): Fails on Pixel 10 gles. | |
| DAWN_SUPPRESS_TEST_IF(IsImgTec()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::TextureBinding, GetParam().mLayerCount)) { | |
| wgpu::Texture texture = memories[0].CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(texture, &beginDesc); | |
| // Create a texture of the same size to use as the source content. | |
| wgpu::TextureDescriptor texDesc; | |
| texDesc.format = texture.GetFormat(); | |
| texDesc.size = {texture.GetWidth(), texture.GetHeight()}; | |
| texDesc.usage = | |
| texture.GetUsage() | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; | |
| wgpu::Texture srcTex = devices[0].CreateTexture(&texDesc); | |
| // Populate the source texture. | |
| wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], srcTex); | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| // Copy from the source texture into `texture`. | |
| { | |
| wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder(); | |
| auto src = utils::CreateTexelCopyTextureInfo(srcTex); | |
| auto dst = utils::CreateTexelCopyTextureInfo(texture); | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| dst.origin.z = layer; | |
| encoder.CopyTextureToTexture(&src, &dst, &texDesc.size); | |
| } | |
| commandBuffer = encoder.Finish(); | |
| } | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(texture, &endState); | |
| // Import fences and texture to the other device. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| // Do concurrent read if the backend supports it, and not Vulkan. | |
| // Note that here, the "backend" means the handle type. So on Vulkan, sync fds do support | |
| // concurrent reads on different devices. But, in Dawn's Vulkan backend within a single | |
| // device, support is not implemented yet. | |
| beginDesc.concurrentRead = GetParam().mBackend->SupportsConcurrentRead() && !IsVulkan(); | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| texDesc.size = {texture.GetWidth(), texture.GetHeight(), texture.GetDepthOrArrayLayers()}; | |
| texDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| texture = memories[1].CreateTexture(&texDesc); | |
| memories[1].BeginAccess(texture, &beginDesc); | |
| // Prepare to sample the texture without submit, and end access. | |
| wgpu::Texture colorTarget; | |
| std::tie(commandBuffer, colorTarget) = | |
| MakeCheckBySamplingCommandBuffer(devices[1], texture); | |
| if (beginDesc.concurrentRead) { | |
| // If concurrent read, make another texture, and begin+end access without using it. | |
| wgpu::Texture noopTexture = memories[1].CreateTexture(&texDesc); | |
| memories[1].BeginAccess(noopTexture, &beginDesc); | |
| memories[1].EndAccess(noopTexture, &endState); | |
| EXPECT_EQ(endState.fenceCount, 0u); | |
| EXPECT_EQ(endState.signaledValueCount, 0u); | |
| } | |
| memories[1].EndAccess(texture, &endState); | |
| // All of the fences should be identical. | |
| EXPECT_EQ(endState.fenceCount, sharedFences.size()); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| EXPECT_NE(std::find_if(sharedFences.begin(), sharedFences.end(), | |
| [&](const auto& fence) { | |
| return fence.Get() == endState.fences[i].Get(); | |
| }), | |
| sharedFences.end()); | |
| } | |
| } | |
| } | |
| // Test copying to texture memory on one device, then sampling it using another device. | |
| TEST_P(SharedTextureMemoryTests, CopyToTextureThenSample2DArray) { | |
| if (GetParam().mLayerCount != 2) { | |
| return; | |
| } | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::TextureBinding, GetParam().mLayerCount)) { | |
| wgpu::Texture texture = memories[0].CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(texture, &beginDesc); | |
| // Create a texture of the same size to use as the source content. | |
| wgpu::TextureDescriptor texDesc; | |
| texDesc.format = texture.GetFormat(); | |
| texDesc.size = {texture.GetWidth(), texture.GetHeight()}; | |
| texDesc.usage = | |
| texture.GetUsage() | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment; | |
| wgpu::Texture srcTex = devices[0].CreateTexture(&texDesc); | |
| // Populate the source texture. | |
| wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], srcTex); | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| // Copy from the source texture into `texture`. | |
| { | |
| wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder(); | |
| auto src = utils::CreateTexelCopyTextureInfo(srcTex); | |
| auto dst = utils::CreateTexelCopyTextureInfo(texture); | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| dst.origin.z = layer; | |
| encoder.CopyTextureToTexture(&src, &dst, &texDesc.size); | |
| } | |
| commandBuffer = encoder.Finish(); | |
| } | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(texture, &endState); | |
| // Sample from the texture | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| texture = memories[1].CreateTexture(); | |
| memories[1].BeginAccess(texture, &beginDesc); | |
| wgpu::Texture colorTarget; | |
| std::tie(commandBuffer, colorTarget) = | |
| MakeCheckBySamplingTexture2DArrayCommandBuffer(devices[1], texture); | |
| devices[1].GetQueue().Submit(1, &commandBuffer); | |
| memories[1].EndAccess(texture, &endState); | |
| CheckFourColors(devices[1], texture.GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test rendering to a texture memory on one device, then sampling it using another device. | |
| // Encode the commands after performing BeginAccess. | |
| TEST_P(SharedTextureMemoryTests, RenderThenSampleEncodeAfterBeginAccess) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| wgpu::Texture texture = memories[0].CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(texture, &beginDesc); | |
| // Clear the texture | |
| wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(devices[0], texture); | |
| devices[0].GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(texture, &endState); | |
| // Sample from the texture | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| texture = memories[1].CreateTexture(); | |
| memories[1].BeginAccess(texture, &beginDesc); | |
| wgpu::Texture colorTarget; | |
| std::tie(commandBuffer, colorTarget) = | |
| MakeCheckBySamplingCommandBuffer(devices[1], texture); | |
| devices[1].GetQueue().Submit(1, &commandBuffer); | |
| memories[1].EndAccess(texture, &endState); | |
| CheckFourColors(devices[1], texture.GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test rendering to a texture memory on one device, then sampling it using another device. | |
| // Encode the commands before performing BeginAccess (the access is only held during) QueueSubmit. | |
| TEST_P(SharedTextureMemoryTests, RenderThenSampleEncodeBeforeBeginAccess) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| // Create two textures from each memory. | |
| wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()}; | |
| // Make two command buffers, one that clears the texture, another that samples. | |
| wgpu::CommandBuffer commandBuffer0 = | |
| MakeFourColorsClearCommandBuffer(devices[0], textures[0]); | |
| auto [commandBuffer1, colorTarget] = | |
| MakeCheckBySamplingCommandBuffer(devices[1], textures[1]); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(textures[0], &beginDesc); | |
| devices[0].GetQueue().Submit(1, &commandBuffer0); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(textures[0], &endState); | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memories[1].BeginAccess(textures[1], &beginDesc); | |
| devices[1].GetQueue().Submit(1, &commandBuffer1); | |
| memories[1].EndAccess(textures[1], &endState); | |
| CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test rendering to a texture memory, then sampling it on the same device from a single read-only | |
| // texture that is reused across several BeginAccess/EndAccess cycles. Regression test for the | |
| // Vulkan backend, where SharedTexture::SetPendingAcquire left mLastExternalState equal to | |
| // mExternalState. The second access of a read-only texture then skipped its acquire barrier and | |
| // transitioned the image to VK_IMAGE_LAYOUT_UNDEFINED. | |
| TEST_P(SharedTextureMemoryTests, RenderThenSampleSameReadOnlyTextureAcrossAccesses) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| {device}, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| auto memory = memories[0]; | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| // One texture writes the contents. A second, read-only texture is reused for every read. | |
| wgpu::Texture writeTexture = memory.CreateTexture(); | |
| wgpu::TextureDescriptor readTextureDesc = {}; | |
| readTextureDesc.format = properties.format; | |
| readTextureDesc.size = properties.size; | |
| readTextureDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| readTextureDesc.label = "read texture"; | |
| wgpu::Texture readTexture = memory.CreateTexture(&readTextureDesc); | |
| // Clear the texture. | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(writeTexture, &beginDesc); | |
| wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(device, writeTexture); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(writeTexture, &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| // Sample from the same read-only texture in several consecutive accesses. The second | |
| // access is the one that previously skipped the acquire barrier on Vulkan. | |
| constexpr int kAccessCount = 3; | |
| for (int i = 0; i < kAccessCount; ++i) { | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t j = 0; j < endState.fenceCount; ++j) { | |
| sharedFences[j] = utils::ImportFenceTo(device, endState.fences[j]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memory.BeginAccess(readTexture, &beginDesc); | |
| wgpu::Texture colorTarget; | |
| std::tie(commandBuffer, colorTarget) = | |
| MakeCheckBySamplingCommandBuffer(device, readTexture); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| memory.EndAccess(readTexture, &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| CheckFourColors(device, readTexture.GetFormat(), colorTarget); | |
| } | |
| } | |
| } | |
| // Test that binding an acquired SharedTexture in a BindGroup whose layout entry has | |
| // wgpu::ShaderStage::None visibility is a no-op for the texture and does not interfere with | |
| // acquiring and sampling the texture in a subsequent pass. On Vulkan, this ensures | |
| // TransitionUsageForPassImpl does not call TweakTransition when no barrier is emitted. | |
| TEST_P(SharedTextureMemoryTests, BoundWithShaderStageNoneBeforeSampling) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| wgpu::ShaderModule noopModule = utils::CreateShaderModule(device, R"( | |
| @vertex fn vert_main() -> @builtin(position) vec4f { | |
| return vec4f(0.0, 0.0, 0.0, 1.0); | |
| } | |
| @fragment fn frag_main() -> @location(0) vec4f { | |
| return vec4f(0.0, 0.0, 0.0, 1.0); | |
| } | |
| )"); | |
| wgpu::BindGroupLayout noneBgl = utils::MakeBindGroupLayout( | |
| device, {{0, wgpu::ShaderStage::None, wgpu::TextureSampleType::UnfilterableFloat}}); | |
| utils::ComboRenderPipelineDescriptor noopPipelineDesc; | |
| noopPipelineDesc.layout = utils::MakePipelineLayout(device, {noneBgl}); | |
| noopPipelineDesc.vertex.module = noopModule; | |
| noopPipelineDesc.cFragment.module = noopModule; | |
| noopPipelineDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm; | |
| wgpu::RenderPipeline noopPipeline = device.CreateRenderPipeline(&noopPipelineDesc); | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| {device}, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| auto memory = memories[0]; | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::Texture writeTexture = memory.CreateTexture(); | |
| wgpu::TextureDescriptor readTextureDesc = {}; | |
| readTextureDesc.format = properties.format; | |
| readTextureDesc.size = properties.size; | |
| readTextureDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| readTextureDesc.label = "read texture"; | |
| wgpu::Texture readTexture = memory.CreateTexture(&readTextureDesc); | |
| // Clear the texture. | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(writeTexture, &beginDesc); | |
| wgpu::CommandBuffer commandBuffer = MakeFourColorsClearCommandBuffer(device, writeTexture); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(writeTexture, &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| // Begin access on readTexture, bind it in a pass with ShaderStage::None visibility, then | |
| // sample it in a subsequent pass before ending access. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t j = 0; j < endState.fenceCount; ++j) { | |
| sharedFences[j] = utils::ImportFenceTo(device, endState.fences[j]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memory.BeginAccess(readTexture, &beginDesc); | |
| wgpu::TextureViewDescriptor viewDesc; | |
| viewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| viewDesc.baseArrayLayer = 0; | |
| viewDesc.arrayLayerCount = 1; | |
| wgpu::BindGroup noneBindGroup = | |
| utils::MakeBindGroup(device, noneBgl, {{0, readTexture.CreateView(&viewDesc)}}); | |
| utils::BasicRenderPass placeholderRenderPass = utils::CreateBasicRenderPass(device, 1, 1); | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::RenderPassEncoder pass = | |
| encoder.BeginRenderPass(&placeholderRenderPass.renderPassInfo); | |
| pass.SetPipeline(noopPipeline); | |
| pass.SetBindGroup(0, noneBindGroup); | |
| pass.Draw(1); | |
| pass.End(); | |
| wgpu::CommandBuffer noopCommandBuffer = encoder.Finish(); | |
| wgpu::Texture colorTarget; | |
| std::tie(commandBuffer, colorTarget) = | |
| MakeCheckBySamplingCommandBuffer(device, readTexture); | |
| wgpu::CommandBuffer commands[] = {noopCommandBuffer, commandBuffer}; | |
| device.GetQueue().Submit(2, commands); | |
| memory.EndAccess(readTexture, &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| CheckFourColors(device, readTexture.GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test copying from the same SharedTexture to a buffer twice in a single command buffer. The | |
| // first copy acquires the texture and transitions it to CopySrc, and the second copy reuses the | |
| // read-only CopySrc usage without needing a barrier. | |
| TEST_P(SharedTextureMemoryTests, CopyTextureToBufferTwiceInSameCommandBuffer) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| for (wgpu::SharedTextureMemory memory : | |
| GetParam().mBackend->CreateSinglePlanarSharedTextureMemories(device, | |
| GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| if (!(properties.usage & wgpu::TextureUsage::CopySrc)) { | |
| continue; | |
| } | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(texture, &beginDesc); | |
| wgpu::BufferDescriptor bufferDesc = {}; | |
| bufferDesc.size = 128; | |
| bufferDesc.usage = wgpu::BufferUsage::CopyDst; | |
| wgpu::Buffer destinationBuffer = device.CreateBuffer(&bufferDesc); | |
| wgpu::TexelCopyTextureInfo source = {}; | |
| source.texture = texture; | |
| wgpu::TexelCopyBufferInfo destination = {}; | |
| destination.buffer = destinationBuffer; | |
| wgpu::Extent3D size = {1, 1, 1}; | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| encoder.CopyTextureToBuffer(&source, &destination, &size); | |
| encoder.CopyTextureToBuffer(&source, &destination, &size); | |
| wgpu::CommandBuffer commandBuffer = encoder.Finish(); | |
| device.GetQueue().Submit(1, &commandBuffer); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(texture, &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| } | |
| } | |
| // Test rendering to a texture memory on one device, then sampling it using another device. | |
| // Destroy the texture from the first device after submitting the commands, but before performing | |
| // EndAccess. The second device should still be able to wait on the first device and see the | |
| // results. | |
| TEST_P(SharedTextureMemoryTests, RenderThenTextureDestroyBeforeEndAccessThenSample) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| // Create two textures from each memory. | |
| wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()}; | |
| // Make two command buffers, one that clears the texture, another that samples. | |
| wgpu::CommandBuffer commandBuffer0 = | |
| MakeFourColorsClearCommandBuffer(devices[0], textures[0]); | |
| auto [commandBuffer1, colorTarget] = | |
| MakeCheckBySamplingCommandBuffer(devices[1], textures[1]); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(textures[0], &beginDesc); | |
| devices[0].GetQueue().Submit(1, &commandBuffer0); | |
| // Destroy the texture before performing EndAccess. | |
| textures[0].Destroy(); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(textures[0], &endState); | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memories[1].BeginAccess(textures[1], &beginDesc); | |
| devices[1].GetQueue().Submit(1, &commandBuffer1); | |
| memories[1].EndAccess(textures[1], &endState); | |
| CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test accessing the memory on one device, dropping all memories, then | |
| // accessing on the second device. Operations on the second device must | |
| // still wait for the preceding operations to complete. | |
| TEST_P(SharedTextureMemoryTests, RenderThenDropAllMemoriesThenSample) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (auto memories : GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| // Create two textures from each memory. | |
| wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()}; | |
| // Make two command buffers, one that clears the texture, another that samples. | |
| wgpu::CommandBuffer commandBuffer0 = | |
| MakeFourColorsClearCommandBuffer(devices[0], textures[0]); | |
| auto [commandBuffer1, colorTarget] = | |
| MakeCheckBySamplingCommandBuffer(devices[1], textures[1]); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| // Render to the texture. | |
| { | |
| memories[0].BeginAccess(textures[0], &beginDesc); | |
| devices[0].GetQueue().Submit(1, &commandBuffer0); | |
| memories[0].EndAccess(textures[0], &endState); | |
| } | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| // Begin access, then drop all memories. | |
| memories[1].BeginAccess(textures[1], &beginDesc); | |
| memories.clear(); | |
| // Sample from the texture and check the contents. | |
| devices[1].GetQueue().Submit(1, &commandBuffer1); | |
| CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget); | |
| } | |
| } | |
| // Test rendering to a texture memory on one device, then sampling it using another device. | |
| // Destroy or destroy the first device after submitting the commands, but before performing | |
| // EndAccess. The second device should still be able to wait on the first device and see the | |
| // results. | |
| // This tests both cases where the device is destroyed, and where the device is lost. | |
| TEST_P(SharedTextureMemoryTests, RenderThenLoseOrDestroyDeviceBeforeEndAccessThenSample) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // Not supported if using the same device. Not possible to lose one without losing the other. | |
| DAWN_TEST_UNSUPPORTED_IF(GetParam().mBackend->UseSameDevice()); | |
| // This test expects a fence returned from EndAccess, which is not possible if fences are | |
| // disabled in D3D11. | |
| DAWN_TEST_UNSUPPORTED_IF(IsD3D11() && HasToggleEnabled("d3d11_disable_fence")); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| auto DoTest = [&](auto DestroyOrLoseDevice) { | |
| std::vector<wgpu::Device> devices = {CreateDevice(), CreateDevice()}; | |
| auto perDeviceMemories = | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount); | |
| DAWN_TEST_UNSUPPORTED_IF(perDeviceMemories.empty()); | |
| const auto& memories = perDeviceMemories[0]; | |
| // Create two textures from each memory. | |
| wgpu::Texture textures[] = {memories[0].CreateTexture(), memories[1].CreateTexture()}; | |
| // Make two command buffers, one that clears the texture, another that samples. | |
| wgpu::CommandBuffer commandBuffer0 = | |
| MakeFourColorsClearCommandBuffer(devices[0], textures[0]); | |
| auto [commandBuffer1, colorTarget] = | |
| MakeCheckBySamplingCommandBuffer(devices[1], textures[1]); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(textures[0], &beginDesc); | |
| devices[0].GetQueue().Submit(1, &commandBuffer0); | |
| // Destroy or lose the device before performing EndAccess. | |
| DestroyOrLoseDevice(devices[0]); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(textures[0], &endState); | |
| EXPECT_GT(endState.fenceCount, 0u); | |
| EXPECT_GT(endState.signaledValueCount, 0u); | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memories[1].BeginAccess(textures[1], &beginDesc); | |
| devices[1].GetQueue().Submit(1, &commandBuffer1); | |
| memories[1].EndAccess(textures[1], &endState); | |
| CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget); | |
| }; | |
| DoTest([](wgpu::Device d) { d.Destroy(); }); | |
| DoTest([this](wgpu::Device d) { LoseDeviceForTesting(d); }); | |
| } | |
| // Test a shared texture memory created on separate devices but wrapping the same underyling data. | |
| // Write to the texture, then read from two separate devices concurrently, then write again. | |
| // Reads should happen strictly after the writes. The final write should wait for the reads. | |
| TEST_P(SharedTextureMemoryTests, SeparateDevicesWriteThenConcurrentReadThenWrite) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| DAWN_TEST_UNSUPPORTED_IF(!GetParam().mBackend->SupportsConcurrentRead()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice(), CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memories[0].GetProperties(&properties); | |
| wgpu::TextureDescriptor writeTextureDesc = {}; | |
| writeTextureDesc.format = properties.format; | |
| writeTextureDesc.size = properties.size; | |
| writeTextureDesc.usage = | |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding; | |
| writeTextureDesc.label = "write texture"; | |
| wgpu::TextureDescriptor readTextureDesc = {}; | |
| readTextureDesc.format = properties.format; | |
| readTextureDesc.size = properties.size; | |
| readTextureDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| readTextureDesc.label = "read texture"; | |
| // Create three textures from each memory. | |
| // The first one will be written to. | |
| // The second two will be concurrently read after the write. | |
| // Then the first one will be written to again. | |
| wgpu::Texture textures[] = {memories[0].CreateTexture(&writeTextureDesc), | |
| memories[1].CreateTexture(&readTextureDesc), | |
| memories[2].CreateTexture(&readTextureDesc)}; | |
| // Build command buffers for the test. | |
| wgpu::CommandBuffer writeCommandBuffer0 = | |
| MakeFourColorsClearCommandBuffer(devices[0], textures[0]); | |
| auto [checkCommandBuffer1, colorTarget1] = | |
| MakeCheckBySamplingCommandBuffer(devices[1], textures[1]); | |
| auto [checkCommandBuffer2, colorTarget2] = | |
| MakeCheckBySamplingCommandBuffer(devices[2], textures[2]); | |
| wgpu::CommandBuffer clearToGrayCommandBuffer0; | |
| { | |
| wgpu::CommandEncoder encoder = devices[0].CreateCommandEncoder(); | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = 0; | |
| desc.arrayLayerCount = 1; | |
| utils::ComboRenderPassDescriptor passDescriptor({textures[0].CreateView(&desc)}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| passDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; | |
| passDescriptor.cColorAttachments[0].clearValue = {0.5, 0.5, 0.5, 1.0}; | |
| encoder.BeginRenderPass(&passDescriptor).End(); | |
| clearToGrayCommandBuffer0 = encoder.Finish(); | |
| } | |
| // Begin access on texture 0 | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memories[0].BeginAccess(textures[0], &beginDesc); | |
| // Write | |
| devices[0].GetQueue().Submit(1, &writeCommandBuffer0); | |
| // End access on texture 0 | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[0].EndAccess(textures[0], &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| // Import fences to devices[1] and begin access. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = sharedFences.size(); | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = sharedFences.size(); | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memories[1].BeginAccess(textures[1], &beginDesc); | |
| // Import fences to devices[2] and begin access. | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[2], endState.fences[i]); | |
| } | |
| memories[2].BeginAccess(textures[2], &beginDesc); | |
| // Check contents | |
| devices[1].GetQueue().Submit(1, &checkCommandBuffer1); | |
| devices[2].GetQueue().Submit(1, &checkCommandBuffer2); | |
| CheckFourColors(devices[1], textures[1].GetFormat(), colorTarget1); | |
| CheckFourColors(devices[2], textures[2].GetFormat(), colorTarget2); | |
| // End access on texture 1 | |
| wgpu::SharedTextureMemoryEndAccessState endState1; | |
| auto backendEndState1 = GetParam().mBackend->ChainEndState(&endState1); | |
| memories[1].EndAccess(textures[1], &endState1); | |
| EXPECT_TRUE(endState1.initialized); | |
| // End access on texture 2 | |
| wgpu::SharedTextureMemoryEndAccessState endState2; | |
| auto backendEndState2 = GetParam().mBackend->ChainEndState(&endState2); | |
| memories[2].EndAccess(textures[2], &endState2); | |
| EXPECT_TRUE(endState2.initialized); | |
| // Import fences back to devices[0] | |
| sharedFences.resize(endState1.fenceCount + endState2.fenceCount); | |
| std::vector<uint64_t> signaledValues(sharedFences.size()); | |
| for (size_t i = 0; i < endState1.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[0], endState1.fences[i]); | |
| signaledValues[i] = endState1.signaledValues[i]; | |
| } | |
| for (size_t i = 0; i < endState2.fenceCount; ++i) { | |
| sharedFences[i + endState1.fenceCount] = | |
| utils::ImportFenceTo(devices[0], endState2.fences[i]); | |
| signaledValues[i + endState1.fenceCount] = endState2.signaledValues[i]; | |
| } | |
| beginDesc.fenceCount = sharedFences.size(); | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = sharedFences.size(); | |
| beginDesc.signaledValues = signaledValues.data(); | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState2); | |
| // Begin access on texture 0 | |
| memories[0].BeginAccess(textures[0], &beginDesc); | |
| // Submit a clear to gray. | |
| devices[0].GetQueue().Submit(1, &clearToGrayCommandBuffer0); | |
| } | |
| } | |
| // Test a shared texture memory created on one device. Create three textures from the memory, | |
| // Write to one texture, then read from two separate textures `concurrently`, then write again. | |
| // Reads should happen strictly after the writes. The final write should wait for the reads. | |
| TEST_P(SharedTextureMemoryTests, SameDeviceWriteThenConcurrentReadThenWrite) { | |
| // TODO(dawn/2276): support concurrent read access. | |
| DAWN_TEST_UNSUPPORTED_IF(IsVulkan()); | |
| DAWN_TEST_UNSUPPORTED_IF(!GetParam().mBackend->SupportsConcurrentRead()); | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| {device}, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| auto memory = memories[0]; | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memory.GetProperties(&properties); | |
| wgpu::TextureDescriptor writeTextureDesc = {}; | |
| writeTextureDesc.format = properties.format; | |
| writeTextureDesc.size = properties.size; | |
| writeTextureDesc.usage = | |
| wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding; | |
| writeTextureDesc.label = "write texture"; | |
| wgpu::TextureDescriptor readTextureDesc = {}; | |
| readTextureDesc.format = properties.format; | |
| readTextureDesc.size = properties.size; | |
| readTextureDesc.usage = wgpu::TextureUsage::TextureBinding; | |
| readTextureDesc.label = "read texture"; | |
| // Create three textures from each memory. | |
| // The first one will be written to. | |
| // The second two will be concurrently read after the write. | |
| // Then the first one will be written to again. | |
| wgpu::Texture textures[] = {memory.CreateTexture(&writeTextureDesc), | |
| memory.CreateTexture(&readTextureDesc), | |
| memory.CreateTexture(&readTextureDesc)}; | |
| // Build command buffers for the test. | |
| wgpu::CommandBuffer writeCommandBuffer0 = | |
| MakeFourColorsClearCommandBuffer(device, textures[0]); | |
| auto [checkCommandBuffer, colorTarget] = | |
| MakeCheckBySamplingTwoTexturesCommandBuffer(textures[1], textures[2]); | |
| wgpu::CommandBuffer clearToGrayCommandBuffer0; | |
| { | |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); | |
| wgpu::TextureViewDescriptor desc; | |
| desc.dimension = wgpu::TextureViewDimension::e2D; | |
| desc.baseArrayLayer = 0; | |
| desc.arrayLayerCount = 1; | |
| utils::ComboRenderPassDescriptor passDescriptor({textures[0].CreateView(&desc)}); | |
| passDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| passDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; | |
| passDescriptor.cColorAttachments[0].clearValue = {0.5, 0.5, 0.5, 1.0}; | |
| encoder.BeginRenderPass(&passDescriptor).End(); | |
| clearToGrayCommandBuffer0 = encoder.Finish(); | |
| } | |
| // Begin access on texture 0 | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(textures[0], &beginDesc); | |
| // Write | |
| device.GetQueue().Submit(1, &writeCommandBuffer0); | |
| // End access on texture 0 | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(textures[0], &endState); | |
| EXPECT_TRUE(endState.initialized); | |
| // Import fences to device and begin access. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(device, endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = sharedFences.size(); | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = true; | |
| beginDesc.initialized = true; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memory.BeginAccess(textures[1], &beginDesc); | |
| // Import fences to device and begin access. | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(device, endState.fences[i]); | |
| } | |
| memory.BeginAccess(textures[2], &beginDesc); | |
| // Check contents | |
| device.GetQueue().Submit(1, &checkCommandBuffer); | |
| CheckFourColors(device, textures[1].GetFormat(), colorTarget); | |
| // End access on texture 1 | |
| wgpu::SharedTextureMemoryEndAccessState endState1; | |
| auto backendEndState1 = GetParam().mBackend->ChainEndState(&endState1); | |
| memory.EndAccess(textures[1], &endState1); | |
| EXPECT_TRUE(endState1.initialized); | |
| // End access on texture 2 | |
| wgpu::SharedTextureMemoryEndAccessState endState2; | |
| auto backendEndState2 = GetParam().mBackend->ChainEndState(&endState2); | |
| memory.EndAccess(textures[2], &endState2); | |
| EXPECT_TRUE(endState2.initialized); | |
| // Import fences back to devices[0] | |
| sharedFences.resize(endState1.fenceCount + endState2.fenceCount); | |
| std::vector<uint64_t> signaledValues(sharedFences.size()); | |
| for (size_t i = 0; i < endState1.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(device, endState1.fences[i]); | |
| signaledValues[i] = endState1.signaledValues[i]; | |
| } | |
| for (size_t i = 0; i < endState2.fenceCount; ++i) { | |
| sharedFences[i + endState1.fenceCount] = | |
| utils::ImportFenceTo(device, endState2.fences[i]); | |
| signaledValues[i + endState1.fenceCount] = endState2.signaledValues[i]; | |
| } | |
| beginDesc.fenceCount = sharedFences.size(); | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = signaledValues.size(); | |
| beginDesc.signaledValues = signaledValues.data(); | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = true; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState2); | |
| // Begin access on texture 0 | |
| memory.BeginAccess(textures[0], &beginDesc); | |
| // Submit a clear to gray. | |
| device.GetQueue().Submit(1, &clearToGrayCommandBuffer0); | |
| } | |
| } | |
| // Test that textures created from SharedTextureMemory may perform sRGB reinterpretation. | |
| TEST_P(SharedTextureMemoryTests, SRGBReinterpretation) { | |
| // Format reinterpretation is not available in compatibility mode. | |
| DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| // TODO(crbug.com/dawn/2304): Investigate if the VVL is wrong here. | |
| DAWN_SUPPRESS_TEST_IF(GetParam().mBackend->Name().find("dma buf") != std::string::npos && | |
| IsBackendValidationEnabled()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc, | |
| GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memories[1].GetProperties(&properties); | |
| wgpu::TextureDescriptor textureDesc = {}; | |
| textureDesc.format = properties.format; | |
| textureDesc.size = properties.size; | |
| textureDesc.usage = wgpu::TextureUsage::RenderAttachment; | |
| wgpu::TextureViewDescriptor viewDesc = {}; | |
| if (properties.format == wgpu::TextureFormat::RGBA8Unorm) { | |
| viewDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb; | |
| } else if (properties.format == wgpu::TextureFormat::BGRA8Unorm) { | |
| viewDesc.format = wgpu::TextureFormat::BGRA8UnormSrgb; | |
| } else { | |
| continue; | |
| } | |
| textureDesc.viewFormatCount = 1; | |
| textureDesc.viewFormats = &viewDesc.format; | |
| // Create the texture on device 1. | |
| wgpu::Texture texture = memories[1].CreateTexture(&textureDesc); | |
| wgpu::CommandEncoder encoder = devices[1].CreateCommandEncoder(); | |
| for (uint32_t layer = 0; layer < texture.GetDepthOrArrayLayers(); ++layer) { | |
| viewDesc.dimension = wgpu::TextureViewDimension::e2D; | |
| viewDesc.baseArrayLayer = layer; | |
| viewDesc.arrayLayerCount = 1; | |
| // Submit a clear operation to sRGB value rgb(234, 51, 35). | |
| utils::ComboRenderPassDescriptor renderPassDescriptor({texture.CreateView(&viewDesc)}, | |
| {}); | |
| renderPassDescriptor.cColorAttachments[0].clearValue = {234.0 / 255.0, 51.0 / 255.0, | |
| 35.0 / 255.0, 1.0}; | |
| encoder.BeginRenderPass(&renderPassDescriptor).End(); | |
| } | |
| wgpu::CommandBuffer commands = encoder.Finish(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memories[1].BeginAccess(texture, &beginDesc); | |
| devices[1].GetQueue().Submit(1, &commands); | |
| memories[1].EndAccess(texture, &endState); | |
| // Create the texture on device 0. | |
| texture = memories[0].CreateTexture(); | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[0], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| memories[0].BeginAccess(texture, &beginDesc); | |
| // Expect the contents to be approximately rgb(246 124 104) | |
| if (properties.format == wgpu::TextureFormat::RGBA8Unorm) { | |
| EXPECT_PIXEL_RGBA8_BETWEEN( // | |
| utils::RGBA8(245, 123, 103, 255), // | |
| utils::RGBA8(247, 125, 105, 255), texture, 0, 0); | |
| } else { | |
| EXPECT_PIXEL_RGBA8_BETWEEN( // | |
| utils::RGBA8(103, 123, 245, 255), // | |
| utils::RGBA8(105, 125, 247, 255), texture, 0, 0); | |
| } | |
| } | |
| } | |
| // Test writing to texture memory in compute pass on one device, then sampling it using another | |
| // device. | |
| TEST_P(SharedTextureMemoryTests, WriteStorageThenReadSample) { | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| // Create the textures on each SharedTextureMemory. | |
| wgpu::Texture texture0 = memories[0].CreateTexture(); | |
| wgpu::Texture texture1 = memories[1].CreateTexture(); | |
| // Make a command buffer to populate the texture contents in a compute shader. | |
| wgpu::CommandBuffer commandBuffer0 = | |
| MakeFourColorsComputeCommandBuffer(devices[0], texture0); | |
| // Make a command buffer to sample and check the texture contents. | |
| wgpu::Texture resultTarget; | |
| wgpu::CommandBuffer commandBuffer1; | |
| std::tie(commandBuffer1, resultTarget) = | |
| MakeCheckBySamplingCommandBuffer(devices[1], texture1); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| // Begin access on memory 0, submit the compute pass, end access. | |
| memories[0].BeginAccess(texture0, &beginDesc); | |
| devices[0].GetQueue().Submit(1, &commandBuffer0); | |
| memories[0].EndAccess(texture0, &endState); | |
| // Import fences to device 1. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| // Begin access on memory 1, check the contents, end access. | |
| memories[1].BeginAccess(texture1, &beginDesc); | |
| devices[1].GetQueue().Submit(1, &commandBuffer1); | |
| memories[1].EndAccess(texture1, &endState); | |
| // Check all the sampled colors are correct. | |
| CheckFourColors(devices[1], texture1.GetFormat(), resultTarget); | |
| } | |
| } | |
| // Test writing to texture memory using queue.writeTexture, then sampling it using another device. | |
| TEST_P(SharedTextureMemoryTests, WriteTextureThenReadSample) { | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| wgpu::SharedTextureMemoryProperties properties; | |
| memories[0].GetProperties(&properties); | |
| if (properties.format != wgpu::TextureFormat::RGBA8Unorm) { | |
| continue; | |
| } | |
| // Create the textures on each SharedTextureMemory. | |
| wgpu::Texture texture0 = memories[0].CreateTexture(); | |
| wgpu::Texture texture1 = memories[1].CreateTexture(); | |
| // Make a command buffer to sample and check the texture contents. | |
| wgpu::Texture resultTarget; | |
| wgpu::CommandBuffer commandBuffer1; | |
| std::tie(commandBuffer1, resultTarget) = | |
| MakeCheckBySamplingCommandBuffer(devices[1], texture1); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.initialized = false; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| // Begin access on memory 0, use queue.writeTexture to populate the contents, end access. | |
| memories[0].BeginAccess(texture0, &beginDesc); | |
| WriteFourColorsToRGBA8Texture(devices[0], texture0); | |
| memories[0].EndAccess(texture0, &endState); | |
| // Import fences to device 1. | |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); | |
| for (size_t i = 0; i < endState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(devices[1], endState.fences[i]); | |
| } | |
| beginDesc.fenceCount = endState.fenceCount; | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = endState.signaledValueCount; | |
| beginDesc.signaledValues = endState.signaledValues; | |
| beginDesc.initialized = endState.initialized; | |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); | |
| // Begin access on memory 1, check the contents, end access. | |
| memories[1].BeginAccess(texture1, &beginDesc); | |
| devices[1].GetQueue().Submit(1, &commandBuffer1); | |
| memories[1].EndAccess(texture1, &endState); | |
| // Check all the sampled colors are correct. | |
| CheckFourColors(devices[1], texture1.GetFormat(), resultTarget); | |
| } | |
| } | |
| // Test that calling memory.EndAccess() implicitly hides the texture, and BeginAccess() unhides it. | |
| TEST_P(SharedTextureMemoryTests, HasResourceOneTextureEndAccess) { | |
| // TODO(473444514): Re-enable when compute shaders support ResourceTable on metal | |
| DAWN_SUPPRESS_TEST_IF(IsMetal()); | |
| // WebGPUOnWebGPU doesn't support ResourceTable | |
| DAWN_TEST_UNSUPPORTED_IF(IsWebGPUOnWebGPU()); | |
| DAWN_TEST_UNSUPPORTED_IF( | |
| !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable})); | |
| // Swiftshader doesn't support variable count descriptor sets used in draw operations. | |
| DAWN_SUPPRESS_TEST_IF(IsSwiftshader()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(texture, &beginDesc); | |
| wgpu::ResourceTable table = | |
| MakeResourceTable(3, {{1, {.textureView = CreateViewForTable(texture)}}}); | |
| // Has one valid entry | |
| TestTableHasResource(table, {false, true, false}); | |
| // After EndAccess it has no more valid entries. | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(texture, &endState); | |
| TestTableHasResource(table, {false, false, false}); | |
| // After BeginAccess again it has the valid entry again. | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc2 = {}; | |
| auto backendBeginState2 = GetParam().mBackend->ChainBeginState(&beginDesc2, endState); | |
| memory.BeginAccess(texture, &beginDesc2); | |
| TestTableHasResource(table, {false, true, false}); | |
| } | |
| // Test that updating a table with a texture whose access has ended works, but doesn't show that | |
| // entry as available until BeginAccess is called. | |
| TEST_P(SharedTextureMemoryTests, HasResourceUpdateWithTextureAlreadyEndedAccess) { | |
| // TODO(473444514): Re-enable when compute shaders support ResourceTable on metal | |
| DAWN_SUPPRESS_TEST_IF(IsMetal()); | |
| // WebGPUOnWebGPU doesn't support ResourceTable | |
| DAWN_TEST_UNSUPPORTED_IF(IsWebGPUOnWebGPU()); | |
| DAWN_TEST_UNSUPPORTED_IF( | |
| !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable})); | |
| // Swiftshader doesn't support variable count descriptor sets used in draw operations. | |
| DAWN_SUPPRESS_TEST_IF(IsSwiftshader()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| // Begin access and then end access so that access is ended. | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(texture, &beginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(texture, &endState); | |
| wgpu::ResourceTable table = | |
| MakeResourceTable(1, {{0, {.textureView = CreateViewForTable(texture)}}}); | |
| TestTableHasResource(table, {false}); | |
| // When access is begun again, it becomes available in the table. | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc2 = {}; | |
| auto backendBeginState2 = GetParam().mBackend->ChainBeginState(&beginDesc2, endState); | |
| memory.BeginAccess(texture, &beginDesc2); | |
| TestTableHasResource(table, {true}); | |
| } | |
| // Test that a texture used in multiple resource tables has its availability correctly updated on | |
| // EndAccess and BeginAccess. | |
| TEST_P(SharedTextureMemoryTests, HasResourceSameTextureMultipleTablesEndAccess) { | |
| // TODO(473444514): Re-enable when compute shaders support ResourceTable on metal | |
| DAWN_SUPPRESS_TEST_IF(IsMetal()); | |
| // WebGPUOnWebGPU doesn't support ResourceTable | |
| DAWN_TEST_UNSUPPORTED_IF(IsWebGPUOnWebGPU()); | |
| DAWN_TEST_UNSUPPORTED_IF( | |
| !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable})); | |
| // Swiftshader doesn't support variable count descriptor sets used in draw operations. | |
| DAWN_SUPPRESS_TEST_IF(IsSwiftshader()); | |
| wgpu::SharedTextureMemory memory = | |
| GetParam().mBackend->CreateSharedTextureMemory(device, GetParam().mLayerCount); | |
| wgpu::Texture texture = memory.CreateTexture(); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); | |
| memory.BeginAccess(texture, &beginDesc); | |
| wgpu::ResourceTable table1 = | |
| MakeResourceTable(3, {{1, {.textureView = CreateViewForTable(texture)}}}); | |
| wgpu::ResourceTable table2 = | |
| MakeResourceTable(1, {{0, {.textureView = CreateViewForTable(texture)}}}); | |
| // Table bound textures are visible by default | |
| TestTableHasResource(table1, {false, true, false}); | |
| TestTableHasResource(table2, {true}); | |
| // After EndAccess on the texture, both tables show the texture as unavailable. | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| memory.EndAccess(texture, &endState); | |
| TestTableHasResource(table1, {false, false, false}); | |
| TestTableHasResource(table2, {false}); | |
| // After BeginAccess again, both tables show the texture as available again. | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc2 = {}; | |
| auto backendBeginState2 = GetParam().mBackend->ChainBeginState(&beginDesc2, endState); | |
| memory.BeginAccess(texture, &beginDesc2); | |
| TestTableHasResource(table1, {false, true, false}); | |
| TestTableHasResource(table2, {true}); | |
| } | |
| // Test synchronization and memory barriers across two devices sharing the same SharedTextureMemory, | |
| // where one device interacts via a bindful render attachment and the other via a bindless resource | |
| // table. | |
| TEST_P(SharedTextureMemoryTests, InterDeviceRenderAttachmentAndBindlessTexture) { | |
| // WebGPUOnWebGPU doesn't support ResourceTable | |
| DAWN_TEST_UNSUPPORTED_IF(IsWebGPUOnWebGPU()); | |
| DAWN_TEST_UNSUPPORTED_IF( | |
| !SupportsFeatures({wgpu::FeatureName::ChromiumExperimentalSamplingResourceTable})); | |
| // Swiftshader doesn't support variable count descriptor sets used in draw operations. | |
| DAWN_SUPPRESS_TEST_IF(IsSwiftshader()); | |
| // crbug.com/358166479 | |
| DAWN_SUPPRESS_TEST_IF(IsLinux() && IsNvidia() && IsVulkan()); | |
| // TODO(crbug.com/523272965): Produces incorrect result on Pixel 10. | |
| DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsImgTec() && IsVulkan()); | |
| // crbug.com/475503907 | |
| DAWN_SUPPRESS_TEST_IF(IsOpenGLES() && IsImgTec()); | |
| wgpu::Device renderDevice = device; | |
| wgpu::Device sampleDevice = CreateDevice(); | |
| std::vector<wgpu::Device> devices = {renderDevice, sampleDevice}; | |
| for (const auto& memories : | |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( | |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding, | |
| GetParam().mLayerCount)) { | |
| const auto& renderMemory = memories[0]; | |
| const auto& sampleMemory = memories[1]; | |
| // Create the textures on each SharedTextureMemory. | |
| wgpu::Texture renderTexture = renderMemory.CreateTexture(); | |
| wgpu::Texture sampleTexture = sampleMemory.CreateTexture(); | |
| wgpu::SharedTextureMemoryEndAccessState sampleEndState = {}; | |
| std::unique_ptr<SharedTextureMemoryTestBackend::BackendEndState> backendSampleEndState; | |
| // Bindful Render Write on renderDevice -> Bindless Read on sampleDevice | |
| { | |
| // Make a command buffer to render four colors in quadrants to renderTexture on | |
| // renderDevice. | |
| wgpu::CommandBuffer renderCommandBuffer = | |
| MakeFourColorsClearCommandBuffer(renderDevice, renderTexture); | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor renderBeginDesc = {}; | |
| renderBeginDesc.concurrentRead = false; | |
| renderBeginDesc.initialized = false; | |
| auto backendRenderBeginState = | |
| GetParam().mBackend->ChainInitialBeginState(&renderBeginDesc); | |
| wgpu::SharedTextureMemoryEndAccessState renderEndState = {}; | |
| auto backendRenderEndState = GetParam().mBackend->ChainEndState(&renderEndState); | |
| // Begin access on renderMemory, submit render pass command buffer, end access. | |
| renderMemory.BeginAccess(renderTexture, &renderBeginDesc); | |
| renderDevice.GetQueue().Submit(1, &renderCommandBuffer); | |
| renderMemory.EndAccess(renderTexture, &renderEndState); | |
| // Import fences from renderDevice to sampleDevice. | |
| std::vector<wgpu::SharedFence> sharedFences(renderEndState.fenceCount); | |
| for (size_t i = 0; i < renderEndState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(sampleDevice, renderEndState.fences[i]); | |
| } | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor sampleBeginDesc = {}; | |
| sampleBeginDesc.fenceCount = sharedFences.size(); | |
| sampleBeginDesc.fences = sharedFences.data(); | |
| sampleBeginDesc.signaledValueCount = renderEndState.signaledValueCount; | |
| sampleBeginDesc.signaledValues = renderEndState.signaledValues; | |
| sampleBeginDesc.concurrentRead = false; | |
| sampleBeginDesc.initialized = renderEndState.initialized; | |
| auto backendSampleBeginState = | |
| GetParam().mBackend->ChainBeginState(&sampleBeginDesc, renderEndState); | |
| backendSampleEndState = GetParam().mBackend->ChainEndState(&sampleEndState); | |
| // Create a resource table on sampleDevice containing sampleTexture. | |
| wgpu::ResourceTable resourceTable = MakeResourceTable( | |
| sampleDevice, 1, {{0, {.textureView = CreateViewForTable(sampleTexture)}}}); | |
| // Make a command buffer to sample sampleTexture via the resource table and check | |
| // contents. | |
| wgpu::Texture sampleResultTarget; | |
| wgpu::CommandBuffer sampleCommandBuffer; | |
| std::tie(sampleCommandBuffer, sampleResultTarget) = | |
| MakeCheckBySamplingResourceTableCommandBuffer(sampleDevice, resourceTable, | |
| sampleTexture); | |
| // Begin access on sampleMemory, submit sampling command buffer, end access. | |
| sampleMemory.BeginAccess(sampleTexture, &sampleBeginDesc); | |
| sampleDevice.GetQueue().Submit(1, &sampleCommandBuffer); | |
| sampleMemory.EndAccess(sampleTexture, &sampleEndState); | |
| // Check that all the sampled colors on sampleDevice are correct. | |
| CheckFourColors(sampleDevice, sampleTexture.GetFormat(), sampleResultTarget); | |
| } | |
| // EndAccess on sampleDevice (after bindless read) -> BeginAccess on renderDevice (write | |
| // clear) | |
| { | |
| // Import fences from sampleDevice back to renderDevice. | |
| std::vector<wgpu::SharedFence> sharedFences(sampleEndState.fenceCount); | |
| for (size_t i = 0; i < sampleEndState.fenceCount; ++i) { | |
| sharedFences[i] = utils::ImportFenceTo(renderDevice, sampleEndState.fences[i]); | |
| } | |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; | |
| beginDesc.fenceCount = sharedFences.size(); | |
| beginDesc.fences = sharedFences.data(); | |
| beginDesc.signaledValueCount = sampleEndState.signaledValueCount; | |
| beginDesc.signaledValues = sampleEndState.signaledValues; | |
| beginDesc.concurrentRead = false; | |
| beginDesc.initialized = sampleEndState.initialized; | |
| auto backendBeginState = | |
| GetParam().mBackend->ChainBeginState(&beginDesc, sampleEndState); | |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; | |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); | |
| // Make a command buffer on renderDevice to clear renderTexture to zero via a render | |
| // pass. | |
| wgpu::CommandEncoder clearEncoder = renderDevice.CreateCommandEncoder(); | |
| utils::ComboRenderPassDescriptor clearPassDescriptor({renderTexture.CreateView()}); | |
| clearPassDescriptor.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; | |
| clearPassDescriptor.cColorAttachments[0].clearValue = {0.0, 0.0, 0.0, 0.0}; | |
| clearPassDescriptor.cColorAttachments[0].storeOp = wgpu::StoreOp::Store; | |
| wgpu::RenderPassEncoder clearPass = clearEncoder.BeginRenderPass(&clearPassDescriptor); | |
| clearPass.End(); | |
| wgpu::CommandBuffer clearCommandBuffer = clearEncoder.Finish(); | |
| // Make a command buffer on renderDevice to sample renderTexture using standard bindful | |
| // sampling to verify the clear. | |
| wgpu::Texture resultTarget; | |
| wgpu::CommandBuffer sampleCommandBuffer; | |
| std::tie(sampleCommandBuffer, resultTarget) = | |
| MakeCheckBySamplingCommandBuffer(renderDevice, renderTexture); | |
| // Begin access on renderMemory, submit clear and sampling command buffers, end access. | |
| renderMemory.BeginAccess(renderTexture, &beginDesc); | |
| wgpu::CommandBuffer commandBuffers[] = {clearCommandBuffer, sampleCommandBuffer}; | |
| renderDevice.GetQueue().Submit(2, commandBuffers); | |
| renderMemory.EndAccess(renderTexture, &endState); | |
| // Check that pixels on renderDevice were cleared to zero. | |
| uint8_t expectedAlpha = HasAlphaChannel(renderTexture.GetFormat()) ? 0x00 : 0xFF; | |
| utils::RGBA8 expectedZero(0, 0, 0, expectedAlpha); | |
| EXPECT_TEXTURE_EQ(renderDevice, &expectedZero, resultTarget, {0, 0}, {1, 1}); | |
| } | |
| } | |
| } | |
| class SharedTextureMemoryVulkanTests : public DawnTest {}; | |
| // Test that only a single Vulkan fence feature may be enabled at once. | |
| TEST_P(SharedTextureMemoryVulkanTests, SingleFenceFeature) { | |
| DAWN_TEST_UNSUPPORTED_IF(UsesWire()); | |
| std::vector<wgpu::FeatureName> fenceFeatures; | |
| wgpu::Adapter adapter(GetAdapter().Get()); | |
| for (wgpu::FeatureName f : { | |
| wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD, | |
| wgpu::FeatureName::SharedFenceSyncFD, | |
| wgpu::FeatureName::SharedFenceVkSemaphoreZirconHandle, | |
| }) { | |
| if (adapter.HasFeature(f)) { | |
| fenceFeatures.push_back(f); | |
| } | |
| } | |
| // Test that creating a device with each feature is valid. | |
| for (wgpu::FeatureName f : fenceFeatures) { | |
| wgpu::DeviceDescriptor deviceDesc; | |
| deviceDesc.requiredFeatureCount = 1; | |
| deviceDesc.requiredFeatures = &f; | |
| EXPECT_NE(adapter.CreateDevice(&deviceDesc), nullptr); | |
| } | |
| // Test that any combination of two features is invalid. | |
| for (size_t i = 0; i < fenceFeatures.size(); ++i) { | |
| for (size_t j = i + 1; j < fenceFeatures.size(); ++j) { | |
| wgpu::FeatureName features[] = {fenceFeatures[i], fenceFeatures[j]}; | |
| wgpu::DeviceDescriptor deviceDesc; | |
| deviceDesc.requiredFeatureCount = 2; | |
| deviceDesc.requiredFeatures = features; | |
| EXPECT_EQ(adapter.CreateDevice(&deviceDesc), nullptr); | |
| } | |
| } | |
| } | |
| DAWN_INSTANTIATE_TEST(SharedTextureMemoryVulkanTests, VulkanBackend()); | |
| } // anonymous namespace | |
| } // namespace dawn |