Custom Output and Rendering
This article explains how to implement custom output and rendering functionality by inheriting from the Sink base class. GPUPixel provides two typical implementation demo: SinkRender and SinkRawData, which we will use to illustrate the implementation process.
Sink Base Class
The Sink class is the base class for all output targets, defining the following key functionalities:
- Managing input framebuffers
- Handling rotation modes
- Providing update mechanism
Main interfaces:
class Sink {
public:
// Constructor, specify input number
Sink(int inputNumber = 1);
// Set input framebuffer
virtual void SetInputFramebuffer(std::shared_ptr<GPUPixelFramebuffer> framebuffer,
RotationMode rotation_mode = NoRotation,
int texIdx = 0);
// Update processing
virtual void Render(){};
};Implementing Custom Sink
1. Basic Steps
- Inherit from the
Sinkclass - Implement constructor with necessary initialization
- Override
SetInputFramebuffermethod (optional) - Override
updatemethod to implement specific rendering logic
2. Rendering to Screen - SinkRender
SinkRender implements functionality to render images to the screen. Main features:
- Supports multiple fill modes (stretch, preserve aspect ratio, etc.)
- Supports mirror display
- Handles different rotation modes
Key implementation:
class SinkRender : public Sink {
public:
// Initialize shader program and attribute locations
void Init() {
}
// Implement update method for actual rendering
void Render() override {
// do render
}
};3. Raw Data Output - SinkRawData
SinkRawData implements functionality to output rendering results as raw data. Main features:
- Supports RGBA and I420 format output
- Uses PBO (Pixel Buffer Object) for optimized reading performance
- Supports asynchronous callbacks
Key implementation:
class SinkRawData : public Sink {
public:
// Set callback functions
void setI420Callbck(RawOutputCallback cb);
void setPixelsCallbck(RawOutputCallback cb);
// Implement update method
void Render() override {
// Check input size changes
if (_width != width || _height != height) {
initPBO(width, height);
initFrameBuffer(width, height);
initOutputBuffer(width, height);
}
// Render to output
renderToOutput();
// Read pixel data using PBO
readPixelsWithPBO(_width, _height);
// Output data through callback
if (i420_callback_) {
i420_callback_(_yuvFrameBuffer, _width, _height, _frame_ts);
}
}
};Implementation Guidelines
Initialization
- Initialize basic member variables in constructor
- Create and configure shader programs
- Initialize necessary OpenGL resources
Resource Management
- Properly release OpenGL resources in destructor
- Use smart pointers for memory management
- Consider thread safety
Performance Optimization
- Use PBO for asynchronous pixel transfer
- Avoid frequent memory allocation and deallocation
- Cache frequently used data and computation results
Error Handling
- Check OpenGL errors
- Validate input parameters
- Provide appropriate error feedback
Example Code
Here's a minimal custom Sink implementation example:
class MyCustomTarget : public Sink {
public:
MyCustomTarget() : Sink(1) {
// Initialize shaders and other resources
initShaders();
}
~MyCustomTarget() {
// Cleanup resources
cleanup();
}
void Render() override {
if (!IsReady()) return;
// Setup render state
setupRenderState();
// Execute custom rendering logic
Render();
// Process output
processOutput();
}
private:
// Custom implementation details
};Summary
Implementing custom output and rendering by inheriting from the Sink class involves the following process:
- Create custom class inheriting from
Sink - Implement necessary initialization and cleanup logic
- Override
updatemethod to implement rendering logic - Implement output processing as needed
By properly utilizing OpenGL features and following best practices, you can achieve efficient and stable custom output targets.