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
30namespace os {
31
32template<typename T>
33class SkiaWindowBase : public T {
34public:
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
196protected:
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
234private:
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