/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "mozilla/widget/DMABufSurface.h"
#include "mozilla/gfx/FileHandleWrapper.h"
#include "mozilla/ipc/FileDescriptor.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "mozilla/NotNull.h"
#include "nsTArray.h"
#include <fcntl.h>
#include <unistd.h>
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
// These tests verify that DMABufSurface descriptors and surfaces round-trip
// stably through serialization and import.
static RefPtr<FileHandleWrapper> MakeFd() {
int fd = open(
"/dev/null", O_RDONLY | O_CLOEXEC);
if (fd == -
1)
return nullptr;
return new FileHandleWrapper(UniqueFileHandle(fd));
}
// Matches what DMABufSurfaceRGBA::Serialize() produces for a single-plane
// 128×128 RGBA surface.
static SurfaceDescriptor MakeRGBADescriptor(RefPtr<FileHandleWrapper> fd) {
AutoTArray<NotNull<RefPtr<FileHandleWrapper>>,
4> fds;
fds.AppendElement(WrapNotNull(fd));
AutoTArray<uint64_t,
1> modifiers = {
0};
AutoTArray<uint32_t,
4> width = {
128}, height = {
128}, strides = {
512},
offsets = {
0};
AutoTArray<uint32_t,
1>
format;
// intentionally empty; Serialize() leaves this unset for RGBA
AutoTArray<NotNull<RefPtr<FileHandleWrapper>>,
1> fence;
AutoTArray<ipc::FileDescriptor,
1> refCount;
return SurfaceDescriptor(SurfaceDescriptorDMABuf(
DMABufSurface::SURFACE_RGBA,
0, modifiers,
0, fds, width, height, width,
height, format, strides, offsets, gfx::YUVColorSpace::BT601,
gfx::ColorRange::LIMITED, gfx::ColorSpace2::UNKNOWN,
gfx::TransferFunction::
Default,
0, fence,
1,
0, refCount, nullptr,
false));
}
// Matches what DMABufSurfaceYUV::Serialize() produces for a two-plane 128×128
// (NV12-style, 4:2:0) YUV surface.
static SurfaceDescriptor MakeYUVDescriptor(RefPtr<FileHandleWrapper> fd0,
RefPtr<FileHandleWrapper> fd1) {
AutoTArray<NotNull<RefPtr<FileHandleWrapper>>,
4> fds;
fds.AppendElement(WrapNotNull(fd0));
fds.AppendElement(WrapNotNull(fd1));
AutoTArray<uint64_t,
4> modifiers = {
0,
0};
// Y plane full size, UV plane half size (4:2:0 chroma).
AutoTArray<uint32_t,
4> width = {
128,
64}, height = {
128,
64},
widthAligned = {
128,
64}, heightAligned = {
128,
64},
format = {
0,
0}, strides = {
128,
128},
offsets = {
0,
0};
AutoTArray<NotNull<RefPtr<FileHandleWrapper>>,
1> fence;
AutoTArray<ipc::FileDescriptor,
1> refCount;
return SurfaceDescriptor(SurfaceDescriptorDMABuf(
DMABufSurface::SURFACE_YUV, VA_FOURCC_NV12, modifiers,
0, fds, width,
height, widthAligned, heightAligned, format, strides, offsets,
gfx::YUVColorSpace::BT601, gfx::ColorRange::LIMITED,
gfx::ColorSpace2::UNKNOWN, gfx::TransferFunction::
Default,
0, fence,
1,
0,
refCount, nullptr,
false));
}
// Run 3 serialize → import cycles for a single-plane RGBA surface.
static void RGBARoundtrip(RefPtr<DMABufSurface> surface) {
SurfaceDescriptor desc;
for (
int i =
0; i <
3; i++) {
ASSERT_TRUE(surface->Serialize(desc));
const auto& d = desc.get_SurfaceDescriptorDMABuf();
EXPECT_EQ(d.bufferType(), uint32_t{DMABufSurface::SURFACE_RGBA});
EXPECT_EQ(d.fds().Length(),
1u);
EXPECT_EQ(d.width()[
0],
128u);
EXPECT_EQ(d.height()[
0],
128u);
EXPECT_EQ(d.strides()[
0],
512u);
EXPECT_EQ(d.modifier()[
0], uint64_t{
0});
surface = DMABufSurface::CreateDMABufSurface(desc);
ASSERT_NE(surface, nullptr);
EXPECT_EQ(surface->GetWidth(),
128);
EXPECT_EQ(surface->GetHeight(),
128);
}
}
// Run 3 serialize → import cycles for a two-plane YUV surface.
static void YUVRoundtrip(RefPtr<DMABufSurface> surface) {
SurfaceDescriptor desc;
for (
int i =
0; i <
3; i++) {
ASSERT_TRUE(surface->Serialize(desc));
const auto& d = desc.get_SurfaceDescriptorDMABuf();
EXPECT_EQ(d.bufferType(), uint32_t{DMABufSurface::SURFACE_YUV});
EXPECT_EQ(d.fds().Length(),
2u);
EXPECT_EQ(d.width()[
0],
128u);
EXPECT_EQ(d.height()[
0],
128u);
EXPECT_EQ(d.width()[
1],
64u);
EXPECT_EQ(d.height()[
1],
64u);
EXPECT_EQ(d.format().Length(),
2u);
EXPECT_EQ(d.modifier().Length(),
2u);
surface = DMABufSurface::CreateDMABufSurface(desc);
ASSERT_NE(surface, nullptr);
EXPECT_EQ(surface->GetWidth(
0),
128);
EXPECT_EQ(surface->GetHeight(
0),
128);
EXPECT_EQ(surface->GetWidth(
1),
64);
EXPECT_EQ(surface->GetHeight(
1),
64);
}
}
// Test both starting points for RGBA: from a descriptor (compositor side,
// verify initial import) and from a surface (GPU process side, no descriptor
// needed thanks to WGPUDMABufInfo).
TEST(DMABufSurface, RGBARoundtrip)
{
{
RefPtr<FileHandleWrapper> fd = MakeFd();
ASSERT_NE(fd, nullptr);
RefPtr<DMABufSurface> surface =
DMABufSurface::CreateDMABufSurface(MakeRGBADescriptor(fd));
ASSERT_NE(surface, nullptr);
EXPECT_EQ(surface->GetWidth(),
128);
EXPECT_EQ(surface->GetHeight(),
128);
RGBARoundtrip(surface);
}
{
RefPtr<FileHandleWrapper> fd = MakeFd();
ASSERT_NE(fd, nullptr);
webgpu::ffi::WGPUDMABufInfo info{
true,
0,
1, {}, {
512}};
RefPtr<DMABufSurface> surface =
DMABufSurfaceRGBA::CreateDMABufSurface(std::move(fd), info,
128,
128);
ASSERT_NE(surface, nullptr);
EXPECT_EQ(surface->GetWidth(),
128);
EXPECT_EQ(surface->GetHeight(),
128);
RGBARoundtrip(surface);
}
}
// YUV surfaces require VA-API hardware to create from scratch, so the surface
// is always bootstrapped via descriptor import. Verify that import then run
// 3 serialize/import cycles from the surface side.
TEST(DMABufSurface, YUVRoundtrip)
{
RefPtr<FileHandleWrapper> fd0 = MakeFd(), fd1 = MakeFd();
ASSERT_NE(fd0, nullptr);
ASSERT_NE(fd1, nullptr);
RefPtr<DMABufSurface> surface =
DMABufSurface::CreateDMABufSurface(MakeYUVDescriptor(fd0, fd1));
ASSERT_NE(surface, nullptr);
EXPECT_EQ(surface->GetWidth(
0),
128);
EXPECT_EQ(surface->GetHeight(
0),
128);
EXPECT_EQ(surface->GetWidth(
1),
64);
EXPECT_EQ(surface->GetHeight(
1),
64);
YUVRoundtrip(surface);
}