|  | // 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 "impeller/renderer/backend/gles/render_pass_gles.h" | 
|  |  | 
|  | #include <cstdint> | 
|  |  | 
|  | #include "GLES3/gl3.h" | 
|  | #include "flutter/fml/trace_event.h" | 
|  | #include "fml/closure.h" | 
|  | #include "fml/logging.h" | 
|  | #include "impeller/base/validation.h" | 
|  | #include "impeller/renderer/backend/gles/context_gles.h" | 
|  | #include "impeller/renderer/backend/gles/device_buffer_gles.h" | 
|  | #include "impeller/renderer/backend/gles/formats_gles.h" | 
|  | #include "impeller/renderer/backend/gles/gpu_tracer_gles.h" | 
|  | #include "impeller/renderer/backend/gles/pipeline_gles.h" | 
|  | #include "impeller/renderer/backend/gles/texture_gles.h" | 
|  |  | 
|  | namespace impeller { | 
|  |  | 
|  | RenderPassGLES::RenderPassGLES(std::shared_ptr<const Context> context, | 
|  | const RenderTarget& target, | 
|  | ReactorGLES::Ref reactor) | 
|  | : RenderPass(std::move(context), target), | 
|  | reactor_(std::move(reactor)), | 
|  | is_valid_(reactor_ && reactor_->IsValid()) {} | 
|  |  | 
|  | // |RenderPass| | 
|  | RenderPassGLES::~RenderPassGLES() = default; | 
|  |  | 
|  | // |RenderPass| | 
|  | bool RenderPassGLES::IsValid() const { | 
|  | return is_valid_; | 
|  | } | 
|  |  | 
|  | // |RenderPass| | 
|  | void RenderPassGLES::OnSetLabel(std::string label) { | 
|  | label_ = std::move(label); | 
|  | } | 
|  |  | 
|  | void ConfigureBlending(const ProcTableGLES& gl, | 
|  | const ColorAttachmentDescriptor* color) { | 
|  | if (color->blending_enabled) { | 
|  | gl.Enable(GL_BLEND); | 
|  | gl.BlendFuncSeparate( | 
|  | ToBlendFactor(color->src_color_blend_factor),  // src color | 
|  | ToBlendFactor(color->dst_color_blend_factor),  // dst color | 
|  | ToBlendFactor(color->src_alpha_blend_factor),  // src alpha | 
|  | ToBlendFactor(color->dst_alpha_blend_factor)   // dst alpha | 
|  | ); | 
|  | gl.BlendEquationSeparate( | 
|  | ToBlendOperation(color->color_blend_op),  // mode color | 
|  | ToBlendOperation(color->alpha_blend_op)   // mode alpha | 
|  | ); | 
|  | } else { | 
|  | gl.Disable(GL_BLEND); | 
|  | } | 
|  |  | 
|  | { | 
|  | const auto is_set = [](ColorWriteMask mask, | 
|  | ColorWriteMask check) -> GLboolean { | 
|  | return (mask & check) ? GL_TRUE : GL_FALSE; | 
|  | }; | 
|  |  | 
|  | gl.ColorMask( | 
|  | is_set(color->write_mask, ColorWriteMaskBits::kRed),    // red | 
|  | is_set(color->write_mask, ColorWriteMaskBits::kGreen),  // green | 
|  | is_set(color->write_mask, ColorWriteMaskBits::kBlue),   // blue | 
|  | is_set(color->write_mask, ColorWriteMaskBits::kAlpha)   // alpha | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConfigureStencil(GLenum face, | 
|  | const ProcTableGLES& gl, | 
|  | const StencilAttachmentDescriptor& stencil, | 
|  | uint32_t stencil_reference) { | 
|  | gl.StencilOpSeparate( | 
|  | face,                                    // face | 
|  | ToStencilOp(stencil.stencil_failure),    // stencil fail | 
|  | ToStencilOp(stencil.depth_failure),      // depth fail | 
|  | ToStencilOp(stencil.depth_stencil_pass)  // depth stencil pass | 
|  | ); | 
|  | gl.StencilFuncSeparate(face,                                        // face | 
|  | ToCompareFunction(stencil.stencil_compare),  // func | 
|  | stencil_reference,                           // ref | 
|  | stencil.read_mask                            // mask | 
|  | ); | 
|  | gl.StencilMaskSeparate(face, stencil.write_mask); | 
|  | } | 
|  |  | 
|  | void ConfigureStencil(const ProcTableGLES& gl, | 
|  | const PipelineDescriptor& pipeline, | 
|  | uint32_t stencil_reference) { | 
|  | if (!pipeline.HasStencilAttachmentDescriptors()) { | 
|  | gl.Disable(GL_STENCIL_TEST); | 
|  | return; | 
|  | } | 
|  |  | 
|  | gl.Enable(GL_STENCIL_TEST); | 
|  | const auto& front = pipeline.GetFrontStencilAttachmentDescriptor(); | 
|  | const auto& back = pipeline.GetBackStencilAttachmentDescriptor(); | 
|  |  | 
|  | if (front.has_value() && back.has_value() && front == back) { | 
|  | ConfigureStencil(GL_FRONT_AND_BACK, gl, *front, stencil_reference); | 
|  | return; | 
|  | } | 
|  | if (front.has_value()) { | 
|  | ConfigureStencil(GL_FRONT, gl, *front, stencil_reference); | 
|  | } | 
|  | if (back.has_value()) { | 
|  | ConfigureStencil(GL_BACK, gl, *back, stencil_reference); | 
|  | } | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | /// @brief      Encapsulates data that will be needed in the reactor for the | 
|  | ///             encoding of commands for this render pass. | 
|  | /// | 
|  | struct RenderPassData { | 
|  | Viewport viewport; | 
|  |  | 
|  | Color clear_color; | 
|  | uint32_t clear_stencil = 0u; | 
|  | Scalar clear_depth = 1.0; | 
|  |  | 
|  | std::shared_ptr<Texture> color_attachment; | 
|  | std::shared_ptr<Texture> depth_attachment; | 
|  | std::shared_ptr<Texture> stencil_attachment; | 
|  |  | 
|  | bool clear_color_attachment = true; | 
|  | bool clear_depth_attachment = true; | 
|  | bool clear_stencil_attachment = true; | 
|  |  | 
|  | bool discard_color_attachment = true; | 
|  | bool discard_depth_attachment = true; | 
|  | bool discard_stencil_attachment = true; | 
|  |  | 
|  | std::string label; | 
|  | }; | 
|  |  | 
|  | [[nodiscard]] bool EncodeCommandsInReactor( | 
|  | const RenderPassData& pass_data, | 
|  | const std::shared_ptr<Allocator>& transients_allocator, | 
|  | const ReactorGLES& reactor, | 
|  | const std::vector<Command>& commands, | 
|  | const std::shared_ptr<GPUTracerGLES>& tracer) { | 
|  | TRACE_EVENT0("impeller", "RenderPassGLES::EncodeCommandsInReactor"); | 
|  |  | 
|  | const auto& gl = reactor.GetProcTable(); | 
|  | #ifdef IMPELLER_DEBUG | 
|  | tracer->MarkFrameStart(gl); | 
|  | #endif  // IMPELLER_DEBUG | 
|  |  | 
|  | fml::ScopedCleanupClosure pop_pass_debug_marker( | 
|  | [&gl]() { gl.PopDebugGroup(); }); | 
|  | if (!pass_data.label.empty()) { | 
|  | gl.PushDebugGroup(pass_data.label); | 
|  | } else { | 
|  | pop_pass_debug_marker.Release(); | 
|  | } | 
|  |  | 
|  | GLuint fbo = GL_NONE; | 
|  | fml::ScopedCleanupClosure delete_fbo([&gl, &fbo]() { | 
|  | if (fbo != GL_NONE) { | 
|  | gl.BindFramebuffer(GL_FRAMEBUFFER, GL_NONE); | 
|  | gl.DeleteFramebuffers(1u, &fbo); | 
|  | } | 
|  | }); | 
|  |  | 
|  | TextureGLES& color_gles = TextureGLES::Cast(*pass_data.color_attachment); | 
|  | const bool is_default_fbo = color_gles.IsWrapped(); | 
|  |  | 
|  | if (is_default_fbo) { | 
|  | if (color_gles.GetFBO().has_value()) { | 
|  | // NOLINTNEXTLINE(bugprone-unchecked-optional-access) | 
|  | gl.BindFramebuffer(GL_FRAMEBUFFER, *color_gles.GetFBO()); | 
|  | } | 
|  | } else { | 
|  | // Create and bind an offscreen FBO. | 
|  | gl.GenFramebuffers(1u, &fbo); | 
|  | gl.BindFramebuffer(GL_FRAMEBUFFER, fbo); | 
|  |  | 
|  | if (!color_gles.SetAsFramebufferAttachment( | 
|  | GL_FRAMEBUFFER, TextureGLES::AttachmentType::kColor0)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (auto depth = TextureGLES::Cast(pass_data.depth_attachment.get())) { | 
|  | if (!depth->SetAsFramebufferAttachment( | 
|  | GL_FRAMEBUFFER, TextureGLES::AttachmentType::kDepth)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (auto stencil = TextureGLES::Cast(pass_data.stencil_attachment.get())) { | 
|  | if (!stencil->SetAsFramebufferAttachment( | 
|  | GL_FRAMEBUFFER, TextureGLES::AttachmentType::kStencil)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | auto status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); | 
|  | if (status != GL_FRAMEBUFFER_COMPLETE) { | 
|  | VALIDATION_LOG << "Could not create a complete frambuffer: " | 
|  | << DebugToFramebufferError(status); | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | gl.ClearColor(pass_data.clear_color.red,    // red | 
|  | pass_data.clear_color.green,  // green | 
|  | pass_data.clear_color.blue,   // blue | 
|  | pass_data.clear_color.alpha   // alpha | 
|  | ); | 
|  | if (pass_data.depth_attachment) { | 
|  | if (gl.DepthRangef.IsAvailable()) { | 
|  | gl.ClearDepthf(pass_data.clear_depth); | 
|  | } else { | 
|  | gl.ClearDepth(pass_data.clear_depth); | 
|  | } | 
|  | } | 
|  | if (pass_data.stencil_attachment) { | 
|  | gl.ClearStencil(pass_data.clear_stencil); | 
|  | } | 
|  |  | 
|  | GLenum clear_bits = 0u; | 
|  | if (pass_data.clear_color_attachment) { | 
|  | clear_bits |= GL_COLOR_BUFFER_BIT; | 
|  | } | 
|  | if (pass_data.clear_depth_attachment) { | 
|  | clear_bits |= GL_DEPTH_BUFFER_BIT; | 
|  | } | 
|  | if (pass_data.clear_stencil_attachment) { | 
|  | clear_bits |= GL_STENCIL_BUFFER_BIT; | 
|  | } | 
|  |  | 
|  | gl.Disable(GL_SCISSOR_TEST); | 
|  | gl.Disable(GL_DEPTH_TEST); | 
|  | gl.Disable(GL_STENCIL_TEST); | 
|  | gl.Disable(GL_CULL_FACE); | 
|  | gl.Disable(GL_BLEND); | 
|  | gl.ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | 
|  | gl.DepthMask(GL_TRUE); | 
|  | gl.StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF); | 
|  | gl.StencilMaskSeparate(GL_BACK, 0xFFFFFFFF); | 
|  |  | 
|  | gl.Clear(clear_bits); | 
|  |  | 
|  | for (const auto& command : commands) { | 
|  | if (command.instance_count != 1u) { | 
|  | VALIDATION_LOG << "GLES backend does not support instanced rendering."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!command.pipeline) { | 
|  | VALIDATION_LOG << "Command has no pipeline specified."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #ifdef IMPELLER_DEBUG | 
|  | fml::ScopedCleanupClosure pop_cmd_debug_marker( | 
|  | [&gl]() { gl.PopDebugGroup(); }); | 
|  | if (!command.label.empty()) { | 
|  | gl.PushDebugGroup(command.label); | 
|  | } else { | 
|  | pop_cmd_debug_marker.Release(); | 
|  | } | 
|  | #endif  // IMPELLER_DEBUG | 
|  |  | 
|  | const auto& pipeline = PipelineGLES::Cast(*command.pipeline); | 
|  |  | 
|  | const auto* color_attachment = | 
|  | pipeline.GetDescriptor().GetLegacyCompatibleColorAttachment(); | 
|  | if (!color_attachment) { | 
|  | VALIDATION_LOG | 
|  | << "Color attachment is too complicated for a legacy renderer."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Configure blending. | 
|  | /// | 
|  | ConfigureBlending(gl, color_attachment); | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Setup stencil. | 
|  | /// | 
|  | ConfigureStencil(gl, pipeline.GetDescriptor(), command.stencil_reference); | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Configure depth. | 
|  | /// | 
|  | if (auto depth = | 
|  | pipeline.GetDescriptor().GetDepthStencilAttachmentDescriptor(); | 
|  | depth.has_value()) { | 
|  | gl.Enable(GL_DEPTH_TEST); | 
|  | gl.DepthFunc(ToCompareFunction(depth->depth_compare)); | 
|  | gl.DepthMask(depth->depth_write_enabled ? GL_TRUE : GL_FALSE); | 
|  | } else { | 
|  | gl.Disable(GL_DEPTH_TEST); | 
|  | } | 
|  |  | 
|  | // Both the viewport and scissor are specified in framebuffer coordinates. | 
|  | // Impeller's framebuffer coordinate system is top left origin, but OpenGL's | 
|  | // is bottom left origin, so we convert the coordinates here. | 
|  | auto target_size = pass_data.color_attachment->GetSize(); | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Setup the viewport. | 
|  | /// | 
|  | const auto& viewport = command.viewport.value_or(pass_data.viewport); | 
|  | gl.Viewport(viewport.rect.GetX(),  // x | 
|  | target_size.height - viewport.rect.GetY() - | 
|  | viewport.rect.GetHeight(),  // y | 
|  | viewport.rect.GetWidth(),       // width | 
|  | viewport.rect.GetHeight()       // height | 
|  | ); | 
|  | if (pass_data.depth_attachment) { | 
|  | if (gl.DepthRangef.IsAvailable()) { | 
|  | gl.DepthRangef(viewport.depth_range.z_near, viewport.depth_range.z_far); | 
|  | } else { | 
|  | gl.DepthRange(viewport.depth_range.z_near, viewport.depth_range.z_far); | 
|  | } | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Setup the scissor rect. | 
|  | /// | 
|  | if (command.scissor.has_value()) { | 
|  | const auto& scissor = command.scissor.value(); | 
|  | gl.Enable(GL_SCISSOR_TEST); | 
|  | gl.Scissor( | 
|  | scissor.GetX(),                                             // x | 
|  | target_size.height - scissor.GetY() - scissor.GetHeight(),  // y | 
|  | scissor.GetWidth(),                                         // width | 
|  | scissor.GetHeight()                                         // height | 
|  | ); | 
|  | } else { | 
|  | gl.Disable(GL_SCISSOR_TEST); | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Setup culling. | 
|  | /// | 
|  | switch (pipeline.GetDescriptor().GetCullMode()) { | 
|  | case CullMode::kNone: | 
|  | gl.Disable(GL_CULL_FACE); | 
|  | break; | 
|  | case CullMode::kFrontFace: | 
|  | gl.Enable(GL_CULL_FACE); | 
|  | gl.CullFace(GL_FRONT); | 
|  | break; | 
|  | case CullMode::kBackFace: | 
|  | gl.Enable(GL_CULL_FACE); | 
|  | gl.CullFace(GL_BACK); | 
|  | break; | 
|  | } | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Setup winding order. | 
|  | /// | 
|  | switch (pipeline.GetDescriptor().GetWindingOrder()) { | 
|  | case WindingOrder::kClockwise: | 
|  | gl.FrontFace(GL_CW); | 
|  | break; | 
|  | case WindingOrder::kCounterClockwise: | 
|  | gl.FrontFace(GL_CCW); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (command.vertex_buffer.index_type == IndexType::kUnknown) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | auto vertex_desc_gles = pipeline.GetBufferBindings(); | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Bind vertex and index buffers. | 
|  | /// | 
|  | auto& vertex_buffer_view = command.vertex_buffer.vertex_buffer; | 
|  |  | 
|  | if (!vertex_buffer_view) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | auto vertex_buffer = vertex_buffer_view.buffer; | 
|  |  | 
|  | if (!vertex_buffer) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const auto& vertex_buffer_gles = DeviceBufferGLES::Cast(*vertex_buffer); | 
|  | if (!vertex_buffer_gles.BindAndUploadDataIfNecessary( | 
|  | DeviceBufferGLES::BindingType::kArrayBuffer)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Bind the pipeline program. | 
|  | /// | 
|  | if (!pipeline.BindProgram()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Bind vertex attribs. | 
|  | /// | 
|  | if (!vertex_desc_gles->BindVertexAttributes( | 
|  | gl, vertex_buffer_view.range.offset)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Bind uniform data. | 
|  | /// | 
|  | if (!vertex_desc_gles->BindUniformData(gl,                        // | 
|  | *transients_allocator,     // | 
|  | command.vertex_bindings,   // | 
|  | command.fragment_bindings  // | 
|  | )) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Determine the primitive type. | 
|  | /// | 
|  | // GLES doesn't support setting the fill mode, so override the primitive | 
|  | // with GL_LINE_STRIP to somewhat emulate PolygonMode::kLine. This isn't | 
|  | // correct; full triangle outlines won't be drawn and disconnected | 
|  | // geometry may appear connected. However this can still be useful for | 
|  | // wireframe debug views. | 
|  | auto mode = pipeline.GetDescriptor().GetPolygonMode() == PolygonMode::kLine | 
|  | ? GL_LINE_STRIP | 
|  | : ToMode(pipeline.GetDescriptor().GetPrimitiveType()); | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Finally! Invoke the draw call. | 
|  | /// | 
|  | if (command.vertex_buffer.index_type == IndexType::kNone) { | 
|  | gl.DrawArrays(mode, command.base_vertex, | 
|  | command.vertex_buffer.vertex_count); | 
|  | } else { | 
|  | // Bind the index buffer if necessary. | 
|  | auto index_buffer_view = command.vertex_buffer.index_buffer; | 
|  | auto index_buffer = index_buffer_view.buffer; | 
|  | const auto& index_buffer_gles = DeviceBufferGLES::Cast(*index_buffer); | 
|  | if (!index_buffer_gles.BindAndUploadDataIfNecessary( | 
|  | DeviceBufferGLES::BindingType::kElementArrayBuffer)) { | 
|  | return false; | 
|  | } | 
|  | gl.DrawElements(mode,                                           // mode | 
|  | command.vertex_buffer.vertex_count,             // count | 
|  | ToIndexType(command.vertex_buffer.index_type),  // type | 
|  | reinterpret_cast<const GLvoid*>(static_cast<GLsizei>( | 
|  | index_buffer_view.range.offset))  // indices | 
|  | ); | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Unbind vertex attribs. | 
|  | /// | 
|  | if (!vertex_desc_gles->UnbindVertexAttributes(gl)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //-------------------------------------------------------------------------- | 
|  | /// Unbind the program pipeline. | 
|  | /// | 
|  | if (!pipeline.UnbindProgram()) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (gl.DiscardFramebufferEXT.IsAvailable()) { | 
|  | std::vector<GLenum> attachments; | 
|  |  | 
|  | // TODO(jonahwilliams): discarding stencil or depth on the default fbo | 
|  | // causes Angle to discard the entire render target. Until we know the | 
|  | // reason, default to storing. | 
|  | bool angle_safe = gl.GetCapabilities()->IsANGLE() ? !is_default_fbo : true; | 
|  |  | 
|  | if (pass_data.discard_color_attachment) { | 
|  | attachments.push_back(is_default_fbo ? GL_COLOR_EXT | 
|  | : GL_COLOR_ATTACHMENT0); | 
|  | } | 
|  | if (pass_data.discard_depth_attachment && angle_safe) { | 
|  | attachments.push_back(is_default_fbo ? GL_DEPTH_EXT | 
|  | : GL_DEPTH_ATTACHMENT); | 
|  | } | 
|  |  | 
|  | if (pass_data.discard_stencil_attachment && angle_safe) { | 
|  | attachments.push_back(is_default_fbo ? GL_STENCIL_EXT | 
|  | : GL_STENCIL_ATTACHMENT); | 
|  | } | 
|  | gl.DiscardFramebufferEXT(GL_FRAMEBUFFER,      // target | 
|  | attachments.size(),  // attachments to discard | 
|  | attachments.data()   // size | 
|  | ); | 
|  | } | 
|  |  | 
|  | #ifdef IMPELLER_DEBUG | 
|  | if (is_default_fbo) { | 
|  | tracer->MarkFrameEnd(gl); | 
|  | } | 
|  | #endif  // IMPELLER_DEBUG | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // |RenderPass| | 
|  | bool RenderPassGLES::OnEncodeCommands(const Context& context) const { | 
|  | if (!IsValid()) { | 
|  | return false; | 
|  | } | 
|  | const auto& render_target = GetRenderTarget(); | 
|  | if (!render_target.HasColorAttachment(0u)) { | 
|  | return false; | 
|  | } | 
|  | const auto& color0 = render_target.GetColorAttachments().at(0u); | 
|  | const auto& depth0 = render_target.GetDepthAttachment(); | 
|  | const auto& stencil0 = render_target.GetStencilAttachment(); | 
|  |  | 
|  | auto pass_data = std::make_shared<RenderPassData>(); | 
|  | pass_data->label = label_; | 
|  | pass_data->viewport.rect = Rect::MakeSize(GetRenderTargetSize()); | 
|  |  | 
|  | //---------------------------------------------------------------------------- | 
|  | /// Setup color data. | 
|  | /// | 
|  | pass_data->color_attachment = color0.texture; | 
|  | pass_data->clear_color = color0.clear_color; | 
|  | pass_data->clear_color_attachment = CanClearAttachment(color0.load_action); | 
|  | pass_data->discard_color_attachment = | 
|  | CanDiscardAttachmentWhenDone(color0.store_action); | 
|  |  | 
|  | // When we are using EXT_multisampled_render_to_texture, it is implicitly | 
|  | // resolved when we bind the texture to the framebuffer. We don't need to | 
|  | // discard the attachment when we are done. | 
|  | if (color0.resolve_texture) { | 
|  | FML_DCHECK(context.GetCapabilities()->SupportsImplicitResolvingMSAA()); | 
|  | pass_data->discard_color_attachment = false; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------------- | 
|  | /// Setup depth data. | 
|  | /// | 
|  | if (depth0.has_value()) { | 
|  | pass_data->depth_attachment = depth0->texture; | 
|  | pass_data->clear_depth = depth0->clear_depth; | 
|  | pass_data->clear_depth_attachment = CanClearAttachment(depth0->load_action); | 
|  | pass_data->discard_depth_attachment = | 
|  | CanDiscardAttachmentWhenDone(depth0->store_action); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------------- | 
|  | /// Setup stencil data. | 
|  | /// | 
|  | if (stencil0.has_value()) { | 
|  | pass_data->stencil_attachment = stencil0->texture; | 
|  | pass_data->clear_stencil = stencil0->clear_stencil; | 
|  | pass_data->clear_stencil_attachment = | 
|  | CanClearAttachment(stencil0->load_action); | 
|  | pass_data->discard_stencil_attachment = | 
|  | CanDiscardAttachmentWhenDone(stencil0->store_action); | 
|  | } | 
|  |  | 
|  | std::shared_ptr<const RenderPassGLES> shared_this = shared_from_this(); | 
|  | auto tracer = ContextGLES::Cast(context).GetGPUTracer(); | 
|  | return reactor_->AddOperation([pass_data, | 
|  | allocator = context.GetResourceAllocator(), | 
|  | render_pass = std::move(shared_this), | 
|  | tracer](const auto& reactor) { | 
|  | auto result = EncodeCommandsInReactor(*pass_data, allocator, reactor, | 
|  | render_pass->commands_, tracer); | 
|  | FML_CHECK(result) << "Must be able to encode GL commands without error."; | 
|  | }); | 
|  | } | 
|  |  | 
|  | }  // namespace impeller |