| // Copyright 2021 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <fcntl.h> |
| #include <gbm.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "VideoViewsTests.h" |
| #include "dawn/common/Assert.h" |
| #include "dawn/native/VulkanBackend.h" |
| |
| namespace dawn { |
| namespace { |
| |
| // "linux-chromeos-rel"'s gbm.h is too old to compile, missing this change at least: |
| // https://chromium-review.googlesource.com/c/chromiumos/platform/minigbm/+/1963001/10/gbm.h#244 |
| #ifndef MINIGBM |
| #define GBM_BO_USE_TEXTURING (1 << 5) |
| #define GBM_BO_USE_SW_WRITE_RARELY (1 << 12) |
| #define GBM_BO_USE_HW_VIDEO_DECODER (1 << 13) |
| #endif |
| |
| #ifndef DRM_FORMAT_MOD_LINEAR |
| #define DRM_FORMAT_MOD_LINEAR 0 |
| #endif |
| |
| class PlatformTextureGbm : public VideoViewsTestBackend::PlatformTexture { |
| public: |
| PlatformTextureGbm(wgpu::Texture&& texture, gbm_bo* gbmBo) |
| : PlatformTexture(std::move(texture)), mGbmBo(gbmBo) {} |
| ~PlatformTextureGbm() override = default; |
| |
| // TODO(chromium:1258986): Add DISJOINT vkImage support for multi-plannar formats. |
| bool CanWrapAsWGPUTexture() override { |
| DAWN_ASSERT(mGbmBo != nullptr); |
| // Checks if all plane handles of a multi-planar gbm_bo are same. |
| gbm_bo_handle plane0Handle = gbm_bo_get_handle_for_plane(mGbmBo, 0); |
| for (int plane = 1; plane < gbm_bo_get_plane_count(mGbmBo); ++plane) { |
| if (gbm_bo_get_handle_for_plane(mGbmBo, plane).u32 != plane0Handle.u32) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| gbm_bo* GetGbmBo() { return mGbmBo; } |
| |
| private: |
| gbm_bo* mGbmBo = nullptr; |
| }; |
| |
| class VideoViewsTestBackendGbm : public VideoViewsTestBackend { |
| public: |
| void OnSetUp(WGPUDevice device) override { |
| mWGPUDevice = device; |
| mGbmDevice = CreateGbmDevice(); |
| } |
| |
| void OnTearDown() override { |
| if (mGbmDevice != nullptr) { |
| gbm_device_destroy(mGbmDevice); |
| } |
| if (mRenderNodeFd >= 0) { |
| close(mRenderNodeFd); |
| } |
| } |
| |
| private: |
| gbm_device* CreateGbmDevice() { |
| // 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"; |
| |
| mRenderNodeFd = -1; |
| 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; |
| } |
| } |
| DAWN_ASSERT(mRenderNodeFd > 0); |
| |
| gbm_device* gbmDevice = gbm_create_device(mRenderNodeFd); |
| DAWN_ASSERT(gbmDevice != nullptr); |
| return gbmDevice; |
| } |
| |
| static uint32_t GetGbmBoFormat(wgpu::TextureFormat format) { |
| switch (format) { |
| case wgpu::TextureFormat::R8BG8Biplanar420Unorm: |
| return GBM_FORMAT_NV12; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| WGPUTextureFormat ToWGPUTextureFormat(wgpu::TextureFormat format) { |
| switch (format) { |
| case wgpu::TextureFormat::R8BG8Biplanar420Unorm: |
| return WGPUTextureFormat_R8BG8Biplanar420Unorm; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| WGPUTextureUsage ToWGPUTextureUsage(wgpu::TextureUsage usage) { |
| switch (usage) { |
| case wgpu::TextureUsage::TextureBinding: |
| return WGPUTextureUsage_TextureBinding; |
| default: |
| DAWN_UNREACHABLE(); |
| } |
| } |
| |
| std::unique_ptr<VideoViewsTestBackend::PlatformTexture> CreateVideoTextureForTest( |
| wgpu::TextureFormat format, |
| wgpu::TextureUsage usage, |
| bool isCheckerboard, |
| bool initialized) override { |
| // The flags Chromium is using for the VAAPI decoder. |
| uint32_t flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_TEXTURING | GBM_BO_USE_HW_VIDEO_DECODER; |
| if (initialized) { |
| // The flag specifically used for tests, which need to initialize the GBM buffer with |
| // the expected raw video data via CPU, and then sample and draw the buffer via GPU. |
| // With the flag added, the buffer's drm modifier will be DRM_FORMAT_MOD_LINEAR instead |
| // of I915_FORMAT_MOD_Y_TILED. |
| flags |= GBM_BO_USE_SW_WRITE_RARELY; |
| } |
| gbm_bo* gbmBo = gbm_bo_create(mGbmDevice, VideoViewsTestsBase::kYUVAImageDataWidthInTexels, |
| VideoViewsTestsBase::kYUVAImageDataHeightInTexels, |
| GetGbmBoFormat(format), flags); |
| if (gbmBo == nullptr) { |
| return nullptr; |
| } |
| |
| if (initialized) { |
| void* mapHandle = nullptr; |
| uint32_t strideBytes = 0; |
| void* addr = gbm_bo_map(gbmBo, 0, 0, VideoViewsTestsBase::kYUVAImageDataWidthInTexels, |
| VideoViewsTestsBase::kYUVAImageDataHeightInTexels, |
| GBM_BO_TRANSFER_WRITE, &strideBytes, &mapHandle); |
| EXPECT_NE(addr, nullptr); |
| std::vector<uint8_t> initialData = VideoViewsTestsBase::GetTestTextureData<uint8_t>( |
| /*isMultiPlane*/ true, isCheckerboard, |
| /*hasAlpha*/ false); |
| uint8_t* srcBegin = initialData.data(); |
| uint8_t* srcEnd = srcBegin + initialData.size(); |
| uint8_t* dstBegin = static_cast<uint8_t*>(addr); |
| for (; srcBegin < srcEnd; srcBegin += VideoViewsTestsBase::kYUVAImageDataWidthInTexels, |
| dstBegin += strideBytes) { |
| std::memcpy(dstBegin, srcBegin, VideoViewsTestsBase::kYUVAImageDataWidthInTexels); |
| } |
| |
| gbm_bo_unmap(gbmBo, mapHandle); |
| } |
| |
| wgpu::TextureDescriptor textureDesc; |
| textureDesc.format = format; |
| textureDesc.dimension = wgpu::TextureDimension::e2D; |
| textureDesc.usage = usage; |
| textureDesc.size = {VideoViewsTestsBase::kYUVAImageDataWidthInTexels, |
| VideoViewsTestsBase::kYUVAImageDataHeightInTexels, 1}; |
| |
| wgpu::DawnTextureInternalUsageDescriptor internalDesc; |
| internalDesc.internalUsage = wgpu::TextureUsage::CopySrc; |
| textureDesc.nextInChain = &internalDesc; |
| |
| native::vulkan::ExternalImageDescriptorDmaBuf descriptor = {}; |
| descriptor.cTextureDescriptor = |
| reinterpret_cast<const WGPUTextureDescriptor*>(&textureDesc); |
| descriptor.isInitialized = initialized; |
| |
| descriptor.memoryFD = gbm_bo_get_fd(gbmBo); |
| for (int plane = 0; plane < gbm_bo_get_plane_count(gbmBo); ++plane) { |
| descriptor.planeLayouts[plane].stride = gbm_bo_get_stride_for_plane(gbmBo, plane); |
| descriptor.planeLayouts[plane].offset = gbm_bo_get_offset(gbmBo, plane); |
| } |
| descriptor.drmModifier = gbm_bo_get_modifier(gbmBo); |
| descriptor.waitFDs = {}; |
| |
| auto texture = std::make_unique<PlatformTextureGbm>( |
| native::vulkan::WrapVulkanImage(mWGPUDevice, &descriptor), gbmBo); |
| // The ownership of FD is only transferred in case of a success import. Otherwise cleanup |
| // is still needed to avoid FD leak. |
| if (!texture->wgpuTexture && descriptor.memoryFD >= 0) { |
| close(descriptor.memoryFD); |
| } |
| return texture; |
| } |
| |
| void DestroyVideoTextureForTest( |
| std::unique_ptr<VideoViewsTestBackend::PlatformTexture>&& platformTexture) override { |
| // Exports the signal and ignores it. |
| native::vulkan::ExternalImageExportInfoDmaBuf exportInfo; |
| native::vulkan::ExportVulkanImage(platformTexture->wgpuTexture.Get(), |
| VK_IMAGE_LAYOUT_UNDEFINED, &exportInfo); |
| for (int fd : exportInfo.semaphoreHandles) { |
| ASSERT_NE(fd, -1); |
| close(fd); |
| } |
| gbm_bo* gbmBo = static_cast<PlatformTextureGbm*>(platformTexture.get())->GetGbmBo(); |
| ASSERT_NE(gbmBo, nullptr); |
| gbm_bo_destroy(gbmBo); |
| } |
| |
| WGPUDevice mWGPUDevice = nullptr; |
| gbm_device* mGbmDevice = nullptr; |
| int mRenderNodeFd = -1; |
| }; |
| |
| } // anonymous namespace |
| |
| // static |
| std::vector<BackendTestConfig> VideoViewsTestBackend::Backends() { |
| return {VulkanBackend()}; |
| } |
| |
| // static |
| std::vector<Format> VideoViewsTestBackend::Formats() { |
| // TODO(dawn:551): Support sharing P010 video surfaces. |
| return {wgpu::TextureFormat::R8BG8Biplanar420Unorm}; |
| } |
| |
| // static |
| std::unique_ptr<VideoViewsTestBackend> VideoViewsTestBackend::Create() { |
| return std::make_unique<VideoViewsTestBackendGbm>(); |
| } |
| |
| } // namespace dawn |