blob: b9af5339e87544f32d2360362278af6629198eed [file] [log] [blame]
// Copyright 2017 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 "chrome/browser/vr/renderers/textured_quad_renderer.h"
#include "base/stl_util.h"
#include "chrome/browser/vr/vr_gl_util.h"
#include "ui/gfx/transform.h"
namespace vr {
namespace {
// clang-format off
//
// A rounded rect is subdivided into a number of triangles.
// _______________
// | / _,-' \ |
// |/_,,-'______\|
// | /|
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// | / |
// |/____________|
// |\ _,-'' /|
// |_\ ,-'____ /_|
//
// Most of these do not contain an arc. To simplify the rendering of those
// that do, we include a "corner position" attribute. The corner position is
// the distance from the center of the nearest "corner circle". Only those
// triangles containing arcs have a non-zero corner position set. The result
// is that for interior triangles, their corner position is uniformly (0, 0).
// I.e., they are always deemed "inside".
//
// A further complication is that different corner radii will require these
// various triangles to be sized differently relative to one another. We
// would prefer not no continually recreate our vertex buffer, so we include
// another attribute, the "offset scalars". These scalars are only ever 1.0,
// 0.0, or -1.0 and control the addition or subtraction of the horizontal
// and vertical corner offset. This lets the corners of the triangles be
// computed in the vertex shader dynamically. It also happens that the
// texture coordinates can also be easily computed in the vertex shader.
//
// So if the the corner offsets are vr and hr where
// vr = corner_radius / height;
// hr = corner_radius / width;
//
// Then the full position is then given by
// p = (x + osx * hr, y + osy * vr, 0.0, 1.0)
//
// And the full texture coordinate is given by
// (0.5 + p[0], 0.5 - p[1])
//
static constexpr float kVertices[120] = {
// x y osx osy cpx cpy
-0.5f, 0.5f, 0.0, -1.0, 0.0, 0.0,
-0.5f, 0.5f, 1.0, 0.0, 0.0, 0.0,
0.5f, 0.5f, -1.0, 0.0, 0.0, 0.0,
0.5f, 0.5f, 0.0, -1.0, 0.0, 0.0,
-0.5f, -0.5f, 0.0, 1.0, 0.0, 0.0,
0.5f, -0.5f, 0.0, 1.0, 0.0, 0.0,
-0.5f, -0.5f, 1.0, 0.0, 0.0, 0.0,
0.5f, -0.5f, -1.0, 0.0, 0.0, 0.0,
// These are the corner triangles (note the non-zero cpx and cpy).
-0.5f, 0.5f, 0.0, -1.0, 1.0, 0.0,
-0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0,
-0.5f, 0.5f, 1.0, 0.0, 0.0, 1.0,
0.5f, 0.5f, -1.0, 0.0, 0.0, 1.0,
0.5f, 0.5f, 0.0, 0.0, 1.0, 1.0,
0.5f, 0.5f, 0.0, -1.0, 1.0, 0.0,
-0.5f, -0.5f, 0.0, 0.0, 1.0, 1.0,
-0.5f, -0.5f, 0.0, 1.0, 1.0, 0.0,
-0.5f, -0.5f, 1.0, 0.0, 0.0, 1.0,
0.5f, -0.5f, -1.0, 0.0, 0.0, 1.0,
0.5f, -0.5f, 0.0, 1.0, 1.0, 0.0,
0.5f, -0.5f, 0.0, 0.0, 1.0, 1.0,
};
static constexpr GLushort kIndices[30] = {
// This is the top trapezoid.
0, 2, 1,
0, 3, 2,
// These are the central triangles (the only triangles that matter if our
// corner radius is zero).
4, 3, 0,
4, 5, 3,
// This is the bottom trapezoid.
4, 6, 5,
6, 7, 5,
// These are the corners.
8, 10, 9,
11, 13, 12,
14, 16, 15,
17, 19, 18,
};
static constexpr int kPositionDataSize = 2;
static constexpr size_t kPositionDataOffset = 0;
static constexpr int kOffsetScaleDataSize = 2;
static constexpr size_t kOffsetScaleDataOffset = 2 * sizeof(float);
static constexpr int kCornerPositionDataSize = 2;
static constexpr size_t kCornerPositionDataOffset = 4 * sizeof(float);
static constexpr size_t kDataStride = 6 * sizeof(float);
static constexpr size_t kInnerRectOffset = 6 * sizeof(GLushort);
static constexpr char const* kVertexShader = SHADER(
precision mediump float;
uniform mat4 u_ModelViewProjMatrix;
uniform vec2 u_CornerOffset;
attribute vec4 a_Position;
attribute vec2 a_CornerPosition;
attribute vec2 a_OffsetScale;
varying vec2 v_TexCoordinate;
varying vec2 v_CornerPosition;
uniform bool u_UsesOverlay;
void main() {
v_CornerPosition = a_CornerPosition;
vec4 position = vec4(
a_Position[0] + u_CornerOffset[0] * a_OffsetScale[0],
a_Position[1] + u_CornerOffset[1] * a_OffsetScale[1],
a_Position[2],
a_Position[3]);
v_TexCoordinate = vec2(0.5 + position[0], 0.5 - position[1]);
gl_Position = u_ModelViewProjMatrix * position;
}
);
static constexpr char const* kFragmentShader = SHADER(
precision highp float;
uniform sampler2D u_Texture;
uniform sampler2D u_OverlayTexture;
uniform vec2 u_ClipRect[2];
varying vec2 v_TexCoordinate;
varying vec2 v_CornerPosition;
uniform mediump float u_Opacity;
uniform mediump float u_OverlayOpacity;
void main() {
vec2 s = step(u_ClipRect[0], v_TexCoordinate)
- step(u_ClipRect[1], v_TexCoordinate);
float insideClip = s.x * s.y;
lowp vec4 color = texture2D(u_Texture, v_TexCoordinate);
float mask = 1.0 - step(1.0, length(v_CornerPosition));
gl_FragColor = insideClip * color * u_Opacity * mask;
}
);
// clang-format on
} // namespace
TexturedQuadRenderer::TexturedQuadRenderer()
: TexturedQuadRenderer(kVertexShader, kFragmentShader) {}
TexturedQuadRenderer::TexturedQuadRenderer(const char* vertex_src,
const char* fragment_src)
: BaseRenderer(vertex_src, fragment_src) {
model_view_proj_matrix_handle_ =
glGetUniformLocation(program_handle_, "u_ModelViewProjMatrix");
corner_offset_handle_ =
glGetUniformLocation(program_handle_, "u_CornerOffset");
corner_position_handle_ =
glGetAttribLocation(program_handle_, "a_CornerPosition");
offset_scale_handle_ = glGetAttribLocation(program_handle_, "a_OffsetScale");
opacity_handle_ = glGetUniformLocation(program_handle_, "u_Opacity");
overlay_opacity_handle_ =
glGetUniformLocation(program_handle_, "u_OverlayOpacity");
texture_handle_ = glGetUniformLocation(program_handle_, "u_Texture");
overlay_texture_handle_ =
glGetUniformLocation(program_handle_, "u_OverlayTexture");
uses_overlay_handle_ = glGetUniformLocation(program_handle_, "u_UsesOverlay");
}
TexturedQuadRenderer::~TexturedQuadRenderer() = default;
void TexturedQuadRenderer::AddQuad(int texture_data_handle,
int overlay_texture_data_handle,
const gfx::Transform& model_view_proj_matrix,
const gfx::RectF& clip_rect,
float opacity,
const gfx::SizeF& element_size,
float corner_radius,
bool blend) {
if (!clip_rect.Intersects(gfx::RectF(1.0f, 1.0f)))
return;
QuadData quad;
quad.texture_data_handle = texture_data_handle;
quad.overlay_texture_data_handle = overlay_texture_data_handle;
quad.model_view_proj_matrix = model_view_proj_matrix;
quad.clip_rect = clip_rect;
quad.opacity = opacity;
quad.element_size = element_size;
quad.corner_radius = corner_radius;
quad.blend = blend;
quad_queue_.push(quad);
}
void TexturedQuadRenderer::Flush() {
if (quad_queue_.empty())
return;
int last_texture = -1;
int last_overlay_texture = -1;
float last_opacity = -1.0f;
gfx::SizeF last_element_size;
float last_corner_radius = -1.0f;
gfx::RectF last_clip_rect;
bool last_blend = true; // All elements blend by default.
// Set up GL state that doesn't change between draw calls.
glUseProgram(program_handle_);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_);
// Link texture data with texture unit.
glUniform1i(texture_handle_, 0);
glUniform1i(overlay_texture_handle_, 1);
// Set up position attribute.
glVertexAttribPointer(position_handle_, kPositionDataSize, GL_FLOAT, false,
kDataStride, VOID_OFFSET(kPositionDataOffset));
glEnableVertexAttribArray(position_handle_);
// Set up offset scale attribute.
glVertexAttribPointer(offset_scale_handle_, kOffsetScaleDataSize, GL_FLOAT,
false, kDataStride,
VOID_OFFSET(kOffsetScaleDataOffset));
glEnableVertexAttribArray(offset_scale_handle_);
// Set up corner position attribute.
glVertexAttribPointer(corner_position_handle_, kCornerPositionDataSize,
GL_FLOAT, false, kDataStride,
VOID_OFFSET(kCornerPositionDataOffset));
glEnableVertexAttribArray(corner_position_handle_);
glUniform1i(uses_overlay_handle_, false);
// TODO(bajones): This should eventually be changed to use instancing so that
// the entire queue can be processed in one draw call. For now this still
// significantly reduces the amount of state changes made per draw.
while (!quad_queue_.empty()) {
const QuadData& quad = quad_queue_.front();
if (last_blend != quad.blend) {
last_blend = quad.blend;
if (quad.blend) {
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} else {
glDisable(GL_BLEND);
}
}
// Only change texture ID or opacity when they differ between quads.
if (last_texture != quad.texture_data_handle) {
last_texture = quad.texture_data_handle;
glActiveTexture(GL_TEXTURE0);
glBindTexture(TextureType(), last_texture);
SetTexParameters(TextureType());
}
if (last_overlay_texture != quad.overlay_texture_data_handle) {
last_overlay_texture = quad.overlay_texture_data_handle;
glUniform1i(uses_overlay_handle_, quad.overlay_texture_data_handle != 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(TextureType(), last_overlay_texture);
SetTexParameters(TextureType());
glUniform1f(overlay_opacity_handle_, last_overlay_texture ? 1.0f : 0.0f);
}
if (last_opacity != quad.opacity) {
last_opacity = quad.opacity;
glUniform1f(opacity_handle_, last_opacity);
}
bool corner_attributes_changed = quad.corner_radius != last_corner_radius ||
quad.element_size != last_element_size;
if (corner_attributes_changed) {
last_corner_radius = quad.corner_radius;
last_element_size = quad.element_size;
if (quad.corner_radius == 0.0f) {
glUniform2f(corner_offset_handle_, 0.0, 0.0);
} else {
glUniform2f(corner_offset_handle_,
quad.corner_radius / quad.element_size.width(),
quad.corner_radius / quad.element_size.height());
}
}
// Pass in model view project matrix.
glUniformMatrix4fv(model_view_proj_matrix_handle_, 1, false,
MatrixToGLArray(quad.model_view_proj_matrix).data());
if (last_clip_rect != quad.clip_rect) {
last_clip_rect = quad.clip_rect;
const GLfloat clip_rect_data[4] = {quad.clip_rect.x(), quad.clip_rect.y(),
quad.clip_rect.right(),
quad.clip_rect.bottom()};
glUniform2fv(clip_rect_handle_, 2, clip_rect_data);
}
if (quad.corner_radius == 0.0f) {
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT,
VOID_OFFSET(kInnerRectOffset));
} else {
glDrawElements(GL_TRIANGLES, base::size(kIndices), GL_UNSIGNED_SHORT, 0);
}
quad_queue_.pop();
}
if (!last_blend) {
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
glDisableVertexAttribArray(position_handle_);
glDisableVertexAttribArray(offset_scale_handle_);
glDisableVertexAttribArray(corner_position_handle_);
}
GLuint TexturedQuadRenderer::vertex_buffer_ = 0;
GLuint TexturedQuadRenderer::index_buffer_ = 0;
void TexturedQuadRenderer::CreateBuffers() {
GLuint buffers[2];
glGenBuffers(2, buffers);
vertex_buffer_ = buffers[0];
index_buffer_ = buffers[1];
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
glBufferData(GL_ARRAY_BUFFER, base::size(kVertices) * sizeof(float),
kVertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, base::size(kIndices) * sizeof(GLushort),
kIndices, GL_STATIC_DRAW);
}
GLenum TexturedQuadRenderer::TextureType() const {
return GL_TEXTURE_2D;
}
const char* TexturedQuadRenderer::VertexShader() {
return kVertexShader;
}
GLuint TexturedQuadRenderer::VertexBuffer() {
return vertex_buffer_;
}
GLuint TexturedQuadRenderer::IndexBuffer() {
return index_buffer_;
}
int TexturedQuadRenderer::NumQuadIndices() {
return base::size(kIndices);
}
} // namespace vr