paint-brush
Một cách dễ dàng để phát triển Plugin Apple Metal của riêng bạn và tích hợp nó vào DaVinci Resolvetừ tác giả@denissvinarchuk
678 lượt đọc
678 lượt đọc

Một cách dễ dàng để phát triển Plugin Apple Metal của riêng bạn và tích hợp nó vào DaVinci Resolve

từ tác giả Denis Svinarchuk13m2024/03/13
Read on Terminal Reader

dài quá đọc không nổi

OFX, hay còn gọi là API xử lý hình ảnh OFX, là một tiêu chuẩn mở để tạo hiệu ứng hình ảnh 2D và tổng hợp video. Nó hoạt động theo mô hình phát triển ứng dụng giống như plugin. Về cơ bản, nó đóng vai trò vừa là Máy chủ - một ứng dụng cung cấp một tập hợp các phương thức, vừa là một Plug-in - một ứng dụng hoặc mô-đun triển khai tập hợp này. Cấu hình này mang lại khả năng mở rộng không giới hạn chức năng của ứng dụng máy chủ.
featured image - Một cách dễ dàng để phát triển Plugin Apple Metal của riêng bạn và tích hợp nó vào DaVinci Resolve
Denis Svinarchuk HackerNoon profile picture

OFX, hay còn gọi là API xử lý hình ảnh OFX , là một tiêu chuẩn mở để tạo hiệu ứng hình ảnh 2D và tổng hợp video. Nó hoạt động theo mô hình phát triển ứng dụng giống như plugin. Về cơ bản, nó đóng vai trò vừa là Máy chủ - một ứng dụng cung cấp một tập hợp các phương thức, vừa là một Plug-in - một ứng dụng hoặc mô-đun triển khai tập hợp này.


Cấu hình này mang lại khả năng mở rộng không giới hạn chức năng của ứng dụng máy chủ.

DaVinci Resolve và Metal

Các ứng dụng như Final Cut X và DaVinci Resolve Studio, bắt đầu từ phiên bản 16, hỗ trợ đầy đủ các đường dẫn Apple Metal. Tương tự như OpenCL và Cuda, trong trường hợp OFX, bạn có thể lấy bộ mô tả hoặc trình xử lý hàng đợi lệnh dành riêng cho nền tảng. Hệ thống máy chủ cũng chịu trách nhiệm phân bổ một nhóm các hàng đợi như vậy và cân bằng các phép tính trên chúng.


Hơn nữa, nó đặt dữ liệu clip hình ảnh nguồn và đích vào bộ nhớ GPU, đơn giản hóa đáng kể việc phát triển chức năng mở rộng.

Hỗ trợ phiên bản OFX trong Resolve

Với Resolve, mọi thứ phức tạp hơn một chút. DaVinci thông báo hỗ trợ OFX v1.4, mặc dù có một số hạn chế. Cụ thể, một số phương pháp làm việc với các chức năng giao diện không có sẵn để sử dụng. Để xác định phương thức nào khả dụng, OFX cho phép bạn kiểm tra bộ được hỗ trợ thông qua các truy vấn khóa/giá trị.


Phương pháp xuất bản trong mã plugin dựa trên lệnh gọi C. Nhưng chúng tôi sẽ sử dụng shell OpenFXS C++ được điều chỉnh cho C++17. Để thuận tiện, tôi đã biên soạn mọi thứ vào một kho lưu trữ: dehancer-external được lấy từ dự án Dehancer mã nguồn mở.

Khái niệm OFXS

Trong dự án này, tôi sẽ sử dụng OpenFXS, một tiện ích mở rộng C++ cho OpenFX do Bruno Nicoletti viết ban đầu và đã trở nên phổ biến theo thời gian trong các dự án xử lý video thương mại và nguồn mở.


OpenFXS ban đầu không được điều chỉnh cho phù hợp với các phương ngữ C++ hiện đại, vì vậy tôi đã cập nhật nó để tương thích với C++17 .


