Custom Output and Rendering
This article explains how to implement custom output and rendering functionality by inheriting from the Target
base class. GPUPixel provides two typical implementation examples: TargetView
and TargetRawDataOutput
, which we will use to illustrate the implementation process.
Target Base Class
The Target
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 Target {
public:
// Constructor, specify input number
Target(int inputNumber = 1);
// Set input framebuffer
virtual void setInputFramebuffer(std::shared_ptr<Framebuffer> framebuffer,
RotationMode rotationMode = NoRotation,
int texIdx = 0);
// Update processing
virtual void update(int64_t frameTime){};
};
Implementing Custom Target
1. Basic Steps
- Inherit from the
Target
class - Implement constructor with necessary initialization
- Override
setInputFramebuffer
method (optional) - Override
update
method to implement specific rendering logic
2. Rendering to Screen - TargetView
TargetView
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 TargetView : public Target {
public:
// Initialize shader program and attribute locations
void init() {
}
// Implement update method for actual rendering
void update(int64_t frameTime) override {
// do render
}
};
3. Raw Data Output - TargetRawDataOutput
TargetRawDataOutput
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 TargetRawDataOutput : public Target {
public:
// Set callback functions
void setI420Callbck(RawOutputCallback cb);
void setPixelsCallbck(RawOutputCallback cb);
// Implement update method
void update(int64_t frameTime) 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 Target implementation example:
class MyCustomTarget : public Target {
public:
MyCustomTarget() : Target(1) {
// Initialize shaders and other resources
initShaders();
}
~MyCustomTarget() {
// Cleanup resources
cleanup();
}
void update(int64_t frameTime) override {
if (!isPrepared()) 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 Target
class involves the following process:
- Create custom class inheriting from
Target
- Implement necessary initialization and cleanup logic
- Override
update
method 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.