| /* |
| * Copyright 2015 Google Inc. All Rights Reserved. |
| * |
| * 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 "glimp/gles/context.h" |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "glimp/egl/error.h" |
| #include "glimp/egl/surface.h" |
| #include "glimp/gles/blend_state.h" |
| #include "glimp/gles/cull_face_state.h" |
| #include "glimp/gles/draw_mode.h" |
| #include "glimp/gles/index_data_type.h" |
| #include "glimp/gles/pixel_format.h" |
| #include "glimp/tracing/tracing.h" |
| #include "nb/pointer_arithmetic.h" |
| #include "starboard/common/log.h" |
| #include "starboard/memory.h" |
| #include "starboard/once.h" |
| |
| namespace glimp { |
| namespace gles { |
| |
| namespace { |
| |
| SbOnceControl s_tls_current_context_key_once_control = SB_ONCE_INITIALIZER; |
| SbThreadLocalKey s_tls_current_context_key = kSbThreadLocalKeyInvalid; |
| |
| void InitializeThreadLocalKey() { |
| s_tls_current_context_key = SbThreadCreateLocalKey(NULL); |
| } |
| |
| SbThreadLocalKey GetThreadLocalKey() { |
| SbOnce(&s_tls_current_context_key_once_control, &InitializeThreadLocalKey); |
| return s_tls_current_context_key; |
| } |
| |
| } // namespace |
| |
| Context::Context(nb::scoped_ptr<ContextImpl> context_impl, |
| Context* share_context) |
| : impl_(context_impl.Pass()), |
| current_thread_(kSbThreadInvalid), |
| has_been_current_(false), |
| active_texture_(GL_TEXTURE0), |
| enabled_textures_dirty_(true), |
| enabled_vertex_attribs_dirty_(true), |
| pack_alignment_(4), |
| unpack_alignment_(4), |
| unpack_row_length_(0), |
| error_(GL_NO_ERROR) { |
| SbAtomicNoBarrier_Store(&has_swapped_buffers_, 0); |
| if (share_context != NULL) { |
| resource_manager_ = share_context->resource_manager_; |
| } else { |
| resource_manager_ = new ResourceManager(); |
| } |
| |
| SetupExtensionsString(); |
| |
| texture_units_.reset( |
| new nb::scoped_refptr<Texture>[impl_->GetMaxFragmentTextureUnits()]); |
| } |
| |
| Context* Context::GetTLSCurrentContext() { |
| return reinterpret_cast<Context*>(SbThreadGetLocalValue(GetThreadLocalKey())); |
| } |
| |
| bool Context::SetTLSCurrentContext(Context* context, |
| egl::Surface* draw, |
| egl::Surface* read) { |
| SB_DCHECK(context); |
| SB_DCHECK(draw); |
| SB_DCHECK(read); |
| |
| if (context->current_thread() != kSbThreadInvalid && |
| context->current_thread() != SbThreadGetCurrent()) { |
| SB_DLOG(WARNING) << "Another thread holds current the context that is to " |
| "be made current on this thread."; |
| egl::SetError(EGL_BAD_ACCESS); |
| return false; |
| } |
| |
| // If this thread currently has another context current, release that one |
| // before we continue. |
| Context* existing_context = GetTLSCurrentContext(); |
| if (existing_context != context) { |
| if (existing_context) { |
| existing_context->ReleaseContext(); |
| } |
| SbThreadSetLocalValue(GetThreadLocalKey(), |
| reinterpret_cast<void*>(context)); |
| } |
| |
| context->MakeCurrent(draw, read); |
| return true; |
| } |
| |
| void Context::ReleaseTLSCurrentContext() { |
| Context* existing_context = GetTLSCurrentContext(); |
| if (existing_context) { |
| existing_context->ReleaseContext(); |
| SbThreadSetLocalValue(GetThreadLocalKey(), NULL); |
| } |
| } |
| |
| GLenum Context::GetError() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| GLenum error = error_; |
| error_ = GL_NO_ERROR; |
| return error; |
| } |
| |
| const GLubyte* Context::GetString(GLenum name) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (name) { |
| case GL_EXTENSIONS: |
| return reinterpret_cast<const GLubyte*>(extensions_string_.c_str()); |
| case GL_VERSION: |
| return reinterpret_cast<const GLubyte*>("OpenGL ES 2.0 (glimp)"); |
| case GL_VENDOR: |
| return reinterpret_cast<const GLubyte*>("Google Inc."); |
| case GL_RENDERER: |
| return reinterpret_cast<const GLubyte*>("glimp"); |
| case GL_SHADING_LANGUAGE_VERSION: |
| return reinterpret_cast<const GLubyte*>("OpenGL ES GLSL ES 1.00"); |
| |
| default: { |
| SetError(GL_INVALID_ENUM); |
| return NULL; |
| } |
| } |
| } |
| |
| void Context::GetIntegerv(GLenum pname, GLint* params) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (pname) { |
| case GL_MAX_TEXTURE_SIZE: |
| *params = impl_->GetMaxTextureSize(); |
| break; |
| case GL_ACTIVE_TEXTURE: |
| *params = static_cast<GLint>(active_texture_); |
| break; |
| case GL_MAX_RENDERBUFFER_SIZE: |
| *params = impl_->GetMaxRenderbufferSize(); |
| break; |
| case GL_NUM_COMPRESSED_TEXTURE_FORMATS: |
| // We don't currently support compressed textures. |
| *params = 0; |
| break; |
| case GL_MAX_VERTEX_ATTRIBS: |
| *params = impl_->GetMaxVertexAttribs(); |
| break; |
| case GL_MAX_TEXTURE_IMAGE_UNITS: |
| *params = impl_->GetMaxFragmentTextureUnits(); |
| break; |
| case GL_MAX_FRAGMENT_UNIFORM_VECTORS: |
| *params = impl_->GetMaxFragmentUniformVectors(); |
| break; |
| case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: |
| *params = impl_->GetMaxVertexTextureImageUnits(); |
| break; |
| case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: |
| *params = impl_->GetMaxCombinedTextureImageUnits(); |
| break; |
| case GL_UNPACK_ALIGNMENT: |
| *params = unpack_alignment_; |
| break; |
| default: { |
| SB_NOTIMPLEMENTED(); |
| SetError(GL_INVALID_ENUM); |
| } |
| } |
| } |
| |
| void Context::GetShaderiv(GLuint shader, GLenum pname, GLint* params) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_refptr<Shader> shader_object = |
| resource_manager_->GetShader(shader); |
| if (!shader_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| GLenum result = shader_object->GetShaderiv(pname, params); |
| if (result != GL_NO_ERROR) { |
| SetError(result); |
| } |
| } |
| |
| void Context::GetShaderInfoLog(GLuint shader, |
| GLsizei bufsize, |
| GLsizei* length, |
| GLchar* infolog) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (bufsize < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| nb::scoped_refptr<Shader> shader_object = |
| resource_manager_->GetShader(shader); |
| if (!shader_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| shader_object->GetShaderInfoLog(bufsize, length, infolog); |
| } |
| |
| void Context::GetProgramiv(GLuint program, GLenum pname, GLint* params) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| GLenum result = program_object->GetProgramiv(pname, params); |
| if (result != GL_NO_ERROR) { |
| SetError(result); |
| } |
| } |
| |
| void Context::GetProgramInfoLog(GLuint program, |
| GLsizei bufsize, |
| GLsizei* length, |
| GLchar* infolog) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (bufsize < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| program_object->GetProgramInfoLog(bufsize, length, infolog); |
| } |
| |
| void Context::PixelStorei(GLenum pname, GLint param) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (pname) { |
| case GL_PACK_ALIGNMENT: |
| case GL_UNPACK_ALIGNMENT: |
| if (param != 1 && param != 4 && param != 8) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| break; |
| |
| default: |
| if (param < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| break; |
| } |
| |
| switch (pname) { |
| case GL_PACK_ALIGNMENT: |
| pack_alignment_ = param; |
| break; |
| case GL_UNPACK_ALIGNMENT: |
| unpack_alignment_ = param; |
| break; |
| case GL_UNPACK_ROW_LENGTH: |
| unpack_row_length_ = param; |
| break; |
| case GL_PACK_ROW_LENGTH: |
| case GL_PACK_SKIP_ROWS: |
| case GL_PACK_SKIP_PIXELS: |
| case GL_UNPACK_IMAGE_HEIGHT: |
| case GL_UNPACK_SKIP_ROWS: |
| case GL_UNPACK_SKIP_PIXELS: |
| case GL_UNPACK_SKIP_IMAGES: |
| SB_NOTIMPLEMENTED(); |
| default: |
| SetError(GL_INVALID_ENUM); |
| break; |
| } |
| } |
| |
| void Context::Enable(GLenum cap) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (cap) { |
| case GL_BLEND: |
| draw_state_.blend_state.enabled = true; |
| draw_state_dirty_flags_.blend_state_dirty = true; |
| break; |
| case GL_SCISSOR_TEST: |
| draw_state_.scissor.enabled = true; |
| draw_state_dirty_flags_.scissor_dirty = true; |
| break; |
| case GL_CULL_FACE: |
| draw_state_.cull_face_state.enabled = true; |
| draw_state_.cull_face_state.mode = CullFaceState::kBack; |
| draw_state_dirty_flags_.cull_face_dirty = true; |
| break; |
| case GL_DEPTH_TEST: |
| case GL_DITHER: |
| case GL_STENCIL_TEST: |
| case GL_POLYGON_OFFSET_FILL: |
| case GL_SAMPLE_ALPHA_TO_COVERAGE: |
| case GL_SAMPLE_COVERAGE: |
| SB_NOTIMPLEMENTED(); |
| default: |
| SetError(GL_INVALID_ENUM); |
| } |
| } |
| |
| void Context::Disable(GLenum cap) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (cap) { |
| case GL_BLEND: |
| draw_state_.blend_state.enabled = false; |
| draw_state_dirty_flags_.blend_state_dirty = true; |
| break; |
| case GL_SCISSOR_TEST: |
| draw_state_.scissor.enabled = false; |
| draw_state_dirty_flags_.scissor_dirty = true; |
| break; |
| case GL_CULL_FACE: |
| draw_state_.cull_face_state.enabled = false; |
| draw_state_dirty_flags_.cull_face_dirty = true; |
| break; |
| case GL_DEPTH_TEST: |
| case GL_DITHER: |
| case GL_STENCIL_TEST: |
| case GL_POLYGON_OFFSET_FILL: |
| case GL_SAMPLE_ALPHA_TO_COVERAGE: |
| case GL_SAMPLE_COVERAGE: |
| // Since these are not implemented yet, it is not an error to do nothing |
| // when we ask for them to be disabled! |
| break; |
| default: |
| SetError(GL_INVALID_ENUM); |
| } |
| } |
| |
| void Context::ColorMask(GLboolean red, |
| GLboolean green, |
| GLboolean blue, |
| GLboolean alpha) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| draw_state_.color_mask = gles::ColorMask(red, green, blue, alpha); |
| draw_state_dirty_flags_.color_mask_dirty = true; |
| } |
| |
| void Context::DepthMask(GLboolean flag) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (flag == GL_TRUE) { |
| SB_NOTIMPLEMENTED() << "glimp currently does not support depth buffers."; |
| SetError(GL_INVALID_OPERATION); |
| } |
| } |
| |
| void Context::Clear(GLbitfield mask) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| impl_->Clear(mask & GL_COLOR_BUFFER_BIT, mask & GL_DEPTH_BUFFER_BIT, |
| mask & GL_STENCIL_BUFFER_BIT, draw_state_, |
| &draw_state_dirty_flags_); |
| } |
| |
| void Context::ClearColor(GLfloat red, |
| GLfloat green, |
| GLfloat blue, |
| GLfloat alpha) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| draw_state_.clear_color = gles::ClearColor(red, green, blue, alpha); |
| draw_state_dirty_flags_.clear_color_dirty = true; |
| } |
| |
| namespace { |
| BlendState::Factor BlendStateFactorFromGLenum(GLenum blend_factor) { |
| switch (blend_factor) { |
| case GL_ZERO: |
| return BlendState::kFactorZero; |
| case GL_ONE: |
| return BlendState::kFactorOne; |
| case GL_SRC_COLOR: |
| return BlendState::kFactorSrcColor; |
| case GL_ONE_MINUS_SRC_COLOR: |
| return BlendState::kFactorOneMinusSrcColor; |
| case GL_DST_COLOR: |
| return BlendState::kFactorDstColor; |
| case GL_ONE_MINUS_DST_COLOR: |
| return BlendState::kFactorOneMinusDstColor; |
| case GL_SRC_ALPHA: |
| return BlendState::kFactorSrcAlpha; |
| case GL_ONE_MINUS_SRC_ALPHA: |
| return BlendState::kFactorOneMinusSrcAlpha; |
| case GL_DST_ALPHA: |
| return BlendState::kFactorDstAlpha; |
| case GL_ONE_MINUS_DST_ALPHA: |
| return BlendState::kFactorOneMinusDstAlpha; |
| case GL_CONSTANT_COLOR: |
| return BlendState::kFactorConstantColor; |
| case GL_ONE_MINUS_CONSTANT_COLOR: |
| return BlendState::kFactorOneMinusConstantColor; |
| case GL_CONSTANT_ALPHA: |
| return BlendState::kFactorConstantAlpha; |
| case GL_ONE_MINUS_CONSTANT_ALPHA: |
| return BlendState::kFactorOneMinusConstantAlpha; |
| case GL_SRC_ALPHA_SATURATE: |
| return BlendState::kFactorSrcAlphaSaturate; |
| default: |
| return BlendState::kFactorInvalid; |
| } |
| } |
| |
| BlendState::Equation BlendStateEquationFromGLenum(GLenum equation) { |
| switch (equation) { |
| case GL_FUNC_ADD: |
| return BlendState::kEquationFuncAdd; |
| case GL_FUNC_SUBTRACT: |
| return BlendState::kEquationFuncSubtract; |
| case GL_FUNC_REVERSE_SUBTRACT: |
| return BlendState::kEquationFuncReverseSubtract; |
| default: |
| return BlendState::kEquationFuncInvalid; |
| } |
| } |
| } // namespace |
| |
| void Context::BlendFunc(GLenum sfactor, GLenum dfactor) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| BlendState::Factor src_factor = BlendStateFactorFromGLenum(sfactor); |
| BlendState::Factor dst_factor = BlendStateFactorFromGLenum(dfactor); |
| if (src_factor == BlendState::kFactorInvalid || |
| dst_factor == BlendState::kFactorInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| draw_state_.blend_state.src_factor = src_factor; |
| draw_state_.blend_state.dst_factor = dst_factor; |
| draw_state_dirty_flags_.blend_state_dirty = true; |
| } |
| |
| void Context::BlendEquation(GLenum mode) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| BlendState::Equation equation = BlendStateEquationFromGLenum(mode); |
| if (equation == BlendState::kEquationFuncInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| draw_state_.blend_state.equation = equation; |
| draw_state_dirty_flags_.blend_state_dirty = true; |
| } |
| |
| namespace { |
| CullFaceState::Mode CullFaceModeFromEnum(GLenum mode) { |
| switch (mode) { |
| case GL_FRONT: |
| return CullFaceState::kFront; |
| case GL_BACK: |
| return CullFaceState::kBack; |
| case GL_FRONT_AND_BACK: |
| return CullFaceState::kFrontAndBack; |
| default: |
| return CullFaceState::kModeInvalid; |
| } |
| } |
| } // namespace |
| |
| void Context::CullFace(GLenum mode) { |
| CullFaceState::Mode cull_face_mode = CullFaceModeFromEnum(mode); |
| if (cull_face_mode == CullFaceState::kModeInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| draw_state_.cull_face_state.mode = cull_face_mode; |
| draw_state_dirty_flags_.cull_face_dirty = true; |
| } |
| |
| GLuint Context::CreateProgram() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_ptr<ProgramImpl> program_impl = impl_->CreateProgram(); |
| SB_DCHECK(program_impl); |
| |
| nb::scoped_refptr<Program> program(new Program(program_impl.Pass())); |
| |
| return resource_manager_->RegisterProgram(program); |
| } |
| |
| void Context::DeleteProgram(GLuint program) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| // As indicated by the specification for glDeleteProgram(), |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteProgram.xml |
| // values of 0 will be silently ignored. |
| if (program == 0) { |
| return; |
| } |
| |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->DeregisterProgram(program); |
| |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| } |
| } |
| |
| void Context::AttachShader(GLuint program, GLuint shader) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| nb::scoped_refptr<Shader> shader_object = |
| resource_manager_->GetShader(shader); |
| if (!shader_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (!program_object->AttachShader(shader_object)) { |
| // A shader of the given type was already attached. |
| SetError(GL_INVALID_OPERATION); |
| } |
| } |
| |
| void Context::LinkProgram(GLuint program) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| program_object->Link(); |
| |
| if (program_object.get() == draw_state_.used_program.get()) { |
| MarkUsedProgramDirty(); |
| } |
| } |
| |
| void Context::BindAttribLocation(GLuint program, |
| GLuint index, |
| const GLchar* name) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (index >= GL_MAX_VERTEX_ATTRIBS) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (name[0] == 'g' && name[1] == 'l' && name[2] == '_') { |
| // |name| is not allowed to begin with the reserved prefix, "gl_". |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| program_object->BindAttribLocation(index, name); |
| |
| if (program_object.get() == draw_state_.used_program.get()) { |
| draw_state_dirty_flags_.vertex_attributes_dirty = true; |
| } |
| } |
| |
| void Context::UseProgram(GLuint program) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (program == 0) { |
| draw_state_.used_program = NULL; |
| MarkUsedProgramDirty(); |
| return; |
| } |
| |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (!program_object->linked()) { |
| // Only linked programs can be used. |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (program_object.get() != draw_state_.used_program.get()) { |
| draw_state_.used_program = program_object; |
| MarkUsedProgramDirty(); |
| } |
| } |
| |
| GLuint Context::CreateShader(GLenum type) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_ptr<ShaderImpl> shader_impl; |
| if (type == GL_VERTEX_SHADER) { |
| shader_impl = impl_->CreateVertexShader(); |
| } else if (type == GL_FRAGMENT_SHADER) { |
| shader_impl = impl_->CreateFragmentShader(); |
| } else { |
| SetError(GL_INVALID_ENUM); |
| return 0; |
| } |
| SB_DCHECK(shader_impl); |
| |
| nb::scoped_refptr<Shader> shader(new Shader(shader_impl.Pass(), type)); |
| |
| return resource_manager_->RegisterShader(shader); |
| } |
| |
| void Context::DeleteShader(GLuint shader) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| // As indicated by the specification for glDeleteShader(), |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteShader.xml |
| // values of 0 will be silently ignored. |
| if (shader == 0) { |
| return; |
| } |
| |
| nb::scoped_refptr<Shader> shader_object = |
| resource_manager_->DeregisterShader(shader); |
| |
| if (!shader_object) { |
| SetError(GL_INVALID_VALUE); |
| } |
| } |
| |
| void Context::ShaderSource(GLuint shader, |
| GLsizei count, |
| const GLchar* const* string, |
| const GLint* length) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (count < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| nb::scoped_refptr<Shader> shader_object = |
| resource_manager_->GetShader(shader); |
| |
| if (!shader_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| shader_object->ShaderSource(count, string, length); |
| } |
| |
| void Context::CompileShader(GLuint shader) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| nb::scoped_refptr<Shader> shader_object = |
| resource_manager_->GetShader(shader); |
| |
| if (!shader_object) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| shader_object->CompileShader(); |
| } |
| |
| void Context::GenBuffers(GLsizei n, GLuint* buffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| nb::scoped_ptr<BufferImpl> buffer_impl = impl_->CreateBuffer(); |
| SB_DCHECK(buffer_impl); |
| |
| nb::scoped_refptr<Buffer> buffer(new Buffer(buffer_impl.Pass())); |
| |
| buffers[i] = resource_manager_->RegisterBuffer(buffer); |
| } |
| } |
| |
| void Context::GenBuffersForVideoFrame(GLsizei n, GLuint* buffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| nb::scoped_ptr<BufferImpl> buffer_impl = impl_->CreateBufferForVideoFrame(); |
| SB_DCHECK(buffer_impl); |
| |
| buffers[i] = resource_manager_->RegisterBuffer( |
| nb::make_scoped_refptr(new Buffer(buffer_impl.Pass()))); |
| } |
| } |
| |
| void Context::FrontFace(GLenum mode) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if ((mode != GL_CW) && (mode != GL_CCW)) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| // The default face is GL_CCW, per documentation at: |
| // https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrontFace.xml |
| if (mode != GL_CCW) { |
| SB_NOTIMPLEMENTED(); |
| } |
| } |
| |
| void Context::DeleteBuffers(GLsizei n, const GLuint* buffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| if (buffers[i] == 0) { |
| // Silently ignore 0 buffers. |
| continue; |
| } |
| |
| nb::scoped_refptr<Buffer> buffer_object = |
| resource_manager_->DeregisterBuffer(buffers[i]); |
| |
| if (!buffer_object) { |
| // The specification does not indicate that any error should be set |
| // in the case that there was an error deleting a specific buffer. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteBuffers.xml |
| return; |
| } |
| |
| if (buffer_object->is_mapped()) { |
| // Buffer objects should be unmapped if they are deleted. |
| // https://www.khronos.org/opengles/sdk/docs/man3/html/glMapBufferRange.xhtml |
| buffer_object->Unmap(); |
| } |
| |
| // If a bound buffer is deleted, set the bound buffer to NULL. The buffer |
| // may be bound to any target, therefore we must scan them all. |
| const GLenum buffer_targets[3] = {GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, |
| GL_PIXEL_UNPACK_BUFFER}; |
| for (int target_index = 0; target_index < SB_ARRAY_SIZE(buffer_targets); |
| ++target_index) { |
| GLenum target = buffer_targets[target_index]; |
| nb::scoped_refptr<Buffer>* bound_buffer = GetBoundBufferForTarget(target); |
| SB_DCHECK(bound_buffer); |
| if ((*bound_buffer).get() == buffer_object.get()) { |
| *bound_buffer = NULL; |
| } |
| } |
| } |
| } |
| |
| namespace { |
| bool IsValidBufferTarget(GLenum target) { |
| switch (target) { |
| case GL_ARRAY_BUFFER: |
| case GL_ELEMENT_ARRAY_BUFFER: |
| case GL_PIXEL_UNPACK_BUFFER: |
| return true; |
| break; |
| case GL_COPY_READ_BUFFER: |
| case GL_COPY_WRITE_BUFFER: |
| case GL_PIXEL_PACK_BUFFER: |
| case GL_TRANSFORM_FEEDBACK_BUFFER: |
| case GL_UNIFORM_BUFFER: |
| SB_NOTIMPLEMENTED() << "Buffer target " << target |
| << " is not supported " |
| "in glimp."; |
| default: |
| return false; |
| } |
| } |
| } // namespace |
| |
| void Context::BindBuffer(GLenum target, GLuint buffer) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (!IsValidBufferTarget(target)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| nb::scoped_refptr<Buffer>* bound_buffer = GetBoundBufferForTarget(target); |
| SB_DCHECK(bound_buffer); |
| nb::scoped_refptr<Buffer> buffer_object; |
| if (buffer != 0) { |
| buffer_object = resource_manager_->GetBuffer(buffer); |
| if (!buffer_object) { |
| // The buffer to be bound is invalid. |
| SB_NOTIMPLEMENTED() |
| << "Creating buffers with glBindBuffer () not supported"; |
| return; |
| } |
| } |
| |
| *bound_buffer = buffer_object; |
| } |
| |
| void Context::BufferData(GLenum target, |
| GLsizeiptr size, |
| const GLvoid* data, |
| GLenum usage) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (size < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (usage != GL_STREAM_DRAW && usage != GL_STATIC_DRAW && |
| usage != GL_DYNAMIC_DRAW) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (!IsValidBufferTarget(target)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| nb::scoped_refptr<Buffer> bound_buffer = *GetBoundBufferForTarget(target); |
| if (bound_buffer == 0) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (bound_buffer->is_mapped()) { |
| // According to the specification, we must unmap the buffer if its data |
| // store is recreated with glBufferData. |
| // https://www.khronos.org/opengles/sdk/docs/man3/html/glMapBufferRange.xhtml |
| bound_buffer->Unmap(); |
| } |
| |
| if (!bound_buffer->Allocate(usage, size)) { |
| SetError(GL_OUT_OF_MEMORY); |
| return; |
| } |
| |
| if (data) { |
| if (!bound_buffer->SetData(0, size, data)) { |
| SetError(GL_OUT_OF_MEMORY); |
| return; |
| } |
| } |
| } |
| |
| void Context::BufferSubData(GLenum target, |
| GLintptr offset, |
| GLsizeiptr size, |
| const GLvoid* data) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (size < 0 || offset < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (!IsValidBufferTarget(target)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| nb::scoped_refptr<Buffer> bound_buffer = *GetBoundBufferForTarget(target); |
| if (bound_buffer == 0) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (offset + size > bound_buffer->size_in_bytes()) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (bound_buffer->is_mapped()) { |
| // According to the specification, we must unmap the buffer if its data |
| // store is recreated with glBufferData. |
| // https://www.khronos.org/opengles/sdk/docs/man3/html/glMapBufferRange.xhtml |
| bound_buffer->Unmap(); |
| } |
| |
| // Nothing in the specification says there should be an error if data |
| // is NULL. |
| if (data) { |
| if (!bound_buffer->SetData(offset, size, data)) { |
| SetError(GL_OUT_OF_MEMORY); |
| return; |
| } |
| } |
| } |
| |
| namespace { |
| // This function is based off of the logic described in the "Errors" section |
| // of the specification: |
| // https://www.khronos.org/opengles/sdk/docs/man3/html/glMapBufferRange.xhtml |
| bool MapBufferRangeAccessFlagsAreValid(GLbitfield access) { |
| if (access & ~(GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | |
| GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | |
| GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_UNSYNCHRONIZED_BIT)) { |
| return false; |
| } |
| |
| if (!(access & (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT))) { |
| return false; |
| } |
| |
| if ((access & GL_MAP_READ_BIT) && |
| (access & (GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | |
| GL_MAP_UNSYNCHRONIZED_BIT))) { |
| return false; |
| } |
| |
| if ((access & GL_MAP_FLUSH_EXPLICIT_BIT) && !(access & GL_MAP_WRITE_BIT)) { |
| return false; |
| } |
| |
| return true; |
| } |
| } // namespace |
| |
| void* Context::MapBufferRange(GLenum target, |
| GLintptr offset, |
| GLsizeiptr length, |
| GLbitfield access) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (!IsValidBufferTarget(target)) { |
| SetError(GL_INVALID_ENUM); |
| return NULL; |
| } |
| |
| nb::scoped_refptr<Buffer> bound_buffer = *GetBoundBufferForTarget(target); |
| if (bound_buffer == 0) { |
| SetError(GL_INVALID_OPERATION); |
| return NULL; |
| } |
| |
| if (offset < 0 || length < 0 || |
| offset + length > bound_buffer->size_in_bytes()) { |
| SetError(GL_INVALID_VALUE); |
| return NULL; |
| } |
| |
| if (bound_buffer->is_mapped()) { |
| SetError(GL_INVALID_OPERATION); |
| return NULL; |
| } |
| |
| if (!MapBufferRangeAccessFlagsAreValid(access)) { |
| SetError(GL_INVALID_OPERATION); |
| return NULL; |
| } |
| |
| SB_DCHECK(access & GL_MAP_INVALIDATE_BUFFER_BIT) |
| << "glimp requires the GL_MAP_INVALIDATE_BUFFER_BIT flag to be set."; |
| SB_DCHECK(access & GL_MAP_UNSYNCHRONIZED_BIT) |
| << "glimp requres the GL_MAP_UNSYNCHRONIZED_BIT flag to be set."; |
| SB_DCHECK(!(access & GL_MAP_FLUSH_EXPLICIT_BIT)) |
| << "glimp does not support the GL_MAP_FLUSH_EXPLICIT_BIT flag."; |
| SB_DCHECK(length == bound_buffer->size_in_bytes()) |
| << "glimp only supports mapping the entire buffer."; |
| |
| void* mapped = bound_buffer->Map(); |
| if (!mapped) { |
| SetError(GL_OUT_OF_MEMORY); |
| } |
| |
| return mapped; |
| } |
| |
| bool Context::UnmapBuffer(GLenum target) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (!IsValidBufferTarget(target)) { |
| SetError(GL_INVALID_ENUM); |
| return GL_FALSE; |
| } |
| |
| nb::scoped_refptr<Buffer> bound_buffer = *GetBoundBufferForTarget(target); |
| if (bound_buffer == 0) { |
| SetError(GL_INVALID_OPERATION); |
| return GL_FALSE; |
| } |
| |
| if (bound_buffer->is_mapped()) { |
| return bound_buffer->Unmap(); |
| } else { |
| // The specification is unclear on what to do in the case where the buffer |
| // was not mapped to begin with, so we return GL_FALSE in this case. |
| // https://www.khronos.org/opengles/sdk/docs/man3/html/glMapBufferRange.xhtml |
| return GL_FALSE; |
| } |
| } |
| |
| void Context::LineWidth(GLfloat width) { |
| if (width <= 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| const GLfloat kDefaultLineWidth = 1.0f; |
| if (fabs(width - kDefaultLineWidth) > 0.01f * kDefaultLineWidth) { |
| SB_NOTIMPLEMENTED(); |
| } |
| } |
| |
| void Context::MakeCurrent(egl::Surface* draw, egl::Surface* read) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(current_thread_ == kSbThreadInvalid || |
| current_thread_ == SbThreadGetCurrent()); |
| |
| current_thread_ = SbThreadGetCurrent(); |
| |
| if (!has_been_current_) { |
| // According to the documentation for eglMakeCurrent(), |
| // https://www.khronos.org/registry/egl/sdk/docs/man/html/eglMakeCurrent.xhtml |
| // we should set the scissor and viewport to the draw surface the first |
| // time this context is made current. |
| Scissor(0, 0, draw->impl()->GetWidth(), draw->impl()->GetHeight()); |
| Viewport(0, 0, draw->impl()->GetWidth(), draw->impl()->GetHeight()); |
| |
| // Setup the default framebuffers and bind them. |
| SB_DCHECK(!default_draw_framebuffer_); |
| SB_DCHECK(!default_read_framebuffer_); |
| SB_DCHECK(!draw_state_.framebuffer); |
| default_draw_framebuffer_ = new Framebuffer(draw); |
| default_read_framebuffer_ = new Framebuffer(read); |
| draw_state_.framebuffer = default_draw_framebuffer_; |
| read_framebuffer_ = default_read_framebuffer_; |
| |
| has_been_current_ = true; |
| } |
| |
| // Update our draw and read framebuffers, marking the framebuffer dirty |
| // flag if the default framebuffer is the one that is currently bound. |
| if (default_draw_framebuffer_->color_attachment_surface() != draw) { |
| default_draw_framebuffer_->UpdateColorSurface(draw); |
| if (draw_state_.framebuffer == default_draw_framebuffer_) { |
| draw_state_dirty_flags_.framebuffer_dirty = true; |
| } |
| } |
| if (default_read_framebuffer_->color_attachment_surface() != read) { |
| default_read_framebuffer_->UpdateColorSurface(read); |
| } |
| } |
| |
| void Context::ReleaseContext() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(current_thread_ != kSbThreadInvalid); |
| SB_DCHECK(current_thread_ == SbThreadGetCurrent()); |
| SB_DCHECK(has_been_current_); |
| |
| current_thread_ = kSbThreadInvalid; |
| } |
| |
| nb::scoped_refptr<Buffer>* Context::GetBoundBufferForTarget(GLenum target) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (target) { |
| case GL_ARRAY_BUFFER: |
| draw_state_dirty_flags_.array_buffer_dirty = true; |
| return &draw_state_.array_buffer; |
| case GL_ELEMENT_ARRAY_BUFFER: |
| draw_state_dirty_flags_.element_array_buffer_dirty = true; |
| return &draw_state_.element_array_buffer; |
| case GL_PIXEL_UNPACK_BUFFER: |
| return &bound_pixel_unpack_buffer_; |
| } |
| |
| SB_NOTREACHED(); |
| return NULL; |
| } |
| |
| nb::scoped_refptr<Texture>* Context::GetBoundTextureForTarget(GLenum target, |
| GLenum texture) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| switch (target) { |
| case GL_TEXTURE_2D: |
| return &(texture_units_[texture - GL_TEXTURE0]); |
| case GL_TEXTURE_CUBE_MAP: |
| SB_NOTREACHED() << "Currently unimplemented in glimp."; |
| return NULL; |
| } |
| |
| SB_NOTREACHED(); |
| return NULL; |
| } |
| |
| void Context::SetupExtensionsString() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| // Extract the list of extensions from the platform-specific implementation |
| // and then turn them into a string. |
| ContextImpl::ExtensionList impl_extensions = impl_->GetExtensions(); |
| |
| extensions_string_ = ""; |
| for (int i = 0; i < impl_extensions.size(); ++i) { |
| if (i > 0) { |
| extensions_string_ += " "; |
| } |
| extensions_string_ += impl_extensions[i]; |
| } |
| |
| // Since extensions_string_ will eventually be returned as an array of |
| // unsigned chars, make sure that none of the characters in it are negative. |
| for (int i = 0; i < extensions_string_.size(); ++i) { |
| SB_DCHECK(extensions_string_[i] > 0); |
| } |
| } |
| |
| void Context::GenTextures(GLsizei n, GLuint* textures) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| nb::scoped_ptr<TextureImpl> texture_impl = impl_->CreateTexture(); |
| SB_DCHECK(texture_impl); |
| |
| nb::scoped_refptr<Texture> texture(new Texture(texture_impl.Pass())); |
| |
| textures[i] = resource_manager_->RegisterTexture(texture); |
| } |
| } |
| |
| void Context::DeleteTextures(GLsizei n, const GLuint* textures) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| if (textures[i] == 0) { |
| // Silently ignore 0 textures. |
| continue; |
| } |
| nb::scoped_refptr<Texture> texture_object = |
| resource_manager_->DeregisterTexture(textures[i]); |
| |
| if (!texture_object) { |
| // The specification does not indicate that any error should be set |
| // in the case that there was an error deleting a specific texture. |
| // https://www.khronos.org/opengles/sdk/1.1/docs/man/glDeleteTextures.xml |
| return; |
| } |
| |
| // If a bound texture is deleted, set the bound texture to NULL. The texture |
| // may be bound to multiple texture units, including texture units that are |
| // not active, therefore we must scan them all. |
| for (int texture_index = 0; |
| texture_index < impl_->GetMaxFragmentTextureUnits(); ++texture_index) { |
| GLenum texture_unit = texture_index + GL_TEXTURE0; |
| nb::scoped_refptr<Texture>* bound_texture = |
| GetBoundTextureForTarget(GL_TEXTURE_2D, texture_unit); |
| if ((*bound_texture).get() == texture_object.get()) { |
| enabled_textures_dirty_ = true; |
| *bound_texture = NULL; |
| } |
| } |
| } |
| } |
| |
| void Context::ActiveTexture(GLenum texture) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (texture < GL_TEXTURE0 || |
| texture >= GL_TEXTURE0 + impl_->GetMaxFragmentTextureUnits()) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| active_texture_ = texture; |
| } |
| |
| void Context::BindTexture(GLenum target, GLuint texture) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_TEXTURE_2D && target != GL_TEXTURE_CUBE_MAP) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| nb::scoped_refptr<Texture>* bound_texture = |
| GetBoundTextureForTarget(target, active_texture_); |
| SB_DCHECK(bound_texture); |
| nb::scoped_refptr<Texture> texture_object; |
| if (texture != 0) { |
| texture_object = resource_manager_->GetTexture(texture); |
| if (!texture_object) { |
| // The texture to be bound is invalid. |
| SB_NOTIMPLEMENTED() |
| << "Creating textures with glBindTexture() not supported"; |
| return; |
| } |
| } |
| |
| if ((*bound_texture).get() == texture_object.get()) { |
| // The new texture being bound is the same as the already the bound |
| // texture. |
| return; |
| } |
| *bound_texture = texture_object; |
| enabled_textures_dirty_ = true; |
| } |
| |
| namespace { |
| Sampler::MinFilter MinFilterFromGLEnum(GLenum min_filter) { |
| switch (min_filter) { |
| case GL_NEAREST: |
| return Sampler::kMinFilterNearest; |
| case GL_LINEAR: |
| return Sampler::kMinFilterLinear; |
| case GL_NEAREST_MIPMAP_NEAREST: |
| return Sampler::kMinFilterNearestMipMapNearest; |
| case GL_NEAREST_MIPMAP_LINEAR: |
| return Sampler::kMinFilterNearestMipMapLinear; |
| case GL_LINEAR_MIPMAP_NEAREST: |
| return Sampler::kMinFilterLinearMipMapNearest; |
| case GL_LINEAR_MIPMAP_LINEAR: |
| return Sampler::kMinFilterLinearMipMapLinear; |
| default: |
| return Sampler::kMinFilterInvalid; |
| } |
| } |
| |
| Sampler::MagFilter MagFilterFromGLEnum(GLenum mag_filter) { |
| switch (mag_filter) { |
| case GL_NEAREST: |
| return Sampler::kMagFilterNearest; |
| case GL_LINEAR: |
| return Sampler::kMagFilterLinear; |
| default: |
| return Sampler::kMagFilterInvalid; |
| } |
| } |
| |
| Sampler::WrapMode WrapModeFromGLEnum(GLenum wrap_mode) { |
| switch (wrap_mode) { |
| case GL_CLAMP_TO_EDGE: |
| return Sampler::kWrapModeClampToEdge; |
| case GL_MIRRORED_REPEAT: |
| return Sampler::kWrapModeMirroredRepeat; |
| case GL_REPEAT: |
| return Sampler::kWrapModeRepeat; |
| default: |
| return Sampler::kWrapModeInvalid; |
| } |
| } |
| |
| GLenum GLEnumFromMinFilter(Sampler::MinFilter min_filter) { |
| switch (min_filter) { |
| case Sampler::kMinFilterNearest: |
| return GL_NEAREST; |
| case Sampler::kMinFilterLinear: |
| return GL_LINEAR; |
| case Sampler::kMinFilterNearestMipMapNearest: |
| return GL_NEAREST_MIPMAP_NEAREST; |
| case Sampler::kMinFilterNearestMipMapLinear: |
| return GL_NEAREST_MIPMAP_LINEAR; |
| case Sampler::kMinFilterLinearMipMapNearest: |
| return GL_LINEAR_MIPMAP_NEAREST; |
| case Sampler::kMinFilterLinearMipMapLinear: |
| return GL_LINEAR_MIPMAP_LINEAR; |
| default: { |
| SB_NOTREACHED(); |
| return GL_LINEAR; |
| } |
| } |
| } |
| |
| GLenum GLEnumFromMagFilter(Sampler::MagFilter mag_filter) { |
| switch (mag_filter) { |
| case Sampler::kMagFilterNearest: |
| return GL_NEAREST; |
| case Sampler::kMagFilterLinear: |
| return GL_LINEAR; |
| default: { |
| SB_NOTREACHED(); |
| return GL_LINEAR; |
| } |
| } |
| } |
| |
| GLenum GLEnumFromWrapMode(Sampler::WrapMode wrap_mode) { |
| switch (wrap_mode) { |
| case Sampler::kWrapModeClampToEdge: |
| return GL_CLAMP_TO_EDGE; |
| case Sampler::kWrapModeMirroredRepeat: |
| return GL_MIRRORED_REPEAT; |
| case Sampler::kWrapModeRepeat: |
| return GL_REPEAT; |
| default: { |
| SB_NOTREACHED(); |
| return GL_REPEAT; |
| } |
| } |
| } |
| } // namespace |
| |
| void Context::GetTexParameteriv(GLenum target, GLenum pname, GLint* params) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| Sampler* active_sampler = (*GetBoundTextureForTarget(target, active_texture_)) |
| ->sampler_parameters(); |
| switch (pname) { |
| case GL_TEXTURE_MAG_FILTER: { |
| *params = GLEnumFromMagFilter(active_sampler->mag_filter); |
| } break; |
| case GL_TEXTURE_MIN_FILTER: { |
| *params = GLEnumFromMinFilter(active_sampler->min_filter); |
| } break; |
| case GL_TEXTURE_WRAP_S: { |
| *params = GLEnumFromWrapMode(active_sampler->wrap_s); |
| } break; |
| case GL_TEXTURE_WRAP_T: { |
| *params = GLEnumFromWrapMode(active_sampler->wrap_t); |
| } break; |
| |
| default: { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| } |
| } |
| |
| void Context::TexParameteri(GLenum target, GLenum pname, GLint param) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| Sampler* active_sampler = (*GetBoundTextureForTarget(target, active_texture_)) |
| ->sampler_parameters(); |
| |
| switch (pname) { |
| case GL_TEXTURE_MAG_FILTER: { |
| Sampler::MagFilter mag_filter = MagFilterFromGLEnum(param); |
| if (mag_filter == Sampler::kMagFilterInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| active_sampler->mag_filter = mag_filter; |
| } break; |
| case GL_TEXTURE_MIN_FILTER: { |
| Sampler::MinFilter min_filter = MinFilterFromGLEnum(param); |
| if (min_filter == Sampler::kMinFilterInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| active_sampler->min_filter = min_filter; |
| } break; |
| case GL_TEXTURE_WRAP_S: |
| case GL_TEXTURE_WRAP_T: { |
| Sampler::WrapMode wrap_mode = WrapModeFromGLEnum(param); |
| if (wrap_mode == Sampler::kWrapModeInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (pname == GL_TEXTURE_WRAP_S) { |
| active_sampler->wrap_s = wrap_mode; |
| } else { |
| SB_DCHECK(pname == GL_TEXTURE_WRAP_T); |
| active_sampler->wrap_t = wrap_mode; |
| } |
| } break; |
| |
| default: { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| } |
| |
| enabled_textures_dirty_ = true; |
| } |
| |
| namespace { |
| |
| bool TextureFormatIsValid(GLenum format) { |
| switch (format) { |
| case GL_ALPHA: |
| case GL_RGB: |
| case GL_RGBA: |
| case GL_LUMINANCE: |
| case GL_LUMINANCE_ALPHA: |
| case GL_RED_INTEGER: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool TextureTypeIsValid(GLenum type) { |
| switch (type) { |
| case GL_UNSIGNED_BYTE: |
| case GL_UNSIGNED_SHORT: |
| case GL_UNSIGNED_SHORT_5_6_5: |
| case GL_UNSIGNED_SHORT_4_4_4_4: |
| case GL_UNSIGNED_SHORT_5_5_5_1: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Converts a GL type and format to a glimp PixelFormat. Information about |
| // the different possible values for type and format can be found here: |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml |
| // Note that glimp may not support all possible formats described above. |
| PixelFormat PixelFormatFromGLTypeAndFormat(GLenum format, GLenum type) { |
| if (type == GL_UNSIGNED_BYTE) { |
| switch (format) { |
| case GL_RGBA: |
| return kPixelFormatRGBA8; |
| case GL_ALPHA: |
| return kPixelFormatA8; |
| case GL_LUMINANCE_ALPHA: |
| return kPixelFormatBA8; |
| } |
| } else if (type == GL_UNSIGNED_SHORT && format == GL_RED_INTEGER) { |
| return kPixelFormatA16; |
| } |
| return kPixelFormatInvalid; |
| } |
| |
| } // namespace |
| |
| void Context::TexImage2D(GLenum target, |
| GLint level, |
| GLint internalformat, |
| GLsizei width, |
| GLsizei height, |
| GLint border, |
| GLenum format, |
| GLenum type, |
| const GLvoid* pixels) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_TEXTURE_2D) { |
| SB_NOTREACHED() << "Only target=GL_TEXTURE_2D is supported in glimp."; |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (width < 0 || height < 0 || level < 0 || border != 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| int max_texture_size = impl_->GetMaxTextureSize(); |
| if (width > max_texture_size || height > max_texture_size) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (format != internalformat) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (!TextureFormatIsValid(format)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (!TextureTypeIsValid(type)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| // Fold format and type together to determine a single glimp PixelFormat |
| // value for the incoming data. |
| PixelFormat pixel_format = PixelFormatFromGLTypeAndFormat(format, type); |
| SB_DCHECK(pixel_format != kPixelFormatInvalid) |
| << "Pixel format not supported by glimp."; |
| |
| nb::scoped_refptr<Texture> texture_object = |
| *GetBoundTextureForTarget(target, active_texture_); |
| if (!texture_object) { |
| // According to the specification, no error is generated if no texture |
| // is bound. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexImage2D.xml |
| return; |
| } |
| |
| // The incoming pixel data should be aligned as the client has specified |
| // that it will be. |
| SB_DCHECK(nb::IsAligned(pixels, static_cast<size_t>(unpack_alignment_))); |
| |
| // Determine pitch taking into account glPixelStorei() settings. |
| int pitch_in_bytes = GetPitchForTextureData(width, pixel_format); |
| |
| texture_object->Initialize(level, pixel_format, width, height); |
| |
| if (bound_pixel_unpack_buffer_) { |
| if (bound_pixel_unpack_buffer_->is_mapped() || |
| height * pitch_in_bytes > bound_pixel_unpack_buffer_->size_in_bytes()) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| texture_object->UpdateDataFromBuffer( |
| level, 0, 0, width, height, pitch_in_bytes, bound_pixel_unpack_buffer_, |
| nb::AsInteger(pixels)); |
| } else if (pixels) { |
| if (!texture_object->UpdateData(level, 0, 0, width, height, pitch_in_bytes, |
| pixels)) { |
| SetError(GL_OUT_OF_MEMORY); |
| } |
| } |
| } |
| |
| void Context::TexSubImage2D(GLenum target, |
| GLint level, |
| GLint xoffset, |
| GLint yoffset, |
| GLsizei width, |
| GLsizei height, |
| GLenum format, |
| GLenum type, |
| const GLvoid* pixels) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_TEXTURE_2D) { |
| SB_NOTREACHED() << "Only target=GL_TEXTURE_2D is supported in glimp."; |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (width < 0 || height < 0 || level < 0 || xoffset < 0 || yoffset < 0) { |
| SetError(GL_INVALID_VALUE); |
| } |
| |
| if (!TextureFormatIsValid(format)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (!TextureTypeIsValid(type)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| PixelFormat pixel_format = PixelFormatFromGLTypeAndFormat(format, type); |
| SB_DCHECK(pixel_format != kPixelFormatInvalid) |
| << "Pixel format not supported by glimp."; |
| |
| nb::scoped_refptr<Texture> texture_object = |
| *GetBoundTextureForTarget(target, active_texture_); |
| if (!texture_object) { |
| // According to the specification, no error is generated if no texture |
| // is bound. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glTexSubImage2D.xml |
| return; |
| } |
| |
| if (!texture_object->texture_allocated() || |
| pixel_format != texture_object->pixel_format()) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (xoffset + width > texture_object->width() || |
| yoffset + height > texture_object->height()) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| // The incoming pixel data should be aligned as the client has specified |
| // that it will be. |
| SB_DCHECK(nb::IsAligned(pixels, static_cast<size_t>(unpack_alignment_))); |
| |
| // Determine pitch taking into account glPixelStorei() settings. |
| int pitch_in_bytes = GetPitchForTextureData(width, pixel_format); |
| |
| if (bound_pixel_unpack_buffer_) { |
| if (bound_pixel_unpack_buffer_->is_mapped() || |
| height * pitch_in_bytes > bound_pixel_unpack_buffer_->size_in_bytes()) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| texture_object->UpdateDataFromBuffer( |
| level, xoffset, yoffset, width, height, pitch_in_bytes, |
| bound_pixel_unpack_buffer_, nb::AsInteger(pixels)); |
| } else { |
| if (!texture_object->UpdateData(level, xoffset, yoffset, width, height, |
| pitch_in_bytes, pixels)) { |
| SetError(GL_OUT_OF_MEMORY); |
| } |
| } |
| } |
| |
| void Context::CopyTexSubImage2D(GLenum target, |
| GLint level, |
| GLint xoffset, |
| GLint yoffset, |
| GLint x, |
| GLint y, |
| GLsizei width, |
| GLsizei height) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_TEXTURE_2D) { |
| SB_NOTREACHED() << "Only target=GL_TEXTURE_2D is supported in glimp."; |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (width < 0 || height < 0 || level < 0 || xoffset < 0 || yoffset < 0) { |
| SetError(GL_INVALID_VALUE); |
| } |
| |
| nb::scoped_refptr<Texture> texture_object = |
| *GetBoundTextureForTarget(target, active_texture_); |
| if (!texture_object) { |
| // According to the specification, no error is generated if no texture |
| // is bound. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glCopyTexSubImage2D.xml |
| return; |
| } |
| |
| if (!texture_object->texture_allocated()) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (xoffset + width > texture_object->width() || |
| yoffset + height > texture_object->height()) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (read_framebuffer_->CheckFramebufferStatus() != GL_FRAMEBUFFER_COMPLETE) { |
| SetError(GL_INVALID_FRAMEBUFFER_OPERATION); |
| return; |
| } |
| |
| // The pixels in the rectangle are processed exactly as if glReadPixels had |
| // been called with format set to GL_RGBA, but the process stops just after |
| // conversion of RGBA values. Subsequent processing is identical to that |
| // described for glTexSubImage2D. |
| uint8_t pixels[read_framebuffer_->GetWidth() * |
| read_framebuffer_->GetHeight() * |
| BytesPerPixel(kPixelFormatRGBA8)]; |
| ReadPixels(0, 0, read_framebuffer_->GetWidth(), |
| read_framebuffer_->GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, |
| &pixels); |
| |
| // If any of the pixels within the specified rectangle are outside the |
| // framebuffer associated with the current rendering context, then the values |
| // obtained for those pixels are undefined. Make sure that we only access |
| // pixels within a valid range. |
| x = std::max(0, x); |
| y = std::max(0, y); |
| width = std::min(read_framebuffer_->GetWidth() - x, width); |
| height = std::min(read_framebuffer_->GetHeight() - y, height); |
| int pitch_in_bytes = |
| read_framebuffer_->GetWidth() * BytesPerPixel(kPixelFormatRGBA8); |
| if (!texture_object->UpdateData( |
| level, xoffset, yoffset, width, height, pitch_in_bytes, |
| &pixels[y * pitch_in_bytes + x * BytesPerPixel(kPixelFormatRGBA8)])) { |
| SetError(GL_OUT_OF_MEMORY); |
| } |
| } |
| |
| void Context::GenFramebuffers(GLsizei n, GLuint* framebuffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| nb::scoped_refptr<Framebuffer> framebuffer(new Framebuffer()); |
| |
| framebuffers[i] = resource_manager_->RegisterFramebuffer(framebuffer); |
| } |
| } |
| |
| void Context::DeleteFramebuffers(GLsizei n, const GLuint* framebuffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| if (framebuffers[i] == 0) { |
| // Silently ignore 0 framebuffers. |
| continue; |
| } |
| |
| nb::scoped_refptr<Framebuffer> framebuffer_object = |
| resource_manager_->DeregisterFramebuffer(framebuffers[i]); |
| |
| if (!framebuffer_object) { |
| // The specification does not indicate that any error should be set |
| // in the case that there was an error deleting a specific framebuffer. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteFramebuffers.xml |
| return; |
| } |
| |
| // If a bound framebuffer is deleted, set the bound framebuffer back to |
| // the default framebuffer. |
| if (framebuffer_object == draw_state_.framebuffer) { |
| SetBoundDrawFramebufferToDefault(); |
| } |
| if (framebuffer_object == read_framebuffer_) { |
| SetBoundReadFramebufferToDefault(); |
| } |
| } |
| } |
| |
| void Context::BindFramebuffer(GLenum target, GLuint framebuffer) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_FRAMEBUFFER) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (framebuffer == 0) { |
| SetBoundDrawFramebufferToDefault(); |
| SetBoundReadFramebufferToDefault(); |
| return; |
| } |
| |
| nb::scoped_refptr<Framebuffer> framebuffer_object = |
| resource_manager_->GetFramebuffer(framebuffer); |
| |
| if (!framebuffer_object) { |
| // According to the specification, no error is generated if the buffer is |
| // invalid. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindFramebuffer.xml |
| SB_DLOG(WARNING) << "Could not glBindFramebuffer() to invalid framebuffer."; |
| return; |
| } |
| |
| draw_state_.framebuffer = framebuffer_object; |
| draw_state_dirty_flags_.framebuffer_dirty = true; |
| |
| read_framebuffer_ = framebuffer_object; |
| } |
| |
| void Context::FramebufferTexture2D(GLenum target, |
| GLenum attachment, |
| GLenum textarget, |
| GLuint texture, |
| GLint level) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_FRAMEBUFFER) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (IsDefaultDrawFramebufferBound() || IsDefaultReadFramebufferBound()) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| if (textarget != GL_TEXTURE_2D) { |
| SB_NOTREACHED() << "Only textarget=GL_TEXTURE_2D is supported in glimp."; |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (attachment != GL_COLOR_ATTACHMENT0) { |
| SB_NOTREACHED() |
| << "Only attachment=GL_COLOR_ATTACHMENT0 is supported in glimp."; |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| nb::scoped_refptr<Texture> texture_object; |
| if (texture != 0) { |
| texture_object = resource_manager_->GetTexture(texture); |
| if (!texture_object) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| } |
| |
| draw_state_.framebuffer->AttachTexture2D(texture_object, level); |
| } |
| |
| GLenum Context::CheckFramebufferStatus(GLenum target) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_FRAMEBUFFER) { |
| SetError(GL_INVALID_ENUM); |
| return 0; |
| } |
| |
| return draw_state_.framebuffer->CheckFramebufferStatus(); |
| } |
| |
| void Context::FramebufferRenderbuffer(GLenum target, |
| GLenum attachment, |
| GLenum renderbuffertarget, |
| GLuint renderbuffer) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_FRAMEBUFFER || renderbuffertarget != GL_RENDERBUFFER) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| SB_DCHECK(attachment != GL_COLOR_ATTACHMENT0) |
| << "glimp does not support attaching color renderbuffers to " |
| "framebuffers."; |
| |
| if (IsDefaultDrawFramebufferBound()) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| nb::scoped_refptr<Renderbuffer> renderbuffer_object = NULL; |
| |
| // Resolve the actual render buffer object to bind if we are not binding |
| // render buffer 0, in which case we leave the value to set as NULL. |
| if (renderbuffer != 0) { |
| renderbuffer_object = resource_manager_->GetRenderbuffer(renderbuffer); |
| |
| if (!renderbuffer_object) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| } |
| |
| switch (attachment) { |
| case GL_DEPTH_ATTACHMENT: |
| draw_state_.framebuffer->SetDepthAttachment(renderbuffer_object); |
| break; |
| case GL_STENCIL_ATTACHMENT: |
| draw_state_.framebuffer->SetStencilAttachment(renderbuffer_object); |
| break; |
| default: |
| SetError(GL_INVALID_ENUM); |
| } |
| } |
| |
| void Context::GenRenderbuffers(GLsizei n, GLuint* renderbuffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| nb::scoped_refptr<Renderbuffer> renderbuffer(new Renderbuffer()); |
| |
| renderbuffers[i] = resource_manager_->RegisterRenderbuffer(renderbuffer); |
| } |
| } |
| |
| void Context::DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (n < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| for (GLsizei i = 0; i < n; ++i) { |
| if (renderbuffers[i] == 0) { |
| // Silently ignore 0 renderbuffers. |
| continue; |
| } |
| |
| nb::scoped_refptr<Renderbuffer> renderbuffer_object = |
| resource_manager_->DeregisterRenderbuffer(renderbuffers[i]); |
| |
| if (!renderbuffer_object) { |
| // The specification does not indicate that any error should be set |
| // in the case that there was an error deleting a specific renderbuffer. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteRenderbuffers.xml |
| return; |
| } |
| |
| // If we're deleting the currently bound renderbuffer, set the currently |
| // bound render buffer to NULL. |
| if (renderbuffer_object == bound_renderbuffer_) { |
| bound_renderbuffer_ = NULL; |
| } |
| } |
| } |
| |
| void Context::BindRenderbuffer(GLenum target, GLuint renderbuffer) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_RENDERBUFFER) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (renderbuffer == 0) { |
| bound_renderbuffer_ = NULL; |
| return; |
| } |
| |
| nb::scoped_refptr<Renderbuffer> renderbuffer_object = |
| resource_manager_->GetRenderbuffer(renderbuffer); |
| |
| if (!renderbuffer_object) { |
| // According to the specification, no error is generated if the buffer is |
| // invalid. |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindRenderbuffer.xml |
| SB_DLOG(WARNING) |
| << "Could not glBindRenderbuffer() to invalid renderbuffer."; |
| return; |
| } |
| |
| bound_renderbuffer_ = renderbuffer_object; |
| } |
| |
| namespace { |
| // Valid formats as listed here: |
| // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml |
| bool RenderbufferStorageFormatIsValid(GLenum internalformat) { |
| switch (internalformat) { |
| case GL_RGBA4: |
| case GL_RGB565: |
| case GL_RGB5_A1: |
| case GL_DEPTH_COMPONENT16: |
| case GL_STENCIL_INDEX8: |
| return true; |
| default: |
| return false; |
| } |
| } |
| } // namespace |
| |
| void Context::RenderbufferStorage(GLenum target, |
| GLenum internalformat, |
| GLsizei width, |
| GLsizei height) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (target != GL_RENDERBUFFER) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (!RenderbufferStorageFormatIsValid(internalformat)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| if (width < 0 || height < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (bound_renderbuffer_ == 0) { |
| SetError(GL_INVALID_OPERATION); |
| return; |
| } |
| |
| bound_renderbuffer_->Initialize(internalformat, width, height); |
| } |
| |
| void Context::StencilMask(GLuint mask) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (mask != 0xFFFFFFFF) { |
| // If we are not setting stencil mask to its initial value then indicate |
| // that our implementation is lacking. |
| SB_NOTIMPLEMENTED(); |
| } |
| } |
| |
| void Context::ClearStencil(GLint s) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (s != 0) { |
| // If we are not setting stencil clear to its initial value then indicate |
| // that our implementation is lacking. |
| SB_NOTIMPLEMENTED(); |
| } |
| } |
| |
| void Context::Viewport(GLint x, GLint y, GLsizei width, GLsizei height) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| draw_state_.viewport.rect = nb::Rect<int>(x, y, width, height); |
| draw_state_dirty_flags_.viewport_dirty = true; |
| } |
| |
| void Context::Scissor(GLint x, GLint y, GLsizei width, GLsizei height) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (x < 0) { |
| SB_DLOG(WARNING) << "glScissor() x coordinate is set to negative."; |
| } |
| if (y < 0) { |
| SB_DLOG(WARNING) << "glScissor() y coordinate is set to negative."; |
| } |
| draw_state_.scissor.rect = nb::Rect<int>(x, y, width, height); |
| draw_state_dirty_flags_.scissor_dirty = true; |
| } |
| |
| namespace { |
| // Converts from the GLenum passed into glVertexAttribPointer() to the enum |
| // defined in VertexAttribute. |
| static VertexAttributeType VertexAttributeTypeFromGLEnum(GLenum type) { |
| switch (type) { |
| case GL_BYTE: |
| return kVertexAttributeTypeByte; |
| case GL_UNSIGNED_BYTE: |
| return kVertexAttributeTypeUnsignedByte; |
| case GL_SHORT: |
| return kVertexAttributeTypeShort; |
| case GL_UNSIGNED_SHORT: |
| return kVertexAttributeTypeUnsignedShort; |
| case GL_FIXED: |
| return kVertexAttributeTypeFixed; |
| case GL_FLOAT: |
| return kVertexAttributeTypeFloat; |
| case GL_HALF_FLOAT: |
| return kVertexAttributeTypeHalfFloat; |
| default: |
| return kVertexAttributeTypeInvalid; |
| } |
| } |
| } // namespace |
| |
| void Context::VertexAttribPointer(GLuint indx, |
| GLint size, |
| GLenum type, |
| GLboolean normalized, |
| GLsizei stride, |
| const GLvoid* ptr) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (indx >= GL_MAX_VERTEX_ATTRIBS) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| if (size < 1 || size > 4) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| if (stride < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| VertexAttributeType vertex_attribute_type = |
| VertexAttributeTypeFromGLEnum(type); |
| if (vertex_attribute_type == kVertexAttributeTypeInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| vertex_attrib_map_[indx] = |
| VertexAttributeArray(size, vertex_attribute_type, normalized, stride, |
| static_cast<int>(reinterpret_cast<uintptr_t>(ptr))); |
| if (enabled_vertex_attribs_.find(indx) != enabled_vertex_attribs_.end()) { |
| enabled_vertex_attribs_dirty_ = true; |
| } |
| } |
| |
| void Context::EnableVertexAttribArray(GLuint index) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (index >= GL_MAX_VERTEX_ATTRIBS) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| enabled_vertex_attribs_.insert(index); |
| enabled_vertex_attribs_dirty_ = true; |
| } |
| |
| void Context::DisableVertexAttribArray(GLuint index) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (index >= GL_MAX_VERTEX_ATTRIBS) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| enabled_vertex_attribs_.erase(index); |
| enabled_vertex_attribs_dirty_ = true; |
| } |
| |
| void Context::VertexAttribfv(GLuint indx, |
| int elem_size, |
| const GLfloat* values) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(elem_size > 0); |
| SB_DCHECK(elem_size <= 4); |
| |
| VertexAttributeConstant* value = &const_vertex_attrib_map_[indx]; |
| memset(value, 0, sizeof(*value)); |
| for (int i = 0; i < elem_size; ++i) { |
| value->data[i] = values[i]; |
| } |
| value->size = elem_size; |
| value->type = kVertexAttributeTypeFloat; |
| |
| enabled_vertex_attribs_dirty_ = true; |
| } |
| |
| GLint Context::GetUniformLocation(GLuint program, const GLchar* name) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (name[0] == 'g' && name[1] == 'l' && name[2] == '_') { |
| // |name| is not allowed to begin with the reserved prefix, "gl_". |
| return -1; |
| } |
| |
| nb::scoped_refptr<Program> program_object = |
| resource_manager_->GetProgram(program); |
| if (!program_object) { |
| SetError(GL_INVALID_VALUE); |
| return -1; |
| } |
| |
| if (!program_object->linked()) { |
| SetError(GL_INVALID_OPERATION); |
| return -1; |
| } |
| |
| return program_object->GetUniformLocation(name); |
| } |
| |
| void Context::Uniformiv(GLint location, |
| GLsizei count, |
| GLsizei elem_size, |
| const GLint* v) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(elem_size >= 1 && elem_size <= 4); |
| |
| if (count < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (!draw_state_.used_program) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| GLenum result = |
| draw_state_.used_program->Uniformiv(location, count, elem_size, v); |
| if (result == GL_NO_ERROR) { |
| draw_state_dirty_flags_.uniforms_dirty.Mark(location); |
| } else { |
| SetError(result); |
| } |
| } |
| |
| void Context::Uniformfv(GLint location, |
| GLsizei count, |
| GLsizei elem_size, |
| const GLfloat* v) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(elem_size >= 1 && elem_size <= 4); |
| |
| if (count < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (!draw_state_.used_program) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| GLenum result = |
| draw_state_.used_program->Uniformfv(location, count, elem_size, v); |
| if (result == GL_NO_ERROR) { |
| draw_state_dirty_flags_.uniforms_dirty.Mark(location); |
| } else { |
| SetError(result); |
| } |
| } |
| |
| void Context::UniformMatrixfv(GLint location, |
| GLsizei count, |
| GLboolean transpose, |
| GLsizei dim_size, |
| const GLfloat* value) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(dim_size >= 2 && dim_size <= 4); |
| |
| if (transpose != GL_FALSE) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (!draw_state_.used_program) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| GLenum result = draw_state_.used_program->UniformMatrixfv(location, count, |
| dim_size, value); |
| if (result == GL_NO_ERROR) { |
| draw_state_dirty_flags_.uniforms_dirty.Mark(location); |
| } else { |
| SetError(result); |
| } |
| } |
| |
| namespace { |
| DrawMode DrawModeFromGLEnum(GLenum mode) { |
| switch (mode) { |
| case GL_POINTS: |
| return kDrawModePoints; |
| case GL_LINE_STRIP: |
| return kDrawModeLineStrip; |
| case GL_LINE_LOOP: |
| return kDrawModeLineLoop; |
| case GL_LINES: |
| return kDrawModeLines; |
| case GL_TRIANGLE_STRIP: |
| return kDrawModeTriangleStrip; |
| case GL_TRIANGLE_FAN: |
| return kDrawModeTriangleFan; |
| case GL_TRIANGLES: |
| return kDrawModeTriangles; |
| default: |
| return kDrawModeInvalid; |
| } |
| } |
| } // namespace |
| |
| void Context::DrawArrays(GLenum mode, GLint first, GLsizei count) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (count < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (draw_state_.framebuffer->CheckFramebufferStatus() != |
| GL_FRAMEBUFFER_COMPLETE) { |
| SetError(GL_INVALID_FRAMEBUFFER_OPERATION); |
| return; |
| } |
| |
| DrawMode draw_mode = DrawModeFromGLEnum(mode); |
| if (draw_mode == kDrawModeInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| SB_DCHECK(draw_state_.array_buffer) |
| << "glimp only supports vertices from vertex buffers."; |
| |
| CompressDrawStateForDrawCall(); |
| |
| impl_->DrawArrays(draw_mode, first, count, draw_state_, |
| &draw_state_dirty_flags_); |
| } |
| |
| namespace { |
| IndexDataType IndexDataTypeFromGLenum(GLenum type) { |
| switch (type) { |
| case GL_UNSIGNED_BYTE: |
| return kIndexDataTypeUnsignedByte; |
| case GL_UNSIGNED_SHORT: |
| return kIndexDataTypeUnsignedShort; |
| default: |
| return kIndexDataTypeInvalid; |
| } |
| } |
| } // namespace |
| |
| void Context::DrawElements(GLenum mode, |
| GLsizei count, |
| GLenum type, |
| const GLvoid* indices) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (count < 0) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| if (draw_state_.framebuffer->CheckFramebufferStatus() != |
| GL_FRAMEBUFFER_COMPLETE) { |
| SetError(GL_INVALID_FRAMEBUFFER_OPERATION); |
| return; |
| } |
| |
| DrawMode draw_mode = DrawModeFromGLEnum(mode); |
| if (draw_mode == kDrawModeInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| IndexDataType index_data_type = IndexDataTypeFromGLenum(type); |
| if (type == kIndexDataTypeInvalid) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| SB_DCHECK(draw_state_.array_buffer) |
| << "glimp only supports vertices from vertex buffers."; |
| SB_DCHECK(draw_state_.element_array_buffer) |
| << "glimp only supports indices from element vertex buffers."; |
| |
| CompressDrawStateForDrawCall(); |
| |
| impl_->DrawElements(draw_mode, count, index_data_type, |
| reinterpret_cast<intptr_t>(indices), draw_state_, |
| &draw_state_dirty_flags_); |
| } |
| |
| namespace { |
| bool ValidReadPixelsFormat(GLenum format) { |
| switch (format) { |
| case GL_RGBA: |
| case GL_RGBA_INTEGER: |
| case GL_RED_INTEGER: |
| return true; |
| break; |
| default: |
| return false; |
| break; |
| } |
| } |
| |
| bool ValidReadPixelsType(GLenum type) { |
| switch (type) { |
| case GL_UNSIGNED_BYTE: |
| case GL_UNSIGNED_INT: |
| case GL_UNSIGNED_SHORT: |
| case GL_INT: |
| case GL_FLOAT: |
| return true; |
| break; |
| default: |
| return false; |
| break; |
| } |
| } |
| } // namespace |
| |
| void Context::ReadPixels(GLint x, |
| GLint y, |
| GLsizei width, |
| GLsizei height, |
| GLenum format, |
| GLenum type, |
| GLvoid* pixels) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (!ValidReadPixelsFormat(format) || !ValidReadPixelsType(type)) { |
| SetError(GL_INVALID_ENUM); |
| return; |
| } |
| |
| SB_DCHECK(format == GL_RGBA) << "glimp only supports format=GL_RGBA."; |
| SB_DCHECK(type == GL_UNSIGNED_BYTE) |
| << "glimp only supports type=GL_UNSIGNED_BYTE."; |
| |
| SB_DCHECK(read_framebuffer_->color_attachment_texture()) |
| << "glimp only supports glReadPixels() calls on non-default " |
| "framebuffers."; |
| |
| if (x < 0 || y < 0 || width < 0 || height < 0 || |
| x + width > read_framebuffer_->GetWidth() || |
| y + height > read_framebuffer_->GetHeight()) { |
| SetError(GL_INVALID_VALUE); |
| return; |
| } |
| |
| // Ensure that all GPU activity (in particular, texture writes) complete |
| // before we attempt to read pixel data from the texture. |
| Finish(); |
| |
| read_framebuffer_->color_attachment_texture()->ReadPixelsAsRGBA8( |
| x, y, width, height, width * BytesPerPixel(kPixelFormatRGBA8), pixels); |
| } |
| |
| void Context::Flush() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| impl_->Flush(); |
| } |
| |
| void Context::Finish() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| impl_->Finish(); |
| } |
| |
| void Context::SwapBuffers() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| egl::Surface* surface = default_draw_framebuffer_->color_attachment_surface(); |
| // If surface is a pixel buffer or a pixmap, eglSwapBuffers has no effect, and |
| // no error is generated. |
| // https://www.khronos.org/registry/egl/sdk/docs/man/html/eglSwapBuffers.xhtml |
| if (surface->impl()->IsWindowSurface()) { |
| Flush(); |
| impl_->SwapBuffers(surface); |
| if (!has_swapped_buffers()) { |
| SbAtomicNoBarrier_Increment(&has_swapped_buffers_, 1); |
| } |
| } |
| } |
| |
| bool Context::BindTextureToEGLSurface(egl::Surface* surface) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| SB_DCHECK(surface->GetTextureTarget() == EGL_TEXTURE_2D); |
| |
| const nb::scoped_refptr<Texture>& current_texture = |
| *GetBoundTextureForTarget(GL_TEXTURE_2D, active_texture_); |
| |
| if (!current_texture) { |
| SB_DLOG(WARNING) << "No texture is currently bound during call to " |
| "eglBindTexImage()."; |
| return false; |
| } |
| |
| SB_DCHECK(bound_egl_surfaces_.find(surface) == bound_egl_surfaces_.end()); |
| |
| bool success = current_texture->BindToEGLSurface(surface); |
| if (success) { |
| bound_egl_surfaces_[surface] = current_texture; |
| } |
| |
| return success; |
| } |
| |
| bool Context::ReleaseTextureFromEGLSurface(egl::Surface* surface) { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| std::map<egl::Surface*, nb::scoped_refptr<Texture> >::iterator found = |
| bound_egl_surfaces_.find(surface); |
| if (found == bound_egl_surfaces_.end()) { |
| SB_LOG(WARNING) << "Releasing EGLSurface was never bound to a texture in " |
| "this context."; |
| return false; |
| } |
| |
| bool success = found->second->ReleaseFromEGLSurface(surface); |
| if (success) { |
| bound_egl_surfaces_.erase(found); |
| } |
| return success; |
| } |
| |
| void Context::UpdateVertexAttribsInDrawState() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| // Setup the dense list of enabled vertex attributes. |
| draw_state_.vertex_attributes.clear(); |
| for (std::set<unsigned int>::const_iterator iter = |
| enabled_vertex_attribs_.begin(); |
| iter != enabled_vertex_attribs_.end(); ++iter) { |
| draw_state_.vertex_attributes.push_back( |
| std::make_pair(*iter, &vertex_attrib_map_[*iter])); |
| } |
| |
| draw_state_.constant_vertex_attributes.clear(); |
| for (std::map<unsigned int, VertexAttributeConstant>::iterator iter = |
| const_vertex_attrib_map_.begin(); |
| iter != const_vertex_attrib_map_.end(); ++iter) { |
| // Add constant vertex attributes only if they do not have a vertex |
| // attribute array enabled for them. |
| if (enabled_vertex_attribs_.find(iter->first) == |
| enabled_vertex_attribs_.end()) { |
| draw_state_.constant_vertex_attributes.push_back( |
| std::make_pair(iter->first, &iter->second)); |
| } |
| } |
| |
| draw_state_dirty_flags_.vertex_attributes_dirty = true; |
| enabled_vertex_attribs_dirty_ = false; |
| } |
| |
| void Context::UpdateSamplersInDrawState() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| // Setup the list of enabled samplers. |
| draw_state_.textures.clear(); |
| int max_active_textures = impl_->GetMaxFragmentTextureUnits(); |
| for (int i = 0; i < max_active_textures; ++i) { |
| if (texture_units_[i]) { |
| draw_state_.textures.push_back(std::make_pair( |
| static_cast<unsigned int>(i), texture_units_[i].get())); |
| } |
| } |
| |
| draw_state_dirty_flags_.textures_dirty = true; |
| enabled_textures_dirty_ = false; |
| } |
| |
| void Context::CompressDrawStateForDrawCall() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (enabled_vertex_attribs_dirty_) { |
| UpdateVertexAttribsInDrawState(); |
| SB_DCHECK(enabled_vertex_attribs_dirty_ == false); |
| } |
| |
| if (enabled_textures_dirty_) { |
| UpdateSamplersInDrawState(); |
| SB_DCHECK(enabled_textures_dirty_ == false); |
| } |
| } |
| |
| void Context::MarkUsedProgramDirty() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| draw_state_dirty_flags_.MarkUsedProgram(); |
| } |
| |
| void Context::SetBoundDrawFramebufferToDefault() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (draw_state_.framebuffer != default_draw_framebuffer_) { |
| draw_state_.framebuffer = default_draw_framebuffer_; |
| draw_state_dirty_flags_.framebuffer_dirty = true; |
| } |
| } |
| |
| void Context::SetBoundReadFramebufferToDefault() { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| if (read_framebuffer_ != default_read_framebuffer_) { |
| read_framebuffer_ = default_read_framebuffer_; |
| } |
| } |
| |
| bool Context::IsDefaultDrawFramebufferBound() const { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| return draw_state_.framebuffer == default_draw_framebuffer_; |
| } |
| |
| bool Context::IsDefaultReadFramebufferBound() const { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| return read_framebuffer_ == default_read_framebuffer_; |
| } |
| |
| int Context::GetPitchForTextureData(int width, PixelFormat pixel_format) const { |
| GLIMP_TRACE_EVENT0(__FUNCTION__); |
| // The equations for determining the pitch are described here: |
| // https://www.khronos.org/opengles/sdk/docs/man3/html/glPixelStorei.xhtml |
| int n = BytesPerPixel(pixel_format); |
| int s = 1; |
| int len = unpack_row_length_ > 0 ? unpack_row_length_ : width; |
| int a = unpack_alignment_; |
| |
| if (s >= a) { |
| return n * len; |
| } else { |
| return nb::AlignUp(s * n * len, a) / s; |
| } |
| } |
| |
| SbAtomic32 Context::has_swapped_buffers_ = 0; |
| |
| } // namespace gles |
| } // namespace glimp |