// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/browser/gfx/aw_gl_surface_external_stencil.h"
#include "android_webview/browser/gfx/scoped_app_gl_state_restore.h"
#include "base/feature_list.h"
#include "base/strings/stringize_macros.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_helper.h"
namespace android_webview {
// Lifetime: WebView
class AwGLSurfaceExternalStencil::BlitContext {
public:
BlitContext() {
// NOTE: Quad is flipped vertically.
// clang-format off
const GLchar vertex_shader_str[] =
STRINGIZE(
attribute vec2 position;
attribute vec2 texcoords;
varying vec2 v_texcoords;
void main()
{
v_texcoords = vec2(texcoords.x, 1.0 - texcoords.y);
vec2 pos = position * 2.0 - vec2(1.0);
gl_Position = vec4(pos.x, -pos.y, 0.0, 1.0);
}
);
const GLchar fragment_shader_str[] =
STRINGIZE(
precision mediump float;
varying vec2 v_texcoords;
uniform sampler2D texture;
void main()
{
gl_FragColor = texture2D ( texture, v_texcoords );
}
);
// clang-format on
GLuint vertex_shader =
gl::GLHelper::LoadShader(GL_VERTEX_SHADER, vertex_shader_str);
GLuint fragment_shader =
gl::GLHelper::LoadShader(GL_FRAGMENT_SHADER, fragment_shader_str);
program_ = glCreateProgram();
glAttachShader(program_, vertex_shader);
glAttachShader(program_, fragment_shader);
glBindAttribLocation(program_, 0, "position");
glBindAttribLocation(program_, 1, "texcoords");
glLinkProgram(program_);
GLint linked;
glGetProgramiv(program_, GL_LINK_STATUS, &linked);
DCHECK(linked);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
texture_uniform_ = glGetUniformLocation(program_, "texture");
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_vertex_attribs_);
}
~BlitContext() { glDeleteProgram(program_); }
void Bind() {
// If vertex array objects are supported we need to reset it to default one,
// so we won't break someones else VAS by changing attributes.
if (gl::g_current_gl_driver->fn.glBindVertexArrayOESFn) {
glBindVertexArrayOES(0);
}
glUseProgram(program_);
for (GLint i = 2; i < gl_max_vertex_attribs_; ++i) {
glDisableVertexAttribArray(i);
}
// Note that function is not ANGLE only.
if (gl::g_current_gl_driver->fn.glVertexAttribDivisorANGLEFn) {
glVertexAttribDivisorANGLE(0, 0);
glVertexAttribDivisorANGLE(1, 0);
}
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glUniform1i(texture_uniform_, 0);
}
private:
GLuint program_;
GLint texture_uniform_;
GLint gl_max_vertex_attribs_;
};
// Lifetime: WebView
class AwGLSurfaceExternalStencil::FrameBuffer {
public:
FrameBuffer(gfx::Size size) : size_(size) {
glGenTextures(1, &texture_id_);
glBindTexture(GL_TEXTURE_2D, texture_id_);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffersEXT(1, &frame_buffer_object_);
glBindFramebufferEXT(GL_FRAMEBUFFER, frame_buffer_object_);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, texture_id_, 0);
DCHECK(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER) ==
GL_FRAMEBUFFER_COMPLETE)
<< "Failed to set up framebuffer for WebView GL drawing.";
}
void Clear() {
glBindFramebufferEXT(GL_FRAMEBUFFER, frame_buffer_object_);
glDisable(GL_SCISSOR_TEST);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
~FrameBuffer() {
glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
if (frame_buffer_object_)
glDeleteFramebuffersEXT(1, &frame_buffer_object_);
if (texture_id_)
glDeleteTextures(1, &texture_id_);
}
GLuint frame_buffer_object() const { return frame_buffer_object_; }
GLuint texture_id() const { return texture_id_; }
gfx::Size size() const { return size_; }
private:
GLuint frame_buffer_object_ = 0;
GLuint texture_id_ = 0;
gfx::Size size_;
};
AwGLSurfaceExternalStencil::AwGLSurfaceExternalStencil(
gl::GLDisplayEGL* display,
bool is_angle)
: AwGLSurface(display, is_angle) {}
AwGLSurfaceExternalStencil::~AwGLSurfaceExternalStencil() {
InvalidateWeakPtrs();
}
unsigned int AwGLSurfaceExternalStencil::GetBackingFramebufferObject() {
const auto& stencil_state =
android_webview::ScopedAppGLStateRestore::Current()->stencil_state();
if (stencil_state.stencil_test_enabled) {
// Framebuffer was created during RecalculateClipAndTransform();
DCHECK(framebuffer_);
return framebuffer_->frame_buffer_object();
}
return AwGLSurface::GetBackingFramebufferObject();
}
gfx::SwapResult AwGLSurfaceExternalStencil::SwapBuffers(
PresentationCallback callback,
gfx::FrameData frame_data) {
const auto& stencil_state =
android_webview::ScopedAppGLStateRestore::Current()->stencil_state();
if (stencil_state.stencil_test_enabled) {
DCHECK(framebuffer_);
DCHECK(blit_context_);
// Flush skia renderer rendering. This is working around what appears to be
// a driver bug that causes rendering to break.
glFlush();
// Bind required context.
blit_context_->Bind();
// Bind real frame buffer.
glBindFramebufferEXT(GL_FRAMEBUFFER,
AwGLSurface::GetBackingFramebufferObject());
// Restore stencil state.
glEnable(GL_STENCIL_TEST);
glStencilFuncSeparate(GL_FRONT, stencil_state.stencil_front_func,
stencil_state.stencil_front_ref,
stencil_state.stencil_front_mask);
glStencilFuncSeparate(GL_BACK, stencil_state.stencil_back_func,
stencil_state.stencil_back_ref,
stencil_state.stencil_back_mask);
glStencilMaskSeparate(GL_FRONT, stencil_state.stencil_front_writemask);
glStencilMaskSeparate(GL_BACK, stencil_state.stencil_back_writemask);
glStencilOpSeparate(GL_FRONT, stencil_state.stencil_front_fail_op,
stencil_state.stencil_front_z_fail_op,
stencil_state.stencil_front_z_pass_op);
glStencilOpSeparate(GL_BACK, stencil_state.stencil_back_fail_op,
stencil_state.stencil_back_z_fail_op,
stencil_state.stencil_back_z_pass_op);
// Scale clip rect to (0, 0)x(1, 1) space.
gfx::QuadF quad = gfx::QuadF(gfx::RectF(clip_rect_));
quad.Scale(1.0 / viewport_.width(), 1.0 / viewport_.height());
gfx::QuadF tex_quad = gfx::QuadF(gfx::RectF(1.0, 1.0));
// Set-up vertex attributes. p1-p4-p2-p3 forms triangle strip for quad.
// clang-format off
const gfx::PointF data[8] = {quad.p1(), tex_quad.p1(),
quad.p4(), tex_quad.p4(),
quad.p2(), tex_quad.p2(),
quad.p3(), tex_quad.p3()};
// clang-format on
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(gfx::PointF) * 2,
&data[1]);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(gfx::PointF) * 2,
&data[0]);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, framebuffer_->texture_id());
if (gl::g_current_gl_driver->fn.glBindSamplerFn)
glBindSampler(0, 0);
// We need to restore viewport as it might have changed by renderer
glViewport(0, 0, viewport_.width(), viewport_.height());
// We draw only inside clip rect, no need to scissor.
glDisable(GL_SCISSOR_TEST);
// Restore color mask in case.
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
// Restore blending.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glFrontFace(GL_CCW);
if (gl::g_current_gl_driver->fn.glWindowRectanglesEXTFn)
glWindowRectanglesEXT(GL_EXCLUSIVE_EXT, 0, nullptr);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
return AwGLSurface::SwapBuffers(std::move(callback), std::move(frame_data));
}
void AwGLSurfaceExternalStencil::RecalculateClipAndTransform(
gfx::Size* viewport,
gfx::Rect* clip_rect,
gfx::Transform* transform) {
clip_rect_ = *clip_rect;
viewport_ = *viewport;
const auto& stencil_state =
android_webview::ScopedAppGLStateRestore::Current()->stencil_state();
if (stencil_state.stencil_test_enabled) {
// Initialize graphics part needed for blit with stencil here so we don't
// change any gl state after GrContext reset after this function call.
if (!blit_context_) {
blit_context_ = std::make_unique<BlitContext>();
}
if (!framebuffer_ || framebuffer_->size() != clip_rect_.size()) {
// Delete old one first to reduce peak memory.
framebuffer_.reset();
framebuffer_ = std::make_unique<FrameBuffer>(clip_rect_.size());
}
framebuffer_->Clear();
// Adjust transform, clip rect and viewport to be in original clip rect
// space as we will draw to FBO of clip_rect size and blit it to screen at
// original location.
transform->PostTranslate(-clip_rect->x(), -clip_rect->y());
clip_rect->set_origin(gfx::Point(0, 0));
*viewport = clip_rect_.size();
} else {
// We're not going to draw to frame buffer, so we can free it to save
// memory, assuming |stencil_test_enabled| doesn't change often.
framebuffer_.reset();
}
}
bool AwGLSurfaceExternalStencil::IsDrawingToFBO() {
const auto& stencil_state =
android_webview::ScopedAppGLStateRestore::Current()->stencil_state();
return stencil_state.stencil_test_enabled;
}
void AwGLSurfaceExternalStencil::DestroyExternalStencilFramebuffer() {
framebuffer_.reset();
blit_context_.reset();
}
} // namespace android_webview