diff --git a/command.sh b/command.sh new file mode 100644 index 0000000..f7dcba5 --- /dev/null +++ b/command.sh @@ -0,0 +1 @@ +__EGL_VENDOR_LIBRARY_NAME=mesa LIBGL_ALWAYS_SOFTWARE=1 MESA_LOADER_DRIVER_OVERRIDE=llvmpipe ./build/exalock diff --git a/math.md b/math.md new file mode 100644 index 0000000..a335378 --- /dev/null +++ b/math.md @@ -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. diff --git a/shaders/particle_render.frag b/shaders/particle_render.frag new file mode 100644 index 0000000..b8c4234 --- /dev/null +++ b/shaders/particle_render.frag @@ -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); +} diff --git a/shaders/particle_render.vert b/shaders/particle_render.vert new file mode 100644 index 0000000..c6079f7 --- /dev/null +++ b/shaders/particle_render.vert @@ -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; +} diff --git a/shaders/physics_update.frag b/shaders/physics_update.frag new file mode 100644 index 0000000..b285556 --- /dev/null +++ b/shaders/physics_update.frag @@ -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); +} diff --git a/shaders/physics_update.vert b/shaders/physics_update.vert new file mode 100644 index 0000000..824b943 --- /dev/null +++ b/shaders/physics_update.vert @@ -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); +} + diff --git a/src/main.cpp b/src/main.cpp index 2323bf5..7206422 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,348 +1,544 @@ -#include "spdlog/spdlog.h" - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - #include "state.hpp" +#include "shader_utils.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(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(stride) * static_cast(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(glGetString(GL_VENDOR))); - spdlog::info("GL_RENDERER = {}", - reinterpret_cast(glGetString(GL_RENDERER))); - spdlog::info("GL_VERSION = {}", - reinterpret_cast(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(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(data); - out.width = static_cast(width); + out.width = static_cast(width); out.height = static_cast(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(data); - - if (strcmp(interface, wl_compositor_interface.name) == 0) { - ctx.compostier = static_cast( - wl_registry_bind(registry, - name, - &wl_compositor_interface, - 6)); - } else if (strcmp(interface, wl_output_interface.name) == 0) { - auto out = std::make_shared(); - out->output = static_cast( - 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( - 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(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(width), + static_cast(height)); + if (out.buffer) { + wl_surface_attach(out.wl_surface, out.buffer, 0, 0); + wl_surface_damage(out.wl_surface, 0, 0, + static_cast(width), + static_cast(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(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(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(data); +// ============================================================ +// Registry listener +// ============================================================ +static void registry_add(void* data, + wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t) +{ + auto& ctx = *static_cast(data); + if (strcmp(interface, wl_compositor_interface.name) == 0) { + ctx.compositor = static_cast( + 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_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(); + out->registry_id = name; + out->output = static_cast( + 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( + 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(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(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(); + 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 dist(0.0f, 1.0f); + + std::vector 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 instance_data; + for (int p = 0; p < 256; ++p) { + instance_data.push_back(static_cast(p % 16)); + instance_data.push_back(static_cast(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(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(); 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(out->surface_width), + static_cast(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(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(out->surface_width), + static_cast(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(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(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; } - diff --git a/src/shader_utils.cpp b/src/shader_utils.cpp new file mode 100644 index 0000000..626ba6d --- /dev/null +++ b/src/shader_utils.cpp @@ -0,0 +1,79 @@ +#include "shader_utils.hpp" +#include +#include +#include + +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 diff --git a/src/shader_utils.hpp b/src/shader_utils.hpp new file mode 100644 index 0000000..31da9c1 --- /dev/null +++ b/src/shader_utils.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include +#include + +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); +} diff --git a/src/state.cpp b/src/state.cpp index 9967b9c..04fd364 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -1,57 +1,36 @@ #include "state.hpp" -#include "ext-session-lock-v1-client-protocol.h" -#include -#include -#include - -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 } } diff --git a/src/state.hpp b/src/state.hpp index 28b6f34..5f404f8 100644 --- a/src/state.hpp +++ b/src/state.hpp @@ -1,55 +1,72 @@ #pragma once - #include - #include -#include #include #include #include #include #include "ext-session-lock-v1-client-protocol.h" #include -#include +#include -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> outputs = std::vector>{}; - + std::vector> 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 particles; + ~LockScreenState(); }; }