| // 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. |
| |
| #include "dawn/tests/white_box/SharedTextureMemoryTests.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "dawn/tests/MockCallback.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/TextureUtils.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| |
| namespace { |
| |
| struct BackendBeginStateVk : public SharedTextureMemoryTestBackend::BackendBeginState { |
| wgpu::SharedTextureMemoryVkImageLayoutBeginState imageLayouts{}; |
| }; |
| |
| struct BackendEndStateVk : public SharedTextureMemoryTestBackend::BackendEndState { |
| wgpu::SharedTextureMemoryVkImageLayoutEndState imageLayouts{}; |
| }; |
| |
| } // 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(); |
| GetParam().mBackend->SetUp(); |
| } |
| |
| 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::TransientAttachments, |
| wgpu::FeatureName::Unorm16TextureFormats, |
| wgpu::FeatureName::BGRA8UnormStorage, |
| }; |
| for (auto feature : kOptionalFeatures) { |
| if (SupportsFeatures({feature})) { |
| features.push_back(feature); |
| } |
| } |
| |
| return features; |
| } |
| |
| void SharedTextureMemoryTests::SetUp() { |
| DAWN_TEST_UNSUPPORTED_IF(UsesWire()); |
| DawnTestWithParams<SharedTextureMemoryTestParams>::SetUp(); |
| DAWN_TEST_UNSUPPORTED_IF( |
| !SupportsFeatures(GetParam().mBackend->RequiredFeatures(GetAdapter().Get()))); |
| GetParam().mBackend->SetUp(); |
| } |
| |
| void SharedTextureMemoryNoFeatureTests::TearDown() { |
| DawnTestWithParams<SharedTextureMemoryTestParams>::TearDown(); |
| GetParam().mBackend->TearDown(); |
| } |
| |
| void SharedTextureMemoryTests::TearDown() { |
| DawnTestWithParams<SharedTextureMemoryTestParams>::TearDown(); |
| GetParam().mBackend->TearDown(); |
| } |
| |
| wgpu::SharedFence SharedTextureMemoryTestBackend::ImportFenceTo(const wgpu::Device& importingDevice, |
| const wgpu::SharedFence& fence) { |
| wgpu::SharedFenceExportInfo exportInfo; |
| fence.ExportInfo(&exportInfo); |
| |
| switch (exportInfo.type) { |
| case wgpu::SharedFenceType::VkSemaphoreOpaqueFD: { |
| wgpu::SharedFenceVkSemaphoreOpaqueFDExportInfo vkExportInfo; |
| exportInfo.nextInChain = &vkExportInfo; |
| fence.ExportInfo(&exportInfo); |
| |
| wgpu::SharedFenceVkSemaphoreOpaqueFDDescriptor vkDesc; |
| vkDesc.handle = vkExportInfo.handle; |
| |
| wgpu::SharedFenceDescriptor fenceDesc; |
| fenceDesc.nextInChain = &vkDesc; |
| return importingDevice.ImportSharedFence(&fenceDesc); |
| } |
| case wgpu::SharedFenceType::VkSemaphoreSyncFD: { |
| wgpu::SharedFenceVkSemaphoreSyncFDExportInfo vkExportInfo; |
| exportInfo.nextInChain = &vkExportInfo; |
| fence.ExportInfo(&exportInfo); |
| |
| wgpu::SharedFenceVkSemaphoreSyncFDDescriptor vkDesc; |
| vkDesc.handle = vkExportInfo.handle; |
| |
| wgpu::SharedFenceDescriptor fenceDesc; |
| fenceDesc.nextInChain = &vkDesc; |
| return importingDevice.ImportSharedFence(&fenceDesc); |
| } |
| case wgpu::SharedFenceType::VkSemaphoreZirconHandle: { |
| wgpu::SharedFenceVkSemaphoreZirconHandleExportInfo vkExportInfo; |
| exportInfo.nextInChain = &vkExportInfo; |
| fence.ExportInfo(&exportInfo); |
| |
| wgpu::SharedFenceVkSemaphoreZirconHandleDescriptor vkDesc; |
| vkDesc.handle = vkExportInfo.handle; |
| |
| wgpu::SharedFenceDescriptor fenceDesc; |
| fenceDesc.nextInChain = &vkDesc; |
| return importingDevice.ImportSharedFence(&fenceDesc); |
| } |
| case wgpu::SharedFenceType::DXGISharedHandle: { |
| wgpu::SharedFenceDXGISharedHandleExportInfo dxgiExportInfo; |
| exportInfo.nextInChain = &dxgiExportInfo; |
| fence.ExportInfo(&exportInfo); |
| |
| wgpu::SharedFenceDXGISharedHandleDescriptor dxgiDesc; |
| dxgiDesc.handle = dxgiExportInfo.handle; |
| |
| wgpu::SharedFenceDescriptor fenceDesc; |
| fenceDesc.nextInChain = &dxgiDesc; |
| return importingDevice.ImportSharedFence(&fenceDesc); |
| } |
| case wgpu::SharedFenceType::MTLSharedEvent: { |
| wgpu::SharedFenceMTLSharedEventExportInfo sharedEventInfo; |
| exportInfo.nextInChain = &sharedEventInfo; |
| |
| fence.ExportInfo(&exportInfo); |
| |
| wgpu::SharedFenceMTLSharedEventDescriptor sharedEventDesc; |
| sharedEventDesc.sharedEvent = sharedEventInfo.sharedEvent; |
| |
| wgpu::SharedFenceDescriptor fenceDesc; |
| fenceDesc.nextInChain = &sharedEventDesc; |
| return importingDevice.ImportSharedFence(&fenceDesc); |
| } |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| std::vector<wgpu::SharedTextureMemory> SharedTextureMemoryTestBackend::CreateSharedTextureMemories( |
| wgpu::Device& device) { |
| std::vector<wgpu::SharedTextureMemory> memories; |
| for (auto& memory : CreatePerDeviceSharedTextureMemories({device})) { |
| 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) { |
| std::vector<wgpu::SharedTextureMemory> out; |
| for (auto& memory : CreateSharedTextureMemories(device)) { |
| 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) { |
| std::vector<std::vector<wgpu::SharedTextureMemory>> out; |
| for (auto& memories : CreatePerDeviceSharedTextureMemories(devices)) { |
| 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(); |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()}); |
| 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::ImageCopyTexture 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::ImageCopyBuffer destination; |
| destination.buffer = deviceObj.CreateBuffer(&bufferDesc); |
| |
| wgpu::Extent3D size = {1, 1, 1}; |
| 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(); |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()}); |
| 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); |
| pass.SetBindGroup(0, utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0), |
| {{0, texture.CreateView()}})); |
| 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::ImageCopyTexture dest; |
| dest.texture = texture; |
| |
| wgpu::TextureDataLayout dataLayout = { |
| .offset = 0, .bytesPerRow = bytesPerRow, .rowsPerImage = height}; |
| |
| 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.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| textureDesc.size = {texture.GetWidth(), texture.GetHeight(), texture.GetDepthOrArrayLayers()}; |
| 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::BindGroup bindGroup = utils::MakeBindGroup(deviceObj, pipeline.GetBindGroupLayout(0), |
| {{0, texture.CreateView()}}); |
| |
| 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(), |
| texture0.GetDepthOrArrayLayers()}; |
| 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::BindGroup bindGroup = |
| utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), |
| {{0, texture0.CreateView()}, {1, texture1.CreateView()}}); |
| |
| 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}; |
| } |
| |
| // 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::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); |
| |
| namespace { |
| |
| using testing::HasSubstr; |
| using testing::MockCallback; |
| |
| 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) { |
| // 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); |
| |
| MockCallback<WGPUErrorCallback> popErrorScopeCallback; |
| EXPECT_CALL(popErrorScopeCallback, |
| Call(WGPUErrorType_Validation, HasSubstr("is not enabled"), this)); |
| |
| device.PopErrorScope(popErrorScopeCallback.Callback(), |
| popErrorScopeCallback.MakeUserdata(this)); |
| |
| 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_FALSE(memory.BeginAccess(texture, &beginDesc)), |
| HasSubstr("is invalid")); |
| |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture, &endState)), |
| 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::Undefined); |
| } |
| |
| // 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); |
| } |
| |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; |
| beginDesc.concurrentRead = false; |
| beginDesc.initialized = true; |
| // That the begin access does not succeed since the device is destroyed. |
| EXPECT_FALSE(memory.BeginAccess(memory.CreateTexture(), &beginDesc)); |
| } |
| |
| // 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); |
| |
| 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); |
| |
| 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); |
| 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_TRUE(memory.BeginAccess(texture, &beginDesc)); |
| |
| // 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 (properties.format != wgpu::TextureFormat::R8BG8Biplanar420Unorm && |
| properties.format != wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm && |
| properties.format != wgpu::TextureFormat::R8BG8A8Triplanar420Unorm) { |
| if (properties.usage & wgpu::TextureUsage::CopySrc) { |
| UseInCopy(device, texture); |
| } else if (properties.usage & wgpu::TextureUsage::CopyDst) { |
| wgpu::Extent3D writeSize = {1, 1, 1}; |
| wgpu::ImageCopyTexture dest = {}; |
| dest.texture = texture; |
| wgpu::TextureDataLayout 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_TRUE(memory.EndAccess(texture, &endState)); |
| |
| // 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] = GetParam().mBackend->ImportFenceTo(device, endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| beginDesc.signaledValues = endState.signaledValues; |
| beginDesc.concurrentRead = false; |
| beginDesc.initialized = endState.initialized; |
| backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState); |
| |
| // Begin access should fail. |
| EXPECT_FALSE(memory.BeginAccess(texture, &beginDesc)); |
| } |
| |
| // 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) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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 (isSinglePlanar && utils::TextureFormatSupportsStorageTexture(properties.format, device, |
| IsCompatibilityMode())) { |
| expectedUsage |= wgpu::TextureUsage::StorageBinding; |
| } |
| |
| 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); |
| |
| 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 texture usages must be a subset of the shared texture memory's usage. |
| TEST_P(SharedTextureMemoryTests, UsageValidation) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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("is not 1")); |
| } |
| } |
| |
| // Test that it is an error if the texture mip level count is not 1. |
| TEST_P(SharedTextureMemoryTests, MipLevelValidation) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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); |
| 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_TRUE(memory.BeginAccess(texture, &beginDesc)); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(texture, &beginDesc)), |
| HasSubstr("is already used to access")); |
| } |
| |
| // 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); |
| |
| 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_TRUE(memory.BeginAccess(writeTexture, &beginDesc)); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(writeTexture, &beginDesc)); |
| beginDesc.concurrentRead = true; |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(readTexture, &beginDesc)); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(readTexture, &beginDesc)); |
| beginDesc.concurrentRead = false; |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(writeTexture1, &beginDesc)); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(writeTexture2, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(readTexture1, &beginDesc)); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture2, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(readTexture1, &beginDesc)); |
| EXPECT_TRUE(memory.BeginAccess(readTexture2, &beginDesc)); |
| |
| wgpu::SharedTextureMemoryEndAccessState endState1 = {}; |
| EXPECT_TRUE(memory.EndAccess(readTexture1, &endState1)); |
| wgpu::SharedTextureMemoryEndAccessState endState2 = {}; |
| EXPECT_TRUE(memory.EndAccess(readTexture2, &endState2)); |
| } |
| |
| // 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); |
| |
| 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_TRUE(memory.BeginAccess(readTexture1, &beginDesc)); |
| beginDesc.concurrentRead = false; |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture2, &beginDesc)), |
| 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); |
| |
| 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_TRUE(memory.BeginAccess(readTexture1, &beginDesc)); |
| beginDesc.concurrentRead = true; |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.BeginAccess(readTexture2, &beginDesc)), |
| 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); |
| |
| 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_FALSE(memory.BeginAccess(writeTexture, &beginDesc)), |
| 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); |
| wgpu::Texture texture = memory.CreateTexture(); |
| |
| wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {}; |
| beginDesc.concurrentRead = false; |
| beginDesc.initialized = true; |
| auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc); |
| |
| EXPECT_TRUE(memory.BeginAccess(texture, &beginDesc)); |
| |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); |
| EXPECT_TRUE(memory.EndAccess(texture, &endState)); |
| |
| // Invalid to end access a second time. |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture, &endState)), |
| 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); |
| 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_TRUE(memory.BeginAccess(texture1, &beginDesc)); |
| |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture2, &endState)), |
| 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); |
| wgpu::Texture texture = memory.CreateTexture(); |
| |
| wgpu::SharedTextureMemoryEndAccessState endState = {}; |
| auto backendEndState = GetParam().mBackend->ChainEndState(&endState); |
| ASSERT_DEVICE_ERROR_MSG(EXPECT_FALSE(memory.EndAccess(texture, &endState)), |
| 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); |
| |
| 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 (properties.format != wgpu::TextureFormat::R8BG8Biplanar420Unorm && |
| properties.format != wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm && |
| properties.format != wgpu::TextureFormat::R8BG8A8Triplanar420Unorm) { |
| 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::ImageCopyTexture dest = {}; |
| dest.texture = texture; |
| wgpu::TextureDataLayout 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) { |
| // NOTE: UseInRenderPass()/UseInCopy() do not currently support multiplanar |
| // formats. |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSinglePlanarSharedTextureMemories(device)) { |
| 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) { |
| for (wgpu::SharedTextureMemory memory : |
| GetParam().mBackend->CreateSharedTextureMemories(device)) { |
| 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) == 0) { |
| 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.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.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(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) { |
| // 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)) { |
| 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(); |
| utils::ComboRenderPassDescriptor passDescriptor({texture.CreateView()}); |
| 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) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::TextureBinding)) { |
| 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::CreateImageCopyTexture(srcTex); |
| auto dst = utils::CreateImageCopyTexture(texture); |
| 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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 after performing BeginAccess. |
| TEST_P(SharedTextureMemoryTests, RenderThenSampleEncodeAfterBeginAccess) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) { |
| 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) { |
| // 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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 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) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) { |
| // 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| for (auto memories : GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) { |
| // 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| // Not supported if using the same device. Not possible to lose one without losing the other. |
| DAWN_TEST_UNSUPPORTED_IF(GetParam().mBackend->UseSameDevice()); |
| |
| auto DoTest = [&](auto DestroyOrLoseDevice) { |
| std::vector<wgpu::Device> devices = {CreateDevice(), CreateDevice()}; |
| auto perDeviceMemories = |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding); |
| 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); |
| |
| std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount); |
| for (size_t i = 0; i < endState.fenceCount; ++i) { |
| sharedFences[i] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| DAWN_TEST_UNSUPPORTED_IF(!GetParam().mBackend->SupportsConcurrentRead()); |
| |
| std::vector<wgpu::Device> devices = {device, CreateDevice(), CreateDevice()}; |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding)) { |
| 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(); |
| utils::ComboRenderPassDescriptor passDescriptor({textures[0].CreateView()}); |
| 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = sharedFences.size(); |
| beginDesc.fences = sharedFences.data(); |
| 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] = GetParam().mBackend->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] = GetParam().mBackend->ImportFenceTo(devices[0], endState1.fences[i]); |
| signaledValues[i] = endState1.signaledValues[i]; |
| } |
| for (size_t i = 0; i < endState2.fenceCount; ++i) { |
| sharedFences[i + endState1.fenceCount] = |
| GetParam().mBackend->ImportFenceTo(devices[0], endState2.fences[i]); |
| signaledValues[i + endState1.fenceCount] = endState2.signaledValues[i]; |
| } |
| |
| beginDesc.fenceCount = sharedFences.size(); |
| beginDesc.fences = sharedFences.data(); |
| 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)) { |
| 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(); |
| utils::ComboRenderPassDescriptor passDescriptor({textures[0].CreateView()}); |
| 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] = GetParam().mBackend->ImportFenceTo(device, endState.fences[i]); |
| } |
| beginDesc.fenceCount = sharedFences.size(); |
| beginDesc.fences = sharedFences.data(); |
| 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] = GetParam().mBackend->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] = GetParam().mBackend->ImportFenceTo(device, endState1.fences[i]); |
| signaledValues[i] = endState1.signaledValues[i]; |
| } |
| for (size_t i = 0; i < endState2.fenceCount; ++i) { |
| sharedFences[i + endState1.fenceCount] = |
| GetParam().mBackend->ImportFenceTo(device, endState2.fences[i]); |
| signaledValues[i + endState1.fenceCount] = endState2.signaledValues[i]; |
| } |
| |
| beginDesc.fenceCount = sharedFences.size(); |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| // 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)) { |
| 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); |
| |
| // 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}; |
| wgpu::CommandEncoder encoder = devices[1].CreateCommandEncoder(); |
| 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] = GetParam().mBackend->ImportFenceTo(devices[0], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding)) { |
| // 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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) { |
| std::vector<wgpu::Device> devices = {device, CreateDevice()}; |
| for (const auto& memories : |
| GetParam().mBackend->CreatePerDeviceSharedTextureMemoriesFilterByUsage( |
| devices, wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::TextureBinding)) { |
| 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] = GetParam().mBackend->ImportFenceTo(devices[1], endState.fences[i]); |
| } |
| beginDesc.fenceCount = endState.fenceCount; |
| beginDesc.fences = sharedFences.data(); |
| 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); |
| } |
| } |
| |
| 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::SharedFenceVkSemaphoreSyncFD, |
| 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 |