From 3507249202b902935ace274e837408dc34d6b10e Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Fri, 21 Jul 2023 15:06:45 +0200 Subject: [PATCH] cefclient: osr: Implement shader-based rendering for Linux (fixes #3187) Windows still uses OpenGL 1.1 to avoid the added complexity of linking newer OpenGL APIs on that platform. MacOS has deprecated OpenGL and we should eventually provide a Metal-based implementation on that platform. --- tests/cefclient/browser/osr_renderer.cc | 495 +++++++++++++++++++++++- tests/cefclient/browser/osr_renderer.h | 31 +- tests/cefclient/cefclient_gtk.cc | 19 +- 3 files changed, 512 insertions(+), 33 deletions(-) diff --git a/tests/cefclient/browser/osr_renderer.cc b/tests/cefclient/browser/osr_renderer.cc index ef0880449..e1344cdfc 100644 --- a/tests/cefclient/browser/osr_renderer.cc +++ b/tests/cefclient/browser/osr_renderer.cc @@ -4,11 +4,20 @@ #include "tests/cefclient/browser/osr_renderer.h" +#if USE_SHADERS +// Expose prototypes for OpenGL shader functions. +#define GL_GLEXT_PROTOTYPES +#endif + #if defined(OS_WIN) #include #elif defined(OS_MAC) #define GL_SILENCE_DEPRECATION +#if USE_SHADERS +#include +#else #include +#endif #elif defined(OS_LINUX) #include #else @@ -41,14 +50,205 @@ namespace client { +#if USE_SHADERS +namespace { + +const char* kScreenVertexShader = + "#version 330 core\n" + "out vec2 texCoord;\n" + "uniform mat4 transform;\n" + "void main() {\n" + "\tfloat x = float(((uint(gl_VertexID) + 2u) / 3u)\%2u);\n" + "\tfloat y = float(((uint(gl_VertexID) + 1u) / 3u)\%2u);\n" + "\tvec4 pos = vec4(-1.0f + x*2.0f, -1.0f + y*2.0f, 0.0f, 1.0f);\n" + "\tgl_Position = transform * pos;\n" + "\ttexCoord = vec2(x, -y);\n" + "}"; + +const char* kScreenFragmentShader = + "#version 330 core\n" + "out vec4 fColor;\n" + "in vec2 texCoord;\n" + "uniform sampler2D texture;\n" + "void main() {\n" + "\tfColor = texture2D(texture, texCoord);\n" + "}"; + +const char* kUpdateRectVertexShader = + "#version 330 core\n" + "layout (location = 0) in vec2 pos;\n" + "layout (location = 1) in vec3 color;\n" + "out vec4 vColor;\n" + "uniform mat4 transform;\n" + "void main() {\n" + "\tgl_Position = transform * vec4(pos, 0.0f, 1.0f);\n" + "\tvColor = vec4(color, 1.0f);\n" + "}"; + +const char* kUpdateRectFragmentShader = + "#version 330 core\n" + "out vec4 fColor;\n" + "in vec4 vColor;\n" + "void main() {\n" + "\tfColor = vColor;\n" + "}"; + +// clang-format off +constexpr float kLineVertices[] = { + // pos // color + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f +}; +// clang-format on + +using mat4x4_t = float[16]; + +void mat4x4_identity(mat4x4_t& matrix) { + // row 0 + matrix[0] = 1.0f; + matrix[1] = 0.0f; + matrix[2] = 0.0f; + matrix[3] = 0.0f; + + // row 1 + matrix[4] = 0.0f; + matrix[5] = 1.0f; + matrix[6] = 0.0f; + matrix[7] = 0.0f; + + // row 2 + matrix[8] = 0.0f; + matrix[9] = 0.0f; + matrix[10] = 1.0f; + matrix[11] = 0.0f; + + // row 3 + matrix[12] = 0.0f; + matrix[13] = 0.0f; + matrix[14] = 0.0f; + matrix[15] = 1.0f; +} + +void mat4x4_rotate(mat4x4_t& matrix, float angle, float x, float y, float z) { + float c, s, t; + float length, theta; + + // degrees to radians + theta = angle * (M_PI / 180.0f); + + // normalize + length = sqrtf(x * x + y * y + z * z); + + // too close to 0, can't make normalized vector + if (length < 0.0001f) { + return; + } + + x /= length; + y /= length; + z /= length; + + c = cosf(theta); + s = sinf(theta); + t = 1.0f - c; + + // row 0 + matrix[0] = t * x * x + c; + matrix[1] = t * x * y - s * z; + matrix[2] = t * x * z + s * y; + matrix[3] = 0.0f; + + // row 1 + matrix[4] = t * y * x + s * z; + matrix[5] = t * y * y + c; + matrix[6] = t * y * z - s * x; + matrix[7] = 0.0f; + + // row 2 + matrix[8] = t * x * z - s * y; + matrix[9] = t * y * z + s * x; + matrix[10] = t * z * z + c; + matrix[11] = 0.0f; + + // row 3 + matrix[12] = 0.0f; + matrix[13] = 0.0f; + matrix[14] = 0.0f; + matrix[15] = 1.0f; +} + +void mat4x4_ortho(mat4x4_t& matrix, + float left, + float right, + float bottom, + float top, + float near, + float far) { + // row 0 + matrix[0] = 2.0f / (right - left); + matrix[1] = 0.0f; + matrix[2] = 0.0f; + matrix[3] = 0.0f; + + // row 1 + matrix[4] = 0.0f; + matrix[5] = 2.0f / (top - bottom); + matrix[6] = 0.0f; + matrix[7] = 0.0f; + + // row 2 + matrix[8] = 0.0f; + matrix[9] = 0.0f; + matrix[10] = -2.0f / (far - near); + matrix[11] = 0.0f; + + // row 3 + matrix[12] = -(right + left) / (right - left); + matrix[13] = -(top + bottom) / (top - bottom); + matrix[14] = -(far + near) / (far - near); + matrix[15] = 1.0f; +} + +void mat4x4_multiply(mat4x4_t& c, const mat4x4_t& a, const mat4x4_t& b) { + // row 0 + c[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8]; + c[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9]; + c[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10]; + c[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3]; + + // row 1 + c[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8]; + c[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9]; + c[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10]; + c[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7]; + + // row 2 + c[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8]; + c[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9]; + c[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10]; + c[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11]; + + // row 3 + c[12] = 0.0f; + c[13] = 0.0f; + c[14] = 0.0f; + c[15] = 1.0f; +} + +} // namespace +#endif // USE_SHADERS + OsrRenderer::OsrRenderer(const OsrRendererSettings& settings) - : settings_(settings), - initialized_(false), - texture_id_(0), - view_width_(0), - view_height_(0), - spin_x_(0), - spin_y_(0) {} + : settings_(settings) +#if USE_SHADERS + , + line_vertices_(kLineVertices, kLineVertices + std::size(kLineVertices)) +#endif +{ +} OsrRenderer::~OsrRenderer() { Cleanup(); @@ -73,6 +273,133 @@ void OsrRenderer::Initialize() { VERIFY_NO_ERROR; } +#if USE_SHADERS + glGenVertexArrays(1, &vao_id_); + VERIFY_NO_ERROR; + glGenBuffers(1, &vbo_id_); + VERIFY_NO_ERROR; + + glBindVertexArray(vao_id_); + VERIFY_NO_ERROR; + + glBindBuffer(GL_ARRAY_BUFFER, vbo_id_); + VERIFY_NO_ERROR; + glBufferData(GL_ARRAY_BUFFER, line_vertices_.size() * sizeof(float), nullptr, + GL_STATIC_DRAW); + VERIFY_NO_ERROR; + + int success; + unsigned int vertex_shader_id; + unsigned int fragment_shader_id; + char infoLog[512]; + + // create & compile screen vertex shader program + vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); + VERIFY_NO_ERROR; + glShaderSource(vertex_shader_id, 1, &kScreenVertexShader, NULL); + VERIFY_NO_ERROR; + glCompileShader(vertex_shader_id); + VERIFY_NO_ERROR; + glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &success); + VERIFY_NO_ERROR; + if (!success) { + glGetShaderInfoLog(vertex_shader_id, 512, NULL, infoLog); + VERIFY_NO_ERROR; + LOG(ERROR) << "Vertex shader compile error: " << infoLog; + } + + // create & compile screen fragment shader program + fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); + VERIFY_NO_ERROR; + glShaderSource(fragment_shader_id, 1, &kScreenFragmentShader, NULL); + VERIFY_NO_ERROR; + glCompileShader(fragment_shader_id); + VERIFY_NO_ERROR; + glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &success); + VERIFY_NO_ERROR; + if (!success) { + glGetShaderInfoLog(fragment_shader_id, 512, NULL, infoLog); + VERIFY_NO_ERROR; + LOG(ERROR) << "Fragment shader compile error: " << infoLog; + } + + // create final shader program + screen_shader_program_id_ = glCreateProgram(); + VERIFY_NO_ERROR; + glAttachShader(screen_shader_program_id_, vertex_shader_id); + VERIFY_NO_ERROR; + glAttachShader(screen_shader_program_id_, fragment_shader_id); + VERIFY_NO_ERROR; + glLinkProgram(screen_shader_program_id_); + VERIFY_NO_ERROR; + glGetProgramiv(screen_shader_program_id_, GL_LINK_STATUS, &success); + VERIFY_NO_ERROR; + if (!success) { + glGetProgramInfoLog(screen_shader_program_id_, 512, NULL, infoLog); + VERIFY_NO_ERROR; + LOG(ERROR) << "Shader program link error: " << infoLog; + } + + // delete the shader's as they're linked into our program now + glDeleteShader(vertex_shader_id); + VERIFY_NO_ERROR; + glDeleteShader(fragment_shader_id); + VERIFY_NO_ERROR; + + // create & compile update rect vertex shader program + vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); + VERIFY_NO_ERROR; + glShaderSource(vertex_shader_id, 1, &kUpdateRectVertexShader, NULL); + VERIFY_NO_ERROR; + glCompileShader(vertex_shader_id); + VERIFY_NO_ERROR; + glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &success); + VERIFY_NO_ERROR; + if (!success) { + glGetShaderInfoLog(vertex_shader_id, 512, NULL, infoLog); + VERIFY_NO_ERROR; + LOG(ERROR) << "Vertex shader compile error: " << infoLog; + } + + // create & compile update rect fragment shader program + fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); + VERIFY_NO_ERROR; + glShaderSource(fragment_shader_id, 1, &kUpdateRectFragmentShader, NULL); + VERIFY_NO_ERROR; + glCompileShader(fragment_shader_id); + VERIFY_NO_ERROR; + glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &success); + VERIFY_NO_ERROR; + if (!success) { + glGetShaderInfoLog(fragment_shader_id, 512, NULL, infoLog); + VERIFY_NO_ERROR; + LOG(ERROR) << "Fragment shader compile error: " << infoLog; + } + + // create final shader program + update_rect_shader_program_id_ = glCreateProgram(); + VERIFY_NO_ERROR; + glAttachShader(update_rect_shader_program_id_, vertex_shader_id); + VERIFY_NO_ERROR; + glAttachShader(update_rect_shader_program_id_, fragment_shader_id); + VERIFY_NO_ERROR; + glLinkProgram(update_rect_shader_program_id_); + VERIFY_NO_ERROR; + glGetProgramiv(update_rect_shader_program_id_, GL_LINK_STATUS, &success); + VERIFY_NO_ERROR; + if (!success) { + glGetProgramInfoLog(update_rect_shader_program_id_, 512, NULL, infoLog); + VERIFY_NO_ERROR; + LOG(ERROR) << "Shader program link error: " << infoLog; + } + + // delete the shader's as they're linked into our program now + glDeleteShader(vertex_shader_id); + VERIFY_NO_ERROR; + glDeleteShader(fragment_shader_id); + VERIFY_NO_ERROR; +#endif // USE_SHADERS + // Necessary for non-power-of-2 textures to render correctly. glPixelStorei(GL_UNPACK_ALIGNMENT, 1); VERIFY_NO_ERROR; @@ -89,8 +416,15 @@ void OsrRenderer::Initialize() { VERIFY_NO_ERROR; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); VERIFY_NO_ERROR; +#if USE_SHADERS + glUseProgram(screen_shader_program_id_); + VERIFY_NO_ERROR; + glUniform1i(glGetUniformLocation(screen_shader_program_id_, "texture"), 0); + VERIFY_NO_ERROR; +#else glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); VERIFY_NO_ERROR; +#endif initialized_ = true; } @@ -99,6 +433,17 @@ void OsrRenderer::Cleanup() { if (texture_id_ != 0) { glDeleteTextures(1, &texture_id_); } +#if USE_SHADERS + if (vao_id_ != 0) { + glDeleteVertexArrays(1, &vao_id_); + } + if (screen_shader_program_id_ != 0) { + glDeleteProgram(screen_shader_program_id_); + } + if (update_rect_shader_program_id_ != 0) { + glDeleteProgram(screen_shader_program_id_); + } +#endif } void OsrRenderer::Render() { @@ -108,6 +453,131 @@ void OsrRenderer::Render() { DCHECK(initialized_); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + VERIFY_NO_ERROR; + // Match GL units to screen coordinates. + glViewport(0, 0, view_width_, view_height_); + VERIFY_NO_ERROR; + +#if USE_SHADERS + mat4x4_t transformation; + mat4x4_t rotX, rotY; + + mat4x4_identity(transformation); + mat4x4_identity(rotX); + mat4x4_identity(rotY); + + // Rotate the view based on the mouse spin. + if (spin_x_ != 0) { + mat4x4_rotate(rotX, -spin_x_, 1.0f, 0.0f, 0.0f); + } + if (spin_y_ != 0) { + mat4x4_rotate(rotY, -spin_y_, 0.0f, 1.0f, 0.0f); + } + mat4x4_multiply(transformation, rotX, rotY); + + if (IsTransparent()) { + // Alpha blending style. Texture values have premultiplied alpha. + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + VERIFY_NO_ERROR; + + // Enable alpha blending. + glEnable(GL_BLEND); + VERIFY_NO_ERROR; + } + glActiveTexture(GL_TEXTURE0); + VERIFY_NO_ERROR; + glBindTexture(GL_TEXTURE_2D, texture_id_); + VERIFY_NO_ERROR; + + glUseProgram(screen_shader_program_id_); + VERIFY_NO_ERROR; + glUniformMatrix4fv( + glGetUniformLocation(screen_shader_program_id_, "transform"), 1, GL_FALSE, + transformation); + VERIFY_NO_ERROR; + glDrawArrays(GL_TRIANGLES, 0, 6); + VERIFY_NO_ERROR; + + if (IsTransparent()) { + // Disable alpha blending. + glDisable(GL_BLEND); + VERIFY_NO_ERROR; + } + // Draw a rectangle around the update region. + if (settings_.show_update_rect && !update_rect_.IsEmpty()) { + mat4x4_t projection; + int left = update_rect_.x; + int right = update_rect_.x + update_rect_.width; + int top = update_rect_.y; + int bottom = update_rect_.y + update_rect_.height; + float* vertices = line_vertices_.data(); + +#if defined(OS_LINUX) + // Shrink the box so that top & right sides are drawn. + top += 1; + right -= 1; +#else + // Shrink the box so that left & bottom sides are drawn. + left += 1; + bottom -= 1; +#endif + + mat4x4_ortho(projection, 0.0f, view_width_, view_height_, 0.0f, 0.0f, 1.0f); + + // v0 + vertices[0] = left; + vertices[1] = top; + + // v1 + vertices[5] = right; + vertices[6] = top; + + // v2 + vertices[10] = right; + vertices[11] = bottom; + + // v3 + vertices[15] = left; + vertices[16] = bottom; + + // v4 + vertices[20] = left; + vertices[21] = top; + + void* ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); + VERIFY_NO_ERROR; + memcpy(ptr, line_vertices_.data(), line_vertices_.size() * sizeof(float)); + glUnmapBuffer(GL_ARRAY_BUFFER); + VERIFY_NO_ERROR; + + glLineWidth(1.0f); + VERIFY_NO_ERROR; + + glUseProgram(update_rect_shader_program_id_); + VERIFY_NO_ERROR; + glUniformMatrix4fv( + glGetUniformLocation(update_rect_shader_program_id_, "transform"), 1, + GL_FALSE, projection); + VERIFY_NO_ERROR; + glEnableVertexAttribArray(0); + VERIFY_NO_ERROR; + glEnableVertexAttribArray(1); + VERIFY_NO_ERROR; + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), + (void*)0); + VERIFY_NO_ERROR; + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), + (void*)(2 * sizeof(float))); + VERIFY_NO_ERROR; + glDrawArrays(GL_LINE_LOOP, 0, 5); + VERIFY_NO_ERROR; + glDisableVertexAttribArray(0); + VERIFY_NO_ERROR; + glDisableVertexAttribArray(1); + VERIFY_NO_ERROR; + } +#else // !USE_SHADERS struct { float tu, tv; float x, y, z; @@ -116,17 +586,11 @@ void OsrRenderer::Render() { {1.0f, 0.0f, 1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, -1.0f, 1.0f, 0.0f}}; - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - VERIFY_NO_ERROR; - glMatrixMode(GL_MODELVIEW); VERIFY_NO_ERROR; glLoadIdentity(); VERIFY_NO_ERROR; - // Match GL units to screen coordinates. - glViewport(0, 0, view_width_, view_height_); - VERIFY_NO_ERROR; glMatrixMode(GL_PROJECTION); VERIFY_NO_ERROR; glLoadIdentity(); @@ -239,6 +703,7 @@ void OsrRenderer::Render() { glPopAttrib(); VERIFY_NO_ERROR; } +#endif // !USE_SHADERS } void OsrRenderer::OnPopupShow(CefRefPtr browser, bool show) { @@ -304,9 +769,11 @@ void OsrRenderer::OnPaint(CefRefPtr browser, VERIFY_NO_ERROR; } +#if !USE_SHADERS // Enable 2D textures. glEnable(GL_TEXTURE_2D); VERIFY_NO_ERROR; +#endif DCHECK_NE(texture_id_, 0U); glBindTexture(GL_TEXTURE_2D, texture_id_); @@ -389,9 +856,11 @@ void OsrRenderer::OnPaint(CefRefPtr browser, VERIFY_NO_ERROR; } +#if !USE_SHADERS // Disable 2D textures. glDisable(GL_TEXTURE_2D); VERIFY_NO_ERROR; +#endif if (IsTransparent()) { // Disable alpha blending. diff --git a/tests/cefclient/browser/osr_renderer.h b/tests/cefclient/browser/osr_renderer.h index f66cc24d5..9754deabd 100644 --- a/tests/cefclient/browser/osr_renderer.h +++ b/tests/cefclient/browser/osr_renderer.h @@ -6,10 +6,22 @@ #define CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDERER_H_ #pragma once +#include + #include "include/cef_browser.h" #include "include/cef_render_handler.h" #include "tests/cefclient/browser/osr_renderer_settings.h" +// Enable shader-based rendering for Linux only. Windows still uses OpenGL 1.1 +// to avoid the added complexity of linking newer OpenGL APIs on that platform. +// MacOS has deprecated OpenGL and we should eventually provide a Metal-based +// implementation on that platform. +#if defined(OS_LINUX) +#define USE_SHADERS 1 +#else +#define USE_SHADERS 0 +#endif + namespace client { class OsrRenderer { @@ -57,14 +69,21 @@ class OsrRenderer { } const OsrRendererSettings settings_; - bool initialized_; - unsigned int texture_id_; - int view_width_; - int view_height_; + bool initialized_ = false; + unsigned int texture_id_ = 0; +#if USE_SHADERS + unsigned int vao_id_ = 0; + unsigned int vbo_id_ = 0; + unsigned int screen_shader_program_id_ = 0; + unsigned int update_rect_shader_program_id_ = 0; + std::vector line_vertices_; +#endif + int view_width_ = 0; + int view_height_ = 0; CefRect popup_rect_; CefRect original_popup_rect_; - float spin_x_; - float spin_y_; + float spin_x_ = 0; + float spin_y_ = 0; CefRect update_rect_; DISALLOW_COPY_AND_ASSIGN(OsrRenderer); diff --git a/tests/cefclient/cefclient_gtk.cc b/tests/cefclient/cefclient_gtk.cc index 1dd53fe8e..e0d170af6 100644 --- a/tests/cefclient/cefclient_gtk.cc +++ b/tests/cefclient/cefclient_gtk.cc @@ -31,13 +31,11 @@ namespace client { namespace { int XErrorHandlerImpl(Display* display, XErrorEvent* event) { - LOG(WARNING) << "X error received: " - << "type " << event->type << ", " - << "serial " << event->serial << ", " - << "error_code " << static_cast(event->error_code) << ", " - << "request_code " << static_cast(event->request_code) - << ", " - << "minor_code " << static_cast(event->minor_code); + LOG(WARNING) << "X error received: " << "type " << event->type << ", " + << "serial " << event->serial << ", " << "error_code " + << static_cast(event->error_code) << ", " << "request_code " + << static_cast(event->request_code) << ", " << "minor_code " + << static_cast(event->minor_code); return 0; } @@ -98,13 +96,6 @@ int RunMain(int argc, char* argv[]) { // Populate the settings based on command line arguments. context->PopulateSettings(&settings); - if (settings.windowless_rendering_enabled) { - // Force the app to use OpenGL <= 3.1 when off-screen rendering is enabled. - // TODO(cefclient): Rewrite OSRRenderer to use shaders instead of the - // fixed-function pipeline which was removed in OpenGL 3.2 (back in 2009). - setenv("MESA_GL_VERSION_OVERRIDE", "3.1", /*overwrite=*/0); - } - // Create the main message loop object. std::unique_ptr message_loop; if (settings.multi_threaded_message_loop) {