blob: 7595dc8a2e409cc8c915f34d44405efc0d4d8cc5 [file] [log] [blame]
/*
* Copyright (C) 2022 Metrological Group B.V.
* Copyright (C) 2022 Igalia S.L.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 "config.h"
#include "TextureMapperPlatformLayerProxyDMABuf.h"
#if USE(COORDINATED_GRAPHICS) && USE(TEXTURE_MAPPER_DMABUF)
#include "PlatformDisplay.h"
#include "TextureMapperGL.h"
#include "TextureMapperGLHeaders.h"
#include "TextureMapperLayer.h"
#include <fcntl.h>
#include <initializer_list>
#include <unistd.h>
#if USE(LIBEPOXY)
#include "EpoxyEGL.h"
#else
#include <EGL/egl.h>
#include <EGL/eglext.h>
#endif
// This has to be included after the EGL headers.
#include "DMABufEGLUtilities.h"
namespace WebCore {
static PFNEGLCREATEIMAGEKHRPROC createImageKHR()
{
static PFNEGLCREATEIMAGEKHRPROC s_createImageKHR;
static std::once_flag s_flag;
std::call_once(s_flag,
[&] {
s_createImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
});
return s_createImageKHR;
}
static PFNEGLDESTROYIMAGEKHRPROC destroyImageKHR()
{
static PFNEGLDESTROYIMAGEKHRPROC s_destroyImageKHR;
static std::once_flag s_flag;
std::call_once(s_flag,
[&] {
s_destroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
});
return s_destroyImageKHR;
}
struct TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::EGLImageData {
WTF_MAKE_STRUCT_FAST_ALLOCATED;
~EGLImageData()
{
if (numImages) {
glDeleteTextures(numImages, texture.data());
for (unsigned i = 0; i < numImages; ++i) {
if (image[i] != EGL_NO_IMAGE_KHR)
destroyImageKHR()(PlatformDisplay::sharedDisplayForCompositing().eglDisplay(), image[i]);
}
}
}
uint32_t width { 0 };
uint32_t height { 0 };
unsigned numImages { 0 };
std::array<GLuint, DMABufFormat::c_maxPlanes> texture { 0, 0, 0, 0 };
std::array<EGLImageKHR, DMABufFormat::c_maxPlanes> image { EGL_NO_IMAGE_KHR, EGL_NO_IMAGE_KHR, EGL_NO_IMAGE_KHR, EGL_NO_IMAGE_KHR };
};
TextureMapperPlatformLayerProxyDMABuf::TextureMapperPlatformLayerProxyDMABuf() = default;
TextureMapperPlatformLayerProxyDMABuf::~TextureMapperPlatformLayerProxyDMABuf() = default;
void TextureMapperPlatformLayerProxyDMABuf::activateOnCompositingThread(Compositor* compositor, TextureMapperLayer* targetLayer)
{
#if ASSERT_ENABLED
if (!m_compositorThread)
m_compositorThread = &Thread::current();
#endif
ASSERT(m_compositorThread == &Thread::current());
ASSERT(compositor);
ASSERT(targetLayer);
{
Locker locker { m_lock };
m_compositor = compositor;
m_targetLayer = targetLayer;
}
}
void TextureMapperPlatformLayerProxyDMABuf::invalidate()
{
ASSERT(m_compositorThread == &Thread::current());
#if ASSERT_ENABLED
m_compositorThread = nullptr;
#endif
Locker locker { m_lock };
m_pendingLayer = nullptr;
m_committedLayer = nullptr;
m_layers = { };
m_compositor = nullptr;
m_targetLayer = nullptr;
}
void TextureMapperPlatformLayerProxyDMABuf::swapBuffer()
{
Locker locker { m_lock };
if (!m_targetLayer || !m_pendingLayer)
return;
auto previousLayer = WTFMove(m_committedLayer);
m_committedLayer = WTFMove(m_pendingLayer);
m_targetLayer->setContentsLayer(m_committedLayer.get());
// The previous and just-committed layers shouldn't be the same. But if they are, don't perform a release.
ASSERT(!previousLayer || previousLayer != m_committedLayer);
if (previousLayer && previousLayer != m_committedLayer)
previousLayer->release();
if (!m_committedLayer->m_imageData)
m_committedLayer->m_imageData = DMABufLayer::createEGLImageData(m_committedLayer->m_object);
m_committedLayer->m_age = 0;
// Remove any stale layers, e.g. if a layer has gone unused for >c_maximumAge swaps or doesn't match the latest size.
auto& committedObject = m_committedLayer->m_object;
auto isStaleLayer =
[&](auto& it)
{
auto& layer = it.value.get();
return layer.m_age > DMABufLayer::c_maximumAge
|| !(layer.m_object.width == committedObject.width && layer.m_object.height == committedObject.height);
};
bool hasStaleLayers = false;
for (auto it = m_layers.begin(); it != m_layers.end(); ++it) {
++it->value->m_age;
hasStaleLayers |= isStaleLayer(*it);
}
if (hasStaleLayers)
m_layers.removeIf(isStaleLayer);
}
void TextureMapperPlatformLayerProxyDMABuf::pushDMABuf(Ref<DMABufLayer>&& dmabufLayer)
{
ASSERT(m_lock.isHeld());
// The pending and just-pushed layers shouldn't be the same. But if they are, don't perform a release.
ASSERT(!m_pendingLayer || m_pendingLayer != dmabufLayer.ptr());
if (m_pendingLayer && m_pendingLayer != dmabufLayer.ptr())
m_pendingLayer->release();
m_pendingLayer = WTFMove(dmabufLayer);
if (m_compositor)
m_compositor->onNewBufferAvailable();
}
TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::DMABufLayer(DMABufObject&& object, TextureMapperGL::Flags flags)
: m_object(WTFMove(object))
, m_flags(flags)
{ }
TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::~DMABufLayer() = default;
void TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::paintToTextureMapper(TextureMapper& textureMapper, const FloatRect& targetRect, const TransformationMatrix& modelViewMatrix, float opacity)
{
if (!m_imageData)
return;
static constexpr std::array<GLfloat, 16> s_bt601ConversionMatrix {
1.164383561643836, 0.0, 1.596026785714286, -0.874202217873451,
1.164383561643836, -0.391762290094914, -0.812967647237771, 0.531667823499146,
1.164383561643836, 2.017232142857143, 0.0, -1.085630789302022,
0.0, 0.0, 0.0, 1.0,
};
static constexpr std::array<GLfloat, 16> s_bt709ConversionMatrix {
1.164383561643836, 0.0, 1.792741071428571, -0.972945075016308,
1.164383561643836, -0.213248614273730, -0.532909328559444, 0.301482665475862,
1.164383561643836, 2.112401785714286, 0.0, -1.133402217873451,
0.0, 0.0, 0.0, 1.0,
};
static constexpr std::array<GLfloat, 16> s_bt2020ConversionMatrix {
1.164383561643836, 0.0, 1.678674107142857, -0.915687932159165,
1.164383561643836, -0.187326104219343, -0.650424318505057, 0.347458498519301,
1.164383561643836, 2.141772321428571, 0.0, -1.148145075016308,
0.0, 0.0, 0.0, 1.0,
};
static constexpr std::array<GLfloat, 16> s_smpte240MConversionMatrix {
1.164383561643836, 0.0, 1.793651785714286, -0.973402217873451,
1.164383561643836, -0.256532845251675, -0.542724809537390, 0.328136638536074,
1.164383561643836, 2.07984375, 0.0, -1.117059360730593,
0.0, 0.0, 0.0, 1.0,
};
// Based on the specified colorspace, a YUV-to-RGB matrix is chosen. The default is the BT.601 matrix.
// Invalid or SRGB colorspace defaults to that as well, but in case of RGBA-like formats, the matrix
// of course goes unused. This is complemented with the below assert that for those formats the specified
// colorspace is either invalid or SRGB.
const std::array<GLfloat, 16>& yuvToRGB =
[&] {
switch (m_object.colorSpace) {
case DMABufColorSpace::Invalid:
case DMABufColorSpace::SRGB:
break;
case DMABufColorSpace::BT601:
return s_bt601ConversionMatrix;
case DMABufColorSpace::BT709:
return s_bt709ConversionMatrix;
case DMABufColorSpace::BT2020:
return s_bt2020ConversionMatrix;
case DMABufColorSpace::SMPTE240M:
return s_smpte240MConversionMatrix;
}
return s_bt601ConversionMatrix;
}();
TextureMapperGL& texmapGL = static_cast<TextureMapperGL&>(textureMapper);
auto& data = *m_imageData;
switch (m_object.format.fourcc) {
case DMABufFormat::FourCC::XRGB8888:
case DMABufFormat::FourCC::XBGR8888:
case DMABufFormat::FourCC::ARGB8888:
case DMABufFormat::FourCC::ABGR8888:
// Either no colorspace or the SRGB colorspace was defined for this object. Other options are not meaningful.
ASSERT(m_object.colorSpace == DMABufColorSpace::Invalid || m_object.colorSpace == DMABufColorSpace::SRGB);
texmapGL.drawTexture(data.texture[0], m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity);
break;
case DMABufFormat::FourCC::I420:
case DMABufFormat::FourCC::Y444:
case DMABufFormat::FourCC::Y41B:
case DMABufFormat::FourCC::Y42B:
texmapGL.drawTexturePlanarYUV(std::array<GLuint, 3> { data.texture[0], data.texture[1], data.texture[2] },
yuvToRGB, m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity, std::nullopt);
break;
case DMABufFormat::FourCC::YV12:
texmapGL.drawTexturePlanarYUV(std::array<GLuint, 3> { data.texture[0], data.texture[2], data.texture[1] },
yuvToRGB, m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity, std::nullopt);
break;
case DMABufFormat::FourCC::A420:
texmapGL.drawTexturePlanarYUV(std::array<GLuint, 3> { data.texture[0], data.texture[1], data.texture[2] },
yuvToRGB, m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity, data.texture[3]);
break;
case DMABufFormat::FourCC::NV12:
case DMABufFormat::FourCC::NV21:
texmapGL.drawTextureSemiPlanarYUV(std::array<GLuint, 2> { data.texture[0], data.texture[1] },
(m_object.format.fourcc == DMABufFormat::FourCC::NV21),
yuvToRGB, m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity);
break;
case DMABufFormat::FourCC::YUY2:
case DMABufFormat::FourCC::YVYU:
case DMABufFormat::FourCC::UYVY:
case DMABufFormat::FourCC::VYUY:
case DMABufFormat::FourCC::VUYA:
case DMABufFormat::FourCC::AYUV:
texmapGL.drawTexturePackedYUV(data.texture[0],
yuvToRGB, m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity);
break;
default:
break;
}
}
std::unique_ptr<TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::EGLImageData> TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::createEGLImageData(DMABufObject& object)
{
using EGLImageData = TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::EGLImageData;
auto& platformDisplay = PlatformDisplay::sharedDisplayForCompositing();
EGLDisplay eglDisplay = platformDisplay.eglDisplay();
EGLImageKHR image[DMABufFormat::c_maxPlanes];
for (unsigned i = 0; i < object.format.numPlanes; ++i) {
auto attributes = DMABufEGLUtilities::constructEGLCreateImageAttributes(object, i,
DMABufEGLUtilities::PlaneModifiersUsage { platformDisplay.eglExtensions().EXT_image_dma_buf_import_modifiers });
image[i] = createImageKHR()(eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attributes.data());
}
auto imageData = makeUnique<EGLImageData>();
auto& data = *imageData;
data.width = object.width;
data.height = object.height;
data.numImages = object.format.numPlanes;
glGenTextures(data.numImages, data.texture.data());
for (unsigned i = 0; i < data.numImages; ++i) {
data.image[i] = image[i];
glBindTexture(GL_TEXTURE_2D, data.texture[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, data.image[i]);
glBindTexture(GL_TEXTURE_2D, 0);
}
return imageData;
}
} // namespace WebCore
#endif // USE(COORDINATED_GRAPHICS) && USE(TEXTURE_MAPPER_DMABUF)