OFX, và do đó là OFXS, là một mô-đun phần mềm độc lập được chương trình máy chủ tải động. Về cơ bản, nó là một thư viện động được tải khi ứng dụng chính khởi động. OpenFXS, giống như OFX, phải xuất bản chữ ký phương thức. Do đó, chúng tôi sử dụng một phương thức C từ mã.


Để bắt đầu phát triển trong OpenFXS, bạn cần đồng ý với một số nhóm lớp phổ biến được sử dụng để tạo chức năng mới trong ứng dụng của bạn. Thông thường, trong một dự án mới, bạn cần kế thừa từ các lớp này và triển khai hoặc ghi đè một số phương thức ảo.


Để tạo plugin của riêng bạn trên hệ thống máy chủ, hãy bắt đầu bằng cách làm quen với các lớp công khai sau và phương thức tương tự:


  • OFX::PluginFactoryHelper là một mẫu cơ bản để tạo bộ cấu trúc dữ liệu và bảng điều khiển của plugin (mặc dù nó có thể để trống). Lớp kế thừa tạo ra một đối tượng đơn đăng ký một tập hợp các tham số và giá trị đặt trước trong hệ thống máy chủ mà nhà phát triển đăng ký mô-đun của mình;


  • OFX::ParamSetDescriptor - lớp chứa cơ sở để tạo và lưu trữ các thuộc tính cấu trúc;


  • OFX::ImageEffectDescriptor - vùng chứa các thuộc tính được sử dụng khi thao tác dữ liệu đồ họa khi gọi các thủ tục xử lý dữ liệu. Được ứng dụng máy chủ sử dụng để lưu bối cảnh xử lý các tham số trong cơ sở dữ liệu nội bộ và làm việc với các thuộc tính plugin được xác định cho từng phiên bản của nó;


  • OFX::ParamSet - một bộ cài đặt cho phép bạn thao tác cấu trúc dữ liệu đã đăng ký;


  • OFX::ImageEffect - một tập hợp các cài đặt cho hiệu ứng trên dữ liệu đồ họa, được kế thừa từ OFX::ParamSet;


  • OFX::MultiThread::Processor - trong lớp con, cần triển khai xử lý luồng dữ liệu: hình ảnh hoặc video;


  • OFX::Plugin::getPluginIDs - phương thức đăng ký plugin (nhà máy) trong ứng dụng máy chủ;

Màu sai

Một đặc điểm giúp phân biệt quá trình quay video với việc chỉ chụp một hình ảnh trong ảnh là sự thay đổi động của cảnh và ánh sáng của cả cảnh nói chung và các khu vực trong ảnh. Điều này xác định cách kiểm soát độ phơi sáng trong quá trình chụp.


Trong video kỹ thuật số, có một chế độ giám sát điều khiển dành cho người vận hành trong đó mức độ phơi sáng của các khu vực được ánh xạ thành một tập hợp giới hạn các vùng, mỗi vùng có màu riêng.


Chế độ này đôi khi được gọi là chế độ "kẻ săn mồi" hoặc chế độ Màu sai. Thang đo thường được tham chiếu đến thang đo IRE.


Màn hình như vậy cho phép bạn xem các vùng phơi sáng và tránh những sai lầm đáng kể khi cài đặt các thông số chụp của máy ảnh. Một cái gì đó có ý nghĩa tương tự được sử dụng khi phơi sáng trong nhiếp ảnh - ví dụ như phân vùng theo Adams.


Bạn có thể đo một mục tiêu cụ thể bằng máy đo độ phơi sáng và xem mục tiêu đó nằm ở vùng nào và trong thời gian thực, chúng tôi nhìn thấy các vùng đó, được tô màu gọn gàng để dễ nhận biết.


Số lượng vùng được xác định bởi mục tiêu và khả năng của màn hình điều khiển. Ví dụ: màn hình được sử dụng với máy ảnh Arri Alexa có thể kết hợp tối đa 6 vùng.


