| // 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/memory/memory_pressure_listener_registry.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_span.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.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 {} |
| bool ShouldYield() override { return false; } |
| |
| 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)); |
| } |
| |
| 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_; |
| base::MemoryPressureListenerRegistry memory_pressure_listener_registry_; |
| |
| void SimulateMemoryPressure(base::MemoryPressureLevel level) { |
| base::RunLoop run_loop; |
| base::MemoryPressureListener::SimulatePressureNotificationAsync( |
| level, 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) { |
| 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); |
| } |
| |
| } // namespace gles2 |
| } // namespace gpu |