blob: f77aabe87f37071f59593c4902bc4885194de7db [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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 "gles/program_data.h"
#include <stdio.h>
#include <GLES/glext.h>
#include "common/alog.h"
#include "gles/debug.h"
#include "gles/gles_context.h"
#include "gles/macros.h"
#include "gles/shader_data.h"
using android::base::AutoLock;
static GlesContext* GetRequiredContext() {
GlesContext* ctx = GetCurrentGlesContext();
LOG_ALWAYS_FATAL_IF(!ctx);
return ctx;
}
ProgramData::ProgramData(ObjectLocalName name)
: ObjectData(PROGRAM, name), current_program_(new ProgramVariant()),
uniform_location_gen_(0), has_link_failure_(false),
has_warned_about_sampler_use_(false), use_count_(0),
uniform_revision_gen_(0), validation_status_(GL_FALSE) {
}
ProgramData::~ProgramData() {
if (use_count_) {
ALOGE("ProgramData destroyed while use count is %d", use_count_);
}
ClearUniformCacheLocked();
}
void ProgramData::ModifyProgramLocked() {
if (linked_programs_.empty() ||
current_program_ != linked_programs_[0].GetProgram()) {
return; // Already modifying a new program variant.
}
// The program has been linked and may be in use. Create a new one.
current_program_ = ProgramVariantPtr(
linked_programs_[0].GetProgram()->Clone());
// TODO(crbug.com/441928): Support a case where a linked program gets
// modified, its compiled shader is re-attached, and then shader source
// is updated.
}
void ProgramData::AttachShader(const ShaderDataPtr& shader) {
AutoLock lock(lock_);
// Check type correctness and that there is no existing shader with that type.
ObjectType type = shader->GetDataType();
LOG_ALWAYS_FATAL_IF(type != VERTEX_SHADER && type != FRAGMENT_SHADER);
for (ShaderList::const_iterator it = shaders_.begin();
it != shaders_.end(); ++it) {
if ((*it)->GetDataType() == type) {
GLES_ERROR(GL_INVALID_OPERATION,
"Shader of type '%s' has already been attached",
GetEnumString(type));
return;
}
}
// Add shader to the list and to the currently-built program variant.
ModifyProgramLocked();
shaders_.push_back(shader);
current_program_->AttachShader(shader->GetShaderVariant());
}
void ProgramData::DetachShader(const ShaderDataPtr& shader) {
AutoLock lock(lock_);
bool found = false;
for (ShaderList::iterator it = shaders_.begin();
it != shaders_.end(); ++it) {
if (shader == *it) {
shaders_.erase(it);
found = true;
break;
}
}
if (!found) {
GLES_ERROR(GL_INVALID_OPERATION, "The shader has not been attached");
return;
}
ModifyProgramLocked();
current_program_->DetachShader(shader->GetShaderVariant());
}
void ProgramData::GetAttachedShaders(GLsizei max_count, GLsizei* count,
GLuint* shaders) const {
int i = 0;
for (ShaderList::const_iterator it = shaders_.begin();
it != shaders_.end() && i < max_count; ++it) {
shaders[i] = (*it)->GetLocalName();
++i;
}
if (count) {
*count = i;
}
}
ProgramVariantPtr ProgramData::GetProgramWithLinkAttemptLocked() const {
if (current_program_->IsLinkAttempted()) {
return current_program_;
}
if (!linked_programs_.empty()) {
return linked_programs_[0].GetProgram();
}
return ProgramVariantPtr();
}
void ProgramData::GetProgramiv(GLenum pname, GLint* params) const {
AutoLock lock(lock_);
switch (pname) {
case GL_DELETE_STATUS:
*params = GL_FALSE; // Not supported (crbug.com/424353).
break;
case GL_INFO_LOG_LENGTH:
*params = current_program_->GetInfoLog().size() + 1;
break;
case GL_ATTACHED_SHADERS:
*params = shaders_.size();
break;
case GL_LINK_STATUS:
*params = (!linked_programs_.empty() && !has_link_failure_ ?
GL_TRUE : GL_FALSE);
break;
case GL_VALIDATE_STATUS:
*params = validation_status_;
break;
case GL_ACTIVE_ATTRIBUTES: {
ProgramVariantPtr program = GetProgramWithLinkAttemptLocked();
if (program != NULL) {
*params = program->GetActiveAttribCount();
} else {
*params = 0;
}
break;
}
case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: {
ProgramVariantPtr program = GetProgramWithLinkAttemptLocked();
if (program != NULL) {
*params = program->GetActiveAttribMaxNameLengh() + 1;
} else {
*params = 0;
}
break;
}
case GL_ACTIVE_UNIFORMS: {
ProgramVariantPtr program = GetProgramWithLinkAttemptLocked();
if (program != NULL) {
*params = program->GetActiveUniformCount();
} else {
*params = 0;
}
break;
}
case GL_ACTIVE_UNIFORM_MAX_LENGTH: {
ProgramVariantPtr program = GetProgramWithLinkAttemptLocked();
if (program != NULL) {
*params = program->GetActiveUniformMaxNameLengh() + 1;
} else {
*params = 0;
}
break;
}
default:
GLES_ERROR_INVALID_ENUM(pname);
break;
}
}
void ProgramData::GetInfoLog(GLsizei max_length, GLsizei* length,
GLchar* info_log) const {
// Use current program as it either equals to the linked program, or may have
// more recent data if there were GL calls after linking.
int info_len = snprintf(info_log, max_length, "%s",
current_program_->GetInfoLog().c_str());
if (info_len >= max_length) {
info_len = max_length - 1; // Text was truncated.
}
if (length) {
*length = info_len;
}
}
void ProgramData::Link() {
AutoLock lock(lock_);
if (current_program_->IsLinked()) {
return;
}
current_program_->Link();
has_warned_about_sampler_use_ = false;
has_link_failure_ = !current_program_->IsLinked();
if (!has_link_failure_) {
SetLinkedProgramLocked(current_program_);
return;
}
ALOGE("Unable to link program %d(%d)\n%s", GetLocalName(),
current_program_->GetOrCreateGlobalName(),
current_program_->GetInfoLog().c_str());
if (use_count_ == 0) {
SetLinkedProgramLocked(ProgramVariantPtr(NULL));
}
}
void ProgramData::SetLinkedProgramLocked(ProgramVariantPtr program) {
// Reset all mappings.
linked_programs_.clear();
ClearUniformCacheLocked();
if (program == NULL) {
return; // No linked program exists.
}
for (int i = 0; i < program->GetActiveUniformCount(); ++i) {
const ProgramVariant::ActiveUniform& uniform =
program->GetActiveUniform(i);
uniform_cache_[uniform.GetBaseName()] = new CachedUniform(
uniform.GetType(), uniform.IsArray(), uniform.GetArraySize());
}
linked_programs_.push_back(LinkedProgram(program));
}
bool ProgramData::Use(bool enable) {
AutoLock lock(lock_);
if (!enable) {
// May be invoked without current context from context destructor.
--use_count_;
if (has_link_failure_ && !linked_programs_.empty() && use_count_ == 0) {
// glUseProgram() spec says an active in-use program that failed
// to re-link should keep working until it goes out of use.
SetLinkedProgramLocked(ProgramVariantPtr(NULL));
}
return true;
}
if (linked_programs_.empty()) {
GLES_ERROR(GL_INVALID_OPERATION, "Shader program has not been linked");
return false;
}
GetRequiredContext();
++use_count_;
return true;
}
void ProgramData::PrepareForRendering(bool* uses_external_as_2d) {
AutoLock lock(lock_);
LinkedProgram* program = GetLinkedProgramForRenderingLocked();
*uses_external_as_2d = program->GetProgram()->UsesExternalTextureAs2D();
GlesContext* ctx = GetRequiredContext();
GLint global_program_name = program->GetProgram()->GetOrCreateGlobalName();
PASS_THROUGH(ctx, UseProgram, global_program_name);
PropagateUniformValuesLocked(program);
}
void ProgramData::CleanupAfterRendering() {
GlesContext* ctx = GetRequiredContext();
PASS_THROUGH(ctx, UseProgram, 0);
}
ProgramData::LinkedProgram* ProgramData::GetLinkedProgramForRenderingLocked() {
LOG_ALWAYS_FATAL_IF(linked_programs_.empty());
LOG_ALWAYS_FATAL_IF(
linked_programs_[0].GetGlobalTextureTarget() != GL_TEXTURE_2D);
if (!linked_programs_[0].GetProgram()->UsesExternalTextureAs2D()) {
// The original program did not use external sampler, cannot rewrite it.
return &linked_programs_[0];
}
// Look for the uniforms that refer appropriate texture units.
GLint texture_unit_index = -1;
for (UniformCacheMap::const_iterator it = uniform_cache_.begin();
it != uniform_cache_.end(); ++it) {
const CachedUniform* uniform = it->second;
if (uniform->GetType() != GL_SAMPLER_2D) {
continue;
}
if (uniform->GetArraySize() != 1) {
if (!has_warned_about_sampler_use_) {
ALOGW("Shader program uses a sampler array, which is not supported "
"for backend texture adjustments (used with HW video)");
has_warned_about_sampler_use_ = true;
}
return &linked_programs_[0];
}
if (texture_unit_index != -1) {
if (!has_warned_about_sampler_use_) {
ALOGW("Shader program uses more than multiple active 2D samplers, "
"which is not supported for backend texture adjustments "
"(used with HW video)");
has_warned_about_sampler_use_ = true;
}
return &linked_programs_[0];
}
LOG_ALWAYS_FATAL_IF(!uniform->StoreAsIntTo(&texture_unit_index));
}
// Find the global target for the sampler's texture.
GLenum global_target = GL_TEXTURE_2D;
if (texture_unit_index != -1) {
GlesContext* ctx = GetRequiredContext();
global_target = ctx->texture_context_.GetGlobalTarget(
GL_TEXTURE0 + texture_unit_index, GL_TEXTURE_EXTERNAL_OES);
}
// The default program is always targeting GL_TEXTURE_2D.
if (global_target == GL_TEXTURE_2D) {
return &linked_programs_[0];
}
// Find or create a program for another texture target.
for (size_t i = 1; i < linked_programs_.size(); ++i) {
LinkedProgram* program = &linked_programs_[i];
if (program->GetGlobalTextureTarget() == global_target) {
return program;
}
}
if (global_target == GL_TEXTURE_EXTERNAL_OES) {
LinkedProgram* program = linked_programs_[0].Clone(global_target);
if (!program) {
if (!has_warned_about_sampler_use_) {
ALOGW("Unable to rewrite shader program, which is required for "
"backend texture adjustments (used with HW video)");
has_warned_about_sampler_use_ = true;
}
return &linked_programs_[0];
}
linked_programs_.push_back(*program);
return &linked_programs_[linked_programs_.size() - 1];
}
ALOGE("Shader program is used with unsupported texture target %s(0x%x)",
GetEnumString(global_target), global_target);
return &linked_programs_[0];
}
void ProgramData::Validate() {
AutoLock lock(lock_);
if (linked_programs_.empty()) {
return;
}
bool has_stale_uniform_data = false;
LinkedProgram* program = &linked_programs_[0];
for (UniformCacheMap::const_iterator it = uniform_cache_.begin();
it != uniform_cache_.end(); ++it) {
if (it->second->GetRevision() > program->GetUniformRevision()) {
has_stale_uniform_data = true;
break;
}
}
GlesContext* ctx = GetRequiredContext();
GLint program_name = program->GetProgram()->GetOrCreateGlobalName();
if (has_stale_uniform_data) {
PASS_THROUGH(ctx, UseProgram, program_name);
PropagateUniformValuesLocked(program);
PASS_THROUGH(ctx, UseProgram, 0);
}
PASS_THROUGH(ctx, ValidateProgram, program_name);
PASS_THROUGH(ctx, GetProgramiv, program_name, GL_VALIDATE_STATUS,
&validation_status_);
program->GetProgram()->ResetInfoLogCache();
}
///////////////////////////////////////////////////////////////////////////////
// Uniform support.
///////////////////////////////////////////////////////////////////////////////
void ProgramData::GetActiveUniform(GLuint index, GLsizei buf_size,
GLsizei* length, GLint* size, GLenum* type,
GLchar* name) const {
ProgramVariantPtr program = GetProgramWithLinkAttemptLocked();
if (program != NULL) {
program->GetActiveUniform(index, buf_size, length, size, type, name);
return;
}
GLES_ERROR(GL_INVALID_OPERATION, "The program has not been linked");
}
void ProgramData::ClearUniformCacheLocked() {
for (UniformCacheMap::iterator it = uniform_cache_.begin();
it != uniform_cache_.end(); ++it) {
delete it->second;
}
uniform_cache_.clear();
uniform_locations_.clear();
uniform_revision_gen_ = 0;
}
GLint ProgramData::GetUniformLocation(const GLchar* name) {
AutoLock lock(lock_);
if (linked_programs_.empty()) {
GLES_ERROR(GL_INVALID_OPERATION, "The program has not been linked");
return -1;
}
// Look for known local locations.
for (UniformLocationMap::const_iterator it = uniform_locations_.begin();
it != uniform_locations_.end(); ++it) {
if (it->second.GetOriginalName() == name) {
return it->first;
}
}
// Look up matching active uniform.
int array_index = -1;
ProgramVariantPtr program = linked_programs_[0].GetProgram();
std::string base_name = program->ParseUniformName(name, &array_index);
if (base_name.empty()) {
return -1;
}
// A sanity check for the location in the underlying shader program.
GLint global_location = program->GetUniformLocation(name);
if (global_location == -1) {
return -1;
}
// Add location to the uniform map.
GLint local_location = ++uniform_location_gen_;
uniform_locations_[local_location] = UniformLocation(
name, base_name, array_index);
return local_location;
}
void ProgramData::PropagateUniformValuesLocked(LinkedProgram* program) {
GlesContext* ctx = GetRequiredContext();
uint64_t max_revision = 0;
for (UniformCacheMap::const_iterator it = uniform_cache_.begin();
it != uniform_cache_.end(); ++it) {
const CachedUniform* uniform = it->second;
uint64_t revision = uniform->GetRevision();
if (revision > max_revision) {
max_revision = revision;
}
if (revision <= program->GetUniformRevision()) {
continue;
}
// Lookup location of the base uniform name, then set the entire value.
// TODO(crbug.com/441930): Propagate only those array elements that were
// modified.
GLint global_location =
program->GetProgram()->GetUniformLocation(it->first);
if (global_location == -1) {
ALOGW("Unable to find global location for uniform '%s'",
it->first.c_str());
continue;
}
uniform->SetOnProgram(ctx, global_location);
}
program->SetUniformRevision(max_revision);
}
void ProgramData::SetUniformInternal(GLint location,
const UniformValue& value) {
AutoLock lock(lock_);
if (location == -1) {
return; // GLES2 requires -1 to be ignored.
}
if (!uniform_locations_.count(location)) {
GLES_ERROR(GL_INVALID_OPERATION,
"Uniform location unknown or program has not been linked");
return;
}
const UniformLocation& location_info =
uniform_locations_.find(location)->second;
UniformCacheMap::iterator it =
uniform_cache_.find(location_info.GetBaseName());
LOG_ALWAYS_FATAL_IF(it == uniform_cache_.end());
CachedUniform* uniform = it->second;
if (value.IsArray() && !uniform->IsArray()) {
GLES_ERROR(GL_INVALID_OPERATION,
"Unable to set uniform with count > 1 on a non-array");
return;
}
for (int i = 0; i < value.GetArraySize(); ++i) {
int target_array_idx = location_info.GetArrayIndex() + i;
if (target_array_idx >= uniform->GetArraySize()) {
break;
}
if (!uniform->CopyElementFrom(target_array_idx, value, i)) {
GLES_ERROR(GL_INVALID_OPERATION, "Unable to convert uniform data");
return;
}
}
uniform->SetRevision(++uniform_revision_gen_);
}
void ProgramData::Uniformfv(GLint location, GLenum type, GLsizei count,
const GLfloat* value) {
if (count < 0) {
GLES_ERROR_INVALID_VALUE_INT(count);
return;
}
bool is_array = count > 1;
SetUniformInternal(location, UniformValue(type, is_array, count, value));
}
void ProgramData::Uniformiv(GLint location, GLenum type, GLsizei count,
const GLint* value) {
if (count < 0) {
GLES_ERROR_INVALID_VALUE_INT(count);
return;
}
bool is_array = count > 1;
SetUniformInternal(location, UniformValue(type, is_array, count, value));
}
void ProgramData::UniformMatrixfv(GLint location, GLenum type, GLsizei count,
GLboolean transpose, const GLfloat* value) {
if (count < 0) {
GLES_ERROR_INVALID_VALUE_INT(count);
return;
}
if (transpose) {
GLES_ERROR_INVALID_VALUE_INT(static_cast<int>(transpose));
return;
}
bool is_array = count > 1;
SetUniformInternal(location, UniformValue(type, is_array, count, value));
}
void ProgramData::GetUniformfv(GLint location, GLfloat* params) const {
AutoLock lock(lock_);
if (!uniform_locations_.count(location)) {
GLES_ERROR(GL_INVALID_OPERATION,
"Uniform location unknown or program has not been linked");
return;
}
const UniformLocation& location_info =
uniform_locations_.find(location)->second;
UniformCacheMap::const_iterator it =
uniform_cache_.find(location_info.GetBaseName());
LOG_ALWAYS_FATAL_IF(it == uniform_cache_.end());
if (!it->second->StoreAsFloatTo(params)) {
GLES_ERROR(GL_INVALID_OPERATION, "Unable to convert uniform data");
}
}
void ProgramData::GetUniformiv(GLint location, GLint* params) const {
AutoLock lock(lock_);
if (!uniform_locations_.count(location)) {
GLES_ERROR(GL_INVALID_OPERATION,
"Uniform location unknown or program has not been linked");
return;
}
const UniformLocation& location_info =
uniform_locations_.find(location)->second;
UniformCacheMap::const_iterator it =
uniform_cache_.find(location_info.GetBaseName());
LOG_ALWAYS_FATAL_IF(it == uniform_cache_.end());
if (!it->second->StoreAsIntTo(params)) {
GLES_ERROR(GL_INVALID_OPERATION, "Unable to convert uniform data");
}
}
///////////////////////////////////////////////////////////////////////////////
// Attribute support.
///////////////////////////////////////////////////////////////////////////////
void ProgramData::GetActiveAttrib(GLuint index, GLsizei buf_size,
GLsizei* length, GLint* size,
GLenum* type, GLchar* name) const {
ProgramVariantPtr program = GetProgramWithLinkAttemptLocked();
if (program != NULL) {
program->GetActiveAttrib(index, buf_size, length, size, type, name);
return;
}
GLES_ERROR(GL_INVALID_OPERATION, "The program has not been linked");
}
void ProgramData::BindAttribLocation(GLuint index, const GLchar* name) {
AutoLock lock(lock_);
// Binding of attributes can only happen before linking.
ModifyProgramLocked();
current_program_->BindAttribLocation(name, index);
}
GLint ProgramData::GetAttribLocation(const GLchar* name) {
AutoLock lock(lock_);
if (linked_programs_.empty()) {
GLES_ERROR(GL_INVALID_OPERATION, "The program has not been linked");
return -1;
}
return linked_programs_[0].GetProgram()->GetAttribLocation(name);
}
///////////////////////////////////////////////////////////////////////////////
// CachedUniform
///////////////////////////////////////////////////////////////////////////////
bool ProgramData::CachedUniform::CopyElementFrom(int dst_idx,
const UniformValue& src,
int src_idx) {
return value_.CopyElementFrom(dst_idx, src, src_idx);
}
bool ProgramData::CachedUniform::StoreAsFloatTo(GLfloat* value) const {
return value_.StoreAsFloatTo(value);
}
bool ProgramData::CachedUniform::StoreAsIntTo(GLint* value) const {
return value_.StoreAsIntTo(value);
}
void ProgramData::CachedUniform::SetOnProgram(GlesContext* ctx,
GLuint global_location) const {
value_.SetOnProgram(ctx, global_location);
}
///////////////////////////////////////////////////////////////////////////////
// LinkedProgram
///////////////////////////////////////////////////////////////////////////////
ProgramData::LinkedProgram::LinkedProgram(ProgramVariantPtr program)
: program_(program), uniform_revision_(0) {
LOG_ALWAYS_FATAL_IF(program_ == NULL);
LOG_ALWAYS_FATAL_IF(!program_->IsLinked());
}
ProgramData::LinkedProgram* ProgramData::LinkedProgram::Clone(
GLenum global_texture_target) const {
if (!program_->UsesExternalTextureAs2D()) {
// It is not possible to use other global targets if the user's program
// does not use external textures.
return NULL;
}
// Create the new program with proper shaders.
ProgramVariantPtr new_program = ProgramVariantPtr(new ProgramVariant());
new_program->AttachShader(program_->GetVertexShader());
ShaderVariantPtr fragment_shader = ShaderVariantPtr(
new ShaderVariant(FRAGMENT_SHADER));
fragment_shader->SetGlobalTextureTarget(global_texture_target);
fragment_shader->SetSource(
program_->GetFragmentShader()->GetOriginalSource());
fragment_shader->Compile();
new_program->AttachShader(fragment_shader);
#ifdef ENABLE_API_LOGGING
ALOGI("Rewrote fragment shader old_name=%d new_name=%d:\n"
"--- Original Source ---\n%s\n"
"--- Modified Source ---\n%s\n"
"--- End of Source ---\n",
program_->GetFragmentShader()->GetOrCreateGlobalName(),
fragment_shader->GetOrCreateGlobalName(),
fragment_shader->GetOriginalSource().c_str(),
fragment_shader->GetUpdatedSource().c_str());
#endif
// Transfer vertex attribute bindings as they refer to app's GL context.
for (int i = 0; i < program_->GetActiveAttribCount(); ++i) {
GLint location = -1;
std::string name = program_->GetActiveAttribNameAndLocation(i, &location);
if (location != -1) {
new_program->BindAttribLocation(name, location);
}
}
// Link the new program.
new_program->Link();
if (!new_program->IsLinked()) {
ALOGW("Unable to link program for texture target 0x%x",
global_texture_target);
return NULL;
}
return new LinkedProgram(new_program);
}