Make FlRenderable interface (flutter/engine#55763)
Added to make testing of FlRenderer easier.
This commit is contained in:
parent
e5ecdd9c94
commit
b857ade17e
@ -44834,6 +44834,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registrar.cc + ../../../
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registrar_private.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registrar_test.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_plugin_registry.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_renderable.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_renderable.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_renderer.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_renderer.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/linux/fl_renderer_gdk.cc + ../../../flutter/LICENSE
|
||||
@ -47739,6 +47741,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar.cc
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar_private.h
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_plugin_registrar_test.cc
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_plugin_registry.cc
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_renderable.cc
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_renderable.h
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_renderer.cc
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_renderer.h
|
||||
FILE: ../../../flutter/shell/platform/linux/fl_renderer_gdk.cc
|
||||
|
@ -129,6 +129,7 @@ source_set("flutter_linux_sources") {
|
||||
"fl_platform_handler.cc",
|
||||
"fl_plugin_registrar.cc",
|
||||
"fl_plugin_registry.cc",
|
||||
"fl_renderable.cc",
|
||||
"fl_renderer.cc",
|
||||
"fl_renderer_gdk.cc",
|
||||
"fl_renderer_headless.cc",
|
||||
|
15
engine/src/flutter/shell/platform/linux/fl_renderable.cc
Normal file
15
engine/src/flutter/shell/platform/linux/fl_renderable.cc
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_renderable.h"
|
||||
|
||||
G_DEFINE_INTERFACE(FlRenderable, fl_renderable, G_TYPE_OBJECT)
|
||||
|
||||
static void fl_renderable_default_init(FlRenderableInterface* iface) {}
|
||||
|
||||
void fl_renderable_redraw(FlRenderable* self) {
|
||||
g_return_if_fail(FL_IS_RENDERABLE(self));
|
||||
|
||||
FL_RENDERABLE_GET_IFACE(self)->redraw(self);
|
||||
}
|
42
engine/src/flutter/shell/platform/linux/fl_renderable.h
Normal file
42
engine/src/flutter/shell/platform/linux/fl_renderable.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERABLE_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERABLE_H_
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_DECLARE_INTERFACE(FlRenderable, fl_renderable, FL, RENDERABLE, GObject);
|
||||
|
||||
/**
|
||||
* FlRenderable:
|
||||
*
|
||||
* An interface for a class that can render views from #FlRenderer.
|
||||
*
|
||||
* This interface is typically implemented by #FlView and is provided to make
|
||||
* #FlRenderer easier to test.
|
||||
*/
|
||||
|
||||
struct _FlRenderableInterface {
|
||||
GTypeInterface g_iface;
|
||||
|
||||
void (*redraw)(FlRenderable* renderable);
|
||||
};
|
||||
|
||||
/**
|
||||
* fl_renderable_redraw:
|
||||
* @renderable: an #FlRenderable
|
||||
*
|
||||
* Indicate the renderable needs to redraw. When ready, the renderable should
|
||||
* call fl_renderer_draw().
|
||||
*/
|
||||
void fl_renderable_redraw(FlRenderable* renderable);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_RENDERABLE_H_
|
@ -10,7 +10,6 @@
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/linux/fl_engine_private.h"
|
||||
#include "flutter/shell/platform/linux/fl_framebuffer.h"
|
||||
#include "flutter/shell/platform/linux/fl_view_private.h"
|
||||
|
||||
// Vertex shader to draw Flutter window contents.
|
||||
static const char* vertex_shader_src =
|
||||
@ -77,6 +76,12 @@ typedef struct {
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE(FlRenderer, fl_renderer, G_TYPE_OBJECT)
|
||||
|
||||
static void free_weak_ref(gpointer value) {
|
||||
GWeakRef* ref = static_cast<GWeakRef*>(value);
|
||||
g_weak_ref_clear(ref);
|
||||
free(ref);
|
||||
}
|
||||
|
||||
// Check if running on an NVIDIA driver.
|
||||
static gboolean is_nvidia() {
|
||||
const gchar* vendor = reinterpret_cast<const gchar*>(glGetString(GL_VENDOR));
|
||||
@ -295,8 +300,8 @@ static void fl_renderer_class_init(FlRendererClass* klass) {
|
||||
static void fl_renderer_init(FlRenderer* self) {
|
||||
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
|
||||
fl_renderer_get_instance_private(self));
|
||||
priv->views =
|
||||
g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr, nullptr);
|
||||
priv->views = g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
|
||||
free_weak_ref);
|
||||
priv->framebuffers_by_view_id =
|
||||
g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
|
||||
(GDestroyNotify)g_ptr_array_unref);
|
||||
@ -311,15 +316,17 @@ void fl_renderer_set_engine(FlRenderer* self, FlEngine* engine) {
|
||||
g_weak_ref_init(&priv->engine, engine);
|
||||
}
|
||||
|
||||
void fl_renderer_add_view(FlRenderer* self,
|
||||
FlutterViewId view_id,
|
||||
FlView* view) {
|
||||
void fl_renderer_add_renderable(FlRenderer* self,
|
||||
FlutterViewId view_id,
|
||||
FlRenderable* renderable) {
|
||||
FlRendererPrivate* priv = reinterpret_cast<FlRendererPrivate*>(
|
||||
fl_renderer_get_instance_private(self));
|
||||
|
||||
g_return_if_fail(FL_IS_RENDERER(self));
|
||||
|
||||
g_hash_table_insert(priv->views, GINT_TO_POINTER(view_id), view);
|
||||
GWeakRef* ref = g_new(GWeakRef, 1);
|
||||
g_weak_ref_init(ref, G_OBJECT(renderable));
|
||||
g_hash_table_insert(priv->views, GINT_TO_POINTER(view_id), ref);
|
||||
}
|
||||
|
||||
void fl_renderer_remove_view(FlRenderer* self, FlutterViewId view_id) {
|
||||
@ -472,10 +479,12 @@ gboolean fl_renderer_present_layers(FlRenderer* self,
|
||||
}
|
||||
}
|
||||
|
||||
FlView* view =
|
||||
FL_VIEW(g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id)));
|
||||
if (view != nullptr) {
|
||||
fl_view_redraw(view);
|
||||
GWeakRef* ref = static_cast<GWeakRef*>(
|
||||
g_hash_table_lookup(priv->views, GINT_TO_POINTER(view_id)));
|
||||
g_autoptr(FlRenderable) renderable =
|
||||
ref != nullptr ? FL_RENDERABLE(g_weak_ref_get(ref)) : nullptr;
|
||||
if (renderable != nullptr) {
|
||||
fl_renderable_redraw(renderable);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
@ -7,11 +7,11 @@
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_dart_project.h"
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
|
||||
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_renderable.h"
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
@ -79,16 +79,16 @@ struct _FlRendererClass {
|
||||
void fl_renderer_set_engine(FlRenderer* renderer, FlEngine* engine);
|
||||
|
||||
/**
|
||||
* fl_renderer_add_view:
|
||||
* fl_renderer_add_renderable:
|
||||
* @renderer: an #FlRenderer.
|
||||
* @view_id: the ID of the view.
|
||||
* @view: the view Flutter is renderering to.
|
||||
* @renderable: object that is to be rendered on.
|
||||
*
|
||||
* Add a view to render on.
|
||||
*/
|
||||
void fl_renderer_add_view(FlRenderer* renderer,
|
||||
FlutterViewId view_id,
|
||||
FlView* view);
|
||||
void fl_renderer_add_renderable(FlRenderer* renderer,
|
||||
FlutterViewId view_id,
|
||||
FlRenderable* renderable);
|
||||
|
||||
/**
|
||||
* fl_renderer_remove_view:
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/shell/platform/linux/fl_framebuffer.h"
|
||||
#include "flutter/shell/platform/linux/testing/fl_test_gtk_logs.h"
|
||||
#include "flutter/shell/platform/linux/testing/mock_epoxy.h"
|
||||
#include "flutter/shell/platform/linux/testing/mock_renderer.h"
|
||||
|
||||
@ -48,14 +47,12 @@ TEST(FlRendererTest, RestoresGLState) {
|
||||
constexpr int kWidth = 100;
|
||||
constexpr int kHeight = 100;
|
||||
|
||||
flutter::testing::fl_ensure_gtk_init();
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
g_autoptr(FlView) view = fl_view_new(project);
|
||||
g_autoptr(FlRenderable) renderable = FL_RENDERABLE(fl_mock_renderable_new());
|
||||
g_autoptr(FlMockRenderer) renderer = fl_mock_renderer_new();
|
||||
g_autoptr(FlFramebuffer) framebuffer =
|
||||
fl_framebuffer_new(GL_RGB, kWidth, kHeight);
|
||||
|
||||
fl_renderer_add_view(FL_RENDERER(renderer), 0, view);
|
||||
fl_renderer_add_renderable(FL_RENDERER(renderer), 0, renderable);
|
||||
fl_renderer_wait_for_frame(FL_RENDERER(renderer), kWidth, kHeight);
|
||||
|
||||
FlutterBackingStore backing_store;
|
||||
@ -84,8 +81,6 @@ TEST(FlRendererTest, RestoresGLState) {
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D,
|
||||
reinterpret_cast<GLint*>(&texture_2d_binding));
|
||||
EXPECT_EQ(texture_2d_binding, kFakeTextureName);
|
||||
|
||||
g_object_ref_sink(view);
|
||||
}
|
||||
|
||||
static constexpr double kExpectedRefreshRate = 120.0;
|
||||
@ -94,8 +89,6 @@ static gdouble renderer_get_refresh_rate(FlRenderer* renderer) {
|
||||
}
|
||||
|
||||
TEST(FlRendererTest, RefreshRate) {
|
||||
flutter::testing::fl_ensure_gtk_init();
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
g_autoptr(FlMockRenderer) renderer =
|
||||
fl_mock_renderer_new(&renderer_get_refresh_rate);
|
||||
|
||||
|
@ -91,6 +91,8 @@ enum { kSignalFirstFrame, kSignalLastSignal };
|
||||
|
||||
static guint fl_view_signals[kSignalLastSignal];
|
||||
|
||||
static void fl_renderable_iface_init(FlRenderableInterface* iface);
|
||||
|
||||
static void fl_view_plugin_registry_iface_init(
|
||||
FlPluginRegistryInterface* iface);
|
||||
|
||||
@ -107,14 +109,16 @@ G_DEFINE_TYPE_WITH_CODE(
|
||||
FlView,
|
||||
fl_view,
|
||||
GTK_TYPE_BOX,
|
||||
G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
|
||||
fl_view_plugin_registry_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
|
||||
fl_view_keyboard_delegate_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(),
|
||||
fl_view_scrolling_delegate_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(),
|
||||
fl_view_text_input_delegate_iface_init))
|
||||
G_IMPLEMENT_INTERFACE(fl_renderable_get_type(), fl_renderable_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
|
||||
fl_view_plugin_registry_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(),
|
||||
fl_view_keyboard_delegate_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(),
|
||||
fl_view_scrolling_delegate_iface_init)
|
||||
G_IMPLEMENT_INTERFACE(
|
||||
fl_text_input_view_delegate_get_type(),
|
||||
fl_view_text_input_delegate_iface_init))
|
||||
|
||||
// Emit the first frame signal in the main thread.
|
||||
static gboolean first_frame_idle_cb(gpointer user_data) {
|
||||
@ -317,6 +321,20 @@ static void on_pre_engine_restart_cb(FlView* self) {
|
||||
init_scrolling(self);
|
||||
}
|
||||
|
||||
// Implements FlRenderable::redraw
|
||||
static void fl_view_redraw(FlRenderable* renderable) {
|
||||
FlView* self = FL_VIEW(renderable);
|
||||
|
||||
gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
|
||||
|
||||
if (!self->have_first_frame) {
|
||||
self->have_first_frame = TRUE;
|
||||
// This is not the main thread, so the signal needs to be done via an idle
|
||||
// callback.
|
||||
g_idle_add(first_frame_idle_cb, self);
|
||||
}
|
||||
}
|
||||
|
||||
// Implements FlPluginRegistry::get_registrar_for_plugin.
|
||||
static FlPluginRegistrar* fl_view_get_registrar_for_plugin(
|
||||
FlPluginRegistry* registry,
|
||||
@ -328,6 +346,10 @@ static FlPluginRegistrar* fl_view_get_registrar_for_plugin(
|
||||
fl_engine_get_texture_registrar(self->engine));
|
||||
}
|
||||
|
||||
static void fl_renderable_iface_init(FlRenderableInterface* iface) {
|
||||
iface->redraw = fl_view_redraw;
|
||||
}
|
||||
|
||||
static void fl_view_plugin_registry_iface_init(
|
||||
FlPluginRegistryInterface* iface) {
|
||||
iface->get_registrar_for_plugin = fl_view_get_registrar_for_plugin;
|
||||
@ -621,7 +643,8 @@ static void realize_cb(FlView* self) {
|
||||
|
||||
init_keyboard(self);
|
||||
|
||||
fl_renderer_add_view(FL_RENDERER(self->renderer), self->view_id, self);
|
||||
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
|
||||
FL_RENDERABLE(self));
|
||||
|
||||
if (!fl_engine_start(self->engine, &error)) {
|
||||
g_warning("Failed to start Flutter engine: %s", error->message);
|
||||
@ -866,7 +889,8 @@ G_MODULE_EXPORT FlView* fl_view_new_for_engine(FlEngine* engine) {
|
||||
|
||||
self->view_id = fl_engine_add_view(self->engine, 1, 1, 1.0, self->cancellable,
|
||||
view_added_cb, self);
|
||||
fl_renderer_add_view(FL_RENDERER(self->renderer), self->view_id, self);
|
||||
fl_renderer_add_renderable(FL_RENDERER(self->renderer), self->view_id,
|
||||
FL_RENDERABLE(self));
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -889,19 +913,6 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self,
|
||||
self->background_color = gdk_rgba_copy(color);
|
||||
}
|
||||
|
||||
void fl_view_redraw(FlView* self) {
|
||||
g_return_if_fail(FL_IS_VIEW(self));
|
||||
|
||||
gtk_widget_queue_draw(GTK_WIDGET(self->gl_area));
|
||||
|
||||
if (!self->have_first_frame) {
|
||||
self->have_first_frame = TRUE;
|
||||
// This is not the main thread, so the signal needs to be done via an idle
|
||||
// callback.
|
||||
g_idle_add(first_frame_idle_cb, self);
|
||||
}
|
||||
}
|
||||
|
||||
GHashTable* fl_view_get_keyboard_state(FlView* self) {
|
||||
g_return_val_if_fail(FL_IS_VIEW(self), nullptr);
|
||||
return fl_keyboard_handler_get_pressed_state(self->keyboard_handler);
|
||||
|
@ -7,14 +7,6 @@
|
||||
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
|
||||
|
||||
/**
|
||||
* fl_view_redraw:
|
||||
* @view: an #FlView.
|
||||
*
|
||||
* Indicate the view needs to redraw.
|
||||
*/
|
||||
void fl_view_redraw(FlView* view);
|
||||
|
||||
/**
|
||||
* fl_view_get_keyboard_state:
|
||||
* @view: an #FlView.
|
||||
|
@ -50,7 +50,7 @@ TEST(FlViewTest, FirstFrameSignal) {
|
||||
|
||||
EXPECT_FALSE(first_frame_emitted);
|
||||
|
||||
fl_view_redraw(view);
|
||||
fl_renderable_redraw(FL_RENDERABLE(view));
|
||||
|
||||
// Signal is emitted in idle, clear the main loop.
|
||||
while (g_main_context_iteration(g_main_context_default(), FALSE)) {
|
||||
|
@ -9,8 +9,20 @@ struct _FlMockRenderer {
|
||||
FlMockRendererGetRefreshRate get_refresh_rate;
|
||||
};
|
||||
|
||||
struct _FlMockRenderable {
|
||||
GObject parent_instance;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(FlMockRenderer, fl_mock_renderer, fl_renderer_get_type())
|
||||
|
||||
static void mock_renderable_iface_init(FlRenderableInterface* iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE(FlMockRenderable,
|
||||
fl_mock_renderable,
|
||||
g_object_get_type(),
|
||||
G_IMPLEMENT_INTERFACE(fl_renderable_get_type(),
|
||||
mock_renderable_iface_init))
|
||||
|
||||
// Implements FlRenderer::make_current.
|
||||
static void fl_mock_renderer_make_current(FlRenderer* renderer) {}
|
||||
|
||||
@ -40,11 +52,27 @@ static void fl_mock_renderer_class_init(FlMockRendererClass* klass) {
|
||||
|
||||
static void fl_mock_renderer_init(FlMockRenderer* self) {}
|
||||
|
||||
static void mock_renderable_redraw(FlRenderable* renderable) {}
|
||||
|
||||
static void mock_renderable_iface_init(FlRenderableInterface* iface) {
|
||||
iface->redraw = mock_renderable_redraw;
|
||||
}
|
||||
|
||||
static void fl_mock_renderable_class_init(FlMockRenderableClass* klass) {}
|
||||
|
||||
static void fl_mock_renderable_init(FlMockRenderable* self) {}
|
||||
|
||||
// Creates a stub renderer
|
||||
FlMockRenderer* fl_mock_renderer_new(
|
||||
FlMockRendererGetRefreshRate get_refresh_rate) {
|
||||
FlMockRenderer* fl_mock_renderer = FL_MOCK_RENDERER(
|
||||
g_object_new_valist(fl_mock_renderer_get_type(), nullptr, nullptr));
|
||||
fl_mock_renderer->get_refresh_rate = get_refresh_rate;
|
||||
return fl_mock_renderer;
|
||||
FlMockRenderer* self =
|
||||
FL_MOCK_RENDERER(g_object_new(fl_mock_renderer_get_type(), nullptr));
|
||||
self->get_refresh_rate = get_refresh_rate;
|
||||
return self;
|
||||
}
|
||||
|
||||
// Creates a sub renderable.
|
||||
FlMockRenderable* fl_mock_renderable_new() {
|
||||
return FL_MOCK_RENDERABLE(
|
||||
g_object_new(fl_mock_renderable_get_type(), nullptr));
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_RENDERER_H_
|
||||
#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_RENDERER_H_
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_renderable.h"
|
||||
#include "flutter/shell/platform/linux/fl_renderer.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@ -15,11 +16,19 @@ G_DECLARE_FINAL_TYPE(FlMockRenderer,
|
||||
MOCK_RENDERER,
|
||||
FlRenderer)
|
||||
|
||||
G_DECLARE_FINAL_TYPE(FlMockRenderable,
|
||||
fl_mock_renderable,
|
||||
FL,
|
||||
MOCK_RENDERABLE,
|
||||
GObject)
|
||||
|
||||
typedef gdouble (*FlMockRendererGetRefreshRate)(FlRenderer* renderer);
|
||||
|
||||
FlMockRenderer* fl_mock_renderer_new(
|
||||
FlMockRendererGetRefreshRate get_refresh_rate = nullptr);
|
||||
|
||||
FlMockRenderable* fl_mock_renderable_new();
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_RENDERER_H_
|
||||
|
Loading…
x
Reference in New Issue
Block a user