| // 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_capacity_(0), |
| vertex_data_reserved_(kVertexDataAlignment - 1), |
| vertex_data_allocated_(0), |
| vertex_index_capacity_(0), |
| vertex_index_reserved_(0), |
| vertex_index_allocated_(0), |
| vertex_buffers_updated_(false) { |
| // https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGet.xml |
| // GL_MAX_VERTEX_ATTRIBS should return at least 8. So default to that in |
| // case the glGetIntergerv call fails. |
| max_vertex_attribs_ = 8; |
| GL_CALL(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attribs_)); |
| |
| GL_CALL(glGenBuffers(kNumFramesBuffered, vertex_data_buffer_handle_)); |
| GL_CALL(glGenBuffers(kNumFramesBuffered, vertex_index_buffer_handle_)); |
| memset(clip_adjustment_, 0, sizeof(clip_adjustment_)); |
| SetDirty(); |
| blend_enabled_ = false; |
| Reset(); |
| } |
| |
| GraphicsState::~GraphicsState() { |
| GL_CALL(glDeleteBuffers(kNumFramesBuffered, vertex_data_buffer_handle_)); |
| GL_CALL(glDeleteBuffers(kNumFramesBuffered, vertex_index_buffer_handle_)); |
| } |
| |
| 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_capacity_) { |
| vertex_data_capacity_ = vertex_data_reserved_; |
| vertex_data_buffer_.reset(new uint8_t[vertex_data_capacity_]); |
| } |
| DCHECK_EQ(vertex_index_allocated_, 0); |
| if (vertex_index_reserved_ > vertex_index_capacity_) { |
| vertex_index_capacity_ = vertex_index_reserved_; |
| vertex_index_buffer_.reset(new uint16_t[vertex_index_capacity_]); |
| } |
| |
| // 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 and index buffers. |
| vertex_data_reserved_ = kVertexDataAlignment - 1; |
| vertex_data_allocated_ = 0; |
| vertex_index_reserved_ = 0; |
| vertex_index_allocated_ = 0; |
| vertex_buffers_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 (vertex_data_allocated_ > 0 && |
| 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_index_allocated_ > 0 && |
| index_buffer_handle_ != vertex_index_buffer_handle_[frame_index_]) { |
| index_buffer_handle_ = vertex_index_buffer_handle_[frame_index_]; |
| GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_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_buffers_updated_); |
| vertex_data_reserved_ += bytes + (kVertexDataAlignment - 1) & |
| ~(kVertexDataAlignment - 1); |
| } |
| |
| uint8_t* GraphicsState::AllocateVertexData(size_t bytes) { |
| DCHECK(!vertex_buffers_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::ReserveVertexIndices(size_t count) { |
| DCHECK_EQ(vertex_index_allocated_, 0); |
| DCHECK(!vertex_buffers_updated_); |
| vertex_index_reserved_ += count; |
| } |
| |
| uint16_t* GraphicsState::AllocateVertexIndices(size_t count) { |
| uint16_t* client_pointer = &vertex_index_buffer_[vertex_index_allocated_]; |
| vertex_index_allocated_ += count; |
| DCHECK_LE(vertex_index_allocated_, vertex_index_reserved_); |
| return client_pointer; |
| } |
| |
| const void* GraphicsState::GetVertexIndexPointer( |
| const uint16_t* client_pointer) { |
| DCHECK_GE(client_pointer, &vertex_index_buffer_[0]); |
| DCHECK_LE(client_pointer, &vertex_index_buffer_[vertex_index_allocated_]); |
| const GLvoid* gl_pointer = reinterpret_cast<const GLvoid*>( |
| reinterpret_cast<const uint8_t*>(client_pointer) - |
| reinterpret_cast<const uint8_t*>(&vertex_index_buffer_[0])); |
| return gl_pointer; |
| } |
| |
| void GraphicsState::UpdateVertexBuffers() { |
| DCHECK(!vertex_buffers_updated_); |
| vertex_buffers_updated_ = true; |
| |
| if (vertex_data_allocated_ > 0) { |
| 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_)); |
| } |
| GL_CALL(glBufferData(GL_ARRAY_BUFFER, vertex_data_allocated_, |
| &vertex_data_buffer_[0], GL_DYNAMIC_DRAW)); |
| } |
| |
| if (vertex_index_allocated_ > 0) { |
| if (index_buffer_handle_ != vertex_index_buffer_handle_[frame_index_]) { |
| index_buffer_handle_ = vertex_index_buffer_handle_[frame_index_]; |
| GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_handle_)); |
| } |
| GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, |
| vertex_index_allocated_ * sizeof(uint16_t), |
| &vertex_index_buffer_[0], GL_DYNAMIC_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(glDisable(GL_DITHER)); |
| GL_CALL(glDisable(GL_STENCIL_TEST)); |
| GL_CALL(glDisable(GL_CULL_FACE)); |
| |
| 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; |
| index_buffer_handle_ = 0; |
| texture_unit_ = 0; |
| memset(&texunit_target_, 0, sizeof(texunit_target_)); |
| memset(&texunit_texture_, 0, sizeof(texunit_texture_)); |
| clip_adjustment_dirty_ = true; |
| |
| enabled_vertex_attrib_array_mask_ = 0; |
| disable_vertex_attrib_array_mask_ = 0; |
| for (int index = 0; index < max_vertex_attribs_; ++index) { |
| GL_CALL(glDisableVertexAttribArray(index)); |
| } |
| |
| if (vertex_buffers_updated_) { |
| if (vertex_data_allocated_ > 0) { |
| array_buffer_handle_ = vertex_data_buffer_handle_[frame_index_]; |
| GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, array_buffer_handle_)); |
| } |
| if (vertex_index_allocated_ > 0) { |
| index_buffer_handle_ = vertex_index_buffer_handle_[frame_index_]; |
| GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_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 |