diff --git a/include/sway/commands.h b/include/sway/commands.h index 68b316b44..94e07be12 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -179,6 +179,7 @@ sway_cmd cmd_scratchpad; sway_cmd cmd_seamless_mouse; sway_cmd cmd_set; sway_cmd cmd_shortcuts_inhibitor; +sway_cmd cmd_shader; sway_cmd cmd_shadow_blur_radius; sway_cmd cmd_shadow_color; sway_cmd cmd_shadows; diff --git a/include/sway/config.h b/include/sway/config.h index 77df25685..c70952012 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -470,6 +470,12 @@ enum xwayland_mode { XWAYLAND_MODE_IMMEDIATE, }; +struct foreign_shader_compile_target { + char *frag; // frag shader file path + char *vert; // vert shader file path + char *label; // shader label +}; + /** * The configuration struct. The result of loading a config file. */ @@ -487,6 +493,8 @@ struct sway_config { bool shadows_on_csd_enabled; int shadow_blur_sigma; float shadow_color[4]; + // foreign_shader_compile_target *, used by fx_rederer during initialization + list_t *foreign_shader_compile_queue; char *swaynag_command; struct swaynag_instance swaynag_config_errors; @@ -716,6 +724,8 @@ void free_switch_binding(struct sway_switch_binding *binding); void free_gesture_binding(struct sway_gesture_binding *binding); +void free_foreign_shader_compile_target(struct foreign_shader_compile_target *target); + void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding); void load_swaybar(struct bar_config *bar); diff --git a/include/sway/desktop/fx_renderer.h b/include/sway/desktop/fx_renderer.h index 8f3aafe24..94c900963 100644 --- a/include/sway/desktop/fx_renderer.h +++ b/include/sway/desktop/fx_renderer.h @@ -4,6 +4,7 @@ #include #include #include +#include "list.h" enum corner_location { ALL, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, NONE }; @@ -16,6 +17,15 @@ struct decoration_data { bool has_titlebar; }; +// TODO: extra params in a list +struct foreign_shader { + char *label; + GLuint program; + GLint tex; + GLint pos_attrib; + GLint tex_attrib; +}; + struct gles2_tex_shader { GLuint program; GLint proj; @@ -97,6 +107,17 @@ struct fx_renderer { GLint corner_radius; } box_shadow; + struct { + GLuint program; + GLint proj; + GLint tex; + GLint pos_attrib; + GLint tex_attrib; + GLint position; + } invert; + + list_t *foreign; // struct foreign_shader* + struct gles2_tex_shader tex_rgba; struct gles2_tex_shader tex_rgbx; struct gles2_tex_shader tex_ext; diff --git a/sway/commands.c b/sway/commands.c index f7312dc70..614f05a2f 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -89,6 +89,7 @@ static const struct cmd_handler handlers[] = { { "popup_during_fullscreen", cmd_popup_during_fullscreen }, { "seat", cmd_seat }, { "set", cmd_set }, + { "shader", cmd_shader }, { "shadow_blur_radius", cmd_shadow_blur_radius }, { "shadow_color", cmd_shadow_color }, { "shadows", cmd_shadows }, diff --git a/sway/commands/shader.c b/sway/commands/shader.c new file mode 100644 index 000000000..86d9e43ea --- /dev/null +++ b/sway/commands/shader.c @@ -0,0 +1,92 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include "sway/commands.h" +#include "log.h" +#include "stringop.h" + +struct cmd_results *stat_file(const char *path) { + // FIXME: ~ paths don't work + FILE *f = fopen(path, "r"); + if (!f) { + return cmd_results_new(CMD_FAILURE, "Failed to open custom shader file: %s", path); + } + struct stat statbuf; + if (fstat(fileno(f), &statbuf) < 0) { + fclose(f); + return cmd_results_new(CMD_FAILURE, "Failed to access custom shader file: %s", path); + } + + fclose(f); + return NULL; +} + +struct cmd_results *set(const char *shader) { + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *load(char *label, char *vert_path, char *frag_path) { + struct cmd_results *result = cmd_results_new(CMD_SUCCESS, NULL); + + // FIXME: erorr handling + if (vert_path != NULL) { + expand_path(&frag_path); + stat_file(vert_path); + } + stat_file(frag_path); + + struct foreign_shader_compile_target *tgt = + calloc(1, sizeof(struct foreign_shader_compile_target)); + if (!tgt) { + free(label); + if (vert_path != NULL) + free(vert_path); + free(frag_path); + return cmd_results_new(CMD_FAILURE, "Allocation error"); + } + tgt->label = strdup(label); + if (vert_path != NULL) + tgt->vert = strdup(vert_path); + tgt->frag = strdup(frag_path); + // FIXME: free pointers! + + list_add(config->foreign_shader_compile_queue, tgt); + return cmd_results_new(CMD_SUCCESS, NULL); +} + +struct cmd_results *param(char *shader, char *param) { + // FIXME: finish + return cmd_results_new(CMD_SUCCESS, NULL); +} + +// FIXME: passing two shader commands ruins everything +struct cmd_results *cmd_shader(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "shader", EXPECTED_AT_LEAST, 1))) { + return error; + } + + /* struct sway_container *con = config->handler_context.container; */ + /* if (con == NULL) { */ + /* return cmd_results_new(CMD_FAILURE, "No current container"); */ + /* } */ + + if (argc == 2) { + // Set shader as default + return cmd_results_new(CMD_SUCCESS, NULL); + } + + // FIXME: quotes and stuff + if (!strcmp(argv[0], "load") && (argc == 3 || argc == 4)) { + char *vert = argc == 4 ? argv[2] : NULL; + char *frag = argc == 4 ? argv[3] : argv[2]; + if (expand_path(&frag) && (!vert || expand_path(&vert))) + return load(argv[1], vert, frag); + } else if (!strcmp(argv[0], "param")) { + return param(argv[1], NULL); + } + + return cmd_results_new(CMD_INVALID, + "Expected: load [vert] |param", argv[0]); +} diff --git a/sway/config.c b/sway/config.c index 54bddda23..930791366 100644 --- a/sway/config.c +++ b/sway/config.c @@ -157,6 +157,13 @@ void free_config(struct sway_config *config) { } list_free(config->criteria); } + if (config->foreign_shader_compile_queue) { + for (int i = 0; i < config->criteria->length; ++i) { + free_foreign_shader_compile_target( + config->foreign_shader_compile_queue->items[0]); + } + list_free(config->criteria); + } list_free(config->no_focus); list_free(config->active_bar_modifiers); list_free_items_and_destroy(config->config_chain); @@ -343,6 +350,7 @@ static void config_defaults(struct sway_config *config) { config->shadows_on_csd_enabled = false; config->shadow_blur_sigma = 20.0f; color_to_rgba(config->shadow_color, 0x0000007F); + if (!(config->foreign_shader_compile_queue = create_list())) goto cleanup; // The keysym to keycode translation struct xkb_rule_names rules = {0}; @@ -927,6 +935,8 @@ bool read_config(FILE *file, struct sway_config *config, config->current_config_line_number = 0; config->current_config_line = NULL; + /* struct foreign_shader_compile_target *target = config->foreign_shader_compile_queue->items[0]; */ + /* sway_log(SWAY_ERROR, "found %s %s at %p", target->label, target->frag, config->foreign_shader_compile_queue->items[0]); */ return success; } diff --git a/sway/desktop/foreign_shader.c b/sway/desktop/foreign_shader.c new file mode 100644 index 000000000..b71d34f0e --- /dev/null +++ b/sway/desktop/foreign_shader.c @@ -0,0 +1,8 @@ +#include "sway/config.h" + +void free_foreign_shader_compile_target(struct foreign_shader_compile_target *target) { + free(target->frag); + if (target->vert) + free(target->vert); + free(target); +} diff --git a/sway/desktop/fx_renderer.c b/sway/desktop/fx_renderer.c index 03645b0f7..163272a03 100644 --- a/sway/desktop/fx_renderer.c +++ b/sway/desktop/fx_renderer.c @@ -3,6 +3,7 @@ // TODO: add push / pop_gles2_debug(renderer)? +#include #define _POSIX_C_SOURCE 200809L #include #include @@ -19,6 +20,7 @@ // shaders #include "common_vert_src.h" +#include "invert_frag_src.h" #include "quad_frag_src.h" #include "quad_round_frag_src.h" #include "quad_round_tl_frag_src.h" @@ -216,6 +218,58 @@ static void load_gl_proc(void *proc_ptr, const char *name) { *(void **)proc_ptr = proc; } +void compile_foreign_shader(struct fx_renderer *renderer, + struct foreign_shader_compile_target *target) { +#define _fail_condition(condition, message, ...) \ + if (condition) { \ + sway_log(SWAY_ERROR, message, ##__VA_ARGS__);\ + goto cleanup; \ + } + + char *src = NULL; + FILE *f = NULL; + struct foreign_shader *shader = NULL; + // TODO: remove this + _fail_condition(!target, "Unable to compile null path %s", target->label); + + // Read file + sway_log(SWAY_ERROR, "Compiling %s", target->label); + f = fopen(target->frag, "r"); + _fail_condition(!f, "Failed to open foreign shader file: %s", target->frag); + struct stat statbuf; + _fail_condition(fstat(fileno(f), &statbuf) < 0, + "Failed to stat foreign shader file: %s", target->frag); + size_t num_bytes = statbuf.st_size; + src = calloc(num_bytes + 1, sizeof(char)); + _fail_condition(src == NULL, + "Unable to allocate string buffer during compilation of %s", target->frag); + size_t read_bytes = fread(src, sizeof(char), num_bytes, f); + _fail_condition(read_bytes < num_bytes || ferror(f), + "Failed to read foreign shader file: %s", target->frag); + + // Compile shader + shader = malloc(sizeof(struct foreign_shader)); + _fail_condition(shader == NULL, "Allocation error during compilation of %s", target->frag); + GLuint prog = link_program(common_vert_src, src); + _fail_condition(prog, "Failed to compile foreign shader: %s", target->frag); + sway_log(SWAY_ERROR, "Finished %s", target->label); + + shader->program = prog; + shader->tex = glGetUniformLocation(prog, "tex"); + shader->pos_attrib = glGetAttribLocation(prog, "pos"); + shader->tex_attrib = glGetAttribLocation(prog, "texcoord"); + list_add(renderer->shaders.foreign, &shader); + +cleanup: + if (f) + fclose(f); + if (src) + free(src); + if (shader) + free(shader); + free_foreign_shader_compile_target(target); +} + struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { struct fx_renderer *renderer = calloc(1, sizeof(struct fx_renderer)); if (renderer == NULL) { @@ -279,6 +333,16 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { goto error; } + prog = link_program(common_vert_src, invert_frag_src); + renderer->shaders.invert.program = prog; + if (!renderer->shaders.invert.program) { + goto error; + } + renderer->shaders.invert.proj = glGetUniformLocation(prog, "proj"); + renderer->shaders.invert.tex = glGetUniformLocation(prog, "tex"); + renderer->shaders.invert.pos_attrib = glGetAttribLocation(prog, "pos"); + renderer->shaders.invert.tex_attrib = glGetAttribLocation(prog, "texcoord"); + // Border corner shader prog = link_program(common_vert_src, corner_frag_src); renderer->shaders.corner.program = prog; @@ -325,6 +389,11 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { goto error; } + if (!(renderer->shaders.foreign = create_list())) { + sway_log(SWAY_ERROR, "Failed to allocate foreign shader list"); + goto error; + } + if (!eglMakeCurrent(wlr_egl_get_display(renderer->egl), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { sway_log(SWAY_ERROR, "GLES2 RENDERER: Could not unset current EGL"); @@ -336,6 +405,7 @@ struct fx_renderer *fx_renderer_create(struct wlr_egl *egl) { error: glDeleteProgram(renderer->shaders.quad.program); + glDeleteProgram(renderer->shaders.invert.program); glDeleteProgram(renderer->shaders.rounded_quad.program); glDeleteProgram(renderer->shaders.rounded_tl_quad.program); glDeleteProgram(renderer->shaders.rounded_tr_quad.program); @@ -373,11 +443,26 @@ void fx_renderer_begin(struct fx_renderer *renderer, uint32_t width, uint32_t he matrix_projection(renderer->projection, width, height, WL_OUTPUT_TRANSFORM_FLIPPED_180); + // Compile foreign shaders + clock_t begin = clock(); + int count = 0; + for (int i = config->foreign_shader_compile_queue->length - 1; i >= 0; --i) { + struct foreign_shader_compile_target *target = + config->foreign_shader_compile_queue->items[i]; + compile_foreign_shader(renderer, target); + list_del(config->foreign_shader_compile_queue, i); + ++count; + } + clock_t end = clock(); + double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; + sway_log(SWAY_ERROR, "%i foreign shaders compiled in %f", count, time_spent); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } void fx_renderer_end() { // TODO + // FIXME: free foreign shader list } void fx_renderer_clear(const float color[static 4]) { @@ -395,6 +480,81 @@ void fx_renderer_scissor(struct wlr_box *box) { } } +/** + * Applies a custom shader to a wlr_texture. + * Returns handle of resulting texture. + */ +GLuint fx_apply_shader(struct fx_renderer *renderer, struct wlr_texture *wlr_texture) { + assert(wlr_texture_is_gles2(wlr_texture)); + struct wlr_gles2_texture_attribs texture_attrs; + wlr_gles2_texture_get_attribs(wlr_texture, &texture_attrs); + + glEnable(GL_BLEND); + + // FIXME: rework this + // fbo id available in wlr_buffer object? + GLint fbo_id = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo_id); + + // read from wlr_texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(texture_attrs.target, texture_attrs.tex); + glTexParameteri(texture_attrs.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + // genereate new texture to write to + GLuint texColorBuffer; + glGenTextures(1, &texColorBuffer); + glBindTexture(GL_TEXTURE_2D, texColorBuffer); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, wlr_texture->width, wlr_texture->height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + GLuint framebuffer; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, texColorBuffer, 0); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + sway_log(SWAY_ERROR, "Unable to initialize a framebuffer: %i", status); + } + + glUseProgram(renderer->shaders.invert.program); + + glUniformMatrix3fv(renderer->shaders.invert.proj, 1, GL_FALSE, transforms[WL_OUTPUT_TRANSFORM_NORMAL]); + glUniform1i(renderer->shaders.invert.tex, 0); + + const GLfloat texcoord[] = { + 1.0, 1.0, // top right + -1.0, 1.0, // top left + 1.0, -1.0, // bottom right + -1.0, -1.0, // bottom left + }; + + glVertexAttribPointer(renderer->shaders.invert.pos_attrib, 2, GL_FLOAT, GL_FALSE, 0, verts); + glVertexAttribPointer(renderer->shaders.invert.tex_attrib, 2, GL_FLOAT, GL_FALSE, 0, texcoord); + + glEnableVertexAttribArray(renderer->shaders.invert.pos_attrib); + glEnableVertexAttribArray(renderer->shaders.invert.tex_attrib); + + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(renderer->shaders.invert.pos_attrib); + glDisableVertexAttribArray(renderer->shaders.invert.tex_attrib); + + glDeleteFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id); + // TODO: remove this check after figuring the default framebuffer thing + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + sway_log(SWAY_ERROR, "Unable to restore framebuffer: %i", status); + } + + return texColorBuffer; +} + bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_texture *wlr_texture, const struct wlr_fbox *src_box, const struct wlr_box *dst_box, const float matrix[static 9], struct decoration_data deco_data) { @@ -433,6 +593,9 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t // to GL_FALSE wlr_matrix_transpose(gl_matrix, gl_matrix); + // Perform initial rendering pass + GLuint source_texture = fx_apply_shader(renderer, wlr_texture); + // if there's no opacity or rounded corners we don't need to blend if (!texture_attrs.has_alpha && deco_data.alpha == 1.0 && !deco_data.corner_radius) { glDisable(GL_BLEND); @@ -441,7 +604,7 @@ bool fx_render_subtexture_with_matrix(struct fx_renderer *renderer, struct wlr_t } glActiveTexture(GL_TEXTURE0); - glBindTexture(texture_attrs.target, texture_attrs.tex); + glBindTexture(texture_attrs.target, source_texture); glTexParameteri(texture_attrs.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); diff --git a/sway/desktop/shaders/invert.frag b/sway/desktop/shaders/invert.frag new file mode 100644 index 000000000..bb8e5c010 --- /dev/null +++ b/sway/desktop/shaders/invert.frag @@ -0,0 +1,10 @@ +precision mediump float; +varying vec2 v_texcoord; +uniform sampler2D tex; + +void main() { + vec4 color = texture2D(tex, v_texcoord); + /* gl_FragColor = vec4(1.0 - color.rgb, color.a); */ + gl_FragColor = vec4(color.rgb, 1.0); +} + diff --git a/sway/desktop/shaders/meson.build b/sway/desktop/shaders/meson.build index ce2439de9..c9b1e3363 100644 --- a/sway/desktop/shaders/meson.build +++ b/sway/desktop/shaders/meson.build @@ -2,6 +2,7 @@ embed = find_program('./embed.sh', native: true) shaders = [ 'common.vert', + 'invert.frag', 'quad.frag', 'quad_round.frag', 'quad_round_tl.frag', diff --git a/sway/meson.build b/sway/meson.build index 4191a9b9d..f7273c664 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -14,6 +14,7 @@ sway_sources = files( 'xdg_decoration.c', 'desktop/desktop.c', + 'desktop/foreign_shader.c', 'desktop/fx_renderer.c', 'desktop/idle_inhibit_v1.c', 'desktop/layer_shell.c', @@ -109,6 +110,7 @@ sway_sources = files( 'commands/seat/shortcuts_inhibitor.c', 'commands/seat/xcursor_theme.c', 'commands/set.c', + 'commands/shader.c', 'commands/shadow_blur_radius.c', 'commands/shadow_color.c', 'commands/shadows.c',