Flutter Impeller
playground_impl_gles.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
6 
7 #define IMPELLER_PLAYGROUND_SUPPORTS_ANGLE FML_OS_MACOSX
8 
9 #if IMPELLER_PLAYGROUND_SUPPORTS_ANGLE
10 #include <dlfcn.h>
11 #endif
12 
13 #define GLFW_INCLUDE_NONE
14 #include "third_party/glfw/include/GLFW/glfw3.h"
15 
16 #include "flutter/fml/build_config.h"
17 #include "flutter/testing/testing.h"
18 #include "impeller/entity/gles/entity_shaders_gles.h"
19 #include "impeller/entity/gles/framebuffer_blend_shaders_gles.h"
20 #include "impeller/entity/gles/modern_shaders_gles.h"
21 #include "impeller/entity/gles3/entity_shaders_gles.h"
22 #include "impeller/entity/gles3/framebuffer_blend_shaders_gles.h"
23 #include "impeller/entity/gles3/modern_shaders_gles.h"
24 #include "impeller/fixtures/gles/fixtures_shaders_gles.h"
25 #include "impeller/fixtures/gles/modern_fixtures_shaders_gles.h"
26 #include "impeller/fixtures/gles3/fixtures_shaders_gles.h"
27 #include "impeller/fixtures/gles3/modern_fixtures_shaders_gles.h"
28 #include "impeller/playground/imgui/gles/imgui_shaders_gles.h"
29 #include "impeller/playground/imgui/gles3/imgui_shaders_gles.h"
32 
33 namespace {
34 
35 class GLESPlaygroundEnvironment : public ::testing::Environment {
36  public:
37  std::unique_ptr<impeller::PlaygroundImplGLES::ShareableContext>
38  shared_context;
39  std::unique_ptr<impeller::PlaygroundImplGLES::ShareableContext>
40  shared_context_sdf;
41 };
42 
43 GLESPlaygroundEnvironment* g_gles_playground_env =
44  static_cast<GLESPlaygroundEnvironment*>(
45  ::testing::AddGlobalTestEnvironment(new GLESPlaygroundEnvironment()));
46 
47 } // namespace
48 
49 namespace impeller {
50 
52  public:
53  ReactorWorker() = default;
54 
55  // |ReactorGLES::Worker|
57  const ReactorGLES& reactor) const override {
58  ReaderLock lock(mutex_);
59  auto found = reactions_allowed_.find(std::this_thread::get_id());
60  if (found == reactions_allowed_.end()) {
61  return false;
62  }
63  return found->second;
64  }
65 
67  WriterLock lock(mutex_);
68  reactions_allowed_[std::this_thread::get_id()] = allowed;
69  }
70 
71  private:
72  mutable RWMutex mutex_;
73  std::map<std::thread::id, bool> reactions_allowed_ IPLR_GUARDED_BY(mutex_);
74 
75  ReactorWorker(const ReactorWorker&) = delete;
76 
77  ReactorWorker& operator=(const ReactorWorker&) = delete;
78 };
79 
81  public:
82  ShareableContext(UniqueHandle window,
83  std::shared_ptr<ReactorWorker> worker,
84  std::shared_ptr<ContextGLES> context,
86  : window(std::move(window)),
87  worker(std::move(worker)),
88  context(std::move(context)),
89  switches(switches) {}
90 
92  if (window) {
93  ::glfwMakeContextCurrent(window.get());
94  }
95  context.reset();
96  worker.reset();
97  window.reset();
98  }
99 
100  // This is a placeholder/dummy window. It is not rendered to by playground
101  // tests. Instead, it is created so different playground tests can create
102  // windows that share the same context.
103  // See https://www.glfw.org/docs/latest/context_guide.html#context_sharing for
104  // details.
105  UniqueHandle window = {nullptr, &DestroyWindowHandle};
106 
107  std::shared_ptr<ReactorWorker> worker;
108  std::shared_ptr<ContextGLES> context;
110 };
111 
112 void PlaygroundImplGLES::DestroyWindowHandle(WindowHandle handle) {
113  if (!handle) {
114  return;
115  }
116  ::glfwDestroyWindow(reinterpret_cast<GLFWwindow*>(handle));
117 }
118 
119 static std::vector<std::shared_ptr<fml::Mapping>>
121 
123  : PlaygroundImpl(switches),
124  handle_(nullptr, &DestroyWindowHandle),
125  use_angle_(switches.use_angle) {
126  if (use_angle_) {
127 #if IMPELLER_PLAYGROUND_SUPPORTS_ANGLE
128  angle_glesv2_ = dlopen("libGLESv2.dylib", RTLD_LAZY);
129 #endif
130  FML_CHECK(angle_glesv2_ != nullptr);
131  }
132 
133  std::unique_ptr<PlaygroundImplGLES::ShareableContext>& shared_context =
134  GetShareableContext();
135  if (!shared_context) {
136  shared_context = MakeShareableContext(switches_);
137  if (!shared_context) {
138  FML_LOG(ERROR) << "Could not create GLES context.";
139  return;
140  }
141  }
142 
143  context_ = shared_context->context;
144 
145  auto window = CreateGLWindow(switches_, shared_context->window.get());
146  handle_.reset(window);
147 
148  shared_context->context->GetGPUTracer()->Reset();
149 }
150 
151 std::unique_ptr<PlaygroundImplGLES::ShareableContext>&
152 PlaygroundImplGLES::GetShareableContext() {
154  // Caller will initialize the information in the private context field.
155  return unique_context_;
156  }
157 
158  FML_CHECK(g_gles_playground_env != nullptr);
159  std::unique_ptr<PlaygroundImplGLES::ShareableContext>& shared_context =
160  switches_.flags.use_sdfs ? g_gles_playground_env->shared_context_sdf
161  : g_gles_playground_env->shared_context;
162 
163  // If the switches have values that result in a different GLES context than
164  // the existing shared context, reset the shared context to create a new one.
165  if (shared_context && (shared_context->switches != switches_)) {
166  shared_context.reset();
167  }
168 
169  // Caller will initialize the shared information in the global shared
170  // context field.
171  return shared_context;
172 }
173 
174 GLFWwindow* PlaygroundImplGLES::CreateGLWindow(
175  const PlaygroundSwitches& switches,
176  GLFWwindow* share_window) {
177  ::glfwDefaultWindowHints();
178 
179 #if FML_OS_MACOSX
180  FML_CHECK(switches.use_angle) << "Must use Angle on macOS for OpenGL ES.";
181  ::glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
182 #endif // FML_OS_MACOSX
183 #if FML_OS_LINUX
184  // Use EGL even on X11 then the client can select the GLES implementation
185  // by defining __EGL_VENDOR_LIBRARY_FILENAMES
186  ::glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
187 #endif
188  ::glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
189  ::glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
190  ::glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
191  ::glfwWindowHint(GLFW_RED_BITS, 8);
192  ::glfwWindowHint(GLFW_GREEN_BITS, 8);
193  ::glfwWindowHint(GLFW_BLUE_BITS, 8);
194  ::glfwWindowHint(GLFW_ALPHA_BITS, 8);
195  ::glfwWindowHint(GLFW_DEPTH_BITS, 32); // 32 bit depth buffer
196  ::glfwWindowHint(GLFW_STENCIL_BITS, 8); // 8 bit stencil buffer
197  ::glfwWindowHint(GLFW_SAMPLES, 4); // 4xMSAA
198 
199  ::glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
200 #ifndef NDEBUG
201  ::glfwWindowHint(GLFW_CONTEXT_DEBUG, GLFW_TRUE);
202 #endif
203 
204  auto window = ::glfwCreateWindow(1, 1, "Test", nullptr, share_window);
205  ::glfwMakeContextCurrent(window);
206  return window;
207 }
208 
209 std::unique_ptr<PlaygroundImplGLES::ShareableContext>
210 PlaygroundImplGLES::MakeShareableContext(const PlaygroundSwitches& switches) {
211  auto window =
212  UniqueHandle(CreateGLWindow(switches, nullptr), &DestroyWindowHandle);
213  if (!window) {
214  FML_LOG(ERROR) << "Could not create GLES window.";
215  return nullptr;
216  }
217 
218  auto gl =
219  std::make_unique<ProcTableGLES>(CreateGLProcAddressResolver(switches));
220  if (!gl->IsValid()) {
221  FML_LOG(ERROR) << "Proc table when creating a playground was invalid.";
222  return nullptr;
223  }
224 
225  if (gl->GetDescription()->HasDebugExtension()) {
226  gl->DebugMessageCallbackKHR(
227  +[](GLenum /* source */, GLenum message_type, GLuint /* message_id */,
228  GLenum /* severity */, GLsizei /* length */, const GLchar* message,
229  const void* /* user_param */) {
230  switch (message_type) {
231  case GL_DEBUG_TYPE_ERROR_KHR:
232  FML_LOG(ERROR) << "GL Error: " << message;
233  return;
234  default:
235  return;
236  }
237  },
238  nullptr);
239 
240 #ifndef NDEBUG
241  gl->Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
242 #endif
243  }
244  bool is_gles3 = gl->GetDescription()->GetGlVersion().IsAtLeast(Version(3));
245  auto context_gles =
246  ContextGLES::Create(switches.flags, std::move(gl),
247  ShaderLibraryMappingsForPlayground(is_gles3), true);
248  if (!context_gles) {
249  FML_LOG(ERROR) << "Could not create context.";
250  return nullptr;
251  }
252 
253  auto worker = std::make_shared<ReactorWorker>();
254  worker->SetReactionsAllowedOnCurrentThread(true);
255 
256  // REMIND: context stores only a weak pointer.
257  auto worker_id = context_gles->AddReactorWorker(worker);
258  if (!worker_id.has_value()) {
259  FML_LOG(ERROR) << "Could not add reactor worker.";
260  return nullptr;
261  }
262 
263  return std::make_unique<ShareableContext>(std::move(window), worker,
264  context_gles, switches);
265 }
266 
268  if (context_ && handle_) {
269  ::glfwMakeContextCurrent(handle_.get());
270  [[maybe_unused]] auto result = context_->FlushCommandBuffers();
271  }
272 }
273 
274 static std::vector<std::shared_ptr<fml::Mapping>>
276  if (is_gles3) {
277  return {
278  std::make_shared<fml::NonOwnedMapping>(
279  impeller_entity_shaders_gles3_data,
280  impeller_entity_shaders_gles3_length),
281  std::make_shared<fml::NonOwnedMapping>(
282  impeller_modern_shaders_gles3_data,
283  impeller_modern_shaders_gles3_length),
284  std::make_shared<fml::NonOwnedMapping>(
285  impeller_framebuffer_blend_shaders_gles3_data,
286  impeller_framebuffer_blend_shaders_gles3_length),
287  std::make_shared<fml::NonOwnedMapping>(
288  impeller_fixtures_shaders_gles3_data,
289  impeller_fixtures_shaders_gles3_length),
290  std::make_shared<fml::NonOwnedMapping>(
291  impeller_modern_fixtures_shaders_gles3_data,
292  impeller_modern_fixtures_shaders_gles3_length),
293  std::make_shared<fml::NonOwnedMapping>(
294  impeller_imgui_shaders_gles3_data,
295  impeller_imgui_shaders_gles3_length),
296  };
297  }
298  return {
299  std::make_shared<fml::NonOwnedMapping>(
300  impeller_entity_shaders_gles_data,
301  impeller_entity_shaders_gles_length),
302  std::make_shared<fml::NonOwnedMapping>(
303  impeller_modern_shaders_gles_data,
304  impeller_modern_shaders_gles_length),
305  std::make_shared<fml::NonOwnedMapping>(
306  impeller_framebuffer_blend_shaders_gles_data,
307  impeller_framebuffer_blend_shaders_gles_length),
308  std::make_shared<fml::NonOwnedMapping>(
309  impeller_fixtures_shaders_gles_data,
310  impeller_fixtures_shaders_gles_length),
311  std::make_shared<fml::NonOwnedMapping>(
312  impeller_modern_fixtures_shaders_gles_data,
313  impeller_modern_fixtures_shaders_gles_length),
314  std::make_shared<fml::NonOwnedMapping>(
315  impeller_imgui_shaders_gles_data, impeller_imgui_shaders_gles_length),
316  };
317 }
318 
319 // |PlaygroundImpl|
320 std::shared_ptr<Context> PlaygroundImplGLES::GetContext() const {
321  return context_;
322 }
323 
324 // |PlaygroundImpl|
326 PlaygroundImplGLES::CreateGLProcAddressResolver() const {
327  return CreateGLProcAddressResolver(switches_);
328 }
329 
331 PlaygroundImplGLES::CreateGLProcAddressResolver(
332  const PlaygroundSwitches& switches) {
333  return switches.use_angle ? [](const char* name) -> void* {
334  void* symbol = nullptr;
335 #if IMPELLER_PLAYGROUND_SUPPORTS_ANGLE
336  void* angle_glesv2 = dlopen("libGLESv2.dylib", RTLD_LAZY);
337  symbol = dlsym(angle_glesv2, name);
338 #endif
339  FML_CHECK(symbol);
340  return symbol;
341  }
342  : [](const char* name) -> void* {
343  return reinterpret_cast<void*>(::glfwGetProcAddress(name));
344  };
345 }
346 
347 // |PlaygroundImpl|
348 PlaygroundImpl::WindowHandle PlaygroundImplGLES::GetWindowHandle() const {
349  return handle_.get();
350 }
351 
352 // |PlaygroundImpl|
353 std::unique_ptr<Surface> PlaygroundImplGLES::AcquireSurfaceFrame(
354  std::shared_ptr<Context> context) {
355  auto window = reinterpret_cast<GLFWwindow*>(GetWindowHandle());
356  int width = 0;
357  int height = 0;
358  ::glfwGetFramebufferSize(window, &width, &height);
359  if (width <= 0 || height <= 0) {
360  return nullptr;
361  }
362  SurfaceGLES::SwapCallback swap_callback = [window]() -> bool {
363  ::glfwSwapBuffers(window);
364  return true;
365  };
366  return SurfaceGLES::WrapFBO(context, //
367  swap_callback, //
368  0u, //
370  ISize::MakeWH(width, height) //
371  );
372 }
373 
375  const std::shared_ptr<Capabilities>& capabilities) {
376  return fml::Status(
377  fml::StatusCode::kUnimplemented,
378  "PlaygroundImplGLES doesn't support setting the capabilities.");
379 }
380 
381 RuntimeStageBackend PlaygroundImplGLES::GetRuntimeStageBackend() const {
382  const auto gl =
383  std::make_unique<ProcTableGLES>(CreateGLProcAddressResolver());
384  if (!gl->IsValid()) {
385  FML_LOG(ERROR) << "Proc table was invalid. Assuming baseline OpenGL ES";
387  }
388  bool is_gles3 = gl->GetDescription()->GetGlVersion().IsAtLeast(Version(3));
389  return is_gles3 ? RuntimeStageBackend::kOpenGLES3
391 }
392 
393 } // namespace impeller
static std::shared_ptr< ContextGLES > Create(const Flags &flags, std::unique_ptr< ProcTableGLES > gl, const std::vector< std::shared_ptr< fml::Mapping >> &shader_libraries, bool enable_gpu_tracing)
Definition: context_gles.cc:21
std::function< void *(const char *proc_name)> GLProcAddressResolver
Definition: playground.h:107
bool CanReactorReactOnCurrentThreadNow(const ReactorGLES &reactor) const override
Determines the ability of the worker to service a reaction on the current thread. The OpenGL context ...
PlaygroundImplGLES(PlaygroundSwitches switches)
fml::Status SetCapabilities(const std::shared_ptr< Capabilities > &capabilities) override
const PlaygroundSwitches switches_
A delegate implemented by a thread on which an OpenGL context is current. There may be multiple worke...
Definition: reactor_gles.h:69
The reactor attempts to make thread-safe usage of OpenGL ES easier to reason about.
Definition: reactor_gles.h:57
static std::unique_ptr< Surface > WrapFBO(const std::shared_ptr< Context > &context, SwapCallback swap_callback, GLuint fbo, PixelFormat color_format, ISize fbo_size)
Definition: surface_gles.cc:14
std::function< bool(void)> SwapCallback
Definition: surface_gles.h:19
static std::vector< std::shared_ptr< fml::Mapping > > ShaderLibraryMappingsForPlayground(bool is_gles3)
Definition: comparable.h:93
std::shared_ptr< ReactorGLES > reactor
std::shared_ptr< ContextGLES > context
bool use_sdfs
Use SDFs for rendering.
Definition: flags.h:13
ShareableContext(UniqueHandle window, std::shared_ptr< ReactorWorker > worker, std::shared_ptr< ContextGLES > context, PlaygroundSwitches switches)
static constexpr TSize MakeWH(Type width, Type height)
Definition: size.h:43