Phần mềm phiên bản “kẻ săn mồi” với 16 vùng


Thêm tiện ích mở rộng

Trước khi tiếp tục với ví dụ, chúng ta cần thêm một số lớp proxy đơn giản để triển khai OpenFXS làm nền tảng xử lý dữ liệu nguồn, chẳng hạn như Kết cấu kim loại. Các lớp này bao gồm:


  • imelling::Image : Một lớp proxy cho dữ liệu clip OFX.


  • immetalling::Image2Texture : Một functor để truyền dữ liệu từ bộ đệm clip sang kết cấu Kim loại. Từ DaVinci, bạn có thể trích xuất bộ đệm có cấu trúc và gói giá trị kênh hình ảnh bất kỳ vào plugin và nó sẽ được trả về ở dạng tương tự.


    Để làm việc với định dạng luồng trong OFX dễ dàng hơn, bạn có thể yêu cầu máy chủ chuẩn bị trước dữ liệu thuộc loại cụ thể. Tôi sẽ sử dụng các phao được đóng gói trong RGBA - đỏ/xanh/xanh/alpha.


  • immetalling::ImageFromTexture : Hàm đảo ngược để chuyển đổi luồng thành bộ đệm hệ thống máy chủ. Như bạn có thể thấy, có khả năng tối ưu hóa đáng kể các phép tính nếu bạn dạy các lõi điện toán Metal hoạt động không phải với kết cấu mà trực tiếp với bộ đệm.


Chúng tôi kế thừa các lớp cơ sở OFXS và viết chức năng của mình mà không đi sâu vào chi tiết về cách hoạt động của lõi Metal:


  • immetalling::falsecolor::Processor : Tại đây, chúng tôi triển khai chuyển đổi luồng và bắt đầu quá trình xử lý.


  • immetalling::falsecolor::Factory : Đây sẽ là phần cụ thể của chúng tôi trong phần mô tả bộ plugin. Chúng ta cần triển khai một số lệnh gọi bắt buộc liên quan đến việc thiết lập cấu trúc và tạo một phiên bản của lớp OFX::ImageEffect với chức năng cụ thể mà chúng tôi chia thành hai lớp con khi triển khai: Tương tác và Plugin.


  • immetalling::falsecolor::Interaction : Triển khai phần tương tác khi làm việc với các hiệu ứng. Về cơ bản, đây là việc triển khai các phương thức ảo duy nhất từ OFX::ImageEffect liên quan đến việc xử lý các thay đổi trong tham số plugin.


  • imetalling::falsecolor::Plugin : Triển khai kết xuất luồng, nghĩa là khởi chạy imetalling::Processor.


Ngoài ra, chúng ta sẽ cần một số lớp tiện ích được xây dựng trên Metal để phân tách hợp lý mã máy chủ và mã hạt nhân trên MSL. Bao gồm các:


  • immetalling::Function : Một lớp cơ sở che khuất hoạt động với hàng đợi lệnh Metal. Tham số chính sẽ là tên của kernel trong mã MSL và người thực thi lệnh gọi kernel.


  • immetalling:Kernel : Một lớp chung để chuyển đổi kết cấu nguồn thành kết cấu đích, mở rộng Hàm để chỉ đơn giản đặt các tham số để gọi hạt nhân MSL.


  • imetalling::PassKernel : Bỏ qua kernel.


  • immetalling::FalseColorKernel : Lớp chức năng chính của chúng tôi, một trình mô phỏng "kẻ săn mồi" áp phích (mẫu xuống) thành một số màu được chỉ định.


Mã hạt nhân cho chế độ "kẻ săn mồi" có thể trông như thế này:

 static constant float3 kIMP_Y_YUV_factor = {0.2125, 0.7154, 0.0721}; constexpr sampler baseSampler(address::clamp_to_edge, filter::linear, coord::normalized); inline float when_eq(float x, float y) {  return 1.0 - abs(sign(x - y)); } static inline float4 sampledColor(        texture2d<float, access::sample> inTexture,        texture2d<float, access::write> outTexture,        uint2 gid ){  float w = outTexture.get_width();  return mix(inTexture.sample(baseSampler, float2(gid) * float2(1.0/(w-1.0), 1.0/float(outTexture.get_height()-1))),             inTexture.read(gid),             when_eq(inTexture.get_width(), w) // whe equal read exact texture color  ); } kernel void kernel_falseColor(        texture2d<float, access::sample> inTexture [[texture(0)]],        texture2d<float, access::write> outTexture [[texture(1)]],        device float3* color_map [[ buffer(0) ]],        constant uint& level [[ buffer(1) ]],        uint2 gid [[thread_position_in_grid]]) {  float4 inColor = sampledColor(inTexture,outTexture,gid);  float luminance = dot(inColor.rgb, kIMP_Y_YUV_factor);  uint     index = clamp(uint(luminance*(level-1)),uint(0),uint(level-1));  float4   color = float4(1);  if (index<level)    color.rgb = color_map[index];  outTexture.write(color,gid); }


Khởi tạo Plugin OFX

Chúng ta sẽ bắt đầu bằng việc định nghĩa lớp imetalling::falsecolor::Factory. Trong lớp này, chúng ta sẽ đặt một tham số duy nhất - trạng thái của màn hình (bật hoặc tắt). Điều này là cần thiết cho ví dụ của chúng tôi.

Chúng ta sẽ kế thừa từ OFX::PluginFactoryHelper và nạp chồng 5 phương thức:


  • Load() : Phương thức này được gọi để định cấu hình toàn cục phiên bản khi plugin được tải lần đầu tiên. Quá tải phương pháp này là tùy chọn.


  • unload() : Phương thức này được gọi khi một phiên bản được dỡ tải, ví dụ, để xóa bộ nhớ. Quá tải phương pháp này cũng là tùy chọn.


  • mô tả(ImageEffectDescriptor&) : Đây là phương thức thứ hai mà máy chủ OFX gọi khi plugin được tải. Nó là ảo và phải được định nghĩa trong lớp của chúng ta. Trong phương pháp này, chúng ta cần đặt tất cả các thuộc tính của plugin, bất kể loại ngữ cảnh của nó. Để biết thêm chi tiết về các thuộc tính, hãy tham khảo mã ImageEffectDescriptor .


  • mô tảInContext(ImageEffectDescriptor&,ContextEnum) : Tương tự như phương thức describe , phương thức này cũng được gọi khi plugin được tải và phải được xác định trong lớp của chúng ta. Nó sẽ xác định các thuộc tính liên quan đến bối cảnh hiện tại.


    Ngữ cảnh xác định loại hoạt động mà ứng dụng hoạt động, chẳng hạn như bộ lọc, sơn, hiệu ứng chuyển tiếp hoặc bộ đếm thời gian khung trong clip.


  • createInstance(OfxImageEffectHandle, ContextEnum) : Đây là phương thức quan trọng nhất mà chúng tôi nạp chồng. Chúng ta trả về một con trỏ tới một đối tượng thuộc loại ImageEffect . Nói cách khác, imetalling::falsecolor::Plugin của chúng tôi trong đó chúng tôi đã xác định tất cả các chức năng, cả về các sự kiện của người dùng trong chương trình máy chủ và hiển thị (chuyển đổi) khung nguồn thành khung đích:
 OFX::ImageEffect *Factory::createInstance(OfxImageEffectHandle handle,OFX::ContextEnum) {     return new Plugin(handle);   }


Xử lý sự kiện

Ở giai đoạn này, nếu bạn biên dịch một gói bằng mô-đun OFX, thì plugin sẽ có sẵn trong ứng dụng máy chủ và trong DaVinci, nó có thể được tải lên nút chỉnh sửa.


Tuy nhiên, để hoạt động hoàn toàn với một phiên bản plugin, bạn cần xác định ít nhất phần tương tác và phần liên quan đến việc xử lý luồng video đến.


Để thực hiện điều này, chúng ta kế thừa từ lớp OFX::ImageEffect và nạp chồng các phương thức ảo:


  • đã thay đổiParam(const OFX::InstanceChangedArgs&, const std::string&) - Phương thức này cho phép chúng ta xác định logic để xử lý sự kiện. Loại sự kiện được xác định bởi giá trị của OFX::InstanceChangedArgs::reason và có thể là: eChangeUserEdit, eChangePluginEdit, eChangeTime - sự kiện xảy ra do thuộc tính được người dùng chỉnh sửa, thay đổi trong plugin hoặc ứng dụng máy chủ hoặc là kết quả của sự thay đổi trong dòng thời gian.


    Tham số thứ hai chỉ định tên chuỗi mà chúng tôi đã xác định ở giai đoạn khởi tạo plugin, trong trường hợp của chúng tôi, đó là một tham số: false_color_enabled_check_box .


  • isIdentity(...) - Phương thức này cho phép chúng ta xác định logic để phản ứng với một sự kiện và trả về trạng thái xác định xem có điều gì đó đã thay đổi hay không và liệu kết xuất có hợp lý hay không. Phương thức phải trả về false hoặc true. Đây là một cách để tối ưu hóa và giảm bớt số lượng tính toán không cần thiết.


Bạn có thể đọc cách triển khai tương tác tương tác với OFX trong mã Interaction.cpp . Như bạn có thể thấy, chúng ta nhận được các con trỏ tới các clip: đoạn nguồn và vùng bộ nhớ mà chúng ta sẽ đặt phép biến đổi đích trong đó.

Triển khai khởi chạy kết xuất

Chúng tôi sẽ thêm một lớp logic khác mà trên đó chúng tôi sẽ xác định tất cả logic để khởi chạy quá trình chuyển đổi. Trong trường hợp của chúng tôi, đây là phương pháp duy nhất để ghi đè cho đến nay:


  • render(const OFX::RenderArguments& args) - Tại đây, bạn có thể tìm hiểu các thuộc tính của clip và quyết định cách hiển thị chúng. Ngoài ra, ở giai đoạn này, hàng đợi lệnh Metal và một số thuộc tính hữu ích liên quan đến thuộc tính dòng thời gian hiện tại sẽ có sẵn cho chúng tôi.

Xử lý

Ở giai đoạn khởi chạy, chúng tôi đã có sẵn một đối tượng với các thuộc tính hữu ích: chúng tôi có ít nhất một con trỏ tới luồng video (chính xác hơn là vùng bộ nhớ có dữ liệu hình ảnh khung) và quan trọng nhất là một hàng lệnh Metal.


Bây giờ, chúng ta có thể xây dựng một lớp chung sẽ đưa chúng ta đến gần hơn với một dạng đơn giản của việc sử dụng lại mã hạt nhân. Tiện ích mở rộng OpenFXS đã có một lớp như vậy: OFX::ImageProcessor; chúng ta chỉ cần làm quá tải nó.


Trong hàm tạo, nó có tham số OFX::ImageEffect, tức là trong đó, chúng ta sẽ không chỉ nhận được trạng thái hiện tại của các tham số plugin mà còn mọi thứ cần thiết để làm việc với GPU.


Ở giai đoạn này, chúng ta chỉ cần nạp chồng phương thức processImagesMetal() và bắt đầu xử lý các kernel đã được triển khai trên Metal.

 Processor::Processor(            OFX::ImageEffect *instance,            OFX::Clip *source,            OFX::Clip *destination,            const OFX::RenderArguments &args,            bool enabled    ) :            OFX::ImageProcessor(*instance),            enabled_(enabled),            interaction_(instance),            wait_command_queue_(false),            /// grab the current frame of a clip from OFX host memory            source_(source->fetchImage(args.time)),            /// create a target frame of a clip with the memory area already specified in OFX            destination_(destination->fetchImage(args.time)),            source_container_(nullptr),            destination_container_(nullptr)    {      /// Set OFX rendering arguments to GPU      setGPURenderArgs(args);      /// Set render window      setRenderWindow(args.renderWindow);      /// Place source frame data in Metal texture      source_container_ = std::make_unique<imetalling::Image2Texture>(_pMetalCmdQ, source_);      /// Create empty target frame texture in Metal      destination_container_ = std::make_unique<imetalling::Image2Texture>(_pMetalCmdQ, destination_);      /// Get parameters for packing data in the memory area of the target frame      OFX::BitDepthEnum dstBitDepth = destination->getPixelDepth();      OFX::PixelComponentEnum dstComponents = destination->getPixelComponents();      /// and original      OFX::BitDepthEnum srcBitDepth = source->getPixelDepth();      OFX::PixelComponentEnum srcComponents = source->getPixelComponents();      /// show a message to the host system that something went wrong      /// and cancel rendering of the current frame      if ((srcBitDepth != dstBitDepth) || (srcComponents != dstComponents)) {        OFX::throwSuiteStatusException(kOfxStatErrValue);      }      /// set in the current processor context a pointer to the memory area of the target frame      setDstImg(destination_.get_ofx_image());    }    void Processor::processImagesMetal() {      try {        if (enabled_)          FalseColorKernel(_pMetalCmdQ,                           source_container_->get_texture(),                           destination_container_->get_texture()).process();        else          PassKernel(_pMetalCmdQ,                           source_container_->get_texture(),                           destination_container_->get_texture()).process();        ImageFromTexture(_pMetalCmdQ,                         destination_,                         destination_container_->get_texture(),                         wait_command_queue_);      }      catch (std::exception &e) {        interaction_->sendMessage(OFX::Message::eMessageError, "#message0", e.what());      }    }


Xây dựng dự án

Để xây dựng dự án, bạn sẽ cần CMake và ít nhất nó phải là phiên bản 3.15. Ngoài ra, bạn sẽ yêu cầu Qt5.13, hỗ trợ việc lắp ráp gói dễ dàng và thuận tiện với trình cài đặt plugin trong thư mục hệ thống. Để bắt đầu cmake, trước tiên bạn phải tạo thư mục bản dựng.


Sau khi tạo thư mục build, bạn có thể thực hiện lệnh sau:


 cmake -DPRINT_DEBUG=ON -DQT_INSTALLER_PREFIX=/Users/<user>/Develop/QtInstaller -DCMAKE_PREFIX_PATH=/Users/<user>/Develop/Qt/5.13.0/clang_64/lib/cmake -DPLUGIN_INSTALLER_DIR=/Users/<user>/Desktop -DCMAKE_INSTALL_PREFIX=/Library/OFX/Plugins .. && make install 


“Kẻ săn mồi” cá nhân của bạn


Sau đó, trình cài đặt có tên IMFalseColorOfxInstaller.app , sẽ xuất hiện trong thư mục mà bạn đã chỉ định trong tham số PLUGIN_INSTALLER_DIR . Hãy tiếp tục và khởi động nó! Sau khi cài đặt thành công, bạn có thể khởi động DaVinci Resolve và bắt đầu sử dụng plugin mới của chúng tôi.


Bạn có thể tìm và chọn nó trong bảng OpenFX trên trang sửa màu và thêm nó làm nút.



Làm việc sai màu



liện kết ngoại

  1. Mã plugin OFX sai màu
  2. Hiệp hội hiệu ứng mở
  3. Download DaVinci Resolve - Phiên bản file tiêu đề OFX và mã thư viện OFXS trong phần Resolve + ví dụ