WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
bug-237328-20220310120233.patch (text/plain), 78.48 KB, created by
Zan Dobersek
on 2022-03-10 03:02:35 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Zan Dobersek
Created:
2022-03-10 03:02:35 PST
Size:
78.48 KB
patch
obsolete
>Subversion Revision: 291090 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 1ce0a921597f9e2ea4730528e8eb194eb09b0c14..78d2897c9fbae5d3a46ddd96d3efe54a637f66a9 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,131 @@ >+2022-03-10 Zan Dobersek <zdobersek@igalia.com> >+ >+ [GTK][WPE] Provide DMABuf-based composition layers, DMABufVideoSink integration >+ https://bugs.webkit.org/show_bug.cgi?id=237328 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Introduce TextureMapperPlatformLayerProxyDMABuf, a TextureMapper proxy >+ implementation that handles platform layers backed by dmabuf objects. >+ This will be used to handle display of dmabuf producers like GStreamer >+ pipelines or ANGLE-backed WebGL contexts. The DMABufLayer class is the >+ platform layer object, handling display of the actual dmabuf. The proxy >+ itself is tasked with creating and caching such layers for any dmabuf >+ object that originates from the producer. >+ >+ For each dmabuf object pushed into the proxy, the according DMABufLayer >+ object is spawned and cached for future reuse, expecting the producer >+ to be able to provide dmabufs from a pool or swapchain of these objects. >+ The cache is emptied whenever dmabufs go unused for a certain amount of >+ swaps, or if the size of the provided dmabufs changes. >+ >+ DMABufFormat provides information for different DRM formats, for >+ instance how different planes for specific formats are formatted and >+ sized. >+ >+ DMABufObject is a class that handles a given grouping of dmabufs >+ according to the specified format. It has an associated handle value, >+ size, and per-plane dmabuf file descriptors, offsets, strides and >+ modifiers, along with a DMABufReleaseFlag instance. >+ >+ DMABufReleaseFlag objects are used as an eventfd-based release mechanism >+ that indicates to the producer that the given DMABufObject has been >+ presented and subsequently released and is thus available for reuse. >+ >+ GBMBufferSwapchain provides a custom swapchain implementation that's >+ based on libgbm functionality. Each such swapchain has a capacity >+ specified during construction, with the capacities of four or eight >+ currently supported. getBuffer() is called by the producer to obtain a >+ buffer object that's then used for backing of specific content like >+ ANGLE execution or media's software-decoded video frames. Buffers >+ obtained this way are moved over to the end of the buffer array, with >+ the expectation of reusing them once they are released by the >+ composition engine. >+ >+ MediaPlayerPrivateGStreamer implementation is enhanced to use the >+ recently-added WebKitDMABufVideoSink element if its use is enabled >+ through the development-purpose environment variable and if the >+ necessary GStreamer facilities are present on the system. When these >+ conditions are met, the Nicosia::ContentLayer instance is also >+ constructed with a TextureMapperPlatformLayerProxyDMABuf instance, since >+ we expect to present the dmabufs through that functionality. >+ >+ With the WebKitDMABufVideoSink, the incoming samples are either based >+ on dmabufs (in case of dmabuf-capable hardware decoder) or are just raw >+ data (most likely coming from a software-based decoder). In case of >+ dmabufs we can use the GstMemory pointer as the handle through which >+ the relevant data is cached in TextureMapperPlatformLayerProxyDMABuf, >+ and upon the first occurrence we retrieve all the relevant dmabuf data >+ so that it can be used by that proxy implementation to construct a >+ renderable EGLImage. >+ >+ In case of software-decoded raw data, we have to use the swapchain >+ object and copy the data on a per-plane basis for each such sample, >+ finally pushing the dmabuf data of that swapchain buffer into the >+ TextureMapperPlatformLayerProxyDMABuf instance. >+ >+ * SourcesGTK.txt: >+ * SourcesWPE.txt: >+ * platform/TextureMapper.cmake: >+ * platform/graphics/gbm/DMABufFormat.h: Added. >+ (WebCore::DMABufFormatImpl::createFourCC): >+ (WebCore::DMABufFormat::planeWidth const): >+ (WebCore::DMABufFormat::planeHeight const): >+ (WebCore::DMABufFormat::Plane::Plane): >+ (WebCore::DMABufFormatImpl::createSinglePlaneRGBA): >+ (WebCore::DMABufFormatImpl::definePlane): >+ (WebCore::DMABufFormat::instantiate): >+ (WebCore::DMABufFormat::create): >+ * platform/graphics/gbm/DMABufObject.h: Added. >+ (WebCore::DMABufObject::DMABufObject): >+ (WebCore::DMABufObject::~DMABufObject): >+ (WebCore::DMABufObject::operator=): >+ * platform/graphics/gbm/DMABufReleaseFlag.h: Added. >+ (WebCore::DMABufReleaseFlag::DMABufReleaseFlag): >+ (WebCore::DMABufReleaseFlag::~DMABufReleaseFlag): >+ (WebCore::DMABufReleaseFlag::operator=): >+ (WebCore::DMABufReleaseFlag::dup const): >+ (WebCore::DMABufReleaseFlag::released const): >+ (WebCore::DMABufReleaseFlag::release): >+ * platform/graphics/gbm/GBMBufferSwapchain.cpp: Added. >+ (WebCore::GBMBufferSwapchain::GBMBufferSwapchain): >+ (WebCore::GBMBufferSwapchain::getBuffer): >+ (WebCore::GBMBufferSwapchain::Buffer::Buffer): >+ (WebCore::GBMBufferSwapchain::Buffer::createDMABufObject const): >+ (WebCore::GBMBufferSwapchain::Buffer::PlaneData::~PlaneData): >+ * platform/graphics/gbm/GBMBufferSwapchain.h: Added. >+ (WebCore::GBMBufferSwapchain::Buffer::handle const): >+ (WebCore::GBMBufferSwapchain::Buffer::numPlanes const): >+ (WebCore::GBMBufferSwapchain::Buffer::planeData const): >+ * platform/graphics/gstreamer/DMABufVideoSinkGStreamer.cpp: >+ (webKitDMABufVideoSinkIsEnabled): >+ (webKitDMABufVideoSinkSetMediaPlayerPrivate): >+ * platform/graphics/gstreamer/DMABufVideoSinkGStreamer.h: >+ * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp: >+ (WebCore::MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer): >+ (WebCore::MediaPlayerPrivateGStreamer::platformLayer const): >+ (WebCore::fourccValue): >+ (WebCore::MediaPlayerPrivateGStreamer::pushDMABufToCompositor): >+ (WebCore::MediaPlayerPrivateGStreamer::triggerRepaint): >+ (WebCore::MediaPlayerPrivateGStreamer::createVideoSinkDMABuf): >+ (WebCore::MediaPlayerPrivateGStreamer::createVideoSink): >+ * platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h: >+ * platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.cpp: >+ (Nicosia::ContentLayerTextureMapperImpl::createFactory): >+ * platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.h: >+ * platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.cpp: Added. >+ (WebCore::createImageKHR): >+ (WebCore::destroyImageKHR): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::EGLImageData::~EGLImageData): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::activateOnCompositingThread): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::invalidate): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::swapBuffer): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::pushDMABuf): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::DMABufLayer): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::paintToTextureMapper): >+ (WebCore::TextureMapperPlatformLayerProxyDMABuf::DMABufLayer::createEGLImageData): >+ * platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.h: Added. >+ > 2022-03-09 Antoine Quint <graouts@webkit.org> > > [web-animations] counter-increment should support discrete animation >diff --git a/Source/WebCore/SourcesGTK.txt b/Source/WebCore/SourcesGTK.txt >index 4e30335cfad9ed79b0573731f52412cbb9d26b36..b5e58ccd7619e47c77cb7b2c6d7ba581cab02563 100644 >--- a/Source/WebCore/SourcesGTK.txt >+++ b/Source/WebCore/SourcesGTK.txt >@@ -101,6 +101,7 @@ platform/graphics/egl/GLContextEGLLibWPE.cpp @no-unify > platform/graphics/egl/GLContextEGLWayland.cpp @no-unify > platform/graphics/egl/GLContextEGLX11.cpp @no-unify > >+platform/graphics/gbm/GBMBufferSwapchain.cpp > platform/graphics/gbm/GBMDevice.cpp > > platform/graphics/glx/GLContextGLX.cpp @no-unify >diff --git a/Source/WebCore/SourcesWPE.txt b/Source/WebCore/SourcesWPE.txt >index 57fa7ca67c4dea4a9e5b9845e72ae25873d8cf55..f8c8c70492fb09d109aed1184b7c69ca25eb2f27 100644 >--- a/Source/WebCore/SourcesWPE.txt >+++ b/Source/WebCore/SourcesWPE.txt >@@ -89,6 +89,7 @@ platform/graphics/PlatformDisplay.cpp > platform/graphics/egl/GLContextEGL.cpp > platform/graphics/egl/GLContextEGLLibWPE.cpp > >+platform/graphics/gbm/GBMBufferSwapchain.cpp > platform/graphics/gbm/GBMDevice.cpp > > platform/graphics/libwpe/PlatformDisplayLibWPE.cpp >diff --git a/Source/WebCore/platform/TextureMapper.cmake b/Source/WebCore/platform/TextureMapper.cmake >index 4e0aebe19747788d640dc995a66c43bba2c91816..73f69c509470ef8ae979ccd97d61a0f788a547f5 100644 >--- a/Source/WebCore/platform/TextureMapper.cmake >+++ b/Source/WebCore/platform/TextureMapper.cmake >@@ -34,6 +34,7 @@ list(APPEND WebCore_PRIVATE_FRAMEWORK_HEADERS > platform/graphics/texmap/TextureMapperLayer.h > platform/graphics/texmap/TextureMapperPlatformLayer.h > platform/graphics/texmap/TextureMapperPlatformLayerProxy.h >+ platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.h > platform/graphics/texmap/TextureMapperPlatformLayerProxyGL.h > platform/graphics/texmap/TextureMapperPlatformLayerProxyProvider.h > platform/graphics/texmap/TextureMapperSolidColorLayer.h >@@ -60,6 +61,7 @@ if (USE_COORDINATED_GRAPHICS) > platform/graphics/texmap/TextureMapperPlatformLayerBuffer.cpp > platform/graphics/texmap/TextureMapperPlatformLayerDmabuf.cpp > platform/graphics/texmap/TextureMapperPlatformLayerProxy.cpp >+ platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.cpp > platform/graphics/texmap/TextureMapperPlatformLayerProxyGL.cpp > > platform/graphics/texmap/coordinated/CoordinatedBackingStore.cpp >diff --git a/Source/WebCore/platform/graphics/gbm/DMABufFormat.h b/Source/WebCore/platform/graphics/gbm/DMABufFormat.h >new file mode 100644 >index 0000000000000000000000000000000000000000..dcd725a970153c4ff78a7bb48e07b8c0e7f7c430 >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gbm/DMABufFormat.h >@@ -0,0 +1,372 @@ >+/* >+ * 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. >+ */ >+ >+#pragma once >+ >+#include <cstddef> >+#include <cstdint> >+ >+namespace WebCore { >+ >+namespace DMABufFormatImpl { >+ >+static constexpr uint32_t createFourCC(char a, char b, char c, char d) >+{ >+ return uint32_t(a) | (uint32_t(b) << 8) | (uint32_t(c) << 16) | (uint32_t(d) << 24); >+} >+ >+} >+ >+struct DMABufFormat { >+ enum class FourCC : uint32_t { >+ Invalid = 0, >+ >+ R8 = DMABufFormatImpl::createFourCC('R', '8', ' ', ' '), >+ GR88 = DMABufFormatImpl::createFourCC('G', 'R', '8', '8'), >+ >+ XRGB8888 = DMABufFormatImpl::createFourCC('X', 'R', '2', '4'), >+ XBGR8888 = DMABufFormatImpl::createFourCC('X', 'B', '2', '4'), >+ RGBX8888 = DMABufFormatImpl::createFourCC('R', 'X', '2', '4'), >+ BGRX8888 = DMABufFormatImpl::createFourCC('B', 'X', '2', '4'), >+ ARGB8888 = DMABufFormatImpl::createFourCC('A', 'R', '2', '4'), >+ ABGR8888 = DMABufFormatImpl::createFourCC('A', 'B', '2', '4'), >+ RGBA8888 = DMABufFormatImpl::createFourCC('R', 'A', '2', '4'), >+ BGRA8888 = DMABufFormatImpl::createFourCC('B', 'A', '2', '4'), >+ RGB888 = DMABufFormatImpl::createFourCC('R', 'G', '2', '4'), >+ BGR888 = DMABufFormatImpl::createFourCC('B', 'G', '2', '4'), >+ >+ I420 = DMABufFormatImpl::createFourCC('I', '4', '2', '0'), >+ YV12 = DMABufFormatImpl::createFourCC('Y', 'V', '1', '2'), >+ A420 = DMABufFormatImpl::createFourCC('A', '4', '2', '0'), >+ NV12 = DMABufFormatImpl::createFourCC('N', 'V', '1', '2'), >+ NV21 = DMABufFormatImpl::createFourCC('N', 'V', '2', '1'), >+ >+ YUY2 = DMABufFormatImpl::createFourCC('Y', 'U', 'Y', '2'), >+ YVYU = DMABufFormatImpl::createFourCC('Y', 'V', 'Y', 'U'), >+ UYVY = DMABufFormatImpl::createFourCC('U', 'Y', 'V', 'Y'), >+ VYUY = DMABufFormatImpl::createFourCC('V', 'Y', 'U', 'Y'), >+ VUYA = DMABufFormatImpl::createFourCC('V', 'U', 'Y', 'A'), >+ AYUV = DMABufFormatImpl::createFourCC('A', 'Y', 'U', 'V'), >+ >+ Y444 = DMABufFormatImpl::createFourCC('Y', '4', '4', '4'), >+ Y41B = DMABufFormatImpl::createFourCC('Y', '4', '1', 'B'), >+ Y42B = DMABufFormatImpl::createFourCC('Y', '4', '2', 'B'), >+ }; >+ >+ static constexpr unsigned c_maxPlanes = 4; >+ >+ template<FourCC> static DMABufFormat create() = delete; >+ static DMABufFormat create(uint32_t); >+ >+ template<FourCC fourccValue, typename... PlaneDefinitionTypes> >+ static DMABufFormat instantiate(); >+ >+ FourCC fourcc { FourCC::Invalid }; >+ unsigned numPlanes { 0 }; >+ >+ uint32_t planeWidth(unsigned planeIndex, uint32_t width) const >+ { >+ ASSERT(planeIndex < c_maxPlanes); >+ return (width >> planes[planeIndex].horizontalSubsampling); >+ } >+ >+ uint32_t planeHeight(unsigned planeIndex, uint32_t height) const >+ { >+ ASSERT(planeIndex < c_maxPlanes); >+ return (height >> planes[planeIndex].verticalSubsampling); >+ } >+ >+ template<FourCC fourccValue, unsigned hsValue, unsigned vsValue> >+ struct PlaneDefinition { >+ static constexpr FourCC fourcc = fourccValue; >+ static constexpr unsigned horizontalSubsampling = hsValue; >+ static constexpr unsigned verticalSubsampling = vsValue; >+ }; >+ >+ struct Plane { >+ Plane() = default; >+ >+ template<typename PlaneDefitionType> >+ Plane(const PlaneDefitionType&) >+ : fourcc(PlaneDefitionType::fourcc) >+ , horizontalSubsampling(PlaneDefitionType::horizontalSubsampling) >+ , verticalSubsampling(PlaneDefitionType::verticalSubsampling) >+ { } >+ >+ FourCC fourcc { FourCC::Invalid }; >+ unsigned horizontalSubsampling { 0 }; >+ unsigned verticalSubsampling { 0 }; >+ }; >+ std::array<Plane, c_maxPlanes> planes; >+}; >+ >+namespace DMABufFormatImpl { >+ >+template<DMABufFormat::FourCC fourccValue> >+inline DMABufFormat createSinglePlaneRGBA() >+{ >+ return DMABufFormat::instantiate<fourccValue, >+ DMABufFormat::PlaneDefinition<fourccValue, 0, 0>>(); >+} >+ >+template<size_t Index, size_t Size, typename PlaneDefinitionType, typename... PlaneDefinitionTypes> >+void definePlane(std::array<DMABufFormat::Plane, DMABufFormat::c_maxPlanes>& planes) >+{ >+ new (&planes[Index]) DMABufFormat::Plane(PlaneDefinitionType { }); >+ if constexpr ((Index + 1) < Size) >+ definePlane<Index + 1, Size, PlaneDefinitionTypes...>(planes); >+} >+ >+} >+ >+template<DMABufFormat::FourCC fourccValue, typename... PlaneDefinitionTypes> >+inline DMABufFormat DMABufFormat::instantiate() >+{ >+ static_assert(sizeof...(PlaneDefinitionTypes) <= 4); >+ >+ DMABufFormat format; >+ format.fourcc = fourccValue; >+ format.numPlanes = sizeof...(PlaneDefinitionTypes); >+ DMABufFormatImpl::definePlane<0, sizeof...(PlaneDefinitionTypes), PlaneDefinitionTypes...>(format.planes); >+ return format; >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::XRGB8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::XRGB8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::XBGR8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::XBGR8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::RGBX8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::RGBX8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::BGRX8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::BGRX8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::ARGB8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::ARGB8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::ABGR8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::ABGR8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::RGBA8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::RGBA8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::BGRA8888>() >+{ >+ return DMABufFormatImpl::createSinglePlaneRGBA<FourCC::BGRA8888>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::I420>() >+{ >+ return DMABufFormat::instantiate<FourCC::I420, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 1, 1>, >+ PlaneDefinition<FourCC::R8, 1, 1>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::YV12>() >+{ >+ return DMABufFormat::instantiate<FourCC::YV12, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 1, 1>, >+ PlaneDefinition<FourCC::R8, 1, 1>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::A420>() >+{ >+ return DMABufFormat::instantiate<FourCC::A420, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 1, 1>, >+ PlaneDefinition<FourCC::R8, 1, 1>, >+ PlaneDefinition<FourCC::R8, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::NV12>() >+{ >+ return DMABufFormat::instantiate<FourCC::NV12, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::GR88, 1, 1>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::NV21>() >+{ >+ return DMABufFormat::instantiate<FourCC::NV21, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::GR88, 1, 1>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::YUY2>() >+{ >+ return DMABufFormat::instantiate<FourCC::YUY2, >+ PlaneDefinition<FourCC::GR88, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::YVYU>() >+{ >+ return DMABufFormat::instantiate<FourCC::YVYU, >+ PlaneDefinition<FourCC::GR88, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::UYVY>() >+{ >+ return DMABufFormat::instantiate<FourCC::UYVY, >+ PlaneDefinition<FourCC::GR88, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::VYUY>() >+{ >+ return DMABufFormat::instantiate<FourCC::YVYU, >+ PlaneDefinition<FourCC::GR88, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::VUYA>() >+{ >+ return DMABufFormat::instantiate<FourCC::VUYA, >+ PlaneDefinition<FourCC::ABGR8888, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::AYUV>() >+{ >+ return DMABufFormat::instantiate<FourCC::VUYA, >+ PlaneDefinition<FourCC::ABGR8888, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::Y444>() >+{ >+ return DMABufFormat::instantiate<FourCC::Y444, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 0, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::Y41B>() >+{ >+ return DMABufFormat::instantiate<FourCC::Y41B, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 2, 0>, >+ PlaneDefinition<FourCC::R8, 2, 0>>(); >+} >+ >+template<> >+inline DMABufFormat DMABufFormat::create<DMABufFormat::FourCC::Y42B>() >+{ >+ return DMABufFormat::instantiate<FourCC::Y42B, >+ PlaneDefinition<FourCC::R8, 0, 0>, >+ PlaneDefinition<FourCC::R8, 1, 0>, >+ PlaneDefinition<FourCC::R8, 1, 0>>(); >+} >+ >+inline DMABufFormat DMABufFormat::create(uint32_t fourccValue) >+{ >+ switch (fourccValue) { >+ case uint32_t(FourCC::XRGB8888): >+ return create<FourCC::XRGB8888>(); >+ case uint32_t(FourCC::XBGR8888): >+ return create<FourCC::XBGR8888>(); >+ case uint32_t(FourCC::RGBX8888): >+ return create<FourCC::RGBX8888>(); >+ case uint32_t(FourCC::BGRX8888): >+ return create<FourCC::BGRX8888>(); >+ case uint32_t(FourCC::ARGB8888): >+ return create<FourCC::ARGB8888>(); >+ case uint32_t(FourCC::ABGR8888): >+ return create<FourCC::ABGR8888>(); >+ case uint32_t(FourCC::RGBA8888): >+ return create<FourCC::RGBA8888>(); >+ case uint32_t(FourCC::BGRA8888): >+ return create<FourCC::BGRA8888>(); >+ case uint32_t(FourCC::I420): >+ return create<FourCC::I420>(); >+ case uint32_t(FourCC::YV12): >+ return create<FourCC::YV12>(); >+ case uint32_t(FourCC::A420): >+ return create<FourCC::A420>(); >+ case uint32_t(FourCC::NV12): >+ return create<FourCC::NV12>(); >+ case uint32_t(FourCC::NV21): >+ return create<FourCC::NV21>(); >+ case uint32_t(FourCC::YUY2): >+ return create<FourCC::YUY2>(); >+ case uint32_t(FourCC::YVYU): >+ return create<FourCC::YVYU>(); >+ case uint32_t(FourCC::UYVY): >+ return create<FourCC::UYVY>(); >+ case uint32_t(FourCC::VYUY): >+ return create<FourCC::UYVY>(); >+ case uint32_t(FourCC::VUYA): >+ return create<FourCC::VUYA>(); >+ case uint32_t(FourCC::AYUV): >+ return create<FourCC::AYUV>(); >+ case uint32_t(FourCC::Y444): >+ return create<FourCC::Y444>(); >+ case uint32_t(FourCC::Y41B): >+ return create<FourCC::Y41B>(); >+ case uint32_t(FourCC::Y42B): >+ return create<FourCC::Y42B>(); >+ default: >+ break; >+ } >+ >+ return { }; >+} >+ >+} // namespace WebCore >diff --git a/Source/WebCore/platform/graphics/gbm/DMABufObject.h b/Source/WebCore/platform/graphics/gbm/DMABufObject.h >new file mode 100644 >index 0000000000000000000000000000000000000000..08644444394f7b9d9ff43dcbdc392abaa77cc5a7 >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gbm/DMABufObject.h >@@ -0,0 +1,92 @@ >+/* >+ * 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. >+ */ >+ >+#pragma once >+ >+#include "DMABufFormat.h" >+#include "DMABufReleaseFlag.h" >+#include <array> >+#include <cstddef> >+#include <cstdint> >+#include <unistd.h> >+ >+namespace WebCore { >+ >+struct DMABufObject { >+ DMABufObject(uintptr_t handle) >+ : handle(handle) >+ { } >+ >+ ~DMABufObject() >+ { >+ for (unsigned i = 0; i < format.numPlanes; ++i) { >+ if (fd[i] != -1) >+ close(fd[i]); >+ } >+ } >+ >+ DMABufObject(const DMABufObject&) = delete; >+ DMABufObject& operator=(const DMABufObject&) = delete; >+ >+ DMABufObject(DMABufObject&& o) >+ : handle(o.handle) >+ , format(o.format) >+ , width(o.width) >+ , height(o.height) >+ , releaseFlag(WTFMove(o.releaseFlag)) >+ { >+ for (unsigned i = 0; i < format.numPlanes; ++i) { >+ fd[i] = o.fd[i]; >+ o.fd[i] = -1; >+ >+ offset[i] = o.offset[i]; >+ stride[i] = o.stride[i]; >+ modifier[i] = o.modifier[i]; >+ } >+ } >+ >+ DMABufObject& operator=(DMABufObject&& o) >+ { >+ if (this == &o) >+ return *this; >+ >+ this->~DMABufObject(); >+ new (this) DMABufObject(WTFMove(o)); >+ return *this; >+ } >+ >+ uintptr_t handle { 0 }; >+ DMABufFormat format { }; >+ uint32_t width { 0 }; >+ uint32_t height { 0 }; >+ DMABufReleaseFlag releaseFlag { }; >+ std::array<int, DMABufFormat::c_maxPlanes> fd { -1, -1, -1, -1 }; >+ std::array<size_t, DMABufFormat::c_maxPlanes> offset { 0, 0, 0, 0 }; >+ std::array<uint32_t, DMABufFormat::c_maxPlanes> stride { 0, 0, 0, 0 }; >+ std::array<uint64_t, DMABufFormat::c_maxPlanes> modifier { 0, 0, 0, 0 }; >+}; >+ >+} // namespace WebCore >diff --git a/Source/WebCore/platform/graphics/gbm/DMABufReleaseFlag.h b/Source/WebCore/platform/graphics/gbm/DMABufReleaseFlag.h >new file mode 100644 >index 0000000000000000000000000000000000000000..cf4d7760053f99c5587650a086aab97104875f7e >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gbm/DMABufReleaseFlag.h >@@ -0,0 +1,102 @@ >+/* >+ * 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. >+ */ >+ >+#pragma once >+ >+#include <sys/eventfd.h> >+#include <wtf/UniStdExtras.h> >+ >+namespace WebCore { >+ >+struct DMABufReleaseFlag { >+ DMABufReleaseFlag() = default; >+ >+ enum InitializeTag { Initialize }; >+ DMABufReleaseFlag(InitializeTag) >+ { >+ fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); >+ } >+ >+ ~DMABufReleaseFlag() >+ { >+ if (fd != -1) >+ close(fd); >+ fd = -1; >+ } >+ >+ DMABufReleaseFlag(const DMABufReleaseFlag&) = delete; >+ DMABufReleaseFlag& operator=(const DMABufReleaseFlag&) = delete; >+ >+ DMABufReleaseFlag(DMABufReleaseFlag&& o) >+ { >+ fd = o.fd; >+ o.fd = -1; >+ } >+ >+ DMABufReleaseFlag& operator=(DMABufReleaseFlag&& o) >+ { >+ if (this == &o) >+ return *this; >+ >+ this->~DMABufReleaseFlag(); >+ new (this) DMABufReleaseFlag(WTFMove(o)); >+ return *this; >+ } >+ >+ DMABufReleaseFlag dup() const >+ { >+ if (fd == -1) >+ return { }; >+ >+ DMABufReleaseFlag flag; >+ flag.fd = dupCloseOnExec(fd); >+ return flag; >+ } >+ >+ bool released() const >+ { >+ if (fd == -1) >+ return true; >+ >+ uint64_t value { 0 }; >+ if (read(fd, &value, sizeof(uint64_t)) == sizeof(uint64_t)) >+ return !!value; >+ return false; >+ } >+ >+ void release() >+ { >+ if (fd == -1) >+ return; >+ >+ uint64_t value { 1 }; >+ write(fd, &value, sizeof(uint64_t)); >+ } >+ >+ int fd { -1 }; >+}; >+ >+} // namespace WebCore >diff --git a/Source/WebCore/platform/graphics/gbm/GBMBufferSwapchain.cpp b/Source/WebCore/platform/graphics/gbm/GBMBufferSwapchain.cpp >new file mode 100644 >index 0000000000000000000000000000000000000000..24da7ed633a6febebdf1d91885f35344181040ce >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gbm/GBMBufferSwapchain.cpp >@@ -0,0 +1,172 @@ >+/* >+ * 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 "GBMBufferSwapchain.h" >+ >+#include "GBMDevice.h" >+#include <gbm.h> >+ >+namespace WebCore { >+ >+GBMBufferSwapchain::GBMBufferSwapchain(BufferSwapchainSize size) >+{ >+ ASSERT(unsigned(size) < c_maxBuffers); >+ m_array.size = unsigned(size); >+} >+ >+GBMBufferSwapchain::~GBMBufferSwapchain() = default; >+ >+RefPtr<GBMBufferSwapchain::Buffer> GBMBufferSwapchain::getBuffer(const BufferDescription& description) >+{ >+ auto& device = GBMDevice::get(); >+ >+ // If the description of the requested byffers has changed, update the description to the new one and wreck the existing buffers. >+ // This should handle changes in format or dimension of the buffers. >+ if (description.format.fourcc != m_array.description.format.fourcc || description.width != m_array.description.width || description.height != m_array.description.height) { >+ m_array.description = description; >+ m_array.object = { }; >+ } >+ >+ // Swapchain was asked to provide a buffer. The buffer array is traversed to find one. >+ for (unsigned i = 0; i < m_array.size; ++i) { >+ if (!m_array.object[i]) { >+ // If no buffer was spawned yet at this location, we do that, and return it. >+ auto buffer = adoptRef(*new Buffer(m_handleGenerator++, description)); >+ m_array.object[i] = buffer.copyRef(); >+ >+ // Fill out the buffer's description and plane information for known and supported formats. >+ switch (description.format.fourcc) { >+ case DMABufFormat::FourCC::XRGB8888: >+ case DMABufFormat::FourCC::XBGR8888: >+ case DMABufFormat::FourCC::RGBX8888: >+ case DMABufFormat::FourCC::BGRX8888: >+ case DMABufFormat::FourCC::ARGB8888: >+ case DMABufFormat::FourCC::ABGR8888: >+ case DMABufFormat::FourCC::RGBA8888: >+ case DMABufFormat::FourCC::BGRA8888: >+ case DMABufFormat::FourCC::I420: >+ case DMABufFormat::FourCC::YV12: >+ case DMABufFormat::FourCC::A420: >+ case DMABufFormat::FourCC::NV12: >+ case DMABufFormat::FourCC::NV21: >+ case DMABufFormat::FourCC::YUY2: >+ case DMABufFormat::FourCC::YVYU: >+ case DMABufFormat::FourCC::UYVY: >+ case DMABufFormat::FourCC::VYUY: >+ case DMABufFormat::FourCC::VUYA: >+ case DMABufFormat::FourCC::AYUV: >+ case DMABufFormat::FourCC::Y444: >+ case DMABufFormat::FourCC::Y41B: >+ case DMABufFormat::FourCC::Y42B: >+ buffer->m_description.format.numPlanes = description.format.numPlanes; >+ for (unsigned i = 0; i < buffer->m_description.format.numPlanes; ++i) { >+ buffer->m_planes[i].fourcc = description.format.planes[i].fourcc; >+ buffer->m_planes[i].width = description.format.planeWidth(i, description.width); >+ buffer->m_planes[i].height = description.format.planeHeight(i, description.height); >+ } >+ break; >+ default: >+ return nullptr; >+ } >+ >+ // For each plane, we spawn a gbm_bo object of the appropriate size and format. >+ // TODO: GBM_BO_USE_LINEAR will be needed when transferring memory into the bo (e.g. copying >+ // over the software-decoded video data), but might not be required for backing e.g. ANGLE rendering. >+ for (unsigned i = 0; i < buffer->m_description.format.numPlanes; ++i) { >+ auto& plane = buffer->m_planes[i]; >+ plane.bo = gbm_bo_create(device.device(), plane.width, plane.height, uint32_t(plane.fourcc), GBM_BO_USE_LINEAR); >+ plane.stride = gbm_bo_get_stride(plane.bo); >+ } >+ >+ // Lock the buffer and return it. >+ buffer->m_state.locked = true; >+ return buffer; >+ } >+ >+ // There is already an existing buffer at this location. If marked as locked, update its state by reading >+ // the release flag. If still locked after that, we have to continue over to the next candidate. >+ { >+ auto& buffer = m_array.object[i]; >+ if (buffer->m_state.locked) >+ buffer->m_state.locked = !buffer->m_state.releaseFlag.released(); >+ >+ if (buffer->m_state.locked) >+ continue; >+ } >+ >+ // This buffer was unlocked, so it can be used. Lock and return it. >+ auto buffer = m_array.object[i].copyRef(); >+ buffer->m_state.locked = true; >+ >+ // Swap out the located buffer to the end of the buffer queue. When the next buffer is requested from this >+ // swapchain, the previously-used buffers will be traversed and tested for release first, meaning there's a >+ // higher chance we will be able to reuse them, without the penalty of having to traverse to deep into the >+ // swapchain array. >+ for (++i; i < m_array.size && !!m_array.object[i]; ++i) >+ std::swap(m_array.object[i - 1], m_array.object[i]); >+ >+ return buffer; >+ } >+ >+ return nullptr; >+} >+ >+GBMBufferSwapchain::Buffer::Buffer(uint32_t handle, const BufferDescription& description) >+ : m_handle(handle) >+ , m_description(description) >+{ >+ m_state.releaseFlag = DMABufReleaseFlag(DMABufReleaseFlag::Initialize); >+} >+ >+GBMBufferSwapchain::Buffer::~Buffer() = default; >+ >+DMABufObject GBMBufferSwapchain::Buffer::createDMABufObject(uintptr_t handle) const >+{ >+ DMABufObject object(handle); >+ object.format = m_description.format; >+ object.width = m_description.width; >+ object.height = m_description.height; >+ object.releaseFlag = m_state.releaseFlag.dup(); >+ >+ for (unsigned i = 0; i < m_description.format.numPlanes; ++i) { >+ object.fd[i] = gbm_bo_get_fd(m_planes[i].bo); >+ object.offset[i] = 0; >+ object.stride[i] = m_planes[i].stride; >+ // TODO: these should be the plane-specific modifiers. We don't use them yet. >+ object.modifier[i] = 0; >+ } >+ >+ return object; >+} >+ >+GBMBufferSwapchain::Buffer::PlaneData::~PlaneData() >+{ >+ if (bo) >+ gbm_bo_destroy(bo); >+} >+ >+} // namespace WebCore >diff --git a/Source/WebCore/platform/graphics/gbm/GBMBufferSwapchain.h b/Source/WebCore/platform/graphics/gbm/GBMBufferSwapchain.h >new file mode 100644 >index 0000000000000000000000000000000000000000..c8424106f3b5c7f5dd053077521eb7adda300845 >--- /dev/null >+++ b/Source/WebCore/platform/graphics/gbm/GBMBufferSwapchain.h >@@ -0,0 +1,127 @@ >+/* >+ * 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. >+ */ >+ >+#pragma once >+ >+#include "DMABufFormat.h" >+#include "DMABufObject.h" >+#include "DMABufReleaseFlag.h" >+#include <array> >+#include <cstdint> >+#include <wtf/Noncopyable.h> >+#include <wtf/Nonmovable.h> >+#include <wtf/RefPtr.h> >+#include <wtf/ThreadSafeRefCounted.h> >+ >+struct gbm_bo; >+ >+namespace WebCore { >+ >+class GBMBufferSwapchain : public ThreadSafeRefCounted<GBMBufferSwapchain> { >+ WTF_MAKE_FAST_ALLOCATED; >+public: >+ // The size should be adjusted to the use-case. >+ // For cyclical rendering (e.g. WebGL), Four should be ideal since we'll rely on >+ // something like vsync to maintain a constant framerate and subsequent buffer churn. >+ // There are other sporadic producers like software-based media decoders that will >+ // be requesting and filling in buffers in bursts but still have them displayed on >+ // a cyclical basis. Eight should be enough to cover that. >+ // In any case, the swapchain will do whatever it can to reuse released buffers >+ // and not unnecessarily spawn new ones, meaning the limit shouldn't be reached. >+ // If it is, getBuffer() will return a null object and the producer has to >+ // handle that somehow. >+ enum class BufferSwapchainSize : unsigned { >+ Four = 4, >+ Eight = 8 >+ }; >+ >+ GBMBufferSwapchain(BufferSwapchainSize); >+ ~GBMBufferSwapchain(); >+ >+ struct BufferDescription { >+ DMABufFormat format { }; >+ uint32_t width { 0 }; >+ uint32_t height { 0 }; >+ }; >+ >+ class Buffer : public ThreadSafeRefCounted<Buffer> { >+ WTF_MAKE_FAST_ALLOCATED; >+ public: >+ Buffer(uint32_t, const BufferDescription&); >+ ~Buffer(); >+ >+ DMABufObject createDMABufObject(uintptr_t) const; >+ >+ struct PlaneData { >+ WTF_MAKE_NONCOPYABLE(PlaneData); >+ WTF_MAKE_NONMOVABLE(PlaneData); >+ >+ PlaneData() = default; >+ ~PlaneData(); >+ >+ DMABufFormat::FourCC fourcc { DMABufFormat::FourCC::Invalid }; >+ uint32_t width { 0 }; >+ uint32_t height { 0 }; >+ uint32_t stride { 0 }; >+ struct gbm_bo* bo { nullptr }; >+ }; >+ >+ uint32_t handle() const { return m_handle; } >+ >+ unsigned numPlanes() const { return m_description.format.numPlanes; } >+ const PlaneData& planeData(unsigned index) const >+ { >+ ASSERT(index < DMABufFormat::c_maxPlanes); >+ return m_planes[index]; >+ } >+ >+ private: >+ friend class GBMBufferSwapchain; >+ >+ uint32_t m_handle { 0 }; >+ struct { >+ bool locked { false }; >+ DMABufReleaseFlag releaseFlag; >+ } m_state; >+ >+ BufferDescription m_description; >+ std::array<PlaneData, DMABufFormat::c_maxPlanes> m_planes; >+ }; >+ >+ RefPtr<Buffer> getBuffer(const BufferDescription&); >+ >+private: >+ static constexpr unsigned c_maxBuffers = 8; >+ >+ uint32_t m_handleGenerator { 0 }; >+ struct { >+ BufferDescription description; >+ unsigned size { 0 }; >+ std::array<RefPtr<Buffer>, c_maxBuffers> object { }; >+ } m_array; >+}; >+ >+} // namespace WebCore >diff --git a/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.cpp >index 70f2047431598a0cb38e4c6d35c88a415559be4a..c1386ca8e9e0da666d7ec57e1417a8bd4da908bd 100644 >--- a/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.cpp >+++ b/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.cpp >@@ -160,12 +160,16 @@ static void webkit_dmabuf_video_sink_class_init(WebKitDMABufVideoSinkClass* klas > "Sink Statistics", GST_TYPE_STRUCTURE, static_cast<GParamFlags>(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); > } > >-void webKitDMABufVideoSinkSetMediaPlayerPrivate(WebKitDMABufVideoSink* sink, MediaPlayerPrivateGStreamer* player) >+bool webKitDMABufVideoSinkIsEnabled() > { >- WebKitDMABufVideoSinkPrivate* priv = sink->priv; >- >- priv->mediaPlayerPrivate = player; >- webKitVideoSinkSetMediaPlayerPrivate(priv->appSink.get(), priv->mediaPlayerPrivate); >+ static bool s_enabled = false; >+ static std::once_flag s_flag; >+ std::call_once(s_flag, >+ [&] { >+ const char* value = g_getenv("WEBKIT_GST_DMABUF_SINK_ENABLED"); >+ s_enabled = value && (equalIgnoringASCIICase(value, "true") || equalIgnoringASCIICase(value, "1")); >+ }); >+ return s_enabled; > } > > bool webKitDMABufVideoSinkProbePlatform() >@@ -173,4 +177,12 @@ bool webKitDMABufVideoSinkProbePlatform() > return isGStreamerPluginAvailable("app"); > } > >+void webKitDMABufVideoSinkSetMediaPlayerPrivate(WebKitDMABufVideoSink* sink, MediaPlayerPrivateGStreamer* player) >+{ >+ WebKitDMABufVideoSinkPrivate* priv = sink->priv; >+ >+ priv->mediaPlayerPrivate = player; >+ webKitVideoSinkSetMediaPlayerPrivate(priv->appSink.get(), priv->mediaPlayerPrivate); >+} >+ > #endif // ENABLE(VIDEO) >diff --git a/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.h >index df24a6f0fa37d237551e68169acaa79d7c990ff2..328e8b7eb9c1771c90cf83987661646588b7b7c6 100644 >--- a/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.h >+++ b/Source/WebCore/platform/graphics/gstreamer/DMABufVideoSinkGStreamer.h >@@ -50,6 +50,7 @@ struct _WebKitDMABufVideoSinkClass { > > GType webkit_dmabuf_video_sink_get_type(void); > >+bool webKitDMABufVideoSinkIsEnabled(); > bool webKitDMABufVideoSinkProbePlatform(); > void webKitDMABufVideoSinkSetMediaPlayerPrivate(WebKitDMABufVideoSink*, WebCore::MediaPlayerPrivateGStreamer*); > >diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp >index 13c3f785554f03c2d5905a6a988fdb61e9eb6bed..07315b62e60f920f2cd3144d822ee60ecbe8f3a6 100644 >--- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp >+++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp >@@ -28,6 +28,11 @@ > > #if ENABLE(VIDEO) && USE(GSTREAMER) > >+#include "DMABufFormat.h" >+#include "DMABufObject.h" >+#include "DMABufVideoSinkGStreamer.h" >+#include "GBMBufferSwapchain.h" >+#include "GBMDevice.h" > #include "GraphicsContext.h" > #include "GStreamerAudioMixer.h" > #include "GStreamerCommon.h" >@@ -75,7 +80,9 @@ > #include "AudioSourceProviderGStreamer.h" > #endif > >+#include <gbm.h> > #include <glib.h> >+#include <gst/allocators/gstdmabuf.h> > #include <gst/audio/streamvolume.h> > #include <gst/gst.h> > #include <gst/pbutils/missing-plugins.h> >@@ -92,6 +99,7 @@ > #include <wtf/text/AtomString.h> > #include <wtf/text/CString.h> > #include <wtf/text/StringConcatenateNumbers.h> >+#include <wtf/UniStdExtras.h> > #include <wtf/URL.h> > #include <wtf/WallTime.h> > >@@ -114,6 +122,7 @@ > #include "GStreamerVideoFrameHolder.h" > #include "TextureMapperContextAttributes.h" > #include "TextureMapperPlatformLayerBuffer.h" >+#include "TextureMapperPlatformLayerProxyDMABuf.h" > #include "TextureMapperPlatformLayerProxyGL.h" > #endif // USE(TEXTURE_MAPPER_GL) > >@@ -155,23 +164,29 @@ MediaPlayerPrivateGStreamer::MediaPlayerPrivateGStreamer(MediaPlayer* player) > , m_maxTimeLoadedAtLastDidLoadingProgress(MediaTime::zeroTime()) > , m_drawTimer(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::repaint) > , m_readyTimerHandler(RunLoop::main(), this, &MediaPlayerPrivateGStreamer::readyTimerFired) >-#if USE(TEXTURE_MAPPER_GL) >-#if USE(NICOSIA) >- , m_nicosiaLayer(Nicosia::ContentLayer::create(Nicosia::ContentLayerTextureMapperImpl::createFactory(*this))) >-#else >+#if USE(TEXTURE_MAPPER_GL) && !USE(NICOSIA) > , m_platformLayerProxy(adoptRef(new TextureMapperPlatformLayerProxyGL)) > #endif >-#endif > #if !RELEASE_LOG_DISABLED > , m_logger(player->mediaPlayerLogger()) > , m_logIdentifier(player->mediaPlayerLogIdentifier()) > #endif >+ , m_swapchain(adoptRef(new GBMBufferSwapchain(GBMBufferSwapchain::BufferSwapchainSize::Eight))) > { > #if USE(GLIB) > m_readyTimerHandler.setPriority(G_PRIORITY_DEFAULT_IDLE); > #endif > m_isPlayerShuttingDown.store(false); > >+#if USE(TEXTURE_MAPPER_GL) && USE(NICOSIA) >+ m_nicosiaLayer = Nicosia::ContentLayer::create(Nicosia::ContentLayerTextureMapperImpl::createFactory(*this, >+ [&]() -> Ref<TextureMapperPlatformLayerProxy> { >+ if (webKitDMABufVideoSinkIsEnabled() && webKitDMABufVideoSinkProbePlatform()) >+ return adoptRef(*new TextureMapperPlatformLayerProxyDMABuf); >+ return adoptRef(*new TextureMapperPlatformLayerProxyGL); >+ }())); >+#endif >+ > ensureGStreamerInitialized(); > m_audioSink = createAudioSink(); > >@@ -2867,7 +2882,7 @@ bool MediaPlayerPrivateGStreamer::performTaskAtMediaTime(Function<void()>&& task > PlatformLayer* MediaPlayerPrivateGStreamer::platformLayer() const > { > #if USE(NICOSIA) >- return m_nicosiaLayer.ptr(); >+ return m_nicosiaLayer.get(); > #else > return const_cast<MediaPlayerPrivateGStreamer*>(this); > #endif >@@ -2979,6 +2994,231 @@ void MediaPlayerPrivateGStreamer::pushTextureToCompositor() > proxyOperation(*m_platformLayerProxy); > #endif > } >+ >+// GStreamer's gst_video_format_to_fourcc() doesn't cover RGB-like formats, so we >+// provide the appropriate FourCC values for those through this funcion. >+static uint32_t fourccValue(GstVideoFormat format) >+{ >+ switch (format) { >+ case GST_VIDEO_FORMAT_RGBx: >+ return uint32_t(DMABufFormat::FourCC::XBGR8888); >+ case GST_VIDEO_FORMAT_BGRx: >+ return uint32_t(DMABufFormat::FourCC::XRGB8888); >+ case GST_VIDEO_FORMAT_xRGB: >+ return uint32_t(DMABufFormat::FourCC::BGRX8888); >+ case GST_VIDEO_FORMAT_xBGR: >+ return uint32_t(DMABufFormat::FourCC::RGBX8888); >+ case GST_VIDEO_FORMAT_RGBA: >+ return uint32_t(DMABufFormat::FourCC::ABGR8888); >+ case GST_VIDEO_FORMAT_BGRA: >+ return uint32_t(DMABufFormat::FourCC::ARGB8888); >+ case GST_VIDEO_FORMAT_ARGB: >+ return uint32_t(DMABufFormat::FourCC::BGRA8888); >+ case GST_VIDEO_FORMAT_ABGR: >+ return uint32_t(DMABufFormat::FourCC::RGBA8888); >+ default: >+ break; >+ } >+ >+ return gst_video_format_to_fourcc(format); >+} >+ >+void MediaPlayerPrivateGStreamer::pushDMABufToCompositor() >+{ >+ Locker sampleLocker { m_sampleMutex }; >+ if (!GST_IS_SAMPLE(m_sample.get())) >+ return; >+ >+ auto* caps = gst_sample_get_caps(m_sample.get()); >+ if (!caps) >+ return; >+ >+ GstVideoInfo videoInfo; >+ gst_video_info_init(&videoInfo); >+ if (!gst_video_info_from_caps(&videoInfo, caps)) >+ return; >+ >+ auto* buffer = gst_sample_get_buffer(m_sample.get()); >+ if (!buffer) >+ return; >+ >+ auto* meta = gst_buffer_get_video_meta(buffer); >+ if (meta) { >+ GST_VIDEO_INFO_WIDTH(&videoInfo) = meta->width; >+ GST_VIDEO_INFO_HEIGHT(&videoInfo) = meta->height; >+ >+ for (unsigned i = 0; i < meta->n_planes; ++i) { >+ GST_VIDEO_INFO_PLANE_OFFSET(&videoInfo, i) = meta->offset[i]; >+ GST_VIDEO_INFO_PLANE_STRIDE(&videoInfo, i) = meta->stride[i]; >+ } >+ } >+ >+ auto& proxy = downcast<Nicosia::ContentLayerTextureMapperImpl>(m_nicosiaLayer->impl()).proxy(); >+ ASSERT(is<TextureMapperPlatformLayerProxyDMABuf>(proxy)); >+ >+ auto* features = gst_caps_get_features(caps, 0); >+ if (gst_caps_features_contains(features, GST_CAPS_FEATURE_MEMORY_DMABUF)) { >+ // In case of a hardware decoder that's yielding dmabuf memory, we can take the relevant data and >+ // push it into the composition process. >+ >+ ++m_sampleCount; >+ Locker locker { proxy.lock() }; >+ if (!proxy.isActive()) >+ return; >+ >+ // Provide the DMABufObject with a relevant handle (memory address). When provided for the first time, >+ // the lambda will be invoked and all dmabuf data is filled in. >+ downcast<TextureMapperPlatformLayerProxyDMABuf>(proxy).pushDMABuf( >+ DMABufObject(reinterpret_cast<uintptr_t>(gst_buffer_peek_memory(buffer, 0))), >+ [&](auto&& object) { >+ object.format = DMABufFormat::create(fourccValue(GST_VIDEO_INFO_FORMAT(&videoInfo))); >+ object.width = GST_VIDEO_INFO_WIDTH(&videoInfo); >+ object.height = GST_VIDEO_INFO_HEIGHT(&videoInfo); >+ >+ // TODO: release mechanism for a decoder-provided dmabuf is a bit tricky. The dmabuf object >+ // itself doesn't provide anything useful, but the decoder won't reuse the dmabuf until the >+ // relevant GstSample reference is dropped by the downstream pipeline. So for this to work, >+ // there's a need to somehow associate the GstSample reference with this release flag so that >+ // the reference is dropped once the release flag is signalled. There's ways to achieve that, >+ // but left for later. >+ object.releaseFlag = DMABufReleaseFlag { }; >+ >+ // For each plane, the relevant data (stride, offset, skip, dmabuf fd) is retrieved and assigned >+ // as appropriate. Modifier values are zeroed out for now, since GStreamer doesn't yet provide >+ // the information. >+ for (unsigned i = 0; i < object.format.numPlanes; ++i) { >+ gsize offset = GST_VIDEO_INFO_PLANE_OFFSET(&videoInfo, i); >+ guint memid = 0; >+ guint length = 0; >+ gsize skip = 0; >+ if (gst_buffer_find_memory(buffer, offset, 1, &memid, &length, &skip)) { >+ auto* mem = gst_buffer_peek_memory(buffer, memid); >+ object.fd[i] = dupCloseOnExec(gst_dmabuf_memory_get_fd(mem)); >+ offset = mem->offset + skip; >+ } else >+ object.fd[i] = -1; >+ >+ gint comp[GST_VIDEO_MAX_COMPONENTS]; >+ gst_video_format_info_component(videoInfo.finfo, i, comp); >+ object.offset[i] = offset; >+ object.stride[i] = GST_VIDEO_INFO_PLANE_STRIDE(&videoInfo, i); >+ object.modifier[i] = 0; >+ } >+ return object; >+ }); >+ return; >+ } >+ >+ // If the decoder is exporting raw memory, we have to use the swapchain to allocate appropriate buffers >+ // and copy over the data for each plane. >+ GBMBufferSwapchain::BufferDescription bufferDescription { >+ DMABufFormat::create(fourccValue(GST_VIDEO_INFO_FORMAT(&videoInfo))), >+ GST_VIDEO_INFO_WIDTH(&videoInfo), GST_VIDEO_INFO_HEIGHT(&videoInfo), >+ }; >+ if (bufferDescription.format.fourcc == DMABufFormat::FourCC::Invalid) >+ return; >+ >+ auto swapchainBuffer = m_swapchain->getBuffer(bufferDescription); >+ >+ // Source helper struct, maps the raw memory and exposes the mapped data for copying. >+ struct Source { >+ Source(GstMemory* memory, gsize offset, uint32_t stride, uint32_t height) >+ : memory(memory) >+ , stride(stride) >+ , height(height) >+ { >+ if (!gst_memory_map(memory, &mapInfo, GST_MAP_READ)) >+ return; >+ >+ valid = true; >+ data = &mapInfo.data[offset]; >+ } >+ >+ ~Source() >+ { >+ if (valid) >+ gst_memory_unmap(memory, &mapInfo); >+ } >+ >+ bool valid { false }; >+ GstMemory* memory { nullptr }; >+ GstMapInfo mapInfo; >+ uint8_t* data { nullptr }; >+ uint32_t stride { 0 }; >+ uint32_t height { 0 }; >+ }; >+ >+ // Destination helper struct, maps the gbm_bo object into CPU-memory space and copies from the accompanying Source in fill(). >+ struct Destination { >+ Destination(struct gbm_bo* bo, uint32_t width, uint32_t height) >+ : bo(bo) >+ { >+ map = gbm_bo_map(bo, 0, 0, width, height, GBM_BO_TRANSFER_WRITE, &stride, &mapData); >+ if (!map) >+ return; >+ >+ valid = true; >+ data = reinterpret_cast<uint8_t*>(map); >+ } >+ >+ ~Destination() >+ { >+ if (valid) >+ gbm_bo_unmap(bo, mapData); >+ } >+ >+ void fill(const Source& source) >+ { >+ for (uint32_t y = 0; y < source.height; ++y) { >+ auto* sourceData = &source.data[y * source.stride]; >+ auto* destinationData = &data[y * stride]; >+ memcpy(destinationData, sourceData, std::min(source.stride, stride)); >+ } >+ } >+ >+ bool valid { false }; >+ struct gbm_bo* bo { nullptr }; >+ void* map { nullptr }; >+ void* mapData { nullptr }; >+ uint8_t* data { nullptr }; >+ uint32_t stride { 0 }; >+ }; >+ >+ for (unsigned i = 0; i < GST_VIDEO_INFO_N_PLANES(&videoInfo); ++i) { >+ gint comp[GST_VIDEO_MAX_COMPONENTS]; >+ gst_video_format_info_component(videoInfo.finfo, i, comp); >+ >+ auto& planeData = swapchainBuffer->planeData(i); >+ gsize offset = GST_VIDEO_INFO_PLANE_OFFSET(&videoInfo, i); >+ guint stride = GST_VIDEO_INFO_PLANE_STRIDE(&videoInfo, i); >+ >+ guint memid, length; >+ gsize skip; >+ if (gst_buffer_find_memory(buffer, offset, 1, &memid, &length, &skip)) { >+ auto* mem = gst_buffer_peek_memory(buffer, memid); >+ >+ Source source(mem, offset, stride, planeData.height); >+ Destination destination(planeData.bo, planeData.width, planeData.height); >+ >+ if (source.valid && destination.valid) >+ destination.fill(source); >+ } >+ } >+ >+ ++m_sampleCount; >+ Locker locker { proxy.lock() }; >+ if (!proxy.isActive()) >+ return; >+ >+ // The updated buffer is pushed into the composition stage. The DMABufObject handle uses the swapchain address as the handle base. >+ // When the buffer is pushed for the first time, the lambda will be invoked to retrieve a more complete DMABufObject for the >+ // given GBMBufferSwapchain::Buffer object. >+ downcast<TextureMapperPlatformLayerProxyDMABuf>(proxy).pushDMABuf( >+ DMABufObject(reinterpret_cast<uintptr_t>(m_swapchain.get()) + swapchainBuffer->handle()), >+ [&](auto&& object) { >+ return swapchainBuffer->createDMABufObject(object.handle); >+ }); >+} > #endif // USE(TEXTURE_MAPPER_GL) > > void MediaPlayerPrivateGStreamer::repaint() >@@ -3185,8 +3425,16 @@ void MediaPlayerPrivateGStreamer::triggerRepaint(GstSample* sample) > #endif > m_drawTimer.startOneShot(0_s); > m_drawCondition.wait(m_drawLock); >- } else >+ } else { >+#if USE(NICOSIA) >+ if (is<TextureMapperPlatformLayerProxyDMABuf>(downcast<Nicosia::ContentLayerTextureMapperImpl>(m_nicosiaLayer->impl()).proxy())) { >+ pushDMABufToCompositor(); >+ return; >+ } >+#endif >+ > pushTextureToCompositor(); >+ } > #endif // USE(TEXTURE_MAPPER_GL) > } > >@@ -3426,6 +3674,21 @@ MediaPlayer::MovieLoadType MediaPlayerPrivateGStreamer::movieLoadType() const > return MediaPlayer::MovieLoadType::Download; > } > >+GstElement* MediaPlayerPrivateGStreamer::createVideoSinkDMABuf() >+{ >+ if (!webKitDMABufVideoSinkIsEnabled()) >+ return nullptr; >+ if (!webKitDMABufVideoSinkProbePlatform()) { >+ g_warning("WebKit wasn't able to find the DMABuf video sink dependencies. Hardware-accelerated zero-copy video rendering won't be achievable with this plugin."); >+ return nullptr; >+ } >+ >+ GstElement* sink = gst_element_factory_make("webkitdmabufvideosink", nullptr); >+ ASSERT(sink); >+ webKitDMABufVideoSinkSetMediaPlayerPrivate(WEBKIT_DMABUF_VIDEO_SINK(sink), this); >+ return sink; >+} >+ > #if USE(GSTREAMER_GL) > GstElement* MediaPlayerPrivateGStreamer::createVideoSinkGL() > { >@@ -3517,8 +3780,10 @@ GstElement* MediaPlayerPrivateGStreamer::createVideoSink() > return m_videoSink.get(); > #endif > >+ if (!m_videoSink && m_canRenderingBeAccelerated) >+ m_videoSink = createVideoSinkDMABuf(); > #if USE(GSTREAMER_GL) >- if (m_canRenderingBeAccelerated) >+ if (!m_videoSink && m_canRenderingBeAccelerated) > m_videoSink = createVideoSinkGL(); > #endif > >diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h >index e38075b2fc26d943bf9a0065433c7bbaae5e0623..64f059cdd1af264ff46d6c009b8acbf65c31ebd7 100644 >--- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h >+++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h >@@ -105,6 +105,8 @@ class InbandTextTrackPrivateGStreamer; > class MediaPlayerRequestInstallMissingPluginsCallback; > class VideoTrackPrivateGStreamer; > >+class GBMBufferSwapchain; >+ > void registerWebKitGStreamerElements(); > > // Use eager initialization for the WeakPtrFactory since we construct WeakPtrs on another thread. >@@ -267,12 +269,14 @@ protected: > bool shouldIgnoreIntrinsicSize() final { return true; } > #endif > >+ GstElement* createVideoSinkDMABuf(); > #if USE(GSTREAMER_GL) > GstElement* createVideoSinkGL(); > #endif > > #if USE(TEXTURE_MAPPER_GL) > void pushTextureToCompositor(); >+ void pushDMABufToCompositor(); > #if USE(NICOSIA) > void swapBuffersIfNeeded() final; > #else >@@ -508,7 +512,7 @@ private: > RunLoop::Timer<MediaPlayerPrivateGStreamer> m_readyTimerHandler; > #if USE(TEXTURE_MAPPER_GL) > #if USE(NICOSIA) >- Ref<Nicosia::ContentLayer> m_nicosiaLayer; >+ RefPtr<Nicosia::ContentLayer> m_nicosiaLayer; > #else > RefPtr<TextureMapperPlatformLayerProxy> m_platformLayerProxy; > #endif >@@ -573,6 +577,8 @@ private: > Ref<const Logger> m_logger; > const void* m_logIdentifier; > #endif >+ >+ RefPtr<GBMBufferSwapchain> m_swapchain; > }; > > } >diff --git a/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.cpp b/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.cpp >index 022844b32670453c6cc775544c296b5d6083c4e9..92375e920122179d835ea51e1f0660ae0b10fba8 100644 >--- a/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.cpp >+++ b/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.cpp >@@ -31,15 +31,21 @@ > > #if USE(TEXTURE_MAPPER) > >+#include "TextureMapperPlatformLayerProxyDMABuf.h" > #include "TextureMapperPlatformLayerProxyGL.h" > > namespace Nicosia { > > auto ContentLayerTextureMapperImpl::createFactory(Client& client) -> Factory >+{ >+ return createFactory(client, adoptRef(*new WebCore::TextureMapperPlatformLayerProxyGL)); >+} >+ >+auto ContentLayerTextureMapperImpl::createFactory(Client& client, Ref<WebCore::TextureMapperPlatformLayerProxy>&& proxy) -> Factory > { > return Factory( >- [&client](ContentLayer&) { >- return makeUnique<ContentLayerTextureMapperImpl>(client, adoptRef(*new WebCore::TextureMapperPlatformLayerProxyGL)); >+ [&client, proxy = WTFMove(proxy)](ContentLayer&) mutable { >+ return makeUnique<ContentLayerTextureMapperImpl>(client, WTFMove(proxy)); > }); > } > >diff --git a/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.h b/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.h >index 97e305913cc8426b37b837c4d3e69da7e3b3076b..5936975025bdb842c49bd377d330b9610b42a294 100644 >--- a/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.h >+++ b/Source/WebCore/platform/graphics/nicosia/texmap/NicosiaContentLayerTextureMapperImpl.h >@@ -50,6 +50,7 @@ public: > }; > > static Factory createFactory(Client&); >+ static Factory createFactory(Client&, Ref<WebCore::TextureMapperPlatformLayerProxy>&&); > > explicit ContentLayerTextureMapperImpl(Client&, Ref<WebCore::TextureMapperPlatformLayerProxy>&&); > virtual ~ContentLayerTextureMapperImpl(); >diff --git a/Source/WebCore/platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.cpp b/Source/WebCore/platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.cpp >new file mode 100644 >index 0000000000000000000000000000000000000000..48baf88a44263f3577511e391da9801bef14c04f >--- /dev/null >+++ b/Source/WebCore/platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.cpp >@@ -0,0 +1,284 @@ >+/* >+ * 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) >+ >+#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 >+ >+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) >+{ >+#ifndef NDEBUG >+ 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() >+{ >+ 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; >+ >+ // TODO: this is the BT.601 colorspace conversion matrix. The exact desired colorspace should be included >+ // in the DMABufObject, and the relevant matrix decided based on it. BT.601 should remain the default. >+ static constexpr std::array<GLfloat, 9> s_yuvToRGB { >+ 1.164f, 0.0f, 1.596f, >+ 1.164f, -0.391f, -0.813f, >+ 1.164f, 2.018f, 0.0f >+ }; >+ >+ 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: >+ 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] }, >+ s_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] }, >+ s_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] }, >+ s_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), >+ s_yuvToRGB, m_flags, IntSize(data.width, data.height), targetRect, modelViewMatrix, opacity); >+ break; >+ case DMABufFormat::FourCC::YUY2: >+ case DMABufFormat::FourCC::UYVY: >+ case DMABufFormat::FourCC::VUYA: >+ case DMABufFormat::FourCC::YVYU: >+ texmapGL.drawTexturePackedYUV(data.texture[0], >+ s_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; >+ >+ EGLDisplay eglDisplay = PlatformDisplay::sharedDisplayForCompositing().eglDisplay(); >+ >+ EGLImageKHR image[DMABufFormat::c_maxPlanes]; >+ for (unsigned i = 0; i < object.format.numPlanes; ++i) { >+ std::initializer_list<EGLint> attributes { >+ EGL_WIDTH, EGLint(object.format.planeWidth(i, object.width)), >+ EGL_HEIGHT, EGLint(object.format.planeHeight(i, object.height)), >+ EGL_LINUX_DRM_FOURCC_EXT, EGLint(object.format.planes[i].fourcc), >+ EGL_DMA_BUF_PLANE0_FD_EXT, object.fd[i], >+ EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGLint(object.offset[i]), >+ EGL_DMA_BUF_PLANE0_PITCH_EXT, EGLint(object.stride[i]), >+ EGL_NONE, >+ }; >+ image[i] = createImageKHR()(eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, std::data(attributes)); >+ } >+ >+ 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) >diff --git a/Source/WebCore/platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.h b/Source/WebCore/platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.h >new file mode 100644 >index 0000000000000000000000000000000000000000..08c0bdaca6d2dbe10951ece83de765c0a732a9f0 >--- /dev/null >+++ b/Source/WebCore/platform/graphics/texmap/TextureMapperPlatformLayerProxyDMABuf.h >@@ -0,0 +1,111 @@ >+/* >+ * 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. >+ */ >+ >+#pragma once >+ >+#include "TextureMapperPlatformLayer.h" >+#include "TextureMapperPlatformLayerProxy.h" >+ >+#if USE(COORDINATED_GRAPHICS) >+ >+#include "DMABufFormat.h" >+#include "DMABufObject.h" >+#include "TextureMapperGL.h" >+#include <cstdint> >+#include <memory> >+ >+namespace WebCore { >+ >+class TextureMapperPlatformLayerProxyDMABuf final : public TextureMapperPlatformLayerProxy { >+ WTF_MAKE_FAST_ALLOCATED; >+public: >+ TextureMapperPlatformLayerProxyDMABuf(); >+ virtual ~TextureMapperPlatformLayerProxyDMABuf(); >+ >+ bool isDMABufBased() const override { return true; } >+ >+ WEBCORE_EXPORT void activateOnCompositingThread(Compositor*, TextureMapperLayer*) override; >+ WEBCORE_EXPORT void invalidate() override; >+ WEBCORE_EXPORT void swapBuffer() override; >+ >+ class DMABufLayer : public ThreadSafeRefCounted<DMABufLayer>, public TextureMapperPlatformLayer { >+ WTF_MAKE_FAST_ALLOCATED; >+ public: >+ DMABufLayer(DMABufObject&&, TextureMapperGL::Flags = 0); >+ virtual ~DMABufLayer(); >+ >+ void paintToTextureMapper(TextureMapper&, const FloatRect&, const TransformationMatrix& modelViewMatrix = { }, float opacity = 1.0) final; >+ >+ void release() >+ { >+ m_object.releaseFlag.release(); >+ } >+ >+ private: >+ friend class TextureMapperPlatformLayerProxyDMABuf; >+ >+ struct EGLImageData; >+ static std::unique_ptr<EGLImageData> createEGLImageData(DMABufObject&); >+ >+ DMABufObject m_object; >+ std::unique_ptr<EGLImageData> m_imageData; >+ TextureMapperGL::Flags m_flags; >+ >+ static constexpr unsigned c_maximumAge { 16 }; >+ unsigned m_age { 0 }; >+ }; >+ >+ template<typename F> >+ void pushDMABuf(DMABufObject&& dmabufObject, const F& constructor, TextureMapperGL::Flags flags = 0) >+ { >+ ASSERT(m_lock.isHeld()); >+ >+ auto result = m_layers.ensure(dmabufObject.handle, >+ [&] { >+ return adoptRef(*new DMABufLayer(constructor(WTFMove(dmabufObject)), flags)); >+ }); >+ pushDMABuf(result.iterator->value.copyRef()); >+ } >+ >+private: >+ void pushDMABuf(Ref<DMABufLayer>&&); >+ >+#ifndef NDEBUG >+ RefPtr<Thread> m_compositorThread; >+#endif >+ >+ using LayerMap = HashMap<uintptr_t, Ref<DMABufLayer>, WTF::DefaultHash<uintptr_t>, WTF::UnsignedWithZeroKeyHashTraits<uintptr_t>>; >+ LayerMap m_layers; >+ >+ RefPtr<DMABufLayer> m_pendingLayer; >+ RefPtr<DMABufLayer> m_committedLayer; >+}; >+ >+} // namespace WebCore >+ >+SPECIALIZE_TYPE_TRAITS_TEXTUREMAPPER_PLATFORMLAYERPROXY(TextureMapperPlatformLayerProxyDMABuf, isDMABufBased()); >+ >+#endif // USE(COORDINATED_GRAPHICS)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 237328
:
453504
|
453709
|
453729
|
453839
|
454329
|
454674
|
454821