At this point, you officially “own” the GPU as a client.
Phase 4 — One compute shader, zero text
Goal
Prove compute shaders work in your environment.
What you add
One SSBO
One compute shader that:
writes a moving point
Render that point
Success criteria
GPU updates positions
CPU only supplies dt
No CPU-side vertex updates
This validates your “everything on GPU” rule.
Phase 5 — Text mask upload (CPU → GPU, ONCE)
Goal
Upload a bitmap and sample it in a shader.
What you add
FreeType + HarfBuzz
Rasterize one word (“LOCKED”)
Upload as R8 texture
Visualize mask as particles or points
Success criteria
Bitmap never changes per frame
No CPU readback
No per-frame uploads
Phase 6 — GPU particle spawn pass
Goal
Spawn particles from the text mask on the GPU.
What you add
Compute shader:
Reads text texture
Allocates particles
Fixed max particle count
Success criteria
Particle count matches glyph density
No CPU involvement
Deterministic layout
Phase 7 — Vector field + failure animation
Goal
Wrong password triggers chaos.
What you add
Vector field function
State uniform (locked / failed)
Forces applied only when failed
Success criteria
Visual response is immediate
Reset does not require regeneration
Phase 8 — Input + PAM
Goal
Make it real.
What you add
wl_keyboard
xkbcommon
PAM worker thread
Unlock signal to Hyprland
Render loop never blocks.
Phase 9 — Hardening
You handle:
Output hotplug
Suspend/resume
VT switch
Compositor restart
GL context loss
This is what separates demos from lockscreens.
# Phase 0 — Define the “golden path”
Before writing real code, lock this in mentally:
> **I want one fullscreen layer-shell surface per output, with a working EGL context and a frame-callback-driven render loop.**
Nothing else matters until this is rock-solid.
---
# Phase 1 — Minimal fullscreen layer-shell client (NO GL)
### Goal
Show a solid-color fullscreen surface on **all outputs** and keep it visible forever.
### What you implement
* `wl_display` connect
* Registry bind:
* `wl_compositor`
* `wl_output`
* `zwlr_layer_shell_v1`
* Output tracking
* One `wl_surface` + `zwlr_layer_surface_v1` **per output**
### Hard requirements
* Layer: `overlay`
* Anchors: all sides
* Exclusive zone: `-1`
* Keyboard interactivity: `exclusive`
If this fails, your lock is not secure.
### Success criteria
* Covers all monitors
* Cannot be obscured
* Survives window creation/destruction
* Survives workspace changes
👉 **Stop here until this works perfectly.**
---
# Phase 2 — Frame callbacks (still NO GL)
### Goal
Prove you can drive animation **without busy-looping**.
### What you add
* `wl_surface_frame`
* A timer or monotonic clock
* Redraw on frame callback
For now:
* Alternate background color
* Or move a rectangle in software (wl_shm)
### Success criteria
* CPU usage near zero when idle
* Smooth updates
* No tearing or flicker
If you can’t get frame callbacks right, **GL will stutter later**.
---
# Phase 3 — EGL + OpenGL (still NO particles)
### Goal
Clear the screen using OpenGL on every frame.
### What you add
* `wayland-egl`
* `EGLDisplay`, `EGLContext`, `EGLSurface`
* OpenGL ES context (3.1+)
Render loop:
```text
frame callback →
eglMakeCurrent →
glClear →
eglSwapBuffers →
request next frame
```
### Success criteria
* No black frames
* No crashes on resize
* Works on all outputs
* Context survives VT switch
At this point, you officially “own” the GPU **as a client**.
---
# Phase 4 — One compute shader, zero text
### Goal
Prove compute shaders work in your environment.
### What you add
* One SSBO
* One compute shader that:
* writes a moving point
* Render that point
### Success criteria
* GPU updates positions
* CPU only supplies `dt`
* No CPU-side vertex updates
This validates your “everything on GPU” rule.
---
# Phase 5 — Text mask upload (CPU → GPU, ONCE)
### Goal
Upload a bitmap and sample it in a shader.
### What you add
* FreeType + HarfBuzz
* Rasterize one word (“LOCKED”)
* Upload as `R8` texture
* Visualize mask as particles or points
### Success criteria
* Bitmap never changes per frame
* No CPU readback
* No per-frame uploads
---
# Phase 6 — GPU particle spawn pass
### Goal
Spawn particles from the text mask **on the GPU**.
### What you add
* Compute shader:
* Reads text texture
* Allocates particles
* Fixed max particle count
### Success criteria
* Particle count matches glyph density
* No CPU involvement
* Deterministic layout
---
# Phase 7 — Vector field + failure animation
### Goal
Wrong password triggers chaos.
### What you add
* Vector field function
* State uniform (`locked / failed`)
* Forces applied only when failed
### Success criteria
* Visual response is immediate
* Reset does not require regeneration
---
# Phase 8 — Input + PAM
### Goal
Make it real.
### What you add
* `wl_keyboard`
* `xkbcommon`
* PAM worker thread
* Unlock signal to Hyprland
Render loop **never blocks**.
---
# Phase 9 — Hardening
You handle:
* Output hotplug
* Suspend/resume
* VT switch
* Compositor restart
* GL context loss
This is what separates demos from lockscreens.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 0 — Define the “golden path”
Before writing real code, lock this in mentally:
Nothing else matters until this is rock-solid.
Phase 1 — Minimal fullscreen layer-shell client (NO GL)
Goal
Show a solid-color fullscreen surface on all outputs and keep it visible forever.
What you implement
wl_displayconnectRegistry bind:
wl_compositorwl_outputzwlr_layer_shell_v1Output tracking
One
wl_surface+zwlr_layer_surface_v1per outputHard requirements
overlay-1exclusiveIf this fails, your lock is not secure.
Success criteria
👉 Stop here until this works perfectly.
Phase 2 — Frame callbacks (still NO GL)
Goal
Prove you can drive animation without busy-looping.
What you add
wl_surface_frameFor now:
Success criteria
If you can’t get frame callbacks right, GL will stutter later.
Phase 3 — EGL + OpenGL (still NO particles)
Goal
Clear the screen using OpenGL on every frame.
What you add
wayland-eglEGLDisplay,EGLContext,EGLSurfaceRender loop:
Success criteria
At this point, you officially “own” the GPU as a client.
Phase 4 — One compute shader, zero text
Goal
Prove compute shaders work in your environment.
What you add
One SSBO
One compute shader that:
Render that point
Success criteria
dtThis validates your “everything on GPU” rule.
Phase 5 — Text mask upload (CPU → GPU, ONCE)
Goal
Upload a bitmap and sample it in a shader.
What you add
R8textureSuccess criteria
Phase 6 — GPU particle spawn pass
Goal
Spawn particles from the text mask on the GPU.
What you add
Compute shader:
Fixed max particle count
Success criteria
Phase 7 — Vector field + failure animation
Goal
Wrong password triggers chaos.
What you add
locked / failed)Success criteria
Phase 8 — Input + PAM
Goal
Make it real.
What you add
wl_keyboardxkbcommonRender loop never blocks.
Phase 9 — Hardening
You handle:
This is what separates demos from lockscreens.