diff options
39 files changed, 2260 insertions, 93 deletions
diff --git a/content/common/gpu/gpu_channel.h b/content/common/gpu/gpu_channel.h index e28ea4e..30f967f 100644 --- a/content/common/gpu/gpu_channel.h +++ b/content/common/gpu/gpu_channel.h @@ -7,6 +7,7 @@ #include <deque> #include <string> +#include <vector> #include "base/id_map.h" #include "base/memory/ref_counted.h" diff --git a/content/common/gpu/gpu_channel_manager.cc b/content/common/gpu/gpu_channel_manager.cc index 5978b2e..be60a19 100644 --- a/content/common/gpu/gpu_channel_manager.cc +++ b/content/common/gpu/gpu_channel_manager.cc @@ -5,12 +5,17 @@ #include "content/common/gpu/gpu_channel_manager.h" #include "base/bind.h" +#include "base/command_line.h" #include "content/common/child_thread.h" #include "content/common/gpu/gpu_channel.h" #include "content/common/gpu/gpu_memory_manager.h" #include "content/common/gpu/gpu_messages.h" #include "content/common/gpu/sync_point_manager.h" +#include "gpu/command_buffer/service/feature_info.h" +#include "gpu/command_buffer/service/gpu_switches.h" #include "gpu/command_buffer/service/mailbox_manager.h" +#include "gpu/command_buffer/service/memory_program_cache.h" +#include "ui/gl/gl_bindings.h" #include "ui/gl/gl_share_group.h" GpuChannelManager::GpuChannelManager(ChildThread* gpu_child_thread, @@ -24,7 +29,8 @@ GpuChannelManager::GpuChannelManager(ChildThread* gpu_child_thread, ALLOW_THIS_IN_INITIALIZER_LIST(gpu_memory_manager_(this, GpuMemoryManager::kDefaultMaxSurfacesWithFrontbufferSoftLimit)), watchdog_(watchdog), - sync_point_manager_(new SyncPointManager) { + sync_point_manager_(new SyncPointManager), + program_cache_(NULL) { DCHECK(gpu_child_thread); DCHECK(io_message_loop); DCHECK(shutdown_event); @@ -34,6 +40,16 @@ GpuChannelManager::~GpuChannelManager() { gpu_channels_.clear(); } +gpu::gles2::ProgramCache* GpuChannelManager::program_cache() { + if (!program_cache_.get() && + (gfx::g_ARB_get_program_binary || gfx::g_OES_get_program_binary) && + !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableGpuProgramCache)) { + program_cache_.reset(new gpu::gles2::MemoryProgramCache()); + } + return program_cache_.get(); +} + void GpuChannelManager::RemoveChannel(int client_id) { gpu_channels_.erase(client_id); } @@ -65,7 +81,6 @@ void GpuChannelManager::AppendAllCommandBufferStubs( it != gpu_channels_.end(); ++it ) { it->second->AppendAllCommandBufferStubs(stubs); } - } bool GpuChannelManager::OnMessageReceived(const IPC::Message& msg) { diff --git a/content/common/gpu/gpu_channel_manager.h b/content/common/gpu/gpu_channel_manager.h index 7b1c997..3e4af65 100644 --- a/content/common/gpu/gpu_channel_manager.h +++ b/content/common/gpu/gpu_channel_manager.h @@ -5,8 +5,11 @@ #ifndef CONTENT_COMMON_GPU_GPU_CHANNEL_MANAGER_H_ #define CONTENT_COMMON_GPU_GPU_CHANNEL_MANAGER_H_ +#include <vector> + #include "base/hash_tables.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/message_loop_proxy.h" #include "build/build_config.h" @@ -26,6 +29,7 @@ class GLShareGroup; namespace gpu { namespace gles2 { class MailboxManager; +class ProgramCache; } } @@ -80,6 +84,8 @@ class GpuChannelManager : public IPC::Listener, void AddRoute(int32 routing_id, IPC::Listener* listener); void RemoveRoute(int32 routing_id); + gpu::gles2::ProgramCache* program_cache(); + GpuMemoryManager* gpu_memory_manager() { return &gpu_memory_manager_; } GpuChannel* LookupChannel(int32 client_id); @@ -116,6 +122,7 @@ class GpuChannelManager : public IPC::Listener, GpuMemoryManager gpu_memory_manager_; GpuWatchdog* watchdog_; scoped_refptr<SyncPointManager> sync_point_manager_; + scoped_ptr<gpu::gles2::ProgramCache> program_cache_; DISALLOW_COPY_AND_ASSIGN(GpuChannelManager); }; diff --git a/content/common/gpu/gpu_command_buffer_stub.cc b/content/common/gpu/gpu_command_buffer_stub.cc index 6a281e4..12cda4b 100644 --- a/content/common/gpu/gpu_command_buffer_stub.cc +++ b/content/common/gpu/gpu_command_buffer_stub.cc @@ -376,6 +376,11 @@ void GpuCommandBufferStub::OnInitialize( return; } + if (!context_group_->has_program_cache()) { + context_group_->set_program_cache( + channel_->gpu_channel_manager()->program_cache()); + } + // Initialize the decoder with either the view or pbuffer GLContext. if (!decoder_->Initialize(surface_, context_, diff --git a/content/common/gpu/gpu_command_buffer_stub.h b/content/common/gpu/gpu_command_buffer_stub.h index 55c2be9..a8de609 100644 --- a/content/common/gpu/gpu_command_buffer_stub.h +++ b/content/common/gpu/gpu_command_buffer_stub.h @@ -14,7 +14,6 @@ #include "base/observer_list.h" #include "content/common/content_export.h" #include "content/common/gpu/gpu_memory_allocation.h" -#include "content/common/gpu/gpu_memory_allocation.h" #include "googleurl/src/gurl.h" #include "gpu/command_buffer/common/constants.h" #include "gpu/command_buffer/service/command_buffer_service.h" diff --git a/gpu/command_buffer/common/gl_mock.h b/gpu/command_buffer/common/gl_mock.h index c68f787..6416a61 100644 --- a/gpu/command_buffer/common/gl_mock.h +++ b/gpu/command_buffer/common/gl_mock.h @@ -213,6 +213,10 @@ class MockGLInterface : public GLInterface { MOCK_METHOD2(GetIntegerv, void(GLenum pname, GLint* params)); + MOCK_METHOD5(GetProgramBinary, void( + GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, + GLvoid* binary)); + MOCK_METHOD3(GetProgramiv, void(GLuint program, GLenum pname, GLint* params)); MOCK_METHOD4(GetProgramInfoLog, void( @@ -307,6 +311,13 @@ class MockGLInterface : public GLInterface { MOCK_METHOD2(PolygonOffset, void(GLfloat factor, GLfloat units)); + MOCK_METHOD4(ProgramBinary, void( + GLuint program, GLenum binaryFormat, const GLvoid* binary, + GLsizei length)); + + MOCK_METHOD3(ProgramParameteri, void( + GLuint program, GLenum pname, GLint value)); + MOCK_METHOD2(QueryCounter, void(GLuint id, GLenum target)); MOCK_METHOD1(ReadBuffer, void(GLenum src)); diff --git a/gpu/command_buffer/service/context_group.cc b/gpu/command_buffer/service/context_group.cc index f4f16c3..a1ec47c 100644 --- a/gpu/command_buffer/service/context_group.cc +++ b/gpu/command_buffer/service/context_group.cc @@ -4,6 +4,7 @@ #include "gpu/command_buffer/service/context_group.h" +#include <algorithm> #include <string> #include "base/command_line.h" @@ -39,6 +40,7 @@ ContextGroup::ContextGroup( max_fragment_uniform_vectors_(0u), max_varying_vectors_(0u), max_vertex_uniform_vectors_(0u), + program_cache_(NULL), feature_info_(new FeatureInfo()) { { TransferBufferManager* manager = new TransferBufferManager(); @@ -93,7 +95,7 @@ bool ContextGroup::Initialize(const DisallowedFeatures& disallowed_features, renderbuffer_manager_.reset(new RenderbufferManager( max_renderbuffer_size, max_samples)); shader_manager_.reset(new ShaderManager()); - program_manager_.reset(new ProgramManager()); + program_manager_.reset(new ProgramManager(program_cache_)); // Lookup GL things we need to know. const GLint kGLES2RequiredMinimumVertexAttribs = 8u; diff --git a/gpu/command_buffer/service/context_group.h b/gpu/command_buffer/service/context_group.h index 2f3eaf9..55e02e6 100644 --- a/gpu/command_buffer/service/context_group.h +++ b/gpu/command_buffer/service/context_group.h @@ -23,6 +23,7 @@ class TransferBufferManagerInterface; namespace gles2 { +class ProgramCache; class BufferManager; class GLES2Decoder; class FramebufferManager; @@ -112,6 +113,14 @@ class GPU_EXPORT ContextGroup : public base::RefCounted<ContextGroup> { return program_manager_.get(); } + bool has_program_cache() const { + return program_cache_ != NULL; + } + + void set_program_cache(ProgramCache* program_cache) { + program_cache_ = program_cache; + } + ShaderManager* shader_manager() const { return shader_manager_.get(); } @@ -149,6 +158,8 @@ class GPU_EXPORT ContextGroup : public base::RefCounted<ContextGroup> { uint32 max_varying_vectors_; uint32 max_vertex_uniform_vectors_; + ProgramCache* program_cache_; + scoped_ptr<BufferManager> buffer_manager_; scoped_ptr<FramebufferManager> framebuffer_manager_; diff --git a/gpu/command_buffer/service/gl_utils.h b/gpu/command_buffer/service/gl_utils.h index 5788670..e31126f 100644 --- a/gpu/command_buffer/service/gl_utils.h +++ b/gpu/command_buffer/service/gl_utils.h @@ -103,6 +103,13 @@ #define GL_GLEXT_PROTOTYPES 1 +// GL_ARB_get_program_binary +#define PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +// GL_OES_get_program_binary +#define GL_PROGRAM_BINARY_LENGTH_OES 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS_OES 0x87FE +#define GL_PROGRAM_BINARY_FORMATS_OES 0x87FF + // Define this for extra GL error debugging (slower). // #define GL_ERROR_DEBUGGING #ifdef GL_ERROR_DEBUGGING diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc index a3bf4bf..9955d98 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc @@ -4719,7 +4719,16 @@ void GLES2DecoderImpl::DoLinkProgram(GLuint program) { return; } - if (info->Link()) { + ShaderTranslator* vertex_translator = NULL; + ShaderTranslator* fragment_translator = NULL; + if (use_shader_translator_) { + vertex_translator = vertex_translator_; + fragment_translator = fragment_translator_; + } + if (info->Link(shader_manager(), + vertex_translator, + fragment_translator, + feature_info_)) { if (info == current_program_.get()) { program_manager()->ClearUniforms(info); } @@ -5812,58 +5821,13 @@ void GLES2DecoderImpl::DoCompileShader(GLuint client_id) { if (!info) { return; } - // Translate GL ES 2.0 shader to Desktop GL shader and pass that to - // glShaderSource and then glCompileShader. - const char* shader_src = info->source() ? info->source()->c_str() : ""; ShaderTranslator* translator = NULL; if (use_shader_translator_) { translator = info->shader_type() == GL_VERTEX_SHADER ? vertex_translator_.get() : fragment_translator_.get(); - - if (!translator->Translate(shader_src)) { - info->SetStatus(false, translator->info_log(), NULL); - return; - } - shader_src = translator->translated_shader(); - if (!feature_info_->feature_flags().angle_translated_shader_source) - info->UpdateTranslatedSource(shader_src); - } - - glShaderSource(info->service_id(), 1, &shader_src, NULL); - glCompileShader(info->service_id()); - if (feature_info_->feature_flags().angle_translated_shader_source) { - GLint max_len = 0; - glGetShaderiv(info->service_id(), - GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE, - &max_len); - scoped_array<char> temp(new char[max_len]); - GLint len = 0; - glGetTranslatedShaderSourceANGLE( - info->service_id(), max_len, &len, temp.get()); - DCHECK(max_len == 0 || len < max_len); - DCHECK(len == 0 || temp[len] == '\0'); - info->UpdateTranslatedSource(temp.get()); - } - - GLint status = GL_FALSE; - glGetShaderiv(info->service_id(), GL_COMPILE_STATUS, &status); - if (status) { - info->SetStatus(true, "", translator); - } else { - // We cannot reach here if we are using the shader translator. - // All invalid shaders must be rejected by the translator. - // All translated shaders must compile. - LOG_IF(ERROR, use_shader_translator_) - << "Shader translator allowed/produced an invalid shader."; - GLint max_len = 0; - glGetShaderiv(info->service_id(), GL_INFO_LOG_LENGTH, &max_len); - scoped_array<char> temp(new char[max_len]); - GLint len = 0; - glGetShaderInfoLog(info->service_id(), max_len, &len, temp.get()); - DCHECK(max_len == 0 || len < max_len); - DCHECK(len == 0 || temp[len] == '\0'); - info->SetStatus(false, std::string(temp.get(), len).c_str(), NULL); } + + program_manager()->DoCompileShader(info, translator, feature_info_); }; void GLES2DecoderImpl::DoGetShaderiv( diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_1.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_1.cc index 0484183..5039cd6 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_1.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_1.cc @@ -238,7 +238,7 @@ void GLES2DecoderTestBase::SpecializedSetup<GetProgramInfoLog, 0>( attach_cmd.Init(client_program_id_, kClientFragmentShaderId); EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd)); - info->Link(); + info->Link(NULL, NULL, NULL, NULL); }; template <> diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc index dd73b65..b865db4 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc @@ -6,6 +6,7 @@ #include <algorithm> #include <string> +#include <vector> #include "base/string_number_conversions.h" #include "gpu/command_buffer/common/gl_mock.h" @@ -874,8 +875,7 @@ void GLES2DecoderTestBase::DoTexImage2DSameSize( GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, uint32 shared_memory_id, uint32 shared_memory_offset) { - if (GLES2Decoder::IsAngle()) - { + if (GLES2Decoder::IsAngle()) { EXPECT_CALL(*gl_, TexSubImage2D( target, level, 0, 0, width, height, format, type, _)) .Times(1) diff --git a/gpu/command_buffer/service/memory_program_cache.cc b/gpu/command_buffer/service/memory_program_cache.cc new file mode 100644 index 0000000..0a061cb --- /dev/null +++ b/gpu/command_buffer/service/memory_program_cache.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2012 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 "gpu/command_buffer/service/memory_program_cache.h" + +#include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "base/sha1.h" +#include "base/string_number_conversions.h" +#include "gpu/command_buffer/service/gl_utils.h" +#include "gpu/command_buffer/service/gpu_switches.h" +#include "ui/gl/gl_bindings.h" + +namespace { +size_t GetCacheSize() { + size_t size; + const CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kGpuProgramCacheSizeKb) && + base::StringToSizeT(command_line->GetSwitchValueNative( + switches::kGpuProgramCacheSizeKb), + &size)) { + return size; + } + return gpu::gles2::MemoryProgramCache::kDefaultMaxProgramCacheMemoryBytes; +} +} // anonymous namespace + +namespace gpu { +namespace gles2 { + +MemoryProgramCache::MemoryProgramCache() + : max_size_bytes_(GetCacheSize()), + curr_size_bytes_(0) { } + +MemoryProgramCache::MemoryProgramCache(const size_t max_cache_size_bytes) + : max_size_bytes_(max_cache_size_bytes), + curr_size_bytes_(0) {} + +MemoryProgramCache::~MemoryProgramCache() {} + +void MemoryProgramCache::ClearBackend() { + curr_size_bytes_ = 0; + store_.clear(); + eviction_helper_.Clear(); +} + +ProgramCache::ProgramLoadResult MemoryProgramCache::LoadLinkedProgram( + GLuint program, + ShaderManager::ShaderInfo* shader_a, + ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map) const { + char a_sha[kHashLength]; + char b_sha[kHashLength]; + ComputeShaderHash(*shader_a->deferred_compilation_source(), a_sha); + ComputeShaderHash(*shader_b->deferred_compilation_source(), b_sha); + + char sha[kHashLength]; + ComputeProgramHash(a_sha, + b_sha, + bind_attrib_location_map, + sha); + const std::string sha_string(sha, kHashLength); + + StoreMap::const_iterator found = store_.find(sha_string); + if (found == store_.end()) { + return PROGRAM_LOAD_FAILURE; + } + const scoped_refptr<ProgramCacheValue> value = found->second; + glProgramBinary(program, + value->format, + static_cast<const GLvoid*>(value->data.get()), + value->length); + GLint success = 0; + glGetProgramiv(program, GL_LINK_STATUS, &success); + if (success == GL_FALSE) { + return PROGRAM_LOAD_FAILURE; + } + shader_a->set_attrib_map(value->attrib_map_0); + shader_a->set_uniform_map(value->uniform_map_0); + shader_b->set_attrib_map(value->attrib_map_1); + shader_b->set_uniform_map(value->uniform_map_1); + return PROGRAM_LOAD_SUCCESS; +} + +void MemoryProgramCache::SaveLinkedProgram( + GLuint program, + const ShaderManager::ShaderInfo* shader_a, + const ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map) { + GLsizei length; + GLenum format; + GLsizei buffer_length = 0; + glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH_OES, &buffer_length); + if (static_cast<unsigned int>(buffer_length) > max_size_bytes_) { + return; + } + scoped_array<char> binary(new char[buffer_length]); + glGetProgramBinary(program, + buffer_length, + &length, + &format, + binary.get()); + if (length == 0) { + return; + } + + char a_sha[kHashLength]; + char b_sha[kHashLength]; + ComputeShaderHash(*shader_a->deferred_compilation_source(), a_sha); + ComputeShaderHash(*shader_b->deferred_compilation_source(), b_sha); + + char sha[kHashLength]; + ComputeProgramHash(a_sha, + b_sha, + bind_attrib_location_map, + sha); + const std::string sha_string(sha, sizeof(sha)); + + if (store_.find(sha_string) != store_.end()) { + return; + } + + while (curr_size_bytes_ + length > max_size_bytes_) { + DCHECK(!eviction_helper_.IsEmpty()); + const std::string* program = eviction_helper_.PeekKey(); + const StoreMap::iterator found = store_.find(*program); + const ProgramCacheValue* evicting = found->second.get(); + curr_size_bytes_ -= evicting->length; + Evict(*program, evicting->shader_0_hash, evicting->shader_1_hash); + store_.erase(found); + eviction_helper_.PopKey(); + } + store_[sha_string] = new ProgramCacheValue(length, + format, + binary.release(), + a_sha, + shader_a->attrib_map(), + shader_a->uniform_map(), + b_sha, + shader_b->attrib_map(), + shader_b->uniform_map()); + curr_size_bytes_ += length; + eviction_helper_.KeyUsed(sha_string); + LinkedProgramCacheSuccess(sha_string, + std::string(a_sha, kHashLength), + std::string(b_sha, kHashLength)); +} + +MemoryProgramCache::ProgramCacheValue::ProgramCacheValue( + GLsizei _length, + GLenum _format, + const char* _data, + const char* _shader_0_hash, + const ShaderTranslator::VariableMap& _attrib_map_0, + const ShaderTranslator::VariableMap& _uniform_map_0, + const char* _shader_1_hash, + const ShaderTranslator::VariableMap& _attrib_map_1, + const ShaderTranslator::VariableMap& _uniform_map_1) + : length(_length), + format(_format), + data(_data), + shader_0_hash(_shader_0_hash, kHashLength), + attrib_map_0(_attrib_map_0), + uniform_map_0(_uniform_map_0), + shader_1_hash(_shader_1_hash, kHashLength), + attrib_map_1(_attrib_map_1), + uniform_map_1(_uniform_map_1) {} + +MemoryProgramCache::ProgramCacheValue::~ProgramCacheValue() {} + +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/memory_program_cache.h b/gpu/command_buffer/service/memory_program_cache.h new file mode 100644 index 0000000..222e61e --- /dev/null +++ b/gpu/command_buffer/service/memory_program_cache.h @@ -0,0 +1,88 @@ +// Copyright (c) 2012 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. + +#ifndef GPU_COMMAND_BUFFER_SERVICE_MEMORY_PROGRAM_CACHE_H_ +#define GPU_COMMAND_BUFFER_SERVICE_MEMORY_PROGRAM_CACHE_H_ + +#include <map> +#include <string> + +#include "base/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/service/program_cache.h" +#include "gpu/command_buffer/service/program_cache_lru_helper.h" +#include "gpu/command_buffer/service/shader_translator.h" + +namespace gpu { +namespace gles2 { + +// Program cache that stores binaries completely in-memory +class GPU_EXPORT MemoryProgramCache : public ProgramCache { + public: + static const size_t kDefaultMaxProgramCacheMemoryBytes = 6 * 1024 * 1024; + + MemoryProgramCache(); + explicit MemoryProgramCache(const size_t max_cache_size_bytes); + virtual ~MemoryProgramCache(); + + virtual ProgramLoadResult LoadLinkedProgram( + GLuint program, + ShaderManager::ShaderInfo* shader_a, + ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map) const OVERRIDE; + virtual void SaveLinkedProgram( + GLuint program, + const ShaderManager::ShaderInfo* shader_a, + const ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map) OVERRIDE; + + private: + virtual void ClearBackend() OVERRIDE; + + struct ProgramCacheValue : public base::RefCounted<ProgramCacheValue> { + public: + ProgramCacheValue(GLsizei _length, + GLenum _format, + const char* _data, + const char* _shader_0_hash, + const ShaderTranslator::VariableMap& _attrib_map_0, + const ShaderTranslator::VariableMap& _uniform_map_0, + const char* _shader_1_hash, + const ShaderTranslator::VariableMap& _attrib_map_1, + const ShaderTranslator::VariableMap& _uniform_map_1); + const GLsizei length; + const GLenum format; + const scoped_array<const char> data; + const std::string shader_0_hash; + const ShaderTranslator::VariableMap attrib_map_0; + const ShaderTranslator::VariableMap uniform_map_0; + const std::string shader_1_hash; + const ShaderTranslator::VariableMap attrib_map_1; + const ShaderTranslator::VariableMap uniform_map_1; + + protected: + friend class base::RefCounted<ProgramCacheValue>; + + ~ProgramCacheValue(); + + private: + DISALLOW_COPY_AND_ASSIGN(ProgramCacheValue); + }; + + typedef base::hash_map<std::string, + scoped_refptr<ProgramCacheValue> > StoreMap; + + const size_t max_size_bytes_; + size_t curr_size_bytes_; + StoreMap store_; + ProgramCacheLruHelper eviction_helper_; + + DISALLOW_COPY_AND_ASSIGN(MemoryProgramCache); +}; + +} // namespace gles2 +} // namespace gpu + +#endif // GPU_COMMAND_BUFFER_SERVICE_MEMORY_PROGRAM_CACHE_H_ diff --git a/gpu/command_buffer/service/memory_program_cache_unittest.cc b/gpu/command_buffer/service/memory_program_cache_unittest.cc new file mode 100644 index 0000000..5d40785 --- /dev/null +++ b/gpu/command_buffer/service/memory_program_cache_unittest.cc @@ -0,0 +1,417 @@ +// Copyright (c) 2012 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 "gpu/command_buffer/service/memory_program_cache.h" + +#include "gpu/command_buffer/common/gl_mock.h" +#include "gpu/command_buffer/common/gles2_cmd_format.h" +#include "gpu/command_buffer/service/gl_utils.h" +#include "gpu/command_buffer/service/shader_translator.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gl/gl_bindings.h" + +using ::testing::_; +using ::testing::ElementsAreArray; +using ::testing::Invoke; +using ::testing::SetArgPointee; +using ::testing::SetArrayArgument; + +namespace { +typedef gpu::gles2::ShaderTranslator::VariableMap VariableMap; +} // anonymous namespace + +namespace gpu { +namespace gles2 { + +class ProgramBinaryEmulator { + public: + ProgramBinaryEmulator(GLsizei length, + GLenum format, + const char* binary) + : length_(length), + format_(format), + binary_(binary) { } + + void GetProgramBinary(GLuint program, + GLsizei buffer_size, + GLsizei* length, + GLenum* format, + GLvoid* binary) { + *length = length_; + *format = format_; + memcpy(binary, binary_, length_); + } + + void ProgramBinary(GLuint program, + GLenum format, + const GLvoid* binary, + GLsizei length) { + // format and length are verified by matcher + EXPECT_EQ(0, memcmp(binary_, binary, length)); + } + + GLsizei length() const { return length_; } + GLenum format() const { return format_; } + const char* binary() const { return binary_; } + + private: + GLsizei length_; + GLenum format_; + const char* binary_; +}; + +class MemoryProgramCacheTest : public testing::Test { + public: + static const size_t kCacheSizeBytes = 1024; + static const GLuint kVertexShaderClientId = 90; + static const GLuint kVertexShaderServiceId = 100; + static const GLuint kFragmentShaderClientId = 91; + static const GLuint kFragmentShaderServiceId = 100; + + MemoryProgramCacheTest() + : cache_(new MemoryProgramCache(kCacheSizeBytes)), + vertex_shader_(NULL), + fragment_shader_(NULL) { } + ~MemoryProgramCacheTest() { + shader_manager_.Destroy(false); + } + + protected: + virtual void SetUp() { + gl_.reset(new ::testing::StrictMock<gfx::MockGLInterface>()); + ::gfx::GLInterface::SetGLInterface(gl_.get()); + + vertex_shader_ = shader_manager_.CreateShaderInfo(kVertexShaderClientId, + kVertexShaderServiceId, + GL_VERTEX_SHADER); + fragment_shader_ = shader_manager_.CreateShaderInfo( + kFragmentShaderClientId, + kFragmentShaderServiceId, + GL_FRAGMENT_SHADER); + ASSERT_TRUE(vertex_shader_ != NULL); + ASSERT_TRUE(fragment_shader_ != NULL); + typedef ShaderTranslatorInterface::VariableInfo VariableInfo; + typedef ShaderTranslator::VariableMap VariableMap; + VariableMap vertex_attrib_map; + VariableMap vertex_uniform_map; + VariableMap fragment_attrib_map; + VariableMap fragment_uniform_map; + + vertex_attrib_map["a"] = VariableInfo(1, 34, "a"); + vertex_uniform_map["a"] = VariableInfo(0, 10, "a"); + vertex_uniform_map["b"] = VariableInfo(2, 3114, "b"); + fragment_attrib_map["jjjbb"] = VariableInfo(463, 1114, "jjjbb"); + fragment_uniform_map["k"] = VariableInfo(10, 34413, "k"); + + vertex_shader_->set_attrib_map(vertex_attrib_map); + vertex_shader_->set_uniform_map(vertex_uniform_map); + fragment_shader_->set_attrib_map(vertex_attrib_map); + fragment_shader_->set_uniform_map(vertex_uniform_map); + + vertex_shader_->UpdateSource("bbbalsldkdkdkd"); + fragment_shader_->UpdateSource("bbbal sldkdkdkas 134 ad"); + vertex_shader_->FlagSourceAsCompiled(true); + fragment_shader_->FlagSourceAsCompiled(true); + + vertex_shader_->SetStatus(true, NULL, NULL); + fragment_shader_->SetStatus(true, NULL, NULL); + } + + virtual void TearDown() { + ::gfx::GLInterface::SetGLInterface(NULL); + gl_.reset(); + } + + void SetExpectationsForSaveLinkedProgram( + const GLint program_id, + ProgramBinaryEmulator* emulator) const { + EXPECT_CALL(*gl_.get(), + GetProgramiv(program_id, GL_PROGRAM_BINARY_LENGTH_OES, _)) + .WillOnce(SetArgPointee<2>(emulator->length())); + EXPECT_CALL(*gl_.get(), + GetProgramBinary(program_id, emulator->length(), _, _, _)) + .WillOnce(Invoke(emulator, &ProgramBinaryEmulator::GetProgramBinary)); + } + + void SetExpectationsForLoadLinkedProgram( + const GLint program_id, + ProgramBinaryEmulator* emulator) const { + EXPECT_CALL(*gl_.get(), + ProgramBinary(program_id, + emulator->format(), + _, + emulator->length())) + .WillOnce(Invoke(emulator, &ProgramBinaryEmulator::ProgramBinary)); + EXPECT_CALL(*gl_.get(), + GetProgramiv(program_id, GL_LINK_STATUS, _)) + .WillOnce(SetArgPointee<2>(GL_TRUE)); + } + + void SetExpectationsForLoadLinkedProgramFailure( + const GLint program_id, + ProgramBinaryEmulator* emulator) const { + EXPECT_CALL(*gl_.get(), + ProgramBinary(program_id, + emulator->format(), + _, + emulator->length())) + .WillOnce(Invoke(emulator, &ProgramBinaryEmulator::ProgramBinary)); + EXPECT_CALL(*gl_.get(), + GetProgramiv(program_id, GL_LINK_STATUS, _)) + .WillOnce(SetArgPointee<2>(GL_FALSE)); + } + + // Use StrictMock to make 100% sure we know how GL will be called. + scoped_ptr< ::testing::StrictMock<gfx::MockGLInterface> > gl_; + scoped_ptr<MemoryProgramCache> cache_; + ShaderManager shader_manager_; + ShaderManager::ShaderInfo* vertex_shader_; + ShaderManager::ShaderInfo* fragment_shader_; +}; + +TEST_F(MemoryProgramCacheTest, CacheSave) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator(kBinaryLength, kFormat, test_binary); + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, cache_->GetLinkedProgramStatus( + *vertex_shader_->deferred_compilation_source(), + *fragment_shader_->deferred_compilation_source(), + NULL)); +} + +TEST_F(MemoryProgramCacheTest, CacheLoadMatchesSave) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator(kBinaryLength, kFormat, test_binary); + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + VariableMap vertex_attrib_map = vertex_shader_->attrib_map(); + VariableMap vertex_uniform_map = vertex_shader_->uniform_map(); + VariableMap fragment_attrib_map = fragment_shader_->attrib_map(); + VariableMap fragment_uniform_map = fragment_shader_->uniform_map(); + + vertex_shader_->set_attrib_map(VariableMap()); + vertex_shader_->set_uniform_map(VariableMap()); + fragment_shader_->set_attrib_map(VariableMap()); + fragment_shader_->set_uniform_map(VariableMap()); + + SetExpectationsForLoadLinkedProgram(kProgramId, &emulator); + + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_SUCCESS, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + NULL)); + + // apparently the hash_map implementation on android doesn't have the + // equality operator +#if !defined(OS_ANDROID) + EXPECT_EQ(vertex_attrib_map, vertex_shader_->attrib_map()); + EXPECT_EQ(vertex_attrib_map, vertex_shader_->uniform_map()); + EXPECT_EQ(vertex_attrib_map, fragment_shader_->attrib_map()); + EXPECT_EQ(vertex_attrib_map, fragment_shader_->uniform_map()); +#endif +} + +TEST_F(MemoryProgramCacheTest, LoadFailOnLinkFalse) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator(kBinaryLength, kFormat, test_binary); + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + SetExpectationsForLoadLinkedProgramFailure(kProgramId, &emulator); + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + NULL)); +} + +TEST_F(MemoryProgramCacheTest, LoadFailOnDifferentSource) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator(kBinaryLength, kFormat, test_binary); + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + const std::string vertex_orig_source = + *vertex_shader_->deferred_compilation_source(); + vertex_shader_->UpdateSource("different!"); + vertex_shader_->FlagSourceAsCompiled(true); + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + NULL)); + + vertex_shader_->UpdateSource(vertex_orig_source.c_str()); + vertex_shader_->FlagSourceAsCompiled(true); + fragment_shader_->UpdateSource("different!"); + fragment_shader_->FlagSourceAsCompiled(true); + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + NULL)); +} + +TEST_F(MemoryProgramCacheTest, LoadFailOnDifferentMap) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator(kBinaryLength, kFormat, test_binary); + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); + ProgramCache::LocationMap binding_map; + binding_map["test"] = 512; + cache_->SaveLinkedProgram(kProgramId, + vertex_shader_, + fragment_shader_, + &binding_map); + + binding_map["different!"] = 59; + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + &binding_map)); + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + NULL)); +} + +TEST_F(MemoryProgramCacheTest, MemoryProgramCacheEviction) { + typedef ShaderTranslator::VariableMap VariableMap; + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator1(kBinaryLength, kFormat, test_binary); + + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator1); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + const int kEvictingProgramId = 11; + const GLuint kEvictingBinaryLength = kCacheSizeBytes - kBinaryLength + 1; + + // save old source and modify for new program + const std::string old_source = + *fragment_shader_->deferred_compilation_source(); + fragment_shader_->UpdateSource("al sdfkjdk"); + fragment_shader_->FlagSourceAsCompiled(true); + + scoped_array<char> bigTestBinary = + scoped_array<char>(new char[kEvictingBinaryLength]); + for (size_t i = 0; i < kEvictingBinaryLength; ++i) { + bigTestBinary[i] = i % 250; + } + ProgramBinaryEmulator emulator2(kEvictingBinaryLength, + kFormat, + bigTestBinary.get()); + + SetExpectationsForSaveLinkedProgram(kEvictingProgramId, &emulator2); + cache_->SaveLinkedProgram(kEvictingProgramId, + vertex_shader_, + fragment_shader_, + NULL); + + EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, cache_->GetLinkedProgramStatus( + *vertex_shader_->deferred_compilation_source(), + *fragment_shader_->deferred_compilation_source(), + NULL)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, cache_->GetLinkedProgramStatus( + old_source, + *fragment_shader_->deferred_compilation_source(), + NULL)); +} + +TEST_F(MemoryProgramCacheTest, SaveCorrectProgram) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator1(kBinaryLength, kFormat, test_binary); + + vertex_shader_->UpdateSource("different!"); + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator1); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, cache_->GetLinkedProgramStatus( + *vertex_shader_->deferred_compilation_source(), + *fragment_shader_->deferred_compilation_source(), + NULL)); +} + +TEST_F(MemoryProgramCacheTest, LoadCorrectProgram) { + const GLenum kFormat = 1; + const int kProgramId = 10; + const int kBinaryLength = 20; + char test_binary[kBinaryLength]; + for (int i = 0; i < kBinaryLength; ++i) { + test_binary[i] = i; + } + ProgramBinaryEmulator emulator(kBinaryLength, kFormat, test_binary); + + SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); + cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, NULL); + + EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, cache_->GetLinkedProgramStatus( + *vertex_shader_->deferred_compilation_source(), + *fragment_shader_->deferred_compilation_source(), + NULL)); + + SetExpectationsForLoadLinkedProgram(kProgramId, &emulator); + + fragment_shader_->UpdateSource("different!"); + EXPECT_EQ(ProgramCache::PROGRAM_LOAD_SUCCESS, cache_->LoadLinkedProgram( + kProgramId, + vertex_shader_, + fragment_shader_, + NULL)); +} + +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/mocks.cc b/gpu/command_buffer/service/mocks.cc index 46a8977..a71c019 100644 --- a/gpu/command_buffer/service/mocks.cc +++ b/gpu/command_buffer/service/mocks.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -33,5 +33,8 @@ MockShaderTranslator::MockShaderTranslator() {} MockShaderTranslator::~MockShaderTranslator() {} +MockProgramCache::MockProgramCache() {} +MockProgramCache::~MockProgramCache() {} + } // namespace gles2 } // namespace gpu diff --git a/gpu/command_buffer/service/mocks.h b/gpu/command_buffer/service/mocks.h index 01dcbd8..ecfcd9d 100644 --- a/gpu/command_buffer/service/mocks.h +++ b/gpu/command_buffer/service/mocks.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -15,6 +15,7 @@ #include "base/logging.h" #include "gpu/command_buffer/service/cmd_parser.h" #include "gpu/command_buffer/service/cmd_buffer_engine.h" +#include "gpu/command_buffer/service/program_cache.h" #include "gpu/command_buffer/service/shader_translator.h" #include "testing/gmock/include/gmock/gmock.h" @@ -89,6 +90,26 @@ class MockShaderTranslator : public ShaderTranslatorInterface { MOCK_CONST_METHOD0(uniform_map, const VariableMap&()); }; +class MockProgramCache : public ProgramCache { + public: + MockProgramCache(); + virtual ~MockProgramCache(); + + MOCK_CONST_METHOD4(LoadLinkedProgram, ProgramLoadResult( + GLuint program, + ShaderManager::ShaderInfo* shader_a, + ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map)); + + MOCK_METHOD4(SaveLinkedProgram, void( + GLuint program, + const ShaderManager::ShaderInfo* shader_a, + const ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map)); + private: + MOCK_METHOD0(ClearBackend, void()); +}; + } // namespace gles2 } // namespace gpu diff --git a/gpu/command_buffer/service/program_cache.cc b/gpu/command_buffer/service/program_cache.cc new file mode 100644 index 0000000..55afa86 --- /dev/null +++ b/gpu/command_buffer/service/program_cache.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2012 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 "gpu/command_buffer/service/program_cache.h" + +#include "base/memory/scoped_ptr.h" + +namespace gpu { +namespace gles2 { + +ProgramCache::ProgramCache() {} +ProgramCache::~ProgramCache() {} + +void ProgramCache::Clear() { + shader_status_.clear(); + link_status_.clear(); + ClearBackend(); +} + +ProgramCache::CompiledShaderStatus ProgramCache::GetShaderCompilationStatus( + const std::string& shader_src) const { + char sha[kHashLength]; + ComputeShaderHash(shader_src, sha); + const std::string sha_string(sha, kHashLength); + + CompileStatusMap::const_iterator found = shader_status_.find(sha_string); + + if (found == shader_status_.end()) { + return ProgramCache::COMPILATION_UNKNOWN; + } else { + return found->second.status; + } +} + +void ProgramCache::ShaderCompilationSucceeded( + const std::string& shader_src) { + char sha[kHashLength]; + ComputeShaderHash(shader_src, sha); + const std::string sha_string(sha, kHashLength); + + CompileStatusMap::iterator it = shader_status_.find(sha_string); + if (it == shader_status_.end()) { + shader_status_[sha_string] = CompiledShaderInfo(COMPILATION_SUCCEEDED); + } else { + it->second.status = COMPILATION_SUCCEEDED; + } +} + +ProgramCache::LinkedProgramStatus ProgramCache::GetLinkedProgramStatus( + const std::string& untranslated_a, + const std::string& untranslated_b, + const std::map<std::string, GLint>* bind_attrib_location_map) const { + char a_sha[kHashLength]; + char b_sha[kHashLength]; + ComputeShaderHash(untranslated_a, a_sha); + ComputeShaderHash(untranslated_b, b_sha); + + char sha[kHashLength]; + ComputeProgramHash(a_sha, + b_sha, + bind_attrib_location_map, + sha); + const std::string sha_string(sha, kHashLength); + + LinkStatusMap::const_iterator found = link_status_.find(sha_string); + if (found == link_status_.end()) { + return ProgramCache::LINK_UNKNOWN; + } else { + return found->second; + } +} + +void ProgramCache::LinkedProgramCacheSuccess( + const std::string& shader_a, + const std::string& shader_b, + const LocationMap* bind_attrib_location_map) { + char a_sha[kHashLength]; + char b_sha[kHashLength]; + ComputeShaderHash(shader_a, a_sha); + ComputeShaderHash(shader_b, b_sha); + char sha[kHashLength]; + ComputeProgramHash(a_sha, + b_sha, + bind_attrib_location_map, + sha); + const std::string sha_string(sha, kHashLength); + + LinkedProgramCacheSuccess(sha_string, + std::string(a_sha, kHashLength), + std::string(b_sha, kHashLength)); +} + +void ProgramCache::LinkedProgramCacheSuccess(const std::string& program_hash, + const std::string& shader_a_hash, + const std::string& shader_b_hash) { + link_status_[program_hash] = LINK_SUCCEEDED; + shader_status_[shader_a_hash].ref_count++; + shader_status_[shader_b_hash].ref_count++; +} + +void ProgramCache::ComputeShaderHash(const std::string& str, + char* result) const { + base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(str.c_str()), + str.length(), reinterpret_cast<unsigned char*>(result)); +} + +void ProgramCache::Evict(const std::string& program_hash, + const std::string& shader_0_hash, + const std::string& shader_1_hash) { + CompileStatusMap::iterator info0 = shader_status_.find(shader_0_hash); + CompileStatusMap::iterator info1 = shader_status_.find(shader_1_hash); + DCHECK(info0 != shader_status_.end()); + DCHECK(info1 != shader_status_.end()); + DCHECK(info0->second.ref_count > 0); + DCHECK(info1->second.ref_count > 0); + if (--info0->second.ref_count <= 0) { + shader_status_.erase(shader_0_hash); + } + if (--info1->second.ref_count <= 0) { + shader_status_.erase(shader_1_hash); + } + link_status_.erase(program_hash); +} + +namespace { +size_t CalculateMapSize(const std::map<std::string, GLint>* map) { + if (!map) { + return 0; + } + std::map<std::string, GLint>::const_iterator it; + size_t total = 0; + for (it = map->begin(); it != map->end(); ++it) { + total += 4 + it->first.length(); + } + return total; +} +} // anonymous namespace + +void ProgramCache::ComputeProgramHash( + const char* hashed_shader_0, + const char* hashed_shader_1, + const std::map<std::string, GLint>* bind_attrib_location_map, + char* result) const { + const size_t shader0_size = kHashLength; + const size_t shader1_size = kHashLength; + const size_t map_size = CalculateMapSize(bind_attrib_location_map); + const size_t total_size = shader0_size + shader1_size + map_size; + + scoped_array<unsigned char> buffer(new unsigned char[total_size]); + memcpy(buffer.get(), hashed_shader_0, shader0_size); + memcpy(&buffer[shader0_size], hashed_shader_1, shader1_size); + if (map_size != 0) { + // copy our map + size_t current_pos = shader0_size + shader1_size; + std::map<std::string, GLint>::const_iterator it; + for (it = bind_attrib_location_map->begin(); + it != bind_attrib_location_map->end(); + ++it) { + const size_t name_size = it->first.length(); + memcpy(&buffer.get()[current_pos], it->first.c_str(), name_size); + current_pos += name_size; + const GLint value = it->second; + buffer[current_pos++] = value >> 24; + buffer[current_pos++] = value >> 16; + buffer[current_pos++] = value >> 8; + buffer[current_pos++] = value; + } + } + base::SHA1HashBytes(buffer.get(), + total_size, reinterpret_cast<unsigned char*>(result)); +} + +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/program_cache.h b/gpu/command_buffer/service/program_cache.h new file mode 100644 index 0000000..310908b --- /dev/null +++ b/gpu/command_buffer/service/program_cache.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012 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. + +#ifndef GPU_COMMAND_BUFFER_SERVICE_PROGRAM_CACHE_H_ +#define GPU_COMMAND_BUFFER_SERVICE_PROGRAM_CACHE_H_ + +#include <map> +#include <string> + +#include "base/hash_tables.h" +#include "base/sha1.h" +#include "gpu/command_buffer/common/gles2_cmd_format.h" +#include "gpu/command_buffer/service/shader_manager.h" + +namespace gpu { +namespace gles2 { + +// Program cache base class for caching linked gpu programs +class GPU_EXPORT ProgramCache { + public: + static const size_t kHashLength = base::kSHA1Length; + + typedef std::map<std::string, GLint> LocationMap; + + enum CompiledShaderStatus { + COMPILATION_UNKNOWN, + COMPILATION_SUCCEEDED + }; + + enum LinkedProgramStatus { + LINK_UNKNOWN, + LINK_SUCCEEDED + }; + + enum ProgramLoadResult { + PROGRAM_LOAD_FAILURE, + PROGRAM_LOAD_SUCCESS + }; + + ProgramCache(); + virtual ~ProgramCache(); + + CompiledShaderStatus GetShaderCompilationStatus( + const std::string& shader_src) const; + void ShaderCompilationSucceeded(const std::string& shader_src); + + LinkedProgramStatus GetLinkedProgramStatus( + const std::string& untranslated_a, + const std::string& untranslated_b, + const LocationMap* bind_attrib_location_map) const; + + // Loads the linked program from the cache. If the program is not found or + // there was an error, PROGRAM_LOAD_FAILURE should be returned. + virtual ProgramLoadResult LoadLinkedProgram( + GLuint program, + ShaderManager::ShaderInfo* shader_a, + ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map) const = 0; + + // Saves the program into the cache. If successful, the implementation should + // call LinkedProgramCacheSuccess. + virtual void SaveLinkedProgram( + GLuint program, + const ShaderManager::ShaderInfo* shader_a, + const ShaderManager::ShaderInfo* shader_b, + const LocationMap* bind_attrib_location_map) = 0; + + // clears the cache + void Clear(); + + // Only for testing + void LinkedProgramCacheSuccess(const std::string& shader_a, + const std::string& shader_b, + const LocationMap* bind_attrib_location_map); + + protected: + // called by implementing class after a shader was successfully cached + void LinkedProgramCacheSuccess(const std::string& program_hash, + const std::string& shader_a_hash, + const std::string& shader_b_hash); + + // result is not null terminated + void ComputeShaderHash(const std::string& shader, + char* result) const; + + // result is not null terminated. hashed shaders are expected to be + // kHashLength in length + void ComputeProgramHash( + const char* hashed_shader_0, + const char* hashed_shader_1, + const LocationMap* bind_attrib_location_map, + char* result) const; + + void Evict(const std::string& program_hash, + const std::string& shader_0_hash, + const std::string& shader_1_hash); + + private: + struct CompiledShaderInfo { + CompiledShaderInfo() : status(COMPILATION_UNKNOWN), ref_count(0) { } + explicit CompiledShaderInfo(CompiledShaderStatus status_) + : status(status_), + ref_count(0) { } + + CompiledShaderStatus status; + size_t ref_count; + }; + + typedef base::hash_map<std::string, + CompiledShaderInfo> CompileStatusMap; + typedef base::hash_map<std::string, + LinkedProgramStatus> LinkStatusMap; + + // called to clear the backend cache + virtual void ClearBackend() = 0; + + CompileStatusMap shader_status_; + LinkStatusMap link_status_; + + DISALLOW_COPY_AND_ASSIGN(ProgramCache); +}; + +} // namespace gles2 +} // namespace gpu + +#endif // GPU_COMMAND_BUFFER_SERVICE_PROGRAM_CACHE_H_ diff --git a/gpu/command_buffer/service/program_cache_lru_helper.cc b/gpu/command_buffer/service/program_cache_lru_helper.cc new file mode 100644 index 0000000..a1f4555 --- /dev/null +++ b/gpu/command_buffer/service/program_cache_lru_helper.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2012 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 "gpu/command_buffer/service/program_cache_lru_helper.h" + +namespace gpu { +namespace gles2 { + +ProgramCacheLruHelper::ProgramCacheLruHelper() {} +ProgramCacheLruHelper::~ProgramCacheLruHelper() {} + +void ProgramCacheLruHelper::Clear() { + location_map.clear(); + queue.clear(); +} + +bool ProgramCacheLruHelper::IsEmpty() { + return queue.empty(); +} + +void ProgramCacheLruHelper::KeyUsed(const std::string& key) { + IteratorMap::iterator location_iterator = location_map.find(key); + if (location_iterator != location_map.end()) { + // already exists, erase it + queue.erase(location_iterator->second); + } + queue.push_front(key); + location_map[key] = queue.begin(); +} + +const std::string* ProgramCacheLruHelper::PeekKey() { + if (queue.empty()) { + return NULL; + } + return &queue.back(); +} + +void ProgramCacheLruHelper::PopKey() { + if (queue.empty()) { + return; + } + const std::string& last = queue.back(); + location_map.erase(last); + queue.pop_back(); +} + +} // namespace gpu +} // namespace gles2 diff --git a/gpu/command_buffer/service/program_cache_lru_helper.h b/gpu/command_buffer/service/program_cache_lru_helper.h new file mode 100644 index 0000000..8560305 --- /dev/null +++ b/gpu/command_buffer/service/program_cache_lru_helper.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012 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. + +#ifndef GPU_COMMAND_BUFFER_SERVICE_PROGRAM_CACHE_LRU_HELPER_H_ +#define GPU_COMMAND_BUFFER_SERVICE_PROGRAM_CACHE_LRU_HELPER_H_ + +#include <list> +#include <string> + +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "gpu/gpu_export.h" + +namespace gpu { +namespace gles2 { + +// LRU helper for the program cache, operates in O(1) time. +// This class uses a linked list with a hash map. Both copy their string keys, +// so be mindful that keys you insert will be stored again twice in memory. +class GPU_EXPORT ProgramCacheLruHelper { + public: + ProgramCacheLruHelper(); + ~ProgramCacheLruHelper(); + + // clears the lru queue + void Clear(); + // returns true if the lru queue is empty + bool IsEmpty(); + // inserts or refreshes a key in the queue + void KeyUsed(const std::string& key); + // Peeks at the next key. Use IsEmpty() first (if the queue is empty then + // null is returned). + const std::string* PeekKey(); + // evicts the next key from the queue. + void PopKey(); + + private: + typedef std::list<std::string> StringList; + typedef base::hash_map<std::string, + StringList::iterator> IteratorMap; + StringList queue; + IteratorMap location_map; + + DISALLOW_COPY_AND_ASSIGN(ProgramCacheLruHelper); +}; + +} // namespace gles2 +} // namespace gpu + +#endif // GPU_COMMAND_BUFFER_SERVICE_PROGRAM_CACHE_LRU_HELPER_H_ diff --git a/gpu/command_buffer/service/program_cache_lru_helper_unittest.cc b/gpu/command_buffer/service/program_cache_lru_helper_unittest.cc new file mode 100644 index 0000000..5aaa088 --- /dev/null +++ b/gpu/command_buffer/service/program_cache_lru_helper_unittest.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2012 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 "gpu/command_buffer/service/program_cache_lru_helper.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gpu { +namespace gles2 { + +class ProgramCacheLruHelperTest : public testing::Test { + public: + ProgramCacheLruHelperTest() : + lru_helper_(new ProgramCacheLruHelper()) { } + + protected: + virtual void SetUp() { + } + + virtual void TearDown() { + lru_helper_->Clear(); + } + + scoped_ptr<ProgramCacheLruHelper> lru_helper_; +}; + +TEST_F(ProgramCacheLruHelperTest, ProgramCacheLruHelperEvictionOrderNoReuse) { + lru_helper_->KeyUsed("1"); + lru_helper_->KeyUsed("2"); + lru_helper_->KeyUsed("3"); + lru_helper_->KeyUsed("4"); + const std::string* key = lru_helper_->PeekKey(); + EXPECT_EQ("1", *key); + EXPECT_EQ("1", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_FALSE(lru_helper_->IsEmpty()); + EXPECT_EQ("2", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_FALSE(lru_helper_->IsEmpty()); + EXPECT_EQ("3", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_FALSE(lru_helper_->IsEmpty()); + EXPECT_EQ("4", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_TRUE(lru_helper_->IsEmpty()); +} + +TEST_F(ProgramCacheLruHelperTest, ProgramCacheLruHelperClear) { + EXPECT_TRUE(lru_helper_->IsEmpty()); + lru_helper_->KeyUsed("1"); + lru_helper_->KeyUsed("2"); + lru_helper_->KeyUsed("3"); + lru_helper_->KeyUsed("4"); + EXPECT_FALSE(lru_helper_->IsEmpty()); + lru_helper_->Clear(); + EXPECT_TRUE(lru_helper_->IsEmpty()); +} + +TEST_F(ProgramCacheLruHelperTest, ProgramCacheLruHelperEvictionOrderWithReuse) { + lru_helper_->KeyUsed("1"); + lru_helper_->KeyUsed("2"); + lru_helper_->KeyUsed("4"); + lru_helper_->KeyUsed("2"); + lru_helper_->KeyUsed("3"); + lru_helper_->KeyUsed("1"); + lru_helper_->KeyUsed("1"); + lru_helper_->KeyUsed("2"); + EXPECT_EQ("4", *lru_helper_->PeekKey()); + EXPECT_EQ("4", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_EQ("3", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_EQ("1", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_FALSE(lru_helper_->IsEmpty()); + EXPECT_EQ("2", *lru_helper_->PeekKey()); + lru_helper_->PopKey(); + EXPECT_TRUE(lru_helper_->IsEmpty()); +} + +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/program_cache_unittest.cc b/gpu/command_buffer/service/program_cache_unittest.cc new file mode 100644 index 0000000..eee7dab --- /dev/null +++ b/gpu/command_buffer/service/program_cache_unittest.cc @@ -0,0 +1,249 @@ +// Copyright (c) 2012 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 "gpu/command_buffer/service/program_cache.h" + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gpu { +namespace gles2 { + +class NoBackendProgramCache : public ProgramCache { + public: + virtual ProgramLoadResult LoadLinkedProgram( + GLuint /* program */, + ShaderManager::ShaderInfo* /* shader_a */, + ShaderManager::ShaderInfo* /* shader_b */, + const LocationMap* /* bind_attrib_location_map */) const OVERRIDE { + return PROGRAM_LOAD_SUCCESS; + } + virtual void SaveLinkedProgram( + GLuint /* program */, + const ShaderManager::ShaderInfo* /* shader_a */, + const ShaderManager::ShaderInfo* /* shader_b */, + const LocationMap* /* bind_attrib_location_map */) OVERRIDE { } + + virtual void ClearBackend() OVERRIDE {} + + void SaySuccessfullyCached(const std::string& shader1, + const std::string& shader2, + std::map<std::string, GLint>* attrib_map) { + char a_sha[kHashLength]; + char b_sha[kHashLength]; + ComputeShaderHash(shader1, a_sha); + ComputeShaderHash(shader2, b_sha); + + char sha[kHashLength]; + ComputeProgramHash(a_sha, + b_sha, + attrib_map, + sha); + const std::string shaString(sha, kHashLength); + + LinkedProgramCacheSuccess(shaString, + std::string(a_sha, kHashLength), + std::string(b_sha, kHashLength)); + } + + void ComputeShaderHash(const std::string& shader, + char* result) const { + ProgramCache::ComputeShaderHash(shader, result); + } + + void ComputeProgramHash(const char* hashed_shader_0, + const char* hashed_shader_1, + const LocationMap* bind_attrib_location_map, + char* result) const { + ProgramCache::ComputeProgramHash(hashed_shader_0, + hashed_shader_1, + bind_attrib_location_map, + result); + } + + void Evict(const std::string& program_hash, + const std::string& shader_0_hash, + const std::string& shader_1_hash) { + ProgramCache::Evict(program_hash, shader_0_hash, shader_1_hash); + } +}; + +class ProgramCacheTest : public testing::Test { + public: + ProgramCacheTest() : + cache_(new NoBackendProgramCache()) { } + + protected: + scoped_ptr<NoBackendProgramCache> cache_; +}; + +TEST_F(ProgramCacheTest, CompilationStatusSave) { + const std::string shader1 = "abcd1234"; + { + std::string shader = shader1; + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader)); + cache_->ShaderCompilationSucceeded(shader); + shader.clear(); + } + // make sure it was copied + EXPECT_EQ(ProgramCache::COMPILATION_SUCCEEDED, + cache_->GetShaderCompilationStatus(shader1)); +} + +TEST_F(ProgramCacheTest, CompilationUnknownOnSourceChange) { + std::string shader1 = "abcd1234"; + cache_->ShaderCompilationSucceeded(shader1); + + shader1 = "different!"; + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader1)); +} + +TEST_F(ProgramCacheTest, LinkStatusSave) { + const std::string shader1 = "abcd1234"; + const std::string shader2 = "abcda sda b1~#4 bbbbb1234"; + { + std::string shader_a = shader1; + std::string shader_b = shader2; + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader_a, shader_b, NULL)); + cache_->SaySuccessfullyCached(shader_a, shader_b, NULL); + + shader_a.clear(); + shader_b.clear(); + } + // make sure it was copied + EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); +} + +TEST_F(ProgramCacheTest, LinkUnknownOnFragmentSourceChange) { + const std::string shader1 = "abcd1234"; + std::string shader2 = "abcda sda b1~#4 bbbbb1234"; + cache_->SaySuccessfullyCached(shader1, shader2, NULL); + + shader2 = "different!"; + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); +} + +TEST_F(ProgramCacheTest, LinkUnknownOnVertexSourceChange) { + std::string shader1 = "abcd1234"; + const std::string shader2 = "abcda sda b1~#4 bbbbb1234"; + cache_->SaySuccessfullyCached(shader1, shader2, NULL); + + shader1 = "different!"; + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); +} + +TEST_F(ProgramCacheTest, StatusEviction) { + const std::string shader1 = "abcd1234"; + const std::string shader2 = "abcda sda b1~#4 bbbbb1234"; + cache_->ShaderCompilationSucceeded(shader1); + cache_->ShaderCompilationSucceeded(shader2); + cache_->SaySuccessfullyCached(shader1, shader2, NULL); + char a_sha[ProgramCache::kHashLength]; + char b_sha[ProgramCache::kHashLength]; + cache_->ComputeShaderHash(shader1, a_sha); + cache_->ComputeShaderHash(shader2, b_sha); + + char sha[ProgramCache::kHashLength]; + cache_->ComputeProgramHash(a_sha, + b_sha, + NULL, + sha); + cache_->Evict(std::string(sha, ProgramCache::kHashLength), + std::string(a_sha, ProgramCache::kHashLength), + std::string(b_sha, ProgramCache::kHashLength)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader1)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader2)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); +} + +TEST_F(ProgramCacheTest, EvictionWithReusedShader) { + const std::string shader1 = "abcd1234"; + const std::string shader2 = "abcda sda b1~#4 bbbbb1234"; + const std::string shader3 = "asbjbbjj239a"; + cache_->ShaderCompilationSucceeded(shader1); + cache_->ShaderCompilationSucceeded(shader2); + cache_->SaySuccessfullyCached(shader1, shader2, NULL); + cache_->ShaderCompilationSucceeded(shader1); + cache_->ShaderCompilationSucceeded(shader3); + cache_->SaySuccessfullyCached(shader1, shader3, NULL); + + char a_sha[ProgramCache::kHashLength]; + char b_sha[ProgramCache::kHashLength]; + char c_sha[ProgramCache::kHashLength]; + cache_->ComputeShaderHash(shader1, a_sha); + cache_->ComputeShaderHash(shader2, b_sha); + cache_->ComputeShaderHash(shader3, c_sha); + + char sha[ProgramCache::kHashLength]; + cache_->ComputeProgramHash(a_sha, + b_sha, + NULL, + sha); + cache_->Evict(std::string(sha, ProgramCache::kHashLength), + std::string(a_sha, ProgramCache::kHashLength), + std::string(b_sha, ProgramCache::kHashLength)); + EXPECT_EQ(ProgramCache::COMPILATION_SUCCEEDED, + cache_->GetShaderCompilationStatus(shader1)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader2)); + EXPECT_EQ(ProgramCache::COMPILATION_SUCCEEDED, + cache_->GetShaderCompilationStatus(shader3)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); + EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, + cache_->GetLinkedProgramStatus(shader1, shader3, NULL)); + + + cache_->ComputeProgramHash(a_sha, + c_sha, + NULL, + sha); + cache_->Evict(std::string(sha, ProgramCache::kHashLength), + std::string(a_sha, ProgramCache::kHashLength), + std::string(c_sha, ProgramCache::kHashLength)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader1)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader2)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader3)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader3, NULL)); +} + +TEST_F(ProgramCacheTest, StatusClear) { + const std::string shader1 = "abcd1234"; + const std::string shader2 = "abcda sda b1~#4 bbbbb1234"; + const std::string shader3 = "asbjbbjj239a"; + cache_->ShaderCompilationSucceeded(shader1); + cache_->ShaderCompilationSucceeded(shader2); + cache_->SaySuccessfullyCached(shader1, shader2, NULL); + cache_->ShaderCompilationSucceeded(shader3); + cache_->SaySuccessfullyCached(shader1, shader3, NULL); + cache_->Clear(); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader1)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader2)); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(shader3)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader2, NULL)); + EXPECT_EQ(ProgramCache::LINK_UNKNOWN, + cache_->GetLinkedProgramStatus(shader1, shader3, NULL)); +} + +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/program_manager.cc b/gpu/command_buffer/service/program_manager.cc index 8b94e8b..c73cab4 100644 --- a/gpu/command_buffer/service/program_manager.cc +++ b/gpu/command_buffer/service/program_manager.cc @@ -4,7 +4,9 @@ #include "gpu/command_buffer/service/program_manager.h" +#include <algorithm> #include <set> +#include <utility> #include <vector> #include "base/basictypes.h" @@ -14,8 +16,10 @@ #include "base/string_number_conversions.h" #include "gpu/command_buffer/common/gles2_cmd_format.h" #include "gpu/command_buffer/common/gles2_cmd_utils.h" +#include "gpu/command_buffer/service/feature_info.h" #include "gpu/command_buffer/service/gles2_cmd_decoder.h" #include "gpu/command_buffer/service/gpu_switches.h" +#include "gpu/command_buffer/service/program_cache.h" namespace gpu { namespace gles2 { @@ -34,6 +38,21 @@ int ShaderTypeToIndex(GLenum shader_type) { } } +ShaderTranslator* ShaderIndexToTranslator( + int index, + ShaderTranslator* vertex_translator, + ShaderTranslator* fragment_translator) { + switch (index) { + case 0: + return vertex_translator; + case 1: + return fragment_translator; + default: + NOTREACHED(); + return NULL; + } +} + // Given a name like "foo.bar[123].moo[456]" sets new_name to "foo.bar[123].moo" // and sets element_index to 456. returns false if element expression was not a // whole decimal number. For example: "foo[1b2]" @@ -360,7 +379,83 @@ void ProgramManager::ProgramInfo::ExecuteBindAttribLocationCalls() { } } -bool ProgramManager::ProgramInfo::Link() { +void ProgramManager::DoCompileShader(ShaderManager::ShaderInfo* info, + ShaderTranslator* translator, + FeatureInfo* feature_info) { + if (program_cache_ && + program_cache_->GetShaderCompilationStatus(*info->source()) == + ProgramCache::COMPILATION_SUCCEEDED) { + info->SetStatus(true, "", translator); + info->FlagSourceAsCompiled(false); + return; + } + ForceCompileShader(info->source(), info, translator, feature_info); +} + +void ProgramManager::ForceCompileShader(const std::string* source, + ShaderManager::ShaderInfo* info, + ShaderTranslator* translator, + FeatureInfo* feature_info) { + info->FlagSourceAsCompiled(true); + + // Translate GL ES 2.0 shader to Desktop GL shader and pass that to + // glShaderSource and then glCompileShader. + const char* shader_src = source ? source->c_str() : ""; + if (translator) { + if (!translator->Translate(shader_src)) { + info->SetStatus(false, translator->info_log(), NULL); + return; + } + shader_src = translator->translated_shader(); + if (!feature_info->feature_flags().angle_translated_shader_source) + info->UpdateTranslatedSource(shader_src); + } + + glShaderSource(info->service_id(), 1, &shader_src, NULL); + glCompileShader(info->service_id()); + if (feature_info->feature_flags().angle_translated_shader_source) { + GLint max_len = 0; + glGetShaderiv(info->service_id(), + GL_TRANSLATED_SHADER_SOURCE_LENGTH_ANGLE, + &max_len); + scoped_array<char> temp(new char[max_len]); + GLint len = 0; + glGetTranslatedShaderSourceANGLE( + info->service_id(), max_len, &len, temp.get()); + DCHECK(max_len == 0 || len < max_len); + DCHECK(len == 0 || temp[len] == '\0'); + info->UpdateTranslatedSource(temp.get()); + } + + GLint status = GL_FALSE; + glGetShaderiv(info->service_id(), GL_COMPILE_STATUS, &status); + if (status) { + info->SetStatus(true, "", translator); + if (program_cache_) { + const char* untranslated_source = source ? source->c_str() : ""; + program_cache_->ShaderCompilationSucceeded(untranslated_source); + } + } else { + // We cannot reach here if we are using the shader translator. + // All invalid shaders must be rejected by the translator. + // All translated shaders must compile. + LOG_IF(ERROR, translator) + << "Shader translator allowed/produced an invalid shader."; + GLint max_len = 0; + glGetShaderiv(info->service_id(), GL_INFO_LOG_LENGTH, &max_len); + scoped_array<char> temp(new char[max_len]); + GLint len = 0; + glGetShaderInfoLog(info->service_id(), max_len, &len, temp.get()); + DCHECK(max_len == 0 || len < max_len); + DCHECK(len == 0 || temp[len] == '\0'); + info->SetStatus(false, std::string(temp.get(), len).c_str(), NULL); + } +} + +bool ProgramManager::ProgramInfo::Link(ShaderManager* manager, + ShaderTranslator* vertex_translator, + ShaderTranslator* fragment_translator, + FeatureInfo* feature_info) { ClearLinkStatus(); if (!CanLink()) { set_log_info("missing shaders"); @@ -371,11 +466,63 @@ bool ProgramManager::ProgramInfo::Link() { return false; } ExecuteBindAttribLocationCalls(); - glLinkProgram(service_id()); + + bool link = true; + ProgramCache* cache = manager_->program_cache_; + if (cache) { + ProgramCache::LinkedProgramStatus status = cache->GetLinkedProgramStatus( + *attached_shaders_[0]->deferred_compilation_source(), + *attached_shaders_[1]->deferred_compilation_source(), + &bind_attrib_location_map_); + + if (status == ProgramCache::LINK_SUCCEEDED) { + ProgramCache::ProgramLoadResult success = cache->LoadLinkedProgram( + service_id(), + attached_shaders_[0], + attached_shaders_[1], + &bind_attrib_location_map_); + link = success != ProgramCache::PROGRAM_LOAD_SUCCESS; + } + + if (link) { + // compile our shaders if they're pending + const int kShaders = ProgramManager::ProgramInfo::kMaxAttachedShaders; + for (int i = 0; i < kShaders; ++i) { + ShaderManager::ShaderInfo* info = attached_shaders_[i].get(); + if (!info->source_compiled()) { + ShaderTranslator* translator = ShaderIndexToTranslator( + i, + vertex_translator, + fragment_translator); + manager_->ForceCompileShader(info->deferred_compilation_source(), + attached_shaders_[i], + translator, + feature_info); + CHECK(info->IsValid()); + } + } + } + } + + if (link) { + if (cache && gfx::g_GL_ARB_get_program_binary) { + glProgramParameteri(service_id(), + PROGRAM_BINARY_RETRIEVABLE_HINT, + GL_TRUE); + } + glLinkProgram(service_id()); + } + GLint success = 0; glGetProgramiv(service_id(), GL_LINK_STATUS, &success); if (success == GL_TRUE) { Update(); + if (cache && link) { + cache->SaveLinkedProgram(service_id(), + attached_shaders_[0], + attached_shaders_[1], + &bind_attrib_location_map_); + } } else { UpdateLogInfo(); } @@ -848,13 +995,14 @@ ProgramManager::ProgramInfo::~ProgramInfo() { } } -ProgramManager::ProgramManager() + +ProgramManager::ProgramManager(ProgramCache* program_cache) : program_info_count_(0), have_context_(true), disable_workarounds_( CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableGpuDriverBugWorkarounds)) { -} + switches::kDisableGpuDriverBugWorkarounds)), + program_cache_(program_cache) { } ProgramManager::~ProgramManager() { DCHECK(program_infos_.empty()); @@ -900,6 +1048,10 @@ bool ProgramManager::GetClientId(GLuint service_id, GLuint* client_id) const { return false; } +ProgramCache* ProgramManager::program_cache() const { + return program_cache_; +} + bool ProgramManager::IsOwned(ProgramManager::ProgramInfo* info) { for (ProgramInfoMap::iterator it = program_infos_.begin(); it != program_infos_.end(); ++it) { diff --git a/gpu/command_buffer/service/program_manager.h b/gpu/command_buffer/service/program_manager.h index 964b933..bf8b348 100644 --- a/gpu/command_buffer/service/program_manager.h +++ b/gpu/command_buffer/service/program_manager.h @@ -11,7 +11,6 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" #include "gpu/command_buffer/service/common_decoder.h" #include "gpu/command_buffer/service/gl_utils.h" #include "gpu/command_buffer/service/shader_manager.h" @@ -19,6 +18,8 @@ namespace gpu { namespace gles2 { +class ProgramCache; +class FeatureInfo; // Tracks the Programs. // @@ -26,6 +27,8 @@ namespace gles2 { // need to be shared by multiple GLES2Decoders. class GPU_EXPORT ProgramManager { public: + typedef std::map<std::string, GLint> LocationMap; + // This is used to track which attributes a particular program needs // so we can verify at glDrawXXX time that every attribute is either disabled // or if enabled that it points to a valid source. @@ -150,7 +153,10 @@ class GPU_EXPORT ProgramManager { bool CanLink() const; // Performs glLinkProgram and related activities. - bool Link(); + bool Link(ShaderManager* manager, + ShaderTranslator* vertex_translator, + ShaderTranslator* fragment_shader, + FeatureInfo* feature_info); // Performs glValidateProgram and related activities. void Validate(); @@ -178,12 +184,15 @@ class GPU_EXPORT ProgramManager { // We only consider the declared attributes in the program. bool DetectAttribLocationBindingConflicts() const; + // Visible for testing + const LocationMap& bind_attrib_location_map() const { + return bind_attrib_location_map_; + } + private: friend class base::RefCounted<ProgramInfo>; friend class ProgramManager; - typedef std::map<std::string, GLint> LocationMap; - ~ProgramInfo(); void set_log_info(const char* str) { @@ -299,7 +308,7 @@ class GPU_EXPORT ProgramManager { LocationMap bind_uniform_location_map_; }; - ProgramManager(); + explicit ProgramManager(ProgramCache* program_cache); ~ProgramManager(); // Must call before destruction. @@ -314,6 +323,9 @@ class GPU_EXPORT ProgramManager { // Gets a client id for a given service id. bool GetClientId(GLuint service_id, GLuint* client_id) const; + // Gets the shader cache + ProgramCache* program_cache() const; + // Marks a program as deleted. If it is not used the info will be deleted. void MarkAsDeleted(ShaderManager* shader_manager, ProgramInfo* info); @@ -334,7 +346,19 @@ class GPU_EXPORT ProgramManager { static int32 MakeFakeLocation(int32 index, int32 element); + // Cache-aware shader compiling. If no cache or if the shader wasn't + // previously compiled, ForceCompileShader is called + void DoCompileShader(ShaderManager::ShaderInfo* info, + ShaderTranslator* translator, + FeatureInfo* feature_info); + private: + // Actually compiles the shader + void ForceCompileShader(const std::string* source, + ShaderManager::ShaderInfo* info, + ShaderTranslator* translator, + FeatureInfo* feature_info); + void StartTracking(ProgramInfo* info); void StopTracking(ProgramInfo* info); @@ -354,6 +378,8 @@ class GPU_EXPORT ProgramManager { // Used to clear uniforms. std::vector<uint8> zero_; + ProgramCache* program_cache_; + void RemoveProgramInfoIfUnused( ShaderManager* shader_manager, ProgramInfo* info); diff --git a/gpu/command_buffer/service/program_manager_unittest.cc b/gpu/command_buffer/service/program_manager_unittest.cc index 8dca84d..e1cc441 100644 --- a/gpu/command_buffer/service/program_manager_unittest.cc +++ b/gpu/command_buffer/service/program_manager_unittest.cc @@ -13,6 +13,7 @@ #include "gpu/command_buffer/common/gles2_cmd_format.h" #include "gpu/command_buffer/common/gles2_cmd_utils.h" #include "gpu/command_buffer/service/common_decoder.h" +#include "gpu/command_buffer/service/feature_info.h" #include "gpu/command_buffer/service/mocks.h" #include "gpu/command_buffer/service/test_helper.h" #include "testing/gtest/include/gtest/gtest.h" @@ -35,7 +36,7 @@ namespace gles2 { class ProgramManagerTest : public testing::Test { public: - ProgramManagerTest() { } + ProgramManagerTest() : manager_(NULL) { } ~ProgramManagerTest() { manager_.Destroy(false); } @@ -134,7 +135,7 @@ TEST_F(ProgramManagerTest, ProgramInfo) { class ProgramManagerWithShaderTest : public testing::Test { public: ProgramManagerWithShaderTest() - : program_info_(NULL) { + : manager_(NULL), program_info_(NULL) { } ~ProgramManagerWithShaderTest() { @@ -218,7 +219,7 @@ class ProgramManagerWithShaderTest : public testing::Test { program_info_->AttachShader(&shader_manager_, vertex_shader); program_info_->AttachShader(&shader_manager_, fragment_shader); - program_info_->Link(); + program_info_->Link(NULL, NULL, NULL, NULL); } void SetupShader(AttribInfo* attribs, size_t num_attribs, @@ -251,7 +252,7 @@ class ProgramManagerWithShaderTest : public testing::Test { SetupShader(kAttribs, kNumAttribs, kUniforms, kNumUniforms, service_id); } - program_info->Link(); + program_info->Link(NULL, NULL, NULL, NULL); GLint link_status; program_info->GetProgramiv(GL_LINK_STATUS, &link_status); return (static_cast<bool>(link_status) == expected_link_status); @@ -584,7 +585,7 @@ TEST_F(ProgramManagerWithShaderTest, GLDriverReturnsGLUnderscoreUniform) { ASSERT_TRUE(program_info != NULL); EXPECT_TRUE(program_info->AttachShader(&shader_manager_, vshader)); EXPECT_TRUE(program_info->AttachShader(&shader_manager_, fshader)); - program_info->Link(); + program_info->Link(NULL, NULL, NULL, NULL); GLint value = 0; program_info->GetProgramiv(GL_ACTIVE_ATTRIBUTES, &value); EXPECT_EQ(3, value); @@ -678,7 +679,7 @@ TEST_F(ProgramManagerWithShaderTest, GLDriverReturnsWrongTypeInfo) { ASSERT_TRUE(program_info != NULL); EXPECT_TRUE(program_info->AttachShader(&shader_manager_, vshader)); EXPECT_TRUE(program_info->AttachShader(&shader_manager_, fshader)); - program_info->Link(); + program_info->Link(NULL, NULL, NULL, NULL); // Check that we got the good type, not the bad. // Check Attribs for (unsigned index = 0; index < kNumAttribs; ++index) { @@ -997,7 +998,7 @@ TEST_F(ProgramManagerWithShaderTest, ClearWithSamplerTypes) { const size_t kNumUniforms = arraysize(kUniforms); SetupShader(kAttribs, kNumAttribs, kUniforms, kNumUniforms, kServiceProgramId); - program_info->Link(); + program_info->Link(NULL, NULL, NULL, NULL); SetupExpectationsForClearingUniforms(kUniforms, kNumUniforms); manager_.ClearUniforms(program_info); } @@ -1069,7 +1070,7 @@ TEST_F(ProgramManagerWithShaderTest, BindUniformLocation) { const size_t kNumUniforms = arraysize(kUniforms); SetupShader(kAttribs, kNumAttribs, kUniforms, kNumUniforms, kServiceProgramId); - program_info->Link(); + program_info->Link(NULL, NULL, NULL, NULL); EXPECT_EQ(kUniform1DesiredLocation, program_info->GetUniformFakeLocation(kUniform1Name)); @@ -1079,7 +1080,380 @@ TEST_F(ProgramManagerWithShaderTest, BindUniformLocation) { program_info->GetUniformFakeLocation(kUniform3GoodName)); } -} // namespace gles2 -} // namespace gpu +class ProgramManagerWithCacheTest : public testing::Test { + public: + static const GLuint kClientProgramId = 1; + static const GLuint kServiceProgramId = 10; + static const GLuint kVertexShaderClientId = 2; + static const GLuint kFragmentShaderClientId = 20; + static const GLuint kVertexShaderServiceId = 3; + static const GLuint kFragmentShaderServiceId = 30; + + ProgramManagerWithCacheTest() + : cache_(new MockProgramCache()), + manager_(cache_.get()), + vertex_shader_(NULL), + fragment_shader_(NULL), + program_info_(NULL) { + } + ~ProgramManagerWithCacheTest() { + manager_.Destroy(false); + shader_manager_.Destroy(false); + } + + protected: + virtual void SetUp() { + gl_.reset(new StrictMock<gfx::MockGLInterface>()); + ::gfx::GLInterface::SetGLInterface(gl_.get()); + + vertex_shader_ = shader_manager_.CreateShaderInfo( + kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); + fragment_shader_ = shader_manager_.CreateShaderInfo( + kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); + ASSERT_TRUE(vertex_shader_ != NULL); + ASSERT_TRUE(fragment_shader_ != NULL); + vertex_shader_->UpdateSource("lka asjf bjajsdfj"); + fragment_shader_->UpdateSource("lka asjf a fasgag 3rdsf3 bjajsdfj"); + + program_info_ = manager_.CreateProgramInfo( + kClientProgramId, kServiceProgramId); + ASSERT_TRUE(program_info_ != NULL); + + program_info_->AttachShader(&shader_manager_, vertex_shader_); + program_info_->AttachShader(&shader_manager_, fragment_shader_); + } + + virtual void TearDown() { + ::gfx::GLInterface::SetGLInterface(NULL); + } + + void SetShadersCompiled() { + cache_->ShaderCompilationSucceeded(*vertex_shader_->source()); + cache_->ShaderCompilationSucceeded(*fragment_shader_->source()); + vertex_shader_->SetStatus(true, NULL, NULL); + fragment_shader_->SetStatus(true, NULL, NULL); + vertex_shader_->FlagSourceAsCompiled(true); + fragment_shader_->FlagSourceAsCompiled(true); + } + + void SetShadersNotCompiledButCached() { + SetShadersCompiled(); + vertex_shader_->FlagSourceAsCompiled(false); + fragment_shader_->FlagSourceAsCompiled(false); + } + + void SetProgramCached() { + cache_->LinkedProgramCacheSuccess( + vertex_shader_->source()->c_str(), + fragment_shader_->source()->c_str(), + &program_info_->bind_attrib_location_map()); + } + + void SetExpectationsForProgramCached() { + SetExpectationsForProgramCached(program_info_, + vertex_shader_, + fragment_shader_); + } + + void SetExpectationsForProgramCached( + ProgramManager::ProgramInfo* program_info, + ShaderManager::ShaderInfo* vertex_shader, + ShaderManager::ShaderInfo* fragment_shader) { + EXPECT_CALL(*cache_.get(), SaveLinkedProgram( + program_info->service_id(), + vertex_shader, + fragment_shader, + &program_info->bind_attrib_location_map())).Times(1); + } + + void SetExpectationsForNotCachingProgram() { + SetExpectationsForNotCachingProgram(program_info_, + vertex_shader_, + fragment_shader_); + } + + void SetExpectationsForNotCachingProgram( + ProgramManager::ProgramInfo* program_info, + ShaderManager::ShaderInfo* vertex_shader, + ShaderManager::ShaderInfo* fragment_shader) { + EXPECT_CALL(*cache_.get(), SaveLinkedProgram( + program_info->service_id(), + vertex_shader, + fragment_shader, + &program_info->bind_attrib_location_map())).Times(0); + } + + void SetExpectationsForProgramLoad(ProgramCache::ProgramLoadResult result) { + SetExpectationsForProgramLoad(kServiceProgramId, + program_info_, + vertex_shader_, + fragment_shader_, + result); + } + + void SetExpectationsForProgramLoad( + GLuint service_program_id, + ProgramManager::ProgramInfo* program_info, + ShaderManager::ShaderInfo* vertex_shader, + ShaderManager::ShaderInfo* fragment_shader, + ProgramCache::ProgramLoadResult result) { + EXPECT_CALL(*cache_.get(), + LoadLinkedProgram(service_program_id, + vertex_shader, + fragment_shader, + &program_info->bind_attrib_location_map())) + .WillOnce(Return(result)); + } + + void SetExpectationsForProgramLoadSuccess() { + SetExpectationsForProgramLoadSuccess(kServiceProgramId); + } + + void SetExpectationsForProgramLoadSuccess(GLuint service_program_id) { + TestHelper::SetupProgramSuccessExpectations(gl_.get(), + NULL, + 0, + NULL, + 0, + service_program_id); + } + + void SetExpectationsForProgramLink() { + SetExpectationsForProgramLink(kServiceProgramId); + } + + void SetExpectationsForProgramLink(GLuint service_program_id) { + TestHelper::SetupShader(gl_.get(), NULL, 0, NULL, 0, service_program_id); + if (gfx::g_GL_ARB_get_program_binary) { + EXPECT_CALL(*gl_.get(), + ProgramParameteri(service_program_id, + PROGRAM_BINARY_RETRIEVABLE_HINT, + GL_TRUE)).Times(1); + } + } + + void SetExpectationsForSuccessCompile( + const ShaderManager::ShaderInfo* shader) { + const GLuint shader_id = shader->service_id(); + const char* src = shader->source()->c_str(); + EXPECT_CALL(*gl_.get(), + ShaderSource(shader_id, 1, Pointee(src), NULL)).Times(1); + EXPECT_CALL(*gl_.get(), CompileShader(shader_id)).Times(1); + EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_COMPILE_STATUS, _)) + .WillOnce(SetArgumentPointee<2>(GL_TRUE)); + } + + void SetExpectationsForNoCompile(const ShaderManager::ShaderInfo* shader) { + const GLuint shader_id = shader->service_id(); + const char* src = shader->source()->c_str(); + EXPECT_CALL(*gl_.get(), + ShaderSource(shader_id, 1, Pointee(src), NULL)).Times(0); + EXPECT_CALL(*gl_.get(), CompileShader(shader_id)).Times(0); + EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_COMPILE_STATUS, _)) + .Times(0); + } + + void SetExpectationsForErrorCompile(const ShaderManager::ShaderInfo* shader) { + const GLuint shader_id = shader->service_id(); + const char* src = shader->source()->c_str(); + EXPECT_CALL(*gl_.get(), + ShaderSource(shader_id, 1, Pointee(src), NULL)).Times(1); + EXPECT_CALL(*gl_.get(), CompileShader(shader_id)).Times(1); + EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_COMPILE_STATUS, _)) + .WillOnce(SetArgumentPointee<2>(GL_FALSE)); + EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_INFO_LOG_LENGTH, _)) + .WillOnce(SetArgumentPointee<2>(0)); + EXPECT_CALL(*gl_.get(), GetShaderInfoLog(shader_id, 0, _, _)) + .Times(1); + } + + scoped_ptr<StrictMock<gfx::MockGLInterface> > gl_; + + scoped_ptr<MockProgramCache> cache_; + ProgramManager manager_; + + ShaderManager::ShaderInfo* vertex_shader_; + ShaderManager::ShaderInfo* fragment_shader_; + ProgramManager::ProgramInfo* program_info_; + ShaderManager shader_manager_; +}; + +// GCC requires these declarations, but MSVC requires they not be present +#ifndef COMPILER_MSVC +const GLuint ProgramManagerWithCacheTest::kClientProgramId; +const GLuint ProgramManagerWithCacheTest::kServiceProgramId; +const GLuint ProgramManagerWithCacheTest::kVertexShaderClientId; +const GLuint ProgramManagerWithCacheTest::kFragmentShaderClientId; +const GLuint ProgramManagerWithCacheTest::kVertexShaderServiceId; +const GLuint ProgramManagerWithCacheTest::kFragmentShaderServiceId; +#endif + +TEST_F(ProgramManagerWithCacheTest, CacheSuccessAfterShaderCompile) { + SetExpectationsForSuccessCompile(vertex_shader_); + FeatureInfo::Ref info(new FeatureInfo()); + manager_.DoCompileShader(vertex_shader_, NULL, info.get()); + EXPECT_EQ(ProgramCache::COMPILATION_SUCCEEDED, + cache_->GetShaderCompilationStatus(*vertex_shader_->source())); +} + +TEST_F(ProgramManagerWithCacheTest, CacheUnknownAfterShaderError) { + SetExpectationsForErrorCompile(vertex_shader_); + FeatureInfo::Ref info(new FeatureInfo()); + manager_.DoCompileShader(vertex_shader_, NULL, info.get()); + EXPECT_EQ(ProgramCache::COMPILATION_UNKNOWN, + cache_->GetShaderCompilationStatus(*vertex_shader_->source())); +} + +TEST_F(ProgramManagerWithCacheTest, NoCompileWhenShaderCached) { + cache_->ShaderCompilationSucceeded(vertex_shader_->source()->c_str()); + SetExpectationsForNoCompile(vertex_shader_); + FeatureInfo::Ref info(new FeatureInfo()); + manager_.DoCompileShader(vertex_shader_, NULL, info.get()); +} + +TEST_F(ProgramManagerWithCacheTest, CacheProgramOnSuccessfulLink) { + SetShadersCompiled(); + SetExpectationsForProgramLink(); + SetExpectationsForProgramCached(); + EXPECT_TRUE(program_info_->Link(NULL, NULL, NULL, NULL)); +} + +TEST_F(ProgramManagerWithCacheTest, CompileShaderOnLinkCacheMiss) { + SetShadersCompiled(); + vertex_shader_->FlagSourceAsCompiled(false); + + FeatureInfo::Ref info(new FeatureInfo()); + + SetExpectationsForSuccessCompile(vertex_shader_); + SetExpectationsForProgramLink(); + SetExpectationsForProgramCached(); + EXPECT_TRUE(program_info_->Link(&shader_manager_, NULL, NULL, info.get())); +} + +TEST_F(ProgramManagerWithCacheTest, LoadProgramOnProgramCacheHit) { + SetShadersNotCompiledButCached(); + SetProgramCached(); + + SetExpectationsForNoCompile(vertex_shader_); + SetExpectationsForNoCompile(fragment_shader_); + SetExpectationsForProgramLoad(ProgramCache::PROGRAM_LOAD_SUCCESS); + SetExpectationsForNotCachingProgram(); + SetExpectationsForProgramLoadSuccess(); + + EXPECT_TRUE(program_info_->Link(NULL, NULL, NULL, NULL)); +} + +TEST_F(ProgramManagerWithCacheTest, CompileAndLinkOnProgramCacheError) { + SetShadersNotCompiledButCached(); + SetProgramCached(); + + SetExpectationsForSuccessCompile(vertex_shader_); + SetExpectationsForSuccessCompile(fragment_shader_); + SetExpectationsForProgramLoad(ProgramCache::PROGRAM_LOAD_FAILURE); + SetExpectationsForProgramLink(); + SetExpectationsForProgramCached(); + + FeatureInfo::Ref info(new FeatureInfo()); + EXPECT_TRUE(program_info_->Link(&shader_manager_, NULL, NULL, info.get())); +} + +TEST_F(ProgramManagerWithCacheTest, CorrectCompileOnSourceChangeNoCompile) { + SetShadersNotCompiledButCached(); + SetProgramCached(); + const GLuint kNewShaderClientId = 4; + const GLuint kNewShaderServiceId = 40; + const GLuint kNewProgramClientId = 5; + const GLuint kNewProgramServiceId = 50; + ShaderManager::ShaderInfo* new_vertex_shader = + shader_manager_.CreateShaderInfo(kNewShaderClientId, + kNewShaderServiceId, + GL_VERTEX_SHADER); + + const std::string original_source = *vertex_shader_->source(); + new_vertex_shader->UpdateSource(original_source.c_str()); + + ProgramManager::ProgramInfo* program_info = manager_.CreateProgramInfo( + kNewProgramClientId, kNewProgramServiceId); + ASSERT_TRUE(program_info != NULL); + program_info->AttachShader(&shader_manager_, new_vertex_shader); + program_info->AttachShader(&shader_manager_, fragment_shader_); + + SetExpectationsForNoCompile(new_vertex_shader); + + manager_.DoCompileShader(new_vertex_shader, NULL, NULL); + + new_vertex_shader->UpdateSource("different!"); + EXPECT_EQ(original_source, + *new_vertex_shader->deferred_compilation_source()); + + EXPECT_FALSE(new_vertex_shader->source_compiled()); + EXPECT_FALSE(fragment_shader_->source_compiled()); + + SetExpectationsForNoCompile(fragment_shader_); + SetExpectationsForNotCachingProgram(program_info, + new_vertex_shader, + fragment_shader_); + SetExpectationsForProgramLoad(kNewProgramServiceId, + program_info, + new_vertex_shader, + fragment_shader_, + ProgramCache::PROGRAM_LOAD_SUCCESS); + SetExpectationsForProgramLoadSuccess(kNewProgramServiceId); + + FeatureInfo::Ref info(new FeatureInfo()); + EXPECT_TRUE(program_info->Link(&shader_manager_, NULL, NULL, info.get())); +} + +TEST_F(ProgramManagerWithCacheTest, CorrectCompileOnSourceChangeWithCompile) { + SetShadersNotCompiledButCached(); + SetProgramCached(); + + const GLuint kNewShaderClientId = 4; + const GLuint kNewShaderServiceId = 40; + const GLuint kNewProgramClientId = 5; + const GLuint kNewProgramServiceId = 50; + + ShaderManager::ShaderInfo* new_vertex_shader = + shader_manager_.CreateShaderInfo(kNewShaderClientId, + kNewShaderServiceId, + GL_VERTEX_SHADER); + + new_vertex_shader->UpdateSource(vertex_shader_->source()->c_str()); + + ProgramManager::ProgramInfo* program_info = manager_.CreateProgramInfo( + kNewProgramClientId, kNewProgramServiceId); + ASSERT_TRUE(program_info != NULL); + program_info->AttachShader(&shader_manager_, new_vertex_shader); + program_info->AttachShader(&shader_manager_, fragment_shader_); + + SetExpectationsForNoCompile(new_vertex_shader); + + manager_.DoCompileShader(new_vertex_shader, NULL, NULL); + + const std::string differentSource = "different!"; + new_vertex_shader->UpdateSource(differentSource.c_str()); + SetExpectationsForSuccessCompile(new_vertex_shader); + + FeatureInfo::Ref info(new FeatureInfo()); + manager_.DoCompileShader(new_vertex_shader, NULL, info.get()); + EXPECT_EQ(differentSource, + *new_vertex_shader->deferred_compilation_source()); + + EXPECT_TRUE(new_vertex_shader->source_compiled()); + EXPECT_FALSE(fragment_shader_->source_compiled()); + + // so we don't recompile because we were pending originally + SetExpectationsForNoCompile(new_vertex_shader); + SetExpectationsForSuccessCompile(fragment_shader_); + SetExpectationsForProgramCached(program_info, + new_vertex_shader, + fragment_shader_); + SetExpectationsForProgramLink(kNewProgramServiceId); + + EXPECT_TRUE(program_info->Link(&shader_manager_, NULL, NULL, info.get())); +} + +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/shader_manager.cc b/gpu/command_buffer/service/shader_manager.cc index 0d30f52..6a6c9ef 100644 --- a/gpu/command_buffer/service/shader_manager.cc +++ b/gpu/command_buffer/service/shader_manager.cc @@ -3,6 +3,9 @@ // found in the LICENSE file. #include "gpu/command_buffer/service/shader_manager.h" + +#include <utility> + #include "base/logging.h" #include "base/string_util.h" @@ -13,7 +16,8 @@ ShaderManager::ShaderInfo::ShaderInfo(GLuint service_id, GLenum shader_type) : use_count_(0), service_id_(service_id), shader_type_(shader_type), - valid_(false) { + valid_(false), + source_compiled_(false) { } ShaderManager::ShaderInfo::~ShaderInfo() { diff --git a/gpu/command_buffer/service/shader_manager.h b/gpu/command_buffer/service/shader_manager.h index 9b6fad2..448dc5a 100644 --- a/gpu/command_buffer/service/shader_manager.h +++ b/gpu/command_buffer/service/shader_manager.h @@ -34,6 +34,11 @@ class GPU_EXPORT ShaderManager { typedef ShaderTranslator::VariableInfo VariableInfo; void UpdateSource(const char* source) { + // If the source is flagged as compiled, then store our previous source + // for deferred compile and caching. + if (!deferred_compilation_source_.get()) { + deferred_compilation_source_.reset(source_.release()); + } source_.reset(source ? new std::string(source) : NULL); translated_source_.reset(NULL); } @@ -63,6 +68,26 @@ class GPU_EXPORT ShaderManager { bool valid, const char* log, ShaderTranslatorInterface* translator); + // If the source was actually compiled (compilation wasn't deferred) + bool source_compiled() const { + return source_compiled_; + } + + // The source that was used when the user called CompileShader. + // This is used for a deferred compile and in the program cache + const std::string* deferred_compilation_source() const { + return deferred_compilation_source_.get() != NULL ? + deferred_compilation_source_.get() : + source_.get(); + } + + // Resets our deferred compilation source and stores if the source was + // actually compiled, or if we're expecting a cache hit + void FlagSourceAsCompiled(bool actually_compiled) { + source_compiled_ = actually_compiled; + deferred_compilation_source_.reset(); + } + const VariableInfo* GetAttribInfo(const std::string& name) const; const VariableInfo* GetUniformInfo(const std::string& name) const; @@ -87,6 +112,28 @@ class GPU_EXPORT ShaderManager { return use_count_ != 0; } + // Used by program cache. + const ShaderTranslator::VariableMap& attrib_map() const { + return attrib_map_; + } + + // Used by program cache. + const ShaderTranslator::VariableMap& uniform_map() const { + return uniform_map_; + } + + // Used by program cache. + void set_attrib_map(const ShaderTranslator::VariableMap& attrib_map) { + // copied because cache might be cleared + attrib_map_ = ShaderTranslator::VariableMap(attrib_map); + } + + // Used by program cache. + void set_uniform_map(const ShaderTranslator::VariableMap& uniform_map) { + // copied because cache might be cleared + uniform_map_ = ShaderTranslator::VariableMap(uniform_map); + } + private: typedef ShaderTranslator::VariableMap VariableMap; @@ -122,6 +169,13 @@ class GPU_EXPORT ShaderManager { // The type info when the shader was last compiled. VariableMap attrib_map_; VariableMap uniform_map_; + + // If the source was actually compiled (otherwise we're deferring + // compilation because of a possible cache hit) + bool source_compiled_; + + // Holds on to the source for a deferred compile. + scoped_ptr<std::string> deferred_compilation_source_; }; ShaderManager(); diff --git a/gpu/command_buffer/service/shader_manager_unittest.cc b/gpu/command_buffer/service/shader_manager_unittest.cc index f927e26..fb1efe4 100644 --- a/gpu/command_buffer/service/shader_manager_unittest.cc +++ b/gpu/command_buffer/service/shader_manager_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -245,7 +245,39 @@ TEST_F(ShaderManagerTest, ShaderInfoUseCount) { EXPECT_TRUE(info2 == NULL); } -} // namespace gles2 -} // namespace gpu +TEST_F(ShaderManagerTest, ShaderInfoStoreCompilationStatus) { + const GLuint kClientId = 1; + const GLuint kServiceId = 11; + const GLenum kShaderType = GL_VERTEX_SHADER; + ShaderManager::ShaderInfo* info = manager_.CreateShaderInfo( + kClientId, kServiceId, kShaderType); + ASSERT_TRUE(info != NULL); + + info->UpdateSource("original source"); + info->FlagSourceAsCompiled(false); + EXPECT_FALSE(info->source_compiled()); + info->FlagSourceAsCompiled(true); + EXPECT_TRUE(info->source_compiled()); +} + +TEST_F(ShaderManagerTest, ShaderInfoStoreDeferredSource) { + const GLuint kClientId = 1; + const GLuint kServiceId = 11; + const GLenum kShaderType = GL_VERTEX_SHADER; + ShaderManager::ShaderInfo* info = manager_.CreateShaderInfo( + kClientId, kServiceId, kShaderType); + ASSERT_TRUE(info != NULL); + + info->UpdateSource("original source"); + info->FlagSourceAsCompiled(false); + + EXPECT_EQ("original source", *info->deferred_compilation_source()); + info->UpdateSource("different!"); + EXPECT_EQ("original source", *info->deferred_compilation_source()); + info->FlagSourceAsCompiled(true); + EXPECT_EQ("different!", *info->deferred_compilation_source()); +} +} // namespace gles2 +} // namespace gpu diff --git a/gpu/command_buffer/service/shader_translator.h b/gpu/command_buffer/service/shader_translator.h index f82343e..be5b1f6 100644 --- a/gpu/command_buffer/service/shader_translator.h +++ b/gpu/command_buffer/service/shader_translator.h @@ -43,6 +43,12 @@ class ShaderTranslatorInterface { size(_size), name(_name) { } + bool operator==( + const ShaderTranslatorInterface::VariableInfo& other) const { + return type == other.type && + size == other.size && + strcmp(name.c_str(), other.name.c_str()) == 0; + } int type; int size; diff --git a/gpu/command_buffer/service/test_helper.cc b/gpu/command_buffer/service/test_helper.cc index bd95c4f7..86eab65 100644 --- a/gpu/command_buffer/service/test_helper.cc +++ b/gpu/command_buffer/service/test_helper.cc @@ -4,6 +4,9 @@ #include "gpu/command_buffer/service/test_helper.h" +#include <string> +#include <algorithm> + #include "base/string_number_conversions.h" #include "base/string_tokenizer.h" #include "gpu/command_buffer/common/gl_mock.h" @@ -12,8 +15,6 @@ #include "gpu/command_buffer/service/program_manager.h" #include "testing/gtest/include/gtest/gtest.h" -#include <string.h> - using ::testing::_; using ::testing::DoAll; using ::testing::InSequence; @@ -368,17 +369,11 @@ void TestHelper::SetupExpectationsForClearingUniforms( } } -void TestHelper::SetupShader( +void TestHelper::SetupProgramSuccessExpectations( ::gfx::MockGLInterface* gl, AttribInfo* attribs, size_t num_attribs, UniformInfo* uniforms, size_t num_uniforms, GLuint service_id) { - InSequence s; - - EXPECT_CALL(*gl, - LinkProgram(service_id)) - .Times(1) - .RetiresOnSaturation(); EXPECT_CALL(*gl, GetProgramiv(service_id, GL_LINK_STATUS, _)) .WillOnce(SetArgumentPointee<2>(1)) @@ -480,6 +475,22 @@ void TestHelper::SetupShader( } } +void TestHelper::SetupShader( + ::gfx::MockGLInterface* gl, + AttribInfo* attribs, size_t num_attribs, + UniformInfo* uniforms, size_t num_uniforms, + GLuint service_id) { + InSequence s; + + EXPECT_CALL(*gl, + LinkProgram(service_id)) + .Times(1) + .RetiresOnSaturation(); + + SetupProgramSuccessExpectations( + gl, attribs, num_attribs, uniforms, num_uniforms, service_id); +} + } // namespace gles2 } // namespace gpu diff --git a/gpu/command_buffer/service/test_helper.h b/gpu/command_buffer/service/test_helper.h index c0dd80f..9fc4550 100644 --- a/gpu/command_buffer/service/test_helper.h +++ b/gpu/command_buffer/service/test_helper.h @@ -81,6 +81,11 @@ class TestHelper { UniformInfo* uniforms, size_t num_uniforms, GLuint service_id); + static void SetupProgramSuccessExpectations(::gfx::MockGLInterface* gl, + AttribInfo* attribs, size_t num_attribs, + UniformInfo* uniforms, size_t num_uniforms, + GLuint service_id); + private: static void SetupTextureInitializationExpectations( ::gfx::MockGLInterface* gl, GLenum target); diff --git a/gpu/command_buffer/tests/gl_manager.cc b/gpu/command_buffer/tests/gl_manager.cc index 14db773..4fad23e 100644 --- a/gpu/command_buffer/tests/gl_manager.cc +++ b/gpu/command_buffer/tests/gl_manager.cc @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "gpu/command_buffer/tests/gl_manager.h" + +#include <vector> + #include "base/at_exit.h" #include "base/bind.h" #include "gpu/command_buffer/client/gles2_implementation.h" @@ -12,7 +16,6 @@ #include "gpu/command_buffer/service/context_group.h" #include "gpu/command_buffer/service/gpu_scheduler.h" #include "gpu/command_buffer/service/mailbox_manager.h" -#include "gpu/command_buffer/tests/gl_manager.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_share_group.h" @@ -94,8 +97,8 @@ void GLManager::Setup( attribs.push_back(EGL_NONE); if (!context_group) { - context_group = new gles2::ContextGroup( - mailbox_manager_.get(), kBindGeneratesResource); + context_group = new gles2::ContextGroup(mailbox_manager_.get(), + kBindGeneratesResource); } decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group)); diff --git a/gpu/command_buffer_service.gypi b/gpu/command_buffer_service.gypi index 4fbd06f..46ffcf0 100644 --- a/gpu/command_buffer_service.gypi +++ b/gpu/command_buffer_service.gypi @@ -57,6 +57,8 @@ 'command_buffer/service/id_manager.cc', 'command_buffer/service/mailbox_manager.cc', 'command_buffer/service/mailbox_manager.h', + 'command_buffer/service/memory_program_cache.h', + 'command_buffer/service/memory_program_cache.cc', 'command_buffer/service/mocks.h', 'command_buffer/service/program_manager.h', 'command_buffer/service/program_manager.cc', @@ -64,6 +66,10 @@ 'command_buffer/service/query_manager.cc', 'command_buffer/service/renderbuffer_manager.h', 'command_buffer/service/renderbuffer_manager.cc', + 'command_buffer/service/program_cache.h', + 'command_buffer/service/program_cache.cc', + 'command_buffer/service/program_cache_lru_helper.h', + 'command_buffer/service/program_cache_lru_helper.cc', 'command_buffer/service/shader_manager.h', 'command_buffer/service/shader_manager.cc', 'command_buffer/service/shader_translator.h', diff --git a/gpu/demos/framework/window.cc b/gpu/demos/framework/window.cc index d30e427..1421aae 100644 --- a/gpu/demos/framework/window.cc +++ b/gpu/demos/framework/window.cc @@ -4,6 +4,8 @@ #include "gpu/demos/framework/window.h" +#include <vector> + #include "base/bind.h" #include "base/bind_helpers.h" #include "base/memory/ref_counted.h" diff --git a/gpu/gpu_common.gypi b/gpu/gpu_common.gypi index d117fc0..2546635 100644 --- a/gpu/gpu_common.gypi +++ b/gpu/gpu_common.gypi @@ -180,11 +180,14 @@ 'command_buffer/service/gl_surface_mock.h', 'command_buffer/service/gpu_scheduler_unittest.cc', 'command_buffer/service/id_manager_unittest.cc', + 'command_buffer/service/memory_program_cache_unittest.cc', 'command_buffer/service/mocks.cc', 'command_buffer/service/mocks.h', 'command_buffer/service/program_manager_unittest.cc', 'command_buffer/service/query_manager_unittest.cc', 'command_buffer/service/renderbuffer_manager_unittest.cc', + 'command_buffer/service/program_cache_lru_helper_unittest.cc', + 'command_buffer/service/program_cache_unittest.cc', 'command_buffer/service/shader_manager_unittest.cc', 'command_buffer/service/shader_translator_unittest.cc', 'command_buffer/service/stream_texture_mock.cc', diff --git a/ui/gl/generate_bindings.py b/ui/gl/generate_bindings.py index 5e7eccd..11ccff2 100755 --- a/ui/gl/generate_bindings.py +++ b/ui/gl/generate_bindings.py @@ -296,6 +296,12 @@ GL_FUNCTIONS = [ 'names': ['glGetIntegerv'], 'arguments': 'GLenum pname, GLint* params', }, { 'return_type': 'void', + 'names': ['glGetProgramBinary', 'glGetProgramBinaryOES'], + 'arguments': 'GLuint program, GLsizei bufSize, GLsizei* length, ' + 'GLenum* binaryFormat, GLvoid* binary', + 'other_extensions': ['ARB_get_program_binary', + 'OES_get_program_binary'] }, +{ 'return_type': 'void', 'names': ['glGetProgramiv'], 'arguments': 'GLuint program, GLenum pname, GLint* params', }, { 'return_type': 'void', @@ -424,6 +430,16 @@ GL_FUNCTIONS = [ 'names': ['glPolygonOffset'], 'arguments': 'GLfloat factor, GLfloat units', }, { 'return_type': 'void', + 'names': ['glProgramBinary', 'glProgramBinaryOES'], + 'arguments': 'GLuint program, GLenum binaryFormat, ' + 'const GLvoid* binary, GLsizei length', + 'other_extensions': ['ARB_get_program_binary', + 'OES_get_program_binary'] }, +{ 'return_type': 'void', + 'names': ['glProgramParameteri'], + 'arguments': 'GLuint program, GLenum pname, GLint value', + 'other_extensions': ['ARB_get_program_binary'] }, +{ 'return_type': 'void', 'names': ['glQueryCounter'], 'arguments': 'GLuint id, GLenum target', }, { 'return_type': 'void', diff --git a/ui/gl/gl_interface.h b/ui/gl/gl_interface.h index af150de..6c6df36 100644 --- a/ui/gl/gl_interface.h +++ b/ui/gl/gl_interface.h @@ -272,6 +272,12 @@ class GL_EXPORT GLInterface { virtual void GetIntegerv(GLenum pname, GLint* params) = 0; + virtual void GetProgramBinary(GLuint program, + GLsizei bufSize, + GLsizei* length, + GLenum* binaryFormat, + GLvoid* binary) = 0; + virtual void GetProgramiv(GLuint program, GLenum pname, GLint* params) = 0; // TODO(gman): Implement this @@ -398,6 +404,13 @@ class GL_EXPORT GLInterface { virtual void PolygonOffset(GLfloat factor, GLfloat units) = 0; + virtual void ProgramBinary(GLuint program, + GLenum binaryFormat, + const GLvoid* binary, + GLsizei length) = 0; + + virtual void ProgramParameteri(GLuint program, GLenum pname, GLint value) = 0; + virtual void QueryCounter(GLuint id, GLenum target) = 0; virtual void ReadBuffer(GLenum src) = 0; diff --git a/webkit/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc b/webkit/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc index b7e93df..06ad53f 100644 --- a/webkit/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc +++ b/webkit/gpu/webgraphicscontext3d_in_process_command_buffer_impl.cc @@ -12,6 +12,7 @@ #include <algorithm> #include <set> +#include <string> #include "base/bind.h" #include "base/bind_helpers.h" |