Phase 11b A: CMake C++17 + базовые headers

Branch phase11b-cpp — refactor композитора на ООП.

Что сделано в этом коммите:
  - CMakeLists.txt: CMAKE_CXX_STANDARD 17, language=CXX
  - include/cuframes_composer/cpp/cuda_raii.hpp: CudaBuffer + CudaStream
    как RAII обёртки (cuMemAlloc/cuMemFree, cuStreamCreate/Destroy).
    Non-copyable, movable. Zero-copy: handle CUdeviceptr передаётся
    идентично C-коду.
  - cpp/types.hpp: Rect (pixel coords) + NV12Ref (общий read-write
    референс на Y/UV plane'ы output буфера — composer + cells + decorations
    делят его без копий).
  - cpp/decoration.hpp: абстрактный Decoration с draw(stream, dst, parent_rect).
  - cpp/cell.hpp: абстрактный Cell с draw() = draw_content() +
    iterate decorations. Композиция через add_decoration().

Что НЕ сделано (следующие коммиты):
  - CameraCell, WidgetCell, BlankCell (cell-content реализации)
  - LabelDecoration, BorderDecoration (с FreeType/cugrid)
  - Layout (контейнер cells + apply_template)
  - Composer класс (owner SourcePool + Layout + OutputSurface)
  - extern "C" ABI shim для совместимости с control.c, grid_record.c
  - Удаление старых composer.c / overlay.c / layouts.c
  - Восстановление функционала JSON templates + auto-labels

Производительность: virtual call overhead 1 indirect call per cell per
frame (negligible), никаких heap allocations в hot path, CUDA pipeline
1:1 идентичен C-версии.

Refs: #195 (Phase 11b C++ refactor)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 21:27:44 +01:00
parent f8e27b9e85
commit f1c79eabde
5 changed files with 286 additions and 1 deletions
+7 -1
View File
@@ -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-сценарии:
+64
View File
@@ -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 <memory>
#include <vector>
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<Decoration> 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<std::unique_ptr<Decoration>> decorations_;
};
} // namespace cfc
#endif /* CUFRAMES_COMPOSER_CPP_CELL_HPP */
+134
View File
@@ -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 <cuda.h>
#include <cuda_runtime.h>
#include <utility>
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<CUstream>(stream_); }
private:
cudaStream_t stream_ = nullptr;
bool owned_ = false;
};
} // namespace cfc
#endif /* CUFRAMES_COMPOSER_CPP_CUDA_RAII_HPP */
@@ -0,0 +1,33 @@
/* Decoration — украшение поверх cell (Phase 11b).
*
* Cell держит vector<unique_ptr<Decoration>> и вызывает 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 */
+48
View File
@@ -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 <cuda.h>
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 */