| // Copyright 2016 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/service/transform_feedback_manager.h" | |
| #include "base/check.h" | |
| #include "base/notreached.h" | |
| #include "base/numerics/checked_math.h" | |
| #include "gpu/command_buffer/service/buffer_manager.h" | |
| #include "gpu/command_buffer/service/program_manager.h" | |
| #include "ui/gl/gl_version_info.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| TransformFeedback::TransformFeedback(TransformFeedbackManager* manager, | |
| GLuint client_id, | |
| GLuint service_id) | |
| : IndexedBufferBindingHost( | |
| manager->max_transform_feedback_separate_attribs(), | |
| GL_TRANSFORM_FEEDBACK_BUFFER, | |
| manager->needs_emulation(), | |
| false), | |
| manager_(manager), | |
| client_id_(client_id), | |
| service_id_(service_id), | |
| has_been_bound_(false), | |
| active_(false), | |
| paused_(false), | |
| primitive_mode_(GL_NONE), | |
| vertices_drawn_(0) { | |
| DCHECK_LE(0u, client_id); | |
| DCHECK_LT(0u, service_id); | |
| } | |
| TransformFeedback::~TransformFeedback() { | |
| if (!manager_->lost_context()) { | |
| if (active_ && is_bound_) { | |
| glEndTransformFeedback(); | |
| } | |
| glDeleteTransformFeedbacks(1, &service_id_); | |
| } | |
| // ForceUnbindBuffers() manages purely CPU-side state tracking/refcounting of | |
| // the attached buffers. We must run it even if the context is lost (unlike | |
| // the GL driver calls above) to ensure that the buffer reference counts are | |
| // correctly decremented, preventing state leaks or triggering BufferManager | |
| // DCHECKs upon destruction of an active but unbound transform feedback | |
| // object. | |
| if (active_ && !is_bound_) { | |
| ForceUnbindBuffers(); | |
| } | |
| } | |
| void TransformFeedback::DoBindTransformFeedback( | |
| GLenum target, | |
| TransformFeedback* last_bound_transform_feedback, | |
| Buffer* bound_transform_feedback_buffer) { | |
| DCHECK_LT(0u, service_id_); | |
| glBindTransformFeedback(target, service_id_); | |
| // GL drivers differ on whether GL_TRANSFORM_FEEDBACK_BUFFER is changed | |
| // when calling glBindTransformFeedback. To make them consistent, we | |
| // explicitly bind the buffer we think should be bound here. | |
| if (bound_transform_feedback_buffer && | |
| !bound_transform_feedback_buffer->IsDeleted()) { | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, | |
| bound_transform_feedback_buffer->service_id()); | |
| } else { | |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0); | |
| } | |
| has_been_bound_ = true; | |
| if (active_ && !paused_) { | |
| // This could only happen during virtual context switching. | |
| // Otherwise the validation should generate a GL error without calling | |
| // into this function. | |
| glResumeTransformFeedback(); | |
| } | |
| if (last_bound_transform_feedback != this) { | |
| if (last_bound_transform_feedback) { | |
| last_bound_transform_feedback->SetIsBound(false); | |
| } | |
| SetIsBound(true); | |
| } | |
| } | |
| bool TransformFeedback::AttachedBuffersAreLocked() const { | |
| return is_bound_ || active_; | |
| } | |
| void TransformFeedback::SetActiveProgram(Program* program) { | |
| CHECK(!active_program_); | |
| CHECK(program); | |
| active_program_ = program; | |
| program->IncrementActiveTransformFeedbackCount(); | |
| } | |
| void TransformFeedback::ClearActiveProgram() { | |
| CHECK(active_program_); | |
| active_program_->DecrementActiveTransformFeedbackCount(); | |
| active_program_ = nullptr; | |
| } | |
| void TransformFeedback::DoBeginTransformFeedback(GLenum primitive_mode) { | |
| DCHECK(!active_); | |
| DCHECK(primitive_mode == GL_POINTS || | |
| primitive_mode == GL_LINES || | |
| primitive_mode == GL_TRIANGLES); | |
| glBeginTransformFeedback(primitive_mode); | |
| active_ = true; | |
| primitive_mode_ = primitive_mode; | |
| vertices_drawn_ = 0; | |
| } | |
| void TransformFeedback::DoEndTransformFeedback() { | |
| DCHECK(active_); | |
| glEndTransformFeedback(); | |
| active_ = false; | |
| paused_ = false; | |
| } | |
| void TransformFeedback::DoPauseTransformFeedback() { | |
| DCHECK(active_ && !paused_); | |
| glPauseTransformFeedback(); | |
| paused_ = true; | |
| } | |
| void TransformFeedback::DoResumeTransformFeedback() { | |
| DCHECK(active_ && paused_); | |
| glResumeTransformFeedback(); | |
| paused_ = false; | |
| } | |
| bool TransformFeedback::GetVerticesNeededForDraw(GLenum mode, | |
| GLsizei count, | |
| GLsizei primcount, | |
| GLsizei pending_vertices_drawn, | |
| GLsizei* vertices_out) const { | |
| // Transform feedback only outputs complete primitives, so we need to round | |
| // down to the nearest complete primitive before multiplying by the number of | |
| // instances. | |
| base::CheckedNumeric<GLsizei> checked_vertices = vertices_drawn_; | |
| checked_vertices += pending_vertices_drawn; | |
| base::CheckedNumeric<GLsizei> checked_count = count; | |
| base::CheckedNumeric<GLsizei> checked_primcount = primcount; | |
| switch (mode) { | |
| case GL_TRIANGLES: | |
| checked_vertices += | |
| checked_primcount * (checked_count - checked_count % 3); | |
| break; | |
| case GL_LINES: | |
| checked_vertices += | |
| checked_primcount * (checked_count - checked_count % 2); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| case GL_POINTS: | |
| checked_vertices += checked_primcount * checked_count; | |
| } | |
| // Note that the use of GLsizei limits us to 32-bit numbers of vertices. If | |
| // the command buffer is upgraded to allow 64-bit buffer sizes then this | |
| // should be expanded to 64 bits as well. | |
| *vertices_out = checked_vertices.ValueOrDefault(0); | |
| return checked_vertices.IsValid(); | |
| } | |
| void TransformFeedback::OnVerticesDrawn(GLsizei vertices_drawn) { | |
| if (active_ && !paused_) { | |
| vertices_drawn_ = vertices_drawn; | |
| } | |
| } | |
| TransformFeedbackManager::TransformFeedbackManager( | |
| GLuint max_transform_feedback_separate_attribs, | |
| bool needs_emulation) | |
| : max_transform_feedback_separate_attribs_( | |
| max_transform_feedback_separate_attribs), | |
| needs_emulation_(needs_emulation), | |
| lost_context_(false) { | |
| DCHECK(needs_emulation); | |
| } | |
| TransformFeedbackManager::~TransformFeedbackManager() { | |
| DCHECK(transform_feedbacks_.empty()); | |
| } | |
| void TransformFeedbackManager::Destroy() { | |
| transform_feedbacks_.clear(); | |
| } | |
| TransformFeedback* TransformFeedbackManager::CreateTransformFeedback( | |
| GLuint client_id, GLuint service_id) { | |
| scoped_refptr<TransformFeedback> transform_feedback( | |
| new TransformFeedback(this, client_id, service_id)); | |
| auto result = transform_feedbacks_.insert( | |
| std::make_pair(client_id, transform_feedback)); | |
| DCHECK(result.second); | |
| return result.first->second.get(); | |
| } | |
| TransformFeedback* TransformFeedbackManager::GetTransformFeedback( | |
| GLuint client_id) { | |
| if (client_id == 0) { | |
| return nullptr; | |
| } | |
| auto it = transform_feedbacks_.find(client_id); | |
| return it != transform_feedbacks_.end() ? it->second.get() : nullptr; | |
| } | |
| void TransformFeedbackManager::RemoveTransformFeedback(GLuint client_id) { | |
| if (client_id) { | |
| transform_feedbacks_.erase(client_id); | |
| } | |
| } | |
| ScopedPauseResumeTransformFeedback::ScopedPauseResumeTransformFeedback( | |
| TransformFeedback* transform_feedback) | |
| : transform_feedback_(transform_feedback) { | |
| if (transform_feedback_ && transform_feedback_->active() && | |
| !transform_feedback_->paused()) { | |
| transform_feedback_->DoPauseTransformFeedback(); | |
| } else { | |
| transform_feedback_ = nullptr; | |
| } | |
| } | |
| ScopedPauseResumeTransformFeedback::~ScopedPauseResumeTransformFeedback() { | |
| if (transform_feedback_) { | |
| transform_feedback_->DoResumeTransformFeedback(); | |
| } | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |