blob: a0a7ad3b8ac14b2fffc0b2dabaafd16e717a04f2 [file] [log] [blame]
/*
* Copyright 2021 The Chromium OS 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 "gpu/image_processor.h"
#include <string>
#include <vector>
#include "cros-camera/common.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"
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";
} // 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});
}
}
GpuImageProcessor::~GpuImageProcessor() = default;
bool GpuImageProcessor::RGBAToNV12(const Texture2D& rgba_input,
const Texture2D& y_output,
const Texture2D& uv_output) {
if ((y_output.width() / 2 != uv_output.width()) ||
(y_output.height() / 2 != uv_output.height())) {
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() / 2 != uv_output.width()) ||
(y_output.height() / 2 != uv_output.height())) {
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() / 2 != uv_input.width()) ||
(y_input.height() / 2 != uv_input.height())) {
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() / 2 != uv_input.width()) ||
(y_input.height() / 2 != uv_input.height())) {
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() / 2 != uv_output.width()) ||
(y_output.height() / 2 != uv_output.height())) {
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::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;
}
} // namespace cros