summaryrefslogtreecommitdiffstats
path: root/src/ui/widget/canvas/glgraphics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/widget/canvas/glgraphics.cpp')
-rw-r--r--src/ui/widget/canvas/glgraphics.cpp873
1 files changed, 873 insertions, 0 deletions
diff --git a/src/ui/widget/canvas/glgraphics.cpp b/src/ui/widget/canvas/glgraphics.cpp
new file mode 100644
index 0000000..b00503c
--- /dev/null
+++ b/src/ui/widget/canvas/glgraphics.cpp
@@ -0,0 +1,873 @@
+// 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<Cairo::Region> const &reg, 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<GLfloat> 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> Graphics::create_gl(Prefs const &prefs, Stores const &stores, PageInfo const &pi)
+{
+ return std::make_unique<GLGraphics>(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<Cairo::ImageSurface> GLGraphics::request_tile_surface(Geom::IntRect const &rect, bool nogl)
+{
+ Cairo::RefPtr<Cairo::ImageSurface> 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<Cairo::ImageSurface> surface, Cairo::RefPtr<Cairo::ImageSurface> 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<Cairo::ImageSurface> 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<Cairo::Context> 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 :