diff --git a/CMakeLists.txt b/CMakeLists.txt index 5101ef8..2f11bd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,17 @@ cmake_minimum_required(VERSION 3.20) project(cuframes-composer VERSION 0.1.0 DESCRIPTION "Multi-source video grid composer на CUDA + NVENC + RTSP" - LANGUAGES C CUDA + LANGUAGES C CXX CUDA ) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) +# Phase 11b — C++17 для ООП-модели Cell/Layout/Decoration. Low-level +# модули (source, nvenc, frigate_mqtt, health, writer, audio) остаются +# на C; их API объявлен `extern "C"` чтобы линковаться с C++ кодом. +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # CUDA архитектуры. Покрываем production-сценарии: diff --git a/include/cuframes_composer/cpp/cell.hpp b/include/cuframes_composer/cpp/cell.hpp new file mode 100644 index 0000000..896dd00 --- /dev/null +++ b/include/cuframes_composer/cpp/cell.hpp @@ -0,0 +1,64 @@ +/* Cell — базовый абстрактный класс ячейки композитора (Phase 11b). + * + * Cell — это прямоугольная область output frame, рисуемая в свою geom_. + * Реализации (CameraCell, WidgetCell, BlankCell) определяют content-рендер; + * декорации (Label, Border) добавляются композицией через add_decoration(). + * + * Lifecycle: + * 1. Layout::apply_template() создаёт нужные Cell-подклассы. + * 2. На каждом compose: cell.draw(stream, dst) рисует свой контент + * + все decorations. + * 3. Layout уничтожает cells при apply нового template'а. + * + * Лицензия: LGPL-2.1+ + */ + +#ifndef CUFRAMES_COMPOSER_CPP_CELL_HPP +#define CUFRAMES_COMPOSER_CPP_CELL_HPP + +#include "decoration.hpp" +#include "types.hpp" + +#include +#include + +namespace cfc { + +class Cell { +public: + explicit Cell(const Rect& geom) : geom_(geom) {} + virtual ~Cell() = default; + + Cell(const Cell&) = delete; + Cell& operator=(const Cell&) = delete; + + /* Геометрия cell в pixel-координатах output frame. */ + const Rect& geometry() const noexcept { return geom_; } + void set_geometry(const Rect& r) noexcept { geom_ = r; } + + /* Добавить decoration (cell takes ownership). */ + void add_decoration(std::unique_ptr d) { + decorations_.push_back(std::move(d)); + } + + /* Основной hook: рисует content + все decorations. Реализации обычно + * переопределяют только draw_content(), а draw_decorations() общий. */ + void draw(CUstream stream, NV12Ref& dst) { + if (geom_.empty()) return; + draw_content(stream, dst); + for (auto& dec : decorations_) { + dec->draw(stream, dst, geom_); + } + } + +protected: + /* Реализуется подклассом. */ + virtual void draw_content(CUstream stream, NV12Ref& dst) = 0; + + Rect geom_; + std::vector> decorations_; +}; + +} // namespace cfc + +#endif /* CUFRAMES_COMPOSER_CPP_CELL_HPP */ diff --git a/include/cuframes_composer/cpp/cuda_raii.hpp b/include/cuframes_composer/cpp/cuda_raii.hpp new file mode 100644 index 0000000..1644ff4 --- /dev/null +++ b/include/cuframes_composer/cpp/cuda_raii.hpp @@ -0,0 +1,134 @@ +/* RAII обёртки над CUDA Driver/Runtime ресурсами (Phase 11b). + * + * Передача handle'ов между объектами по-прежнему zero-copy (CUdeviceptr — + * это unsigned long long; обмен идентичен plain C-коду). Эти обёртки только + * автоматизируют lifetime — без них приходилось бы вручную помнить про + * cuMemFree и закрывать stream'ы в путях ошибок. + * + * NB: классы non-copyable (чтобы не вызвать двойной cuMemFree), но movable. + * + * Лицензия: LGPL-2.1+ + */ + +#ifndef CUFRAMES_COMPOSER_CPP_CUDA_RAII_HPP +#define CUFRAMES_COMPOSER_CPP_CUDA_RAII_HPP + +#include +#include +#include + +namespace cfc { + +/* VMM-allocated NV12 буфер для output / staging. Используется и compose, + * и NVENC (через тот же CUdeviceptr — zero-copy). */ +class CudaBuffer { +public: + CudaBuffer() = default; + + /* Аллокация в ctor; бросать исключения не хочется — проверяем ok(). */ + explicit CudaBuffer(std::size_t bytes) { + if (cuMemAlloc(&ptr_, bytes) == CUDA_SUCCESS) { + size_ = bytes; + } + } + + ~CudaBuffer() { reset(); } + + CudaBuffer(const CudaBuffer&) = delete; + CudaBuffer& operator=(const CudaBuffer&) = delete; + + CudaBuffer(CudaBuffer&& other) noexcept + : ptr_(other.ptr_), size_(other.size_) { + other.ptr_ = 0; + other.size_ = 0; + } + CudaBuffer& operator=(CudaBuffer&& other) noexcept { + if (this != &other) { + reset(); + ptr_ = other.ptr_; + size_ = other.size_; + other.ptr_ = 0; + other.size_ = 0; + } + return *this; + } + + void reset() noexcept { + if (ptr_) { + cuMemFree(ptr_); + ptr_ = 0; + size_ = 0; + } + } + + CUdeviceptr ptr() const noexcept { return ptr_; } + std::size_t size() const noexcept { return size_; } + bool ok() const noexcept { return ptr_ != 0; } + +private: + CUdeviceptr ptr_ = 0; + std::size_t size_ = 0; +}; + +/* CUDA stream — owner. Композитор использует default stream (Phase 2/3), + * но обёртка готова к stream-pipelining (Phase 12+). */ +class CudaStream { +public: + CudaStream() = default; + + /* Создать non-default stream. */ + static CudaStream create() { + CudaStream s; + cudaStreamCreate(&s.stream_); + s.owned_ = (s.stream_ != nullptr); + return s; + } + + /* Обёртка над уже существующим stream'ом (не владеет). */ + static CudaStream wrap(cudaStream_t s) noexcept { + CudaStream w; + w.stream_ = s; + w.owned_ = false; + return w; + } + + ~CudaStream() { reset(); } + + CudaStream(const CudaStream&) = delete; + CudaStream& operator=(const CudaStream&) = delete; + + CudaStream(CudaStream&& other) noexcept + : stream_(other.stream_), owned_(other.owned_) { + other.stream_ = nullptr; + other.owned_ = false; + } + CudaStream& operator=(CudaStream&& other) noexcept { + if (this != &other) { + reset(); + stream_ = other.stream_; + owned_ = other.owned_; + other.stream_ = nullptr; + other.owned_ = false; + } + return *this; + } + + void reset() noexcept { + if (owned_ && stream_) { + cudaStreamDestroy(stream_); + } + stream_ = nullptr; + owned_ = false; + } + + cudaStream_t handle() const noexcept { return stream_; } + CUstream cu_handle() const noexcept { return reinterpret_cast(stream_); } + +private: + cudaStream_t stream_ = nullptr; + bool owned_ = false; +}; + +} // namespace cfc + +#endif /* CUFRAMES_COMPOSER_CPP_CUDA_RAII_HPP */ diff --git a/include/cuframes_composer/cpp/decoration.hpp b/include/cuframes_composer/cpp/decoration.hpp new file mode 100644 index 0000000..4ae008c --- /dev/null +++ b/include/cuframes_composer/cpp/decoration.hpp @@ -0,0 +1,33 @@ +/* Decoration — украшение поверх cell (Phase 11b). + * + * Cell держит vector> и вызывает draw() каждого + * после своего content-рендера. Decorations знают только Rect cell'а + * (для позиционирования относительно неё) и пишут в тот же NV12Ref. + * + * Типы (минимум): + * LabelDecoration — текстовая подпись (FreeType atlas), позиция = угол cell + * BorderDecoration — рамка thickness px (4 fill_nv12 — top/bottom/left/right) + * + * Расширяется: BadgeDecoration, MotionIndicator, RecordingDot и т.д. + * + * Лицензия: LGPL-2.1+ + */ + +#ifndef CUFRAMES_COMPOSER_CPP_DECORATION_HPP +#define CUFRAMES_COMPOSER_CPP_DECORATION_HPP + +#include "types.hpp" + +namespace cfc { + +class Decoration { +public: + virtual ~Decoration() = default; + + /* Нарисовать поверх parent_rect. NV12Ref общий с cell'ом. */ + virtual void draw(CUstream stream, NV12Ref& dst, const Rect& parent_rect) = 0; +}; + +} // namespace cfc + +#endif /* CUFRAMES_COMPOSER_CPP_DECORATION_HPP */ diff --git a/include/cuframes_composer/cpp/types.hpp b/include/cuframes_composer/cpp/types.hpp new file mode 100644 index 0000000..371a8fe --- /dev/null +++ b/include/cuframes_composer/cpp/types.hpp @@ -0,0 +1,48 @@ +/* Базовые типы C++-модели композитора (Phase 11b). + * + * Rect — pixel-координаты в output frame buffer'е (1920×1080 default). + * NV12Surface — wrapper над VMM-buffer'ом с pitch_y/pitch_uv для совместного + * использования compose и encoder'ом. По сути reference на CUdeviceptr — + * никаких копий не делается, ownership держит OutputSurface. + * + * Лицензия: LGPL-2.1+ + */ + +#ifndef CUFRAMES_COMPOSER_CPP_TYPES_HPP +#define CUFRAMES_COMPOSER_CPP_TYPES_HPP + +#include + +namespace cfc { + +/* Прямоугольник в pixel-координатах output frame. Все координаты должны + * быть чётными (требование NV12 4:2:0). */ +struct Rect { + int x = 0, y = 0; + int w = 0, h = 0; + + bool empty() const noexcept { return w <= 0 || h <= 0; } + int right() const noexcept { return x + w; } + int bottom() const noexcept { return y + h; } +}; + +/* Reference на NV12-плоскости в VRAM. НЕ owner — все CUdeviceptr'ы + * принадлежат OutputSurface, передаются read-write всем cells/decorations. + * + * Слои: + * y_ptr — Y plane, size = pitch_y * height + * uv_ptr — UV plane (interleaved 2:0), size = pitch_uv * height/2 + * + * frame_w / frame_h — размер всего output буфера (для clipping). */ +struct NV12Ref { + CUdeviceptr y_ptr = 0; + int pitch_y = 0; + CUdeviceptr uv_ptr = 0; + int pitch_uv = 0; + int frame_w = 0; + int frame_h = 0; +}; + +} // namespace cfc + +#endif /* CUFRAMES_COMPOSER_CPP_TYPES_HPP */