Warning, /multimedia/kdenlive/src/monitor/metalvideowidget.mm is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2023 Meltytech, LLC
0003     SPDX-License-Identifier: GPL-3.0-or-later
0004 */
0005 
0006 #include "metalvideowidget.h"
0007 #include "core.h"
0008 #include "profiles/profilemodel.hpp"
0009 #include <Metal/Metal.h>
0010 
0011 
0012 class MetalVideoRenderer : public QObject
0013 {
0014     Q_OBJECT
0015 public:
0016     int displayRulerHeight = 0;
0017     MetalVideoRenderer()
0018     {
0019         for (int i = 0; i < 3; ++i) {
0020             m_ubuf[i] = nil;
0021             m_texture[i] = nil;
0022         }
0023         m_vbuf = nil;
0024         m_vs.first = nil;
0025         m_vs.second = nil;
0026         m_fs.first = nil;
0027         m_fs.second = nil;
0028     }
0029 
0030     ~MetalVideoRenderer()
0031     {
0032         qDebug("cleanup");
0033 
0034         for (int i = 0; i < 3; i++) {
0035             [m_texture[i] release];
0036             [m_ubuf[i] release];
0037         }
0038         [m_vbuf release];
0039         [m_vs.first release];
0040         [m_vs.second release];
0041         [m_fs.first release];
0042         [m_fs.second release];
0043     }
0044 
0045     void initialize(QQuickWindow *window)
0046     {
0047         qDebug("init");
0048         m_window = window;
0049 
0050         QSGRendererInterface *rif = m_window->rendererInterface();
0051 
0052         // We are not prepared for anything other than running with the RHI and its Metal backend.
0053         Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::Metal);
0054 
0055         m_device = (id<MTLDevice>) rif->getResource(m_window, QSGRendererInterface::DeviceResource);
0056         Q_ASSERT(m_device);
0057 
0058         if (m_vert.isEmpty())
0059             prepareShader(VertexStage);
0060         if (m_frag.isEmpty())
0061             prepareShader(FragmentStage);
0062 
0063         m_vbuf = [m_device newBufferWithLength: 16*sizeof(float) options: MTLResourceStorageModeShared];
0064 
0065         for (int i = 0; i < m_window->graphicsStateInfo().framesInFlight && i < 3; ++i)
0066             m_ubuf[i] = [m_device newBufferWithLength: sizeof(int) options: MTLResourceStorageModeShared];
0067 
0068         MTLVertexDescriptor *inputLayout = [MTLVertexDescriptor vertexDescriptor];
0069         inputLayout.attributes[0].format = MTLVertexFormatFloat4;
0070         inputLayout.attributes[0].offset = 0;
0071         inputLayout.attributes[0].bufferIndex = 1; // ubuf is 0, vbuf is 1
0072         inputLayout.layouts[1].stride = 4 * sizeof(float);
0073 
0074         MTLRenderPipelineDescriptor *rpDesc = [[MTLRenderPipelineDescriptor alloc] init];
0075         rpDesc.vertexDescriptor = inputLayout;
0076 
0077         m_vs = compileShader(m_vert, m_vertEntryPoint);
0078         rpDesc.vertexFunction = m_vs.first;
0079         m_fs = compileShader(m_frag, m_fragEntryPoint);
0080         rpDesc.fragmentFunction = m_fs.first;
0081 
0082         rpDesc.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
0083 //        if (m_device.depth24Stencil8PixelFormatSupported) {
0084 //            rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8;
0085 //            rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth24Unorm_Stencil8;
0086 //        } else {
0087 //            rpDesc.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
0088 //            rpDesc.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
0089 //        }
0090 
0091         NSError *err = nil;
0092         m_pipeline = [m_device newRenderPipelineStateWithDescriptor: rpDesc error: &err];
0093         if (!m_pipeline) {
0094             const QString msg = QString::fromNSString(err.localizedDescription);
0095             qFatal("Failed to create render pipeline state: %s", qPrintable(msg));
0096         }
0097         [rpDesc release];
0098     }
0099 
0100     void render(const QSize& viewportSize, const QRectF& videoRect, const double devicePixelRatio,
0101                 const double zoom, const QPoint& offset, const SharedFrame& sharedFrame)
0102     {
0103         const QQuickWindow::GraphicsStateInfo &stateInfo(m_window->graphicsStateInfo());
0104 
0105         QSGRendererInterface *rif = m_window->rendererInterface();
0106         id<MTLRenderCommandEncoder> encoder = (id<MTLRenderCommandEncoder>) rif->getResource(
0107                     m_window, QSGRendererInterface::CommandEncoderResource);
0108         Q_ASSERT(encoder);
0109 
0110         // Provide vertices of triangle strip
0111         float width = videoRect.width() * devicePixelRatio / 2.0f;
0112         float height = videoRect.height() * devicePixelRatio / 2.0f;
0113         float vertexData[] = { // x,y plus u,v texture coordinates
0114             -width,  height, 0.f, 0.f,
0115              width,  height, 1.f, 0.f,
0116             -width, -height, 0.f, 1.f,
0117              width, -height, 1.f, 1.f
0118         };
0119 
0120         // Setup an orthographic projection
0121         QMatrix4x4 modelView;
0122         width = viewportSize.width() * devicePixelRatio;
0123         height = viewportSize.height() * devicePixelRatio;
0124         modelView.scale(2.0f / width, 2.0f / height);
0125 
0126         // Set model-view
0127         if (videoRect.width() > 0.0 && zoom > 0.0) {
0128             if (offset.x() || offset.y())
0129                 modelView.translate(-offset.x() * devicePixelRatio,
0130                                     offset.y() * devicePixelRatio);
0131             modelView.scale(zoom, zoom);
0132         }
0133         for (int i = 0; i < 4; i++) {
0134             vertexData[4 * i] *= modelView(0, 0);
0135             vertexData[4 * i] += modelView(0, 3);
0136             vertexData[4 * i + 1] *= modelView(1, 1);
0137             vertexData[4 * i + 1] += modelView(1, 3);
0138         }
0139 
0140         m_window->beginExternalCommands();
0141 
0142         void *p = [m_vbuf contents];
0143         memcpy(p, vertexData, sizeof(vertexData));
0144 
0145         p = [m_ubuf[stateInfo.currentFrameSlot] contents];
0146         int colorspace = pCore->getCurrentProfile()->colorspace();
0147         memcpy(p, &colorspace, sizeof(colorspace));
0148 
0149         MTLViewport vp;
0150         vp.originX = 0;
0151         vp.originY = -displayRulerHeight;
0152         vp.width = width;
0153         vp.height = height;
0154         vp.znear = 0;
0155         vp.zfar = 1;
0156         [encoder setViewport: vp];
0157 
0158         // (Re)create the textures
0159         int iwidth = sharedFrame.get_image_width();
0160         int iheight = sharedFrame.get_image_height();
0161         const uint8_t *image = sharedFrame.get_image(mlt_image_yuv420p);
0162         for (int i = 0; i < 3; i++) {
0163             [m_texture[i] release];
0164         }
0165         m_texture[0] = initTexture(image, iwidth, iheight);
0166         m_texture[1] = initTexture(image + iwidth * iheight, iwidth / 2, iheight / 2);
0167         m_texture[2] = initTexture(image + iwidth * iheight + iwidth / 2 * iheight / 2, iwidth / 2,
0168                                    iheight / 2);
0169         // Set the texture object.  The AAPLTextureIndexBaseColor enum value corresponds
0170         ///  to the 'colorMap' argument in the 'samplingShader' function because its
0171         //   texture attribute qualifier also uses AAPLTextureIndexBaseColor for its index.
0172         for (NSUInteger i = 0; i < 3; i++) {
0173             [encoder setFragmentTexture:m_texture[i] atIndex:i];
0174         }
0175 
0176 
0177         [encoder setFragmentBuffer: m_ubuf[stateInfo.currentFrameSlot] offset: 0 atIndex: 0];
0178         [encoder setVertexBuffer: m_vbuf offset: 0 atIndex: 1];
0179         [encoder setRenderPipelineState: m_pipeline];
0180         [encoder drawPrimitives: MTLPrimitiveTypeTriangleStrip vertexStart: 0 vertexCount: 4 instanceCount: 1 baseInstance: 0];
0181 
0182         m_window->endExternalCommands();
0183     }
0184 
0185     id<MTLTexture> initTexture(const void *p, NSUInteger width, NSUInteger height)
0186     {
0187         MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
0188 
0189         // 8-bit unsigned normalized value (i.e. 0 maps to 0.0 and 255 maps to 1.0)
0190         textureDescriptor.pixelFormat = MTLPixelFormatR8Unorm;
0191         textureDescriptor.width = width;
0192         textureDescriptor.height = height;
0193         id<MTLTexture> texture = [m_device newTextureWithDescriptor:textureDescriptor];
0194         [textureDescriptor release];
0195 
0196         MTLRegion region = {
0197             { 0, 0, 0 },  // MTLOrigin
0198             {width, height, 1} // MTLSize
0199         };
0200 
0201         // Copy the bytes from the data object into the texture
0202         [texture replaceRegion:region mipmapLevel:0 withBytes:p bytesPerRow:width];
0203         return texture;
0204     }
0205 
0206 private:
0207     enum Stage {
0208         VertexStage,
0209         FragmentStage
0210     };
0211     using FuncAndLib = QPair<id<MTLFunction>, id<MTLLibrary> >;
0212     QQuickWindow *m_window = nullptr;
0213     QByteArray m_vert;
0214     QByteArray m_vertEntryPoint;
0215     QByteArray m_frag;
0216     QByteArray m_fragEntryPoint;
0217     id<MTLDevice> m_device;
0218     id<MTLBuffer> m_vbuf;
0219     id<MTLBuffer> m_ubuf[3];
0220     FuncAndLib m_vs;
0221     FuncAndLib m_fs;
0222     id<MTLRenderPipelineState> m_pipeline;
0223     id<MTLTexture> m_texture[3];
0224 
0225     void prepareShader(Stage stage)
0226     {
0227         if (stage == VertexStage) {
0228             m_vert ="#include <metal_stdlib>\n"
0229                     "#include <simd/simd.h>\n"
0230                     "using namespace metal;"
0231                     "struct main0_out {"
0232                     "    float2 coords [[user(locn0)]];"
0233                     "    float4 vertices [[position]];"
0234                     "};"
0235                     "struct main0_in {"
0236                     "    float4 vertices [[attribute(0)]];"
0237                     "};"
0238                     "vertex main0_out main0(main0_in in [[stage_in]]) {"
0239                     "    main0_out out = {};"
0240                     "    out.vertices = vector_float4(in.vertices.xy, 0.0f, 1.0f);"
0241                     "    out.coords = in.vertices.zw;"
0242                     "    return out;"
0243                     "}";
0244             Q_ASSERT(!m_vert.isEmpty());
0245             m_vertEntryPoint = QByteArrayLiteral("main0");
0246         } else {
0247             m_frag ="#include <metal_stdlib>\n"
0248                     "#include <simd/simd.h>\n"
0249                     "using namespace metal;"
0250                     "struct buf {"
0251                     "    int colorspace;"
0252                     "};"
0253                     "struct main0_out {"
0254                     "    float4 fragColor [[color(0)]];"
0255                     "};"
0256                     "struct main0_in {"
0257                     "    float2 coords [[user(locn0)]];"
0258                     "};"
0259                     "fragment main0_out main0(main0_in in [[stage_in]], constant buf& ubuf [[buffer(0)]],"
0260                     "      texture2d<half> yTex [[texture(0)]],"
0261                     "      texture2d<half> uTex [[texture(1)]],"
0262                     "      texture2d<half> vTex [[texture(2)]]"
0263                     "      ) {"
0264                     "    main0_out out = {};"
0265                     "    constexpr sampler yuvSampler (mag_filter::linear, min_filter::linear);"
0266                     "    float3 yuv;"
0267                     "    yuv.x = yTex.sample(yuvSampler, in.coords).r -  16.0f/255.0f;"
0268                     "    yuv.y = uTex.sample(yuvSampler, in.coords).r - 128.0f/255.0f;"
0269                     "    yuv.z = vTex.sample(yuvSampler, in.coords).r - 128.0f/255.0f;"
0270                     "    float3x3 coefficients;"
0271                     "    if (ubuf.colorspace == 601) {"
0272                     "      coefficients = float3x3("
0273                     "        {1.1643f,  1.1643f,  1.1643f},"
0274                     "        {0.0f,    -0.39173f, 2.017f},"
0275                     "        {1.5958f, -0.8129f,  0.0f});"
0276                     "    } else {" // ITU-R 709
0277                     "      coefficients = float3x3("
0278                     "        {1.1643f, 1.1643f, 1.1643f},"
0279                     "        {0.0f,   -0.213f,  2.112f},"
0280                     "        {1.793f, -0.533f,  0.0f});"
0281                     "    }"
0282                     "    out.fragColor = float4(coefficients * yuv, 1.0f);"
0283                     "    return out;"
0284                     "}";
0285             m_fragEntryPoint = QByteArrayLiteral("main0");
0286         }
0287     }
0288 
0289     FuncAndLib compileShader(const QByteArray &source, const QByteArray &entryPoint)
0290     {
0291         FuncAndLib fl;
0292 
0293         NSString *srcstr = [NSString stringWithUTF8String: source.constData()];
0294         MTLCompileOptions *opts = [[MTLCompileOptions alloc] init];
0295         opts.languageVersion = MTLLanguageVersion1_2;
0296         NSError *err = nil;
0297         fl.second = [m_device newLibraryWithSource: srcstr options: opts error: &err];
0298         [opts release];
0299         // srcstr is autoreleased
0300 
0301         if (err) {
0302             const QString msg = QString::fromNSString(err.localizedDescription);
0303             qFatal("%s", qPrintable(msg));
0304             return fl;
0305         }
0306 
0307         NSString *name = [NSString stringWithUTF8String: entryPoint.constData()];
0308         fl.first = [fl.second newFunctionWithName: name];
0309 //        [name release];
0310 
0311         return fl;
0312     }
0313 };
0314 // clang-format on
0315 
0316 MetalVideoWidget::MetalVideoWidget(int id, QObject *parent)
0317     : VideoWidget{id, parent}
0318     , m_renderer{new MetalVideoRenderer}
0319 {
0320     m_maxTextureSize = 16384;
0321 }
0322 
0323 MetalVideoWidget::~MetalVideoWidget() {}
0324 
0325 void MetalVideoWidget::initialize()
0326 {
0327     m_renderer->displayRulerHeight = qRound(m_displayRulerHeight * devicePixelRatioF() * 0.5);
0328     m_renderer->initialize(quickWindow());
0329     VideoWidget::initialize();
0330 }
0331 
0332 void MetalVideoWidget::renderVideo()
0333 {
0334     m_mutex.lock();
0335     if (m_sharedFrame.is_valid()) {
0336         m_renderer->render(size(), rect(), devicePixelRatio(), zoom(), offset(), m_sharedFrame);
0337     }
0338     m_mutex.unlock();
0339     VideoWidget::renderVideo();
0340 }
0341 
0342 void MetalVideoWidget::updateRulerHeight(int addedHeight)
0343 {
0344     VideoWidget::updateRulerHeight(addedHeight);
0345     m_renderer->displayRulerHeight = qRound(m_displayRulerHeight * devicePixelRatioF() * 0.5);
0346 }
0347 
0348 #include "metalvideowidget.moc"