Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
optglarea.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2
3#include "optglarea.h"
4
5#include <cassert>
6#include <utility>
7#include <gdkmm/glcontext.h>
8#include <gdkmm/gltexture.h>
9#include <gdkmm/gltexturebuilder.h>
10#include <gtkmm/snapshot.h>
11#include <2geom/int-point.h>
13
14namespace Inkscape::UI::Widget {
15namespace {
16
17template <auto &f>
18GLuint create_buffer()
19{
20 GLuint result;
21 f(1, &result);
22 return result;
23}
24
25template <typename T>
26std::weak_ptr<T> weakify(std::shared_ptr<T> const &p)
27{
28 return p;
29}
30
31// Workaround for sigc not supporting move-only lambdas.
32template <typename F>
33auto share_lambda(F &&f)
34{
35 using Fd = std::decay_t<F>;
36
37 struct Result
38 {
39 auto operator()() { (*f)(); }
40 std::shared_ptr<Fd> f;
41 };
42
43 return Result{std::make_shared<Fd>(std::move(f))};
44}
45
46} // namespace
47
48struct OptGLArea::GLState
49{
50 std::shared_ptr<Gdk::GLContext> const context;
51
52 GLuint const framebuffer = create_buffer<glGenFramebuffers>();
53 GLuint const stencilbuffer = create_buffer<glGenRenderbuffers>();
54
55 Glib::RefPtr<Gdk::GLTextureBuilder> const builder = Gdk::GLTextureBuilder::create();
56
57 std::optional<Geom::IntPoint> size;
58
59 Texture current_texture;
60 std::vector<Texture> spare_textures;
61
62 GLState(Glib::RefPtr<Gdk::GLContext> &&context_)
63 : context{std::move(context_)}
64 {
65 builder->set_context(context);
66 builder->set_format(Gdk::MemoryFormat::B8G8R8A8_PREMULTIPLIED);
67 }
68
69 ~GLState()
70 {
71 glDeleteRenderbuffers(1, &stencilbuffer);
72 glDeleteFramebuffers (1, &framebuffer);
73 }
74};
75
76OptGLArea::OptGLArea() = default;
77OptGLArea::~OptGLArea() = default;
78
80{
81 Gtk::Widget::on_realize();
83}
84
86{
88 Gtk::Widget::on_unrealize();
89}
90
92{
93 if (opengl_enabled == enabled) return;
94 if (opengl_enabled && get_realized()) uninit_opengl();
95 opengl_enabled = enabled;
96 if (opengl_enabled && get_realized()) init_opengl();
97}
98
100{
101 auto context = create_context();
102 if (!context) {
103 opengl_enabled = false;
104 return;
105 }
106 context->make_current();
107 gl = std::make_shared<GLState>(std::move(context));
108 Gdk::GLContext::clear_current();
109}
110
112{
113 gl->context->make_current();
114 gl.reset();
115 Gdk::GLContext::clear_current();
116}
117
119{
120 assert(gl);
121 gl->context->make_current();
122}
123
125{
126 assert(gl);
127 assert(gl->current_texture);
128
129 glBindFramebuffer(GL_FRAMEBUFFER, gl->framebuffer);
130 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->current_texture.id(), 0);
131 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gl->stencilbuffer);
132}
133
134void OptGLArea::snapshot_vfunc(Glib::RefPtr<Gtk::Snapshot> const &snapshot)
135{
136 if (opengl_enabled) {
137 auto const size = Geom::IntPoint(get_width(), get_height()) * get_scale_factor();
138
139 if (size.x() == 0 || size.y() == 0) {
140 return;
141 }
142
143 gl->context->make_current();
144
145 // Check if the size has changed.
146 if (size != gl->size) {
147 gl->size = size;
148
149 // Resize the framebuffer.
150 glBindRenderbuffer(GL_RENDERBUFFER, gl->stencilbuffer);
151 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x(), size.y());
152
153 // Resize the texture builder.
154 gl->builder->set_width(size.x());
155 gl->builder->set_height(size.y());
156 }
157
158 // Discard wrongly-sized spare textures.
159 std::erase_if(gl->spare_textures, [&] (auto &tex) { return tex.size() != size; });
160 // Todo: Consider clearing out excess spare textures every once in a while.
161
162 // Set the current texture.
163 assert(!gl->current_texture);
164 if (!gl->spare_textures.empty()) {
165 // Grab a spare texture.
166 gl->current_texture = std::move(gl->spare_textures.back());
167 gl->spare_textures.pop_back();
168 } else {
169 // Create a new one.
170 gl->current_texture = Texture(size);
171 }
172
173 // This typically calls bind_framebuffer().
174 paint_widget({});
175
176 // Wrap the OpenGL texture we've just drawn to in a Gdk::GLTexture.
177 gl->builder->set_id(gl->current_texture.id());
178 auto gdktexture = std::static_pointer_cast<Gdk::GLTexture>(gl->builder->build(
179 share_lambda([texture = std::move(gl->current_texture),
180 context = gl->context,
181 gl_weak = weakify(gl)] () mutable
182 {
183 if (auto gl = gl_weak.lock()) {
184 // Return the texture to the texture pool.
185 gl->spare_textures.emplace_back(std::move(texture));
186 } else {
187 // Destroy the texture in its GL context.
188 context->make_current();
189 texture.clear();
190 Gdk::GLContext::clear_current();
191 }
192 })
193 ));
194
195 // Render the texture upside-down.
196 // Todo: The canvas does the same, so both transformations can be removed.
197 snapshot->save();
198 snapshot->translate({ 0.0f, (float)get_height() });
199 snapshot->scale(1, -1);
200 snapshot->append_texture(std::move(gdktexture), Gdk::Graphene::Rect(0, 0, get_width(), get_height()).gobj());
201 snapshot->restore();
202 } else {
203 auto const cr = snapshot->append_cairo(Gdk::Graphene::Rect(0, 0, get_width(), get_height()).gobj());
204 paint_widget(cr);
205 }
206}
207
208} // namespace Inkscape::UI::Widget
209
210/*
211 Local Variables:
212 mode:c++
213 c-file-style:"stroustrup"
214 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
215 indent-tabs-mode:nil
216 fill-column:99
217 End:
218*/
219// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
Two-dimensional point with integer coordinates.
Definition int-point.h:57
std::shared_ptr< GLState > gl
Definition optglarea.h:64
virtual void paint_widget(Cairo::RefPtr< Cairo::Context > const &)
Reimplement to render the widget.
Definition optglarea.h:58
void snapshot_vfunc(Glib::RefPtr< Gtk::Snapshot > const &snapshot) override
void set_opengl_enabled(bool)
Set whether OpenGL is enabled.
Definition optglarea.cpp:91
void bind_framebuffer() const
Call before rendering to the widget to bind the widget's framebuffer.
virtual Glib::RefPtr< Gdk::GLContext > create_context()=0
Reimplement to create the desired OpenGL context.
void make_current()
Call before doing any OpenGL operations to make the context current.
Css & result
Cartesian point / 2D vector with integer coordinates.
Custom widgets.
Definition desktop.h:126
STL namespace.
int size
Glib::RefPtr< Gtk::Builder > builder