diff --git a/Makefile b/Makefile index 150d69b..8c34dbf 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,7 @@ SHADERTARGET = $(shell readlink -m "$(DESTDIR)$(SHADERDIR)") install: install -Dm755 glava $(EXECTARGET) install -d $(SHADERTARGET) - cp -Rv shaders/* $(SHADERTARGET) + shopt -s extglob && cp -Rv shaders/!(test|test_rc.glsl) $(SHADERTARGET) .PHONY: uninstall uninstall: diff --git a/glava.c b/glava.c index 1d8cf20..e5c7542 100644 --- a/glava.c +++ b/glava.c @@ -218,6 +218,9 @@ static struct option p_opts[] = { {"backend", required_argument, 0, 'b'}, {"stdin", optional_argument, 0, 'i'}, {"version", no_argument, 0, 'V'}, + #ifdef GLAVA_DEBUG + {"run-tests", no_argument, 0, 'T'}, + #endif {0, 0, 0, 0 } }; @@ -295,6 +298,12 @@ int main(int argc, char** argv) { exit(EXIT_FAILURE); } } + #ifdef GLAVA_DEBUG + case 'T': { + entry = "test_rc.glsl"; + rd_enable_test_mode(); + } + #endif } } @@ -387,16 +396,32 @@ int main(int argc, char** argv) { audio.modified = false; /* set this flag to false until the next time we read */ } pthread_mutex_unlock(&audio.mutex); + + bool ret = rd_update(rd, lb, rb, rd->bufsize_request, modified); - if (!rd_update(rd, lb, rb, rd->bufsize_request, modified)) { + if (!ret) { /* Sleep for 50ms and then attempt to render again */ struct timespec tv = { .tv_sec = 0, .tv_nsec = 50 * 1000000 }; nanosleep(&tv, NULL); } + #ifdef GLAVA_DEBUG + if (ret && rd_get_test_mode()) + break; + #endif } + #ifdef GLAVA_DEBUG + if (rd_get_test_mode()) { + if (rd_test_evaluate(rd)) { + fprintf(stderr, "Test results did not match expected output\n"); + fflush(stderr); + exit(EXIT_FAILURE); + } + } + #endif + audio.terminate = 1; int return_status; if ((return_status = pthread_join(thread, NULL))) { diff --git a/glx_wcb.c b/glx_wcb.c index 01feaac..e3b1a5c 100644 --- a/glx_wcb.c +++ b/glx_wcb.c @@ -162,6 +162,11 @@ __GLXextFuncPtr (*glXGetProcAddressARB) (const GLubyte *); void (*glXSwapBuffers) (Display* dpy, GLXDrawable drawable); void (*glXDestroyContext) (Display* dpy, GLXContext ctx); Bool (*glXQueryVersion) (Display* dpy, int* major, int* minor); +#ifdef GLAVA_DEBUG +GLXPixmap (*glXCreateGLXPixmap) (Display* dpy, XVisualInfo* vis, Pixmap pixmap); +static Pixmap test_pixmap; +static GLXPixmap test_glxpm; +#endif extern struct gl_wcb wcb_glx; @@ -227,6 +232,9 @@ static void init(void) { resolve(glXSwapBuffers); resolve(glXDestroyContext); resolve(glXQueryVersion); + #ifdef GLAVA_DEBUG + resolve(glXCreateGLXPixmap); + #endif intern(_MOTIF_WM_HINTS, false); intern(WM_DELETE_WINDOW, true); @@ -487,10 +495,20 @@ static void* create_and_bind(const char* name, const char* class, } XSync(display, False); - - glXMakeCurrent(display, w->w, w->context); - gladLoadGL(); + #ifdef GLAVA_DEBUG + if (rd_get_test_mode()) { + test_pixmap = XCreatePixmap(display, w->w, d, h, + DefaultDepth(display, DefaultScreen(display))); + test_glxpm = glXCreateGLXPixmap(display, vi, test_pixmap); + glXMakeCurrent(display, test_glxpm, w->context); + } else + glXMakeCurrent(display, w->w, w->context); + #else + glXMakeCurrent(display, w->w, w->context); + #endif + gladLoadGL(); + GLXDrawable drawable = glXGetCurrentDrawable(); if (glXSwapIntervalEXT) glXSwapIntervalEXT(display, drawable, swap); @@ -538,6 +556,10 @@ static void set_geometry(struct glxwin* w, int x, int y, int d, int h) { } static void set_visible(struct glxwin* w, bool visible) { + #ifdef GLAVA_DEBUG + if (rd_get_test_mode()) + return; + #endif if (visible) { XMapWindow(display, w->w); switch (w->override_state) { @@ -553,6 +575,10 @@ static void set_visible(struct glxwin* w, bool visible) { static bool should_close (struct glxwin* w) { return w->should_close; } static bool bg_changed (struct glxwin* w) { return w->bg_changed; } static bool should_render(struct glxwin* w) { + #ifdef GLAVA_DEBUG + if (rd_get_test_mode()) + return true; + #endif /* For nearly all window managers, windows are 'minimized' by unmapping parent windows. VisibilityNotify events are not sent in these instances, so we have to read window attributes to see if our window isn't viewable. */ @@ -563,7 +589,14 @@ static bool should_render(struct glxwin* w) { } static void swap_buffers(struct glxwin* w) { + #ifdef GLAVA_DEBUG + if (rd_get_test_mode()) + glXSwapBuffers(display, test_glxpm); + else + glXSwapBuffers(display, w->w); + #else glXSwapBuffers(display, w->w); + #endif process_events(w); } diff --git a/render.c b/render.c index 2e06a6f..4e67795 100644 --- a/render.c +++ b/render.c @@ -139,9 +139,32 @@ struct gl_data { float* interpolate_buf[6]; int geometry[4]; int stdin_type; + #ifdef GLAVA_DEBUG + struct { + float r, g, b, a; + } test_eval_color; + bool debug_verbose; + #endif }; +#ifdef GLAVA_DEBUG +static bool test_mode = false; +static struct gl_sfbo test_sfbo = { + .name = "test", + .shader = 0, + .indirect = false, + .nativeonly = false, + .binds = NULL, + .binds_sz = 0 +}; +void rd_enable_test_mode(void) { + test_mode = true; +} +bool rd_get_test_mode(void) { + return test_mode; +} +#endif /* load shader file */ static GLuint shaderload(const char* rpath, @@ -258,8 +281,10 @@ static GLuint shaderload(const char* rpath, fprintf(stderr, "Shader compilation failed for '%s':\n", path); fwrite(ebuf, sizeof(GLchar), ilen - 1, stderr); #ifdef GLAVA_DEBUG - fprintf(stderr, "Processed shader source for '%s':\n", path); - fwrite(buf, sizeof(GLchar), sl, stderr); + if (gl->debug_verbose) { + fprintf(stderr, "Processed shader source for '%s':\n", path); + fwrite(buf, sizeof(GLchar), sl, stderr); + } #endif free_ebuf: @@ -732,7 +757,7 @@ struct renderer* rd_new(const char** paths, const char* entry, const char** requests, const char* force_backend, int stdin_type, bool auto_desktop, bool verbose) { - + xwin_wait_for_wm(); renderer* r = malloc(sizeof(struct renderer)); @@ -780,7 +805,11 @@ struct renderer* rd_new(const char** paths, const char* entry, .geometry = { 0, 0, 500, 400 }, .clear_color = { 0.0F, 0.0F, 0.0F, 0.0F }, .clickthrough = false, - .stdin_type = stdin_type + .stdin_type = stdin_type, + #ifdef GLAVA_DEBUG + .test_eval_color = { 0.0F, 0.0F, 0.0F, 0.0F }, + .debug_verbose = verbose + #endif }; bool forced = force_backend != NULL; @@ -898,6 +927,23 @@ struct renderer* rd_new(const char** paths, const char* entry, } }) }, + #ifdef GLAVA_DEBUG + { + .name = "settesteval", .fmt = "s", + .handler = RHANDLER(name, args, { + float* results[] = { + &gl->test_eval_color.r, + &gl->test_eval_color.g, + &gl->test_eval_color.b, + &gl->test_eval_color.a + }; + if (!ext_parse_color((char*) args[0], 2, results)) { + fprintf(stderr, "Invalid value for `setbg` request: '%s'\n", (char*) args[0]); + exit(EXIT_FAILURE); + } + }) + }, + #endif { .name = "setbgf", .fmt = "ffff", .handler = RHANDLER(name, args, { @@ -1357,6 +1403,14 @@ struct renderer* rd_new(const char** paths, const char* entry, gl->stages = stages; gl->stages_sz = count; + + #ifdef GLAVA_DEBUG + { + int w, h; + gl->wcb->get_fbsize(gl->w, &w, &h); + setup_sfbo(&test_sfbo, w, h); + } + #endif { struct gl_sfbo* final = NULL; @@ -1504,6 +1558,9 @@ bool rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi setup_sfbo(&gl->stages[t], ww, wh); } } + #ifdef GLAVA_DEBUG + setup_sfbo(&test_sfbo, ww, wh); + #endif } /* Resize and grab new background data if needed */ @@ -1631,6 +1688,12 @@ bool rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi if (current->indirect) glBindFramebuffer(GL_FRAMEBUFFER, current->fbo); + #ifdef GLAVA_DEBUG + if (!current->indirect && test_mode) { + glBindFramebuffer(GL_FRAMEBUFFER, test_sfbo.fbo); + } + #endif + glClear(GL_COLOR_BUFFER_BIT); if (!current->indirect && gl->copy_desktop) { @@ -1910,6 +1973,43 @@ bool rd_update(struct renderer* r, float* lb, float* rb, size_t bsz, bool modifi return true; } +#ifdef GLAVA_DEBUG +bool rd_test_evaluate(struct renderer* r) { + int w, h; + struct gl_data* gl = r->gl; + gl->wcb->get_fbsize(gl->w, &w, &h); + printf("Reading pixels from final framebuffer (%dx%d)\n", w, h); + float margin = 1.0 / (255.0F * 2.0F); + float eval[4] = { + gl->test_eval_color.r, + gl->test_eval_color.g, + gl->test_eval_color.b, + gl->test_eval_color.a + }; + bool err = false; + for (int x = 0; x < w; ++x) { + for (int y = 0; y < h; ++y) { + float ret[4]; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_FLOAT, &ret); + if (ret[0] < eval[0] - margin || ret[0] > eval[0] + margin || + ret[1] < eval[1] - margin || ret[1] > eval[1] + margin || + ret[2] < eval[2] - margin || ret[2] > eval[2] + margin || + ret[3] < eval[3] - margin || ret[3] > eval[3] + margin) { + fprintf(stderr, "px (%d,%d) failed test, (%f,%f,%f,%f)" + " is not within margins for (%f,%f,%f,%f)\n", + x, y, + (double) ret[0], (double) ret[1], (double) ret[2], (double) ret[3], + (double) eval[0], (double) eval[1], (double) eval[2], (double) eval[3]); + err = true; + goto end_test; + } + } + } +end_test: + return err; +} +#endif + void* rd_get_impl_window (struct renderer* r) { return r->gl->w; } struct gl_wcb* rd_get_wcb (struct renderer* r) { return r->gl->wcb; } diff --git a/render.h b/render.h index a8a4f53..dacf00f 100644 --- a/render.h +++ b/render.h @@ -24,6 +24,12 @@ typedef struct renderer { struct gl_data* gl; } renderer; +#ifdef GLAVA_DEBUG +void rd_enable_test_mode(void); +bool rd_get_test_mode (void); +bool rd_test_evaluate (struct renderer*); +#endif + struct renderer* rd_new (const char** paths, const char* entry, const char** requests, const char* force_backend, int stdin_type, bool auto_desktop, diff --git a/shaders/test/1.frag b/shaders/test/1.frag new file mode 100644 index 0000000..5a7bd63 --- /dev/null +++ b/shaders/test/1.frag @@ -0,0 +1,33 @@ +/* Request transforms and basic uniforms to assert nothing here breaks */ + +#include ":util/smooth.glsl" + +in vec4 gl_FragCoord; + +#request uniform "screen" screen +uniform ivec2 screen; + +#request uniform "audio_sz" audio_sz +uniform int audio_sz; + +#request uniform "audio_l" audio_l +#request transform audio_l "window" +#request transform audio_l "fft" +#request transform audio_l "gravity" +#request transform audio_l "avg" +uniform sampler1D audio_l; + +#request uniform "audio_r" audio_r +#request transform audio_r "window" +#request transform audio_r "fft" +#request transform audio_r "gravity" +#request transform audio_r "avg" +uniform sampler1D audio_r; + +out vec4 fragment; + +void main() { + float dummy_result0 = smooth_audio(audio_l, audio_sz, gl_FragCoord.x / float(screen.x)); + float dummy_result1 = smooth_audio(audio_r, audio_sz, gl_FragCoord.x / float(screen.x)); + fragment = vec4(1.0, 0, 0, float(1) / float(3)); +} diff --git a/shaders/test/2.frag b/shaders/test/2.frag new file mode 100644 index 0000000..85498f8 --- /dev/null +++ b/shaders/test/2.frag @@ -0,0 +1,12 @@ +/* Pass the initial results to a dummy shader to assert that linking works correctly */ + +in vec4 gl_FragCoord; + +#request uniform "prev" tex +uniform sampler2D tex; /* screen texture */ + +out vec4 fragment; /* output */ + +void main() { + fragment = texelFetch(tex, ivec2(gl_FragCoord.x, gl_FragCoord.y), 0); +} diff --git a/shaders/test/3.frag b/shaders/test/3.frag new file mode 100644 index 0000000..d5db2d2 --- /dev/null +++ b/shaders/test/3.frag @@ -0,0 +1,2 @@ +/* Assert that the premultiply step works */ +#include ":util/premultiply.frag" diff --git a/shaders/test_rc.glsl b/shaders/test_rc.glsl new file mode 100644 index 0000000..f352d29 --- /dev/null +++ b/shaders/test_rc.glsl @@ -0,0 +1,27 @@ +#request mod test +#request setfloating false +#request setdecorated true +#request setfocused false +#request setmaximized false +#request setopacity "native" +#request setmirror false +#request setversion 3 3 +#request setshaderversion 330 +#request settitle "GLava" +#request setgeometry 0 0 640 640 +#request setbg 00000000 +#request setxwintype "desktop" +#request setclickthrough false +#request setsource "auto" +#request setswap 0 +#request setinterpolate true +#request setframerate 0 +#request setfullscreencheck false +#request setprintframes true +#request setsamplesize 1024/ +#request setbufsize 4096 +#request setsamplerate 22050 +#request setforcegeometry false +#request setforceraised false +#request setbufscale 1 +#request settesteval 55000055