Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
render_pass_gles_unittests.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
5#include <memory>
6#include "flutter/testing/testing.h" // IWYU pragma: keep
7#include "gmock/gmock.h"
8#include "gtest/gtest.h"
21
22namespace impeller {
23namespace testing {
24
25using ::testing::_;
26using ::testing::Args;
27using ::testing::ElementsAreArray;
28using ::testing::NiceMock;
29using ::testing::Return;
30using ::testing::SetArgPointee;
31using ::testing::TestWithParam;
32
33class TestReactorGLES : public ReactorGLES {
34 public:
37
38 ~TestReactorGLES() = default;
39};
40
41class MockWorker final : public ReactorGLES::Worker {
42 public:
43 MockWorker() = default;
44
45 // |ReactorGLES::Worker|
47 const ReactorGLES& reactor) const override {
48 return true;
49 }
50};
51
54 std::array<GLenum, 3> expected_attachments;
55};
56
58 : public TestWithParam<DiscardFrameBufferParams> {};
59
60namespace {
61std::shared_ptr<ContextGLES> CreateFakeGLESContext() {
62 auto dummy_gl_procs = std::make_unique<ProcTableGLES>(kMockResolverGLES);
63 auto dummy_shader_library = std::vector<std::shared_ptr<fml::Mapping>>{};
64 auto flags = Flags{};
65 return ContextGLES::Create(flags, std::move(dummy_gl_procs),
66 dummy_shader_library, false);
67}
68} // namespace
69
71 auto mock_gl_impl = std::make_unique<NiceMock<MockGLESImpl>>();
72 auto& mock_gl_impl_ref = *mock_gl_impl;
73 auto mock_gl =
74 MockGLES::Init(std::move(mock_gl_impl), {{"GL_EXT_discard_framebuffer"}},
75 "OpenGL ES 2.0");
76
77 auto context = CreateFakeGLESContext();
78 auto dummy_worker = std::make_shared<MockWorker>();
79 context->AddReactorWorker(dummy_worker);
80 auto reactor = context->GetReactor();
81
82 const auto command_buffer =
83 std::static_pointer_cast<Context>(context)->CreateCommandBuffer();
84 auto render_target = RenderTarget{};
85 const auto description = TextureDescriptor{
86 .format = PixelFormat::kR8G8B8A8UNormInt, .size = {10, 10}};
87
88 const auto& test_params = GetParam();
89 auto framebuffer_texture =
90 TextureGLES::WrapFBO(reactor, description, test_params.frame_buffer_id);
91
92 auto color_attachment = ColorAttachment{Attachment{
93 .texture = framebuffer_texture, .store_action = StoreAction::kDontCare}};
94 render_target.SetColorAttachment(color_attachment, 0);
95 const auto render_pass = command_buffer->CreateRenderPass(render_target);
96
97 EXPECT_CALL(mock_gl_impl_ref, GetIntegerv(GL_FRAMEBUFFER_BINDING, _))
98 .WillOnce(SetArgPointee<1>(test_params.frame_buffer_id));
99
100 EXPECT_CALL(mock_gl_impl_ref, DiscardFramebufferEXT(GL_FRAMEBUFFER, _, _))
101 .With(Args<2, 1>(ElementsAreArray(test_params.expected_attachments)))
102 .Times(1);
103 ASSERT_TRUE(render_pass->EncodeCommands());
104 ASSERT_TRUE(reactor->React());
105}
106
108 FrameBufferObject,
110 ::testing::ValuesIn(std::vector<DiscardFrameBufferParams>{
111 {.frame_buffer_id = 0,
112 .expected_attachments = {GL_COLOR_EXT, GL_DEPTH_EXT, GL_STENCIL_EXT}},
113 {.frame_buffer_id = 1,
114 .expected_attachments = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT,
115 GL_STENCIL_ATTACHMENT}}}),
116 [](const ::testing::TestParamInfo<DiscardFrameBufferParams>& info) {
117 return (info.param.frame_buffer_id == 0) ? "Default" : "NonDefault";
118 });
119
121 auto mock_gl_impl = std::make_unique<NiceMock<MockGLESImpl>>();
122 auto& mock_gl_impl_ref = *mock_gl_impl;
123 auto mock_gl =
124 MockGLES::Init(std::move(mock_gl_impl), std::nullopt, "OpenGL ES 3.0");
125
126 auto context = CreateFakeGLESContext();
127 auto dummy_worker = std::make_shared<MockWorker>();
128 context->AddReactorWorker(dummy_worker);
129 auto reactor = context->GetReactor();
130
131 const auto command_buffer =
132 std::static_pointer_cast<Context>(context)->CreateCommandBuffer();
133 auto render_target = RenderTarget{};
134 const auto description = TextureDescriptor{
135 .format = PixelFormat::kR8G8B8A8UNormInt, .size = {10, 10}};
136
137 const auto& test_params = GetParam();
138 auto framebuffer_texture =
139 TextureGLES::WrapFBO(reactor, description, test_params.frame_buffer_id);
140
141 auto color_attachment = ColorAttachment{Attachment{
142 .texture = framebuffer_texture, .store_action = StoreAction::kDontCare}};
143 render_target.SetColorAttachment(color_attachment, 0);
144 const auto render_pass = command_buffer->CreateRenderPass(render_target);
145
146 EXPECT_CALL(mock_gl_impl_ref, GetIntegerv(GL_FRAMEBUFFER_BINDING, _))
147 .WillOnce(SetArgPointee<1>(test_params.frame_buffer_id));
148
149 // InvalidateFramebuffer should be called instead of DiscardFramebufferEXT
150 EXPECT_CALL(mock_gl_impl_ref, InvalidateFramebuffer(GL_FRAMEBUFFER, _, _))
151 .With(Args<2, 1>(ElementsAreArray(test_params.expected_attachments)))
152 .Times(1);
153 EXPECT_CALL(mock_gl_impl_ref, DiscardFramebufferEXT(GL_FRAMEBUFFER, _, _))
154 .Times(0);
155
156 ASSERT_TRUE(render_pass->EncodeCommands());
157 ASSERT_TRUE(reactor->React());
158}
159
160TEST(RenderPassGLESTest, ResolvingMultisampleTextureCachesResolveFBO) {
161 auto mock_gl_impl = std::make_unique<NiceMock<MockGLESImpl>>();
162 auto& mock_gl_impl_ref = *mock_gl_impl;
163 // Make sure implicit resolving isn't supported so we go down explicit path.
164 auto mock_gl =
165 MockGLES::Init(std::move(mock_gl_impl), std::nullopt, "OpenGL ES 3.0");
166
167 auto context = CreateFakeGLESContext();
168 auto dummy_worker = std::make_shared<MockWorker>();
169 context->AddReactorWorker(dummy_worker);
170 auto reactor = context->GetReactor();
171
172 const auto command_buffer =
173 std::static_pointer_cast<Context>(context)->CreateCommandBuffer();
174
175 const auto msaa_desc =
178 .size = {10, 10},
180 .sample_count = SampleCount::kCount4};
181 const auto resolve_desc =
185 .size = {10, 10},
187 .sample_count = SampleCount::kCount1};
188
189 auto msaa_tex = std::make_shared<TextureGLES>(reactor, msaa_desc);
190 auto resolve_tex = std::make_shared<TextureGLES>(reactor, resolve_desc);
191
192 auto render_target = RenderTarget{};
193 auto color_attachment = ColorAttachment{Attachment{
194 .texture = msaa_tex,
195 .resolve_texture = resolve_tex,
196 .load_action = LoadAction::kClear,
197 .store_action = StoreAction::kMultisampleResolve,
198 }};
199 color_attachment.clear_color = Color::Black();
200 render_target.SetColorAttachment(color_attachment, 0);
201
202 EXPECT_CALL(mock_gl_impl_ref, CheckFramebufferStatus(_))
203 .WillRepeatedly(Return(GL_FRAMEBUFFER_COMPLETE));
204
205 // Expect GenFramebuffers is called exactly once for the offscreen FBO,
206 // and exactly once for the resolve FBO over two passes.
207 EXPECT_CALL(mock_gl_impl_ref, GenFramebuffers(_, _)).Times(2);
208
209 {
210 const auto render_pass = command_buffer->CreateRenderPass(render_target);
211 ASSERT_TRUE(render_pass->EncodeCommands());
212 ASSERT_TRUE(reactor->React());
213 }
214 {
215 const auto render_pass2 = command_buffer->CreateRenderPass(render_target);
216 ASSERT_TRUE(render_pass2->EncodeCommands());
217 ASSERT_TRUE(reactor->React());
218 }
219}
220
221class RenderPassGLESViewportTest : public ::testing::Test {
222 protected:
224 std::shared_ptr<MockGLES> mock_gl;
225 testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref;
226 std::shared_ptr<ContextGLES> context;
227 std::shared_ptr<MockWorker> dummy_worker;
228 std::shared_ptr<ReactorGLES> reactor;
229 std::shared_ptr<CommandBuffer> command_buffer;
230 std::shared_ptr<RenderPass> render_pass;
231 std::shared_ptr<PipelineGLES> pipeline;
232 };
233
235 std::unique_ptr<NiceMock<MockGLESImpl>> mock_gl_impl =
236 std::make_unique<NiceMock<MockGLESImpl>>();
237 testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref = *mock_gl_impl;
238 std::shared_ptr<MockGLES> mock_gl = MockGLES::Init(std::move(mock_gl_impl));
239
240 std::shared_ptr<ContextGLES> context = CreateFakeGLESContext();
241 std::shared_ptr<MockWorker> dummy_worker = std::make_shared<MockWorker>();
242 context->AddReactorWorker(dummy_worker);
243 std::shared_ptr<ReactorGLES> reactor = context->GetReactor();
244
245 TextureDescriptor tex_desc;
246 tex_desc.size = {100, 100};
248 auto texture = std::make_shared<TextureGLES>(reactor, tex_desc, false);
249
251 ColorAttachment color0;
252 color0.texture = texture;
255 target.SetColorAttachment(color0, 0);
256
257 std::shared_ptr<CommandBuffer> command_buffer =
258 std::static_pointer_cast<Context>(context)->CreateCommandBuffer();
259 std::shared_ptr<RenderPass> render_pass =
260 command_buffer->CreateRenderPass(target);
261
262 EXPECT_CALL(mock_gl_impl_ref, CheckFramebufferStatus(_))
263 .WillRepeatedly(Return(GL_FRAMEBUFFER_COMPLETE));
264
266 ColorAttachmentDescriptor color0_desc;
268 desc.SetColorAttachmentDescriptor(0, color0_desc);
269
270 HandleGLES pipeline_handle = reactor->CreateHandle(HandleType::kProgram);
271 std::shared_ptr<PipelineGLES> pipeline =
272 std::shared_ptr<PipelineGLES>(new PipelineGLES(
273 reactor, std::weak_ptr<PipelineLibrary>(), desc,
274 std::make_shared<UniqueHandleGLES>(reactor, pipeline_handle)));
275 pipeline->buffer_bindings_ = std::make_unique<BufferBindingsGLES>();
276
277 return {std::move(mock_gl), mock_gl_impl_ref,
278 std::move(context), std::move(dummy_worker),
279 std::move(reactor), std::move(command_buffer),
280 std::move(render_pass), std::move(pipeline)};
281 }
282};
283
284TEST_F(RenderPassGLESViewportTest, ViewportCachedAcrossCommands) {
285 auto ctx = CreateRenderPassGLESContext();
286 testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref = ctx.mock_gl_impl_ref;
287 std::shared_ptr<RenderPass>& render_pass = ctx.render_pass;
288 std::shared_ptr<PipelineGLES>& pipeline = ctx.pipeline;
289 std::shared_ptr<ReactorGLES>& reactor = ctx.reactor;
290
291 render_pass->SetPipeline(PipelineRef(pipeline));
292 render_pass->SetElementCount(1);
293 render_pass->SetIndexBuffer({}, IndexType::kNone);
294 EXPECT_TRUE(render_pass->Draw().ok());
295
296 render_pass->SetPipeline(PipelineRef(pipeline));
297 render_pass->SetElementCount(1);
298 render_pass->SetIndexBuffer({}, IndexType::kNone);
299 render_pass->SetViewport(
300 Viewport{Rect::MakeXYWH(0, 0, 50, 50), DepthRange{0.0f, 1.0f}});
301 EXPECT_TRUE(render_pass->Draw().ok());
302
303 render_pass->SetPipeline(PipelineRef(pipeline));
304 render_pass->SetElementCount(1);
305 render_pass->SetIndexBuffer({}, IndexType::kNone);
306 render_pass->SetViewport(
307 Viewport{Rect::MakeXYWH(0, 0, 50, 50), DepthRange{0.0f, 1.0f}});
308 EXPECT_TRUE(render_pass->Draw().ok());
309
310 // Viewport should only be called twice. Once for the fallback, once for the
311 // first override. We set a catch-all to 0 to ensure no other calls occur.
312 EXPECT_CALL(mock_gl_impl_ref, Viewport(_, _, _, _)).Times(0);
313 EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 100, 100)).Times(1);
314 EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 50, 50, 50)).Times(1);
315
316 EXPECT_TRUE(render_pass->EncodeCommands());
317 EXPECT_TRUE(reactor->React());
318}
319
321 CommandsWithoutViewportGetRenderPassViewport) {
322 auto ctx = CreateRenderPassGLESContext();
323 testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref = ctx.mock_gl_impl_ref;
324 std::shared_ptr<RenderPass>& render_pass = ctx.render_pass;
325 std::shared_ptr<PipelineGLES>& pipeline = ctx.pipeline;
326 std::shared_ptr<ReactorGLES>& reactor = ctx.reactor;
327
328 render_pass->SetPipeline(PipelineRef(pipeline));
329 render_pass->SetElementCount(1);
330 render_pass->SetIndexBuffer({}, IndexType::kNone);
331 EXPECT_TRUE(render_pass->Draw().ok());
332
333 render_pass->SetPipeline(PipelineRef(pipeline));
334 render_pass->SetElementCount(1);
335 render_pass->SetIndexBuffer({}, IndexType::kNone);
336 render_pass->SetViewport(
337 Viewport{Rect::MakeXYWH(0, 0, 50, 50), DepthRange{0.0f, 1.0f}});
338 EXPECT_TRUE(render_pass->Draw().ok());
339
340 render_pass->SetPipeline(PipelineRef(pipeline));
341 render_pass->SetElementCount(1);
342 render_pass->SetIndexBuffer({}, IndexType::kNone);
343 EXPECT_TRUE(render_pass->Draw().ok());
344
345 EXPECT_CALL(mock_gl_impl_ref, Viewport(_, _, _, _)).Times(0);
346 EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 0, 100, 100)).Times(2);
347 EXPECT_CALL(mock_gl_impl_ref, Viewport(0, 50, 50, 50)).Times(1);
348
349 EXPECT_TRUE(render_pass->EncodeCommands());
350 EXPECT_TRUE(reactor->React());
351}
352
353// Sibling regression guard for the bug fixed alongside this test on the
354// Vulkan backend. The GLES backend has always honored the X offset; this
355// asserts that explicitly so a future change can't silently regress it.
356TEST_F(RenderPassGLESViewportTest, ViewportWithNonZeroXOffsetReachesGL) {
357 auto ctx = CreateRenderPassGLESContext();
358 testing::NiceMock<MockGLESImpl>& mock_gl_impl_ref = ctx.mock_gl_impl_ref;
359 std::shared_ptr<RenderPass>& render_pass = ctx.render_pass;
360 std::shared_ptr<PipelineGLES>& pipeline = ctx.pipeline;
361 std::shared_ptr<ReactorGLES>& reactor = ctx.reactor;
362
363 render_pass->SetPipeline(PipelineRef(pipeline));
364 render_pass->SetElementCount(1);
365 render_pass->SetIndexBuffer({}, IndexType::kNone);
366 render_pass->SetViewport(
367 Viewport{Rect::MakeXYWH(25, 0, 50, 100), DepthRange{0.0f, 1.0f}});
368 EXPECT_TRUE(render_pass->Draw().ok());
369
370 EXPECT_CALL(mock_gl_impl_ref, Viewport(_, _, _, _)).Times(0);
371 EXPECT_CALL(mock_gl_impl_ref, Viewport(25, 0, 50, 100)).Times(1);
372
373 EXPECT_TRUE(render_pass->EncodeCommands());
374 EXPECT_TRUE(reactor->React());
375}
376
377} // namespace testing
378} // 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)
Represents a handle to an underlying OpenGL object. Unlike OpenGL object handles, these handles can b...
Definition handle_gles.h:42
PipelineDescriptor & SetColorAttachmentDescriptor(size_t index, ColorAttachmentDescriptor desc)
A delegate implemented by a thread on which an OpenGL context is current. There may be multiple worke...
The reactor attempts to make thread-safe usage of OpenGL ES easier to reason about.
static std::shared_ptr< TextureGLES > WrapFBO(std::shared_ptr< ReactorGLES > reactor, TextureDescriptor desc, GLuint fbo)
Create a texture by wrapping an external framebuffer object whose lifecycle is owned by the caller.
static std::shared_ptr< MockGLES > Init(std::unique_ptr< MockGLESImpl > impl, const std::optional< std::vector< const char * > > &extensions=std::nullopt, const char *version_string="OpenGL ES 3.0")
Definition mock_gles.cc:360
bool CanReactorReactOnCurrentThreadNow(const ReactorGLES &reactor) const override
Determines the ability of the worker to service a reaction on the current thread. The OpenGL context ...
uint32_t * target
FlTexture * texture
TEST(FrameTimingsRecorderTest, RecordVsync)
TEST_P(AiksTest, DrawAtlasNoColor)
const ProcTableGLES::Resolver kMockResolverGLES
Definition mock_gles.cc:402
TEST_F(GoldenTests, ConicalGradient)
INSTANTIATE_TEST_SUITE_P(FrameBufferObject, RenderPassGLESWithDiscardFrameBufferExtTest, ::testing::ValuesIn(std::vector< DiscardFrameBufferParams >{ {.frame_buffer_id=0,.expected_attachments={GL_COLOR_EXT, GL_DEPTH_EXT, GL_STENCIL_EXT}}, {.frame_buffer_id=1,.expected_attachments={GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}}}), [](const ::testing::TestParamInfo< DiscardFrameBufferParams > &info) { return(info.param.frame_buffer_id==0) ? "Default" :"NonDefault";})
@ kNone
Does not use the index buffer.
raw_ptr< Pipeline< PipelineDescriptor > > PipelineRef
A raw ptr to a pipeline object.
Definition pipeline.h:89
Definition ref_ptr.h:261
LoadAction load_action
Definition formats.h:663
std::shared_ptr< Texture > texture
Definition formats.h:661
StoreAction store_action
Definition formats.h:664
Describe the color attachment that will be used with this pipeline.
Definition formats.h:522
static constexpr Color Black()
Definition color.h:271
static constexpr TRect MakeXYWH(Type x, Type y, Type width, Type height)
Definition rect.h:136
A lightweight object that describes the attributes of a texture that can then used an allocator to cr...