// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cc/test/test_web_graphics_context_3d.h" #include #include #include #include #include "base/bind.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/numerics/safe_conversions.h" #include "cc/test/test_context_support.h" #include "gpu/GLES2/gl2extchromium.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/khronos/GLES2/gl2ext.h" namespace cc { static unsigned s_context_id = 1; const GLuint TestWebGraphicsContext3D::kExternalTextureId = 1337; static base::LazyInstance::Leaky g_shared_namespace_lock = LAZY_INSTANCE_INITIALIZER; TestWebGraphicsContext3D::Namespace* TestWebGraphicsContext3D::shared_namespace_ = NULL; TestWebGraphicsContext3D::Namespace::Namespace() : next_buffer_id(1), next_image_id(1), next_texture_id(1), next_renderbuffer_id(1) { } TestWebGraphicsContext3D::Namespace::~Namespace() { g_shared_namespace_lock.Get().AssertAcquired(); if (shared_namespace_ == this) shared_namespace_ = NULL; } // static scoped_ptr TestWebGraphicsContext3D::Create() { return make_scoped_ptr(new TestWebGraphicsContext3D()); } TestWebGraphicsContext3D::TestWebGraphicsContext3D() : context_id_(s_context_id++), times_bind_texture_succeeds_(-1), times_end_query_succeeds_(-1), context_lost_(false), times_map_buffer_chromium_succeeds_(-1), current_used_transfer_buffer_usage_bytes_(0), max_used_transfer_buffer_usage_bytes_(0), next_program_id_(1000), next_shader_id_(2000), next_framebuffer_id_(1), current_framebuffer_(0), max_texture_size_(2048), reshape_called_(false), width_(0), height_(0), scale_factor_(-1.f), test_support_(NULL), last_update_type_(NO_UPDATE), next_insert_sync_point_(1), unpack_alignment_(4), bound_buffer_(0), weak_ptr_factory_(this) { CreateNamespace(); set_support_image(true); } TestWebGraphicsContext3D::~TestWebGraphicsContext3D() { base::AutoLock lock(g_shared_namespace_lock.Get()); namespace_ = NULL; } void TestWebGraphicsContext3D::CreateNamespace() { base::AutoLock lock(g_shared_namespace_lock.Get()); if (shared_namespace_) { namespace_ = shared_namespace_; } else { namespace_ = new Namespace; shared_namespace_ = namespace_.get(); } } void TestWebGraphicsContext3D::reshapeWithScaleFactor( int width, int height, float scale_factor) { reshape_called_ = true; width_ = width; height_ = height; scale_factor_ = scale_factor; } bool TestWebGraphicsContext3D::isContextLost() { return context_lost_; } GLenum TestWebGraphicsContext3D::checkFramebufferStatus( GLenum target) { if (context_lost_) return GL_FRAMEBUFFER_UNDEFINED_OES; return GL_FRAMEBUFFER_COMPLETE; } GLint TestWebGraphicsContext3D::getUniformLocation( GLuint program, const GLchar* name) { return 0; } GLsizeiptr TestWebGraphicsContext3D::getVertexAttribOffset( GLuint index, GLenum pname) { return 0; } GLboolean TestWebGraphicsContext3D::isBuffer( GLuint buffer) { return false; } GLboolean TestWebGraphicsContext3D::isEnabled( GLenum cap) { return false; } GLboolean TestWebGraphicsContext3D::isFramebuffer( GLuint framebuffer) { return false; } GLboolean TestWebGraphicsContext3D::isProgram( GLuint program) { return false; } GLboolean TestWebGraphicsContext3D::isRenderbuffer( GLuint renderbuffer) { return false; } GLboolean TestWebGraphicsContext3D::isShader( GLuint shader) { return false; } GLboolean TestWebGraphicsContext3D::isTexture( GLuint texture) { return false; } void TestWebGraphicsContext3D::genBuffers(GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) ids[i] = NextBufferId(); } void TestWebGraphicsContext3D::genFramebuffers( GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) ids[i] = NextFramebufferId(); } void TestWebGraphicsContext3D::genRenderbuffers( GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) ids[i] = NextRenderbufferId(); } void TestWebGraphicsContext3D::genTextures(GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) { ids[i] = NextTextureId(); DCHECK_NE(ids[i], kExternalTextureId); } base::AutoLock lock(namespace_->lock); for (int i = 0; i < count; ++i) namespace_->textures.Append(ids[i], new TestTexture()); } void TestWebGraphicsContext3D::deleteBuffers(GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) RetireBufferId(ids[i]); } void TestWebGraphicsContext3D::deleteFramebuffers( GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) { if (ids[i]) { RetireFramebufferId(ids[i]); if (ids[i] == current_framebuffer_) current_framebuffer_ = 0; } } } void TestWebGraphicsContext3D::deleteRenderbuffers( GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) RetireRenderbufferId(ids[i]); } void TestWebGraphicsContext3D::deleteTextures(GLsizei count, GLuint* ids) { for (int i = 0; i < count; ++i) RetireTextureId(ids[i]); base::AutoLock lock(namespace_->lock); for (int i = 0; i < count; ++i) { namespace_->textures.Remove(ids[i]); texture_targets_.UnbindTexture(ids[i]); } } GLuint TestWebGraphicsContext3D::createBuffer() { GLuint id; genBuffers(1, &id); return id; } GLuint TestWebGraphicsContext3D::createFramebuffer() { GLuint id; genFramebuffers(1, &id); return id; } GLuint TestWebGraphicsContext3D::createRenderbuffer() { GLuint id; genRenderbuffers(1, &id); return id; } GLuint TestWebGraphicsContext3D::createTexture() { GLuint id; genTextures(1, &id); return id; } void TestWebGraphicsContext3D::deleteBuffer(GLuint id) { deleteBuffers(1, &id); } void TestWebGraphicsContext3D::deleteFramebuffer(GLuint id) { deleteFramebuffers(1, &id); } void TestWebGraphicsContext3D::deleteRenderbuffer(GLuint id) { deleteRenderbuffers(1, &id); } void TestWebGraphicsContext3D::deleteTexture(GLuint id) { deleteTextures(1, &id); } unsigned TestWebGraphicsContext3D::createProgram() { unsigned program = next_program_id_++ | context_id_ << 16; program_set_.insert(program); return program; } GLuint TestWebGraphicsContext3D::createShader(GLenum) { unsigned shader = next_shader_id_++ | context_id_ << 16; shader_set_.insert(shader); return shader; } GLuint TestWebGraphicsContext3D::createExternalTexture() { base::AutoLock lock(namespace_->lock); namespace_->textures.Append(kExternalTextureId, new TestTexture()); return kExternalTextureId; } void TestWebGraphicsContext3D::deleteProgram(GLuint id) { if (!program_set_.count(id)) ADD_FAILURE() << "deleteProgram called on unknown program " << id; program_set_.erase(id); } void TestWebGraphicsContext3D::deleteShader(GLuint id) { if (!shader_set_.count(id)) ADD_FAILURE() << "deleteShader called on unknown shader " << id; shader_set_.erase(id); } void TestWebGraphicsContext3D::attachShader(GLuint program, GLuint shader) { if (!program_set_.count(program)) ADD_FAILURE() << "attachShader called with unknown program " << program; if (!shader_set_.count(shader)) ADD_FAILURE() << "attachShader called with unknown shader " << shader; } void TestWebGraphicsContext3D::useProgram(GLuint program) { if (!program) return; if (!program_set_.count(program)) ADD_FAILURE() << "useProgram called on unknown program " << program; } void TestWebGraphicsContext3D::bindFramebuffer( GLenum target, GLuint framebuffer) { base::AutoLock lock_for_framebuffer_access(namespace_->lock); if (framebuffer != 0 && framebuffer_set_.find(framebuffer) == framebuffer_set_.end()) { ADD_FAILURE() << "bindFramebuffer called with unknown framebuffer"; } else if (framebuffer != 0 && (framebuffer >> 16) != context_id_) { ADD_FAILURE() << "bindFramebuffer called with framebuffer from other context"; } else { current_framebuffer_ = framebuffer; } } void TestWebGraphicsContext3D::bindRenderbuffer( GLenum target, GLuint renderbuffer) { if (!renderbuffer) return; base::AutoLock lock_for_renderbuffer_access(namespace_->lock); if (renderbuffer != 0 && namespace_->renderbuffer_set.find(renderbuffer) == namespace_->renderbuffer_set.end()) { ADD_FAILURE() << "bindRenderbuffer called with unknown renderbuffer"; } else if ((renderbuffer >> 16) != context_id_) { ADD_FAILURE() << "bindRenderbuffer called with renderbuffer from other context"; } } void TestWebGraphicsContext3D::bindTexture( GLenum target, GLuint texture_id) { if (times_bind_texture_succeeds_ >= 0) { if (!times_bind_texture_succeeds_) { loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB); } --times_bind_texture_succeeds_; } if (!texture_id) return; base::AutoLock lock(namespace_->lock); DCHECK(namespace_->textures.ContainsId(texture_id)); texture_targets_.BindTexture(target, texture_id); used_textures_.insert(texture_id); } GLuint TestWebGraphicsContext3D::BoundTextureId( GLenum target) { return texture_targets_.BoundTexture(target); } scoped_refptr TestWebGraphicsContext3D::BoundTexture( GLenum target) { // The caller is expected to lock the namespace for texture access. namespace_->lock.AssertAcquired(); return namespace_->textures.TextureForId(BoundTextureId(target)); } scoped_refptr TestWebGraphicsContext3D::UnboundTexture( GLuint texture) { // The caller is expected to lock the namespace for texture access. namespace_->lock.AssertAcquired(); return namespace_->textures.TextureForId(texture); } void TestWebGraphicsContext3D::CheckTextureIsBound(GLenum target) { DCHECK(BoundTextureId(target)); } GLuint TestWebGraphicsContext3D::createQueryEXT() { return 1u; } void TestWebGraphicsContext3D::endQueryEXT(GLenum target) { if (times_end_query_succeeds_ >= 0) { if (!times_end_query_succeeds_) { loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, GL_INNOCENT_CONTEXT_RESET_ARB); } --times_end_query_succeeds_; } } void TestWebGraphicsContext3D::getQueryObjectuivEXT( GLuint query, GLenum pname, GLuint* params) { // If the context is lost, behave as if result is available. if (pname == GL_QUERY_RESULT_AVAILABLE_EXT) *params = 1; } void TestWebGraphicsContext3D::getIntegerv( GLenum pname, GLint* value) { if (pname == GL_MAX_TEXTURE_SIZE) *value = max_texture_size_; else if (pname == GL_ACTIVE_TEXTURE) *value = GL_TEXTURE0; else if (pname == GL_UNPACK_ALIGNMENT) *value = unpack_alignment_; else if (pname == GL_FRAMEBUFFER_BINDING) *value = current_framebuffer_; } void TestWebGraphicsContext3D::getProgramiv(GLuint program, GLenum pname, GLint* value) { if (pname == GL_LINK_STATUS) *value = 1; } void TestWebGraphicsContext3D::getShaderiv(GLuint shader, GLenum pname, GLint* value) { if (pname == GL_COMPILE_STATUS) *value = 1; } void TestWebGraphicsContext3D::getShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) { // Return the minimum precision requirements of the GLES2 // specification. switch (precisiontype) { case GL_LOW_INT: range[0] = 8; range[1] = 8; *precision = 0; break; case GL_MEDIUM_INT: range[0] = 10; range[1] = 10; *precision = 0; break; case GL_HIGH_INT: range[0] = 16; range[1] = 16; *precision = 0; break; case GL_LOW_FLOAT: range[0] = 8; range[1] = 8; *precision = 8; break; case GL_MEDIUM_FLOAT: range[0] = 14; range[1] = 14; *precision = 10; break; case GL_HIGH_FLOAT: range[0] = 62; range[1] = 62; *precision = 16; break; default: NOTREACHED(); break; } } void TestWebGraphicsContext3D::genMailboxCHROMIUM(GLbyte* mailbox) { static char mailbox_name1 = '1'; static char mailbox_name2 = '1'; mailbox[0] = mailbox_name1; mailbox[1] = mailbox_name2; mailbox[2] = '\0'; if (++mailbox_name1 == 0) { mailbox_name1 = '1'; ++mailbox_name2; } } GLuint TestWebGraphicsContext3D::createAndConsumeTextureCHROMIUM( GLenum target, const GLbyte* mailbox) { GLuint texture_id = createTexture(); consumeTextureCHROMIUM(target, mailbox); return texture_id; } void TestWebGraphicsContext3D::loseContextCHROMIUM(GLenum current, GLenum other) { if (context_lost_) return; context_lost_ = true; if (!context_lost_callback_.is_null()) context_lost_callback_.Run(); for (size_t i = 0; i < shared_contexts_.size(); ++i) shared_contexts_[i]->loseContextCHROMIUM(current, other); shared_contexts_.clear(); } void TestWebGraphicsContext3D::finish() { test_support_->CallAllSyncPointCallbacks(); } void TestWebGraphicsContext3D::flush() { test_support_->CallAllSyncPointCallbacks(); } void TestWebGraphicsContext3D::shallowFinishCHROMIUM() { test_support_->CallAllSyncPointCallbacks(); } GLint TestWebGraphicsContext3D::getAttribLocation(GLuint program, const GLchar* name) { return 0; } GLenum TestWebGraphicsContext3D::getError() { return GL_NO_ERROR; } void TestWebGraphicsContext3D::bindBuffer(GLenum target, GLuint buffer) { bound_buffer_ = buffer; if (!bound_buffer_) return; unsigned context_id = buffer >> 16; unsigned buffer_id = buffer & 0xffff; base::AutoLock lock(namespace_->lock); DCHECK(buffer_id); DCHECK_LT(buffer_id, namespace_->next_buffer_id); DCHECK_EQ(context_id, context_id_); base::ScopedPtrHashMap>& buffers = namespace_->buffers; if (buffers.count(bound_buffer_) == 0) buffers.set(bound_buffer_, make_scoped_ptr(new Buffer)); buffers.get(bound_buffer_)->target = target; } void TestWebGraphicsContext3D::bufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) { base::AutoLock lock(namespace_->lock); base::ScopedPtrHashMap>& buffers = namespace_->buffers; DCHECK_GT(buffers.count(bound_buffer_), 0u); DCHECK_EQ(target, buffers.get(bound_buffer_)->target); Buffer* buffer = buffers.get(bound_buffer_); if (context_lost_) { buffer->pixels = nullptr; return; } size_t old_size = buffer->size; buffer->pixels.reset(new uint8_t[size]); buffer->size = size; if (data != NULL) memcpy(buffer->pixels.get(), data, size); if (buffer->target == GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM) current_used_transfer_buffer_usage_bytes_ += base::checked_cast(buffer->size) - base::checked_cast(old_size); max_used_transfer_buffer_usage_bytes_ = std::max(max_used_transfer_buffer_usage_bytes_, current_used_transfer_buffer_usage_bytes_); } void TestWebGraphicsContext3D::pixelStorei(GLenum pname, GLint param) { switch (pname) { case GL_UNPACK_ALIGNMENT: // Param should be a power of two <= 8. EXPECT_EQ(0, param & (param - 1)); EXPECT_GE(8, param); switch (param) { case 1: case 2: case 4: case 8: unpack_alignment_ = param; break; default: break; } break; default: break; } } void* TestWebGraphicsContext3D::mapBufferCHROMIUM(GLenum target, GLenum access) { base::AutoLock lock(namespace_->lock); base::ScopedPtrHashMap>& buffers = namespace_->buffers; DCHECK_GT(buffers.count(bound_buffer_), 0u); DCHECK_EQ(target, buffers.get(bound_buffer_)->target); if (times_map_buffer_chromium_succeeds_ >= 0) { if (!times_map_buffer_chromium_succeeds_) { return NULL; } --times_map_buffer_chromium_succeeds_; } return buffers.get(bound_buffer_)->pixels.get(); } GLboolean TestWebGraphicsContext3D::unmapBufferCHROMIUM( GLenum target) { base::AutoLock lock(namespace_->lock); base::ScopedPtrHashMap>& buffers = namespace_->buffers; DCHECK_GT(buffers.count(bound_buffer_), 0u); DCHECK_EQ(target, buffers.get(bound_buffer_)->target); buffers.get(bound_buffer_)->pixels = nullptr; return true; } GLuint TestWebGraphicsContext3D::createImageCHROMIUM(ClientBuffer buffer, GLsizei width, GLsizei height, GLenum internalformat) { DCHECK_EQ(GL_RGBA, static_cast(internalformat)); GLuint image_id = NextImageId(); base::AutoLock lock(namespace_->lock); base::hash_set& images = namespace_->images; images.insert(image_id); return image_id; } void TestWebGraphicsContext3D::destroyImageCHROMIUM( GLuint id) { RetireImageId(id); base::AutoLock lock(namespace_->lock); base::hash_set& images = namespace_->images; if (!images.count(id)) ADD_FAILURE() << "destroyImageCHROMIUM called on unknown image " << id; images.erase(id); } GLuint TestWebGraphicsContext3D::createGpuMemoryBufferImageCHROMIUM( GLsizei width, GLsizei height, GLenum internalformat, GLenum usage) { DCHECK_EQ(GL_RGBA, static_cast(internalformat)); GLuint image_id = NextImageId(); base::AutoLock lock(namespace_->lock); base::hash_set& images = namespace_->images; images.insert(image_id); return image_id; } unsigned TestWebGraphicsContext3D::insertSyncPoint() { return next_insert_sync_point_++; } void TestWebGraphicsContext3D::waitSyncToken(const GLbyte* sync_token) { if (sync_token) { gpu::SyncToken sync_token_data; memcpy(sync_token_data.GetData(), sync_token, sizeof(sync_token_data)); if (sync_token_data.HasData()) last_waited_sync_token_ = sync_token_data; } } size_t TestWebGraphicsContext3D::NumTextures() const { base::AutoLock lock(namespace_->lock); return namespace_->textures.Size(); } GLuint TestWebGraphicsContext3D::TextureAt(int i) const { base::AutoLock lock(namespace_->lock); return namespace_->textures.IdAt(i); } GLuint TestWebGraphicsContext3D::NextTextureId() { base::AutoLock lock(namespace_->lock); GLuint texture_id = namespace_->next_texture_id++; DCHECK(texture_id < (1 << 16)); texture_id |= context_id_ << 16; return texture_id; } void TestWebGraphicsContext3D::RetireTextureId(GLuint id) { base::AutoLock lock(namespace_->lock); unsigned context_id = id >> 16; unsigned texture_id = id & 0xffff; DCHECK(texture_id); DCHECK_LT(texture_id, namespace_->next_texture_id); DCHECK_EQ(context_id, context_id_); } GLuint TestWebGraphicsContext3D::NextBufferId() { base::AutoLock lock(namespace_->lock); GLuint buffer_id = namespace_->next_buffer_id++; DCHECK(buffer_id < (1 << 16)); buffer_id |= context_id_ << 16; return buffer_id; } void TestWebGraphicsContext3D::RetireBufferId(GLuint id) { base::AutoLock lock(namespace_->lock); unsigned context_id = id >> 16; unsigned buffer_id = id & 0xffff; DCHECK(buffer_id); DCHECK_LT(buffer_id, namespace_->next_buffer_id); DCHECK_EQ(context_id, context_id_); } GLuint TestWebGraphicsContext3D::NextImageId() { base::AutoLock lock(namespace_->lock); GLuint image_id = namespace_->next_image_id++; DCHECK(image_id < (1 << 16)); image_id |= context_id_ << 16; return image_id; } void TestWebGraphicsContext3D::RetireImageId(GLuint id) { base::AutoLock lock(namespace_->lock); unsigned context_id = id >> 16; unsigned image_id = id & 0xffff; DCHECK(image_id); DCHECK_LT(image_id, namespace_->next_image_id); DCHECK_EQ(context_id, context_id_); } GLuint TestWebGraphicsContext3D::NextFramebufferId() { base::AutoLock lock_for_framebuffer_access(namespace_->lock); GLuint id = next_framebuffer_id_++; DCHECK(id < (1 << 16)); id |= context_id_ << 16; framebuffer_set_.insert(id); return id; } void TestWebGraphicsContext3D::RetireFramebufferId(GLuint id) { base::AutoLock lock_for_framebuffer_access(namespace_->lock); DCHECK(framebuffer_set_.find(id) != framebuffer_set_.end()); framebuffer_set_.erase(id); } GLuint TestWebGraphicsContext3D::NextRenderbufferId() { base::AutoLock lock_for_renderbuffer_access(namespace_->lock); GLuint id = namespace_->next_renderbuffer_id++; DCHECK(id < (1 << 16)); id |= context_id_ << 16; namespace_->renderbuffer_set.insert(id); return id; } void TestWebGraphicsContext3D::RetireRenderbufferId(GLuint id) { base::AutoLock lock_for_renderbuffer_access(namespace_->lock); DCHECK(namespace_->renderbuffer_set.find(id) != namespace_->renderbuffer_set.end()); namespace_->renderbuffer_set.erase(id); } void TestWebGraphicsContext3D::SetMaxTransferBufferUsageBytes( size_t max_transfer_buffer_usage_bytes) { test_capabilities_.max_transfer_buffer_usage_bytes = max_transfer_buffer_usage_bytes; } void TestWebGraphicsContext3D::SetMaxSamples(int max_samples) { test_capabilities_.gpu.max_samples = max_samples; } TestWebGraphicsContext3D::TextureTargets::TextureTargets() { // Initialize default bindings. bound_textures_[GL_TEXTURE_2D] = 0; bound_textures_[GL_TEXTURE_EXTERNAL_OES] = 0; bound_textures_[GL_TEXTURE_RECTANGLE_ARB] = 0; } TestWebGraphicsContext3D::TextureTargets::~TextureTargets() {} void TestWebGraphicsContext3D::TextureTargets::BindTexture( GLenum target, GLuint id) { // Make sure this is a supported target by seeing if it was bound to before. DCHECK(bound_textures_.find(target) != bound_textures_.end()); bound_textures_[target] = id; } void TestWebGraphicsContext3D::texParameteri(GLenum target, GLenum pname, GLint param) { CheckTextureIsBound(target); base::AutoLock lock_for_texture_access(namespace_->lock); scoped_refptr texture = BoundTexture(target); DCHECK(texture->IsValidParameter(pname)); texture->params[pname] = param; } void TestWebGraphicsContext3D::getTexParameteriv(GLenum target, GLenum pname, GLint* value) { CheckTextureIsBound(target); base::AutoLock lock_for_texture_access(namespace_->lock); scoped_refptr texture = BoundTexture(target); DCHECK(texture->IsValidParameter(pname)); TestTexture::TextureParametersMap::iterator it = texture->params.find(pname); if (it != texture->params.end()) *value = it->second; } void TestWebGraphicsContext3D::TextureTargets::UnbindTexture( GLuint id) { // Bind zero to any targets that the id is bound to. for (TargetTextureMap::iterator it = bound_textures_.begin(); it != bound_textures_.end(); it++) { if (it->second == id) it->second = 0; } } GLuint TestWebGraphicsContext3D::TextureTargets::BoundTexture( GLenum target) { DCHECK(bound_textures_.find(target) != bound_textures_.end()); return bound_textures_[target]; } TestWebGraphicsContext3D::Buffer::Buffer() : target(0), size(0) {} TestWebGraphicsContext3D::Buffer::~Buffer() {} TestWebGraphicsContext3D::Image::Image() {} TestWebGraphicsContext3D::Image::~Image() {} } // namespace cc