| // Copyright 2020 The Chromium 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 "android_webview/browser/gfx/aw_gl_surface_external_stencil.h" |
| |
| #include "android_webview/browser/gfx/scoped_app_gl_state_restore.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 { |
| |
| 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_; |
| }; |
| |
| 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(bool is_angle) |
| : AwGLSurface(is_angle) {} |
| |
| AwGLSurfaceExternalStencil::~AwGLSurfaceExternalStencil() = default; |
| |
| 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) { |
| 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(); |
| |
| // Restore stencil state. |
| glEnable(GL_STENCIL_TEST); |
| glStencilFuncSeparate(GL_FRONT, stencil_state.stencil_front_func, |
| stencil_state.stencil_front_mask, |
| stencil_state.stencil_front_ref); |
| glStencilFuncSeparate(GL_BACK, stencil_state.stencil_back_func, |
| stencil_state.stencil_back_mask, |
| stencil_state.stencil_back_ref); |
| 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); |
| |
| // Bind required context. |
| blit_context_->Bind(); |
| |
| // Bind real frame buffer. |
| glBindFramebufferEXT(GL_FRAMEBUFFER, |
| AwGLSurface::GetBackingFramebufferObject()); |
| |
| // 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)); |
| } |
| |
| 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; |
| } |
| |
| } // namespace android_webview |