| // Copyright 2012 The Chromium Authors | |
| // 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 <stddef.h> | |
| #include <stdint.h> | |
| #include <array> | |
| #include <memory> | |
| #include <numeric> | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/span.h" | |
| #include "base/functional/bind.h" | |
| #include "base/functional/callback_helpers.h" | |
| #include "base/memory/memory_pressure_listener_registry.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/memory/raw_span.h" | |
| #include "base/memory_coordinator/memory_coordinator_features.h" | |
| #include "base/memory_coordinator/test_memory_consumer_registry.h" | |
| #include "base/memory_coordinator/utils.h" | |
| #include "base/numerics/safe_conversions.h" | |
| #include "base/run_loop.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/test/scoped_feature_list.h" | |
| #include "build/build_config.h" | |
| #include "gpu/command_buffer/common/gles2_cmd_format.h" | |
| #include "gpu/command_buffer/common/shm_count.h" | |
| #include "gpu/command_buffer/service/gl_utils.h" | |
| #include "gpu/command_buffer/service/gpu_service_test.h" | |
| #include "gpu/command_buffer/service/shader_manager.h" | |
| #include "gpu/command_buffer/service/shader_translator.h" | |
| #include "gpu/command_buffer/service/test_helper.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "ui/gl/gl_bindings.h" | |
| #include "ui/gl/gl_mock.h" | |
| using ::testing::_; | |
| using ::testing::Invoke; | |
| using ::testing::SetArgPointee; | |
| namespace gpu { | |
| namespace gles2 { | |
| class ProgramBinaryEmulator { | |
| public: | |
| ProgramBinaryEmulator(GLenum format, base::span<const char> binary) | |
| : format_(format), binary_(binary) {} | |
| void GetProgramBinary(GLuint program, | |
| GLsizei buffer_size, | |
| GLsizei* length, | |
| GLenum* format, | |
| GLvoid* binary) { | |
| if (length) { | |
| *length = binary_.size(); | |
| } | |
| *format = format_; | |
| // SAFETY: This is an OpenGL entry point and cannot be spanified: | |
| // https://docs.gl/es3/glGetProgramBinary | |
| // We can only trust what we're given. | |
| base::span<char> out_arg = | |
| UNSAFE_BUFFERS(base::span(static_cast<char*>(binary), binary_.size())); | |
| out_arg.copy_from(binary_); | |
| } | |
| void ProgramBinary(GLuint program, | |
| GLenum format, | |
| const GLvoid* binary, | |
| GLsizei length) { | |
| size_t unsigned_length = base::checked_cast<size_t>(length); | |
| // SAFETY: This is an OpenGL entry point and cannot be spanified: | |
| // https://docs.gl/es3/glProgramBinary | |
| // We can only trust what we're given. | |
| base::span<const char> cast_binary = UNSAFE_BUFFERS( | |
| base::span(static_cast<const char*>(binary), unsigned_length)); | |
| // format and length are verified by matcher | |
| EXPECT_EQ(binary_.first(unsigned_length), | |
| cast_binary.first(unsigned_length)); | |
| } | |
| GLsizei length() const { return binary_.size(); } | |
| GLenum format() const { return format_; } | |
| const char* binary() const { return binary_.data(); } | |
| private: | |
| GLenum format_; | |
| base::raw_span<const char> binary_; | |
| }; | |
| class MemoryProgramCacheTest : public GpuServiceTest, public DecoderClient { | |
| public: | |
| static const size_t kCacheSizeBytes = 1024; | |
| static const bool kDisableGpuDiskCache = false; | |
| static const bool kDisableCachingForTransformFeedback = false; | |
| static const GLuint kVertexShaderClientId = 90; | |
| static const GLuint kVertexShaderServiceId = 100; | |
| static const GLuint kFragmentShaderClientId = 91; | |
| static const GLuint kFragmentShaderServiceId = 100; | |
| static constexpr const char* kDefaultFragmentSource = | |
| "bbbal sldkdkdkas 134 ad"; | |
| MemoryProgramCacheTest() | |
| : cache_(new MemoryProgramCache(kCacheSizeBytes, | |
| kDisableGpuDiskCache, | |
| kDisableCachingForTransformFeedback, | |
| &use_shader_cache_shm_count_)), | |
| shader_manager_(nullptr), | |
| vertex_shader_(nullptr), | |
| fragment_shader_(nullptr), | |
| shader_cache_count_(0) {} | |
| ~MemoryProgramCacheTest() override { | |
| vertex_shader_ = nullptr; | |
| fragment_shader_ = nullptr; | |
| shader_manager_.Destroy(false); | |
| } | |
| void OnConsoleMessage(int32_t id, const std::string& message) override {} | |
| void CacheBlob(gpu::GpuDiskCacheType type, | |
| const std::string& key, | |
| const std::string& blob) override { | |
| ASSERT_EQ(type, gpu::GpuDiskCacheType::kGlShaders); | |
| shader_cache_count_++; | |
| shader_cache_shader_ = blob; | |
| } | |
| void OnFenceSyncRelease(uint64_t release) override {} | |
| void OnDescheduleUntilFinished() override {} | |
| void OnRescheduleAfterFinished() override {} | |
| void ScheduleGrContextCleanup() override {} | |
| void HandleReturnData(base::span<const uint8_t> data) override {} | |
| int32_t shader_cache_count() { return shader_cache_count_; } | |
| const std::string& shader_cache_shader() { return shader_cache_shader_; } | |
| protected: | |
| void SetUp() override { | |
| GpuServiceTest::SetUpWithGLVersion("OpenGL ES 3.0", | |
| "GL_OES_get_program_binary"); | |
| vertex_shader_ = shader_manager_.CreateShader(kVertexShaderClientId, | |
| kVertexShaderServiceId, | |
| GL_VERTEX_SHADER); | |
| fragment_shader_ = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, | |
| kFragmentShaderServiceId, | |
| GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(vertex_shader_ != nullptr); | |
| ASSERT_TRUE(fragment_shader_ != nullptr); | |
| AttributeMap vertex_attrib_map; | |
| UniformMap vertex_uniform_map; | |
| VaryingMap vertex_varying_map; | |
| OutputVariableList vertex_output_variable_list; | |
| AttributeMap fragment_attrib_map; | |
| UniformMap fragment_uniform_map; | |
| VaryingMap fragment_varying_map; | |
| OutputVariableList fragment_output_variable_list; | |
| vertex_attrib_map["a"] = TestHelper::ConstructAttribute( | |
| GL_FLOAT_VEC2, 34, GL_LOW_FLOAT, false, "a"); | |
| vertex_uniform_map["a"] = TestHelper::ConstructUniform( | |
| GL_FLOAT, 10, GL_MEDIUM_FLOAT, true, "a"); | |
| vertex_uniform_map["b"] = TestHelper::ConstructUniform( | |
| GL_FLOAT_VEC3, 3114, GL_HIGH_FLOAT, true, "b"); | |
| vertex_varying_map["c"] = TestHelper::ConstructVarying( | |
| GL_FLOAT_VEC4, 2, GL_HIGH_FLOAT, true, "c"); | |
| vertex_output_variable_list.push_back(TestHelper::ConstructOutputVariable( | |
| GL_FLOAT, 0, GL_HIGH_FLOAT, true, "d")); | |
| fragment_attrib_map["jjjbb"] = TestHelper::ConstructAttribute( | |
| GL_FLOAT_MAT4, 1114, GL_MEDIUM_FLOAT, false, "jjjbb"); | |
| fragment_uniform_map["k"] = TestHelper::ConstructUniform( | |
| GL_FLOAT_MAT2, 34413, GL_MEDIUM_FLOAT, true, "k"); | |
| fragment_varying_map["c"] = TestHelper::ConstructVarying( | |
| GL_FLOAT_VEC4, 2, GL_HIGH_FLOAT, true, "c"); | |
| fragment_output_variable_list.push_back(TestHelper::ConstructOutputVariable( | |
| GL_FLOAT, 0, GL_HIGH_FLOAT, true, "d")); | |
| vertex_shader_->set_source("bbbalsldkdkdkd"); | |
| fragment_shader_->set_source(kDefaultFragmentSource); | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader_, true, nullptr, | |
| nullptr, nullptr, &vertex_attrib_map, | |
| &vertex_uniform_map, &vertex_varying_map, | |
| nullptr, &vertex_output_variable_list, nullptr); | |
| TestHelper::SetShaderStates( | |
| gl_.get(), fragment_shader_, true, nullptr, nullptr, nullptr, | |
| &fragment_attrib_map, &fragment_uniform_map, &fragment_varying_map, | |
| nullptr, &fragment_output_variable_list, nullptr); | |
| } | |
| 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)); | |
| } | |
| base::TestMemoryConsumerRegistry test_memory_consumer_registry_; | |
| GpuProcessShmCount use_shader_cache_shm_count_; | |
| std::unique_ptr<MemoryProgramCache> cache_; | |
| ShaderManager shader_manager_; | |
| // These shaders are owned by |shader_manager_|. | |
| raw_ptr<Shader> vertex_shader_; | |
| raw_ptr<Shader> fragment_shader_; | |
| int32_t shader_cache_count_; | |
| std::string shader_cache_shader_; | |
| std::vector<std::string> varyings_; | |
| void SimulateMemoryPressure(base::MemoryPressureLevel level) { | |
| base::MemoryLimit memory_limit = base::MemoryLimit::Default(); | |
| switch (level) { | |
| case base::MEMORY_PRESSURE_LEVEL_NONE: | |
| memory_limit = base::kNoMemoryPressureThreshold; | |
| break; | |
| case base::MEMORY_PRESSURE_LEVEL_MODERATE: | |
| memory_limit = base::kModerateMemoryPressureThreshold; | |
| break; | |
| case base::MEMORY_PRESSURE_LEVEL_CRITICAL: | |
| memory_limit = base::kCriticalMemoryPressureThreshold; | |
| break; | |
| } | |
| base::RunLoop run_loop; | |
| test_memory_consumer_registry_.NotifyUpdateMemoryLimitAsync( | |
| memory_limit, base::DoNothing()); | |
| test_memory_consumer_registry_.NotifyReleaseMemoryAsync( | |
| run_loop.QuitClosure()); | |
| run_loop.Run(); | |
| } | |
| void SaveProgram(int program_id, | |
| const std::string& source, | |
| const std::vector<char>& binary) { | |
| fragment_shader_->set_source(source); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader_, true); | |
| ProgramBinaryEmulator emulator(1, binary); | |
| SetExpectationsForSaveLinkedProgram(program_id, &emulator); | |
| cache_->SaveLinkedProgram(program_id, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| } | |
| void SaveProgramFailure(int i, int blob_length) { | |
| fragment_shader_->set_source("shader" + base::NumberToString(i)); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader_, true); | |
| std::vector<char> binary(blob_length, static_cast<char>(i)); | |
| ProgramBinaryEmulator emulator(1, binary); | |
| EXPECT_CALL(*gl_.get(), | |
| GetProgramiv(10 + i, GL_PROGRAM_BINARY_LENGTH_OES, _)) | |
| .WillOnce(SetArgPointee<2>(emulator.length())); | |
| cache_->SaveLinkedProgram(10 + i, vertex_shader_, fragment_shader_, nullptr, | |
| varyings_, GL_NONE, this); | |
| } | |
| void CheckProgramStatus(const std::string& source, | |
| ProgramCache::LinkedProgramStatus expected_status) { | |
| fragment_shader_->set_source(source); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader_, true); | |
| EXPECT_EQ(expected_status, cache_->GetLinkedProgramStatus( | |
| vertex_shader_->last_compiled_signature(), | |
| fragment_shader_->last_compiled_signature(), | |
| nullptr, varyings_, GL_NONE)); | |
| } | |
| }; | |
| namespace { | |
| std::array<char, 20> TwentyIncrementingChars() { | |
| std::array<char, 20> chars; | |
| std::iota(chars.begin(), chars.end(), 0); | |
| return chars; | |
| } | |
| } // namespace | |
| TEST_F(MemoryProgramCacheTest, CacheSave) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, | |
| cache_->GetLinkedProgramStatus( | |
| vertex_shader_->last_compiled_signature(), | |
| fragment_shader_->last_compiled_signature(), nullptr, varyings_, | |
| GL_NONE)); | |
| EXPECT_EQ(1, shader_cache_count()); | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadProgram) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, | |
| cache_->GetLinkedProgramStatus( | |
| vertex_shader_->last_compiled_signature(), | |
| fragment_shader_->last_compiled_signature(), nullptr, varyings_, | |
| GL_NONE)); | |
| EXPECT_EQ(1, shader_cache_count()); | |
| cache_->Clear(); | |
| std::string blank; | |
| cache_->LoadProgram(blank, shader_cache_shader()); | |
| EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, | |
| cache_->GetLinkedProgramStatus( | |
| vertex_shader_->last_compiled_signature(), | |
| fragment_shader_->last_compiled_signature(), nullptr, varyings_, | |
| GL_NONE)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, CacheLoadMatchesSave) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| EXPECT_EQ(1, shader_cache_count()); | |
| AttributeMap vertex_attrib_map = vertex_shader_->attrib_map(); | |
| UniformMap vertex_uniform_map = vertex_shader_->uniform_map(); | |
| VaryingMap vertex_varying_map = vertex_shader_->varying_map(); | |
| OutputVariableList vertex_output_variable_list = | |
| vertex_shader_->output_variable_list(); | |
| AttributeMap fragment_attrib_map = fragment_shader_->attrib_map(); | |
| UniformMap fragment_uniform_map = fragment_shader_->uniform_map(); | |
| VaryingMap fragment_varying_map = fragment_shader_->varying_map(); | |
| OutputVariableList fragment_output_variable_list = | |
| fragment_shader_->output_variable_list(); | |
| vertex_shader_->set_attrib_map(AttributeMap()); | |
| vertex_shader_->set_uniform_map(UniformMap()); | |
| vertex_shader_->set_varying_map(VaryingMap()); | |
| vertex_shader_->set_output_variable_list(OutputVariableList()); | |
| fragment_shader_->set_attrib_map(AttributeMap()); | |
| fragment_shader_->set_uniform_map(UniformMap()); | |
| fragment_shader_->set_varying_map(VaryingMap()); | |
| fragment_shader_->set_output_variable_list(OutputVariableList()); | |
| SetExpectationsForLoadLinkedProgram(kProgramId, &emulator); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_SUCCESS, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| // apparently the hash_map implementation on android doesn't have the | |
| // equality operator | |
| #if !BUILDFLAG(IS_ANDROID) | |
| EXPECT_EQ(vertex_attrib_map, vertex_shader_->attrib_map()); | |
| EXPECT_EQ(vertex_uniform_map, vertex_shader_->uniform_map()); | |
| EXPECT_EQ(vertex_varying_map, vertex_shader_->varying_map()); | |
| EXPECT_EQ(vertex_output_variable_list, | |
| vertex_shader_->output_variable_list()); | |
| EXPECT_EQ(fragment_attrib_map, fragment_shader_->attrib_map()); | |
| EXPECT_EQ(fragment_uniform_map, fragment_shader_->uniform_map()); | |
| EXPECT_EQ(fragment_varying_map, fragment_shader_->varying_map()); | |
| EXPECT_EQ(fragment_output_variable_list, | |
| fragment_shader_->output_variable_list()); | |
| #endif | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadProgramMatchesSave) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| EXPECT_EQ(1, shader_cache_count()); | |
| AttributeMap vertex_attrib_map = vertex_shader_->attrib_map(); | |
| UniformMap vertex_uniform_map = vertex_shader_->uniform_map(); | |
| VaryingMap vertex_varying_map = vertex_shader_->varying_map(); | |
| OutputVariableList vertex_output_variable_list = | |
| vertex_shader_->output_variable_list(); | |
| AttributeMap fragment_attrib_map = fragment_shader_->attrib_map(); | |
| UniformMap fragment_uniform_map = fragment_shader_->uniform_map(); | |
| VaryingMap fragment_varying_map = fragment_shader_->varying_map(); | |
| OutputVariableList fragment_output_variable_list = | |
| fragment_shader_->output_variable_list(); | |
| vertex_shader_->set_attrib_map(AttributeMap()); | |
| vertex_shader_->set_uniform_map(UniformMap()); | |
| vertex_shader_->set_varying_map(VaryingMap()); | |
| vertex_shader_->set_output_variable_list(OutputVariableList()); | |
| fragment_shader_->set_attrib_map(AttributeMap()); | |
| fragment_shader_->set_uniform_map(UniformMap()); | |
| fragment_shader_->set_varying_map(VaryingMap()); | |
| fragment_shader_->set_output_variable_list(OutputVariableList()); | |
| SetExpectationsForLoadLinkedProgram(kProgramId, &emulator); | |
| std::string blank; | |
| cache_->Clear(); | |
| cache_->LoadProgram(blank, shader_cache_shader()); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_SUCCESS, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| // apparently the hash_map implementation on android doesn't have the | |
| // equality operator | |
| #if !BUILDFLAG(IS_ANDROID) | |
| EXPECT_EQ(vertex_attrib_map, vertex_shader_->attrib_map()); | |
| EXPECT_EQ(vertex_uniform_map, vertex_shader_->uniform_map()); | |
| EXPECT_EQ(vertex_varying_map, vertex_shader_->varying_map()); | |
| EXPECT_EQ(vertex_output_variable_list, | |
| vertex_shader_->output_variable_list()); | |
| EXPECT_EQ(fragment_attrib_map, fragment_shader_->attrib_map()); | |
| EXPECT_EQ(fragment_uniform_map, fragment_shader_->uniform_map()); | |
| EXPECT_EQ(fragment_varying_map, fragment_shader_->varying_map()); | |
| EXPECT_EQ(fragment_output_variable_list, | |
| fragment_shader_->output_variable_list()); | |
| #endif | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadFailOnLinkFalse) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| SetExpectationsForLoadLinkedProgramFailure(kProgramId, &emulator); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadFailOnDifferentSource) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| const std::string vertex_orig_source = vertex_shader_->last_compiled_source(); | |
| vertex_shader_->set_source("different!"); | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader_, true); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| vertex_shader_->set_source(vertex_orig_source); | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader_, true); | |
| fragment_shader_->set_source("different!"); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader_, true); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadFailOnDifferentMap) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| ProgramCache::LocationMap binding_map; | |
| binding_map["test"] = 512; | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| &binding_map, varyings_, GL_NONE, this); | |
| binding_map["different!"] = 59; | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| &binding_map, varyings_, GL_NONE, this)); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadFailOnDifferentTransformFeedbackVaryings) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| varyings_.push_back("test"); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_INTERLEAVED_ATTRIBS, this); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_SEPARATE_ATTRIBS, this)); | |
| varyings_.push_back("different!"); | |
| EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, | |
| fragment_shader_, nullptr, varyings_, | |
| GL_INTERLEAVED_ATTRIBS, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadFailIfTransformFeedbackCachingDisabled) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| // Forcibly reset the program cache so we can disable caching of | |
| // programs which include transform feedback varyings. | |
| cache_.reset(new MemoryProgramCache(kCacheSizeBytes, kDisableGpuDiskCache, | |
| true, &use_shader_cache_shm_count_)); | |
| varyings_.push_back("test"); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_INTERLEAVED_ATTRIBS, this); | |
| EXPECT_EQ(ProgramCache::PROGRAM_LOAD_FAILURE, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, | |
| fragment_shader_, nullptr, varyings_, | |
| GL_INTERLEAVED_ATTRIBS, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, MemoryProgramCacheEviction) { | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| std::vector<char> binary1(test_binary.begin(), test_binary.end()); | |
| SaveProgram(kProgramId, kDefaultFragmentSource, binary1); | |
| const int kEvictingProgramId = 11; | |
| const GLuint kEvictingBinaryLength = kCacheSizeBytes - test_binary.size() + 1; | |
| auto bigTestBinary = base::HeapArray<char>::Uninit(kEvictingBinaryLength); | |
| for (size_t i = 0; i < kEvictingBinaryLength; ++i) { | |
| bigTestBinary[i] = i % 250; | |
| } | |
| std::vector<char> binary2(bigTestBinary.begin(), bigTestBinary.end()); | |
| SaveProgram(kEvictingProgramId, "al sdfkjdk", binary2); | |
| CheckProgramStatus("al sdfkjdk", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus(kDefaultFragmentSource, ProgramCache::LINK_UNKNOWN); | |
| } | |
| TEST_F(MemoryProgramCacheTest, SaveCorrectProgram) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator1(kFormat, test_binary); | |
| vertex_shader_->set_source("different!"); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator1); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, | |
| cache_->GetLinkedProgramStatus( | |
| vertex_shader_->last_compiled_signature(), | |
| fragment_shader_->last_compiled_signature(), nullptr, varyings_, | |
| GL_NONE)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, LoadCorrectProgram) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| EXPECT_EQ(ProgramCache::LINK_SUCCEEDED, | |
| cache_->GetLinkedProgramStatus( | |
| vertex_shader_->last_compiled_signature(), | |
| fragment_shader_->last_compiled_signature(), nullptr, varyings_, | |
| GL_NONE)); | |
| SetExpectationsForLoadLinkedProgram(kProgramId, &emulator); | |
| fragment_shader_->set_source("different!"); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_SUCCESS, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, OverwriteOnNewSave) { | |
| const GLenum kFormat = 1; | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| ProgramBinaryEmulator emulator(kFormat, test_binary); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| std::array<char, test_binary.size()> test_binary2; | |
| for (size_t i = 0; i < test_binary.size(); ++i) { | |
| test_binary2[i] = (i*2) % 250; | |
| } | |
| ProgramBinaryEmulator emulator2(kFormat, test_binary2); | |
| SetExpectationsForSaveLinkedProgram(kProgramId, &emulator2); | |
| cache_->SaveLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this); | |
| SetExpectationsForLoadLinkedProgram(kProgramId, &emulator2); | |
| EXPECT_EQ( | |
| ProgramCache::PROGRAM_LOAD_SUCCESS, | |
| cache_->LoadLinkedProgram(kProgramId, vertex_shader_, fragment_shader_, | |
| nullptr, varyings_, GL_NONE, this)); | |
| } | |
| TEST_F(MemoryProgramCacheTest, MemoryProgramCacheTrim) { | |
| // Insert a 20 byte program. | |
| const int kProgramId = 10; | |
| const auto test_binary = TwentyIncrementingChars(); | |
| std::vector<char> binary(test_binary.begin(), test_binary.end()); | |
| SaveProgram(kProgramId, kDefaultFragmentSource, binary); | |
| // Insert a second 20 byte program. | |
| const int kSecondProgramId = 11; | |
| SaveProgram(kSecondProgramId, "al sdfkjdk", binary); | |
| // Both programs should be present. | |
| CheckProgramStatus("al sdfkjdk", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus(kDefaultFragmentSource, ProgramCache::LINK_SUCCEEDED); | |
| // Trim cache to 20 bytes - this should evict the first program. | |
| cache_->Trim(20); | |
| CheckProgramStatus("al sdfkjdk", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus(kDefaultFragmentSource, ProgramCache::LINK_UNKNOWN); | |
| // Trim cache to 0 bytes - this should evict both programs. | |
| cache_->Trim(0); | |
| CheckProgramStatus("al sdfkjdk", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus(kDefaultFragmentSource, ProgramCache::LINK_UNKNOWN); | |
| } | |
| TEST_F(MemoryProgramCacheTest, MemoryPressure) { | |
| base::test::ScopedFeatureList scoped_feature_list; | |
| scoped_feature_list.InitAndEnableFeature(base::kStatefulMemoryPressure); | |
| const int kCacheCapacity = 4; | |
| // Compute a blob length giving us a cache capacity of 4 entries. | |
| const int kBlobLength = kCacheSizeBytes / kCacheCapacity; | |
| // Fill the cache. | |
| for (int i = 0; i < kCacheCapacity; i++) { | |
| std::vector<char> binary(kBlobLength, static_cast<char>(i)); | |
| SaveProgram(10 + i, "shader" + base::NumberToString(i), binary); | |
| } | |
| CheckProgramStatus("shader0", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader1", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader2", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader3", ProgramCache::LINK_SUCCEEDED); | |
| SimulateMemoryPressure(base::MEMORY_PRESSURE_LEVEL_MODERATE); | |
| // Cache size should be reduced to 1/4 of the max size under moderate | |
| // pressure. | |
| CheckProgramStatus("shader0", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader1", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader2", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader3", ProgramCache::LINK_SUCCEEDED); | |
| // Adding an item removes the previous one. | |
| std::vector<char> binary4(kBlobLength, static_cast<char>(4)); | |
| SaveProgram(14, "shader4", binary4); | |
| CheckProgramStatus("shader3", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader4", ProgramCache::LINK_SUCCEEDED); | |
| SimulateMemoryPressure(base::MEMORY_PRESSURE_LEVEL_CRITICAL); | |
| // Cache is emptied under critical pressure. | |
| CheckProgramStatus("shader4", ProgramCache::LINK_UNKNOWN); | |
| // Verify new insertions are rejected. | |
| SaveProgramFailure(5, kBlobLength); | |
| // Return memory pressure state to normal. | |
| SimulateMemoryPressure(base::MEMORY_PRESSURE_LEVEL_NONE); | |
| for (int i = 6; i < 6 + kCacheCapacity; i++) { | |
| std::vector<char> binary(kBlobLength, static_cast<char>(i)); | |
| SaveProgram(10 + i, "shader" + base::NumberToString(i), binary); | |
| } | |
| CheckProgramStatus("shader6", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader7", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader8", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader9", ProgramCache::LINK_SUCCEEDED); | |
| } | |
| TEST_F(MemoryProgramCacheTest, MemoryPressureStateful) { | |
| base::test::ScopedFeatureList scoped_feature_list; | |
| scoped_feature_list.InitAndEnableFeature(base::kStatefulMemoryPressure); | |
| const int kCacheCapacity = 4; | |
| const int kBlobLength = kCacheSizeBytes / kCacheCapacity; | |
| // Fill the cache. | |
| for (int i = 0; i < kCacheCapacity; i++) { | |
| std::vector<char> binary(kBlobLength, static_cast<char>(i)); | |
| SaveProgram(10 + i, "shader" + base::NumberToString(i), binary); | |
| } | |
| CheckProgramStatus("shader0", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader1", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader2", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader3", ProgramCache::LINK_SUCCEEDED); | |
| // Moderate pressure (50%). | |
| base::RunLoop run_loop; | |
| test_memory_consumer_registry_.NotifyUpdateMemoryLimitAsync( | |
| base::kModerateMemoryPressureThreshold, run_loop.QuitClosure()); | |
| run_loop.Run(); | |
| // Verify that NO memory is released yet! | |
| CheckProgramStatus("shader0", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader1", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader2", ProgramCache::LINK_SUCCEEDED); | |
| CheckProgramStatus("shader3", ProgramCache::LINK_SUCCEEDED); | |
| // Now release memory. | |
| base::RunLoop run_loop2; | |
| test_memory_consumer_registry_.NotifyReleaseMemoryAsync( | |
| run_loop2.QuitClosure()); | |
| run_loop2.Run(); | |
| // Now memory should be released! | |
| // At 50% limit, size should be 1/4 (1 item). | |
| CheckProgramStatus("shader0", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader1", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader2", ProgramCache::LINK_UNKNOWN); | |
| CheckProgramStatus("shader3", ProgramCache::LINK_SUCCEEDED); | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |