913 lines
33 KiB
C++
913 lines
33 KiB
C++
#include <wayland-client.h>
|
|
#include <wayland-egl.h>
|
|
#include <EGL/egl.h>
|
|
#include <GLES2/gl2.h>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
#include "xdg-shell-client-protocol.h"
|
|
#include "font8x8.h"
|
|
|
|
// --- Globals & State ---
|
|
|
|
struct wl_display *display = nullptr;
|
|
struct wl_registry *registry = nullptr;
|
|
struct wl_compositor *compositor = nullptr;
|
|
struct xdg_wm_base *wm_base = nullptr;
|
|
struct wl_seat *seat = nullptr;
|
|
struct wl_pointer *pointer = nullptr;
|
|
|
|
struct wl_surface *surface = nullptr;
|
|
struct xdg_surface *xdg_surface = nullptr;
|
|
struct xdg_toplevel *xdg_toplevel = nullptr;
|
|
struct wl_egl_window *egl_window = nullptr;
|
|
|
|
EGLDisplay egl_display;
|
|
EGLContext egl_context;
|
|
EGLSurface egl_surface;
|
|
|
|
// Text State
|
|
GLuint font_texture;
|
|
GLuint text_program;
|
|
|
|
bool running = true;
|
|
int width = 800;
|
|
int height = 600;
|
|
|
|
// Input state
|
|
double cursor_x = 0;
|
|
double cursor_y = 0;
|
|
bool button_pressed = false;
|
|
|
|
// Time state
|
|
auto start_time = std::chrono::high_resolution_clock::now();
|
|
|
|
// --- Framebuffer State (Multipass) ---
|
|
GLuint fbo[2];
|
|
GLuint fbo_texture[2];
|
|
int ping = 0;
|
|
int pong = 1;
|
|
bool fbo_initialized = false;
|
|
int fbo_width = 0;
|
|
int fbo_height = 0;
|
|
|
|
void init_fbos(int w, int h) {
|
|
if (fbo_initialized && w == fbo_width && h == fbo_height) return;
|
|
|
|
if (fbo_initialized) {
|
|
glDeleteFramebuffers(2, fbo);
|
|
glDeleteTextures(2, fbo_texture);
|
|
}
|
|
|
|
fbo_width = w;
|
|
fbo_height = h;
|
|
|
|
glGenFramebuffers(2, fbo);
|
|
glGenTextures(2, fbo_texture);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]);
|
|
glBindTexture(GL_TEXTURE_2D, fbo_texture[i]);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture[i], 0);
|
|
|
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
|
std::cerr << "Framebuffer " << i << " incomplete!" << std::endl;
|
|
}
|
|
}
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind
|
|
fbo_initialized = true;
|
|
}
|
|
|
|
// Shaders
|
|
struct ShaderProgram {
|
|
GLuint program;
|
|
GLint posAttrib;
|
|
GLint timeUniform;
|
|
GLint resolutionUniform;
|
|
GLint mouseUniform;
|
|
GLint backbufferUniform;
|
|
std::string name;
|
|
float color[3];
|
|
bool is_stateful;
|
|
};
|
|
|
|
std::vector<ShaderProgram> shaders;
|
|
int current_shader_index = 0;
|
|
GLuint sidebar_program; // Simple shader for drawing UI quads
|
|
GLuint copy_program; // Helper for copying FBO to screen
|
|
|
|
// UI Layout
|
|
const int SIDEBAR_WIDTH = 200;
|
|
const int BUTTON_HEIGHT = 60;
|
|
const int BUTTON_MARGIN = 10;
|
|
|
|
// Slider UI
|
|
struct Slider {
|
|
std::string name;
|
|
float* value_ptr;
|
|
float min_val;
|
|
float max_val;
|
|
bool is_dragging;
|
|
};
|
|
|
|
// Mandelbrot Parameters
|
|
float m_exponent = 2.0f;
|
|
float m_z_real = 0.0f;
|
|
float m_z_imag = 0.0f;
|
|
float m_c_real = 0.0f;
|
|
float m_c_imag = 0.0f;
|
|
|
|
std::vector<Slider> mandel_sliders = {
|
|
{"Exp", &m_exponent, 1.0f, 10.0f, false},
|
|
// {"Zr", &m_z_real, -1.0f, 1.0f, false}, // Removing these to save space/simplify logic for Perturbation
|
|
// {"Zi", &m_z_imag, -1.0f, 1.0f, false},
|
|
{"Cr", &m_c_real, -0.1f, 0.1f, false}, // Detail C offset
|
|
{"Ci", &m_c_imag, -0.1f, 0.1f, false}
|
|
};
|
|
|
|
// --- Deep Zoom (Perturbation) State ---
|
|
// Using __float128 (Quad Precision) if available (GCC/Clang usually have it).
|
|
// This gives ~34 decimal digits ($10^34$ zoom).
|
|
// For true "Infinite", we'd need GMP or a custom BigFloat array class.
|
|
// For this demo, __float128 is a massive upgrade over double ($10^15$) and is much faster/safer to implement here.
|
|
typedef __float128 DeepFloat;
|
|
|
|
DeepFloat center_r = -0.5;
|
|
DeepFloat center_i = 0.0;
|
|
DeepFloat zoom_deep = 1.0;
|
|
|
|
// Reference Orbit Data (uploaded to texture)
|
|
const int MAX_REF_ITER = 1024; // Texture width
|
|
std::vector<float> ref_orbit_data(MAX_REF_ITER * 4); // RGBA per step
|
|
GLuint ref_orbit_texture = 0;
|
|
|
|
void calc_reference_orbit() {
|
|
DeepFloat zr = 0.0;
|
|
DeepFloat zi = 0.0;
|
|
DeepFloat cr = center_r;
|
|
DeepFloat ci = center_i;
|
|
|
|
// Upload Z0
|
|
ref_orbit_data[0] = (float)zr;
|
|
ref_orbit_data[1] = (float)zi;
|
|
ref_orbit_data[2] = 0.0f;
|
|
ref_orbit_data[3] = 1.0f;
|
|
|
|
for (int i = 1; i < MAX_REF_ITER; i++) {
|
|
DeepFloat zr2 = zr * zr;
|
|
DeepFloat zi2 = zi * zi;
|
|
DeepFloat two_zr_zi = 2.0 * zr * zi;
|
|
|
|
zr = zr2 - zi2 + cr;
|
|
zi = two_zr_zi + ci;
|
|
|
|
// Encoding for GPU (Safety against 0..1 clamping)
|
|
// Range [-4, 4] -> [0, 1]
|
|
float r_encoded = ((float)zr + 4.0f) / 8.0f;
|
|
float i_encoded = ((float)zi + 4.0f) / 8.0f;
|
|
|
|
ref_orbit_data[i * 4 + 0] = r_encoded;
|
|
ref_orbit_data[i * 4 + 1] = i_encoded;
|
|
ref_orbit_data[i * 4 + 2] = 0.0f;
|
|
ref_orbit_data[i * 4 + 3] = 1.0f;
|
|
|
|
if ((zr2 + zi2) > 4.0) {
|
|
// Continue filling
|
|
}
|
|
}
|
|
|
|
// Upload to Texture
|
|
if (ref_orbit_texture == 0) {
|
|
glGenTextures(1, &ref_orbit_texture);
|
|
glBindTexture(GL_TEXTURE_2D, ref_orbit_texture);
|
|
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);
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, ref_orbit_texture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MAX_REF_ITER, 1, 0, GL_RGBA, GL_FLOAT, ref_orbit_data.data());
|
|
}
|
|
|
|
// Forward declarations
|
|
void render_frame();
|
|
GLuint load_shader_src(const char *source, GLenum type);
|
|
|
|
// --- Font & Text Utils ---
|
|
|
|
void init_font() {
|
|
int tex_w = 128; // 16 chars * 8 px
|
|
int tex_h = 64; // 8 chars * 8 px
|
|
std::vector<GLubyte> pixels(tex_w * tex_h, 0);
|
|
|
|
for (int char_idx = 0; char_idx < 128; ++char_idx) {
|
|
int grid_x = char_idx % 16;
|
|
int grid_y = char_idx / 16;
|
|
|
|
for (int row = 0; row < 8; ++row) {
|
|
unsigned char bits = font8x8_basic[char_idx][row];
|
|
for (int col = 0; col < 8; ++col) {
|
|
// Revert to standard LSB=Right?
|
|
// Wait, if (bits >> col) & 1 IS standard, and I previously FLIPPED it to (bits >> (7-col)).
|
|
// And the user saw MIRRORED text.
|
|
// Then (7-col) WAS mirrored.
|
|
// So 'col' should be correct.
|
|
// Assuming bit 0 is Rightmost pixel?
|
|
// If I use (bits >> col) & 1 -> Col 0 gets Bit 0 (Right).
|
|
// So Right-Bit is drawn on Left.
|
|
// THIS produces Mirroring IF bit 0 is Right.
|
|
// If the user SAW mirroring with (7-col) which puts Bit 7 on Left.
|
|
// Then Bit 7 IS Left.
|
|
// Meaning (7-col) is CORRECT.
|
|
|
|
// Wait.
|
|
// If I saw "AMSALP" (Mirrored String).
|
|
// BUT "Normal Letters" (Upright P).
|
|
// Then the LETTERS are fine. The STRING is mirrored.
|
|
|
|
// IF I revert the letters, they will become Mirrored P ('q').
|
|
// This won't fix the string order.
|
|
|
|
// Let's stick to the plan: Revert bit flips to be safe, BUT also investigate String Order.
|
|
// String order is P L A S M A.
|
|
// Drawn at X=0, X=10...
|
|
// If P is at Right. X-axis is flipped.
|
|
|
|
// Okay, I will revert to `(bits >> col) & 1` and `row`.
|
|
// This is the "default" implementation I found online for font8x8 usually.
|
|
if ((bits >> col) & 1) {
|
|
int px = grid_x * 8 + col;
|
|
int py = grid_y * 8 + row;
|
|
pixels[py * tex_w + px] = 255;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
glGenTextures(1, &font_texture);
|
|
glBindTexture(GL_TEXTURE_2D, font_texture);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tex_w, tex_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels.data());
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
}
|
|
|
|
void init_text_shader() {
|
|
const char* vs =
|
|
"#version 100\n"
|
|
"attribute vec2 position;\n"
|
|
"attribute vec2 texcoord;\n"
|
|
"varying vec2 v_tex;\n"
|
|
"uniform vec2 u_screen_size;\n"
|
|
"void main() {\n"
|
|
" vec2 ndc = (position / u_screen_size) * 2.0 - 1.0;\n"
|
|
" gl_Position = vec4(ndc.x, -ndc.y, 0.0, 1.0);\n"
|
|
" v_tex = texcoord;\n"
|
|
"}";
|
|
const char* fs =
|
|
"#version 100\n"
|
|
"precision mediump float;\n"
|
|
"varying vec2 v_tex;\n"
|
|
"uniform sampler2D u_tex;\n"
|
|
"uniform vec3 u_color;\n"
|
|
"void main() {\n"
|
|
" float alpha = texture2D(u_tex, v_tex).a;\n"
|
|
" if (alpha < 0.5) discard;\n"
|
|
" gl_FragColor = vec4(u_color, 1.0);\n"
|
|
"}";
|
|
|
|
GLuint v = load_shader_src(vs, GL_VERTEX_SHADER);
|
|
GLuint f = load_shader_src(fs, GL_FRAGMENT_SHADER);
|
|
text_program = glCreateProgram();
|
|
glAttachShader(text_program, v);
|
|
glAttachShader(text_program, f);
|
|
glLinkProgram(text_program);
|
|
}
|
|
|
|
void draw_text(const std::string& text, float x, float y, float scale, float r, float g, float b) {
|
|
glUseProgram(text_program);
|
|
GLint posLoc = glGetAttribLocation(text_program, "position");
|
|
GLint texLoc = glGetAttribLocation(text_program, "texcoord");
|
|
GLint scrLoc = glGetUniformLocation(text_program, "u_screen_size");
|
|
GLint colLoc = glGetUniformLocation(text_program, "u_color");
|
|
|
|
glUniform2f(scrLoc, (float)width, (float)height);
|
|
glUniform3f(colLoc, r, g, b);
|
|
|
|
// Bind Font Texture
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, font_texture);
|
|
GLint samplerLoc = glGetUniformLocation(text_program, "u_tex");
|
|
glUniform1i(samplerLoc, 0);
|
|
|
|
std::vector<float> verts;
|
|
float cx = x;
|
|
float cy = y;
|
|
|
|
for (char c : text) {
|
|
if (c < 0 || c > 127) continue;
|
|
int grid_x = c % 16;
|
|
int grid_y = c / 16;
|
|
|
|
float u1 = (float)grid_x / 16.0;
|
|
float v1 = (float)grid_y / 8.0;
|
|
float u2 = u1 + 1.0/16.0;
|
|
float v2 = v1 + 1.0/8.0;
|
|
|
|
float w = 8 * scale;
|
|
float h = 8 * scale;
|
|
|
|
verts.push_back(cx); verts.push_back(cy + h); verts.push_back(u1); verts.push_back(v2);
|
|
verts.push_back(cx + w); verts.push_back(cy + h); verts.push_back(u2); verts.push_back(v2);
|
|
verts.push_back(cx + w); verts.push_back(cy); verts.push_back(u2); verts.push_back(v1);
|
|
|
|
verts.push_back(cx); verts.push_back(cy + h); verts.push_back(u1); verts.push_back(v2);
|
|
verts.push_back(cx + w); verts.push_back(cy); verts.push_back(u2); verts.push_back(v1);
|
|
verts.push_back(cx); verts.push_back(cy); verts.push_back(u1); verts.push_back(v1);
|
|
|
|
cx += w + 2.0f;
|
|
}
|
|
|
|
glEnableVertexAttribArray(posLoc);
|
|
glEnableVertexAttribArray(texLoc);
|
|
glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), verts.data());
|
|
glVertexAttribPointer(texLoc, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), verts.data() + 2);
|
|
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
glDrawArrays(GL_TRIANGLES, 0, verts.size() / 4);
|
|
|
|
glDisable(GL_BLEND);
|
|
}
|
|
|
|
// --- Shader Utilities ---
|
|
|
|
GLuint load_shader_src(const char *source, GLenum type) {
|
|
GLuint shader = glCreateShader(type);
|
|
glShaderSource(shader, 1, &source, nullptr);
|
|
glCompileShader(shader);
|
|
|
|
GLint compiled;
|
|
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
|
|
if (!compiled) {
|
|
char log[512];
|
|
glGetShaderInfoLog(shader, 512, nullptr, log);
|
|
std::cerr << "Shader compile error: " << log << std::endl;
|
|
return 0;
|
|
}
|
|
return shader;
|
|
}
|
|
|
|
GLuint load_shader_file(const char *path, GLenum type) {
|
|
std::ifstream file(path);
|
|
if (!file.is_open()) {
|
|
std::cerr << "Failed to open shader: " << path << std::endl;
|
|
return 0;
|
|
}
|
|
std::stringstream buffer;
|
|
buffer << file.rdbuf();
|
|
std::string source_str = buffer.str();
|
|
return load_shader_src(source_str.c_str(), type);
|
|
}
|
|
|
|
GLuint create_program(const char* vertPath, const char* fragPath) {
|
|
GLuint vert = load_shader_file(vertPath, GL_VERTEX_SHADER);
|
|
GLuint frag = load_shader_file(fragPath, GL_FRAGMENT_SHADER);
|
|
GLuint prog = glCreateProgram();
|
|
glAttachShader(prog, vert);
|
|
glAttachShader(prog, frag);
|
|
glLinkProgram(prog);
|
|
return prog;
|
|
}
|
|
|
|
// --- Input Handling (Pointer) ---
|
|
|
|
static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) {}
|
|
static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {}
|
|
|
|
// Interactive State
|
|
float zoom_level = 1.0f;
|
|
float view_x = -0.5f;
|
|
float view_y = 0.0f;
|
|
bool is_dragging = false;
|
|
|
|
|
|
|
|
static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {
|
|
double old_x = cursor_x;
|
|
double old_y = cursor_y;
|
|
cursor_x = wl_fixed_to_double(sx);
|
|
cursor_y = wl_fixed_to_double(sy);
|
|
|
|
// Slider Logic
|
|
if (shaders[current_shader_index].name == "MANDEL") {
|
|
for (auto& s : mandel_sliders) {
|
|
if (s.is_dragging) {
|
|
float track_x = 10;
|
|
float track_w = SIDEBAR_WIDTH - 20;
|
|
float normalized = (float)(cursor_x - track_x) / track_w;
|
|
if (normalized < 0.0f) normalized = 0.0f;
|
|
if (normalized > 1.0f) normalized = 1.0f;
|
|
*s.value_ptr = s.min_val + normalized * (s.max_val - s.min_val);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_dragging) {
|
|
if (shaders[current_shader_index].name == "MANDEL") {
|
|
// Deep Pan
|
|
double dx = (cursor_x - old_x) / height;
|
|
double dy = (cursor_y - old_y) / height;
|
|
|
|
// Adjust Center (Deep Float)
|
|
// Zoom is zoom_deep
|
|
DeepFloat scale = 1.0 / zoom_deep;
|
|
center_r -= (DeepFloat)dx * scale * 2.0;
|
|
center_i += (DeepFloat)dy * scale * 2.0;
|
|
|
|
} else {
|
|
// Normal Pan
|
|
float dx = (cursor_x - old_x) / height;
|
|
float dy = (cursor_y - old_y) / height;
|
|
view_x -= dx / zoom_level * 2.0;
|
|
view_y += dy / zoom_level * 2.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
|
|
if (button == 0x110) { // Left click
|
|
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
|
|
if (cursor_x < SIDEBAR_WIDTH) {
|
|
// Check Sliders
|
|
bool hit_slider = false;
|
|
if (shaders[current_shader_index].name == "MANDEL") {
|
|
float sy = 490 + 20;
|
|
for (auto& s : mandel_sliders) {
|
|
if (cursor_y >= sy && cursor_y < sy + 20) {
|
|
s.is_dragging = true;
|
|
hit_slider = true;
|
|
}
|
|
sy += 30;
|
|
}
|
|
}
|
|
|
|
if (!hit_slider) {
|
|
int clicked_index = -1;
|
|
for (size_t i = 0; i < shaders.size(); ++i) {
|
|
int y_start = BUTTON_MARGIN + i * (BUTTON_HEIGHT + BUTTON_MARGIN);
|
|
int y_end = y_start + BUTTON_HEIGHT;
|
|
if (cursor_y >= y_start && cursor_y <= y_end && cursor_x >= BUTTON_MARGIN && cursor_x <= SIDEBAR_WIDTH - BUTTON_MARGIN) {
|
|
clicked_index = i;
|
|
break;
|
|
}
|
|
}
|
|
if (clicked_index != -1) {
|
|
current_shader_index = clicked_index;
|
|
for (auto& s : mandel_sliders) s.is_dragging = false;
|
|
|
|
if (shaders[current_shader_index].name == "MANDEL") {
|
|
// Reset Deep State
|
|
center_r = -0.5;
|
|
center_i = 0.0;
|
|
zoom_deep = 1.0;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
is_dragging = true;
|
|
}
|
|
} else {
|
|
is_dragging = false;
|
|
for (auto& s : mandel_sliders) s.is_dragging = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
|
|
if (cursor_x > SIDEBAR_WIDTH) { // Only zoom in main area
|
|
double val = wl_fixed_to_double(value);
|
|
if (shaders[current_shader_index].name == "MANDEL") {
|
|
// Deep Zoom
|
|
if (val < 0) {
|
|
zoom_deep *= 1.1;
|
|
} else if (val > 0) {
|
|
zoom_deep /= 1.1;
|
|
}
|
|
} else {
|
|
if (val < 0) {
|
|
zoom_level *= 1.1f;
|
|
} else if (val > 0) {
|
|
zoom_level /= 1.1f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static const struct wl_pointer_listener pointer_listener = {
|
|
.enter = pointer_handle_enter,
|
|
.leave = pointer_handle_leave,
|
|
.motion = pointer_handle_motion,
|
|
.button = pointer_handle_button,
|
|
.axis = pointer_handle_axis,
|
|
};
|
|
|
|
// --- Seat Listener ---
|
|
|
|
static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {
|
|
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
|
|
pointer = wl_seat_get_pointer(wl_seat);
|
|
wl_pointer_add_listener(pointer, &pointer_listener, nullptr);
|
|
}
|
|
}
|
|
|
|
static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) {}
|
|
|
|
static const struct wl_seat_listener seat_listener = {
|
|
.capabilities = seat_handle_capabilities,
|
|
.name = seat_handle_name,
|
|
};
|
|
|
|
// --- Wayland/XDG Code ---
|
|
|
|
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) { xdg_wm_base_pong(xdg_wm_base, serial); }
|
|
static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_ping };
|
|
|
|
static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
|
}
|
|
static const struct xdg_surface_listener xdg_surface_listener = { .configure = xdg_surface_configure };
|
|
|
|
static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t w, int32_t h, struct wl_array *states) {
|
|
if (w > 0 && h > 0) {
|
|
width = w;
|
|
height = h;
|
|
if (egl_window) wl_egl_window_resize(egl_window, width, height, 0, 0);
|
|
}
|
|
}
|
|
static void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { running = false; }
|
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = xdg_toplevel_configure, .close = xdg_toplevel_close };
|
|
|
|
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
|
compositor = (struct wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 1);
|
|
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
|
wm_base = (struct xdg_wm_base *)wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
|
xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, nullptr);
|
|
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
|
seat = (struct wl_seat *)wl_registry_bind(registry, name, &wl_seat_interface, 1);
|
|
wl_seat_add_listener(seat, &seat_listener, nullptr);
|
|
}
|
|
}
|
|
static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = [](void*, wl_registry*, uint32_t){} };
|
|
|
|
// --- Rendering ---
|
|
|
|
static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t time);
|
|
static const struct wl_callback_listener frame_listener = { .done = frame_handle_done };
|
|
|
|
static void frame_handle_done(void *data, struct wl_callback *callback, uint32_t time) {
|
|
wl_callback_destroy(callback);
|
|
render_frame();
|
|
}
|
|
|
|
void draw_quad(float x, float y, float w, float h, float screen_w, float screen_h, float r, float g, float b) {
|
|
float x1 = (x / screen_w) * 2.0f - 1.0f;
|
|
float y1 = 1.0f - (y / screen_h) * 2.0f;
|
|
float x2 = ((x + w) / screen_w) * 2.0f - 1.0f;
|
|
float y2 = 1.0f - ((y + h) / screen_h) * 2.0f;
|
|
|
|
GLfloat vertices[] = { x1, y1, x1, y2, x2, y2, x2, y2, x2, y1, x1, y1 };
|
|
|
|
GLint posLoc = glGetAttribLocation(sidebar_program, "position");
|
|
GLint colLoc = glGetUniformLocation(sidebar_program, "u_color");
|
|
|
|
glUseProgram(sidebar_program);
|
|
glUniform3f(colLoc, r, g, b);
|
|
glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
|
glEnableVertexAttribArray(posLoc);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
}
|
|
|
|
void render_frame() {
|
|
if (!running) return;
|
|
|
|
// 1. Sidebar BG
|
|
glViewport(0, 0, width, height);
|
|
glUseProgram(sidebar_program);
|
|
draw_quad(0, 0, SIDEBAR_WIDTH, height, width, height, 0.2f, 0.2f, 0.2f);
|
|
|
|
// 2. Buttons
|
|
for (size_t i = 0; i < shaders.size(); ++i) {
|
|
float r = shaders[i].color[0];
|
|
float g = shaders[i].color[1];
|
|
float b = shaders[i].color[2];
|
|
|
|
if ((int)i == current_shader_index) {
|
|
r = std::min(r + 0.3f, 1.0f);
|
|
g = std::min(g + 0.3f, 1.0f);
|
|
b = std::min(b + 0.3f, 1.0f);
|
|
}
|
|
|
|
float by = BUTTON_MARGIN + i * (BUTTON_HEIGHT + BUTTON_MARGIN);
|
|
|
|
// Quad
|
|
glUseProgram(sidebar_program);
|
|
draw_quad(BUTTON_MARGIN, by, SIDEBAR_WIDTH - 2 * BUTTON_MARGIN, BUTTON_HEIGHT, width, height, r, g, b);
|
|
|
|
// Text
|
|
float text_scale = 2.0f;
|
|
float text_h = 8 * text_scale;
|
|
float text_y = by + (BUTTON_HEIGHT - text_h) / 2;
|
|
float text_x = BUTTON_MARGIN + 10;
|
|
draw_text(shaders[i].name, text_x, text_y, text_scale, 1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
// Render Sliders if Mandelbrot
|
|
if (shaders[current_shader_index].name == "MANDEL") {
|
|
float sy = 490 + 20;
|
|
for (const auto& s : mandel_sliders) {
|
|
// Track
|
|
glUseProgram(sidebar_program);
|
|
draw_quad(10, sy + 8, SIDEBAR_WIDTH - 20, 4, width, height, 0.3f, 0.3f, 0.3f);
|
|
|
|
// Knob
|
|
float normalized = (*s.value_ptr - s.min_val) / (s.max_val - s.min_val);
|
|
float kx = 10 + normalized * (SIDEBAR_WIDTH - 20);
|
|
draw_quad(kx - 5, sy, 10, 20, width, height, 0.8f, 0.8f, 0.8f);
|
|
|
|
// Label
|
|
draw_text(s.name, 10, sy - 8, 1.0f, 1.0f, 1.0f, 0.0f); // Yellowish
|
|
|
|
sy += 30;
|
|
}
|
|
}
|
|
|
|
// 3. Render Active Shader
|
|
int view_w = width - SIDEBAR_WIDTH;
|
|
int view_h = height;
|
|
|
|
// Safety check for view dimensions
|
|
if (view_w <= 0) view_w = 1;
|
|
if (view_h <= 0) view_h = 1;
|
|
|
|
init_fbos(view_w, view_h);
|
|
|
|
ShaderProgram &active = shaders[current_shader_index];
|
|
glUseProgram(active.program);
|
|
|
|
auto now = std::chrono::high_resolution_clock::now();
|
|
float time = std::chrono::duration<float>(now - start_time).count();
|
|
|
|
if (active.timeUniform != -1) glUniform1f(active.timeUniform, time);
|
|
if (active.resolutionUniform != -1) glUniform2f(active.resolutionUniform, (float)view_w, (float)view_h);
|
|
|
|
// Mouse relative to viewport
|
|
if (active.mouseUniform != -1) {
|
|
float mx = (float)cursor_x - SIDEBAR_WIDTH;
|
|
float my = (float)height - (float)cursor_y;
|
|
glUniform2f(active.mouseUniform, mx, my);
|
|
}
|
|
|
|
GLint zoomLoc = glGetUniformLocation(active.program, "u_zoom");
|
|
if (zoomLoc != -1) glUniform1f(zoomLoc, zoom_level);
|
|
|
|
GLint centerLoc = glGetUniformLocation(active.program, "u_center");
|
|
if (centerLoc != -1) {
|
|
if (active.name == "MANDEL") {
|
|
// Pass Deep Orbit Texture
|
|
calc_reference_orbit();
|
|
|
|
glActiveTexture(GL_TEXTURE1);
|
|
glBindTexture(GL_TEXTURE_2D, ref_orbit_texture);
|
|
GLint refLoc = glGetUniformLocation(active.program, "u_ref_orbit");
|
|
if (refLoc != -1) glUniform1i(refLoc, 1); // Unit 1
|
|
|
|
// Pass Zoom and Center?
|
|
// We actually don't need Center if we are doing Delta?
|
|
// We need Delta C for the pixel.
|
|
// Wait, standard Delta approach:
|
|
// dc = (uv) / zoom
|
|
// z = 0 + 0 (if starting at 0)
|
|
// But u_ref_orbit contains the center's orbit.
|
|
|
|
// We still need to pass zoom.
|
|
glUniform1f(zoomLoc, (float)zoom_deep);
|
|
// Center is implicit 0,0 relative to reference?
|
|
// No, we still want to visualize.
|
|
|
|
// Actually, for Delta Shader:
|
|
// C_pixel = C_ref + DeltaC
|
|
// C_ref is used on CPU.
|
|
// DeltaC = (FragCoord - CenterScreen) * PixelScale
|
|
|
|
// So we assume u_center is 0,0 in the shader logic because we are tracking C_ref on CPU.
|
|
glUniform2f(centerLoc, 0.0f, 0.0f);
|
|
|
|
} else {
|
|
glUniform2f(centerLoc, view_x, view_y);
|
|
}
|
|
}
|
|
|
|
// Mandelbrot Params
|
|
GLint expLoc = glGetUniformLocation(active.program, "u_exponent");
|
|
if (expLoc != -1) glUniform1f(expLoc, m_exponent);
|
|
|
|
GLint zStartLoc = glGetUniformLocation(active.program, "u_z_start");
|
|
if (zStartLoc != -1) glUniform2f(zStartLoc, m_z_real, m_z_imag);
|
|
|
|
GLint cOffsetLoc = glGetUniformLocation(active.program, "u_c_offset");
|
|
if (cOffsetLoc != -1) glUniform2f(cOffsetLoc, m_c_real, m_c_imag);
|
|
|
|
if (active.is_stateful) {
|
|
// Ping-Pong rendering
|
|
|
|
// Pass 1: Draw to FBO[pong] reading from FBO[ping]
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo[pong]);
|
|
glViewport(0, 0, view_w, view_h);
|
|
|
|
glUseProgram(active.program); // Re-bind to be safe
|
|
|
|
// Update uniforms for FBO pass
|
|
if (active.timeUniform != -1) glUniform1f(active.timeUniform, time);
|
|
if (active.resolutionUniform != -1) glUniform2f(active.resolutionUniform, (float)view_w, (float)view_h);
|
|
if (active.mouseUniform != -1) {
|
|
float mx = (float)cursor_x - SIDEBAR_WIDTH;
|
|
float my = (float)height - (float)cursor_y;
|
|
glUniform2f(active.mouseUniform, mx, my);
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, fbo_texture[ping]);
|
|
if (active.backbufferUniform != -1) glUniform1i(active.backbufferUniform, 0);
|
|
|
|
GLfloat vertices[] = { -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1 };
|
|
glVertexAttribPointer(active.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
|
glEnableVertexAttribArray(active.posAttrib);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
|
|
// Swap ping/pong
|
|
int temp = ping;
|
|
ping = pong;
|
|
pong = temp;
|
|
|
|
// Pass 2: Copy to Screen
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glViewport(SIDEBAR_WIDTH, 0, view_w, view_h);
|
|
|
|
glUseProgram(copy_program);
|
|
GLint pos = glGetAttribLocation(copy_program, "position");
|
|
GLint tex = glGetUniformLocation(copy_program, "u_tex");
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D, fbo_texture[ping]);
|
|
glUniform1i(tex, 0);
|
|
|
|
glVertexAttribPointer(pos, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
|
glEnableVertexAttribArray(pos);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
|
|
} else {
|
|
// Stateless Rendering
|
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
glViewport(SIDEBAR_WIDTH, 0, view_w, view_h);
|
|
|
|
GLfloat vertices[] = { -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1 };
|
|
glVertexAttribPointer(active.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, vertices);
|
|
glEnableVertexAttribArray(active.posAttrib);
|
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
}
|
|
|
|
struct wl_callback *callback = wl_surface_frame(surface);
|
|
wl_callback_add_listener(callback, &frame_listener, nullptr);
|
|
|
|
eglSwapBuffers(egl_display, egl_surface);
|
|
}
|
|
|
|
// --- Init & Main ---
|
|
|
|
void init_copy_shader() {
|
|
const char* vs =
|
|
"#version 100\n"
|
|
"attribute vec2 position;\n"
|
|
"varying vec2 uv;\n"
|
|
"void main(){\n"
|
|
" uv = position * 0.5 + 0.5;\n"
|
|
" gl_Position = vec4(position, 0.0, 1.0);\n"
|
|
"}\n";
|
|
const char* fs =
|
|
"#version 100\n"
|
|
"precision mediump float;\n"
|
|
"varying vec2 uv;\n"
|
|
"uniform sampler2D u_tex;\n"
|
|
"void main(){\n"
|
|
" gl_FragColor = texture2D(u_tex, uv);\n"
|
|
"}\n";
|
|
|
|
GLuint v = load_shader_src(vs, GL_VERTEX_SHADER);
|
|
GLuint f = load_shader_src(fs, GL_FRAGMENT_SHADER);
|
|
copy_program = glCreateProgram();
|
|
glAttachShader(copy_program, v);
|
|
glAttachShader(copy_program, f);
|
|
glLinkProgram(copy_program);
|
|
}
|
|
|
|
void init_shaders() {
|
|
const char* sidebar_vert =
|
|
"#version 100\n"
|
|
"attribute vec2 position;\n"
|
|
"void main() { gl_Position = vec4(position, 0.0, 1.0); }";
|
|
const char* sidebar_frag =
|
|
"#version 100\n"
|
|
"precision mediump float;\n"
|
|
"uniform vec3 u_color;\n"
|
|
"void main() { gl_FragColor = vec4(u_color, 1.0); }";
|
|
|
|
GLuint sv = load_shader_src(sidebar_vert, GL_VERTEX_SHADER);
|
|
GLuint sf = load_shader_src(sidebar_frag, GL_FRAGMENT_SHADER);
|
|
sidebar_program = glCreateProgram();
|
|
glAttachShader(sidebar_program, sv);
|
|
glAttachShader(sidebar_program, sf);
|
|
glLinkProgram(sidebar_program);
|
|
|
|
init_copy_shader();
|
|
|
|
auto add_shader = [&](const char* name, const char* fpath, float r, float g, float b, bool stateful) {
|
|
GLuint prog = create_program("shaders/vert.glsl", fpath);
|
|
if (prog) {
|
|
ShaderProgram sp;
|
|
sp.program = prog;
|
|
sp.name = name;
|
|
sp.posAttrib = glGetAttribLocation(prog, "position");
|
|
sp.timeUniform = glGetUniformLocation(prog, "u_time");
|
|
sp.resolutionUniform = glGetUniformLocation(prog, "u_resolution");
|
|
sp.mouseUniform = glGetUniformLocation(prog, "u_mouse");
|
|
sp.backbufferUniform = glGetUniformLocation(prog, "u_backbuffer");
|
|
sp.color[0] = r; sp.color[1] = g; sp.color[2] = b;
|
|
sp.is_stateful = stateful;
|
|
shaders.push_back(sp);
|
|
}
|
|
};
|
|
|
|
add_shader("PLASMA", "shaders/plasma.frag", 0.8f, 0.2f, 0.2f, false);
|
|
add_shader("TUNNEL", "shaders/tunnel.frag", 0.2f, 0.8f, 0.2f, false);
|
|
add_shader("FRACTAL", "shaders/fractal.frag", 0.2f, 0.2f, 0.8f, false);
|
|
add_shader("MANDEL", "shaders/mandelbrot.frag", 0.5f, 0.0f, 0.5f, false);
|
|
add_shader("DBL PEND", "shaders/pendulum.frag", 0.7f, 0.7f, 0.1f, false);
|
|
add_shader("REACTION", "shaders/reaction.frag", 0.0f, 0.8f, 0.8f, true);
|
|
add_shader("FLUID", "shaders/fluid.frag", 0.2f, 0.3f, 0.9f, true);
|
|
}
|
|
|
|
int main() {
|
|
display = wl_display_connect(nullptr);
|
|
if (!display) return 1;
|
|
registry = wl_display_get_registry(display);
|
|
wl_registry_add_listener(registry, ®istry_listener, nullptr);
|
|
wl_display_roundtrip(display);
|
|
|
|
egl_display = eglGetDisplay((EGLNativeDisplayType)display);
|
|
eglInitialize(egl_display, nullptr, nullptr);
|
|
EGLint attribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE };
|
|
EGLConfig config; EGLint num; eglChooseConfig(egl_display, attribs, &config, 1, &num);
|
|
EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
|
|
egl_context = eglCreateContext(egl_display, config, EGL_NO_CONTEXT, ctx_attribs);
|
|
|
|
surface = wl_compositor_create_surface(compositor);
|
|
xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
|
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, nullptr);
|
|
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
|
|
xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, nullptr);
|
|
xdg_toplevel_set_title(xdg_toplevel, "Shader Studio");
|
|
wl_surface_commit(surface);
|
|
wl_display_roundtrip(display);
|
|
|
|
egl_window = wl_egl_window_create(surface, width, height);
|
|
egl_surface = eglCreateWindowSurface(egl_display, config, (EGLNativeWindowType)egl_window, nullptr);
|
|
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
|
|
|
// Initialize Shaders & Font
|
|
init_font();
|
|
init_text_shader();
|
|
init_shaders();
|
|
|
|
render_frame();
|
|
|
|
while (running && wl_display_dispatch(display) != -1) {}
|
|
|
|
return 0;
|
|
}
|