blob: d7560584d2048ed79a6903047ced3feac125f034 [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/state.h"
#include <algorithm>
#include <stdio.h>
#include <GLES/glext.h>
#include "common/alog.h"
#include "common/dlog.h"
#include "gles/gles_options.h"
#include "gles/buffer_data.h"
#include "gles/debug.h"
#include "gles/gles_context.h"
#include "gles/macros.h"
#define UNIFORM_KEY(enum, name) name,
const char* kUniformNames[] = {UNIFORM_KEY_TUPLE};
#undef UNIFORM_KEY
inline int Log2Floor(int max_texture_size) {
const double size = static_cast<double>(max_texture_size);
return floor(log(size) / log(2.0));
}
struct ElementRange {
ElementRange() : first(0), last(0) {}
size_t first;
size_t last;
};
static ElementRange GetElementRange(GLsizei count, GLenum type,
const GLvoid* data) {
ElementRange range;
if (count == 0) {
return range;
} else if (type == GL_UNSIGNED_BYTE) {
const GLubyte* arr = (const GLubyte*)data;
range.first = arr[0];
range.last = arr[0];
for (int i = 1; i < count; ++i) {
range.first = std::min<size_t>(arr[i], range.first);
range.last = std::max<size_t>(arr[i], range.last);
}
} else if (type == GL_UNSIGNED_SHORT) {
const GLushort* arr = (const GLushort*)data;
range.first = arr[0];
range.last = arr[0];
for (int i = 1; i < count; ++i) {
range.first = std::min<size_t>(arr[i], range.first);
range.last = std::max<size_t>(arr[i], range.last);
}
} else if (type == GL_UNSIGNED_INT) {
const GLuint* arr = (const GLuint*)data;
range.first = arr[0];
range.last = arr[0];
for (int i = 1; i < count; ++i) {
range.first = std::min<size_t>(arr[i], range.first);
range.last = std::max<size_t>(arr[i], range.last);
}
} else {
LOG_ALWAYS_FATAL("Unknown type");
}
return range;
}
static GLint GetTypeSize(GLenum type) {
switch (type) {
case GL_BYTE:
return sizeof(GLbyte);
case GL_UNSIGNED_BYTE:
return sizeof(GLubyte);
case GL_SHORT:
return sizeof(GLshort);
case GL_UNSIGNED_SHORT:
return sizeof(GLushort);
case GL_INT:
return sizeof(GLint);
case GL_UNSIGNED_INT:
return sizeof(GLuint);
case GL_FLOAT:
return sizeof(GLfloat);
case GL_FIXED:
return sizeof(GLfixed);
default:
LOG_ALWAYS_FATAL("Unknown GL type: %d", type);
return -1;
}
}
// Default values for lights can be found here:
// es_full_spec.1.1.12.pdf section 2.12.1 table 2.8.
Light::Light()
: ambient(0.f, 0.f, 0.f, 1.f),
diffuse(0.f, 0.f, 0.f, 1.f),
specular(0.f, 0.f, 0.f, 1.f),
position(0.f, 0.f, 1.f, 0.f),
direction(0.f, 0.f, -1.f, 0.f),
spot_exponent(0.f),
spot_cutoff(180.f),
const_attenuation(1.f),
linear_attenuation(0.f),
quadratic_attenuation(0.f) {}
bool Light::IsSpot() const { return spot_cutoff != 180.f; }
bool Light::IsPositional() const { return position.Get(3) != 0.f; }
bool Light::IsDirectional() const { return position.Get(3) == 0.f; }
bool Light::ShouldAttenuate() const {
return IsPositional() &&
(const_attenuation != 1.f || linear_attenuation != 0.f ||
quadratic_attenuation != 0.f);
}
// Default values for lights can be found here:
// es_full_spec.1.1.12.pdf section 2.12.1 table 2.8.
Material::Material()
: ambient(0.2f, 0.2f, 0.2f, 1.0f),
diffuse(0.8f, 0.8f, 0.8f, 1.0f),
specular(0.f, 0.f, 0.f, 1.f),
emission(0.f, 0.f, 0.f, 1.f),
shininess(0.f) {}
// Default values for lights can be found here:
// es_full_spec.1.1.12.pdf section 3.8.
Fog::Fog()
: mode(GL_EXP),
color(0.f, 0.f, 0.f, 0.f),
density(1.f),
start(0.f),
end(1.f) {}
// See es_full_spec.1.1.12.pdf section 3.7.12.
// The initial values are defined at the end of the section.
TexEnv::TexEnv()
: mode(GL_MODULATE),
combine_rgb(GL_MODULATE),
combine_alpha(GL_MODULATE),
color(0.f, 0.f, 0.f, 0.f),
rgb_scale(1.0f),
alpha_scale(1.0f) {
src_rgb[0] = GL_TEXTURE;
src_rgb[1] = GL_PREVIOUS;
src_rgb[2] = GL_CONSTANT;
src_alpha[0] = GL_TEXTURE;
src_alpha[1] = GL_PREVIOUS;
src_alpha[2] = GL_CONSTANT;
operand_rgb[0] = GL_SRC_COLOR;
operand_rgb[1] = GL_SRC_COLOR;
operand_rgb[2] = GL_SRC_ALPHA; // Note: alpha here conforms to spec
operand_alpha[0] = GL_SRC_ALPHA;
operand_alpha[1] = GL_SRC_ALPHA;
operand_alpha[2] = GL_SRC_ALPHA;
}
// See https://www.khronos.org/registry/gles/extensions/OES/OES_texture_cube_map.txt
// The initial values are defined in Section 2.11.4
TexGen::TexGen()
: mode(GL_REFLECTION_MAP_OES),
enabled(GL_FALSE) {
}
// See es_full_spec.1.1.12.pdf section 6.2
// The initial values are defined in table 6.11
PointParameters::PointParameters()
: size_min(0.f),
size_max(1.f),
current_size(1.f) {
attenuation[0] = 1.f;
attenuation[1] = 0.f;
attenuation[2] = 0.f;
}
AlphaTest::AlphaTest()
: func(GL_ALWAYS), value(0.f) {
}
PointerData::PointerData()
: enabled(false),
size(4),
type(GL_FLOAT),
stride(0),
normalize(GL_FALSE),
buffer_name(0),
pointer(NULL) {
}
void StateCacheLoaderHelper::Load(GLenum value, GLboolean* data) {
GlesContext* c = GetCurrentGlesContext();
LOG_ALWAYS_FATAL_IF(c == NULL);
PASS_THROUGH(c, GetBooleanv, value, data);
}
void StateCacheLoaderHelper::Load(GLenum value, GLfloat* data) {
GlesContext* c = GetCurrentGlesContext();
LOG_ALWAYS_FATAL_IF(c == NULL);
PASS_THROUGH(c, GetFloatv, value, data);
}
void StateCacheLoaderHelper::Load(GLenum value, GLint* data) {
GlesContext* c = GetCurrentGlesContext();
LOG_ALWAYS_FATAL_IF(c == NULL);
PASS_THROUGH(c, GetIntegerv, value, data);
}
void StateCacheLoaderHelper::Load(GLenum value, GLuint* data) {
GlesContext* c = GetCurrentGlesContext();
LOG_ALWAYS_FATAL_IF(c == NULL);
PASS_THROUGH(c, GetIntegerv, value, reinterpret_cast<GLint*>(data));
}
void StateCacheLoaderHelper::Load(
GLenum value, GLboolean& data) { // NOLINT(runtime/references)
Load(value, &data);
}
void StateCacheLoaderHelper::Load(
GLenum value, GLfloat& data) { // NOLINT(runtime/references)
Load(value, &data);
}
void StateCacheLoaderHelper::Load(
GLenum value, GLint& data) { // NOLINT(runtime/references)
Load(value, &data);
}
void StateCacheLoaderHelper::Load(
GLenum value, GLuint& data) { // NOLINT(runtime/references)
Load(value, &data);
}
namespace {
const GLfloat kFullscreenQuadVertices[] = {
// position
-1.0f, -1.0f,
+1.0f, -1.0f,
-1.0f, +1.0f,
+1.0f, +1.0f,
// texcoord
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
// flipped texcoord
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
const size_t kNormalUVOffset = sizeof(GLfloat) * 8;
const size_t kFlippedUVOffset = sizeof(GLfloat) * 16;
const char kVertexShader[] =
"attribute mediump vec2 a_position;"
"attribute mediump vec2 a_texcoord;"
"varying mediump vec2 v_texcoord;"
"void main() {"
" v_texcoord = a_texcoord;"
" gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);"
"}";
const char kFragmentShader[] =
"uniform sampler2D u_sampler;"
"varying mediump vec2 v_texcoord;"
"void main() {"
" gl_FragColor = texture2D(u_sampler, v_texcoord);"
"}";
} // namespace
FullscreenQuad::FullscreenQuad(GlesContext* context)
: context_(context),
program_(0),
buffer_(0),
position_attrib_idx_(-1),
sampler_uniform_idx_(-1) {
const GLuint vsh = context_->CompileShader(GL_VERTEX_SHADER, kVertexShader);
const GLuint fsh = context_->CompileShader(GL_FRAGMENT_SHADER,
kFragmentShader);
program_ = context_->CompileProgram(vsh, fsh);
position_attrib_idx_ = PASS_THROUGH(context_, GetAttribLocation, program_,
"a_position");
texcoord_attrib_idx_ = PASS_THROUGH(context_, GetAttribLocation, program_,
"a_texcoord");
sampler_uniform_idx_ = PASS_THROUGH(context_, GetUniformLocation, program_,
"u_sampler");
PASS_THROUGH(context_, DeleteShader, vsh);
PASS_THROUGH(context_, DeleteShader, fsh);
PASS_THROUGH(context_, GenBuffers, 1, &buffer_);
PASS_THROUGH(context_, BindBuffer, GL_ARRAY_BUFFER, buffer_);
PASS_THROUGH(context_, BufferData, GL_ARRAY_BUFFER,
sizeof(kFullscreenQuadVertices), kFullscreenQuadVertices,
GL_STATIC_DRAW);
PASS_THROUGH(context_, UseProgram, program_);
PASS_THROUGH(context_, Uniform1i, sampler_uniform_idx_, 0);
}
FullscreenQuad::~FullscreenQuad() {
PASS_THROUGH(context_, DeleteBuffers, 1, &buffer_);
PASS_THROUGH(context_, DeleteProgram, program_);
}
void FullscreenQuad::Draw(GLuint texture, bool flip_v) {
const GLuint global_name =
context_->GetShareGroup()->GetTextureGlobalName(texture);
LOG_ALWAYS_FATAL_IF(global_name == 0);
PASS_THROUGH(context_, UseProgram, program_);
PASS_THROUGH(context_, ActiveTexture, GL_TEXTURE0);
PASS_THROUGH(context_, BindTexture, GL_TEXTURE_2D, global_name);
PASS_THROUGH(context_, EnableVertexAttribArray, position_attrib_idx_);
PASS_THROUGH(context_, EnableVertexAttribArray, texcoord_attrib_idx_);
PASS_THROUGH(context_, BindBuffer, GL_ARRAY_BUFFER, buffer_);
PASS_THROUGH(context_, VertexAttribPointer, position_attrib_idx_, 2,
GL_FLOAT, GL_FALSE, 0, 0);
const size_t offset = flip_v ? kFlippedUVOffset : kNormalUVOffset;
PASS_THROUGH(context_, VertexAttribPointer, texcoord_attrib_idx_, 2,
GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void*>(offset));
PASS_THROUGH(context_, DrawArrays, GL_TRIANGLE_STRIP, 0, 4);
PASS_THROUGH(context_, DisableVertexAttribArray, position_attrib_idx_);
PASS_THROUGH(context_, DisableVertexAttribArray, texcoord_attrib_idx_);
}
// Default values can be found here:
// color:es_full_spec.1.1.12.pdf section 2.7
// normal: es_full_spec.1.1.12.pdf section 2.7
// ambient: es_full_spec.1.1.12.pdf section 2.12.1 table 2.8.
UniformContext::UniformContext(GlesContext* context)
: context_(context),
program_object_(0),
active_texture_unit_(0),
current_palette_matrix_(0),
ambient_(emugl::Vector(0.2f, 0.2f, 0.2f, 1.f)),
color_(emugl::Vector(1.f, 1.f, 1.f, 1.f)),
normal_(emugl::Vector(0.f, 0.f, 1.f, 0.f)) {
DLOG("Created uniform context for GLES1 context @ %p", context);
// GL_MODELVIEW is the default matrix mode.
// es_full_spec.1.1.12.pdf section 2.10.2.
SetMatrixMode(GL_MODELVIEW);
// Some default values for LIGHT0 are different than the other lights.
// es_full_spec.1.1.12.pdf section 2.12.1 table 2.8.
lights_[0].Mutate().diffuse = emugl::Vector(1.f, 1.f, 1.f, 1.f);
lights_[0].Mutate().specular = emugl::Vector(1.f, 1.f, 1.f, 1.f);
}
void UniformContext::Init(int num_texture_units) {
DLOG("Initialized uniform context for GLES1 context @ %p", context_);
LOG_ALWAYS_FATAL_IF(num_texture_units != kMaxTextureUnits);
}
void UniformContext::Bind(ProgramContext* program) {
LOG_ALWAYS_FATAL_IF(!program);
// If the program has changed, we need to make sure all the uniforms
// are updated as the new program may have restored out-of-date uniform
// data.
const bool force_update = (program_object_ != program->GetProgramObject());
const bool mv_is_dirty = modelview_matrix_stack_.IsDirty();
const bool p_is_dirty = projection_matrix_stack_.IsDirty();
if (force_update || mv_is_dirty) {
const emugl::Matrix& model_view_matrix =
modelview_matrix_stack_.Get().GetTop();
// TODO(crbug.com/441941): Use the adjoint of the model view matrix for
// computing normals.
if (program->GetUniformLocation(
kModelViewInverseTransposeMatrixUniform) != -1) {
emugl::Matrix mv_inverse_transpose = model_view_matrix;
mv_inverse_transpose.Inverse();
mv_inverse_transpose.Transpose();
if (context_->IsEnabled(GL_RESCALE_NORMAL)) {
mv_inverse_transpose.RescaleNormal();
}
program->BindUniform(mv_inverse_transpose,
kModelViewInverseTransposeMatrixUniform);
}
program->BindUniform(model_view_matrix, kModelViewMatrixUniform);
modelview_matrix_stack_.Clean();
}
if (force_update || p_is_dirty) {
const emugl::Matrix& projection_matrix =
projection_matrix_stack_.Get().GetTop();
program->BindUniform(projection_matrix, kProjectionMatrixUniform);
projection_matrix_stack_.Clean();
}
if (force_update || alpha_test_.IsDirty()) {
program->BindUniform(alpha_test_.Get().value, kAlphaTestConstantUniform);
alpha_test_.Clean();
}
if (force_update || ambient_.IsDirty()) {
program->BindUniform(ambient_.Get(), kAmbientLightUniform);
ambient_.Clean();
}
if (force_update || material_.IsDirty()) {
program->BindUniform(material_.Get());
material_.Clean();
}
if (force_update || fog_.IsDirty()) {
program->BindUniform(fog_.Get());
fog_.Clean();
}
if (force_update || point_parameters_.IsDirty()) {
program->BindUniform(point_parameters_.Get());
point_parameters_.Clean();
}
// TODO(crbug.com/441942): Bind light uniforms as one array of data rather
// than individually with a loop.
for (int i = 0; i < kMaxLights; ++i) {
if (force_update || lights_[i].IsDirty()) {
program->BindUniform(lights_[i].Get(), i);
lights_[i].Clean();
}
}
// TODO(crbug.com/441942): Bind clip plane uniforms as one array of data
// rather than individually with a loop.
for (int i = 0; i < kMaxClipPlanes; ++i) {
if (force_update || clip_planes_[i].IsDirty()) {
program->BindUniform(clip_planes_[i].Get(), kClipPlaneUniform, i);
clip_planes_[i].Clean();
}
}
// TODO(crbug.com/441942): Bind texture uniforms as one array of data rather
// than individually with a loop.
for (int i = 0; i < kMaxTextureUnits; ++i) {
// Bind sampler N to texture unit N.
const GLint sampler_id = i;
program->BindUniform(sampler_id, kTextureSamplerUniform, i);
if (force_update || texenv_[i].IsDirty()) {
program->BindUniform(texenv_[i].Get(), i);
texenv_[i].Clean();
}
if (force_update || texture_matrix_stack_[i].IsDirty()) {
const emugl::Matrix& texture_matrix =
texture_matrix_stack_[i].Get().GetTop();
program->BindUniform(texture_matrix, kTextureMatrixUniform, i);
texture_matrix_stack_[i].Clean();
}
}
for (int i = 0; i < kMaxPaletteMatricesOES; ++i) {
if (force_update || palette_matrices_[i].IsDirty()) {
emugl::Matrix matrix = palette_matrices_[i].Get();
program->BindUniform(matrix, kPaletteMatrixUniform, i);
matrix.Inverse();
matrix.Transpose();
program->BindUniform(matrix, kPaletteInverseMatrixUniform, i);
palette_matrices_[i].Clean();
}
}
program_object_ = program->GetProgramObject();
}
void UniformContext::Enable(GLenum cap) {
if (cap == GL_TEXTURE_GEN_STR_OES) {
MutateActiveTexGen().enabled = GL_TRUE;
}
}
void UniformContext::Disable(GLenum cap) {
if (cap == GL_TEXTURE_GEN_STR_OES) {
MutateActiveTexGen().enabled = GL_FALSE;
}
}
GLboolean UniformContext::IsEnabled(GLenum cap) const {
if (cap == GL_TEXTURE_GEN_STR_OES) {
return GetActiveTexGen().enabled;
}
return GL_FALSE;
}
bool UniformContext::SetMatrixMode(GLenum mode) {
switch (mode) {
case GL_PROJECTION:
case GL_MATRIX_PALETTE_OES:
case GL_MODELVIEW:
case GL_TEXTURE:
matrix_mode_ = mode;
return true;
default:
return false;
}
}
bool UniformContext::SetActiveTexture(GLenum texture_unit) {
const int stage = texture_unit - GL_TEXTURE0;
if (stage >= 0 && stage < kMaxTextureUnits) {
active_texture_unit_ = stage;
return true;
}
return false;
}
const emugl::Matrix& UniformContext::GetModelViewMatrix() const {
return modelview_matrix_stack_.Get().GetTop();
}
emugl::Matrix& UniformContext::MutateModelViewMatrix() {
return modelview_matrix_stack_.Mutate().GetTop();
}
const emugl::Matrix& UniformContext::GetProjectionMatrix() const {
return projection_matrix_stack_.Get().GetTop();
}
emugl::Matrix& UniformContext::MutateProjectionMatrix() {
return projection_matrix_stack_.Mutate().GetTop();
}
const emugl::Matrix& UniformContext::GetTextureMatrix() const {
return texture_matrix_stack_[active_texture_unit_].Get().GetTop();
}
const emugl::Matrix& UniformContext::GetTextureMatrixByStage(GLint stage) const {
LOG_ALWAYS_FATAL_IF(stage < 0 || stage >= kMaxTextureUnits);
return texture_matrix_stack_[stage].Get().GetTop();
}
emugl::Matrix& UniformContext::MutateTextureMatrixByStage(GLint stage) {
LOG_ALWAYS_FATAL_IF(stage < 0 || stage >= kMaxTextureUnits);
return texture_matrix_stack_[stage].Mutate().GetTop();
}
bool UniformContext::ActiveMatrixPush() {
if (matrix_mode_ == GL_MATRIX_PALETTE_OES) {
return false;
}
const Dirtiable<MatrixStack>* stack = GetActiveMatrixStackInternal();
return const_cast<Dirtiable<MatrixStack>*>(stack)->Mutate().Push();
}
bool UniformContext::ActiveMatrixPop() {
if (matrix_mode_ == GL_MATRIX_PALETTE_OES) {
return false;
}
const Dirtiable<MatrixStack>* stack = GetActiveMatrixStackInternal();
return const_cast<Dirtiable<MatrixStack>*>(stack)->Mutate().Pop();
}
emugl::Matrix& UniformContext::MutateActiveMatrix() {
if (matrix_mode_ == GL_MATRIX_PALETTE_OES) {
return MutatePaletteMatrix();
}
const Dirtiable<MatrixStack>* stack = GetActiveMatrixStackInternal();
return const_cast<Dirtiable<MatrixStack>*>(stack)->Mutate().GetTop();
}
const emugl::Matrix& UniformContext::GetActiveMatrix() const {
if (matrix_mode_ == GL_MATRIX_PALETTE_OES) {
return GetPaletteMatrix();
}
const Dirtiable<MatrixStack>* stack = GetActiveMatrixStackInternal();
return stack->Get().GetTop();
}
bool UniformContext::SetCurrentPaletteMatrix(GLuint index) {
if (index >= kMaxPaletteMatricesOES) {
return false;
}
current_palette_matrix_ = index;
return true;
}
const emugl::Matrix& UniformContext::GetPaletteMatrix() const {
return palette_matrices_[current_palette_matrix_].Get();
}
emugl::Matrix& UniformContext::MutatePaletteMatrix() {
return palette_matrices_[current_palette_matrix_].Mutate();
}
TexEnv& UniformContext::MutateActiveTexEnv() {
return texenv_[active_texture_unit_].Mutate();
}
const TexEnv& UniformContext::GetActiveTexEnv() const {
return texenv_[active_texture_unit_].Get();
}
TexGen& UniformContext::MutateActiveTexGen() {
return texgen_[active_texture_unit_].Mutate();
}
const TexGen& UniformContext::GetActiveTexGen() const {
return texgen_[active_texture_unit_].Get();
}
emugl::Vector* UniformContext::MutateClipPlane(GLenum id) {
const int index = id - GL_CLIP_PLANE0;
if (index >= 0 && index < kMaxClipPlanes) {
return &clip_planes_[index].Mutate();
}
return NULL;
}
const emugl::Vector* UniformContext::GetClipPlane(GLenum id) const {
const int index = id - GL_CLIP_PLANE0;
if (index >= 0 && index < kMaxClipPlanes) {
return &clip_planes_[index].Get();
}
return NULL;
}
TexEnv* UniformContext::MutateTexEnv(GLenum id) {
const int stage = id - GL_TEXTURE0;
if (stage >= 0 && stage < kMaxTextureUnits) {
return &texenv_[stage].Mutate();
}
return NULL;
}
const TexEnv* UniformContext::GetTexEnv(GLenum id) const {
const int stage = id - GL_TEXTURE0;
if (stage >= 0 && stage < kMaxTextureUnits) {
return &texenv_[stage].Get();
}
return NULL;
}
TexGen* UniformContext::MutateTexGen(GLenum id) {
const int stage = id - GL_TEXTURE0;
if (stage >= 0 && stage < kMaxTextureUnits) {
return &texgen_[stage].Mutate();
}
return NULL;
}
const TexGen* UniformContext::GetTexGen(GLenum id) const {
const int stage = id - GL_TEXTURE0;
if (stage >= 0 && stage < kMaxTextureUnits) {
return &texgen_[stage].Get();
}
return NULL;
}
Light* UniformContext::MutateLight(GLenum id) {
const int index = id - GL_LIGHT0;
if (index >= 0 && index < kMaxLights) {
return &lights_[index].Mutate();
}
return NULL;
}
const Light* UniformContext::GetLight(GLenum id) const {
const int index = id - GL_LIGHT0;
if (index >= 0 && index < kMaxLights) {
return &lights_[index].Get();
}
return NULL;
}
const Dirtiable<MatrixStack>*
UniformContext::GetActiveMatrixStackInternal() const {
switch (matrix_mode_) {
case GL_PROJECTION:
return &projection_matrix_stack_;
case GL_MODELVIEW:
return &modelview_matrix_stack_;
case GL_TEXTURE:
return &texture_matrix_stack_[active_texture_unit_];
default:
return NULL;
}
}
ProgramContext::ProgramContext(GlesContext* context, GLuint program)
: context_(context), program_object_(program) {
DLOG("Created program context for GLES1 context @ %p", context_);
}
void ProgramContext::Bind() {
PASS_THROUGH(context_, UseProgram, program_object_);
}
void ProgramContext::Delete() {
PASS_THROUGH(context_, DeleteProgram, program_object_);
}
GLint ProgramContext::GetUniformLocation(UniformKey key) {
return GetUniformLocation(key, kScalarUniform);
}
GLint ProgramContext::GetUniformLocation(UniformKey key, int idx) {
const std::pair<UniformKey, int> entry = std::make_pair(key, idx);
UniformLocationMap::iterator iter = uniform_location_map_.find(entry);
if (iter != uniform_location_map_.end()) {
return iter->second;
}
static const int kMaxBufferLength = 256;
char buffer[kMaxBufferLength];
const char* name = kUniformNames[key];
if (strstr(name, "%d")) {
LOG_ALWAYS_FATAL_IF(idx < 0, "%s is an array", name);
snprintf(buffer, sizeof(buffer), name, idx);
} else {
LOG_ALWAYS_FATAL_IF(idx >= 0, "%s is not an array", name);
strncpy(buffer, name, sizeof(buffer));
}
buffer[kMaxBufferLength - 1] = 0;
const GLint location =
PASS_THROUGH(context_, GetUniformLocation, program_object_, buffer);
uniform_location_map_[entry] = location;
return location;
}
void ProgramContext::BindUniform(const int& value, UniformKey key) {
BindUniform(value, key, kScalarUniform);
}
void ProgramContext::BindUniform(const int& value, UniformKey key, int index) {
const GLint location = GetUniformLocation(key, index);
if (location != -1) {
PASS_THROUGH(context_, Uniform1i, location, value);
}
}
void ProgramContext::BindUniform(const float& value, UniformKey key) {
BindUniform(value, key, kScalarUniform);
}
void ProgramContext::BindUniform(const float& value, UniformKey key,
int index) {
const GLint location = GetUniformLocation(key, index);
if (location != -1) {
PASS_THROUGH(context_, Uniform1f, location, value);
}
}
void ProgramContext::BindUniform(const GLfloat (&values)[3], UniformKey key) {
const int location = GetUniformLocation(key);
if (location != -1) {
PASS_THROUGH(context_, Uniform3fv, location, 1, values);
}
}
void ProgramContext::BindUniform(const emugl::Vector& vector, UniformKey key) {
BindUniform(vector, key, kScalarUniform);
}
void ProgramContext::BindUniform(const emugl::Vector& vector, UniformKey key,
int index) {
const GLint location = GetUniformLocation(key, index);
if (location != -1) {
GLfloat float_array[emugl::Vector::kEntries];
vector.GetFloatArray(float_array);
PASS_THROUGH(context_, Uniform4fv, location, 1, float_array);
}
}
void ProgramContext::BindUniform(const emugl::Matrix& matrix, UniformKey key) {
BindUniform(matrix, key, kScalarUniform);
}
void ProgramContext::BindUniform(const emugl::Matrix& matrix, UniformKey key,
int index) {
const GLint location = GetUniformLocation(key, index);
if (location != -1) {
GLfloat float_array[emugl::Matrix::kEntries];
matrix.GetColumnMajorArray(float_array);
PASS_THROUGH(context_, UniformMatrix4fv, location, 1, GL_FALSE,
float_array);
}
}
void ProgramContext::BindUniform(const Fog& fog) {
BindUniform(fog.density, kFogDensityUniform);
BindUniform(fog.start, kFogStartUniform);
BindUniform(fog.end, kFogEndUniform);
BindUniform(fog.color, kFogColorUniform);
}
void ProgramContext::BindUniform(const Light& light, int index) {
const float light_spot_cutoff =
cos(light.spot_cutoff * emugl::kRadiansPerDegree);
emugl::Vector light_position = light.position;
if (light_position.Get(3) == 0.f) {
light_position.Normalize();
}
emugl::Vector light_direction = light.direction;
light_direction.Normalize();
light_direction.Scale(-1.f);
BindUniform(light.ambient, kLightAmbientUniform, index);
BindUniform(light.diffuse, kLightDiffuseUniform, index);
BindUniform(light.specular, kLightSpecularUniform, index);
BindUniform(light_position, kLightPositionUniform, index);
BindUniform(light_direction, kLightDirectionUniform, index);
BindUniform(light.spot_exponent, kLightSpotExponentUniform, index);
BindUniform(light_spot_cutoff, kLightSpotCutoffUniform, index);
BindUniform(light.const_attenuation, kLightConstAttenuationUniform, index);
BindUniform(light.linear_attenuation, kLightLinearAttenuationUniform, index);
BindUniform(light.quadratic_attenuation, kLightQuadraticAttenuationUniform,
index);
}
void ProgramContext::BindUniform(const Material& material) {
BindUniform(material.ambient, kMaterialAmbientUniform);
BindUniform(material.diffuse, kMaterialDiffuseUniform);
BindUniform(material.specular, kMaterialSpecularUniform);
BindUniform(material.emission, kMaterialEmissionUniform);
BindUniform(material.shininess, kMaterialShininessUniform);
}
void ProgramContext::BindUniform(const PointParameters& point_parameters) {
BindUniform(point_parameters.size_min, kPointSizeMinUniform);
BindUniform(point_parameters.size_max, kPointSizeMaxUniform);
BindUniform(point_parameters.attenuation, kPointSizeAttenuationUniform);
}
void ProgramContext::BindUniform(const TexEnv& texenv, int index) {
// es_full_spec.1.1.12.pdf section 3.7.13
// Load up the various constants used by the texture environment for each
// supported stage.
// Each stage has:
// * An constant color (RGBA), which can be used as a source.
// * A color/alpha scale. For simplicity we generate a four component scale
// here, though we only really need two components.
BindUniform(texenv.color, kTextureEnvColorUniform, index);
const emugl::Vector scale = emugl::Vector(texenv.rgb_scale, texenv.rgb_scale,
texenv.rgb_scale, texenv.alpha_scale);
BindUniform(scale, kTextureEnvScaleUniform, index);
}
const GLenum TextureContext::supported_texture_targets_[kNumTargets] = {
GL_TEXTURE_2D,
GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_EXTERNAL_OES,
};
TextureContext::TextureContext(GlesContext* context)
: context_(context),
max_texture_levels_(0),
num_texture_units_(0),
active_texture_unit_(0),
client_active_texture_unit_(0) {
DLOG("Created texture context for GLES1 context @ %p", context_);
}
TextureContext::~TextureContext() {
}
void TextureContext::Init(int max_texture_units, int max_texture_size) {
DLOG("Initialized texture context for GLES1 context @ %p", context_);
ALOG_ASSERT(max_texture_size > 0);
ALOG_ASSERT((max_texture_size & (max_texture_size - 1)) == 0);
num_texture_units_ = max_texture_units;
max_texture_levels_ = Log2Floor(max_texture_size);
for (int i = 0; i < kNumTargets; ++i) {
const GLenum target = supported_texture_targets_[i];
texture_units_[i].resize(num_texture_units_);
for (GLuint j = 0; j < num_texture_units_; ++j) {
texture_units_[i][j].global_target = target;
}
TextureDataPtr data(new TextureData(0));
data->Bind(target, max_texture_levels_);
default_textures_.push_back(data);
}
}
void TextureContext::DeleteTexture(GLuint texture) {
for (int i = 0; i < kNumTargets; ++i) {
for (GLuint j = 0; j < num_texture_units_; ++j) {
TextureUnit& unit = texture_units_[i][j];
if (unit.texture == texture) {
unit.texture = 0;
}
}
}
}
bool TextureContext::SetActiveTexture(GLenum texture_unit) {
const GLuint stage = texture_unit - GL_TEXTURE0;
if (stage < num_texture_units_) {
active_texture_unit_ = stage;
return true;
}
return false;
}
TextureDataPtr TextureContext::GetDefaultTextureData(GLenum target) {
const Target t = MapTarget(target);
return default_textures_[t];
}
bool TextureContext::BindTextureToTarget(const TextureDataPtr& tex,
GLenum target) {
const GLenum tex_target = tex->GetTarget();
if (tex_target != 0) {
if (MapTarget(tex_target) != MapTarget(target)) {
ALOGW("Cannot change texture target once bound.");
return false;
}
} else {
tex->Bind(target, max_texture_levels_);
}
return true;
}
void TextureContext::SetTargetTexture(GLenum target, GLuint texture,
GLenum global_target) {
// The logic below in PrepareTextures/RestoreTextures assumes that we are only
// ever remapping TEXTURE_EXTERNAL_OES to TEXTURE_2D.
LOG_ALWAYS_FATAL_IF(target != global_target &&
(global_target != GL_TEXTURE_2D ||
target != GL_TEXTURE_EXTERNAL_OES),
"Unsupported mapping of target(%d) and "
"global_target(%d)", target, global_target);
const Target t = MapTarget(target);
TextureUnit& unit = GetTextureUnit(t, active_texture_unit_);
unit.texture = texture;
unit.global_target = global_target;
}
GLenum TextureContext::EnsureCorrectBinding(GLenum target) {
const TextureUnit& unit = GetTextureUnit(MapTarget(target),
active_texture_unit_);
// We only need to rebind if there is a remapping in effect.
if (target != unit.global_target) {
BindUnderlying(unit);
}
return unit.global_target;
}
void TextureContext::RestoreBinding(GLenum target) {
const TextureUnit& unit = GetTextureUnit(MapTarget(target),
active_texture_unit_);
// We only need to restore if there is a remapping in effect.
if (unit.global_target != target) {
const TextureUnit& orig = GetTextureUnit(MapTarget(unit.global_target),
active_texture_unit_);
// We only rebind if that texture is expected at that local target
if (orig.global_target == target) {
BindUnderlying(orig);
}
}
}
void TextureContext::PrepareTextures(bool gles11,
bool shader_uses_external_as_2d) {
GLuint current_active_texture_unit = active_texture_unit_;
for (GLuint i = 0; i < num_texture_units_; ++i) {
const TextureUnit& unit_ext = GetTextureUnit(kTextureExternal, i);
// We only need to do worry about rebinding textures if we have a
// external texture that is actually supposed to be a TEXTURE_2D.
if (unit_ext.global_target == GL_TEXTURE_2D) {
// For the GLES1 code path, we know if the texture target was enabled or
// not. If it is enabled, the external texture overrides any normal
// texture 2d according to the specification.
// For the GLES2+ code path, things are much more interesting, and
// we need to know what uniform type the shader uses for each texture
// unit, as valid textures can be bound to each target and the shader
// defines which one is read.
// TODO(crbug.com/441940): This really needs to be revisited to not
// assume that only the first texture unit is the one that needs to be
// remapped.
bool bind_ext_as_2d = ((gles11 && unit_ext.enabled) ||
(i == 0 && shader_uses_external_as_2d));
ActiveTextureUnderlying(i);
current_active_texture_unit = i;
const TextureUnit& unit_2d = GetTextureUnit(kTexture2d, i);
// If the app was using the unit associated with external textures
// bind that unit's data, otherwise the app was using 2D-related unit.
BindUnderlying(bind_ext_as_2d ? unit_ext : unit_2d);
}
}
if (current_active_texture_unit != active_texture_unit_) {
ActiveTextureUnderlying(active_texture_unit_);
}
}
void TextureContext::RestoreTextures() {
GLuint current_active_texture_unit = active_texture_unit_;
for (GLuint i = 0; i < num_texture_units_; ++i) {
const TextureUnit& unit_ext = GetTextureUnit(kTextureExternal, i);
// If we have an external texture that maps to TEXTURE_2D we simply rebind
// the 2D texture unconditionally rather than duplicate the logic from
// PrepareTextures. Worst case this texture is already bound.
if (unit_ext.global_target == GL_TEXTURE_2D) {
ActiveTextureUnderlying(i);
current_active_texture_unit = i;
BindUnderlying(GetTextureUnit(kTexture2d, i));
}
}
if (current_active_texture_unit != active_texture_unit_) {
ActiveTextureUnderlying(active_texture_unit_);
}
}
void TextureContext::Enable(GLenum target) {
const Target t = MapTarget(target);
TextureUnit& unit = GetTextureUnit(t, active_texture_unit_);
unit.enabled = true;
}
void TextureContext::Disable(GLenum target) {
const Target t = MapTarget(target);
TextureUnit& unit = GetTextureUnit(t, active_texture_unit_);
unit.enabled = false;
}
bool TextureContext::IsEnabled(GLenum target) const {
return IsEnabled(active_texture_unit_ + GL_TEXTURE0, target);
}
bool TextureContext::IsEnabled(GLenum id, GLenum target) const {
const GLuint index = id - GL_TEXTURE0;
LOG_ALWAYS_FATAL_IF(index >= num_texture_units_);
const Target t = MapTarget(target);
const TextureUnit& unit = GetTextureUnit(t, index);
return unit.enabled;
}
GLuint TextureContext::GetBoundTexture(GLenum target) const {
return GetTexture(active_texture_unit_ + GL_TEXTURE0, target);
}
GLuint TextureContext::GetTexture(GLenum id, GLenum target) const {
const GLuint index = id - GL_TEXTURE0;
LOG_ALWAYS_FATAL_IF(index >= num_texture_units_);
const Target t = MapTarget(target);
const TextureUnit& unit = GetTextureUnit(t, index);
return unit.texture;
}
GLenum TextureContext::GetGlobalTarget(GLenum id, GLenum target) const {
const GLuint index = id - GL_TEXTURE0;
LOG_ALWAYS_FATAL_IF(index >= (GLuint)num_texture_units_);
const Target t = MapTarget(target);
const TextureUnit& unit = GetTextureUnit(t, index);
return unit.global_target;
}
TextureContext::TextureUnit&
TextureContext::GetTextureUnit(Target t, GLuint index) {
return texture_units_[t][index];
}
const TextureContext::TextureUnit&
TextureContext::GetTextureUnit(Target t, GLuint index) const {
return texture_units_[t][index];
}
void TextureContext::BindUnderlying(const TextureUnit& unit) {
const GLuint global_name =
context_->GetShareGroup()->GetTextureGlobalName(unit.texture);
const GLenum global_target = unit.global_target;
PASS_THROUGH(context_, BindTexture, global_target, global_name);
}
void TextureContext::ActiveTextureUnderlying(GLuint index) {
const GLenum id = GL_TEXTURE0 + index;
PASS_THROUGH(context_, ActiveTexture, id);
}
bool TextureContext::SetClientActiveTexture(GLenum texture) {
const GLuint texture_unit = texture - GL_TEXTURE0;
if (texture_unit >= num_texture_units_) {
return false;
}
client_active_texture_unit_ = texture_unit;
return true;
}
TextureContext::Target TextureContext::MapTarget(GLenum target) const {
switch (target) {
case GL_TEXTURE_2D:
return kTexture2d;
case GL_TEXTURE_CUBE_MAP:
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
return kTextureCubeMap;
case GL_TEXTURE_EXTERNAL_OES:
return kTextureExternal;
default:
LOG_ALWAYS_FATAL("Unsupported.");
}
}
PointerContext::PointerContext(GlesContext* context)
: context_(context),
array_buffer_(0),
array_buffer_size_(0),
element_array_buffer_(0),
element_array_buffer_size_(0),
disable_gl_fixed_attribs_(emugl::GlesOptions::GLFixedAttribsEnabled()) {
DLOG("Created pointer context for GLES1 context @ %p", context);
}
PointerContext::~PointerContext() {
Release();
}
void PointerContext::Init(int num_pointers) {
DLOG("Initialized pointer context for GLES1 context @ %p", context_);
LOG_ALWAYS_FATAL_IF(array_buffer_ != 0);
pointers_.resize(num_pointers);
GLuint buffers[2];
PASS_THROUGH(context_, GenBuffers, 2, buffers);
array_buffer_ = buffers[0];
element_array_buffer_ = buffers[1];
}
void PointerContext::Release() {
if (array_buffer_) {
GLuint buffers[2] = { array_buffer_, element_array_buffer_ };
PASS_THROUGH(context_, DeleteBuffers, 2, buffers);
array_buffer_ = 0;
element_array_buffer_ = 0;
array_buffer_size_ = 0;
element_array_buffer_size_ = 0;
}
}
void PointerContext::EnableArray(GLuint index) {
if (index < pointers_.size()) {
pointers_[index].enabled = true;
}
}
void PointerContext::DisableArray(GLuint index) {
if (index < pointers_.size()) {
pointers_[index].enabled = false;
}
}
bool PointerContext::IsArrayEnabled(GLuint index) const {
if (index < pointers_.size()) {
return pointers_[index].enabled;
}
return false;
}
void PointerContext::SetPointer(GLuint index, GLint size, GLenum type,
GLsizei stride, const GLvoid* pointer,
GLboolean normalized) {
const GLuint buffer = context_->array_buffer_binding_;
LOG_ALWAYS_FATAL_IF(index >= pointers_.size());
PointerData& ptr = pointers_[index];
ptr.size = size;
ptr.type = type;
ptr.stride = stride;
ptr.normalize = normalized;
ptr.buffer_name = buffer;
ptr.pointer = pointer;
}
const PointerData* PointerContext::GetPointerData(GLuint index) const {
if (index < pointers_.size()) {
return &pointers_[index];
}
return NULL;
}
void PointerContext::SetPointers(const PointerDataVector& pointers) {
LOG_ALWAYS_FATAL_IF(pointers_.size() != pointers.size());
std::copy(pointers.begin(), pointers.end(), pointers_.begin());
}
void PointerContext::PrepareBuffersForDrawArrays(GLint first, GLsizei count) {
BindPointers(first, first + count);
}
const GLvoid* PointerContext::PrepareBuffersForDrawElements(
GLsizei count, GLenum type, const GLvoid* indices) {
BufferDataPtr vbo =
context_->GetBoundTargetBufferData(GL_ELEMENT_ARRAY_BUFFER);
// There is no currently bound element array buffer, so copy the indices
// into the PointerContext's element array buffer object and use that VBO
// for the duration of this draw call.
if (vbo == NULL) {
const size_t size = GetTypeSize(type) * count;
PASS_THROUGH(context_, BindBuffer, GL_ELEMENT_ARRAY_BUFFER,
element_array_buffer_);
if (size > element_array_buffer_size_) {
PASS_THROUGH(context_, BufferData, GL_ELEMENT_ARRAY_BUFFER, size, NULL,
GL_STREAM_DRAW);
element_array_buffer_size_ = size;
}
PASS_THROUGH(context_, BufferSubData, GL_ELEMENT_ARRAY_BUFFER, 0, size,
indices);
}
// If we have any vertex attributes that are client-side, then we need to
// calculate the range of the elements we are drawing so that we can copy the
// client-data into VBOs. The copying of the data is done in BindPointers.
// If we have no client-side vertex attributes, then the range does not
// matter.
bool has_client_vertex_attribs = false;
for (size_t i = 0; i < pointers_.size(); ++i) {
if (!pointers_[i].enabled) {
continue;
}
if (pointers_[i].buffer_name) {
continue;
}
has_client_vertex_attribs = true;
break;
}
if (has_client_vertex_attribs) {
const GLvoid* data = indices;
if (vbo != NULL) {
const unsigned char* buffer_data = vbo->GetData();
LOG_ALWAYS_FATAL_IF(buffer_data == NULL,
"Element array buffers must have data!");
data = buffer_data + reinterpret_cast<uintptr_t>(indices);
}
const ElementRange range = GetElementRange(count, type, data);
BindPointers(range.first, range.last + 1);
} else {
BindPointers(0, 0);
}
// If we have an element array VBO, then use the offset as specified.
// Otherwise we have copied the client-data data into a VBO, so we
// can use a zero (NULL) offset into the VBO instead.
return vbo != NULL ? indices : NULL;
}
void PointerContext::BindPointers(GLint first, GLint last) {
// Calculate data size for needed data in client side arrays.
size_t size = 0;
for (size_t index = 0; index < pointers_.size(); ++index) {
PointerData& ptr = pointers_[index];
if (!ptr.enabled) {
continue;
}
if (disable_gl_fixed_attribs_) {
LOG_ALWAYS_FATAL_IF(ptr.type == GL_FIXED,
"GL_FIXED type attribs not supported.");
}
// The vertex data is already stored in a VBO. Bind the VBO now so
// that we can correctly update the vertex attribute pointer to point
// to it.
if (ptr.buffer_name) {
const ObjectGlobalName global_name =
context_->GetShareGroup()->GetBufferGlobalName(ptr.buffer_name);
PASS_THROUGH(context_, BindBuffer, GL_ARRAY_BUFFER, global_name);
// TODO(crbug.com/482070): Convert any elements in the buffer data that
// are of type GL_FIXED to GL_FLOAT and re-upload that data to the bound
// buffer.
if (ptr.type == GL_FIXED) {
// We should be able to safely assume that ptr.size is [1,4] as
// per the glVertexAttribPointer specs. This allows us to just
// use a local array instead of having to allocate a dynamic
// array for doing the conversion.
LOG_ALWAYS_FATAL_IF(ptr.size > 4);
BufferDataPtr buffer =
context_->GetShareGroup()->GetBufferData(ptr.buffer_name);
const unsigned char* data = buffer->GetData();
const size_t size = buffer->GetSize();
const GLintptr stride =
ptr.stride ? ptr.stride : ptr.size * GetTypeSize(ptr.type);
for (size_t marker = reinterpret_cast<size_t>(ptr.pointer);
marker < size; marker += stride) {
GLfloat dst[4];
const GLfixed* src = reinterpret_cast<const GLfixed*>(&data[marker]);
for (int i = 0; i < ptr.size; ++i) {
dst[i] = X2F(src[i]);
}
PASS_THROUGH(context_, BufferSubData, GL_ARRAY_BUFFER, marker,
ptr.size * sizeof(GLfloat), dst);
}
}
PASS_THROUGH(context_, VertexAttribPointer, index, ptr.size,
ptr.type == GL_FIXED ? GL_FLOAT : ptr.type,
ptr.normalize, ptr.stride, ptr.pointer);
continue;
}
size +=
ptr.stride ? ptr.stride * last : GetTypeSize(ptr.type) * last * ptr.size;
}
// No client side array is used.
if (size == 0) {
return;
}
PASS_THROUGH(context_, BindBuffer, GL_ARRAY_BUFFER, array_buffer_);
if (size > array_buffer_size_) {
PASS_THROUGH(context_, BufferData, GL_ARRAY_BUFFER, size, NULL,
GL_STREAM_DRAW);
array_buffer_size_ = size;
}
size_t offset = 0;
for (size_t index = 0; index < pointers_.size(); ++index) {
PointerData& ptr = pointers_[index];
if (!ptr.enabled) {
continue;
}
if (ptr.buffer_name) {
continue;
}
const size_t stride =
ptr.stride ? ptr.stride : GetTypeSize(ptr.type) * ptr.size;
const size_t offset_first = stride * first;
const size_t offset_last = stride * last;
const unsigned char* data =
reinterpret_cast<const unsigned char*>(ptr.pointer);
// TODO(crbug.com/482070): Convert any elements in the buffer data that are
// of type GL_FIXED to GL_FLOAT before copying it to our buffer object.
if (ptr.type == GL_FIXED) {
unsigned char* tmp = new unsigned char[offset_last];
for (size_t marker = offset_first; marker < offset_last; marker += stride) {
GLfloat* dst = reinterpret_cast<GLfloat*>(&tmp[marker]);
const GLfixed* src = reinterpret_cast<const GLfixed*>(&data[marker]);
for (int i = 0; i < ptr.size; ++i) {
dst[i] = X2F(src[i]);
}
}
data = tmp;
}
PASS_THROUGH(context_, BufferSubData, GL_ARRAY_BUFFER,
offset + offset_first, offset_last - offset_first,
data + offset_first);
PASS_THROUGH(context_, VertexAttribPointer, index, ptr.size,
ptr.type == GL_FIXED ? GL_FLOAT : ptr.type,
ptr.normalize, ptr.stride, reinterpret_cast<void*>(offset));
if (ptr.type == GL_FIXED) {
delete[] data;
}
offset += offset_last;
}
}