/* * The MIT License (MIT) * * Copyright (c) 2020 Scott Moreau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Shaders ported from: * https://www.shadertoy.com/view/4sd3WB (Buffer A and B) * https://www.shadertoy.com/view/Xsd3DB (Image) * */ #include #include #include #include #include static const char *vertex_shader = R"( #version 100 attribute mediump vec2 position; attribute highp vec2 uvPosition; varying highp vec2 uvpos; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); uvpos = uvPosition; } )"; static const char *fragment_shader_a = R"( #version 100 precision mediump float; uniform int num_points; uniform vec2 points[64]; uniform int button_down; varying highp vec2 uvpos; uniform sampler2D u_texture; void main() { int i; for (i = 0; i < num_points; i++) { vec2 r = gl_FragCoord.xy - points[i]; float d = 0.005 * dot(r, r); if (button_down == 1 && d < 0.05) { gl_FragColor = vec4(0.0, 1.0, 0.0, 0.0); return; } } gl_FragColor = texture2D(u_texture, uvpos); } )"; static const char *fragment_shader_b = R"( #version 100 precision mediump float; uniform vec2 resolution; varying highp vec2 uvpos; uniform sampler2D u_texture; void main() { float dx = resolution.x; float dy = resolution.y; vec2 uv = uvpos; vec2 udu = texture2D(u_texture, uv).xy; // old elevation float u = udu.x; // old velocity float du = udu.y; // Finite differences float ux = texture2D(u_texture, vec2(uv.x + dx, uv.y)).x; float umx = texture2D(u_texture, vec2(uv.x - dx, uv.y)).x; float uy = texture2D(u_texture, vec2(uv.x, uv.y + dy)).x; float umy = texture2D(u_texture, vec2(uv.x, uv.y - dy)).x; // new elevation float nu = u + du + 0.28 * (umx + ux + umy + uy - 4.0 * u); nu *= 0.99; // evaporation if (nu < 0.025) { nu *= 0.2; } // store elevation and velocity gl_FragColor = vec4(nu, nu - u, 0.0, 0.0); } )"; static const char *fragment_shader_c = R"( #version 100 precision mediump float; #define DEBUG 0 uniform float fade; uniform vec2 resolution; varying highp vec2 uvpos; uniform sampler2D u_texture; uniform sampler2D water_texture; void main() { vec2 uv = uvpos; #if DEBUG == 1 float h = texture2D(water_texture, uv).x; float sh = 1.35 - h * 2.; vec4 effect = vec4(exp(pow(sh - .75, 2.) * -10.), exp(pow(sh - .50, 2.) * -20.), exp(pow(sh - .25, 2.) * -10.), 1.); vec4 fb_pixel = vec4(0.); vec4 color = effect; if (fade < 1.) { fb_pixel = texture2D(u_texture, uv) * (1. - fade); color *= fade; color += fb_pixel; } gl_FragColor = color; #else vec3 e = vec3(resolution, 0.); float p10 = texture2D(water_texture, uv - e.zy).x; float p01 = texture2D(water_texture, uv - e.xz).x; float p21 = texture2D(water_texture, uv + e.xz).x; float p12 = texture2D(water_texture, uv + e.zy).x; vec3 grad = normalize(vec3(p21 - p01, p12 - p10, 1.)); vec4 c = texture2D(u_texture, uv + grad.xy * .35); vec3 light = normalize(vec3(.2, -.5, .7)); float diffuse = dot(grad, light); if (diffuse > 0.75) { diffuse = 1.0; } float spec = pow(max(0., -reflect(light, grad).z), 32.); c = c * diffuse + spec; if (fade < 1.) { vec4 fb_pixel = texture2D(u_texture, uv) * (1. - fade); c = c * fade + fb_pixel; } gl_FragColor = c; #endif } )"; class wayfire_water_screen : public wf::plugin_interface_t { wf::option_wrapper_t button{"water/activate"}; wf::animation::simple_animation_t animation = wf::animation::simple_animation_t(wf::create_option(5000)); OpenGL::program_t program[3]; wf::framebuffer_t buffer[2]; wf::pointf_t last_cursor; bool button_down = false; bool hook_set = false; wf::wl_timer timer; int points_loc; public: void init() override { grab_interface->name = "water"; grab_interface->capabilities = wf::CAPABILITY_MANAGE_COMPOSITOR; OpenGL::render_begin(); program[0].set_simple( OpenGL::compile_program(vertex_shader, fragment_shader_a)); program[1].set_simple( OpenGL::compile_program(vertex_shader, fragment_shader_b)); program[2].set_simple( OpenGL::compile_program(vertex_shader, fragment_shader_c)); points_loc = GL_CALL(glGetUniformLocation( program[0].get_program_id(wf::TEXTURE_TYPE_RGBA), "points")); OpenGL::render_end(); output->add_button(button, &activate_binding); grab_interface->callbacks.pointer.button = [=] (uint32_t b, uint32_t s) { if (s == WL_POINTER_BUTTON_STATE_RELEASED) { output->deactivate_plugin(grab_interface); timer.set_timeout(5000, timeout); grab_interface->ungrab(); button_down = false; } }; animation.set(0, 0); } wf::button_callback activate_binding = [=] (auto) { if (!output->is_plugin_active(grab_interface->name)) { if (!output->activate_plugin(grab_interface)) { return false; } } if (!hook_set) { output->render->add_post(&render); hook_set = true; } last_cursor = output->get_cursor_position(); animation.animate(animation, 1); grab_interface->grab(); timer.disconnect(); button_down = true; return true; }; wf::wl_timer::callback_t timeout = [=] () { animation.animate(animation, 0); return false; // disconnect }; wf::post_hook_t render = [=] (const wf::framebuffer_base_t& source, const wf::framebuffer_base_t& destination) { auto transform = output->render->get_target_framebuffer().transform; auto cursor_position = output->get_cursor_position(); auto og = output->get_relative_geometry(); auto fbg = output->render->get_target_framebuffer(). framebuffer_box_from_geometry_box(og); transform = glm::inverse(transform); static const float vertexData[] = { -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f }; static const float coordData[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; wf::pointf_t step; std::vector points; wf::pointf_t center{0.5, 0.5}; auto d = glm::distance(glm::vec2(last_cursor.x, last_cursor.y), glm::vec2(cursor_position.x, cursor_position.y)); /* Interpolate between last and current cursor */ int num_points = std::min(int(d / 5 + 1), 64); step.x = (cursor_position.x - last_cursor.x) / num_points; step.y = (cursor_position.y - last_cursor.y) / num_points; for (int i = 0; i < num_points; i++) { wf::pointf_t p = wf::pointf_t{ cursor_position.x - step.x * i, cursor_position.y - step.y * i}; float x = p.x / og.width; float y = p.y / og.height; /* Apply transform to cursor position */ glm::vec4 point{x - center.x, y - center.y, 1.0, 1.0}; glm::vec4 result = transform * point; x = (result.x + center.x) * fbg.width; y = (result.y + center.y) * fbg.height; y = fbg.height - y; points.push_back(x); points.push_back(y); } last_cursor = cursor_position; /* First pass */ OpenGL::render_begin(); if (buffer[0].allocate(fbg.width, fbg.height)) { buffer[0].bind(); buffer[0].geometry = fbg; OpenGL::clear({0, 0, 0, 1}); } if (buffer[1].allocate(fbg.width, fbg.height)) { buffer[1].bind(); buffer[1].geometry = fbg; OpenGL::clear({0, 0, 0, 1}); } buffer[0].bind(); program[0].use(wf::TEXTURE_TYPE_RGBA); program[0].attrib_pointer("position", 2, 0, vertexData); program[0].attrib_pointer("uvPosition", 2, 0, coordData); GL_CALL(glUniform2fv(points_loc, num_points, points.data())); program[0].uniform1i("num_points", num_points); program[0].uniform1i("button_down", button_down ? 1 : 0); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer[1].tex)); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program[0].deactivate(); OpenGL::render_end(); /* Second pass */ OpenGL::render_begin(buffer[1]); program[1].use(wf::TEXTURE_TYPE_RGBA); program[1].attrib_pointer("position", 2, 0, vertexData); program[1].attrib_pointer("uvPosition", 2, 0, coordData); program[1].uniform2f("resolution", 1.0 / fbg.width, 1.0 / fbg.height); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer[0].tex)); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program[1].deactivate(); OpenGL::render_end(); /* Final pass */ OpenGL::render_begin(destination); program[2].use(wf::TEXTURE_TYPE_RGBA); program[2].attrib_pointer("position", 2, 0, vertexData); program[2].attrib_pointer("uvPosition", 2, 0, coordData); program[2].uniform2f("resolution", 1.0 / fbg.width, 1.0 / fbg.height); program[2].uniform1f("fade", animation); program[2].uniform1i("water_texture", 1); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, source.tex)); GL_CALL(glActiveTexture(GL_TEXTURE0 + 1)); GL_CALL(glBindTexture(GL_TEXTURE_2D, buffer[1].tex)); GL_CALL(glDisable(GL_BLEND)); GL_CALL(glDrawArrays(GL_TRIANGLE_FAN, 0, 4)); GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); GL_CALL(glActiveTexture(GL_TEXTURE0)); GL_CALL(glBindTexture(GL_TEXTURE_2D, 0)); program[2].deactivate(); OpenGL::render_end(); if (!button_down && !timer.is_connected() && !animation.running()) { hook_set = false; output->render->rem_post(&render); OpenGL::render_begin(); buffer[0].release(); buffer[1].release(); OpenGL::render_end(); } output->render->schedule_redraw(); }; void fini() override { output->deactivate_plugin(grab_interface); output->rem_binding(&activate_binding); grab_interface->ungrab(); timer.disconnect(); if (hook_set) { output->render->rem_post(&render); } OpenGL::render_begin(); buffer[0].release(); buffer[1].release(); program[0].free_resources(); program[1].free_resources(); program[2].free_resources(); OpenGL::render_end(); } }; DECLARE_WAYFIRE_PLUGIN(wayfire_water_screen);