Repository navigation
Graphics Atlas Quickstart
This page explains how to create an HTML atlas and use it in HlaeObsTools.
The graphics feature renders HTML atlases in a separate producer process and exposes them to HLAE through shared D3D11 textures.
Run GfxProducerService.exe on the game PC to use atlases.
Current flow:
- HlaeObsTools connects to GfxProducerService over WebSocket.
- HlaeObsTools asks the producer to create one or more atlases.
- GfxProducerService loads the atlas HTML in CEF offscreen.
- CEF paints frames into a shared GPU texture.
- The producer returns the shared texture handle to HlaeObsTools.
- HlaeObsTools uses that handle with the HLAE graphics pipeline.
- When GSI updates arrive, HlaeObsTools forwards raw GSI JSON plus optional extras to the producer.
- The producer injects that data into every active atlas.
An atlas is a single HTML page rendered by the graphics producer into a texture.
Inside that atlas you can define one or more regions, and then use those regions in HlaeObsTools graphics instances.
Create a folder for your atlas anywhere on disk.
Example:
E:\MyAtlases\scoreboard\
index.html
style.css
app.js
Your HTML can be any normal web UI. HlaeObsTools will render it through the graphics producer.
Minimal example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Atlas</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div id="root">Hello atlas</div>
<script src="./app.js"></script>
</body>
</html>In the Graphics dock, create a new atlas and configure:
- atlas name
- width
- height
- HTML path
Important:
-
HtmlPathmay point either to your local atlasindex.htmlor to anhttp:///https://URL - width and height define the render resolution of the atlas texture and the offscreen browser viewport size
- the atlas name is what instances use to reference the atlas
An atlas can expose one or more named regions.
Each region is defined by:
- region name
-
u0,v0,u1,v1 - default width / height
Those UV values define which part of the atlas texture a graphics instance should display.
Common patterns:
- one full-screen region covering the entire atlas (e.g. "scoreboard")
- multiple named slots or panels inside one atlas (e.g. "slot1", "slot2", ...)
After the atlas exists, create graphics instances in HlaeObsTools that use:
- source type: atlas
- atlas: your atlas name
- region: the region name you defined
That is what actually places the atlas region into the HLAE graphics system.
NOTE:
You can also use source type:image
In that case, the instance uses an image fromresources/AfxHookSource2/imagesinstead of an atlas region.
Your atlas receives raw GSI updates automatically.
The easiest way is to listen for:
window.addEventListener("gsi:update", (e) => {
const { gsi, heartbeat, extras } = e.detail || {};
if (!gsi) return;
// update your DOM here
});You can also define:
window.gsiUpdate = (gsi, heartbeat, extras) => {
// update your DOM here
};gsi is the raw CS2 GSI payload.
extras currently contains additional derived data tracked by HlaeObsTools.
Current useful extras:
{
playerDamageStats: {
[steamId]: {
adr: number,
totalDamage: number
}
}
}Example:
function getAdr(extras, steamId) {
const adr = extras?.playerDamageStats?.[steamId]?.adr;
return typeof adr === "number" ? Math.round(adr) : null;
}
function getTotalDamage(extras, steamId) {
const totalDamage = extras?.playerDamageStats?.[steamId]?.totalDamage;
return typeof totalDamage === "number" ? totalDamage : null;
}Atlases can react to animation in/out triggers sent by HlaeObsTools.
Triggers are sent to:
- an atlas, by atlas name
- with a target string that is the region name
The target is what your JS switches on, which is the region name used by the graphics instance:
window.hotTrigger = (action, target) => {
if (action === "animIn" && target === "scoreboard") {
// show animation
}
if (action === "animOut" && target === "scoreboard") {
// hide animation
}
};If your atlas runs an animation and HlaeObsTools should know when it is done to automatically hide the instance, call:
window.hotNotify.triggerDone("animOut", "scoreboard");
window.addEventListener("gsi:update", (e) => {
const { gsi, extras } = e.detail || {};
if (!gsi?.allplayers) return;
// update UI from gsi / extras
});
window.hotTrigger = (action, target) => {
if (target !== "scoreboard") return;
if (action === "animIn") {
document.body.classList.add("visible");
}
if (action === "animOut") {
document.body.classList.remove("visible");
setTimeout(() => {
if (window.hotNotify?.triggerDone) {
window.hotNotify.triggerDone("animOut", "scoreboard");
}
}, 300);
}
};- Check out https://github.com/papesgit/hot/tree/main/GfxProducerService/examples for some atlas examples
- Start with one full-atlas region before splitting into multiple regions.
- Keep atlas resolution only as high as you actually need.
- Build your UI like a normal web page first, then wire in GSI.