GPT is reall good at generating comments and fixing spelling error
This commit is contained in:
1
command.sh
Normal file
1
command.sh
Normal file
@@ -0,0 +1 @@
|
||||
__EGL_VENDOR_LIBRARY_NAME=mesa LIBGL_ALWAYS_SOFTWARE=1 MESA_LOADER_DRIVER_OVERRIDE=llvmpipe ./build/exalock
|
||||
100
math.md
Normal file
100
math.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Silk Particle Simulation: Mathematical Formulation
|
||||
|
||||
## 1. Stream Function
|
||||
|
||||
The velocity field is derived from a scalar stream function $\psi(x, y, t)$:
|
||||
|
||||
$$
|
||||
\psi(x, y, t) = \sum_{i=1}^{4} A_i \Psi_i(x, y, t)
|
||||
$$
|
||||
|
||||
where:
|
||||
|
||||
$$
|
||||
\begin{align}
|
||||
\Psi_1 &= \sin(\omega_1 x + \phi_1(t)) \sin(\omega_1 y) \\
|
||||
\Psi_2 &= \sin(\omega_2 x) \sin(\omega_2 y + \phi_2(t)) \\
|
||||
\Psi_3 &= \sin(\omega_3 r + \phi_3(t)), \quad r = \sqrt{(x - 0.5)^2 + (y - 0.5)^2} \\
|
||||
\Psi_4 &= (x - 0.5)(y - 0.5) \sin(\omega_4 t)
|
||||
\end{align}
|
||||
$$
|
||||
|
||||
Suggested parameters: $A = (0.3, 0.25, 0.2, 0.15)$, $\omega = (2\pi, 3\pi, 4\pi, 0.5)$, $\phi_i(t) = c_i t$ with $c = (0.5, 0.3, 0.4)$
|
||||
|
||||
---
|
||||
|
||||
## 2. Velocity Field
|
||||
|
||||
Incompressible flow via curl of stream function:
|
||||
|
||||
$$
|
||||
\mathbf{v} = \nabla \times \psi = \left(\frac{\partial \psi}{\partial y}, -\frac{\partial \psi}{\partial x}\right)
|
||||
$$
|
||||
|
||||
This ensures $\nabla \cdot \mathbf{v} = 0$ (divergence-free).
|
||||
|
||||
---
|
||||
|
||||
## 3. Vorticity (for color)
|
||||
|
||||
Scalar vorticity equals the negative Laplacian of the stream function:
|
||||
|
||||
$$
|
||||
\omega = \frac{\partial v_y}{\partial x} - \frac{\partial v_x}{\partial y} = -\nabla^2 \psi = -\left(\frac{\partial^2 \psi}{\partial x^2} + \frac{\partial^2 \psi}{\partial y^2}\right)
|
||||
$$
|
||||
|
||||
---
|
||||
|
||||
## 4. Boundary Confinement
|
||||
|
||||
Soft quartic potential keeps particles on screen:
|
||||
|
||||
$$
|
||||
V_{\text{boundary}}(x, y) = k \left[(x - 0.5)^4 + (y - 0.5)^4\right]
|
||||
$$
|
||||
|
||||
Boundary force:
|
||||
|
||||
$$
|
||||
\mathbf{F}_{\text{boundary}} = -\nabla V_{\text{boundary}} = -4k\left[(x - 0.5)^3, (y - 0.5)^3\right]
|
||||
$$
|
||||
|
||||
---
|
||||
|
||||
## 5. Equations of Motion
|
||||
|
||||
Particle position $\mathbf{q} = (x, y)$ evolves as:
|
||||
|
||||
$$
|
||||
\frac{d\mathbf{q}}{dt} = (1 - \alpha)\mathbf{v}(\mathbf{q}, t) + \beta \mathbf{F}_{\text{boundary}}(\mathbf{q})
|
||||
$$
|
||||
|
||||
where $\alpha \approx 0.1$ (damping), $\beta \approx 0.05$ (boundary strength).
|
||||
|
||||
---
|
||||
|
||||
## 6. RK4 Integration
|
||||
|
||||
Fourth-order Runge-Kutta for state $\mathbf{s}$ with derivative $\mathbf{f}(\mathbf{s}, t)$:
|
||||
|
||||
$$
|
||||
\begin{align}
|
||||
\mathbf{k}_1 &= \mathbf{f}(\mathbf{s}, t) \\
|
||||
\mathbf{k}_2 &= \mathbf{f}(\mathbf{s} + \tfrac{\Delta t}{2} \mathbf{k}_1, t + \tfrac{\Delta t}{2}) \\
|
||||
\mathbf{k}_3 &= \mathbf{f}(\mathbf{s} + \tfrac{\Delta t}{2} \mathbf{k}_2, t + \tfrac{\Delta t}{2}) \\
|
||||
\mathbf{k}_4 &= \mathbf{f}(\mathbf{s} + \Delta t \mathbf{k}_3, t + \Delta t) \\
|
||||
\mathbf{s}_{\text{next}} &= \mathbf{s} + \tfrac{\Delta t}{6} (\mathbf{k}_1 + 2\mathbf{k}_2 + 2\mathbf{k}_3 + \mathbf{k}_4)
|
||||
\end{align}
|
||||
$$
|
||||
|
||||
---
|
||||
|
||||
## 7. Color Mapping
|
||||
|
||||
Map vorticity to HSV hue:
|
||||
|
||||
$$
|
||||
H = \text{mod}\left(\frac{\omega - \omega_{\min}}{\omega_{\max} - \omega_{\min}} \cdot 360°, 360°\right), \quad S = 0.8, \quad V = 0.9
|
||||
$$
|
||||
|
||||
Positive $\omega$ (CCW) → warm colors; negative $\omega$ (CW) → cool colors.
|
||||
14
shaders/particle_render.frag
Normal file
14
shaders/particle_render.frag
Normal file
@@ -0,0 +1,14 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec3 vColor;
|
||||
in float vAlpha;
|
||||
in vec2 vQuadCoord;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
float dist = length(vQuadCoord);
|
||||
float falloff = smoothstep(1.0, 0.5, dist);
|
||||
fragColor = vec4(vColor, vAlpha * falloff);
|
||||
}
|
||||
82
shaders/particle_render.vert
Normal file
82
shaders/particle_render.vert
Normal file
@@ -0,0 +1,82 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec2 quadVertex;
|
||||
in vec2 particleID;
|
||||
in float trailIndex;
|
||||
|
||||
uniform sampler2D stateTexture;
|
||||
uniform float time;
|
||||
uniform vec2 resolution;
|
||||
|
||||
out vec3 vColor;
|
||||
out float vAlpha;
|
||||
out vec2 vQuadCoord;
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
const float r_min = 0.002;
|
||||
const float r_max = 0.008;
|
||||
const float sigmoid_k = 20.0;
|
||||
const float sigmoid_s0 = 0.15;
|
||||
|
||||
float sigmoid(float x) {
|
||||
return 1.0 / (1.0 + exp(-sigmoid_k * (x - sigmoid_s0)));
|
||||
}
|
||||
|
||||
float velocityToRadius(vec2 vel) {
|
||||
float speed = length(vel);
|
||||
return r_min + (r_max - r_min) * sigmoid(speed);
|
||||
}
|
||||
|
||||
float streamFunction(vec2 q, float t) {
|
||||
float x = q.x, y = q.y;
|
||||
float phi1 = 0.5*t, phi2 = 0.3*t, phi3 = 0.4*t;
|
||||
float r = length(q - vec2(0.5));
|
||||
return 0.3*sin(2.0*PI*x + phi1)*sin(2.0*PI*y)
|
||||
+ 0.25*sin(3.0*PI*x)*sin(3.0*PI*y + phi2)
|
||||
+ 0.2*sin(4.0*PI*r + phi3)
|
||||
+ 0.15*(x - 0.5)*(y - 0.5)*sin(0.5*t);
|
||||
}
|
||||
|
||||
float computeVorticity(vec2 q, float t) {
|
||||
const float h = 0.001;
|
||||
float psi_c = streamFunction(q, t);
|
||||
float psi_r = streamFunction(vec2(q.x + h, q.y), t);
|
||||
float psi_l = streamFunction(vec2(q.x - h, q.y), t);
|
||||
float psi_u = streamFunction(vec2(q.x, q.y + h), t);
|
||||
float psi_d = streamFunction(vec2(q.x, q.y - h), t);
|
||||
|
||||
float d2psi_dx2 = (psi_r - 2.0*psi_c + psi_l) / (h*h);
|
||||
float d2psi_dy2 = (psi_u - 2.0*psi_c + psi_d) / (h*h);
|
||||
|
||||
return -(d2psi_dx2 + d2psi_dy2);
|
||||
}
|
||||
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 texCoord = (particleID + 0.5) / 16.0;
|
||||
vec4 state = texture(stateTexture, texCoord);
|
||||
|
||||
vec2 pos = state.rg;
|
||||
vec2 vel = state.ba;
|
||||
|
||||
float radius = velocityToRadius(vel);
|
||||
|
||||
vec2 aspectRatio = vec2(resolution.y / resolution.x, 1.0);
|
||||
vec2 screenPos = (pos * 2.0 - 1.0) * aspectRatio;
|
||||
vec2 offset = quadVertex * radius * aspectRatio;
|
||||
|
||||
gl_Position = vec4(screenPos + offset, 0.0, 1.0);
|
||||
|
||||
float vorticity = computeVorticity(pos, time);
|
||||
float hue = mod((vorticity + 3.0) / 6.0, 1.0);
|
||||
vColor = hsv2rgb(vec3(hue, 0.8, 0.9));
|
||||
|
||||
vAlpha = (1.0 - trailIndex / 20.0) * 0.6;
|
||||
vQuadCoord = quadVertex;
|
||||
}
|
||||
62
shaders/physics_update.frag
Normal file
62
shaders/physics_update.frag
Normal file
@@ -0,0 +1,62 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D stateTexture;
|
||||
uniform float time;
|
||||
uniform float dt;
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
const float alpha = 0.1;
|
||||
const float beta = 0.05;
|
||||
const float k = 2.0;
|
||||
|
||||
float streamFunction(vec2 q, float t) {
|
||||
float x = q.x, y = q.y;
|
||||
float phi1 = 0.5*t, phi2 = 0.3*t, phi3 = 0.4*t;
|
||||
float r = length(q - vec2(0.5));
|
||||
return 0.3*sin(2.0*PI*x + phi1)*sin(2.0*PI*y)
|
||||
+ 0.25*sin(3.0*PI*x)*sin(3.0*PI*y + phi2)
|
||||
+ 0.2*sin(4.0*PI*r + phi3)
|
||||
+ 0.15*(x - 0.5)*(y - 0.5)*sin(0.5*t);
|
||||
}
|
||||
|
||||
float dPsi_dx(vec2 q, float t) {
|
||||
const float h = 0.001;
|
||||
return (streamFunction(vec2(q.x + h, q.y), t) -
|
||||
streamFunction(vec2(q.x - h, q.y), t)) / (2.0 * h);
|
||||
}
|
||||
|
||||
float dPsi_dy(vec2 q, float t) {
|
||||
const float h = 0.001;
|
||||
return (streamFunction(vec2(q.x, q.y + h), t) -
|
||||
streamFunction(vec2(q.x, q.y - h), t)) / (2.0 * h);
|
||||
}
|
||||
|
||||
vec2 derivative(vec2 q, float t) {
|
||||
vec2 vel = vec2(dPsi_dy(q, t), -dPsi_dx(q, t));
|
||||
vec2 center_offset = q - vec2(0.5);
|
||||
vec2 F_boundary = -4.0 * k * vec2(
|
||||
pow(center_offset.x, 3.0),
|
||||
pow(center_offset.y, 3.0)
|
||||
);
|
||||
return (1.0 - alpha) * vel + beta * F_boundary;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 state = texture(stateTexture, vTexCoord);
|
||||
vec2 pos = state.rg;
|
||||
|
||||
// RK4 integration
|
||||
vec2 k1 = derivative(pos, time);
|
||||
vec2 k2 = derivative(pos + 0.5*dt*k1, time + 0.5*dt);
|
||||
vec2 k3 = derivative(pos + 0.5*dt*k2, time + 0.5*dt);
|
||||
vec2 k4 = derivative(pos + dt*k3, time + dt);
|
||||
|
||||
vec2 newPos = pos + (dt / 6.0) * (k1 + 2.0*k2 + 2.0*k3 + k4);
|
||||
vec2 vel = (k1 + 2.0*k2 + 2.0*k3 + k4) / 6.0;
|
||||
|
||||
fragColor = vec4(newPos, vel);
|
||||
}
|
||||
11
shaders/physics_update.vert
Normal file
11
shaders/physics_update.vert
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec2 position; // Fullscreen quad vertices
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
vTexCoord = position * 0.5 + 0.5; // [-1,1] -> [0,1]
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
|
||||
758
src/main.cpp
758
src/main.cpp
@@ -1,348 +1,544 @@
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <glad/gles2.h>
|
||||
|
||||
#include <wayland-client-core.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-egl-core.h>
|
||||
|
||||
#include "state.hpp"
|
||||
#include "shader_utils.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <random>
|
||||
#include <poll.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <EGL/eglplatform.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <wayland-egl-core.h>
|
||||
#include "ext-session-lock-v1-client-protocol.h"
|
||||
|
||||
// ============================================================================
|
||||
// EGL setup
|
||||
// ============================================================================
|
||||
#ifndef EGL_PLATFORM_WAYLAND_KHR
|
||||
#define EGL_PLATFORM_WAYLAND_KHR 0x31D8
|
||||
#endif
|
||||
|
||||
void init_egl(state::LockScreenState& ctx) {
|
||||
ctx.egl_display = eglGetDisplay((EGLNativeDisplayType)ctx.display);
|
||||
if (ctx.egl_display == EGL_NO_DISPLAY) {
|
||||
throw std::runtime_error("Failed to get EGL display");
|
||||
// ============================================================
|
||||
// Helper: Create shared memory buffer
|
||||
// ============================================================
|
||||
static int create_shm_file(size_t size) {
|
||||
const char* path = getenv("XDG_RUNTIME_DIR");
|
||||
if (!path) {
|
||||
spdlog::error("XDG_RUNTIME_DIR not set");
|
||||
return -1;
|
||||
}
|
||||
|
||||
EGLint major = 0, minor = 0;
|
||||
if (!eglInitialize(ctx.egl_display, &major, &minor)) {
|
||||
throw std::runtime_error("Failed to initialize EGL");
|
||||
std::string name = std::string(path) + "/exalock-XXXXXX";
|
||||
int fd = mkstemp(&name[0]);
|
||||
if (fd < 0) return -1;
|
||||
unlink(name.c_str());
|
||||
if (ftruncate(fd, static_cast<off_t>(size)) < 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
spdlog::info("EGL version {}.{}", major, minor);
|
||||
|
||||
if (!eglBindAPI(EGL_OPENGL_ES_API)) {
|
||||
throw std::runtime_error("Failed to bind OpenGL ES API");
|
||||
}
|
||||
|
||||
const EGLint config_attribs[] = {
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint num_configs = 0;
|
||||
if (!eglChooseConfig(ctx.egl_display,
|
||||
config_attribs,
|
||||
&ctx.egl_config,
|
||||
1,
|
||||
&num_configs) ||
|
||||
num_configs == 0) {
|
||||
throw std::runtime_error("Failed to choose EGL config");
|
||||
}
|
||||
|
||||
spdlog::info("EGL initialized successfully");
|
||||
return fd;
|
||||
}
|
||||
|
||||
void create_egl_surface_for_output(state::LockScreenState& ctx,
|
||||
state::Output& out,
|
||||
bool load_glad) {
|
||||
if (!out.configured) {
|
||||
throw std::runtime_error("Output not configured");
|
||||
static wl_buffer* create_black_buffer(wl_shm* shm, int32_t width, int32_t height) {
|
||||
int32_t stride = width * 4;
|
||||
size_t size = static_cast<size_t>(stride) * static_cast<size_t>(height);
|
||||
|
||||
int fd = create_shm_file(size);
|
||||
if (fd < 0) {
|
||||
spdlog::error("Failed to create shm file");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
out.window = wl_egl_window_create(out.wl_out_surface,
|
||||
out.surface_width,
|
||||
out.surface_height);
|
||||
if (!out.window) {
|
||||
throw std::runtime_error("Failed to create wl_egl_window");
|
||||
|
||||
void* data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
close(fd);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
out.egl_surface = eglCreateWindowSurface(
|
||||
ctx.egl_display,
|
||||
ctx.egl_config,
|
||||
(EGLNativeWindowType)out.window,
|
||||
nullptr);
|
||||
|
||||
if (out.egl_surface == EGL_NO_SURFACE) {
|
||||
throw std::runtime_error("Failed to create EGL surface");
|
||||
}
|
||||
|
||||
const EGLint context_attribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
out.egl_context = eglCreateContext(ctx.egl_display,
|
||||
ctx.egl_config,
|
||||
EGL_NO_CONTEXT,
|
||||
context_attribs);
|
||||
|
||||
if (out.egl_context == EGL_NO_CONTEXT) {
|
||||
throw std::runtime_error("Failed to create EGL context");
|
||||
}
|
||||
|
||||
if (!eglMakeCurrent(ctx.egl_display,
|
||||
out.egl_surface,
|
||||
out.egl_surface,
|
||||
out.egl_context)) {
|
||||
throw std::runtime_error("eglMakeCurrent failed");
|
||||
}
|
||||
|
||||
if (load_glad) {
|
||||
if (!gladLoadGLES2((GLADloadfunc)eglGetProcAddress)) {
|
||||
throw std::runtime_error("Failed to load GLAD");
|
||||
}
|
||||
|
||||
spdlog::info("GL_VENDOR = {}",
|
||||
reinterpret_cast<const char*>(glGetString(GL_VENDOR)));
|
||||
spdlog::info("GL_RENDERER = {}",
|
||||
reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||
spdlog::info("GL_VERSION = {}",
|
||||
reinterpret_cast<const char*>(glGetString(GL_VERSION)));
|
||||
|
||||
}
|
||||
|
||||
spdlog::info("EGL surface created for output {} ({}x{})",
|
||||
out.registry_id,
|
||||
out.surface_width,
|
||||
out.surface_height);
|
||||
|
||||
memset(data, 0, size);
|
||||
munmap(data, size);
|
||||
|
||||
wl_shm_pool* pool = wl_shm_create_pool(shm, fd, static_cast<int32_t>(size));
|
||||
wl_buffer* buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
|
||||
stride, WL_SHM_FORMAT_ARGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
close(fd);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wayland listeners
|
||||
// ============================================================================
|
||||
|
||||
static void output_handle_geometry(void*, wl_output*, int32_t, int32_t,
|
||||
int32_t, int32_t, int32_t,
|
||||
const char*, const char*, int32_t) {}
|
||||
|
||||
static void output_handle_mode(void* data,
|
||||
wl_output*,
|
||||
uint32_t flags,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t refresh) {
|
||||
if (!(flags & WL_OUTPUT_MODE_CURRENT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// wl_output listener
|
||||
// ============================================================
|
||||
static void output_geometry(void*, wl_output*, int, int, int, int, int,
|
||||
const char*, const char*, int) {}
|
||||
static void output_mode(void* data, wl_output*, uint32_t flags,
|
||||
int32_t width, int32_t height, int32_t) {
|
||||
if (!(flags & WL_OUTPUT_MODE_CURRENT)) return;
|
||||
auto& out = *static_cast<state::Output*>(data);
|
||||
out.width = static_cast<uint32_t>(width);
|
||||
out.width = static_cast<uint32_t>(width);
|
||||
out.height = static_cast<uint32_t>(height);
|
||||
|
||||
spdlog::info("Output mode {}x{} @ {}Hz",
|
||||
width,
|
||||
height,
|
||||
refresh / 1000);
|
||||
}
|
||||
|
||||
static void output_handle_done(void*, wl_output*) {}
|
||||
static void output_handle_scale(void*, wl_output*, int32_t) {}
|
||||
static void output_done(void*, wl_output*) {}
|
||||
static void output_scale(void*, wl_output*, int32_t) {}
|
||||
static void output_name(void*, wl_output*, const char*) {}
|
||||
static void output_description(void*, wl_output*, const char*) {}
|
||||
|
||||
static const wl_output_listener output_listener = {
|
||||
.geometry = output_handle_geometry,
|
||||
.mode = output_handle_mode,
|
||||
.done = output_handle_done,
|
||||
.scale = output_handle_scale,
|
||||
.geometry = output_geometry,
|
||||
.mode = output_mode,
|
||||
.done = output_done,
|
||||
.scale = output_scale,
|
||||
.name = output_name,
|
||||
.description = output_description
|
||||
};
|
||||
|
||||
static void registry_handle_global(void* data,
|
||||
wl_registry* registry,
|
||||
uint32_t name,
|
||||
const char* interface,
|
||||
uint32_t) {
|
||||
auto& ctx = *static_cast<state::LockScreenState*>(data);
|
||||
|
||||
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||
ctx.compostier = static_cast<wl_compositor*>(
|
||||
wl_registry_bind(registry,
|
||||
name,
|
||||
&wl_compositor_interface,
|
||||
6));
|
||||
} else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
auto out = std::make_shared<state::Output>();
|
||||
out->output = static_cast<wl_output*>(
|
||||
wl_registry_bind(registry,
|
||||
name,
|
||||
&wl_output_interface,
|
||||
4));
|
||||
out->registry_id = name;
|
||||
|
||||
wl_output_add_listener(out->output,
|
||||
&output_listener,
|
||||
out.get());
|
||||
|
||||
ctx.outputs.push_back(out);
|
||||
} else if (strcmp(interface,
|
||||
ext_session_lock_manager_v1_interface.name) == 0) {
|
||||
ctx.lock_manager =
|
||||
static_cast<ext_session_lock_manager_v1*>(
|
||||
wl_registry_bind(registry,
|
||||
name,
|
||||
&ext_session_lock_manager_v1_interface,
|
||||
1));
|
||||
// ============================================================
|
||||
// Lock surface listener
|
||||
// ============================================================
|
||||
static void lock_surface_configure(
|
||||
void* data,
|
||||
ext_session_lock_surface_v1* surface,
|
||||
uint32_t serial,
|
||||
uint32_t width,
|
||||
uint32_t height)
|
||||
{
|
||||
auto& out = *static_cast<state::Output*>(data);
|
||||
ext_session_lock_surface_v1_ack_configure(surface, serial);
|
||||
out.surface_width = width;
|
||||
out.surface_height = height;
|
||||
out.configured = true;
|
||||
|
||||
if (out.shm && !out.buffer) {
|
||||
out.buffer = create_black_buffer(out.shm,
|
||||
static_cast<int32_t>(width),
|
||||
static_cast<int32_t>(height));
|
||||
if (out.buffer) {
|
||||
wl_surface_attach(out.wl_surface, out.buffer, 0, 0);
|
||||
wl_surface_damage(out.wl_surface, 0, 0,
|
||||
static_cast<int32_t>(width),
|
||||
static_cast<int32_t>(height));
|
||||
wl_surface_commit(out.wl_surface);
|
||||
spdlog::info("Lock surface configured with black buffer: {}x{}", width, height);
|
||||
} else {
|
||||
spdlog::error("Failed to create buffer for surface: {}x{}", width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void registry_handle_global_remove(void*, wl_registry*, uint32_t) {}
|
||||
|
||||
static const wl_registry_listener registry_listener = {
|
||||
.global = registry_handle_global,
|
||||
.global_remove = registry_handle_global_remove,
|
||||
static const ext_session_lock_surface_v1_listener lock_surface_listener = {
|
||||
.configure = lock_surface_configure
|
||||
};
|
||||
|
||||
static void lock_handle_locked(void* data, ext_session_lock_v1*) {
|
||||
// ============================================================
|
||||
// Session lock listener
|
||||
// ============================================================
|
||||
static void lock_locked(void* data, ext_session_lock_v1*) {
|
||||
auto& ctx = *static_cast<state::LockScreenState*>(data);
|
||||
ctx.locked = true;
|
||||
spdlog::info("🔒 Session locked");
|
||||
}
|
||||
|
||||
static void lock_handle_finished(void* data, ext_session_lock_v1*) {
|
||||
static void lock_finished(void* data, ext_session_lock_v1*) {
|
||||
auto& ctx = *static_cast<state::LockScreenState*>(data);
|
||||
ctx.running = false;
|
||||
spdlog::error("Session lock denied/finished");
|
||||
spdlog::info("🔓 Session finished by compositor");
|
||||
}
|
||||
|
||||
static const ext_session_lock_v1_listener lock_listener = {
|
||||
.locked = lock_handle_locked,
|
||||
.finished = lock_handle_finished,
|
||||
.locked = lock_locked,
|
||||
.finished = lock_finished
|
||||
};
|
||||
|
||||
static void lock_surface_handle_configure(void* data,
|
||||
ext_session_lock_surface_v1* surface,
|
||||
uint32_t serial,
|
||||
uint32_t width,
|
||||
uint32_t height) {
|
||||
auto& out = *static_cast<state::Output*>(data);
|
||||
// ============================================================
|
||||
// Registry listener
|
||||
// ============================================================
|
||||
static void registry_add(void* data,
|
||||
wl_registry* registry,
|
||||
uint32_t name,
|
||||
const char* interface,
|
||||
uint32_t)
|
||||
{
|
||||
auto& ctx = *static_cast<state::LockScreenState*>(data);
|
||||
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||
ctx.compositor = static_cast<wl_compositor*>(
|
||||
wl_registry_bind(registry, name, &wl_compositor_interface, 4)
|
||||
);
|
||||
spdlog::info("Bound wl_compositor");
|
||||
}
|
||||
else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
ctx.shm = static_cast<wl_shm*>(
|
||||
wl_registry_bind(registry, name, &wl_shm_interface, 1)
|
||||
);
|
||||
spdlog::info("Bound wl_shm");
|
||||
}
|
||||
else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
auto out = std::make_shared<state::Output>();
|
||||
out->registry_id = name;
|
||||
out->output = static_cast<wl_output*>(
|
||||
wl_registry_bind(registry, name, &wl_output_interface, 4)
|
||||
);
|
||||
wl_output_add_listener(out->output, &output_listener, out.get());
|
||||
ctx.outputs.push_back(out);
|
||||
spdlog::info("Discovered output: registry_id={}", name);
|
||||
}
|
||||
else if (strcmp(interface, ext_session_lock_manager_v1_interface.name) == 0) {
|
||||
ctx.lock_manager = static_cast<ext_session_lock_manager_v1*>(
|
||||
wl_registry_bind(registry, name,
|
||||
&ext_session_lock_manager_v1_interface, 1)
|
||||
);
|
||||
spdlog::info("Bound ext_session_lock_manager_v1");
|
||||
}
|
||||
}
|
||||
static void registry_remove(void*, wl_registry*, uint32_t) {}
|
||||
|
||||
ext_session_lock_surface_v1_ack_configure(surface, serial);
|
||||
static const wl_registry_listener registry_listener = {
|
||||
.global = registry_add,
|
||||
.global_remove = registry_remove
|
||||
};
|
||||
|
||||
out.surface_width = width;
|
||||
out.surface_height = height;
|
||||
out.configured = true;
|
||||
// ============================================================
|
||||
// EGL initialization
|
||||
// ============================================================
|
||||
static void init_egl(state::LockScreenState& ctx) {
|
||||
ctx.egl_display = eglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR,
|
||||
static_cast<void*>(ctx.display),
|
||||
nullptr);
|
||||
|
||||
spdlog::info("Lock surface configured {}x{}", width, height);
|
||||
if (ctx.egl_display == EGL_NO_DISPLAY) {
|
||||
spdlog::warn("eglGetPlatformDisplay failed, falling back to eglGetDisplay");
|
||||
ctx.egl_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(ctx.display));
|
||||
}
|
||||
|
||||
if (ctx.egl_display == EGL_NO_DISPLAY) {
|
||||
spdlog::error("Failed to obtain EGL display");
|
||||
throw std::runtime_error("No EGL display");
|
||||
}
|
||||
|
||||
if (!eglInitialize(ctx.egl_display, nullptr, nullptr)) {
|
||||
EGLint err = eglGetError();
|
||||
spdlog::error("eglInitialize failed: 0x{:x}", err);
|
||||
throw std::runtime_error("eglInitialize failed");
|
||||
}
|
||||
|
||||
spdlog::info("EGL initialized successfully");
|
||||
|
||||
const EGLint cfg_attribs[] = {
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 8,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLint n = 0;
|
||||
if (!eglChooseConfig(ctx.egl_display, cfg_attribs, &ctx.egl_config, 1, &n) || n == 0) {
|
||||
EGLint err = eglGetError();
|
||||
spdlog::error("eglChooseConfig failed: 0x{:x}", err);
|
||||
throw std::runtime_error("eglChooseConfig failed");
|
||||
}
|
||||
spdlog::info("EGL config selected");
|
||||
}
|
||||
|
||||
static const ext_session_lock_surface_v1_listener lock_surface_listener = {
|
||||
.configure = lock_surface_handle_configure,
|
||||
};
|
||||
// ============================================================
|
||||
// Particle system initialization
|
||||
// ============================================================
|
||||
static void init_particle_system(state::LockScreenState& ctx) {
|
||||
ctx.particles = std::make_shared<state::ParticleSystem>();
|
||||
auto& ps = *ctx.particles;
|
||||
|
||||
ps.physics_program = shader::createProgram(
|
||||
"shaders/physics_update.vert",
|
||||
"shaders/physics_update.frag"
|
||||
);
|
||||
ps.render_program = shader::createProgram(
|
||||
"shaders/particle_render.vert",
|
||||
"shaders/particle_render.frag"
|
||||
);
|
||||
|
||||
spdlog::info("Shaders compiled and linked");
|
||||
|
||||
ps.u_time = glGetUniformLocation(ps.physics_program, "time");
|
||||
ps.u_dt = glGetUniformLocation(ps.physics_program, "dt");
|
||||
ps.u_stateTexture = glGetUniformLocation(ps.physics_program, "stateTexture");
|
||||
ps.u_resolution = glGetUniformLocation(ps.render_program, "resolution");
|
||||
|
||||
glGenTextures(2, ps.state_texture);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
glBindTexture(GL_TEXTURE_2D, ps.state_texture[i]);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 16, 16, 0,
|
||||
GL_RGBA, GL_FLOAT, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::uniform_real_distribution<float> dist(0.0f, 1.0f);
|
||||
|
||||
std::vector<float> initial_state(16 * 16 * 4);
|
||||
for (size_t i = 0; i < 256; ++i) {
|
||||
initial_state[i * 4 + 0] = dist(gen);
|
||||
initial_state[i * 4 + 1] = dist(gen);
|
||||
initial_state[i * 4 + 2] = 0.0f;
|
||||
initial_state[i * 4 + 3] = 0.0f;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, ps.state_texture[0]);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_RGBA, GL_FLOAT, initial_state.data());
|
||||
|
||||
glGenFramebuffers(2, ps.fbo);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, ps.fbo[i]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D, ps.state_texture[i], 0);
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
spdlog::error("FBO {} incomplete: 0x{:x}", i, status);
|
||||
}
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
float quad_verts[] = {
|
||||
-1.0f, -1.0f,
|
||||
1.0f, -1.0f,
|
||||
-1.0f, 1.0f,
|
||||
1.0f, 1.0f
|
||||
};
|
||||
glGenBuffers(1, &ps.quad_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ps.quad_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_verts), quad_verts, GL_STATIC_DRAW);
|
||||
|
||||
float particle_quad[] = {
|
||||
-1.0f, -1.0f,
|
||||
1.0f, -1.0f,
|
||||
1.0f, 1.0f,
|
||||
-1.0f, 1.0f
|
||||
};
|
||||
glGenBuffers(1, &ps.particle_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ps.particle_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(particle_quad), particle_quad, GL_STATIC_DRAW);
|
||||
|
||||
std::vector<float> instance_data;
|
||||
for (int p = 0; p < 256; ++p) {
|
||||
instance_data.push_back(static_cast<float>(p % 16));
|
||||
instance_data.push_back(static_cast<float>(p / 16));
|
||||
instance_data.push_back(0.0f);
|
||||
}
|
||||
|
||||
glGenBuffers(1, &ps.instance_vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ps.instance_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
static_cast<GLsizeiptr>(instance_data.size() * sizeof(float)),
|
||||
instance_data.data(), GL_STATIC_DRAW);
|
||||
|
||||
spdlog::info("Particle system initialized with 256 particles");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ============================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
// ============================================================
|
||||
int main() {
|
||||
auto file_logger = spdlog::basic_logger_mt("lockscreen_logger", "lockscreen.log");
|
||||
spdlog::set_default_logger(file_logger);
|
||||
spdlog::set_level(spdlog::level::info);
|
||||
spdlog::info("Starting lockscreen program");
|
||||
|
||||
auto ctx = std::make_shared<state::LockScreenState>();
|
||||
|
||||
ctx->display = wl_display_connect(nullptr);
|
||||
if (!ctx->display) {
|
||||
throw std::runtime_error("Failed to connect to Wayland");
|
||||
}
|
||||
if (!ctx->display) throw std::runtime_error("Failed to connect to Wayland");
|
||||
|
||||
wl_registry* registry = wl_display_get_registry(ctx->display);
|
||||
wl_registry_add_listener(registry,
|
||||
®istry_listener,
|
||||
ctx.get());
|
||||
ctx->registry = wl_display_get_registry(ctx->display);
|
||||
wl_registry_add_listener(ctx->registry, ®istry_listener, ctx.get());
|
||||
wl_display_roundtrip(ctx->display);
|
||||
wl_display_roundtrip(ctx->display);
|
||||
|
||||
if (!ctx->compostier || !ctx->lock_manager || ctx->outputs.empty()) {
|
||||
throw std::runtime_error("Required Wayland globals missing");
|
||||
}
|
||||
if (!ctx->compositor || !ctx->shm || !ctx->lock_manager || ctx->outputs.empty())
|
||||
throw std::runtime_error("Missing required Wayland globals");
|
||||
|
||||
ctx->session_lock =
|
||||
ext_session_lock_manager_v1_lock(ctx->lock_manager);
|
||||
ext_session_lock_v1_add_listener(ctx->session_lock,
|
||||
&lock_listener,
|
||||
ctx.get());
|
||||
spdlog::info("Found {} outputs, requesting session lock", ctx->outputs.size());
|
||||
|
||||
ctx->session_lock = ext_session_lock_manager_v1_lock(ctx->lock_manager);
|
||||
ext_session_lock_v1_add_listener(ctx->session_lock, &lock_listener, ctx.get());
|
||||
|
||||
for (auto& out : ctx->outputs) {
|
||||
out->wl_out_surface =
|
||||
wl_compositor_create_surface(ctx->compostier);
|
||||
out->shm = ctx->shm;
|
||||
out->wl_surface = wl_compositor_create_surface(ctx->compositor);
|
||||
out->lock_surface = ext_session_lock_v1_get_lock_surface(
|
||||
ctx->session_lock, out->wl_surface, out->output);
|
||||
ext_session_lock_surface_v1_add_listener(out->lock_surface,
|
||||
&lock_surface_listener, out.get());
|
||||
}
|
||||
wl_display_flush(ctx->display);
|
||||
|
||||
out->lock_surface =
|
||||
ext_session_lock_v1_get_lock_surface(
|
||||
ctx->session_lock,
|
||||
out->wl_out_surface,
|
||||
out->output);
|
||||
spdlog::info("Waiting for lock confirmation...");
|
||||
while (ctx->running) {
|
||||
if (wl_display_dispatch(ctx->display) == -1) {
|
||||
spdlog::error("Wayland connection lost during setup");
|
||||
return 1;
|
||||
}
|
||||
bool ready = ctx->locked;
|
||||
for (const auto& o : ctx->outputs) ready &= o->configured;
|
||||
if (ready) break;
|
||||
}
|
||||
|
||||
if (!ctx->locked) {
|
||||
spdlog::error("Failed to acquire lock");
|
||||
return 1;
|
||||
}
|
||||
|
||||
spdlog::info("Lock acquired, initializing EGL");
|
||||
|
||||
ext_session_lock_surface_v1_add_listener(
|
||||
out->lock_surface,
|
||||
&lock_surface_listener,
|
||||
out.get());
|
||||
|
||||
wl_surface_commit(out->wl_out_surface);
|
||||
try {
|
||||
init_egl(*ctx);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("EGL setup failed: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
wl_display_roundtrip(ctx->display);
|
||||
|
||||
init_egl(*ctx);
|
||||
|
||||
bool glad_loaded = false;
|
||||
|
||||
EGLContext shared_context = EGL_NO_CONTEXT;
|
||||
for (auto& out : ctx->outputs) {
|
||||
create_egl_surface_for_output(*ctx,
|
||||
*out,
|
||||
!glad_loaded);
|
||||
glad_loaded = true;
|
||||
if (!out->configured || out->surface_width == 0 || out->surface_height == 0) continue;
|
||||
|
||||
glViewport(0, 0,
|
||||
out->surface_width,
|
||||
out->surface_height);
|
||||
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
out->window = wl_egl_window_create(out->wl_surface,
|
||||
static_cast<int>(out->surface_width),
|
||||
static_cast<int>(out->surface_height));
|
||||
if (!out->window) {
|
||||
spdlog::warn("wl_egl_window_create failed for output {}", out->registry_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
eglSwapBuffers(ctx->egl_display, out->egl_surface);
|
||||
out->egl_surface = eglCreateWindowSurface(ctx->egl_display, ctx->egl_config,
|
||||
out->window, nullptr);
|
||||
if (out->egl_surface == EGL_NO_SURFACE) {
|
||||
spdlog::warn("eglCreateWindowSurface failed for output {}", out->registry_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
|
||||
out->egl_context = eglCreateContext(ctx->egl_display, ctx->egl_config,
|
||||
shared_context, ctx_attribs);
|
||||
if (out->egl_context == EGL_NO_CONTEXT) {
|
||||
spdlog::warn("eglCreateContext failed for output {}", out->registry_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (shared_context == EGL_NO_CONTEXT) shared_context = out->egl_context;
|
||||
}
|
||||
|
||||
wl_display_roundtrip(ctx->display);
|
||||
|
||||
// ======================
|
||||
// CLEAN SHUTDOWN
|
||||
// ======================
|
||||
|
||||
for (auto& out : ctx->outputs) {
|
||||
if (out->egl_context != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(ctx->egl_display, out->egl_context);
|
||||
}
|
||||
if (out->egl_surface != EGL_NO_SURFACE) {
|
||||
eglDestroySurface(ctx->egl_display, out->egl_surface);
|
||||
}
|
||||
if (out->window) {
|
||||
wl_egl_window_destroy(out->window);
|
||||
}
|
||||
if (out->lock_surface) {
|
||||
ext_session_lock_surface_v1_destroy(out->lock_surface);
|
||||
}
|
||||
if (out->wl_out_surface) {
|
||||
wl_surface_destroy(out->wl_out_surface);
|
||||
}
|
||||
// Initialize particle system
|
||||
auto& first_out = ctx->outputs[0];
|
||||
eglMakeCurrent(ctx->egl_display, first_out->egl_surface,
|
||||
first_out->egl_surface, first_out->egl_context);
|
||||
|
||||
try {
|
||||
init_particle_system(*ctx);
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Particle system init failed: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int fd = wl_display_get_fd(ctx->display);
|
||||
|
||||
while (ctx->running) {
|
||||
struct pollfd pfd = { fd, POLLIN, 0 };
|
||||
int ret = poll(&pfd, 1, 16);
|
||||
if (ret > 0 && (pfd.revents & POLLIN)) {
|
||||
if (wl_display_dispatch(ctx->display) == -1) break;
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float time = std::chrono::duration<float>(now - start).count();
|
||||
float dt = 0.016f;
|
||||
|
||||
auto& ps = *ctx->particles;
|
||||
|
||||
for (auto& out : ctx->outputs) {
|
||||
if (out->egl_surface == EGL_NO_SURFACE) continue;
|
||||
|
||||
eglMakeCurrent(ctx->egl_display, out->egl_surface,
|
||||
out->egl_surface, out->egl_context);
|
||||
|
||||
// Physics update
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, ps.fbo[1 - ps.current_state]);
|
||||
glViewport(0, 0, 16, 16);
|
||||
|
||||
glUseProgram(ps.physics_program);
|
||||
glUniform1f(ps.u_time, time);
|
||||
glUniform1f(ps.u_dt, dt);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, ps.state_texture[ps.current_state]);
|
||||
glUniform1i(ps.u_stateTexture, 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ps.quad_vbo);
|
||||
GLint pos_attrib = glGetAttribLocation(ps.physics_program, "position");
|
||||
glEnableVertexAttribArray(pos_attrib);
|
||||
glVertexAttribPointer(pos_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
// Render particles
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glViewport(0, 0, out->surface_width, out->surface_height);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glUseProgram(ps.render_program);
|
||||
glUniform1f(glGetUniformLocation(ps.render_program, "time"), time);
|
||||
glUniform2f(ps.u_resolution,
|
||||
static_cast<float>(out->surface_width),
|
||||
static_cast<float>(out->surface_height));
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, ps.state_texture[1 - ps.current_state]);
|
||||
glUniform1i(glGetUniformLocation(ps.render_program, "stateTexture"), 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ps.particle_vbo);
|
||||
GLint quad_attrib = glGetAttribLocation(ps.render_program, "quadVertex");
|
||||
glEnableVertexAttribArray(quad_attrib);
|
||||
glVertexAttribPointer(quad_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ps.instance_vbo);
|
||||
GLint pid_attrib = glGetAttribLocation(ps.render_program, "particleID");
|
||||
GLint trail_attrib = glGetAttribLocation(ps.render_program, "trailIndex");
|
||||
glEnableVertexAttribArray(pid_attrib);
|
||||
glEnableVertexAttribArray(trail_attrib);
|
||||
glVertexAttribPointer(pid_attrib, 2, GL_FLOAT, GL_FALSE,
|
||||
3 * sizeof(float), 0);
|
||||
glVertexAttribPointer(trail_attrib, 1, GL_FLOAT, GL_FALSE,
|
||||
3 * sizeof(float),
|
||||
reinterpret_cast<void*>(2 * sizeof(float)));
|
||||
glVertexAttribDivisor(pid_attrib, 1);
|
||||
glVertexAttribDivisor(trail_attrib, 1);
|
||||
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, 256);
|
||||
|
||||
eglSwapBuffers(ctx->egl_display, out->egl_surface);
|
||||
wl_surface_commit(out->wl_surface);
|
||||
}
|
||||
|
||||
ps.current_state = 1 - ps.current_state;
|
||||
wl_display_flush(ctx->display);
|
||||
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() >= 10)
|
||||
break;
|
||||
}
|
||||
|
||||
eglMakeCurrent(ctx->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
if (ctx->session_lock) {
|
||||
ext_session_lock_v1_destroy(ctx->session_lock);
|
||||
ext_session_lock_v1_unlock_and_destroy(ctx->session_lock);
|
||||
}
|
||||
wl_display_flush(ctx->display);
|
||||
wl_display_roundtrip(ctx->display);
|
||||
|
||||
eglTerminate(ctx->egl_display);
|
||||
wl_display_disconnect(ctx->display);
|
||||
|
||||
spdlog::info("Exited cleanly after one frame");
|
||||
|
||||
spdlog::info("Clean exit after 10 seconds");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
79
src/shader_utils.cpp
Normal file
79
src/shader_utils.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "shader_utils.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace shader {
|
||||
|
||||
std::string loadFile(const char* path) {
|
||||
std::ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
spdlog::error("Failed to open shader file: {}", path);
|
||||
throw std::runtime_error(std::string("Cannot open shader: ") + path);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
GLuint compileShader(GLenum type, const char* source) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint success = 0;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
char log[512];
|
||||
glGetShaderInfoLog(shader, 512, nullptr, log);
|
||||
spdlog::error("Shader compilation failed:\n{}", log);
|
||||
glDeleteShader(shader);
|
||||
throw std::runtime_error("Shader compilation failed");
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint linkProgram(GLuint vertShader, GLuint fragShader) {
|
||||
GLuint program = glCreateProgram();
|
||||
glAttachShader(program, vertShader);
|
||||
glAttachShader(program, fragShader);
|
||||
glLinkProgram(program);
|
||||
|
||||
GLint success = 0;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
char log[512];
|
||||
glGetProgramInfoLog(program, 512, nullptr, log);
|
||||
spdlog::error("Program linking failed:\n{}", log);
|
||||
glDeleteProgram(program);
|
||||
throw std::runtime_error("Program linking failed");
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
GLuint createProgram(const char* vertPath, const char* fragPath) {
|
||||
std::string vertSource = loadFile(vertPath);
|
||||
std::string fragSource = loadFile(fragPath);
|
||||
|
||||
GLuint vertShader = compileShader(GL_VERTEX_SHADER, vertSource.c_str());
|
||||
GLuint fragShader = compileShader(GL_FRAGMENT_SHADER, fragSource.c_str());
|
||||
|
||||
GLuint program = linkProgram(vertShader, fragShader);
|
||||
|
||||
glDeleteShader(vertShader);
|
||||
glDeleteShader(fragShader);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
void checkGLError(const char* op) {
|
||||
GLenum err;
|
||||
while ((err = glGetError()) != GL_NO_ERROR) {
|
||||
spdlog::error("GL error after {}: 0x{:x}", op, err);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace shader
|
||||
21
src/shader_utils.hpp
Normal file
21
src/shader_utils.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <GLES2/gl2.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace shader {
|
||||
// Load shader from file
|
||||
std::string loadFile(const char* path);
|
||||
|
||||
// Compile shader
|
||||
GLuint compileShader(GLenum type, const char* source);
|
||||
|
||||
// Link program
|
||||
GLuint linkProgram(GLuint vertShader, GLuint fragShader);
|
||||
|
||||
// All-in-one: load, compile, link
|
||||
GLuint createProgram(const char* vertPath, const char* fragPath);
|
||||
|
||||
// Check for GL errors
|
||||
void checkGLError(const char* op);
|
||||
}
|
||||
@@ -1,57 +1,36 @@
|
||||
#include "state.hpp"
|
||||
#include "ext-session-lock-v1-client-protocol.h"
|
||||
#include <wayland-client-core.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-egl-core.h>
|
||||
|
||||
|
||||
namespace state{
|
||||
LockScreenState::~LockScreenState() {
|
||||
// IMPORTANT: Clear outputs BEFORE destroying display
|
||||
// This ensures Output destructors run while display is still valid
|
||||
namespace state {
|
||||
LockScreenState::~LockScreenState() {
|
||||
outputs.clear();
|
||||
if (egl_display != EGL_NO_DISPLAY) {
|
||||
eglTerminate(egl_display);
|
||||
egl_display = EGL_NO_DISPLAY;
|
||||
}
|
||||
if (lock_manager) {
|
||||
ext_session_lock_manager_v1_destroy(lock_manager);
|
||||
lock_manager = nullptr;
|
||||
}
|
||||
if (compostier) {
|
||||
wl_compositor_destroy(compostier);
|
||||
compostier = nullptr;
|
||||
}
|
||||
if (compositer_registry) {
|
||||
wl_registry_destroy(compositer_registry);
|
||||
compositer_registry = nullptr;
|
||||
}
|
||||
if (display) {
|
||||
wl_display_flush(display);
|
||||
wl_display_disconnect(display);
|
||||
display = nullptr;
|
||||
}
|
||||
|
||||
if(session_lock) ext_session_lock_v1_destroy(session_lock);
|
||||
locked=false;
|
||||
|
||||
if (egl_display != EGL_NO_DISPLAY) eglTerminate(egl_display);
|
||||
if (lock_manager) ext_session_lock_manager_v1_destroy(lock_manager);
|
||||
if (shm) wl_shm_destroy(shm);
|
||||
if (compositor) wl_compositor_destroy(compositor);
|
||||
if (registry) wl_registry_destroy(registry);
|
||||
if (display) wl_display_disconnect(display);
|
||||
running = false;
|
||||
}
|
||||
|
||||
|
||||
Output::~Output() {
|
||||
if (egl_context != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(eglGetCurrentDisplay(), egl_context);
|
||||
egl_context = EGL_NO_CONTEXT;
|
||||
if (egl_context != EGL_NO_CONTEXT) eglDestroyContext(eglGetCurrentDisplay(), egl_context);
|
||||
if (egl_surface != EGL_NO_SURFACE) eglDestroySurface(eglGetCurrentDisplay(), egl_surface);
|
||||
if (window) wl_egl_window_destroy(window);
|
||||
if (buffer) wl_buffer_destroy(buffer);
|
||||
if (lock_surface) ext_session_lock_surface_v1_destroy(lock_surface);
|
||||
if (wl_surface) wl_surface_destroy(wl_surface);
|
||||
if (output) wl_output_destroy(output);
|
||||
}
|
||||
if (egl_surface != EGL_NO_SURFACE) {
|
||||
eglDestroySurface(eglGetCurrentDisplay(), egl_surface);
|
||||
egl_surface = EGL_NO_SURFACE;
|
||||
}
|
||||
if (output) {
|
||||
wl_output_destroy(output);
|
||||
output = nullptr;
|
||||
}
|
||||
if(lock_surface) ext_session_lock_surface_v1_destroy(lock_surface);
|
||||
if(wl_out_surface) wl_surface_destroy(wl_out_surface);
|
||||
|
||||
ParticleSystem::~ParticleSystem() {
|
||||
// Clean up OpenGL resources if any exist
|
||||
if (physics_program) glDeleteProgram(physics_program);
|
||||
if (render_program) glDeleteProgram(render_program);
|
||||
if (fbo[0]) glDeleteBuffers(2, fbo);
|
||||
if (state_texture[0]) glDeleteTextures(2, state_texture);
|
||||
if (quad_vbo) glDeleteBuffers(1, &quad_vbo);
|
||||
if (particle_vbo) glDeleteBuffers(1, &particle_vbo);
|
||||
if (instance_vbo) glDeleteBuffers(1, &instance_vbo);
|
||||
// You can leave some as empty if you're sure they're 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wayland-client-core.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-egl-core.h>
|
||||
#include "ext-session-lock-v1-client-protocol.h"
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES3/gl3.h>
|
||||
|
||||
namespace state{
|
||||
struct Output{
|
||||
namespace state {
|
||||
struct Output {
|
||||
wl_output* output = nullptr;
|
||||
uint32_t registry_id = 0;
|
||||
uint32_t width = uint32_t {0};
|
||||
uint32_t height = uint32_t {0};
|
||||
|
||||
wl_surface* wl_out_surface = nullptr;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
wl_surface* wl_surface = nullptr;
|
||||
ext_session_lock_surface_v1* lock_surface = nullptr;
|
||||
bool configured = false;
|
||||
uint32_t surface_width = uint32_t {0};
|
||||
uint32_t surface_height = uint32_t {0};
|
||||
|
||||
uint32_t surface_width = 0;
|
||||
uint32_t surface_height = 0;
|
||||
|
||||
wl_shm* shm = nullptr;
|
||||
wl_buffer* buffer = nullptr;
|
||||
|
||||
wl_egl_window* window = nullptr;
|
||||
EGLSurface egl_surface = EGL_NO_SURFACE;
|
||||
EGLContext egl_context = EGL_NO_CONTEXT;
|
||||
|
||||
|
||||
~Output();
|
||||
};
|
||||
struct LockScreenState{
|
||||
|
||||
struct ParticleSystem {
|
||||
GLuint physics_program = 0;
|
||||
GLuint render_program = 0;
|
||||
|
||||
GLuint fbo[2] = {0, 0};
|
||||
GLuint state_texture[2] = {0, 0};
|
||||
int current_state = 0;
|
||||
|
||||
GLuint quad_vbo = 0;
|
||||
GLuint particle_vbo = 0;
|
||||
GLuint instance_vbo = 0;
|
||||
|
||||
GLint u_time = -1;
|
||||
GLint u_dt = -1;
|
||||
GLint u_resolution = -1;
|
||||
GLint u_stateTexture = -1;
|
||||
|
||||
~ParticleSystem();
|
||||
};
|
||||
|
||||
struct LockScreenState {
|
||||
wl_display* display = nullptr;
|
||||
wl_registry* compositer_registry = nullptr;
|
||||
wl_compositor* compostier = nullptr;
|
||||
wl_registry* registry = nullptr;
|
||||
wl_compositor* compositor = nullptr;
|
||||
wl_shm* shm = nullptr;
|
||||
ext_session_lock_manager_v1* lock_manager = nullptr;
|
||||
std::string username = std::string {};
|
||||
std::string password = std::string {};
|
||||
|
||||
std::vector<std::shared_ptr<Output>> outputs = std::vector<std::shared_ptr<Output>>{};
|
||||
|
||||
std::vector<std::shared_ptr<Output>> outputs;
|
||||
bool running = true;
|
||||
|
||||
ext_session_lock_v1* session_lock = nullptr;
|
||||
bool locked=false;
|
||||
|
||||
bool locked = false;
|
||||
|
||||
EGLDisplay egl_display = EGL_NO_DISPLAY;
|
||||
EGLConfig egl_config = nullptr;
|
||||
|
||||
|
||||
std::shared_ptr<ParticleSystem> particles;
|
||||
|
||||
~LockScreenState();
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user