# Real-Time Watermark Rendering Using GLRecordingPipeline in FadCam

> Discover real-time watermark rendering with FadCam's GLRecordingPipeline. This technique overlays dynamic text onto camera frames efficiently using a background thread. Learn how.

- Repository: [Faded/FadCam](https://github.com/anonfaded/FadCam)
- Tags: how-to-guide
- Published: 2026-05-13

---

**FadCam's `GLRecordingPipeline` enables real-time watermark rendering by delegating OpenGL texture updates to a dedicated background thread that overlays dynamic text onto camera frames before they reach the video encoder.**

The FadCam Android application provides a production-ready implementation of real-time watermark rendering using OpenGL ES. By combining the `GLRecordingPipeline` class with the `GLWatermarkRenderer`, developers can overlay live data—such as GPS coordinates, timestamps, or speed—onto every video frame without interrupting the encoding process or dropping frames.

## Architecture of the GLRecordingPipeline

The rendering system splits responsibilities between two primary classes to maintain clean separation between lifecycle management and graphics operations.

### Core Components: Pipeline and Renderer

**`GLRecordingPipeline`** (defined in [`app/src/main/java/com/fadcam/opengl/GLRecordingPipeline.java`](https://github.com/anonfaded/FadCam/blob/main/app/src/main/java/com/fadcam/opengl/GLRecordingPipeline.java)) orchestrates the entire recording lifecycle. It manages the EGL context, creates the encoder surface, synchronizes timestamps between video and audio threads, and coordinates segment rollover. The constructor (lines **665-694**) accepts a `WatermarkInfoProvider` interface that supplies the initial watermark text and a `GLWatermarkRenderer` instance that performs the actual drawing.

**`GLWatermarkRenderer`** (defined in [`app/src/main/java/com/fadcam/opengl/GLWatermarkRenderer.java`](https://github.com/anonfaded/FadCam/blob/main/app/src/main/java/com/fadcam/opengl/GLWatermarkRenderer.java)) handles the low-level OpenGL work. It creates the OES texture that receives camera frames, executes the draw calls to render frames to the encoder surface, and manages the watermark texture updates. When the watermark text changes, the renderer regenerates the bitmap on the GL thread via `updateWatermarkTextOnGlThread()` and uploads it as a texture through `applyWatermarkAndOverlayPayload()` (lines **588-614**).

### Thread Safety and EGL Context

Camera frames arrive via `SurfaceTexture.onFrameAvailable()` and are queued on a dedicated `HandlerThread` running a `renderRunnable`. All GL operations—including watermark texture updates—execute on this thread to ensure the EGL context remains current. The pipeline provides `getSynchronizedVideoTimestamp()` to align video and audio streams using a monotonic clock, preventing drift between the overlay graphics and encoded audio.

## Initializing the Recording Pipeline

To begin real-time watermark rendering, instantiate the pipeline with your desired configuration and a `WatermarkInfoProvider` implementation.

```java
// Context from Activity or Service
Context ctx = this;

// Implement the provider to supply dynamic watermark content
WatermarkInfoProvider waterMarkProvider = new WatermarkInfoProvider() {
    @Override
    public String getWatermarkText() {
        return "Lat:" + latitude + " Lon:" + longitude + " Speed:" + speed + "km/h";
    }
};

// Configure video parameters
int videoWidth = 1280;
int videoHeight = 720;
int frameRate = 30;
String outputPath = getExternalFilesDir(null) + "/recording.mp4";

// Create the pipeline (constructor at lines 665-694)
GLRecordingPipeline pipeline = new GLRecordingPipeline(
        ctx,
        waterMarkProvider,
        videoWidth,
        videoHeight,
        frameRate,
        outputPath,
        5L * 1024 * 1024,  // 5MB max file size
        1,                 // Segment number
        null,              // Segment callback
        null,              // Preview surface (null for encoder-only)
        "portrait",        // Orientation
        0,                 // Sensor orientation
        VideoCodec.H264,   // Video codec
        null,              // Latitude
        null               // Longitude
);

```

## Implementing Real-Time Watermark Updates

Once initialized, the pipeline requires surface preparation before accepting frames.

### Preparing Surfaces and Starting Recording

Call `prepareSurfaces()` to create the EGL context, encoder input surface, and camera input surfaces. This method instantiates the internal `GLWatermarkRenderer` and configures the OpenGL viewport.

```java
// Initialize EGL and create renderer (lines 556-711)
pipeline.prepareSurfaces();

// Start audio capture, muxer, and render loop
pipeline.startRecording();

```

The `startRecording()` method also invokes `ensureWatermarkUpdaterRunning()` (lines **606-608**) to activate a low-frequency handler that polls the `WatermarkInfoProvider` for text changes.

### Updating Watermark Text Dynamically

For immediate updates—such as when a new GPS fix arrives—you can push text directly to the renderer:

```java
String updatedText = "Lat:" + lat + " Lon:" + lon + "  Speed:" + speed + "km/h";
pipeline.glRenderer.setWatermarkText(updatedText);  // Executes on GL thread

```

Alternatively, update the `WatermarkInfoProvider` implementation and let the pipeline's internal handler manage the refresh automatically. Both approaches trigger `updateWatermarkTextOnGlThread()`, ensuring the texture regeneration occurs safely within the EGL context without blocking the camera preview or encoder.

## Advanced Configuration Options

The pipeline supports additional rendering modes for preview display and multi-camera setups.

### Live Preview Integration

To render the watermarked output to a screen preview while encoding, pass a `Surface` from a `TextureView` or `SurfaceView` to the constructor:

```java
Surface previewSurface = surfaceView.getHolder().getSurface();

GLRecordingPipeline pipeline = new GLRecordingPipeline(
        ctx, waterMarkProvider, 1280, 720, 30,
        outputPath, 5L * 1024 * 1024, 1, null,
        previewSurface, "portrait", 0,
        VideoCodec.H264, null, null);

pipeline.prepareSuraces();  // Creates both encoder and preview EGL surfaces
pipeline.startRecording();  // Renders to both targets simultaneously

```

The preview rendering path uses `GLWatermarkRenderer.renderToPreview()` (lines **723-835**) with aspect-ratio-preserving viewport calculations (lines **786-845**) to maintain proper display scaling without affecting the full-resolution encoder output.

### Dual-Camera Picture-in-Picture

Enable PiP recording by passing a `DualCameraConfig` to the appropriate constructor overload (starting at line **661**). The pipeline exposes `getSecondaryCameraInputSurface()` and `swapCameras()` to manage dual-stream input, with the renderer compositing the secondary feed over the primary camera frame before applying the watermark texture.

## Resource Cleanup

Properly release resources to ensure the output MP4 file is finalized and playable:

```java
pipeline.stopRecording();  // Drains encoders, stops audio, signals EOF to muxer
pipeline.release();      // Releases EGL context, surfaces, and renderer resources

```

The `stopRecording()` method handles the final frame drain and muxer shutdown, ensuring all watermarked frames are written to disk.

## Summary

- **`GLRecordingPipeline`** manages the recording lifecycle and EGL context in [`app/src/main/java/com/fadcam/opengl/GLRecordingPipeline.java`](https://github.com/anonfaded/FadCam/blob/main/app/src/main/java/com/fadcam/opengl/GLRecordingPipeline.java), while **`GLWatermarkRenderer`** executes OpenGL draw calls in [`app/src/main/java/com/fadcam/opengl/GLWatermarkRenderer.java`](https://github.com/anonfaded/FadCam/blob/main/app/src/main/java/com/fadcam/opengl/GLWatermarkRenderer.java).
- Watermark updates occur **on the GL thread** via `setWatermarkText()` or the `WatermarkInfoProvider` interface, guaranteeing thread safety without encoder stalls.
- The system supports **simultaneous preview and encoding** using separate EGL surfaces with independent viewport calculations.
- **Dual-camera PiP** mode is available through extended constructor APIs that accept `DualCameraConfig`.
- All EGL operations include retry logic to survive transient graphics driver failures (lines **663-720**).

## Frequently Asked Questions

### How does GLRecordingPipeline ensure thread-safe watermark updates?

All watermark texture updates execute on the dedicated GL thread through `updateWatermarkTextOnGlThread()`. This method creates a bitmap from the text string and uploads it to the GPU using `applyWatermarkAndOverlayPayload()` (lines **588-614**), ensuring the EGL context is current and preventing race conditions with the frame rendering loop.

### Can I update the watermark text while recording without stopping the encoder?

Yes. The `setWatermarkText()` method can be called at any time during recording. The change applies to the next frame rendered to the encoder surface without interrupting the MediaCodec session or causing frame drops, as the texture swap occurs atomically within the existing GL rendering cycle.

### What video codecs does GLRecordingPipeline support?

The pipeline supports **H.264** and **H.265** (HEVC) through the `VideoCodec` enum defined in [`app/src/main/java/com/fadcam/VideoCodec.java`](https://github.com/anonfaded/FadCam/blob/main/app/src/main/java/com/fadcam/VideoCodec.java). Pass the desired codec to the `GLRecordingPipeline` constructor to configure the underlying `MediaCodec` encoder format.

### How do I handle orientation changes during recording?

The constructor accepts `orientation` and `sensorOrientation` parameters that configure the renderer's transformation matrix. For dynamic orientation changes while recording, create a new pipeline instance with updated parameters and restart the recording session, as the encoder surface format is fixed at initialization time.