blob: d97d12abb84ffaa667e2964c922d74786b482cd2 [file] [log] [blame]
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/renderer/rasterizer/egl/graphics_state.h"
#include <algorithm>
#include "cobalt/renderer/backend/egl/utils.h"
namespace cobalt {
namespace renderer {
namespace rasterizer {
namespace egl {
namespace {
void GLViewport(const math::Rect& viewport, const math::Size& target_size) {
// Incoming origin is top-left, but GL origin is bottom-left, so flip
// vertically.
GL_CALL(glViewport(viewport.x(), target_size.height() - viewport.bottom(),
viewport.width(), viewport.height()));
}
void GLScissor(const math::Rect& scissor, const math::Size& target_size) {
// Incoming origin is top-left, but GL origin is bottom-left, so flip
// vertically.
GL_CALL(glScissor(scissor.x(), target_size.height() - scissor.bottom(),
scissor.width(), scissor.height()));
}
} // namespace
GraphicsState::GraphicsState()
: render_target_handle_(0),
render_target_serial_(0),
frame_index_(0),
vertex_data_reserved_(kVertexDataAlignment - 1),
vertex_data_allocated_(0),
vertex_data_buffer_updated_(false) {
GL_CALL(glGenBuffers(kNumFramesBuffered, &vertex_data_buffer_handle_[0]));
memset(clip_adjustment_, 0, sizeof(clip_adjustment_));
SetDirty();
blend_enabled_ = false;
Reset();
// These settings should only need to be set once. Nothing should touch them.
GL_CALL(glDisable(GL_DITHER));
GL_CALL(glDisable(GL_CULL_FACE));
GL_CALL(glDisable(GL_STENCIL_TEST));
}
GraphicsState::~GraphicsState() {
GL_CALL(glDeleteBuffers(kNumFramesBuffered, &vertex_data_buffer_handle_[0]));
}
void GraphicsState::SetDirty() {
state_dirty_ = true;
clip_adjustment_dirty_ = true;
}
void GraphicsState::BeginFrame() {
DCHECK_EQ(vertex_data_allocated_, 0);
if (vertex_data_reserved_ > vertex_data_buffer_.capacity()) {
vertex_data_buffer_.reserve(vertex_data_reserved_);
}
// Reset to default GL state. Assume the current state is dirty, so just
// set the cached state. The actual GL state will be updated to match the
// cached state when needed.
SetDirty();
blend_enabled_ = false;
}
void GraphicsState::EndFrame() {
// Reset the vertex data buffer.
vertex_data_reserved_ = kVertexDataAlignment - 1;
vertex_data_allocated_ = 0;
vertex_data_buffer_updated_ = false;
frame_index_ = (frame_index_ + 1) % kNumFramesBuffered;
// Force default GL state. The current state may be marked dirty, so don't
// rely on any functions which check the cached state before issuing GL calls.
GL_CALL(glDisable(GL_BLEND));
GL_CALL(glUseProgram(0));
// Since the GL state was changed without going through the cache, mark it
// as dirty.
SetDirty();
}
void GraphicsState::Clear() {
Clear(0.0f, 0.0f, 0.0f, 0.0f);
}
void GraphicsState::Clear(float r, float g, float b, float a) {
if (state_dirty_) {
Reset();
}
GL_CALL(glClearColor(r, g, b, a));
GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
}
void GraphicsState::UseProgram(GLuint program) {
if (state_dirty_) {
Reset();
}
if (program_ != program) {
program_ = program;
clip_adjustment_dirty_ = true;
GL_CALL(glUseProgram(program));
}
if (array_buffer_handle_ != vertex_data_buffer_handle_[frame_index_]) {
array_buffer_handle_ = vertex_data_buffer_handle_[frame_index_];
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, array_buffer_handle_));
}
// Disable any vertex attribute arrays that will not be used.
disable_vertex_attrib_array_mask_ = enabled_vertex_attrib_array_mask_;
}
void GraphicsState::BindFramebuffer(
const backend::RenderTarget* render_target) {
if (render_target == nullptr) {
// Unbind the framebuffer immediately.
if (render_target_handle_ != 0) {
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
}
render_target_handle_ = 0;
render_target_serial_ = 0;
render_target_size_.SetSize(0, 0);
SetClipAdjustment();
} else if (render_target->GetPlatformHandle() != render_target_handle_ ||
render_target->GetSerialNumber() != render_target_serial_ ||
render_target->GetSize() != render_target_size_) {
render_target_handle_ = render_target->GetPlatformHandle();
render_target_serial_ = render_target->GetSerialNumber();
render_target_size_ = render_target->GetSize();
SetClipAdjustment();
if (!state_dirty_) {
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, render_target_handle_));
}
}
}
void GraphicsState::Viewport(int x, int y, int width, int height) {
if (viewport_.x() != x || viewport_.y() != y ||
viewport_.width() != width || viewport_.height() != height) {
viewport_.SetRect(x, y, width, height);
if (!state_dirty_) {
GLViewport(viewport_, render_target_size_);
}
}
}
void GraphicsState::Scissor(int x, int y, int width, int height) {
if (scissor_.x() != x || scissor_.y() != y ||
scissor_.width() != width || scissor_.height() != height) {
scissor_.SetRect(x, y, width, height);
if (!state_dirty_) {
GLScissor(scissor_, render_target_size_);
}
}
}
void GraphicsState::EnableBlend() {
if (!blend_enabled_) {
blend_enabled_ = true;
GL_CALL(glEnable(GL_BLEND));
}
}
void GraphicsState::DisableBlend() {
if (blend_enabled_) {
blend_enabled_ = false;
GL_CALL(glDisable(GL_BLEND));
}
}
void GraphicsState::ActiveBindTexture(GLenum texture_unit, GLenum target,
GLuint texture) {
int texunit_index = texture_unit - GL_TEXTURE0;
// Update only if it doesn't match the current state.
if (texunit_index >= kNumTextureUnitsCached ||
texunit_target_[texunit_index] != target ||
texunit_texture_[texunit_index] != texture) {
if (texture_unit_ != texture_unit) {
texture_unit_ = texture_unit;
GL_CALL(glActiveTexture(texture_unit));
}
GL_CALL(glBindTexture(target, texture));
if (texunit_index < kNumTextureUnitsCached) {
texunit_target_[texunit_index] = target;
texunit_texture_[texunit_index] = texture;
}
}
}
void GraphicsState::ActiveBindTexture(GLenum texture_unit, GLenum target,
GLuint texture, GLint texture_wrap_mode) {
int texunit_index = texture_unit - GL_TEXTURE0;
if (texture_unit_ != texture_unit) {
texture_unit_ = texture_unit;
GL_CALL(glActiveTexture(texture_unit));
GL_CALL(glBindTexture(target, texture));
} else if (texunit_index >= kNumTextureUnitsCached ||
texunit_target_[texunit_index] != target ||
texunit_texture_[texunit_index] != texture) {
GL_CALL(glBindTexture(target, texture));
}
if (texunit_index < kNumTextureUnitsCached) {
texunit_target_[texunit_index] = target;
texunit_texture_[texunit_index] = texture;
}
GL_CALL(glTexParameteri(target, GL_TEXTURE_WRAP_S, texture_wrap_mode));
GL_CALL(glTexParameteri(target, GL_TEXTURE_WRAP_T, texture_wrap_mode));
}
void GraphicsState::SetClipAdjustment() {
clip_adjustment_dirty_ = true;
// Clip adjustment is a vec4 used to transform a given 2D position from view
// space to clip space. Given a 2D position, pos, the output is:
// output = pos * clip_adjustment_.xy + clip_adjustment_.zw
if (render_target_size_.width() > 0) {
clip_adjustment_[0] = 2.0f / render_target_size_.width();
clip_adjustment_[2] = -1.0f;
} else {
clip_adjustment_[0] = 0.0f;
clip_adjustment_[2] = 0.0f;
}
if (render_target_size_.height() > 0) {
// Incoming origin is top-left, but GL origin is bottom-left, so flip the
// image vertically.
clip_adjustment_[1] = -2.0f / render_target_size_.height();
clip_adjustment_[3] = 1.0f;
} else {
clip_adjustment_[1] = 0.0f;
clip_adjustment_[3] = 0.0f;
}
}
void GraphicsState::UpdateClipAdjustment(GLint handle) {
if (clip_adjustment_dirty_) {
clip_adjustment_dirty_ = false;
GL_CALL(glUniform4fv(handle, 1, clip_adjustment_));
}
}
void GraphicsState::UpdateTransformMatrix(GLint handle,
const math::Matrix3F& transform) {
// Manually transpose our row-major matrix to column-major. Don't rely on
// glUniformMatrix3fv to do it, since the driver may not support that.
float transpose[] = {
transform(0, 0), transform(1, 0), transform(2, 0),
transform(0, 1), transform(1, 1), transform(2, 1),
transform(0, 2), transform(1, 2), transform(2, 2)
};
GL_CALL(glUniformMatrix3fv(handle, 1, GL_FALSE, transpose));
}
void GraphicsState::ReserveVertexData(size_t bytes) {
DCHECK_EQ(vertex_data_allocated_, 0);
DCHECK(!vertex_data_buffer_updated_);
vertex_data_reserved_ += bytes + (kVertexDataAlignment - 1) &
~(kVertexDataAlignment - 1);
}
uint8_t* GraphicsState::AllocateVertexData(size_t bytes) {
DCHECK(!vertex_data_buffer_updated_);
// Ensure the start address is aligned.
uintptr_t start_address =
reinterpret_cast<uintptr_t>(&vertex_data_buffer_[0]) +
vertex_data_allocated_ + (kVertexDataAlignment - 1) &
~(kVertexDataAlignment - 1);
vertex_data_allocated_ = start_address -
reinterpret_cast<uintptr_t>(&vertex_data_buffer_[0]) + bytes;
DCHECK_LE(vertex_data_allocated_, vertex_data_reserved_);
return reinterpret_cast<uint8_t*>(start_address);
}
void GraphicsState::UpdateVertexData() {
DCHECK(!vertex_data_buffer_updated_);
vertex_data_buffer_updated_ = true;
if (array_buffer_handle_ != vertex_data_buffer_handle_[frame_index_]) {
array_buffer_handle_ = vertex_data_buffer_handle_[frame_index_];
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, array_buffer_handle_));
}
if (vertex_data_allocated_ > 0) {
GL_CALL(glBufferData(GL_ARRAY_BUFFER, vertex_data_allocated_,
&vertex_data_buffer_[0], GL_STREAM_DRAW));
}
}
void GraphicsState::VertexAttribPointer(GLint index, GLint size, GLenum type,
GLboolean normalized, GLsizei stride, const void* client_pointer) {
const GLvoid* gl_pointer = reinterpret_cast<const GLvoid*>
(reinterpret_cast<const uint8_t*>(client_pointer) -
&vertex_data_buffer_[0]);
GL_CALL(glVertexAttribPointer(index, size, type, normalized, stride,
gl_pointer));
// Ensure the vertex attrib array is enabled.
const uint32_t mask = 1 << index;
if ((enabled_vertex_attrib_array_mask_ & mask) == 0) {
enabled_vertex_attrib_array_mask_ |= mask;
GL_CALL(glEnableVertexAttribArray(index));
}
disable_vertex_attrib_array_mask_ &= ~mask;
}
void GraphicsState::VertexAttribFinish() {
enabled_vertex_attrib_array_mask_ &= ~disable_vertex_attrib_array_mask_;
for (int index = 0; disable_vertex_attrib_array_mask_ != 0; ++index) {
if ((disable_vertex_attrib_array_mask_ & 1) != 0) {
GL_CALL(glDisableVertexAttribArray(index));
}
disable_vertex_attrib_array_mask_ >>= 1;
}
}
void GraphicsState::Reset() {
program_ = 0;
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, render_target_handle_));
GLViewport(viewport_, render_target_size_);
GLScissor(scissor_, render_target_size_);
GL_CALL(glEnable(GL_SCISSOR_TEST));
array_buffer_handle_ = 0;
texture_unit_ = 0;
memset(&texunit_target_, 0, sizeof(texunit_target_));
memset(&texunit_texture_, 0, sizeof(texunit_texture_));
enabled_vertex_attrib_array_mask_ = 0;
disable_vertex_attrib_array_mask_ = 0;
clip_adjustment_dirty_ = true;
if (vertex_data_buffer_updated_) {
array_buffer_handle_ = vertex_data_buffer_handle_[frame_index_];
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, array_buffer_handle_));
}
if (blend_enabled_) {
GL_CALL(glEnable(GL_BLEND));
} else {
GL_CALL(glDisable(GL_BLEND));
}
GL_CALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
GL_CALL(glDisable(GL_DEPTH_TEST));
state_dirty_ = false;
}
} // namespace egl
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt