1 | // LAF OS Library |
2 | // Copyright (C) 2021-2022 Igara Studio S.A. |
3 | // |
4 | // This file is released under the terms of the MIT license. |
5 | // Read LICENSE.txt for more information. |
6 | |
7 | #ifndef OS_SKIA_SKIA_WINDOW_BASE_INCLUDED |
8 | #define OS_SKIA_SKIA_WINDOW_BASE_INCLUDED |
9 | #pragma once |
10 | |
11 | #include "os/event.h" |
12 | #include "os/event_queue.h" |
13 | #include "os/gl/gl_context.h" |
14 | #include "os/skia/skia_gl.h" |
15 | #include "os/skia/skia_surface.h" |
16 | #include "os/system.h" |
17 | #include "os/window.h" |
18 | |
19 | #include "include/core/SkCanvas.h" |
20 | |
21 | #if SK_SUPPORT_GPU |
22 | #if LAF_WINDOWS |
23 | #include <windows.h> |
24 | #include <GL/gl.h> |
25 | #elif LAF_MACOS |
26 | #include <OpenGL/gl.h> |
27 | #endif |
28 | #endif |
29 | |
30 | namespace os { |
31 | |
32 | template<typename T> |
33 | class SkiaWindowBase : public T { |
34 | public: |
35 | using Base = SkiaWindowBase<T>; |
36 | |
37 | template<typename... Args> |
38 | SkiaWindowBase(Args&&... args) |
39 | : T(std::forward<Args&&>(args)...) |
40 | , m_initialized(false) |
41 | , m_surface(new SkiaSurface) |
42 | , m_colorSpace(nullptr) { |
43 | } |
44 | |
45 | void initColorSpace() { |
46 | // Needed on macOS because WindowOSX::colorSpace() needs the |
47 | // m_nsWindow created, and that happens after |
48 | // WindowOSX::createWindow() is called. |
49 | m_colorSpace = T::colorSpace(); |
50 | } |
51 | |
52 | bool isInitialized() const { |
53 | return m_initialized; |
54 | } |
55 | |
56 | void resetSkiaSurface() { |
57 | if (m_surface) |
58 | m_surface.reset(); |
59 | |
60 | resizeSkiaSurface(this->clientSize()); |
61 | } |
62 | |
63 | void resizeSkiaSurface(const gfx::Size& size) { |
64 | if (!m_initialized) |
65 | return; |
66 | |
67 | gfx::Size newSize(size.w / this->scale(), |
68 | size.h / this->scale()); |
69 | newSize.w = std::max(1, newSize.w); |
70 | newSize.h = std::max(1, newSize.h); |
71 | |
72 | if (m_initialized && |
73 | m_surface && |
74 | m_surface->width() == newSize.w && |
75 | m_surface->height() == newSize.h) { |
76 | return; |
77 | } |
78 | |
79 | m_backend = Backend::NONE; |
80 | m_surface.reset(); |
81 | |
82 | #if SK_SUPPORT_GPU |
83 | // Re-create OpenGL context |
84 | m_gl.detachGL(); |
85 | if (m_glCtx->isValid()) |
86 | m_glCtx->destroyGLContext(); |
87 | |
88 | // GPU-accelerated surface |
89 | if (os::instance()->gpuAcceleration()) { |
90 | m_glCtx->createGLContext(); |
91 | |
92 | if (m_glCtx->isValid()) { |
93 | m_glCtx->makeCurrent(); |
94 | |
95 | if (m_gl.attachGL() && |
96 | m_gl.createRenderTarget( |
97 | size, this->scale(), |
98 | ((SkiaColorSpace*)colorSpace().get())->skColorSpace())) { |
99 | m_surface = make_ref<SkiaSurface>(m_gl.surface()); |
100 | m_backend = Backend::GL; |
101 | } |
102 | } |
103 | } |
104 | #endif // SK_SUPPORT_GPU |
105 | |
106 | // Raster surface |
107 | if (!m_surface) { |
108 | m_surface = make_ref<SkiaSurface>(); |
109 | |
110 | if (T::isTransparent()) |
111 | m_surface->createRgba(newSize.w, newSize.h, m_colorSpace); |
112 | else |
113 | m_surface->create(newSize.w, newSize.h, m_colorSpace); |
114 | } |
115 | } |
116 | |
117 | // Returns the main surface to draw into this window. |
118 | // You must not dispose this surface. |
119 | Surface* surface() override { |
120 | return m_surface.get(); |
121 | } |
122 | |
123 | // Overrides the colorSpace() method to return the cached/stored |
124 | // color space in this instance (instead of asking for the color |
125 | // space to the screen as T::colorSpace() should do). |
126 | os::ColorSpaceRef colorSpace() const override { |
127 | return m_colorSpace; |
128 | } |
129 | |
130 | void setColorSpace(const os::ColorSpaceRef& colorSpace) override { |
131 | if (colorSpace) |
132 | m_colorSpace = colorSpace; |
133 | else |
134 | m_colorSpace = T::colorSpace(); // Screen color space |
135 | |
136 | if (m_surface) |
137 | resetSkiaSurface(); |
138 | |
139 | // Generate the resizing window event to redraw everything. |
140 | // TODO we could create a new event like Event::ColorSpaceChange, |
141 | // but the result would be the same, the window must be re-painted. |
142 | Event ev; |
143 | ev.setType(Event::ResizeWindow); |
144 | ev.setWindow(AddRef(this)); |
145 | os::queue_event(ev); |
146 | } |
147 | |
148 | void swapBuffers() override { |
149 | #if SK_SUPPORT_GPU |
150 | if (m_backend == Backend::NONE || |
151 | !m_gl.backbufferSurface() || |
152 | !m_glCtx->isValid()) |
153 | return; |
154 | |
155 | auto surface = static_cast<SkiaSurface*>(this->surface()); |
156 | if (!surface) |
157 | return; |
158 | |
159 | m_glCtx->makeCurrent(); |
160 | |
161 | // Draw the small (unscaled) surface to the backbuffer surface |
162 | // scaling it to the this->scale() factor. |
163 | if (m_gl.backbufferSurface() != m_gl.surface()) { |
164 | SkSamplingOptions sampling; |
165 | SkPaint paint; |
166 | |
167 | SkCanvas* dstCanvas = m_gl.backbufferSurface()->getCanvas(); |
168 | dstCanvas->save(); |
169 | dstCanvas->scale(SkScalar(this->scale()), |
170 | SkScalar(this->scale())); |
171 | m_gl.surface()->draw( |
172 | dstCanvas, |
173 | 0.0, 0.0, sampling, &paint); |
174 | dstCanvas->restore(); |
175 | } |
176 | |
177 | surface->flushAndSubmit(); |
178 | m_glCtx->swapBuffers(); |
179 | #endif // SK_SUPPORT_GPU |
180 | } |
181 | |
182 | bool isGpuAccelerated() const override { |
183 | #if SK_SUPPORT_GPU |
184 | return (m_backend == Backend::GL); |
185 | #else |
186 | return false; |
187 | #endif |
188 | } |
189 | |
190 | #if SK_SUPPORT_GPU |
191 | GrDirectContext* sk_grCtx() const override { |
192 | return m_gl.grCtx(); |
193 | } |
194 | #endif |
195 | |
196 | protected: |
197 | void initializeSurface() { |
198 | m_initialized = true; |
199 | resetSkiaSurface(); |
200 | } |
201 | |
202 | void onResize(const gfx::Size& sz) override { |
203 | resizeSkiaSurface(sz); |
204 | |
205 | if (os::instance()->handleWindowResize && |
206 | // Check that the surface is created to avoid a call to |
207 | // handleWindowResize() with an empty surface (or null |
208 | // SkiaSurface::m_canvas) when the window is being created. |
209 | isInitialized()) { |
210 | os::instance()->handleWindowResize(this); |
211 | } |
212 | else { |
213 | Event ev; |
214 | ev.setType(Event::ResizeWindow); |
215 | ev.setWindow(AddRef(this)); |
216 | queue_event(ev); |
217 | } |
218 | } |
219 | |
220 | enum class Backend { |
221 | NONE, |
222 | #if SK_SUPPORT_GPU |
223 | GL, |
224 | #endif |
225 | }; |
226 | |
227 | Backend backend() const { return m_backend; } |
228 | |
229 | #if SK_SUPPORT_GPU |
230 | std::unique_ptr<GLContext> m_glCtx; |
231 | SkiaGL m_gl; |
232 | #endif |
233 | |
234 | private: |
235 | Backend m_backend = Backend::NONE; |
236 | // Flag used to avoid accessing to an invalid m_surface in the first |
237 | // SkiaWindow::resize() call when the window is created (as the |
238 | // window is created, it send a first resize event.) |
239 | bool m_initialized; |
240 | Ref<SkiaSurface> m_surface; |
241 | os::ColorSpaceRef m_colorSpace; |
242 | }; |
243 | |
244 | } // namespace os |
245 | |
246 | #endif |
247 | |