20 KiB
Vimix Rendering Architecture Overview
Executive Summary
Vimix is a real-time video mixing application with a sophisticated three-tier rendering pipeline:
- Source Rendering Layer - Individual sources render to frame buffers via GStreamer
- Composition Layer - Session compositing sources together into a single output frame
- UI/Display Layer - OpenGL window rendering with ImGui UI overlay
The main rendering loop operates at a fixed ~62 FPS framerate with callbacks for each stage.
1. Main Application Loop
Loop Structure
// Main rendering loop (lines 261-262)
while ( Rendering::manager().isActive() )
Rendering::manager().draw();
Initialization Flow (lines 209-247)
1. Rendering::manager().init() // OpenGL/GLFW initialization
2. UserInterface::manager().Init() // ImGui setup
3. Audio::manager().initialize() // Audio system
4. pushBackDrawCallback(prepare) // Register update callback
5. pushBackDrawCallback(drawScene) // Register scene rendering callback
6. pushBackDrawCallback(renderGUI) // Register UI rendering callback
7. Rendering::manager().show() // Display windows
8. Mixer::manager().load(_openfile) // Load session
9. Main loop starts
2. Main Rendering Loop Implementation
Location
- Method:
Rendering::draw()(lines 608-673)
Rendering Pipeline Flow
Rendering::draw()
├─ glfwPollEvents() // Poll input/window events
│
├─ TabletInput::instance().pollEvents() // Tablet pressure support
│
├─ main_.changeFullscreen_() // Handle fullscreen requests
│
├─ OUTPUT WINDOWS RENDERING
│ └─ for (output windows):
│ └─ output.draw(Mixer::session()->frame()) // Render to output windows
│
├─ MAIN WINDOW CONTEXT
│ └─ main_.makeCurrent() // Switch to main window GL context
│
├─ EXECUTE DRAW CALLBACKS (key stage)
│ └─ for (draw_callbacks_):
│ 1. prepare()
│ └─ Control::manager().update()
│ └─ Mixer::manager().update()
│ └─ UserInterface::manager().NewFrame()
│
│ 2. drawScene()
│ └─ Mixer::manager().draw()
│
│ 3. renderGUI()
│ └─ UserInterface::manager().Render()
│
├─ SCREENSHOT CAPTURE
│ └─ if (request_screenshot_):
│ └─ screenshot_.captureGL()
│
└─ glfwSwapBuffers() // Display frame
└─ FPS LIMITER (15600 microseconds) // Soft-cap at ~62 FPS
static GTimer for frame timing
Framerate Control
- Location: Lines 666-672 in RenderingManager.cpp
- Method: GLib GTimer (g_timer_*)
- Target: ~62 FPS (15600 microseconds between frames)
- Implementation: Busy-sleep if frame completes too quickly
3. Scene Update and Rendering (Mixer)
Location
- Methods:
Mixer::update()(lines 86-276),Mixer::draw()(lines 278-283)
Update Phase (prepare() callback)
Executed in Mixer::update():
Mixer::update(dt_)
├─ Session::update(dt_) // Update all sources
│ └─ for (each source):
│ └─ Source::update(dt) // Update individual source
│
├─ FrameGrabbing::grabFrame() // Grab frames for recording
│
├─ Process failed sources
│ ├─ FAIL_RETRY: Attempt to recreate
│ ├─ FAIL_CRITICAL: Detach from mixer
│ └─ FAIL_FATAL: Delete source
│
└─ Update views for deep refresh
└─ View::update(dt) for dirty views
Framerate tracking:
dt_ = elapsed time since last frame (ms)
dt__ = stabilized dt (EMA: 5% new + 95% old)
fps = 1000 / dt__
Draw Phase (drawScene() callback)
Executed in Mixer::draw():
Mixer::draw()
└─ current_view_->draw() // Render current view
The actual rendering is delegated to the View's draw method.
4. Source Rendering Pipeline
Source Architecture
Base Class: vimix/src/Source/Source.h
Source hierarchy:
Source (base class)
├─ MediaSource // Video/image files via GStreamer
├─ DeviceSource // Camera/webcam input
├─ StreamSource // Generic GStreamer pipeline
├─ PatternSource // Generated patterns
├─ TextSource // Rendered text
├─ ShaderSource // GLSL shader output
├─ RenderSource // Renders nested session
├─ CloneSource // References another source
├─ SessionSource // Embedded session file
├─ ScreenCaptureSource // Screen capture
├─ MultiFileSource // Image sequence
├─ NetworkSource // Network stream
└─ SrtReceiverSource // SRT protocol input
Source Update/Render Cycle
Location: Source.h (lines 162-169)
// Each source has:
virtual void update(float dt); // Called once per frame
virtual void render(); // Render to frame buffer
virtual FrameBuffer *frame() const; // Get output texture
MediaSource (Primary Video Source) Flow
File: vimix/src/Source/MediaSource.cpp
MediaSource::update(dt)
└─ mediaplayer_->update() // Update GStreamer pipeline
MediaSource::init()
└─ Create FrameBuffer matching media resolution
└─ Link texture from MediaPlayer to source framebuffer
MediaSource::render()
└─ Render media texture to source's frame buffer
5. GStreamer Integration
MediaPlayer Class
Location: /vimix/src/MediaPlayer.h (Lines 59-434)
Key components:
struct MediaInfo {
guint width, height, bitrate;
guint framerate_n, framerate_d;
std::string codec_name;
bool isimage, interlaced, seekable;
GstClockTime dt, end; // Frame duration and end time
};
class MediaPlayer {
GstElement *pipeline_; // GStreamer pipeline
GstBus *bus_; // Pipeline message bus
GstAppSink *appsink_; // Video output sink
Frame frame_[N_VFRAME]; // Frame buffer stack
uint textureid_; // OpenGL texture
struct TimeCounter {
GTimer *timer; // FPS measurement
gdouble fps;
} timecount_;
};
Pipeline Structure
File: /vimix/src/MediaPlayer.cpp
GStreamer Pipeline:
[uridecodebin]
├─ Video path:
│ ├─ [videoscale]
│ ├─ [videoconvert]
│ ├─ [videofilter (optional)]
│ └─ [glsinkbin] (GL rendering) or [appsink] (CPU)
│
└─ Audio path:
└─ [audioconvert]
└─ [appsink]
GStreamer OpenGL Integration
Location: RenderingManager.cpp (lines 42-52, 172-221)
// GL context sharing with GStreamer
#ifdef USE_GST_OPENGL_SYNC_HANDLER
static GstGLContext *global_gl_context = NULL;
static GstGLDisplay *global_display = NULL;
// Linking pipeline to rendering instance
void Rendering::LinkPipeline(GstPipeline *pipeline)
{
GstBus* m_bus = gst_pipeline_get_bus(pipeline);
gst_bus_set_sync_handler(m_bus, bus_sync_handler, pipeline, NULL);
}
GPU Hardware Decoding
Location: RenderingManager.cpp (lines 500-515)
// Enable GPU decoding if available
std::list<std::string> gpuplugins =
GstToolkit::enable_gpu_decoding_plugins(
Settings::application.render.gpu_decoding);
// Supported plugins logged
if (Settings::application.render.gpu_decoding)
Log::Info("Hardware decoding enabled.");
Frame Handling
File: MediaPlayer.cpp
// Frame stack with mutex protection
struct Frame {
GstBuffer *buffer;
FrameStatus status; // SAMPLE, PREROLL, EOS, INVALID
bool is_new;
GstClockTime position;
std::mutex access;
};
Frame frame_[N_VFRAME]; // N_VFRAME = 5 frames
// Callback when new sample arrives
static GstFlowReturn callback_new_sample(GstAppSink *sink, gpointer data)
{
// Get sample, map to GPU texture
// Fill texture via PBO (Pixel Buffer Object) upload
}
6. Session Composition and Output
Session Class
Location: /vimix/src/Session.h
class Session {
SourceList sources_; // All sources in session
RenderView render_; // Composition renderer
// Main output framebuffer
FrameBuffer *frame() const {
return render_.frame();
}
void update(float dt); // Update all sources
};
RenderView (Composition)
Location: /vimix/src/View/RenderView.h
class RenderView : public View {
FrameBuffer *frame_buffer_; // Output framebuffer
Surface *fading_overlay_; // Fading effect overlay
void draw() override; // Composite all sources
};
Composition Flow
RenderView::draw() (called from current View)
RenderView::draw()
├─ frame_buffer_->begin() // Bind FBO
│
├─ DrawVisitor traversal
│ └─ Visit and draw all source nodes
│ └─ Each source renders its texture to composition
│
├─ Apply blending/effects
│ └─ ImageProcessingShader
│ └─ BlendingShader
│
├─ Apply fading overlay
│ └─ fading_overlay_->draw()
│
└─ frame_buffer_->end() // Unbind FBO
Result: frame() returns the composed image
7. GUI Rendering (ImGui)
Location
- Init:
UserInterface::Init()(lines 117-150) - Frame Start:
UserInterface::NewFrame()(lines 866-871) - Render:
UserInterface::Render()(lines 1046-1047)
ImGui Integration
// Initialization
ImGui::CreateContext();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(VIMIX_GLSL_VERSION);
// Per-frame sequence (in renderGUI callback)
void UserInterface::NewFrame()
{
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
void UserInterface::Render()
{
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
UI Rendering Stages
renderGUI() callback
├─ UserInterface::NewFrame()
│ └─ Poll input, start ImGui frame
│
├─ Build UI (in UserInterface::Render())
│ ├─ Main menu
│ ├─ View navigator
│ ├─ Source panels
│ ├─ Output windows
│ └─ Various controls
│
└─ ImGui::Render() + RenderDrawData()
└─ Draw ImGui command buffer to OpenGL
8. Existing Timing and Performance Measurement
GLib GTimer Usage
Location: Multiple files
// RenderingManager.cpp (lines 667-671)
static GTimer *timer = g_timer_new();
double elapsed = g_timer_elapsed(timer, NULL) * 1000000.0;
if (elapsed < 15600.0 && elapsed > 0.0)
g_usleep(15600 - (gulong)elapsed);
g_timer_start(timer);
// Mixer.cpp (lines 201-206)
static GTimer *timer = g_timer_new();
dt_ = g_timer_elapsed(timer, NULL) * 1000.0; // Frame delta time in ms
dt__ = 0.05f * dt_ + 0.95f * dt__; // Smoothed delta time (EMA)
g_timer_start(timer);
FPS Calculation
Location: Mixer.h (line 87)
inline int fps() const { return int(roundf(1000.f/dt__)); }
Also in MediaPlayer for individual sources:
// MediaPlayer.h (lines 369-378)
struct TimeCounter {
GTimer *timer;
gdouble fps;
public:
TimeCounter();
~TimeCounter();
void tic();
inline gdouble frameRate() const { return fps; }
};
UI Metrics Display
Location: UserInterfaceManager.cpp
#define PLOT_ARRAY_SIZE 180 // Rolling window of 180 frames
// RenderMetrics() method displays:
- Current FPS
- Frame time plot
- GPU memory usage
- Render statistics
Memory Monitoring
Location: RenderingManager.cpp (lines 786-850)
static glm::ivec2 getGPUMemoryInformation()
{
// NVIDIA: GL_NVX_gpu_memory_info extension
// ATI: GL_ATI_meminfo extension
return { current_available_MB, total_available_MB }
}
static bool shouldHaveEnoughMemory(glm::vec3 resolution, int flags)
{
// Check if enough VRAM before creating framebuffer
}
Source Update Timing
Location: Mixer.cpp (lines 200-206)
// Detailed frametime tracking
dt_ = g_timer_elapsed(timer, NULL) * 1000.0; // Instantaneous
dt__ = 0.05f * dt_ + 0.95f * dt__; // Exponential Moving Average
// Accessible via
Mixer::manager().dt() // Raw delta
Mixer::manager().dt__() // Smoothed delta
Mixer::manager().fps() // Calculated FPS
9. Callback Architecture
Rendering Callbacks
Location: RenderingManager.h (lines 129-131)
typedef void (* RenderingCallback)(void);
void pushBackDrawCallback(RenderingCallback function);
// Callbacks are executed in sequence each frame
std::list<RenderingCallback> draw_callbacks_;
Current Registered Callbacks
Location: main.cpp (lines 241-243)
1. prepare() // Update phase
2. drawScene() // Render scene phase
3. renderGUI() // Render UI phase
Each callback is invoked in order during Rendering::draw() (lines 651-655).
10. Rendering Call Flow Diagram
┌─ Main Loop (main.cpp:261)
│
└─ Rendering::draw() [RenderingManager.cpp:608]
│
├─ Event handling
│ ├─ glfwPollEvents()
│ ├─ TabletInput::pollEvents()
│ └─ Handle fullscreen/window state changes
│
├─ Output Windows Rendering
│ └─ For each output window:
│ └─ RenderingWindow::draw(frame)
│ └─ Render session()->frame() to monitor
│
├─ Main Window Context Activation
│ └─ main_.makeCurrent()
│
├─ Execute Draw Callbacks (in sequence)
│ │
│ ├─ [1] prepare() [main.cpp:46]
│ │ └─ Mixer::update(dt) [Mixer.cpp:86]
│ │ └─ Session::update(dt) [Session.h:96]
│ │ ├─ For each source:
│ │ │ ├─ Source::update(dt) [Source.h:162]
│ │ │ │ ├─ MediaSource::update(dt) [MediaSource.cpp:181]
│ │ │ │ │ └─ MediaPlayer::update() [MediaPlayer.cpp]
│ │ │ │ │ └─ Poll GStreamer pipeline
│ │ │ │ │ └─ Update texture from latest frame
│ │ │ │ │ └─ Measure FPS via TimeCounter
│ │ │ │ │
│ │ │ │ └─ SourceCallback execution
│ │ │ │
│ │ │ ├─ RenderView internal update
│ │ │ │ └─ Update mixing nodes positions/transforms
│ │ │ │
│ │ │ └─ View::update(dt) [View.h:38]
│ │ │
│ │ ├─ Frame delta time calculation
│ │ │ └─ GTimer: dt_, dt__ (smoothed), fps
│ │ │
│ │ └─ Handle failed sources
│ │
│ │ └─ UserInterface::NewFrame() [UserInterfaceManager.cpp:866]
│ │ ├─ ImGui_ImplOpenGL3_NewFrame()
│ │ ├─ ImGui_ImplGlfw_NewFrame()
│ │ └─ ImGui::NewFrame()
│ │
│ ├─ [2] drawScene() [main.cpp:53]
│ │ └─ Mixer::draw() [Mixer.cpp:278]
│ │ └─ current_view_->draw() [View.h:39]
│ │ │
│ │ └─ RenderView::draw() [RenderView.cpp]
│ │ ├─ frame_buffer_->begin() [FrameBuffer.cpp:58]
│ │ │ └─ Bind FBO, set viewport, clear
│ │ │
│ │ ├─ DrawVisitor traversal
│ │ │ ├─ Visit scene root
│ │ │ └─ Draw all source nodes
│ │ │ └─ For each source:
│ │ │ ├─ Source::render() [Source.h:169]
│ │ │ │ └─ Render texture to composition FB
│ │ │ │
│ │ │ └─ Apply blending shader
│ │ │ ├─ ImageProcessingShader
│ │ │ └─ BlendingShader effects
│ │ │
│ │ ├─ Apply fading overlay
│ │ │ └─ fading_overlay_->draw()
│ │ │
│ │ └─ frame_buffer_->end()
│ │ └─ Unbind FBO
│ │
│ │ Result: Session frame buffer updated
│ │
│ └─ [3] renderGUI() [main.cpp:58]
│ └─ UserInterface::Render() [UserInterfaceManager.cpp:1046]
│ ├─ Build ImGui window hierarchy
│ │ ├─ Menu bar
│ │ ├─ View navigator
│ │ ├─ Source control panels
│ │ ├─ Inspector windows
│ │ └─ Other UI elements
│ │
│ └─ ImGui::Render() + RenderDrawData()
│ └─ Execute OpenGL draw calls for UI
│
├─ Screenshot Capture (if requested)
│ └─ screenshot_.captureGL()
│
├─ Buffer Swap
│ └─ glfwSwapBuffers()
│
└─ Framerate Limiting
└─ GTimer: sleep until 15600 microseconds elapsed (~62 FPS)
11. Key Files and Their Roles
| Component | File | Key Responsibility |
|---|---|---|
| Main Loop | main.cpp |
Application entry, callback registration |
| Rendering Manager | RenderingManager.cpp/.h |
Window management, GL context, callback execution |
| Mixer | Mixer.cpp/.h |
Scene management, source updating, FPS calculation |
| Session | Session.cpp/.h |
Source composition, session state |
| RenderView | View/RenderView.cpp/.h |
Final composition/rendering to FBO |
| Source Base | Source/Source.cpp/.h |
Source interface, framebuffer attachment |
| MediaSource | Source/MediaSource.cpp |
Video/image file handling |
| MediaPlayer | MediaPlayer.cpp/.h |
GStreamer pipeline control, texture management |
| Stream | Stream.cpp/.h |
Generic GStreamer stream handling |
| FrameBuffer | FrameBuffer.cpp/.h |
OpenGL FBO creation and management |
| UI Manager | UserInterfaceManager.cpp/.h |
ImGui initialization and rendering |
| DrawVisitor | Visitor/DrawVisitor.cpp/.h |
Scene graph traversal for rendering |
12. Profiling Opportunities
Existing Instrumentation Points
-
GLib GTimer - Used for frame timing
- Location:
RenderingManager.cpp,Mixer.cpp - Already measures: Frame delta time, FPS, framerate limiting
- Location:
-
MediaPlayer TimeCounter - FPS per source
- Location:
MediaPlayer.h(lines 370-378) - Already measures: Update framerate per media player
- Location:
-
GPU Memory - VRAM usage queries
- Location:
RenderingManager.cpp(getGPUMemoryInformation) - Uses: NVIDIA/ATI GPU memory extensions
- Location:
Recommended Profiling Points
-
Per-stage timing (prepare, draw, GUI)
- Insert GTimer at start/end of each callback
- Track: Update time, render time, UI time
-
Source-level profiling
- Time each
Source::update()andSource::render() - Track: GStreamer decoding, texture upload, GPU rendering
- Time each
-
View composition timing
- Time RenderView::draw() phases
- Track: DrawVisitor traversal, shader execution
-
GStreamer pipeline monitoring
- Query pipeline state and buffer queue depth
- Track: Frame drops, decoding lag
-
ImGui rendering stats
- Use ImGui metrics window
- Track: Draw call count, vertex count, UI layout time
13. Architecture Summary
Vimix's rendering is organized as:
-
Event-Driven Main Loop
- Framerate-limited (~62 FPS)
- Callback-based architecture
-
Three-Stage Pipeline Per Frame
- Update phase: Process all sources and state
- Render phase: Compose sources to session framebuffer
- UI phase: Render ImGui overlay
-
GStreamer-Based Source Handling
- Asynchronous decoding via GStreamer pipelines
- GPU texture management (GL interop)
- Hardware decoding support
-
Layered Rendering Model
- Individual source → source framebuffer
- All sources → session framebuffer (composition)
- Session → output windows (display)
- UI overlay on top (ImGui)
-
Visitor Pattern for Scene Traversal
- DrawVisitor renders scene graph
- Depth-sorted source ordering
- Flexible for different rendering modes
This architecture enables efficient real-time video mixing with flexible source types and advanced effects processing.