blob: 2b9ed0b3e7867ff1bde276a2feaef6d11a42ff0e [file] [log] [blame]
// Copyright (c) 2010 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 "gpu/command_buffer/service/program_manager.h"
#include <algorithm>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/scoped_ptr.h"
#include "base/string_number_conversions.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
namespace gpu {
namespace gles2 {
static int ShaderTypeToIndex(GLenum shader_type) {
switch (shader_type) {
case GL_VERTEX_SHADER:
return 0;
case GL_FRAGMENT_SHADER:
return 1;
default:
NOTREACHED();
return 0;
}
}
ProgramManager::ProgramInfo::UniformInfo::UniformInfo(GLsizei _size,
GLenum _type,
const std::string& _name)
: size(_size),
type(_type),
name(_name) {
}
ProgramManager::ProgramInfo::UniformInfo::~UniformInfo() {}
bool ProgramManager::IsInvalidPrefix(const char* name, size_t length) {
static const char kInvalidPrefix[] = { 'g', 'l', '_' };
return (length >= sizeof(kInvalidPrefix) &&
memcmp(name, kInvalidPrefix, sizeof(kInvalidPrefix)) == 0);
}
ProgramManager::ProgramInfo::ProgramInfo(GLuint service_id)
: use_count_(0),
max_attrib_name_length_(0),
max_uniform_name_length_(0),
service_id_(service_id),
valid_(false),
link_status_(false) {
}
void ProgramManager::ProgramInfo::Reset() {
valid_ = false;
link_status_ = false;
max_uniform_name_length_ = 0;
max_attrib_name_length_ = 0;
attrib_infos_.clear();
uniform_infos_.clear();
sampler_indices_.clear();
attrib_location_to_index_map_.clear();
uniform_location_to_index_map_.clear();
UpdateLogInfo();
}
void ProgramManager::ProgramInfo::UpdateLogInfo() {
GLint max_len = 0;
glGetProgramiv(service_id_, GL_INFO_LOG_LENGTH, &max_len);
if (max_len == 0) {
set_log_info(NULL);
return;
}
scoped_array<char> temp(new char[max_len]);
GLint len = 0;
glGetProgramInfoLog(service_id_, max_len, &len, temp.get());
DCHECK(max_len == 0 || len < max_len);
DCHECK(len == 0 || temp[len] == '\0');
set_log_info(std::string(temp.get(), len).c_str());
}
void ProgramManager::ProgramInfo::Update() {
Reset();
link_status_ = true;
GLint num_attribs = 0;
GLint max_len = 0;
GLint max_location = -1;
glGetProgramiv(service_id_, GL_ACTIVE_ATTRIBUTES, &num_attribs);
glGetProgramiv(service_id_, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &max_len);
// TODO(gman): Should we check for error?
scoped_array<char> name_buffer(new char[max_len]);
for (GLint ii = 0; ii < num_attribs; ++ii) {
GLsizei length = 0;
GLsizei size = 0;
GLenum type = 0;
glGetActiveAttrib(
service_id_, ii, max_len, &length, &size, &type, name_buffer.get());
DCHECK(max_len == 0 || length < max_len);
DCHECK(length == 0 || name_buffer[length] == '\0');
if (!IsInvalidPrefix(name_buffer.get(), length)) {
std::string name;
GetCorrectedVariableInfo(false, name_buffer.get(), &name, &size, &type);
// TODO(gman): Should we check for error?
GLint location = glGetAttribLocation(service_id_, name_buffer.get());
if (location > max_location) {
max_location = location;
}
attrib_infos_.push_back(VertexAttribInfo(size, type, name, location));
max_attrib_name_length_ = std::max(max_attrib_name_length_, length);
}
}
// Create attrib location to index map.
attrib_location_to_index_map_.resize(max_location + 1);
for (GLint ii = 0; ii <= max_location; ++ii) {
attrib_location_to_index_map_[ii] = -1;
}
for (size_t ii = 0; ii < attrib_infos_.size(); ++ii) {
const VertexAttribInfo& info = attrib_infos_[ii];
attrib_location_to_index_map_[info.location] = ii;
}
GLint num_uniforms = 0;
max_len = 0;
glGetProgramiv(service_id_, GL_ACTIVE_UNIFORMS, &num_uniforms);
glGetProgramiv(service_id_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_len);
name_buffer.reset(new char[max_len]);
max_location = -1;
int index = 0; // this index tracks valid uniforms.
for (GLint ii = 0; ii < num_uniforms; ++ii) {
GLsizei length = 0;
GLsizei size = 0;
GLenum type = 0;
glGetActiveUniform(
service_id_, ii, max_len, &length, &size, &type, name_buffer.get());
DCHECK(max_len == 0 || length < max_len);
DCHECK(length == 0 || name_buffer[length] == '\0');
// TODO(gman): Should we check for error?
if (!IsInvalidPrefix(name_buffer.get(), length)) {
GLint location = glGetUniformLocation(service_id_, name_buffer.get());
std::string name;
GetCorrectedVariableInfo(true, name_buffer.get(), &name, &size, &type);
const UniformInfo* info = AddUniformInfo(size, type, location, name);
for (size_t jj = 0; jj < info->element_locations.size(); ++jj) {
if (info->element_locations[jj] > max_location) {
max_location = info->element_locations[jj];
}
}
if (info->IsSampler()) {
sampler_indices_.push_back(index);
}
max_uniform_name_length_ =
std::max(max_uniform_name_length_,
static_cast<GLsizei>(info->name.size()));
++index;
}
}
// Create uniform location to index map.
uniform_location_to_index_map_.resize(max_location + 1);
for (GLint ii = 0; ii <= max_location; ++ii) {
uniform_location_to_index_map_[ii] = -1;
}
for (size_t ii = 0; ii < uniform_infos_.size(); ++ii) {
const UniformInfo& info = uniform_infos_[ii];
for (size_t jj = 0; jj < info.element_locations.size(); ++jj) {
uniform_location_to_index_map_[info.element_locations[jj]] = ii;
}
}
valid_ = true;
}
GLint ProgramManager::ProgramInfo::GetUniformLocation(
const std::string& name) const {
for (GLuint ii = 0; ii < uniform_infos_.size(); ++ii) {
const UniformInfo& info = uniform_infos_[ii];
if (info.name == name ||
(info.is_array &&
info.name.compare(0, info.name.size() - 3, name) == 0)) {
return info.element_locations[0];
} else if (info.is_array &&
name.size() >= 3 && name[name.size() - 1] == ']') {
// Look for an array specification.
size_t open_pos = name.find_last_of('[');
if (open_pos != std::string::npos &&
open_pos < name.size() - 2 &&
info.name.size() > open_pos &&
name.compare(0, open_pos, info.name, 0, open_pos) == 0) {
GLint index = 0;
size_t last = name.size() - 1;
bool bad = false;
for (size_t pos = open_pos + 1; pos < last; ++pos) {
int8 digit = name[pos] - '0';
if (digit < 0 || digit > 9) {
bad = true;
break;
}
index = index * 10 + digit;
}
if (!bad && index >= 0 && index < info.size) {
return info.element_locations[index];
}
}
}
}
return -1;
}
GLint ProgramManager::ProgramInfo::GetAttribLocation(
const std::string& name) const {
for (GLuint ii = 0; ii < attrib_infos_.size(); ++ii) {
const VertexAttribInfo& info = attrib_infos_[ii];
if (info.name == name) {
return info.location;
}
}
return -1;
}
bool ProgramManager::ProgramInfo::GetUniformTypeByLocation(
GLint location, GLenum* type) const {
if (location >= 0 &&
static_cast<size_t>(location) < uniform_location_to_index_map_.size()) {
GLint index = uniform_location_to_index_map_[location];
if (index >= 0) {
*type = uniform_infos_[index].type;
return true;
}
}
return false;
}
// Note: This is only valid to call right after a program has been linked
// successfully.
void ProgramManager::ProgramInfo::GetCorrectedVariableInfo(
bool use_uniforms,
const std::string& name, std::string* corrected_name,
GLsizei* size, GLenum* type) const {
DCHECK(corrected_name);
DCHECK(size);
DCHECK(type);
const char* kArraySpec = "[0]";
for (int jj = 0; jj < 2; ++jj) {
std::string test_name(name + ((jj == 1) ? kArraySpec : ""));
for (int ii = 0; ii < kMaxAttachedShaders; ++ii) {
ShaderManager::ShaderInfo* shader_info = attached_shaders_[ii].get();
if (shader_info) {
const ShaderManager::ShaderInfo::VariableInfo* variable_info =
use_uniforms ? shader_info->GetUniformInfo(test_name) :
shader_info->GetAttribInfo(test_name);
// Note: There is an assuption here that if an attrib is defined in more
// than 1 attached shader their types and sizes match. Should we check
// for that case?
if (variable_info) {
*corrected_name = test_name;
*type = variable_info->type;
*size = variable_info->size;
return;
}
}
}
}
*corrected_name = name;
}
const ProgramManager::ProgramInfo::UniformInfo*
ProgramManager::ProgramInfo::AddUniformInfo(
GLsizei size, GLenum type, GLint location, const std::string& name) {
const char* kArraySpec = "[0]";
uniform_infos_.push_back(UniformInfo(size, type, name));
UniformInfo& info = uniform_infos_.back();
info.element_locations.resize(size);
info.element_locations[0] = location;
DCHECK_GE(size, 0);
size_t num_texture_units = info.IsSampler() ? static_cast<size_t>(size) : 0u;
info.texture_units.clear();
info.texture_units.resize(num_texture_units, 0);
if (size > 1) {
// Sadly there is no way to tell if this is an array except if the name
// has an array string or the size > 1. That means an array of size 1 can
// be ambiguous.
//
// For now we just make sure that if the size is > 1 then the name must have
// an array spec.
// Go through the array element locations looking for a match.
// We can skip the first element because it's the same as the
// the location without the array operators.
size_t array_pos = name.rfind(kArraySpec);
std::string base_name = name;
if (name.size() > 3) {
if (array_pos != name.size() - 3) {
info.name = name + kArraySpec;
} else {
base_name = name.substr(0, name.size() - 3);
}
}
for (GLsizei ii = 1; ii < info.size; ++ii) {
std::string element_name(base_name + "[" + base::IntToString(ii) + "]");
info.element_locations[ii] =
glGetUniformLocation(service_id_, element_name.c_str());
}
}
info.is_array =
(size > 1 ||
(info.name.size() > 3 &&
info.name.rfind(kArraySpec) == info.name.size() - 3));
return &info;
}
bool ProgramManager::ProgramInfo::SetSamplers(
GLint location, GLsizei count, const GLint* value) {
if (location >= 0 &&
static_cast<size_t>(location) < uniform_location_to_index_map_.size()) {
GLint index = uniform_location_to_index_map_[location];
if (index >= 0) {
UniformInfo& info = uniform_infos_[index];
if (info.IsSampler() && count <= info.size) {
std::copy(value, value + count, info.texture_units.begin());
return true;
}
}
}
return false;
}
void ProgramManager::ProgramInfo::GetProgramiv(GLenum pname, GLint* params) {
switch (pname) {
case GL_ACTIVE_ATTRIBUTES:
*params = attrib_infos_.size();
break;
case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
// Notice +1 to accomodate NULL terminator.
*params = max_attrib_name_length_ + 1;
break;
case GL_ACTIVE_UNIFORMS:
*params = uniform_infos_.size();
break;
case GL_ACTIVE_UNIFORM_MAX_LENGTH:
// Notice +1 to accomodate NULL terminator.
*params = max_uniform_name_length_ + 1;
break;
case GL_LINK_STATUS:
*params = link_status_;
break;
case GL_INFO_LOG_LENGTH:
// Notice +1 to accomodate NULL terminator.
*params = log_info_.get() ? (log_info_->size() + 1) : 0;
break;
case GL_VALIDATE_STATUS:
if (!CanLink()) {
*params = GL_FALSE;
} else {
glGetProgramiv(service_id_, pname, params);
}
break;
default:
glGetProgramiv(service_id_, pname, params);
break;
}
}
bool ProgramManager::ProgramInfo::AttachShader(
ShaderManager* shader_manager,
ShaderManager::ShaderInfo* info) {
DCHECK(shader_manager);
DCHECK(info);
int index = ShaderTypeToIndex(info->shader_type());
if (attached_shaders_[index] != NULL) {
return false;
}
attached_shaders_[index] = ShaderManager::ShaderInfo::Ref(info);
shader_manager->UseShader(info);
return true;
}
void ProgramManager::ProgramInfo::DetachShader(
ShaderManager* shader_manager,
ShaderManager::ShaderInfo* info) {
DCHECK(shader_manager);
DCHECK(info);
attached_shaders_[ShaderTypeToIndex(info->shader_type())] = NULL;
shader_manager->UnuseShader(info);
}
void ProgramManager::ProgramInfo::DetachShaders(ShaderManager* shader_manager) {
DCHECK(shader_manager);
for (int ii = 0; ii < kMaxAttachedShaders; ++ii) {
if (attached_shaders_[ii]) {
DetachShader(shader_manager, attached_shaders_[ii]);
}
}
}
bool ProgramManager::ProgramInfo::CanLink() const {
for (int ii = 0; ii < kMaxAttachedShaders; ++ii) {
if (!attached_shaders_[ii] || !attached_shaders_[ii]->IsValid()) {
return false;
}
}
return true;
}
ProgramManager::ProgramInfo::~ProgramInfo() {}
ProgramManager::ProgramManager() {}
ProgramManager::~ProgramManager() {
DCHECK(program_infos_.empty());
}
void ProgramManager::Destroy(bool have_context) {
while (!program_infos_.empty()) {
if (have_context) {
ProgramInfo* info = program_infos_.begin()->second;
if (!info->IsDeleted()) {
glDeleteProgram(info->service_id());
info->MarkAsDeleted();
}
}
program_infos_.erase(program_infos_.begin());
}
}
void ProgramManager::CreateProgramInfo(GLuint client_id, GLuint service_id) {
std::pair<ProgramInfoMap::iterator, bool> result =
program_infos_.insert(
std::make_pair(client_id,
ProgramInfo::Ref(new ProgramInfo(service_id))));
DCHECK(result.second);
}
ProgramManager::ProgramInfo* ProgramManager::GetProgramInfo(GLuint client_id) {
ProgramInfoMap::iterator it = program_infos_.find(client_id);
return it != program_infos_.end() ? it->second : NULL;
}
bool ProgramManager::GetClientId(GLuint service_id, GLuint* client_id) const {
// This doesn't need to be fast. It's only used during slow queries.
for (ProgramInfoMap::const_iterator it = program_infos_.begin();
it != program_infos_.end(); ++it) {
if (it->second->service_id() == service_id) {
*client_id = it->first;
return true;
}
}
return false;
}
void ProgramManager::RemoveProgramInfoIfUnused(
ShaderManager* shader_manager, ProgramInfo* info) {
DCHECK(shader_manager);
DCHECK(info);
if (info->IsDeleted() && !info->InUse()) {
info->DetachShaders(shader_manager);
for (ProgramInfoMap::iterator it = program_infos_.begin();
it != program_infos_.end(); ++it) {
if (it->second->service_id() == info->service_id()) {
program_infos_.erase(it);
return;
}
}
NOTREACHED();
}
}
void ProgramManager::MarkAsDeleted(
ShaderManager* shader_manager,
ProgramManager::ProgramInfo* info) {
DCHECK(shader_manager);
DCHECK(info);
info->MarkAsDeleted();
RemoveProgramInfoIfUnused(shader_manager, info);
}
void ProgramManager::UseProgram(ProgramManager::ProgramInfo* info) {
DCHECK(info);
info->IncUseCount();
}
void ProgramManager::UnuseProgram(
ShaderManager* shader_manager,
ProgramManager::ProgramInfo* info) {
DCHECK(shader_manager);
DCHECK(info);
info->DecUseCount();
RemoveProgramInfoIfUnused(shader_manager, info);
}
} // namespace gles2
} // namespace gpu