blob: e19ae9e41701489259c07f5bd2f2736376ba66c9 [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/shader_variant.h"
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include "common/alog.h"
#include "gles/gles_context.h"
#include "gles/gles_validate.h"
#include "gles/macros.h"
using android::base::AutoLock;
static GlesContext* GetRequiredContext() {
GlesContext* ctx = GetCurrentGlesContext();
LOG_ALWAYS_FATAL_IF(!ctx);
return ctx;
}
ShaderVariant::ShaderVariant(ObjectType object_type)
: object_type_(object_type), global_name_(0),
compile_status_(COMPILE_NOT_ATTEMPTED),
global_texture_target_(GL_TEXTURE_2D),
has_replaced_external_texture_with_2d_(false) {
LOG_ALWAYS_FATAL_IF(object_type != VERTEX_SHADER &&
object_type != FRAGMENT_SHADER);
}
ShaderVariant::~ShaderVariant() {
if (global_name_) {
GlesContext* ctx = GetRequiredContext();
PASS_THROUGH(ctx, DeleteShader, global_name_);
}
}
void ShaderVariant::EnsureLiveShaderObject(GlesContext* ctx) {
if (!global_name_) {
if (object_type_ == VERTEX_SHADER) {
global_name_ = PASS_THROUGH(ctx, CreateShader, GL_VERTEX_SHADER);
} else {
global_name_ = PASS_THROUGH(ctx, CreateShader, GL_FRAGMENT_SHADER);
}
LOG_ALWAYS_FATAL_IF(!global_name_);
}
}
GLuint ShaderVariant::GetOrCreateGlobalName() {
EnsureLiveShaderObject(GetRequiredContext());
return global_name_;
}
void ShaderVariant::SetGlobalTextureTarget(GLenum target) {
AutoLock lock(lock_);
LOG_ALWAYS_FATAL_IF(IsCompileRequested());
LOG_ALWAYS_FATAL_IF(!IsValidTextureTargetLimited(target));
global_texture_target_ = target;
}
void ShaderVariant::SetSource(const std::string& str) {
AutoLock lock(lock_);
LOG_ALWAYS_FATAL_IF(IsCompileRequested());
original_source_ = str;
RewriteSource();
}
std::string ShaderVariant::GetInfoLog() const {
AutoLock lock(lock_);
if (!global_name_ || !info_log_cache_.IsDirty()) {
return info_log_cache_.Get();
}
GLint info_log_length = 0;
GlesContext* ctx = GetRequiredContext();
PASS_THROUGH(ctx, GetShaderiv, global_name_,
GL_INFO_LOG_LENGTH, &info_log_length);
if (info_log_length > 0) {
char* buf = new char[info_log_length];
PASS_THROUGH(ctx, GetShaderInfoLog, global_name_,
info_log_length, NULL, buf);
info_log_cache_.Mutate() = buf;
delete[] buf;
} else {
info_log_cache_.Mutate() = "";
}
info_log_cache_.Clean();
return info_log_cache_.Get();
}
bool ShaderVariant::VerifySuccessfulCompile() const {
AutoLock lock(lock_);
if (compile_status_ == COMPILE_ATTEMPTED) {
GLint compileStatus = 0;
GlesContext* ctx = GetRequiredContext();
PASS_THROUGH(ctx, GetShaderiv, global_name_,
GL_COMPILE_STATUS, &compileStatus);
compile_status_ = (compileStatus != GL_FALSE ?
COMPILE_SUCCESSFUL : COMPILE_FAILED);
}
return (compile_status_ == COMPILE_SUCCESSFUL);
}
void ShaderVariant::Compile() {
AutoLock lock(lock_);
LOG_ALWAYS_FATAL_IF(IsCompileRequested());
GlesContext* ctx = GetRequiredContext();
EnsureLiveShaderObject(ctx);
const char* source_ptr = updated_source_.c_str();
PASS_THROUGH(ctx, ShaderSource, global_name_, 1, &source_ptr, NULL);
PASS_THROUGH(ctx, CompileShader, global_name_);
compile_status_ = COMPILE_ATTEMPTED;
info_log_cache_.Mutate() = "";
}
void ShaderVariant::RewriteSource() {
// We will be injecting some extra code into the source so we need to
// reserve just a little extra space for it.
const size_t extra_source_size = 256;
updated_source_.reserve(original_source_.size() + extra_source_size);
updated_source_.clear();
std::string modified_source = original_source_;
// Note: ExtractVersion actually modifies the original source, removing the
// #version line. One way to prevent this would be to create a temporary
// copy of the original source, but modifying the original source directly
// is faster and should not really cause any issues.
const int version = ExtractVersion(&modified_source);
if (version > 0) {
char version_decl[26];
snprintf(version_decl, sizeof(version_decl), "#version %d\n", version);
updated_source_ += version_decl;
}
// TODO(crbug.com/441937): Parse the extension correctly by handling things
// like whitespace as well as enable/warn/disable options.
if (global_texture_target_ == GL_TEXTURE_2D) {
Substitute(&modified_source,
"#extension GL_OES_EGL_image_external : require",
" ");
// TODO(crbug.com/441937): Parse the declarations correctly so these
// substitutions are only applied to the intended uniform declarations.
has_replaced_external_texture_with_2d_ =
Substitute(&modified_source,
" samplerExternalOES ",
" sampler2D ");
} else {
// The underlying texture is external - do not change the program.
has_replaced_external_texture_with_2d_ = false;
}
// TODO(crbug.com/422014): Remove these subtitutions when we either can turn
// off the compiler error in Chrome, or if we decided to require developers
// running under ARC to conform to the GLES standard.
//
// We strip out these specific default precision statements as a workaround
// for a handful of apps. PepperGL follows the GLES standard, and requires
// that the qualifiers used on uniforms shared between the vertex and
// fragment shaders match. Android does not perform that check, and there
// are some apps that therefore fail to run.
//
// Note that there is still possible to have a mismatch if the shader uses
// preprocessor definitions to generate the default precision statement,
// or if the shader uses precision qualifiers when declaring the uniforms
// (not using the defaults). We should have a better answer before it becomes
// necessary to do something more complicated than this.
Substitute(&modified_source,
"precision highp float;",
" ");
Substitute(&modified_source,
"precision mediump float;",
" ");
Substitute(&modified_source,
"precision lowp float;",
" ");
// The consequence of stripping out the default precision statements is that
// we need to add one back in. Fragment shaders in particular have no default
// precision defined for floating point values, which is also an error.
updated_source_ += "precision highp float;\n";
updated_source_ += "#line 1\n";
updated_source_ += modified_source;
}
// static
bool ShaderVariant::Substitute(std::string* src, const char* match,
const char* repl) {
const size_t match_len = strlen(match);
const size_t repl_len = strlen(repl);
LOG_ALWAYS_FATAL_IF(match_len != repl_len);
bool any_matches = false;
size_t idx = src->find(match);
while (idx != std::string::npos) {
any_matches = true;
for (size_t i = 0; i < match_len; ++i) {
(*src)[idx + i] = repl[i];
}
idx = src->find(match);
}
return any_matches;
}
// static
int ShaderVariant::ExtractVersion(std::string* src) {
// Find and remove the #version token if it exists.
const int min_version = 100;
enum State {
PARSE_NONE,
PARSE_IN_C_COMMENT,
PARSE_IN_LINE_COMMENT
};
int version = min_version;
State state = PARSE_NONE;
size_t idx = 0;
while (idx < src->size()) {
char curr = src->at(idx);
char next = src->at(idx + 1);
if (state == PARSE_IN_C_COMMENT) {
if (curr == '*' && next == '/') {
state = PARSE_NONE;
idx += 2;
} else {
++idx;
}
} else if (state == PARSE_IN_LINE_COMMENT) {
if (curr == '\n') {
state = PARSE_NONE;
}
++idx;
} else if (curr == '/' && next == '/') {
state = PARSE_IN_LINE_COMMENT;
idx += 2;
} else if (curr == '/' && next == '*') {
state = PARSE_IN_C_COMMENT;
idx += 2;
} else if (curr == ' ' || curr == '\t' || curr == '\r' || curr == '\n') {
++idx;
} else {
// We have reached the first non-blank character outside a comment. If
// there is a #version token in the source, then it must be here.
char* c = &src->at(idx);
// TODO(crbug.com/441937): Make this more robust. It should be able to
// correctly handle something like "#version123".
if (!strncmp(c, "#version", 8)) {
int ver;
if (sscanf(c + 8, "%d", &ver) == 1) { // NOLINT(runtime/printf)
// Blank out the version token from the source.
for (int i = 0; i < 8; i++, c++)
*c = ' ';
while (*c < '0' || *c > '9') {
*c = ' ';
++c;
}
while (*c >= '0' && *c <= '9') {
*c = ' ';
++c;
}
version = std::max(version, ver);
}
}
break;
}
}
return version;
}