blob: fb707139a7b1abdd7890017902080a18cb54e9d6 [file] [log] [blame]
// 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