How Bilibili’s Chronos Engine Powers Real‑Time Support Bullet‑Screen Word Clouds on Mobile
This article explains how Bilibili’s Chronos cross‑platform engine enables a new support‑bullet‑screen feature that turns user comments into animated word‑cloud overlays on video, detailing the client‑side layout algorithm, integral‑image optimization, spiral point generation, and worker‑thread rendering to achieve sub‑second performance on mobile devices.
Chronos Rendering Engine
Chronos is Bilibili’s cross‑platform rendering engine for Android and iOS. It is written in TypeScript and offers high‑level APIs for drawing text, graphics and animations efficiently.
Support‑Bullet‑Screen (Support Danmaku)
The feature maps user comments onto a background image, forming a word‑cloud that fills the image while animating, thereby increasing interaction during video playback.
progress – time offset when the bullet appears
duration – how long the bullet stays on screen
position – screen coordinates for the bullet group
picture – background image used as the word‑cloud canvas
dms – array of comment data to be placed inside the cloud
Layout Strategy
Two approaches were evaluated:
Client‑side real‑time layout calculation before each display.
Server‑side pre‑computed layout sent to the client.
Client‑side layout was chosen because server‑side results cannot guarantee consistent font rendering across devices and would lose per‑bullet animation.
Word‑Cloud Fill Implementation
Fillable regions are identified by reading the alpha channel of the background image. Pixels with zero alpha are marked as occupied.
image.toPixelData().forEach((pixel, index) => {
if (pixel.alpha <= 0) {
this._filledSet.add(index);
}
});To test overlap quickly, an integral image (summed‑area table) is built. The sum of a rectangular region can be obtained with four look‑ups:
const totalPixelCount = this._integralPixelArray[this.positionToIndex(maxCol, maxRow)];
const topPixelCount = minRow <= 0 ? 0 : this._integralPixelArray[this.positionToIndex(maxCol, minRow - 1)];
const leftPixelCount = minCol <= 0 ? 0 : this._integralPixelArray[this.positionToIndex(minCol - 1, maxRow)];
const leftTopPixelCount = (minRow <= 0 || minCol <= 0) ? 0 : this._integralPixelArray[this.positionToIndex(minCol - 1, minRow - 1)];
return totalPixelCount - topPixelCount - leftPixelCount + leftTopPixelCount <= 0;Integral Image Construction
for (let col = 0; col < this._width; col++) {
for (let row = 0; row < this._height; row++) {
const index = this.positionToIndex(col, row);
this._filledArray[index] = this._colorArray[index][3] > 0 ? 0 : 1;
this._integralPixelArray[index] = this._filledArray[index];
if (col > 0) {
this._integralPixelArray[index] += this._integralPixelArray[this.positionToIndex(col - 1, row)];
}
if (row > 0) {
this._integralPixelArray[index] += this._integralPixelArray[this.positionToIndex(col, row - 1)];
}
if (col > 0 && row > 0) {
this._integralPixelArray[index] -= this._integralPixelArray[this.positionToIndex(col - 1, row - 1)];
}
}
}Greedy Placement with Rectangular Spiral
A rectangular spiral generator produces candidate points starting from the image centre. The algorithm moves each comment rectangle along the spiral until the integral‑image check reports no overlap.
function createRectSpiralGenerator(width, height, dt) {
let x = 0, y = 0;
return function(t, offset = 0) {
t = t + offset;
const sign = t < 0 ? -1 : 1;
const num = Math.ceil(Math.sqrt(1 + 4 * t * sign) - sign) & 3;
if (num === 0) x += dt;
else if (num === 1) y += dt;
else if (num === 2) x -= dt;
else y -= dt;
return { col: x + Math.floor(width / 2), row: y + Math.floor(height / 2) };
};
}Asynchronous Layout and Rendering
Layout and fill calculations run on a worker thread. The main thread receives incremental results and updates the screen in small batches, avoiding long VSYNC stalls.
App.getTaskWorkerBinder()?.sendMsg(Task.WORD_CLOUD_UPDATE, { timeout, count }, (response, expired) => {
const textureKey = response.key ?? null;
if (expired) {
cron.TransferCenter.instance.popObjectForKey(textureKey)?.release();
return;
}
const count = response.count ?? 0;
const completed = response.completed ?? false;
const success = response.success ?? false;
if (completed) this.reportShowEventIfNeeded(success, count);
else this.triggerWordCloudEngineUpdate();
const texture = cron.TransferCenter.instance.popObjectForKey(textureKey);
this._displayer.addOneGroup(texture, completed, success);
texture.release();
});Rendering Optimizations
Layout results are batched into multiple textures. Blending modes are used to compose these textures, reducing draw calls and keeping the animation smooth.
Performance
On a 600 × 600 background image the complete layout and fill process finishes in under 1.5 seconds on typical mobile hardware. By splitting the work into incremental worker messages, the perceived latency drops to a few frames, preventing noticeable frame drops.
Conclusion
The client‑side implementation delivers a responsive support‑bullet‑screen with real‑time word‑cloud layout. Integral images, rectangular‑spiral candidate generation, and asynchronous processing together satisfy the strict performance constraints of mobile video playback while preserving per‑bullet animation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Bilibili Tech
Provides introductions and tutorials on Bilibili-related technologies.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
