Skip to content

Commit b0a8dcf

Browse files
chiefcllthetarnav
andauthored
Making Rc3 the default now (#80)
* fix Marquee case on export * Release 3.0.0-9 * update core * Release 3.0.0-10 * Revert "fix up focus of children being added after column/row is created" This reverts commit fec6923. * Release 3.0.0-11 * only check selected if row / column doesnt have focus already * Release 3.0.0-12 * Release 3.0.0-13 * Suspense forwardFocus, Lazy to add to offset if selected * update buffer for auto which is default * Release 3.0.0-14 * Spatial navigation and refactor (#61) * Cleanup and rename col/row navigation helpers * Add spatial navigation helpers * Refactor navigation functions to improve selection logic and simplify child focus handling * Fix onGridFocus no scroll issue * Refactor onGridFocus to use optional parameter syntax * Rename getDistanceBetweenRects to getWeightedDistanceBetweenRects and update logic to calculate distances based on center and edge positions. * Enhance navigableForwardFocus to include lastSelected in onSelectedChanged callback * Use findFirstSelectableChildIdx in moveSelection * Refactor selectChild to handle null children and update onSelectedChanged callback * Refactor navigableForwardFocus to simplify focus checking and update selected child logic * Only check for skipfocus for selectable child * Use findFirstFocusableChildIdx in navigableForwardFocus * Add idxInArray to cleanup code * Switch to calculating rect dist from centers * Use new navigable helpers in row and col * Fix selection logic in moveSelection to handle skipFocus correctly * Add jsdoc to navigation helpers * Rename navigable helpers and add examples * Correct calculating child rect Depends on lightning-tv/core#66 * update to latest core * Release 3.0.0-15 * refocus children * Release 3.0.0-16 * call onSelectedChanged on focus * Release 3.0.0-17 * Fix tests (#66) * Add scrollColumn and scrollRow helpers (#68) * working with latest beta * Release 3.0.0-18 * update to latest core / renderer b15 * Release 3.0.0-19 * only setFocus if the row has focus * merge main, update core + renderer * Release 3.0.0-20 * merge core back into solid, one project * fix transitions for width and height, now w & h * Release 3.0.0-21 * copy over additional changes from main branch * add back in source to package * handle negative selected input * fix up contain & sprite maps * teaks to contain for text * Release 3.0.0-22 * add fontWeightAlias to help migrate * add migration guide * add rounded with border shader * Release 3.0.0-23 * fix calling scrollToIndex(0) * create tags at bottom corner so not visible to users * add more performant rounded shader * Release 3.0.0-24 * run lint:fix * add article, fps 1 alpha * fix packages * fix up build with latest renderer * fix prettier issues * fix up tests * feat(focus): pass nodeWithCallback to focus handlers Updates `onFocus`, `onBlur`, and `onFocusChanged` callbacks to receive the node triggering the event as the last argument. * address issue where node sizes can be negative if parentHeight-y * fix dual package hazard - types of renderer must match * Release 3.0.0-25 * fix global types * Release 3.0.0-26 * fix jsx-runtime import * Release 3.0.0-27 --------- Co-authored-by: Damian Tarnawski <[email protected]>
1 parent 008756c commit b0a8dcf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+7653
-130
lines changed

docs/_sidebar.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
- Articles
22
- [Basics](/articles/basics.md)
3+
- [Migration 2.x to 3.0](/articles/migration-2x-to-3.0.md)
4+
- [Boosting LightningJS Performance by 50%](/articles/boostingperf.md)
35
- [Real World Performance](/articles/realworldperformance.md)
46
- [Solid / Blits Framework Comparison](/articles/solidvsblits.md)
57
- Essentials

docs/articles/boostingperf.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<style>
2+
img {
3+
transition: transform 0.25s ease;
4+
}
5+
6+
img:hover {
7+
-webkit-transform: scale(1.8);
8+
transform: scale(1.8);
9+
position: relative;
10+
z-index: 5;
11+
}
12+
</style>
13+
14+
# Boosting LightningJS Performance by 50%
15+
16+
LightningJS has earned its reputation as a fast and capable framework for building TV applications. When Lightning 2.0 emerged, it set a new bar for what was possible on constrained TV hardware, offering a WebGL-based renderer that was lightweight, approachable for web developers, and performant enough to run smoothly even on most TV devices. For many teams, it became the de facto standard, and for good reason.
17+
18+
That legacy matters. LightningJS is a solid idea, built on a strong architectural foundation. Using WebGL for TV UIs is a smart choice, and the renderer exists thanks to a tremendous amount of thoughtful engineering. Credit is due to the people who carried Lightning this far; shout out to Frank and Jeffrey for building something that has enabled an entire ecosystem of TV applications.
19+
20+
Lightning 3.0 was introduced with the promise of moving that success forward. In some areas, it does. Node creation is faster, the internal structure is more modern, and the ambition to evolve the framework is clear. The potential is absolutely there. But in practice, especially on real TV hardware, Lightning 3 has not yet delivered the performance improvements many of us were hoping for.
21+
22+
This article is not a critique for its own sake. It is a call to the community and to the LightningJS team to focus together on what made Lightning successful in the first place: real, measurable performance on real devices.
23+
24+
---
25+
26+
## Building on Lightning 3 in the Real World
27+
28+
Over the last year, I built a SolidJS integration on top of the Lightning 3 renderer with a clear goal: to provide a highly performant, developer-friendly way to build production-grade TV apps.
29+
30+
From a developer experience perspective, the results were encouraging. SolidJS proved to be an excellent match: fine-grained reactivity, predictable updates, and access to a large, healthy open-source ecosystem. Teams enjoyed building with it, iteration was fast, and initial page loads were snappy.
31+
32+
However, once these applications reached real TV hardware, the experience changed. While startup performance was good, and pages loaded quickly, interaction performance was inconsistent. Animations felt heavier than expected. Basic navigation, something TV UIs must excel at, often fails to meet the smoothness bar users intuitively expect.
33+
34+
After extensive profiling and testing, one thing became clear: SolidJS was not the limiting factor. The bottleneck was the Lightning JS Renderer itself.
35+
36+
---
37+
38+
## From Assumptions to Evidence
39+
40+
Initially, it was reasonable to assume hardware limitations were the cause. TV devices are underpowered, and pushing pixels is expensive. Maybe 30 fps was the best that could be achieved? Maybe the latest beta of Lightning 3 Renderer will improve performance…
41+
42+
Instead, nearly a year after Lightning 3’s release, progress has centered around a prolonged beta with breaking changes but little measurable performance improvement. Text rendering has improved a bit. But these changes don’t help the render loop.
43+
44+
The original authors of Lightning 2 and Lightning 3 are no longer involved. With that transition, some of the hard-won institutional knowledge around renderer performance appears to have faded.
45+
46+
At the same time, there is a prevailing sense that the renderer is already well-optimized and continuing to improve. Unfortunately, extensive profiling and real-device testing do not support that conclusion. This disconnect creates a real risk: without deep familiarity with the renderer’s internals, performance issues can be underestimated, misdiagnosed, or deprioritized.
47+
48+
At this stage, meaningful gains will not come from surface-level changes, but from deliberate, informed work in the renderer’s hot paths. This is precisely the moment for renewed focus, not complacency. If LightningJS is to regain its performance leadership, rebuilding that depth of understanding must be treated as a priority.
49+
50+
---
51+
52+
## A Breaking Point: When “Good Enough” Wasn’t
53+
54+
One client project made the situation impossible to ignore. The UI was intentionally simple: a hero section at the top, followed by horizontally scrolling rows of content tiles. This is a common, well-understood TV layout, and it was aggressively optimized at the application level.
55+
56+
The feedback from the boss:
57+
58+
> “Scrolling between rows feels a bit slow on the box. Can we make it smoother?”
59+
60+
At that point, there was nothing left to optimize in the application code. The framework layer had been pushed as far as it reasonably could. The remaining bottleneck was the renderer.
61+
62+
So I started examining the renderer itself.
63+
64+
---
65+
66+
## Understanding the Renderer
67+
68+
Over the course of a month, I profiled the renderer deeply: the update loop, node lifecycle, shader switching, draw calls, garbage generation, and how state changes propagate frame to frame. This meant digging into WebGL internals and understanding exactly where time and memory were being spent.
69+
70+
WebGL is powerful, but it does not make performance problems disappear. At the end of the day, this is still JavaScript running tight loops on constrained hardware. Every allocation matters. Every unnecessary branch matters. Every redundant calculation matters. And in its current form, the renderer was simply doing too much work per frame.
71+
72+
---
73+
74+
## Fixing What Was Already There
75+
76+
One of the first discoveries was the number of subtle but impactful bugs in the renderer. These were not failures that caused crashes, but inefficiencies that quietly compounded over time. The update loop, in particular, was performing significant unnecessary work each frame, amplifying performance costs on constrained hardware.
77+
78+
I attempted to address these issues upstream by opening numerous pull requests. While some small fixes were ultimately accepted as bug corrections, discussion around performance implications was nonexistent, and to make an impactful performance difference would require a large refactor of the core.
79+
80+
This experience highlighted a broader challenge. Despite being positioned as a community-driven open-source project, there is limited transparency into current priorities, performance goals, or the renderer roadmap. Without clear signals around what is being optimized, why certain changes matter, or how contributors can meaningfully engage, collaboration is impossible.
81+
82+
This lack of clarity is also visible in long-standing feature requests with clear user impact. Right-to-left text support, for example, has been requested since January 2025, yet there is still no visible roadmap or guidance on when or how it will be addressed. This effectively excludes entire regions and languages from fully adopting LightningJS, not because of technical limitations, but because of prioritization.
83+
84+
Similarly, magic mouse support was started but never completed. After waiting without clarity on timelines or ownership, I ultimately implemented full support directly within the SolidJS integration in order to ship production applications over a year ago.
85+
86+
These are not edge cases. They illustrate a broader pattern: when priorities are opaque and follow-through is uncertain, contributors are forced to work around the platform rather than build alongside it.
87+
88+
---
89+
90+
## Garbage Collection: A Silent Performance Killer
91+
92+
Garbage collection proved to be another major factor.
93+
94+
On TV hardware, GC is unforgiving. Allocations inside high-frequency loops, temporary arrays, short-lived objects, and implicit allocations inevitably lead to unpredictable stutters when the collector runs.
95+
96+
The renderer was generating garbage continuously during the render loop. By restructuring this logic to reduce allocations as much as possible, the impact was immediate. GC pauses became rarer and shorter, and animations stopped hitching unpredictably.
97+
98+
This is not about clever tricks or clean code; it’s about respecting the realities of constrained devices. Reducing garbage pressure is critical to overall application performance.
99+
100+
---
101+
102+
## Doing Less Work Per Frame
103+
104+
A third major area of improvement was caching.
105+
106+
A surprising amount of work was being recomputed every frame, even when nothing had changed. By aggressively caching results and skipping unchanged states, the renderer simply did less work per frame. I still have a bunch of additional ideas to pursue in this area.
107+
108+
---
109+
110+
## Real-World Results
111+
112+
After fixing bugs in the update loop, eliminating garbage in hot paths, and caching work, the forked renderer consistently outperforms the upstream LightningJS renderer by approximately 50% in real-world scenarios.
113+
114+
Testing was done on a Raspberry Pi 3, a constrained device that closely mirrors the performance characteristics of low-end TV hardware. Using Lightning 3 beta v20, the upstream renderer averaged around 30 FPS in a representative TV UI. With the forked changes applied, the same application on the same device consistently exceeded 45 FPS.
115+
116+
These numbers come from real hardware, not desktop simulations, and the difference is immediately visible to end users.
117+
118+
Watch the video for yourself:
119+
[Youtube](https://www.youtube.com/watch?v=byM5IzrLKJE)
120+
121+
---
122+
123+
## Looking Forward
124+
125+
At this point, continuing to upstream these changes no longer feels productive. The performance issues have been clearly identified, fixes have been proposed, and ample time has passed for review and discussion. That process has not resulted in meaningful action.
126+
127+
At the same time, this work represents a significant investment of time, expertise, and real-world validation. It would be irresponsible to discard it or indefinitely delay its use while teams shipping production applications continue to struggle with performance constraints.
128+
129+
For that reason, I am choosing a different path. The renderer improvements will be shared directly with the clients I work with, ensuring they can deliver the level of performance their products require. In parallel, I am exploring whether there is broader interest in accessing these updates and what a fair valuation for that work might be.
130+
131+
This is not an attempt to fragment the ecosystem, but to acknowledge reality. Historically, competition has been one of the most reliable drivers of innovation in open-source and platform ecosystems. When progress stalls, alternative implementations often serve as the catalyst for renewed focus, clearer priorities, and better outcomes for everyone involved.
132+
133+
---
134+
135+
To learn more and get involved:
136+
137+
- **Official Website**: [lightningtv.dev](https://lightningtv.dev)
138+
- **GitHub Repository**: [github.com/lightning-tv/solid](https://github.com/lightning-tv/solid)
139+
- **Community Discord**: [Discord](https://discord.gg/HEqckxcB)
140+
- **Connect with Me**: [Chris Lorenzo on LinkedIn](https://www.linkedin.com/in/chris-lorenzo/)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Migration Guide: Lightning 3 v2.x to v3.0
2+
3+
This guide helps you migrate from Lightning 3 version 2.x to the new 3.0 release. The 3.0 version introduces several breaking changes to improve performance and the Solid integration has done a lot of under the hood changes to improve performance and reduce the amount of code you need to update.
4+
5+
To get the full list of changes from the renderer you can check the [changelog](https://github.com/lightning-tv/renderer/blob/main/CHANGELOG.md).
6+
7+
### Fonts
8+
9+
`fontWeight` was removed from the renderer and is now polyfilled by Solid. When you load fonts with different weights, ie `Roboto` 400 and 700, you'll need to update your fonts with different fontFamily names:
10+
11+
```typescript
12+
{
13+
type: "msdf",
14+
fontFamily: "Roboto",
15+
atlasDataUrl: basePath + "fonts/Roboto-Regular.msdf.json",
16+
atlasUrl: basePath + "fonts/Roboto-Regular.msdf.png"
17+
} as const,
18+
{
19+
type: "msdf",
20+
fontFamily: "Roboto700",
21+
atlasDataUrl: basePath + "fonts/Roboto-Bold.msdf.json",
22+
atlasUrl: basePath + "fonts/Roboto-Bold.msdf.png"
23+
} as const,
24+
```
25+
26+
Notice we removed the descriptor with the weight. Long term you can just change the fontFamily to change fonts.
27+
28+
To help with migration we have exposed a fontWeightAlias that maps common font weights to their numeric values. You can override this in your config if needed.
29+
30+
```typescript
31+
Config.fontWeightAlias = {
32+
thin: 100,
33+
light: 300,
34+
regular: '',
35+
400: '',
36+
medium: 500,
37+
bold: 700,
38+
black: 900,
39+
},
40+
```
41+
42+
Setting `fontWeight` to 700 is now a function that does the following:
43+
44+
```typescript
45+
set fontWeight(v) {
46+
this._fontWeight = v;
47+
const family = this.fontFamily || Config.fontSettings?.fontFamily;
48+
const weight =
49+
(Config.fontWeightAlias &&
50+
(Config.fontWeightAlias[v as string] as number | string)) ??
51+
v;
52+
this.fontFamily = `${family}${weight}`;
53+
}
54+
```
55+
56+
#### Enhanced Font Metrics
57+
58+
Lightning 3.0 provides better font metrics support:
59+
60+
```typescript
61+
// Recommended: Always provide font metrics for optimal rendering
62+
await stage.loadFont('canvas', {
63+
fontFamily: 'MyFont',
64+
fontUrl: '/fonts/my-font.ttf',
65+
metrics: {
66+
ascender: 800, // Font ascender in font units
67+
descender: -200, // Font descender in font units
68+
lineGap: 0, // Additional line spacing
69+
unitsPerEm: 1000, // Font units per EM
70+
},
71+
});
72+
```
73+
74+
### Width/Height Property Changes
75+
76+
The `width` and `height` properties were changed in favor of shorter `w` and `h` properties for by the Lightning team.
77+
78+
Width and height are still available but are now polyfilled by Solid. Note that in some places like Shaders you may need to use `w` and `h` instead of `width` and `height`.
79+
80+
Oh and some properties are still `maxWidth` and `maxHeight`. 🤷
81+
82+
### Texture Loaded Event Changes
83+
84+
The texture loaded event now reports dimensions using `w` and `h` properties:
85+
86+
```typescript
87+
// v2.x - Dimensions with width/height
88+
node.on('loaded', (event) => {
89+
if (event.type === 'texture') {
90+
console.log('Texture loaded:', {
91+
width: event.dimensions.width, // ⚠️ No longer available
92+
height: event.dimensions.height, // ⚠️ No longer available
93+
});
94+
}
95+
});
96+
97+
// v3.0 - Dimensions with w/h
98+
node.on('loaded', (event) => {
99+
if (event.type === 'texture') {
100+
console.log('Texture loaded:', {
101+
w: event.dimensions.w, // ✅ Available
102+
h: event.dimensions.h, // ✅ Available
103+
});
104+
}
105+
});
106+
```
107+
108+
## Updated Shaders
109+
110+
Shaders are imported when needed by application. The following example shows how to import and register shaders:
111+
112+
```typescript
113+
const { renderer, render } = createRenderer();
114+
loadFonts(fonts);
115+
// Prepare for RC3 of Renderer
116+
import {
117+
Rounded,
118+
RoundedWithBorder,
119+
RoundedWithShadow,
120+
RoundedWithBorderAndShadow,
121+
RadialGradient,
122+
LinearGradient,
123+
HolePunch,
124+
} from '@lightningjs/renderer/webgl/shaders';
125+
const shManager = renderer.stage.shManager;
126+
shManager.registerShaderType('rounded', Rounded);
127+
shManager.registerShaderType('roundedWithBorder', RoundedWithBorder);
128+
shManager.registerShaderType('roundedWithShadow', RoundedWithShadow);
129+
shManager.registerShaderType(
130+
'roundedWithBorderWithShadow',
131+
RoundedWithBorderAndShadow,
132+
);
133+
shManager.registerShaderType('radialGradient', RadialGradient);
134+
shManager.registerShaderType('linearGradient', LinearGradient);
135+
shManager.registerShaderType('holePunch', HolePunch);
136+
```
137+
138+
Be mindful some of the properties may have changed (like width/height).

docs/primitives/useFocusManager.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ const App = () => {
3939

4040
The `useFocusManager` returns a signal, `focusPath`, which is an array of elements that currently have focus. When the `activeElement` changes, the focus path is recalculated. During this process:
4141

42-
- All elements in focus will have a `focus` state added, and `onFocus(currentFocusedElm, prevFocusedElm)` event is called.
43-
- Elements losing focus will have the `focus` state removed, and `onBlur(currentFocusedElm, prevFocusedElm)` event is called.
42+
- All elements in focus will have a `focus` state added, and `onFocus(currentFocusedElm, prevFocusedElm, nodeWithCallback)` event is called.
43+
- Elements losing focus will have the `focus` state removed, and `onBlur(currentFocusedElm, prevFocusedElm, nodeWithCallback)` event is called.
4444

45-
There is also an `onFocusChanged(hasFocus, currentFocusedElm, prevFocusedElm)` callback which is useful for setting a focusSignal to use for more complicated scenarios.
45+
There is also an `onFocusChanged(hasFocus, currentFocusedElm, prevFocusedElm, nodeWithCallback)` callback which is useful for setting a focusSignal to use for more complicated scenarios.
4646

4747
```jsx
4848
const [hasFocus, setHasFocus] = createSignal(false);
@@ -71,7 +71,7 @@ You can now control input speed in two powerful ways . This feature helps preven
7171
For a quick, app-wide solution, you can set a global throttle on all key inputs directly in your configuration. This is perfect for setting a baseline input speed for your entire application.
7272

7373
```javascript
74-
import { Config } from '@lightningtv/core';
74+
import { Config } from '@lightningtv/solid';
7575

7676
// Allow one keypress every 200ms across the entire app
7777
Config.throttleInput = 200;

docs/primitives/virtual.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ Use `cursor` property on the node to get the absolute index in the list of items
5252
- Automatically re-calculates the slice on selection or data change.
5353
- Reuses Children components
5454
- Merges styles with internal layout defaults:
55-
5655
- `display: flex`, `flexWrap: wrap`, `gap: 30`
5756
- `transition: { y: 250ms ease-out }`
5857

docs/primitives/virtualgrid.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ import { VirtualGrid } from './components/VirtualGrid';
6060
- Automatically re-calculates the slice on selection or data change.
6161
- Reuses Children components
6262
- Merges styles with internal layout defaults:
63-
6463
- `display: flex`, `flexWrap: wrap`, `gap: 30`
6564
- `transition: { y: 250ms ease-out }`
6665

jsx-runtime.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-namespace */
2-
import type { NodeProps, TextProps } from '@lightningtv/core';
2+
import type { NodeProps, TextProps } from './src/core/index.js';
33

44
declare module 'solid-js' {
55
namespace JSX {

0 commit comments

Comments
 (0)