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:
+7
-1
@@ -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-сценарии:
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
Reference in New Issue
Block a user