| // 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/toolkit/glvk/trampoline.h" |
| |
| #include <array> |
| |
| #include "flutter/fml/closure.h" |
| #include "flutter/fml/trace_event.h" |
| #include "impeller/base/timing.h" |
| #include "impeller/base/validation.h" |
| #include "impeller/geometry/point.h" |
| #include "impeller/renderer/backend/gles/formats_gles.h" |
| #include "impeller/toolkit/android/proc_table.h" |
| #include "impeller/toolkit/egl/image.h" |
| |
| namespace impeller::glvk { |
| |
| static GLuint kAttributeIndexPosition = 0u; |
| static GLuint kAttributeIndexTexCoord = 1u; |
| |
| static constexpr const char* kVertShader = R"IMPELLER_SHADER(#version 100 |
| |
| precision mediump float; |
| |
| attribute vec2 aPosition; |
| attribute vec2 aTexCoord; |
| |
| varying vec2 vTexCoord; |
| |
| void main() { |
| gl_Position = vec4(aPosition, 0.0, 1.0); |
| vTexCoord = aTexCoord; |
| |
| } |
| )IMPELLER_SHADER"; |
| |
| static constexpr const char* kFragShader = R"IMPELLER_SHADER(#version 100 |
| |
| #extension GL_OES_EGL_image_external : require |
| |
| precision mediump float; |
| |
| uniform samplerExternalOES uTexture; |
| uniform mat4 uUVTransformation; |
| |
| varying vec2 vTexCoord; |
| |
| void main() { |
| vec2 texture_coords = (uUVTransformation * vec4(vTexCoord, 0, 1)).xy; |
| gl_FragColor = texture2D(uTexture, texture_coords); |
| } |
| |
| )IMPELLER_SHADER"; |
| |
| Trampoline::Trampoline() { |
| auto egl_display = std::make_unique<egl::Display>(); |
| if (!egl_display->IsValid()) { |
| VALIDATION_LOG |
| << "Could not create EGL display for external texture interop."; |
| return; |
| } |
| |
| egl::ConfigDescriptor egl_config_desc; |
| egl_config_desc.api = egl::API::kOpenGLES2; |
| egl_config_desc.samples = egl::Samples::kOne; |
| egl_config_desc.color_format = egl::ColorFormat::kRGBA8888; |
| egl_config_desc.stencil_bits = egl::StencilBits::kZero; |
| egl_config_desc.depth_bits = egl::DepthBits::kZero; |
| egl_config_desc.surface_type = egl::SurfaceType::kPBuffer; |
| auto egl_config = egl_display->ChooseConfig(egl_config_desc); |
| if (!egl_config) { |
| VALIDATION_LOG |
| << "Could not choose EGL config for external texture interop."; |
| return; |
| } |
| |
| auto egl_surface = egl_display->CreatePixelBufferSurface(*egl_config, 1u, 1u); |
| auto egl_context = egl_display->CreateContext(*egl_config, nullptr); |
| |
| if (!egl_surface || !egl_context) { |
| VALIDATION_LOG << "Could not create EGL surface and/or context for " |
| "external texture interop."; |
| return; |
| } |
| |
| // Make the context current so the proc addresses can be resolved. |
| if (!egl_context->MakeCurrent(*egl_surface)) { |
| VALIDATION_LOG << "Could not make the context current."; |
| return; |
| } |
| |
| auto gl = std::make_unique<ProcTable>(egl::CreateProcAddressResolver()); |
| |
| if (!gl->IsValid()) { |
| egl_context->ClearCurrent(); |
| VALIDATION_LOG << "Could not setup trampoline proc table."; |
| return; |
| } |
| |
| // Generate program object. |
| auto vert_shader = gl->CreateShader(GL_VERTEX_SHADER); |
| auto frag_shader = gl->CreateShader(GL_FRAGMENT_SHADER); |
| |
| GLint vert_shader_size = strlen(kVertShader); |
| GLint frag_shader_size = strlen(kFragShader); |
| |
| gl->ShaderSource(vert_shader, 1u, &kVertShader, &vert_shader_size); |
| gl->ShaderSource(frag_shader, 1u, &kFragShader, &frag_shader_size); |
| |
| gl->CompileShader(vert_shader); |
| gl->CompileShader(frag_shader); |
| |
| GLint vert_status = GL_FALSE; |
| GLint frag_status = GL_FALSE; |
| |
| gl->GetShaderiv(vert_shader, GL_COMPILE_STATUS, &vert_status); |
| gl->GetShaderiv(frag_shader, GL_COMPILE_STATUS, &frag_status); |
| |
| FML_CHECK(vert_status == GL_TRUE); |
| FML_CHECK(frag_status == GL_TRUE); |
| |
| program_ = gl->CreateProgram(); |
| gl->AttachShader(program_, vert_shader); |
| gl->AttachShader(program_, frag_shader); |
| |
| gl->BindAttribLocation(program_, kAttributeIndexPosition, "aPosition"); |
| gl->BindAttribLocation(program_, kAttributeIndexTexCoord, "aTexCoord"); |
| |
| gl->LinkProgram(program_); |
| |
| GLint link_status = GL_FALSE; |
| gl->GetProgramiv(program_, GL_LINK_STATUS, &link_status); |
| FML_CHECK(link_status == GL_TRUE); |
| |
| texture_uniform_location_ = gl->GetUniformLocation(program_, "uTexture"); |
| uv_transformation_location_ = |
| gl->GetUniformLocation(program_, "uUVTransformation"); |
| |
| gl->DeleteShader(vert_shader); |
| gl->DeleteShader(frag_shader); |
| |
| egl_context->ClearCurrent(); |
| |
| gl_ = std::move(gl); |
| egl_display_ = std::move(egl_display); |
| egl_context_ = std::move(egl_context); |
| egl_surface_ = std::move(egl_surface); |
| is_valid_ = true; |
| } |
| |
| Trampoline::~Trampoline() { |
| if (!is_valid_) { |
| return; |
| } |
| auto context = MakeCurrentContext(); |
| gl_->DeleteProgram(program_); |
| } |
| |
| bool Trampoline::IsValid() const { |
| return is_valid_; |
| } |
| |
| static UniqueEGLImageKHR CreateEGLImageFromAHBTexture( |
| const EGLDisplay& display, |
| const AHBTextureSourceVK& to_texture) { |
| if (!android::GetProcTable().eglGetNativeClientBufferANDROID.IsAvailable()) { |
| VALIDATION_LOG << "Could not get native client buffer."; |
| return {}; |
| } |
| |
| EGLClientBuffer client_buffer = |
| android::GetProcTable().eglGetNativeClientBufferANDROID( |
| to_texture.GetBackingStore()->GetHandle()); |
| |
| if (!client_buffer) { |
| VALIDATION_LOG |
| << "Could not get client buffer from Android hardware buffer."; |
| return {}; |
| } |
| |
| auto image = ::eglCreateImageKHR(display, // |
| EGL_NO_CONTEXT, // |
| EGL_NATIVE_BUFFER_ANDROID, // |
| client_buffer, // |
| nullptr // |
| ); |
| if (image == NULL) { |
| VALIDATION_LOG << "Could not create EGL Image."; |
| return {}; |
| } |
| |
| return UniqueEGLImageKHR(EGLImageKHRWithDisplay{image, display}); |
| } |
| |
| bool Trampoline::BlitTextureOpenGLToVulkan( |
| const GLTextureInfo& src_texture, |
| const AHBTextureSourceVK& dst_texture) const { |
| TRACE_EVENT0("impeller", __FUNCTION__); |
| if (!is_valid_) { |
| return false; |
| } |
| |
| FML_DCHECK(egl_context_->IsCurrent()); |
| |
| auto dst_egl_image = |
| CreateEGLImageFromAHBTexture(egl_display_->GetHandle(), dst_texture); |
| if (!dst_egl_image.is_valid()) { |
| VALIDATION_LOG << "Could not create EGL image from AHB texture."; |
| return false; |
| } |
| |
| const auto& gl = *gl_; |
| |
| GLuint dst_gl_texture = GL_NONE; |
| gl.GenTextures(1u, &dst_gl_texture); |
| gl.BindTexture(GL_TEXTURE_2D, dst_gl_texture); |
| gl.EGLImageTargetTexture2DOES(GL_TEXTURE_2D, dst_egl_image.get().image); |
| |
| GLuint offscreen_fbo = GL_NONE; |
| gl.GenFramebuffers(1u, &offscreen_fbo); |
| gl.BindFramebuffer(GL_FRAMEBUFFER, offscreen_fbo); |
| gl.FramebufferTexture2D(GL_FRAMEBUFFER, // |
| GL_COLOR_ATTACHMENT0, // |
| GL_TEXTURE_2D, // |
| dst_gl_texture, // |
| 0 // |
| ); |
| |
| FML_CHECK(gl.CheckFramebufferStatus(GL_FRAMEBUFFER) == |
| GL_FRAMEBUFFER_COMPLETE); |
| |
| gl.Disable(GL_BLEND); |
| gl.Disable(GL_SCISSOR_TEST); |
| gl.Disable(GL_DITHER); |
| gl.Disable(GL_CULL_FACE); |
| gl.ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| |
| gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
| gl.Clear(GL_COLOR_BUFFER_BIT); |
| |
| const auto& fb_size = dst_texture.GetTextureDescriptor().size; |
| gl.Viewport(0, 0, fb_size.width, fb_size.height); |
| |
| gl.UseProgram(program_); |
| |
| struct VertexData { |
| Point position; |
| Point tex_coord; |
| }; |
| |
| // The vertex coordinates assume OpenGL NDC because that's the API we are |
| // using to draw the quad. But the texture will be sampled in Vulkan so the |
| // texture coordinate system assumes Vulkan convention. |
| // |
| // See the following help link for an overview of the different coordinate |
| // systems: |
| // https://github.com/flutter/flutter/blob/master/docs/engine/impeller/docs/coordinate_system.md |
| static constexpr const VertexData kVertData[] = { |
| {{-1, -1}, {0, 1}}, // bottom left |
| {{-1, +1}, {0, 0}}, // top left |
| {{+1, +1}, {1, 0}}, // top right |
| {{+1, -1}, {1, 1}}, // bottom right |
| }; |
| |
| // This is tedious but we assume no vertex array objects (VAO) are available |
| // because of ES 2 versioning constraints. |
| GLuint vertex_buffer = GL_NONE; |
| gl.GenBuffers(1u, &vertex_buffer); |
| gl.BindBuffer(GL_ARRAY_BUFFER, vertex_buffer); |
| gl.BufferData(GL_ARRAY_BUFFER, sizeof(kVertData), kVertData, GL_STATIC_DRAW); |
| gl.EnableVertexAttribArray(kAttributeIndexPosition); |
| gl.EnableVertexAttribArray(kAttributeIndexTexCoord); |
| gl.VertexAttribPointer( |
| kAttributeIndexPosition, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), |
| reinterpret_cast<void*>(offsetof(VertexData, position))); |
| gl.VertexAttribPointer( |
| kAttributeIndexTexCoord, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), |
| reinterpret_cast<void*>(offsetof(VertexData, tex_coord))); |
| |
| gl.ActiveTexture(GL_TEXTURE0); |
| gl.BindTexture(src_texture.target, src_texture.texture); |
| gl.TexParameteri(src_texture.target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| gl.TexParameteri(src_texture.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| gl.TexParameteri(src_texture.target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl.TexParameteri(src_texture.target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gl.Uniform1i(texture_uniform_location_, 0u); |
| |
| auto gl_uv_transformation = src_texture.uv_transformation; |
| |
| gl.UniformMatrix4fv(uv_transformation_location_, 1u, GL_FALSE, |
| reinterpret_cast<GLfloat*>(&gl_uv_transformation)); |
| |
| gl.DrawArrays(GL_TRIANGLE_FAN, 0, 4); |
| |
| gl.UseProgram(GL_NONE); |
| |
| gl.Flush(); |
| |
| gl.DeleteFramebuffers(1u, &offscreen_fbo); |
| gl.DeleteTextures(1u, &dst_gl_texture); |
| gl.DeleteBuffers(1u, &vertex_buffer); |
| |
| // Theoretically, this does nothing because the surface is a 1x1 pbuffer |
| // surface. But frame capture tools use this to denote a frame boundary in |
| // OpenGL. So add this as a debugging aid anyway. |
| eglSwapBuffers(egl_display_->GetHandle(), egl_surface_->GetHandle()); |
| |
| return true; |
| } |
| |
| AutoTrampolineContext Trampoline::MakeCurrentContext() const { |
| FML_DCHECK(is_valid_); |
| return AutoTrampolineContext{*this}; |
| } |
| |
| AutoTrampolineContext::AutoTrampolineContext(const Trampoline& trampoline) |
| : context_(trampoline.egl_context_.get()), |
| surface_(trampoline.egl_surface_.get()) { |
| if (!context_->IsCurrent() && !context_->MakeCurrent(*surface_)) { |
| VALIDATION_LOG << "Could not make context current."; |
| } |
| }; |
| |
| AutoTrampolineContext::~AutoTrampolineContext() { |
| if (!context_->ClearCurrent()) { |
| VALIDATION_LOG << "Could not clear current context."; |
| } |
| } |
| |
| } // namespace impeller::glvk |