// SPDX-License-Identifier: GPL-2.0-or-later #include <2geom/transforms.h> #include <2geom/rect.h> #include "ui/util.h" #include "helper/geom.h" #include "glgraphics.h" #include "stores.h" #include "prefs.h" #include "pixelstreamer.h" #include "util.h" namespace Inkscape { namespace UI { namespace Widget { namespace { // 2Geom <-> OpenGL void geom_to_uniform_mat(Geom::Affine const &affine, GLuint location) { glUniformMatrix2fv(location, 1, GL_FALSE, std::begin({(GLfloat)affine[0], (GLfloat)affine[1], (GLfloat)affine[2], (GLfloat)affine[3]})); } void geom_to_uniform_trans(Geom::Affine const &affine, GLuint location) { glUniform2fv(location, 1, std::begin({(GLfloat)affine[4], (GLfloat)affine[5]})); } void geom_to_uniform(Geom::Affine const &affine, GLuint mat_location, GLuint trans_location) { geom_to_uniform_mat(affine, mat_location); geom_to_uniform_trans(affine, trans_location); } void geom_to_uniform(Geom::Point const &vec, GLuint location) { glUniform2fv(location, 1, std::begin({(GLfloat)vec.x(), (GLfloat)vec.y()})); } // Get the affine transformation required to paste fragment A onto fragment B, assuming // coordinates such that A is a texture (0 to 1) and B is a framebuffer (-1 to 1). static auto calc_paste_transform(Fragment const &a, Fragment const &b) { Geom::Affine result = Geom::Scale(a.rect.dimensions()); if (a.affine == b.affine) { result *= Geom::Translate(a.rect.min() - b.rect.min()); } else { result *= Geom::Translate(a.rect.min()) * a.affine.inverse() * b.affine * Geom::Translate(-b.rect.min()); } return result * Geom::Scale(2.0 / b.rect.dimensions()) * Geom::Translate(-1.0, -1.0); } // Given a region, shrink it by 0.5px, and convert the result to a VAO of triangles. static auto region_shrink_vao(Cairo::RefPtr const ®, Geom::IntRect const &rel) { // Shrink the region by 0.5 (translating it by (0.5, 0.5) in the process). auto reg2 = shrink_region(reg, 1); // Preallocate the vertex buffer. int nrects = reg2->get_num_rectangles(); std::vector verts; verts.reserve(nrects * 12); // Add a vertex to the buffer, transformed to a coordinate system in which the enclosing rectangle 'rel' goes from 0 to 1. // Also shift them up/left by 0.5px; combined with the width/height increase from earlier, this shrinks the region by 0.5px. auto emit_vertex = [&] (Geom::IntPoint const &pt) { verts.emplace_back((pt.x() - 0.5f - rel.left()) / rel.width()); verts.emplace_back((pt.y() - 0.5f - rel.top() ) / rel.height()); }; // Todo: Use a better triangulation algorithm here that results in 1) less triangles, and 2) no seaming. for (int i = 0; i < nrects; i++) { auto rect = cairo_to_geom(reg2->get_rectangle(i)); for (int j = 0; j < 6; j++) { int constexpr indices[] = {0, 1, 2, 0, 2, 3}; emit_vertex(rect.corner(indices[j])); } } // Package the data in a VAO. VAO result; glGenBuffers(1, &result.vbuf); glBindBuffer(GL_ARRAY_BUFFER, result.vbuf); glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(GLfloat), verts.data(), GL_STREAM_DRAW); glGenVertexArrays(1, &result.vao); glBindVertexArray(result.vao); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, 0); // Return the VAO and the number of rectangles. return std::make_pair(std::move(result), nrects); } auto pref_to_pixelstreamer(int index) { auto constexpr arr = std::array{PixelStreamer::Method::Auto, PixelStreamer::Method::Persistent, PixelStreamer::Method::Asynchronous, PixelStreamer::Method::Synchronous}; assert(1 <= index && index <= arr.size()); return arr[index - 1]; } } // namespace GLGraphics::GLGraphics(Prefs const &prefs, Stores const &stores, PageInfo const &pi) : prefs(prefs) , stores(stores) , pi(pi) { // Create rectangle geometry. GLfloat constexpr verts[] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f}; glGenBuffers(1, &rect.vbuf); glBindBuffer(GL_ARRAY_BUFFER, rect.vbuf); glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); glGenVertexArrays(1, &rect.vao); glBindVertexArray(rect.vao); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 2, 0); // Create shader programs. auto vs = VShader(R"( #version 330 core uniform mat2 mat; uniform vec2 trans; uniform vec2 subrect; layout(location = 0) in vec2 pos; smooth out vec2 uv; void main() { uv = pos * subrect; vec2 pos2 = mat * pos + trans; gl_Position = vec4(pos2.x, pos2.y, 0.0, 1.0); } )"); auto texcopy_fs = FShader(R"( #version 330 core uniform sampler2D tex; smooth in vec2 uv; out vec4 outColour; void main() { outColour = texture(tex, uv); } )"); auto texcopydouble_fs = FShader(R"( #version 330 core uniform sampler2D tex; uniform sampler2D tex_outline; smooth in vec2 uv; layout(location = 0) out vec4 outColour; layout(location = 1) out vec4 outColour_outline; void main() { outColour = texture(tex, uv); outColour_outline = texture(tex_outline, uv); } )"); auto outlineoverlay_fs = FShader(R"( #version 330 core uniform sampler2D tex; uniform sampler2D tex_outline; uniform float opacity; smooth in vec2 uv; out vec4 outColour; void main() { vec4 c1 = texture(tex, uv); vec4 c2 = texture(tex_outline, uv); vec4 c1w = vec4(mix(c1.rgb, vec3(1.0, 1.0, 1.0) * c1.a, opacity), c1.a); outColour = c1w * (1.0 - c2.a) + c2; } )"); auto xray_fs = FShader(R"( #version 330 core uniform sampler2D tex; uniform sampler2D tex_outline; uniform vec2 pos; uniform float radius; smooth in vec2 uv; out vec4 outColour; void main() { vec4 c1 = texture(tex, uv); vec4 c2 = texture(tex_outline, uv); float r = length(gl_FragCoord.xy - pos); r = clamp((radius - r) / 2.0, 0.0, 1.0); outColour = mix(c1, c2, r); } )"); auto outlineoverlayxray_fs = FShader(R"( #version 330 core uniform sampler2D tex; uniform sampler2D tex_outline; uniform float opacity; uniform vec2 pos; uniform float radius; smooth in vec2 uv; out vec4 outColour; void main() { vec4 c1 = texture(tex, uv); vec4 c2 = texture(tex_outline, uv); vec4 c1w = vec4(mix(c1.rgb, vec3(1.0, 1.0, 1.0) * c1.a, opacity), c1.a); outColour = c1w * (1.0 - c2.a) + c2; float r = length(gl_FragCoord.xy - pos); r = clamp((radius - r) / 2.0, 0.0, 1.0); outColour = mix(outColour, c2, r); } )"); auto checker_fs = FShader(R"( #version 330 core uniform float size; uniform vec3 col1, col2; out vec4 outColour; void main() { vec2 a = floor(fract(gl_FragCoord.xy / size) * 2.0); float b = abs(a.x - a.y); outColour = vec4((1.0 - b) * col1 + b * col2, 1.0); } )"); auto shadow_gs = GShader(R"( #version 330 core layout(triangles) in; layout(triangle_strip, max_vertices = 10) out; uniform vec2 wh; uniform float size; uniform vec2 dir; smooth out vec2 uv; flat out vec2 maxuv; void f(vec4 p, vec4 v0, mat2 m) { gl_Position = p; uv = m * (p.xy - v0.xy); EmitVertex(); } float push(float x) { return 0.15 * (1.0 + clamp(x / 0.707, -1.0, 1.0)); } void main() { vec4 v0 = gl_in[0].gl_Position; vec4 v1 = gl_in[1].gl_Position; vec4 v2 = gl_in[2].gl_Position; vec4 v3 = gl_in[2].gl_Position - gl_in[1].gl_Position + gl_in[0].gl_Position; vec2 a = normalize((v1 - v0).xy * wh); vec2 b = normalize((v3 - v0).xy * wh); float det = a.x * b.y - a.y * b.x; float s = -sign(det); vec2 c = size / abs(det) / wh; vec4 d = vec4(a * c, 0.0, 0.0); vec4 e = vec4(b * c, 0.0, 0.0); mat2 m = s * mat2(a.y, -b.y, -a.x, b.x) * mat2(wh.x, 0.0, 0.0, wh.y) / size; float ap = s * dot(vec2(a.y, -a.x), dir); float bp = s * dot(vec2(-b.y, b.x), dir); v0.xy += (b * push( ap) + a * push( bp)) * size / wh; v1.xy += (b * push( ap) + a * -push(-bp)) * size / wh; v2.xy += (b * -push(-ap) + a * -push(-bp)) * size / wh; v3.xy += (b * -push(-ap) + a * push( bp)) * size / wh; maxuv = m * (v2.xy - v0.xy); f(v0, v0, m); f(v0 - d - e, v0, m); f(v1, v0, m); f(v1 + d - e, v0, m); f(v2, v0, m); f(v2 + d + e, v0, m); f(v3, v0, m); f(v3 - d + e, v0, m); f(v0, v0, m); f(v0 - d - e, v0, m); EndPrimitive(); } )"); auto shadow_fs = FShader(R"( #version 330 core uniform vec4 shadow_col; smooth in vec2 uv; flat in vec2 maxuv; out vec4 outColour; void main() { float x = max(uv.x - maxuv.x, 0.0) - max(-uv.x, 0.0); float y = max(uv.y - maxuv.y, 0.0) - max(-uv.y, 0.0); float s = min(length(vec2(x, y)), 1.0); float A = 4.0; // This coefficient changes how steep the curve is and controls shadow drop-off. s = (exp(A * (1.0 - s)) - 1.0) / (exp(A) - 1.0); // Exponential decay for drop shadow - long tail. outColour = shadow_col * s; } )"); texcopy.create(vs, texcopy_fs); texcopydouble.create(vs, texcopydouble_fs); outlineoverlay.create(vs, outlineoverlay_fs); xray.create(vs, xray_fs); outlineoverlayxray.create(vs, outlineoverlayxray_fs); checker.create(vs, checker_fs); shadow.create(vs, shadow_gs, shadow_fs); // Create the framebuffer object for rendering to off-view fragments. glGenFramebuffers(1, &fbo); // Create the texture cache. texturecache = TextureCache::create(); // Create the PixelStreamer. pixelstreamer = PixelStreamer::create_supported(pref_to_pixelstreamer(prefs.pixelstreamer_method)); // Set the last known state as unspecified, forcing a pipeline recreation whatever the next operation is. state = State::None; } GLGraphics::~GLGraphics() { glDeleteFramebuffers(1, &fbo); } std::unique_ptr Graphics::create_gl(Prefs const &prefs, Stores const &stores, PageInfo const &pi) { return std::make_unique(prefs, stores, pi); } void GLGraphics::set_outlines_enabled(bool enabled) { outlines_enabled = enabled; if (!enabled) { store.outline_texture.clear(); snapshot.outline_texture.clear(); } } void GLGraphics::setup_stores_pipeline() { if (state == State::Stores) return; state = State::Stores; glDisable(GL_BLEND); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); GLuint constexpr attachments[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glDrawBuffers(outlines_enabled ? 2 : 1, attachments); auto const &shader = outlines_enabled ? texcopydouble : texcopy; glUseProgram(shader.id); mat_loc = shader.loc("mat"); trans_loc = shader.loc("trans"); geom_to_uniform({1.0, 1.0}, shader.loc("subrect")); tex_loc = shader.loc("tex"); if (outlines_enabled) texoutline_loc = shader.loc("tex_outline"); } void GLGraphics::recreate_store(Geom::IntPoint const &dims) { auto tex_size = dims * scale_factor; // Setup the base pipeline. setup_stores_pipeline(); // Recreate the store textures. auto recreate = [&] (Texture &tex) { if (tex && tex.size() == tex_size) { tex.invalidate(); } else { tex = Texture(tex_size); } }; recreate(store.texture); if (outlines_enabled) { recreate(store.outline_texture); } // Bind the store to the framebuffer for writing to. glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, store.texture.id(), 0); if (outlines_enabled) glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, store.outline_texture.id(), 0); glViewport(0, 0, store.texture.size().x(), store.texture.size().y()); // Clear the store to transparent. glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } void GLGraphics::shift_store(Fragment const &dest) { auto tex_size = dest.rect.dimensions() * scale_factor; // Setup the base pipeline. setup_stores_pipeline(); // Create the new fragment. auto create_or_reuse = [&] (Texture &tex, Texture &from) { if (from && from.size() == tex_size) { from.invalidate(); tex = std::move(from); } else { tex = Texture(tex_size); } }; GLFragment fragment; create_or_reuse(fragment.texture, snapshot.texture); if (outlines_enabled) { create_or_reuse(fragment.outline_texture, snapshot.outline_texture); } // Bind new store to the framebuffer to writing to. glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fragment.texture .id(), 0); if (outlines_enabled) glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, fragment.outline_texture.id(), 0); glViewport(0, 0, fragment.texture.size().x(), fragment.texture.size().y()); // Clear new store to transparent. glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); // Bind the old store to texture units 0 and 1 for reading from. glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, store.texture.id()); glUniform1i(tex_loc, 0); if (outlines_enabled) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, store.outline_texture.id()); glUniform1i(texoutline_loc, 1); } glBindVertexArray(rect.vao); // Copy re-usuable contents of the old store into the new store. geom_to_uniform(calc_paste_transform(stores.store(), dest), mat_loc, trans_loc); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Set the result as the new store. snapshot = std::move(store); store = std::move(fragment); } void GLGraphics::swap_stores() { std::swap(store, snapshot); } void GLGraphics::fast_snapshot_combine() { // Ensure the base pipeline is correctly set up. setup_stores_pipeline(); // Compute the vertex data for the drawn region. auto [clean_vao, clean_numrects] = region_shrink_vao(stores.store().drawn, stores.store().rect); // Bind the snapshot to the framebuffer for writing to. glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, snapshot.texture.id(), 0); if (outlines_enabled) glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, snapshot.outline_texture.id(), 0); glViewport(0, 0, snapshot.texture.size().x(), snapshot.texture.size().y()); // Bind the store to texture unit 0 (and its outline to 1, if necessary). glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, store.texture.id()); glUniform1i(tex_loc, 0); if (outlines_enabled) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, store.outline_texture.id()); glUniform1i(texoutline_loc, 1); } // Copy the clean region of the store to the snapshot. geom_to_uniform(calc_paste_transform(stores.store(), stores.snapshot()), mat_loc, trans_loc); glBindVertexArray(clean_vao.vao); glDrawArrays(GL_TRIANGLES, 0, 6 * clean_numrects); } void GLGraphics::snapshot_combine(Fragment const &dest) { // Create the new fragment. auto content_size = dest.rect.dimensions() * scale_factor; // Ensure the base pipeline is correctly set up. setup_stores_pipeline(); // Compute the vertex data for the clean region. auto [clean_vao, clean_numrects] = region_shrink_vao(stores.store().drawn, stores.store().rect); GLFragment fragment; fragment.texture = Texture(content_size); if (outlines_enabled) fragment.outline_texture = Texture(content_size); // Bind the new fragment to the framebuffer for writing to. glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fragment.texture.id(), 0); if (outlines_enabled) glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, fragment.outline_texture.id(), 0); // Clear the new fragment to transparent. glViewport(0, 0, fragment.texture.size().x(), fragment.texture.size().y()); glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); // Bind the store and snapshot to texture units 0 and 1 (and their outlines to 2 and 3, if necessary). glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, snapshot.texture.id()); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, store.texture.id()); if (outlines_enabled) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, snapshot.outline_texture.id()); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, store.outline_texture.id()); } // Paste the snapshot store onto the new fragment. glUniform1i(tex_loc, 0); if (outlines_enabled) glUniform1i(texoutline_loc, 2); geom_to_uniform(calc_paste_transform(stores.snapshot(), dest), mat_loc, trans_loc); glBindVertexArray(rect.vao); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Paste the backing store onto the new fragment. glUniform1i(tex_loc, 1); if (outlines_enabled) glUniform1i(texoutline_loc, 3); geom_to_uniform(calc_paste_transform(stores.store(), dest), mat_loc, trans_loc); glBindVertexArray(clean_vao.vao); glDrawArrays(GL_TRIANGLES, 0, 6 * clean_numrects); // Set the result as the new snapshot. snapshot = std::move(fragment); } void GLGraphics::invalidate_snapshot() { if (snapshot.texture) snapshot.texture.invalidate(); if (snapshot.outline_texture) snapshot.outline_texture.invalidate(); } void GLGraphics::setup_tiles_pipeline() { if (state == State::Tiles) return; state = State::Tiles; glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); GLuint constexpr attachments[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glDrawBuffers(outlines_enabled ? 2 : 1, attachments); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, store.texture.id(), 0); if (outlines_enabled) glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, store.outline_texture.id(), 0); glViewport(0, 0, store.texture.size().x(), store.texture.size().y()); auto const &shader = outlines_enabled ? texcopydouble : texcopy; glUseProgram(shader.id); mat_loc = shader.loc("mat"); trans_loc = shader.loc("trans"); subrect_loc = shader.loc("subrect"); glUniform1i(shader.loc("tex"), 0); if (outlines_enabled) glUniform1i(shader.loc("tex_outline"), 1); glBindVertexArray(rect.vao); glDisable(GL_BLEND); }; Cairo::RefPtr GLGraphics::request_tile_surface(Geom::IntRect const &rect, bool nogl) { Cairo::RefPtr surface; { auto g = std::lock_guard(ps_mutex); surface = pixelstreamer->request(rect.dimensions() * scale_factor, nogl); } if (surface) { cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); } return surface; } void GLGraphics::draw_tile(Fragment const &fragment, Cairo::RefPtr surface, Cairo::RefPtr outline_surface) { auto g = std::lock_guard(ps_mutex); auto surface_size = dimensions(surface); Texture texture, outline_texture; glActiveTexture(GL_TEXTURE0); texture = texturecache->request(surface_size); // binds pixelstreamer->finish(std::move(surface)); // uploads content if (outlines_enabled) { glActiveTexture(GL_TEXTURE1); outline_texture = texturecache->request(surface_size); pixelstreamer->finish(std::move(outline_surface)); } setup_tiles_pipeline(); geom_to_uniform(calc_paste_transform(fragment, stores.store()), mat_loc, trans_loc); geom_to_uniform(Geom::Point(surface_size) / texture.size(), subrect_loc); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); texturecache->finish(std::move(texture)); if (outlines_enabled) { texturecache->finish(std::move(outline_texture)); } } void GLGraphics::junk_tile_surface(Cairo::RefPtr surface) { auto g = std::lock_guard(ps_mutex); pixelstreamer->finish(std::move(surface), true); } void GLGraphics::setup_widget_pipeline(Fragment const &view) { state = State::Widget; glDrawBuffer(GL_COLOR_ATTACHMENT0); glViewport(0, 0, view.rect.width() * scale_factor, view.rect.height() * scale_factor); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, 1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, store.texture.id()); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, snapshot.texture.id()); if (outlines_enabled) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, store.outline_texture.id()); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, snapshot.outline_texture.id()); } glBindVertexArray(rect.vao); }; void GLGraphics::paint_widget(Fragment const &view, PaintArgs const &a, Cairo::RefPtr const&) { // If in decoupled mode, create the vertex data describing the drawn region of the store. VAO clean_vao; int clean_numrects; if (stores.mode() == Stores::Mode::Decoupled) { std::tie(clean_vao, clean_numrects) = region_shrink_vao(stores.store().drawn, stores.store().rect); } setup_widget_pipeline(view); // Clear the buffers. Since we have to pick a clear colour, we choose the page colour, enabling the single-page optimisation later. glClearColor(SP_RGBA32_R_U(page) / 255.0f, SP_RGBA32_G_U(page) / 255.0f, SP_RGBA32_B_U(page) / 255.0f, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (check_single_page(view, pi)) { // A single page occupies the whole view. if (SP_RGBA32_A_U(page) == 255) { // Page is solid - nothing to do, since already cleared to this colour. } else { // Page is checkerboard - fill view with page pattern. glDisable(GL_BLEND); glUseProgram(checker.id); glUniform1f(checker.loc("size"), 12.0 * scale_factor); glUniform3fv(checker.loc("col1"), 1, std::begin(rgb_to_array(page))); glUniform3fv(checker.loc("col2"), 1, std::begin(checkerboard_darken(page))); geom_to_uniform(Geom::Scale(2.0, -2.0) * Geom::Translate(-1.0, 1.0), checker.loc("mat"), checker.loc("trans")); geom_to_uniform({1.0, 1.0}, checker.loc("subrect")); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); auto set_page_transform = [&] (Geom::Rect const &rect, Program const &prog) { geom_to_uniform(Geom::Scale(rect.dimensions()) * Geom::Translate(rect.min()) * calc_paste_transform({{}, Geom::IntRect::from_xywh(0, 0, 1, 1)}, view) * Geom::Scale(1.0, -1.0), prog.loc("mat"), prog.loc("trans")); }; // Pages glUseProgram(checker.id); glUniform1f(checker.loc("size"), 12.0 * scale_factor); glUniform3fv(checker.loc("col1"), 1, std::begin(rgb_to_array(page))); glUniform3fv(checker.loc("col2"), 1, std::begin(checkerboard_darken(page))); geom_to_uniform({1.0, 1.0}, checker.loc("subrect")); for (auto &rect : pi.pages) { set_page_transform(rect, checker); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Desk glUniform3fv(checker.loc("col1"), 1, std::begin(rgb_to_array(desk))); glUniform3fv(checker.loc("col2"), 1, std::begin(checkerboard_darken(desk))); geom_to_uniform(Geom::Scale(2.0, -2.0) * Geom::Translate(-1.0, 1.0), checker.loc("mat"), checker.loc("trans")); geom_to_uniform({1.0, 1.0}, checker.loc("subrect")); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Shadows if (SP_RGBA32_A_U(border) != 0) { auto dir = (Geom::Point(1.0, a.yaxisdir) * view.affine * Geom::Scale(1.0, -1.0)).normalized(); // Shadow direction rotates with view. glUseProgram(shadow.id); geom_to_uniform({1.0, 1.0}, shadow.loc("subrect")); glUniform2fv(shadow.loc("wh"), 1, std::begin({(GLfloat)view.rect.width(), (GLfloat)view.rect.height()})); glUniform1f(shadow.loc("size"), 40.0 * std::pow(std::abs(view.affine.det()), 0.25)); glUniform2fv(shadow.loc("dir"), 1, std::begin({(GLfloat)dir.x(), (GLfloat)dir.y()})); glUniform4fv(shadow.loc("shadow_col"), 1, std::begin(premultiplied(rgba_to_array(border)))); for (auto &rect : pi.pages) { set_page_transform(rect, shadow); glDrawArrays(GL_TRIANGLES, 0, 3); } } glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } glStencilFunc(GL_NOTEQUAL, 2, 2); enum class DrawMode { Store, Outline, Combine }; auto draw_store = [&, this] (Program const &prog, DrawMode drawmode) { glUseProgram(prog.id); geom_to_uniform({1.0, 1.0}, prog.loc("subrect")); glUniform1i(prog.loc("tex"), drawmode == DrawMode::Outline ? 2 : 0); if (drawmode == DrawMode::Combine) { glUniform1i(prog.loc("tex_outline"), 2); glUniform1f(prog.loc("opacity"), prefs.outline_overlay_opacity / 100.0); } if (stores.mode() == Stores::Mode::Normal) { // Backing store fragment. geom_to_uniform(calc_paste_transform(stores.store(), view) * Geom::Scale(1.0, -1.0), prog.loc("mat"), prog.loc("trans")); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } else { // Backing store fragment, clipped to its clean region. geom_to_uniform(calc_paste_transform(stores.store(), view) * Geom::Scale(1.0, -1.0), prog.loc("mat"), prog.loc("trans")); glBindVertexArray(clean_vao.vao); glDrawArrays(GL_TRIANGLES, 0, 6 * clean_numrects); // Snapshot fragment. glUniform1i(prog.loc("tex"), drawmode == DrawMode::Outline ? 3 : 1); if (drawmode == DrawMode::Combine) glUniform1i(prog.loc("tex_outline"), 3); geom_to_uniform(calc_paste_transform(stores.snapshot(), view) * Geom::Scale(1.0, -1.0), prog.loc("mat"), prog.loc("trans")); glBindVertexArray(rect.vao); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } }; if (a.splitmode == Inkscape::SplitMode::NORMAL || (a.splitmode == Inkscape::SplitMode::XRAY && !a.mouse)) { // Drawing the backing store over the whole view. a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY ? draw_store(outlineoverlay, DrawMode::Combine) : draw_store(texcopy, DrawMode::Store); } else if (a.splitmode == Inkscape::SplitMode::SPLIT) { // Calculate the clipping rectangles for split view. auto [store_clip, outline_clip] = calc_splitview_cliprects(view.rect.dimensions(), a.splitfrac, a.splitdir); glEnable(GL_SCISSOR_TEST); // Draw the backing store. glScissor(store_clip.left() * scale_factor, (view.rect.height() - store_clip.bottom()) * scale_factor, store_clip.width() * scale_factor, store_clip.height() * scale_factor); a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY ? draw_store(outlineoverlay, DrawMode::Combine) : draw_store(texcopy, DrawMode::Store); // Draw the outline store. glScissor(outline_clip.left() * scale_factor, (view.rect.height() - outline_clip.bottom()) * scale_factor, outline_clip.width() * scale_factor, outline_clip.height() * scale_factor); draw_store(texcopy, DrawMode::Outline); glDisable(GL_SCISSOR_TEST); glDisable(GL_STENCIL_TEST); // Calculate the bounding rectangle of the split view controller. auto rect = Geom::IntRect({0, 0}, view.rect.dimensions()); auto dim = a.splitdir == Inkscape::SplitDirection::EAST || a.splitdir == Inkscape::SplitDirection::WEST ? Geom::X : Geom::Y; rect[dim] = Geom::IntInterval(-21, 21) + std::round(a.splitfrac[dim] * view.rect.dimensions()[dim]); // Lease out a PixelStreamer mapping to draw on. auto surface_size = rect.dimensions() * scale_factor; auto surface = pixelstreamer->request(surface_size); cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); // Actually draw the content with Cairo. auto cr = Cairo::Context::create(surface); cr->set_operator(Cairo::OPERATOR_SOURCE); cr->set_source_rgba(0.0, 0.0, 0.0, 0.0); cr->paint(); cr->translate(-rect.left(), -rect.top()); paint_splitview_controller(view.rect.dimensions(), a.splitfrac, a.splitdir, a.hoverdir, cr); // Convert the surface to a texture. glActiveTexture(GL_TEXTURE0); auto texture = texturecache->request(surface_size); pixelstreamer->finish(std::move(surface)); // Paint the texture onto the view. glUseProgram(texcopy.id); glUniform1i(texcopy.loc("tex"), 0); geom_to_uniform(Geom::Scale(rect.dimensions()) * Geom::Translate(rect.min()) * Geom::Scale(2.0 / view.rect.width(), -2.0 / view.rect.height()) * Geom::Translate(-1.0, 1.0), texcopy.loc("mat"), texcopy.loc("trans")); geom_to_uniform(Geom::Point(surface_size) / texture.size(), texcopy.loc("subrect")); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Return the texture back to the texture cache. texturecache->finish(std::move(texture)); } else { // if (_split_mode == Inkscape::SplitMode::XRAY && a.mouse) // Draw the backing store over the whole view. auto const &shader = a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY ? outlineoverlayxray : xray; glUseProgram(shader.id); glUniform1f(shader.loc("radius"), prefs.xray_radius * scale_factor); glUniform2fv(shader.loc("pos"), 1, std::begin({(GLfloat)(a.mouse->x() * scale_factor), (GLfloat)((view.rect.height() - a.mouse->y()) * scale_factor)})); draw_store(shader, DrawMode::Combine); } } } // namespace Widget } // namespace UI } // namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :