| // 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 <fcntl.h> |
| #include <gbm.h> |
| #include <unistd.h> |
| #include <vulkan/vulkan.h> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "dawn/tests/end2end/SharedTextureMemoryTests.h" |
| #include "dawn/webgpu_cpp.h" |
| |
| namespace dawn { |
| namespace { |
| |
| template <wgpu::FeatureName FenceFeature> |
| class Backend : public SharedTextureMemoryTestBackend { |
| public: |
| static SharedTextureMemoryTestBackend* GetInstance() { |
| static Backend b; |
| return &b; |
| } |
| |
| std::string Name() const override { |
| switch (FenceFeature) { |
| case wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD: |
| return "dma buf, opaque fd"; |
| case wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD: |
| return "dma buf, sync fd"; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| std::vector<wgpu::FeatureName> RequiredFeatures(const wgpu::Adapter&) const override { |
| return {wgpu::FeatureName::SharedTextureMemoryDmaBuf, |
| wgpu::FeatureName::DawnMultiPlanarFormats, FenceFeature}; |
| } |
| |
| static std::string MakeLabel(const wgpu::SharedTextureMemoryDmaBufDescriptor& desc) { |
| // Internally, the GBM enums are defined as their fourcc values. Cast to that and use |
| // it as the label. The fourcc value is a four-character name that can be |
| // interpreted as a 32-bit integer enum ('ABGR', 'r011', etc.) |
| return std::string(reinterpret_cast<const char*>(&desc.drmFormat), 4) + " " + |
| "modifier:" + std::to_string(desc.drmModifier) + " " + |
| std::to_string(desc.size.width) + "x" + std::to_string(desc.size.height); |
| } |
| |
| template <typename CreateFn> |
| auto CreateSharedTextureMemoryHelper(uint32_t size, |
| uint32_t format, |
| uint32_t usage, |
| CreateFn createFn) { |
| gbm_bo* bo = gbm_bo_create(mGbmDevice, size, size, format, usage); |
| EXPECT_NE(bo, nullptr) << "Failed to create GBM buffer object"; |
| |
| wgpu::SharedTextureMemoryDmaBufDescriptor dmaBufDesc; |
| dmaBufDesc.size = {size, size}; |
| dmaBufDesc.drmFormat = format; |
| dmaBufDesc.drmModifier = gbm_bo_get_modifier(bo); |
| |
| wgpu::SharedTextureMemoryDmaBufPlane planes[GBM_MAX_PLANES]; |
| dmaBufDesc.planeCount = gbm_bo_get_plane_count(bo); |
| dmaBufDesc.planes = planes; |
| DAWN_ASSERT(dmaBufDesc.planeCount <= GBM_MAX_PLANES); |
| |
| for (uint32_t plane = 0; plane < dmaBufDesc.planeCount; ++plane) { |
| planes[plane].fd = gbm_bo_get_fd(bo); |
| planes[plane].stride = gbm_bo_get_stride_for_plane(bo, plane); |
| planes[plane].offset = gbm_bo_get_offset(bo, plane); |
| } |
| |
| std::string label = MakeLabel(dmaBufDesc); |
| wgpu::SharedTextureMemoryDescriptor desc; |
| desc.label = label.c_str(); |
| desc.nextInChain = &dmaBufDesc; |
| |
| auto ret = createFn(desc); |
| |
| for (uint32_t plane = 0; plane < dmaBufDesc.planeCount; ++plane) { |
| close(planes[plane].fd); |
| } |
| gbm_bo_destroy(bo); |
| |
| return ret; |
| } |
| |
| // Create one basic shared texture memory. It should support most operations. |
| wgpu::SharedTextureMemory CreateSharedTextureMemory(const wgpu::Device& device) override { |
| auto format = GBM_FORMAT_ABGR8888; |
| auto usage = GBM_BO_USE_LINEAR; |
| |
| DAWN_ASSERT(gbm_device_is_format_supported(mGbmDevice, format, usage)); |
| |
| return CreateSharedTextureMemoryHelper( |
| 16, format, usage, [&](const wgpu::SharedTextureMemoryDescriptor& desc) { |
| return device.ImportSharedTextureMemory(&desc); |
| }); |
| } |
| |
| std::vector<std::vector<wgpu::SharedTextureMemory>> CreatePerDeviceSharedTextureMemories( |
| const std::vector<wgpu::Device>& devices) override { |
| std::vector<std::vector<wgpu::SharedTextureMemory>> memories; |
| for (uint32_t format : { |
| GBM_FORMAT_R8, |
| GBM_FORMAT_GR88, |
| GBM_FORMAT_ABGR8888, |
| GBM_FORMAT_ARGB8888, |
| GBM_FORMAT_XBGR8888, |
| GBM_FORMAT_XRGB8888, |
| GBM_FORMAT_ABGR2101010, |
| GBM_FORMAT_NV12, |
| }) { |
| for (gbm_bo_flags usage : { |
| gbm_bo_flags(0), |
| GBM_BO_USE_LINEAR, |
| GBM_BO_USE_RENDERING, |
| gbm_bo_flags(GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR), |
| }) { |
| if (format == GBM_FORMAT_NV12 && (usage & GBM_BO_USE_LINEAR)) { |
| // TODO(crbug.com/dawn/1548): Linear multiplanar formats require disjoint |
| // planes which are not supported yet. |
| continue; |
| } |
| if (!gbm_device_is_format_supported(mGbmDevice, format, usage)) { |
| continue; |
| } |
| for (uint32_t size : {4, 64}) { |
| CreateSharedTextureMemoryHelper( |
| size, format, usage, [&](const wgpu::SharedTextureMemoryDescriptor& desc) { |
| std::vector<wgpu::SharedTextureMemory> perDeviceMemories; |
| for (auto& device : devices) { |
| perDeviceMemories.push_back( |
| device.ImportSharedTextureMemory(&desc)); |
| } |
| memories.push_back(std::move(perDeviceMemories)); |
| return true; |
| }); |
| } |
| } |
| } |
| return memories; |
| } |
| |
| wgpu::SharedFence ImportFenceTo(const wgpu::Device& importingDevice, |
| const wgpu::SharedFence& fence) override { |
| 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); |
| } |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| struct BackendBeginStateVk : public BackendBeginState { |
| wgpu::SharedTextureMemoryVkImageLayoutBeginState imageLayouts{}; |
| }; |
| |
| struct BackendEndStateVk : public BackendEndState { |
| wgpu::SharedTextureMemoryVkImageLayoutEndState imageLayouts{}; |
| }; |
| |
| std::unique_ptr<BackendBeginState> ChainInitialBeginState( |
| wgpu::SharedTextureMemoryBeginAccessDescriptor* beginDesc) override { |
| auto state = std::make_unique<BackendBeginStateVk>(); |
| state->imageLayouts.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| state->imageLayouts.newLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| beginDesc->nextInChain = &state->imageLayouts; |
| return state; |
| } |
| |
| std::unique_ptr<BackendEndState> ChainEndState( |
| wgpu::SharedTextureMemoryEndAccessState* endState) override { |
| auto state = std::make_unique<BackendEndStateVk>(); |
| endState->nextInChain = &state->imageLayouts; |
| return state; |
| } |
| |
| std::unique_ptr<BackendBeginState> ChainBeginState( |
| wgpu::SharedTextureMemoryBeginAccessDescriptor* beginDesc, |
| const wgpu::SharedTextureMemoryEndAccessState& endState) override { |
| 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; |
| } |
| |
| private: |
| void SetUp() override { |
| // Render nodes [1] are the primary interface for communicating with the GPU on |
| // devices that support DRM. The actual filename of the render node is |
| // implementation-specific, so we must scan through all possible filenames to find |
| // one that we can use [2]. |
| // |
| // [1] https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#render-nodes |
| // [2] |
| // https://cs.chromium.org/chromium/src/ui/ozone/platform/wayland/gpu/drm_render_node_path_finder.cc |
| const uint32_t kRenderNodeStart = 128; |
| const uint32_t kRenderNodeEnd = kRenderNodeStart + 16; |
| const std::string kRenderNodeTemplate = "/dev/dri/renderD"; |
| |
| for (uint32_t i = kRenderNodeStart; i < kRenderNodeEnd; i++) { |
| std::string renderNode = kRenderNodeTemplate + std::to_string(i); |
| mRenderNodeFd = open(renderNode.c_str(), O_RDWR); |
| if (mRenderNodeFd >= 0) { |
| break; |
| } |
| } |
| |
| // Failed to get file descriptor for render node and mGbmDevice is nullptr. |
| DAWN_TEST_UNSUPPORTED_IF(mRenderNodeFd < 0); |
| |
| mGbmDevice = gbm_create_device(mRenderNodeFd); |
| DAWN_TEST_UNSUPPORTED_IF(mGbmDevice == nullptr); |
| |
| // Make sure we can successfully create a basic buffer object. |
| gbm_bo* bo = gbm_bo_create(mGbmDevice, 16, 16, GBM_FORMAT_XBGR8888, GBM_BO_USE_LINEAR); |
| if (bo != nullptr) { |
| gbm_bo_destroy(bo); |
| } |
| DAWN_TEST_UNSUPPORTED_IF(bo == nullptr); |
| } |
| |
| void TearDown() override { |
| if (mGbmDevice) { |
| gbm_device_destroy(mGbmDevice); |
| mGbmDevice = nullptr; |
| } |
| if (mRenderNodeFd >= 0) { |
| close(mRenderNodeFd); |
| } |
| } |
| |
| int mRenderNodeFd = -1; |
| gbm_device* mGbmDevice = nullptr; |
| }; |
| |
| DAWN_INSTANTIATE_PREFIXED_TEST_P( |
| Vulkan, |
| SharedTextureMemoryNoFeatureTests, |
| {VulkanBackend()}, |
| {Backend<wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD>::GetInstance(), |
| Backend<wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD>::GetInstance()}); |
| |
| DAWN_INSTANTIATE_PREFIXED_TEST_P( |
| Vulkan, |
| SharedTextureMemoryTests, |
| {VulkanBackend()}, |
| {Backend<wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD>::GetInstance(), |
| Backend<wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD>::GetInstance()}); |
| |
| } // anonymous namespace |
| } // namespace dawn |