| /* |
| * Copyright 2021 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "gpu/image_processor.h" |
| |
| #include <drm_fourcc.h> |
| #include <sync/sync.h> |
| |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/scoped_file.h> |
| #include <base/numerics/safe_conversions.h> |
| #include <base/synchronization/waitable_event.h> |
| |
| #include "cros-camera/camera_buffer_manager.h" |
| #include "cros-camera/common.h" |
| #include "gpu/egl/egl_fence.h" |
| #include "gpu/embedded_gpu_shaders_toc.h" |
| #include "gpu/gles/framebuffer.h" |
| #include "gpu/gles/sampler.h" |
| #include "gpu/gles/shader.h" |
| #include "gpu/gles/state_guard.h" |
| #include "gpu/gles/transform.h" |
| #include "gpu/shared_image.h" |
| |
| namespace cros { |
| |
| namespace { |
| |
| constexpr const char* kVertexShaderFilename = |
| "fullscreen_rect_highp_310_es.vert"; |
| constexpr const char* kRgbaToNv12Filename = "rgba_to_nv12.frag"; |
| constexpr const char* kExternalYuvToNv12Filename = "external_yuv_to_nv12.frag"; |
| constexpr const char* kExternalYuvToRgbaFilename = "external_yuv_to_rgba.frag"; |
| constexpr const char* kNv12ToRgbaFilename = "nv12_to_rgba.frag"; |
| constexpr const char* kYuvToYuvFilename = "yuv_to_yuv.frag"; |
| constexpr const char* kGammaCorrectionFilename = "gamma_correction.frag"; |
| constexpr const char* kLutFilename = "lut.frag"; |
| constexpr const char* kCropFilename = "crop.frag"; |
| constexpr const char* kYuyvToNv12Filename = "yuyv_to_nv12.frag"; |
| constexpr const char* kSubsampleChromaFilename = "subsample_chroma.frag"; |
| |
| } // namespace |
| |
| GpuImageProcessor::GpuImageProcessor() |
| : nearest_clamp_to_edge_(NearestClampToEdge()), |
| linear_clamp_to_edge_(LinearClampToEdge()) { |
| EmbeddedFileToc gpu_shaders = GetEmbeddedGpuShadersToc(); |
| |
| // Create the vextex shader. |
| base::span<const char> src = gpu_shaders.Get(kVertexShaderFilename); |
| Shader vertex_shader(GL_VERTEX_SHADER, std::string(src.data(), src.size())); |
| CHECK(vertex_shader.IsValid()); |
| |
| { |
| base::span<const char> src = gpu_shaders.Get(kRgbaToNv12Filename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| rgba_to_nv12_program_ = ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kExternalYuvToNv12Filename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| external_yuv_to_nv12_program_ = |
| ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kExternalYuvToRgbaFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| external_yuv_to_rgba_program_ = |
| ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kNv12ToRgbaFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| nv12_to_rgba_program_ = ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kYuvToYuvFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| nv12_to_nv12_program_ = ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kGammaCorrectionFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| gamma_correction_program_ = |
| ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kLutFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| lut_program_ = ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kCropFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| crop_yuv_program_ = ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kYuyvToNv12Filename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| yuyv_to_nv12_program_ = ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| { |
| base::span<const char> src = gpu_shaders.Get(kSubsampleChromaFilename); |
| Shader fragment_shader(GL_FRAGMENT_SHADER, |
| std::string(src.data(), src.size())); |
| CHECK(fragment_shader.IsValid()); |
| subsample_chroma_program_ = |
| ShaderProgram({&vertex_shader, &fragment_shader}); |
| } |
| } |
| |
| GpuImageProcessor::~GpuImageProcessor() = default; |
| |
| bool GpuImageProcessor::RGBAToNV12(const Texture2D& rgba_input, |
| const Texture2D& y_output, |
| const Texture2D& uv_output) { |
| if ((y_output.width() != uv_output.width() * 2) || |
| (y_output.height() != uv_output.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_output.width() << ", " |
| << y_output.height() << ") and UV (" << uv_output.width() |
| << ", " << uv_output.height() << ") output dimension"; |
| return false; |
| } |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| // Set up RGBA input texture. |
| constexpr int kInputBinding = 0; |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| rgba_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kInputBinding); |
| |
| rgba_to_nv12_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| rgba_to_nv12_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| GLint uIsYPlane = rgba_to_nv12_program_.GetUniformLocation("uIsYPlane"); |
| |
| // Y pass. |
| { |
| glUniform1i(uIsYPlane, true); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, y_output); |
| glViewport(0, 0, y_output.width(), y_output.height()); |
| rect_.Draw(); |
| } |
| |
| // UV pass. |
| { |
| glUniform1i(uIsYPlane, false); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, uv_output); |
| glViewport(0, 0, uv_output.width(), uv_output.height()); |
| rect_.Draw(); |
| } |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| rgba_input.Unbind(); |
| Sampler::Unbind(kInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::ExternalYUVToNV12(const Texture2D& external_yuv_input, |
| const Texture2D& y_output, |
| const Texture2D& uv_output) { |
| if ((y_output.width() != uv_output.width() * 2) || |
| (y_output.height() != uv_output.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_output.width() << ", " |
| << y_output.height() << ") and UV (" << uv_output.width() |
| << ", " << uv_output.height() << ") output dimension"; |
| return false; |
| } |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| // Set up input external YUV texture. |
| constexpr int kInputBinding = 0; |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| external_yuv_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kInputBinding); |
| |
| external_yuv_to_nv12_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| rgba_to_nv12_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| GLint uIsYPlane = rgba_to_nv12_program_.GetUniformLocation("uIsYPlane"); |
| |
| // Y pass. |
| { |
| glUniform1i(uIsYPlane, true); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, y_output); |
| glViewport(0, 0, y_output.width(), y_output.height()); |
| rect_.Draw(); |
| } |
| |
| // UV pass. |
| { |
| glUniform1i(uIsYPlane, false); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, uv_output); |
| glViewport(0, 0, uv_output.width(), uv_output.height()); |
| rect_.Draw(); |
| } |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| external_yuv_input.Unbind(); |
| Sampler::Unbind(kInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::ExternalYUVToRGBA(const Texture2D& external_yuv_input, |
| const Texture2D& rgba_output) { |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| // Set up input external YUV texture. |
| constexpr int kInputBinding = 0; |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| external_yuv_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kInputBinding); |
| |
| external_yuv_to_rgba_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| external_yuv_to_rgba_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, rgba_output); |
| glViewport(0, 0, rgba_output.width(), rgba_output.height()); |
| rect_.Draw(); |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| external_yuv_input.Unbind(); |
| Sampler::Unbind(kInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::NV12ToRGBA(const Texture2D& y_input, |
| const Texture2D& uv_input, |
| const Texture2D& rgba_output) { |
| if ((y_input.width() != uv_input.width() * 2) || |
| (y_input.height() != uv_input.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_input.width() << ", " << y_input.height() |
| << ") and UV (" << uv_input.width() << ", " << uv_input.height() |
| << ") input dimension"; |
| return false; |
| } |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| constexpr int kYInputBinding = 0; |
| constexpr int kUvInputBinding = 1; |
| glActiveTexture(GL_TEXTURE0 + kYInputBinding); |
| y_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kYInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| uv_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kUvInputBinding); |
| |
| nv12_to_rgba_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| nv12_to_rgba_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, rgba_output); |
| glViewport(0, 0, rgba_output.width(), rgba_output.height()); |
| rect_.Draw(); |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kYInputBinding); |
| y_input.Unbind(); |
| Sampler::Unbind(kYInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| uv_input.Unbind(); |
| Sampler::Unbind(kUvInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::YUVToYUV(const Texture2D& y_input, |
| const Texture2D& uv_input, |
| const Texture2D& y_output, |
| const Texture2D& uv_output) { |
| if ((y_input.width() != uv_input.width() * 2) || |
| (y_input.height() != uv_input.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_input.width() << ", " << y_input.height() |
| << ") and UV (" << uv_input.width() << ", " << uv_input.height() |
| << ") input dimension"; |
| return false; |
| } |
| if ((y_output.width() != uv_output.width() * 2) || |
| (y_output.height() != uv_output.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_output.width() << ", " |
| << y_output.height() << ") and UV (" << uv_output.width() |
| << ", " << uv_output.height() << ") output dimension"; |
| return false; |
| } |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| constexpr int kYInputBinding = 0; |
| constexpr int kUvInputBinding = 1; |
| glActiveTexture(GL_TEXTURE0 + kYInputBinding); |
| y_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kYInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| uv_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kUvInputBinding); |
| |
| nv12_to_nv12_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| nv12_to_nv12_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| GLint uIsYPlane = nv12_to_nv12_program_.GetUniformLocation("uIsYPlane"); |
| |
| // Y pass. |
| { |
| glUniform1i(uIsYPlane, true); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, y_output); |
| glViewport(0, 0, y_output.width(), y_output.height()); |
| rect_.Draw(); |
| } |
| |
| // UV pass. |
| { |
| glUniform1i(uIsYPlane, false); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, uv_output); |
| glViewport(0, 0, uv_output.width(), uv_output.height()); |
| rect_.Draw(); |
| } |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kYInputBinding); |
| y_input.Unbind(); |
| Sampler::Unbind(kYInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| uv_input.Unbind(); |
| Sampler::Unbind(kUvInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::YUYVToNV12(const Texture2D& yx_input, |
| const Texture2D& yuyv_input, |
| const Texture2D& y_output, |
| const Texture2D& uv_output) { |
| if ((yx_input.width() != yuyv_input.width() * 2) || |
| (yx_input.height() != yuyv_input.height())) { |
| LOGF(ERROR) << "Invalid Y (" << yx_input.width() << ", " |
| << yx_input.height() << ") and UV (" << yuyv_input.width() |
| << ", " << yuyv_input.height() << ") input dimension"; |
| return false; |
| } |
| if ((y_output.width() != uv_output.width() * 2) || |
| (y_output.height() != uv_output.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_output.width() << ", " |
| << y_output.height() << ") and UV (" << uv_output.width() |
| << ", " << uv_output.height() << ") output dimension"; |
| return false; |
| } |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| constexpr int kYInputBinding = 0; |
| constexpr int kUvInputBinding = 1; |
| glActiveTexture(GL_TEXTURE0 + kYInputBinding); |
| yx_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kYInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| yuyv_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kUvInputBinding); |
| |
| yuyv_to_nv12_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| yuyv_to_nv12_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| GLint uIsYPlane = yuyv_to_nv12_program_.GetUniformLocation("uIsYPlane"); |
| |
| // Y pass. |
| { |
| glUniform1i(uIsYPlane, true); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, y_output); |
| glViewport(0, 0, y_output.width(), y_output.height()); |
| rect_.Draw(); |
| } |
| |
| // UV pass. |
| { |
| glUniform1i(uIsYPlane, false); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, uv_output); |
| glViewport(0, 0, uv_output.width(), uv_output.height()); |
| rect_.Draw(); |
| } |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kYInputBinding); |
| yx_input.Unbind(); |
| Sampler::Unbind(kYInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| yuyv_input.Unbind(); |
| Sampler::Unbind(kUvInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::ApplyGammaCorrection(float gamma_value, |
| const Texture2D& rgba_input, |
| const Texture2D& rgba_output) { |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| // Set up input RGBA texture. |
| constexpr int kInputBinding = 0; |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| rgba_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kInputBinding); |
| |
| gamma_correction_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| gamma_correction_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| GLint uGammaValue = |
| gamma_correction_program_.GetUniformLocation("uGammaValue"); |
| glUniform1f(uGammaValue, gamma_value); |
| |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, rgba_output); |
| glViewport(0, 0, rgba_output.width(), rgba_output.height()); |
| rect_.Draw(); |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| rgba_input.Unbind(); |
| Sampler::Unbind(kInputBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::ApplyRgbLut(const Texture2D& r_lut, |
| const Texture2D& g_lut, |
| const Texture2D& b_lut, |
| const Texture2D& rgba_input, |
| const Texture2D& rgba_output) { |
| if (!r_lut.IsValid() || !g_lut.IsValid() || !b_lut.IsValid()) { |
| return false; |
| } |
| if (!rgba_input.IsValid() || !rgba_output.IsValid()) { |
| return false; |
| } |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| constexpr int kInputBinding = 0; |
| constexpr int kRLutBinding = 1; |
| constexpr int kGLutBinding = 2; |
| constexpr int kBLutBinding = 3; |
| |
| // Set up input RGBA texture. |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| rgba_input.Bind(); |
| nearest_clamp_to_edge_.Bind(kInputBinding); |
| |
| // Set up RGB LUT textures. |
| glActiveTexture(GL_TEXTURE0 + kRLutBinding); |
| r_lut.Bind(); |
| linear_clamp_to_edge_.Bind(kRLutBinding); |
| glActiveTexture(GL_TEXTURE0 + kGLutBinding); |
| g_lut.Bind(); |
| linear_clamp_to_edge_.Bind(kGLutBinding); |
| glActiveTexture(GL_TEXTURE0 + kBLutBinding); |
| b_lut.Bind(); |
| linear_clamp_to_edge_.Bind(kBLutBinding); |
| |
| lut_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = lut_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, rgba_output); |
| glViewport(0, 0, rgba_output.width(), rgba_output.height()); |
| rect_.Draw(); |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kInputBinding); |
| rgba_input.Unbind(); |
| Sampler::Unbind(kInputBinding); |
| glActiveTexture(GL_TEXTURE0 + kRLutBinding); |
| r_lut.Unbind(); |
| Sampler::Unbind(kRLutBinding); |
| glActiveTexture(GL_TEXTURE0 + kGLutBinding); |
| g_lut.Unbind(); |
| Sampler::Unbind(kGLutBinding); |
| glActiveTexture(GL_TEXTURE0 + kBLutBinding); |
| b_lut.Unbind(); |
| Sampler::Unbind(kBLutBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::CropYuv(const Texture2D& y_input, |
| const Texture2D& uv_input, |
| Rect<float> crop_region, |
| const Texture2D& y_output, |
| const Texture2D& uv_output, |
| FilterMode filter_mode) { |
| if ((y_input.width() != uv_input.width() * 2) || |
| (y_input.height() != uv_input.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_input.width() << ", " << y_input.height() |
| << ") and UV (" << uv_input.width() << ", " << uv_input.height() |
| << ") input dimension"; |
| return false; |
| } |
| if ((y_output.width() != uv_output.width() * 2) || |
| (y_output.height() != uv_output.height() * 2)) { |
| LOGF(ERROR) << "Invalid Y (" << y_output.width() << ", " |
| << y_output.height() << ") and UV (" << uv_output.width() |
| << ", " << uv_output.height() << ") output dimension"; |
| return false; |
| } |
| if (crop_region.left < 0.0f || crop_region.left > 1.0f || |
| crop_region.top < 0.0f || crop_region.top > 1.0f) { |
| LOGF(ERROR) << "Invalid crop coordinate (" << crop_region.left << ", " |
| << crop_region.top << ")"; |
| return false; |
| } |
| if (crop_region.width < 0.0f || crop_region.width > 1.0f || |
| crop_region.height < 0.0f || crop_region.height > 1.0f) { |
| LOGF(ERROR) << "Invalid crop dimension (" << crop_region.width << ", " |
| << crop_region.height << ")"; |
| return false; |
| } |
| |
| Sampler& sampler = GetSampler(filter_mode); |
| |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| constexpr int kInputTextureBinding = 0; |
| sampler.Bind(kInputTextureBinding); |
| |
| crop_yuv_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = crop_yuv_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| GLint uCropRegion = crop_yuv_program_.GetUniformLocation("uCropRegion"); |
| GLint uBicubic = crop_yuv_program_.GetUniformLocation("uBicubic"); |
| |
| glUniform4f(uCropRegion, crop_region.left, crop_region.top, crop_region.width, |
| crop_region.height); |
| glUniform1i(uBicubic, |
| filter_mode == FilterMode::kBicubic ? GL_TRUE : GL_FALSE); |
| // Y pass. |
| { |
| glActiveTexture(GL_TEXTURE0 + kInputTextureBinding); |
| y_input.Bind(); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, y_output); |
| glViewport(0, 0, y_output.width(), y_output.height()); |
| rect_.Draw(); |
| } |
| |
| // UV pass. |
| { |
| glActiveTexture(GL_TEXTURE0 + kInputTextureBinding); |
| uv_input.Bind(); |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, uv_output); |
| glViewport(0, 0, uv_output.width(), uv_output.height()); |
| rect_.Draw(); |
| } |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kInputTextureBinding); |
| y_input.Unbind(); |
| Sampler::Unbind(kInputTextureBinding); |
| |
| return true; |
| } |
| |
| bool GpuImageProcessor::SubsampleChroma(const Texture2D& input_uv, |
| const Texture2D& output_uv) { |
| FramebufferGuard fb_guard; |
| ViewportGuard viewport_guard; |
| ProgramGuard program_guard; |
| VertexArrayGuard va_guard; |
| |
| rect_.SetAsVertexInput(); |
| |
| constexpr int kUvInputBinding = 0; |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| input_uv.Bind(); |
| nearest_clamp_to_edge_.Bind(kUvInputBinding); |
| |
| subsample_chroma_program_.UseProgram(); |
| |
| // Set shader uniforms. |
| std::vector<float> texture_matrix = TextureSpaceFromNdc(); |
| GLint uTextureMatrix = |
| subsample_chroma_program_.GetUniformLocation("uTextureMatrix"); |
| glUniformMatrix4fv(uTextureMatrix, 1, false, texture_matrix.data()); |
| |
| Framebuffer fb; |
| fb.Bind(); |
| fb.Attach(GL_COLOR_ATTACHMENT0, output_uv); |
| glViewport(0, 0, output_uv.width(), output_uv.height()); |
| rect_.Draw(); |
| |
| // Clean up. |
| glActiveTexture(GL_TEXTURE0 + kUvInputBinding); |
| input_uv.Unbind(); |
| Sampler::Unbind(kUvInputBinding); |
| |
| return true; |
| } |
| |
| Sampler& GpuImageProcessor::GetSampler(FilterMode filter_mode) { |
| switch (filter_mode) { |
| case FilterMode::kNearest: |
| return nearest_clamp_to_edge_; |
| case FilterMode::kBilinear: |
| case FilterMode::kBicubic: |
| return linear_clamp_to_edge_; |
| } |
| } |
| |
| std::optional<base::ScopedFD> CropScaleOnGpuImageProcessor( |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| GpuImageProcessor* image_processor, |
| buffer_handle_t input, |
| base::ScopedFD input_release_fence, |
| buffer_handle_t output, |
| base::ScopedFD output_acquire_fence, |
| const Rect<float>& crop) { |
| CHECK_NE(task_runner, nullptr); |
| CHECK_NE(image_processor, nullptr); |
| CHECK_EQ(CameraBufferManager::GetDrmPixelFormat(input), DRM_FORMAT_NV12); |
| CHECK_EQ(CameraBufferManager::GetDrmPixelFormat(output), DRM_FORMAT_NV12); |
| CHECK(crop.is_valid()); |
| |
| if (!task_runner->BelongsToCurrentThread()) { |
| std::optional<base::ScopedFD> ret; |
| base::WaitableEvent done; |
| task_runner->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CropScaleOnGpuImageProcessor, task_runner, |
| image_processor, input, std::move(input_release_fence), |
| output, std::move(output_acquire_fence), crop) |
| .Then(base::BindOnce( |
| [](base::WaitableEvent* done, |
| std::optional<base::ScopedFD>* ret, |
| std::optional<base::ScopedFD> result) { |
| *ret = std::move(result); |
| done->Signal(); |
| }, |
| base::Unretained(&done), base::Unretained(&ret)))); |
| done.Wait(); |
| return ret; |
| } |
| |
| constexpr int kSyncWaitTimeoutMs = 300; |
| if (input_release_fence.is_valid() && |
| sync_wait(input_release_fence.get(), kSyncWaitTimeoutMs) != 0) { |
| LOGF(ERROR) << "Sync wait timed out on input release fence"; |
| return std::nullopt; |
| } |
| if (output_acquire_fence.is_valid() && |
| sync_wait(output_acquire_fence.get(), kSyncWaitTimeoutMs) != 0) { |
| LOGF(ERROR) << "Sync wait timed out on output acquire fence"; |
| return std::nullopt; |
| } |
| |
| SharedImage input_image = SharedImage::CreateFromBuffer( |
| input, Texture2D::Target::kTarget2D, /*separate_yuv_textures=*/true); |
| if (!input_image.IsValid()) { |
| LOGF(ERROR) << "Failed to create shared image from input buffer"; |
| return std::nullopt; |
| } |
| SharedImage output_image = SharedImage::CreateFromBuffer( |
| output, Texture2D::Target::kTarget2D, /*separate_yuv_textures=*/true); |
| if (!output_image.IsValid()) { |
| LOGF(ERROR) << "Failed to create shared image from output buffer"; |
| return std::nullopt; |
| } |
| |
| // When not scaling, use nearest sampling to keep same pixel values. |
| const uint32_t cropped_width = base::checked_cast<uint32_t>( |
| static_cast<float>(input_image.y_texture().width()) * crop.width + 0.5f); |
| const uint32_t cropped_height = base::checked_cast<uint32_t>( |
| static_cast<float>(input_image.y_texture().height()) * crop.height + |
| 0.5f); |
| const FilterMode filter_mode = |
| cropped_width == output_image.y_texture().width() && |
| cropped_height == output_image.y_texture().height() |
| ? FilterMode::kNearest |
| : FilterMode::kBilinear; |
| if (!image_processor->CropYuv( |
| input_image.y_texture(), input_image.uv_texture(), crop, |
| output_image.y_texture(), output_image.uv_texture(), filter_mode)) { |
| LOGF(ERROR) << "Failed to crop and scale buffers"; |
| return std::nullopt; |
| } |
| |
| EglFence fence; |
| return fence.GetNativeFd(); |
| } |
| |
| } // namespace cros |