| // 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/program_manager.h" | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <array> | |
| #include <memory> | |
| #include "base/command_line.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/span.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/strings/string_view_util.h" | |
| #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/decoder_client.h" | |
| #include "gpu/command_buffer/service/feature_info.h" | |
| #include "gpu/command_buffer/service/gpu_service_test.h" | |
| #include "gpu/command_buffer/service/gpu_switches.h" | |
| #include "gpu/command_buffer/service/mocks.h" | |
| #include "gpu/command_buffer/service/shader_manager.h" | |
| #include "gpu/command_buffer/service/test_helper.h" | |
| #include "gpu/config/gpu_preferences.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "ui/gl/gl_mock.h" | |
| using ::testing::_; | |
| using ::testing::DoAll; | |
| using ::testing::Exactly; | |
| using ::testing::InSequence; | |
| using ::testing::MatcherCast; | |
| using ::testing::Pointee; | |
| using ::testing::Return; | |
| using ::testing::ReturnRef; | |
| using ::testing::SetArrayArgument; | |
| using ::testing::SetArgPointee; | |
| using ::testing::StrEq; | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| const uint32_t kMaxVaryingVectors = 8; | |
| const uint32_t kMaxDrawBuffers = 8; | |
| const uint32_t kMaxDualSourceDrawBuffers = 8; | |
| const uint32_t kMaxVertexAttribs = 8; | |
| uint32_t ComputeOffset(const void* start, const void* position) { | |
| return static_cast<const uint8_t*>(position) - | |
| static_cast<const uint8_t*>(start); | |
| } | |
| } // namespace anonymous | |
| class ProgramManagerTestBase : public GpuServiceTest, public DecoderClient { | |
| protected: | |
| virtual void SetupProgramManager() { | |
| manager_ = std::make_unique<ProgramManager>( | |
| nullptr, kMaxVaryingVectors, kMaxDrawBuffers, kMaxDualSourceDrawBuffers, | |
| kMaxVertexAttribs, gpu_preferences_, feature_info_.get(), nullptr); | |
| } | |
| void SetUpBase(const char* gl_version, | |
| const char* gl_extensions, | |
| FeatureInfo* feature_info = nullptr) { | |
| GpuServiceTest::SetUpWithGLVersion(gl_version, gl_extensions); | |
| if (!feature_info) | |
| feature_info = new FeatureInfo(); | |
| TestHelper::SetupFeatureInfoInitExpectationsWithGLVersion( | |
| gl_.get(), gl_extensions, "", gl_version, feature_info->context_type()); | |
| feature_info->InitializeForTesting(); | |
| feature_info_ = feature_info; | |
| SetupProgramManager(); | |
| } | |
| void SetUp() override { | |
| // Parameters same as GpuServiceTest::SetUp | |
| SetUpBase("OpenGL ES 2.0", "GL_EXT_framebuffer_object"); | |
| } | |
| void TearDown() override { | |
| manager_->Destroy(false); | |
| manager_.reset(); | |
| feature_info_ = nullptr; | |
| GpuServiceTest::TearDown(); | |
| } | |
| void OnConsoleMessage(int32_t id, const std::string& message) override {} | |
| void CacheBlob(gpu::GpuDiskCacheType type, | |
| const std::string& key, | |
| const std::string& blob) override {} | |
| void OnFenceSyncRelease(uint64_t release) override {} | |
| void OnDescheduleUntilFinished() override {} | |
| void OnRescheduleAfterFinished() override {} | |
| void ScheduleGrContextCleanup() override {} | |
| void HandleReturnData(base::span<const uint8_t> data) override {} | |
| std::unique_ptr<ProgramManager> manager_; | |
| GpuPreferences gpu_preferences_; | |
| scoped_refptr<FeatureInfo> feature_info_; | |
| }; | |
| class ProgramManagerTest : public ProgramManagerTestBase {}; | |
| TEST_F(ProgramManagerTest, Basic) { | |
| const GLuint kClient1Id = 1; | |
| const GLuint kService1Id = 11; | |
| const GLuint kClient2Id = 2; | |
| // Check we can create program. | |
| manager_->CreateProgram(kClient1Id, kService1Id); | |
| // Check program got created. | |
| Program* program1 = manager_->GetProgram(kClient1Id); | |
| ASSERT_TRUE(program1 != nullptr); | |
| GLuint client_id = 0; | |
| EXPECT_TRUE(manager_->GetClientId(program1->service_id(), &client_id)); | |
| EXPECT_EQ(kClient1Id, client_id); | |
| // Check we get nothing for a non-existent program. | |
| EXPECT_TRUE(manager_->GetProgram(kClient2Id) == nullptr); | |
| } | |
| // Regression test: GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT must be recognised as a | |
| // sampler. The UniformInfo constructor accepts it (sets accepts_api_type = | |
| // kUniform1i), so IsSampler() must also return true; otherwise the | |
| // SetSamplers() texture-unit range check is skipped and an attacker-supplied | |
| // out-of-range unit index is forwarded to the native driver via glUniform1i. | |
| TEST(ProgramManagerUniformInfoTest, SamplerExternal2DY2YIsSampler) { | |
| std::vector<GLint> locs = {0}; | |
| // Control: GL_SAMPLER_EXTERNAL_OES is correctly handled. | |
| Program::UniformInfo oes("s_oes", 0, GL_SAMPLER_EXTERNAL_OES, false, locs); | |
| EXPECT_EQ(UniformApiType::kUniform1i, oes.accepts_api_type); | |
| EXPECT_TRUE(oes.IsSampler()); | |
| EXPECT_EQ(1u, oes.texture_units.size()); | |
| // Bug: GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT is accepted as a kUniform1i target | |
| // (program_manager.cc:371) but IsSampler() returns false | |
| // (program_manager.h:103-126 omits it). texture_units is therefore empty, | |
| // the uniform is excluded from sampler_indices_, and Program::SetSamplers() | |
| // skips the `value[ii] < num_texture_units` bounds check at | |
| // program_manager.cc:1433. | |
| Program::UniformInfo y2y("s_y2y", 0, GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT, false, | |
| locs); | |
| EXPECT_EQ(UniformApiType::kUniform1i, y2y.accepts_api_type) | |
| << "Y2Y sampler accepts glUniform1i (program_manager.cc:371)"; | |
| EXPECT_TRUE(y2y.IsSampler()) | |
| << "GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT missing from " | |
| "UniformInfo::IsSampler() switch -- SetSamplers() texture-unit " | |
| "bounds check is bypassed"; | |
| EXPECT_EQ(1u, y2y.texture_units.size()) | |
| << "texture_units sized to 0 because IsSampler() returned false; " | |
| "uniform excluded from sampler_indices_"; | |
| } | |
| TEST_F(ProgramManagerTest, Destroy) { | |
| const GLuint kClient1Id = 1; | |
| const GLuint kService1Id = 11; | |
| // Check we can create program. | |
| Program* program0 = manager_->CreateProgram(kClient1Id, kService1Id); | |
| ASSERT_TRUE(program0 != nullptr); | |
| // Check program got created. | |
| Program* program1 = manager_->GetProgram(kClient1Id); | |
| ASSERT_EQ(program0, program1); | |
| EXPECT_CALL(*gl_, DeleteProgram(kService1Id)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| manager_->Destroy(true); | |
| // Check the resources were released. | |
| program1 = manager_->GetProgram(kClient1Id); | |
| ASSERT_TRUE(program1 == nullptr); | |
| } | |
| TEST_F(ProgramManagerTest, DeleteBug) { | |
| ShaderManager shader_manager(nullptr); | |
| const GLuint kClient1Id = 1; | |
| const GLuint kClient2Id = 2; | |
| const GLuint kService1Id = 11; | |
| const GLuint kService2Id = 12; | |
| // Check we can create program. | |
| scoped_refptr<Program> program1( | |
| manager_->CreateProgram(kClient1Id, kService1Id)); | |
| scoped_refptr<Program> program2( | |
| manager_->CreateProgram(kClient2Id, kService2Id)); | |
| // Check program got created. | |
| ASSERT_TRUE(program1.get()); | |
| ASSERT_TRUE(program2.get()); | |
| manager_->UseProgram(program1.get()); | |
| manager_->MarkAsDeleted(&shader_manager, program1.get()); | |
| // Program will be deleted when last ref is released. | |
| EXPECT_CALL(*gl_, DeleteProgram(kService2Id)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| manager_->MarkAsDeleted(&shader_manager, program2.get()); | |
| EXPECT_TRUE(manager_->IsOwned(program1.get())); | |
| EXPECT_FALSE(manager_->IsOwned(program2.get())); | |
| } | |
| TEST_F(ProgramManagerTest, Program) { | |
| const GLuint kClient1Id = 1; | |
| const GLuint kService1Id = 11; | |
| // Check we can create program. | |
| Program* program1 = manager_->CreateProgram(kClient1Id, kService1Id); | |
| ASSERT_TRUE(program1); | |
| EXPECT_EQ(kService1Id, program1->service_id()); | |
| EXPECT_FALSE(program1->InUse()); | |
| EXPECT_FALSE(program1->IsValid()); | |
| EXPECT_FALSE(program1->IsDeleted()); | |
| EXPECT_FALSE(program1->CanLink()); | |
| EXPECT_TRUE(program1->log_info() == nullptr); | |
| } | |
| class ProgramManagerWithShaderTest : public ProgramManagerTestBase { | |
| public: | |
| static const GLint kNumVertexAttribs = 16; | |
| static const GLuint kClientProgramId = 123; | |
| static const GLuint kServiceProgramId = 456; | |
| static const GLuint kVertexShaderClientId = 201; | |
| static const GLuint kFragmentShaderClientId = 202; | |
| static const GLuint kVertexShaderServiceId = 301; | |
| static const GLuint kFragmentShaderServiceId = 302; | |
| static const char* kAttrib1Name; | |
| static const char* kAttrib2Name; | |
| static const char* kAttrib3Name; | |
| static const char* kAttrib4Name; | |
| static const GLint kAttrib1Size = 1; | |
| static const GLint kAttrib2Size = 1; | |
| static const GLint kAttrib3Size = 1; | |
| static const GLint kAttrib4Size = 1; | |
| static const GLenum kAttrib1Precision = GL_MEDIUM_FLOAT; | |
| static const GLenum kAttrib2Precision = GL_HIGH_FLOAT; | |
| static const GLenum kAttrib3Precision = GL_LOW_INT; | |
| static const GLenum kAttrib4Precision = GL_HIGH_FLOAT; | |
| static const bool kAttribStaticUse = true; | |
| static const GLint kAttrib1Location = 0; | |
| static const GLint kAttrib2Location = 1; | |
| static const GLint kAttrib3Location = 2; | |
| static const GLint kAttrib4Location = 3; | |
| static const GLenum kAttrib1Type = GL_FLOAT_VEC4; | |
| static const GLenum kAttrib2Type = GL_FLOAT_VEC2; | |
| static const GLenum kAttrib3Type = GL_INT_VEC3; | |
| static const GLenum kAttrib4Type = GL_FLOAT_MAT3x2; | |
| static const GLint kInvalidAttribLocation = 30; | |
| static const GLint kBadAttribIndex = kNumVertexAttribs; | |
| static const char* kUniform1Name; | |
| static const char* kUniform2Name; | |
| static const char* kUniform2NameWithArrayIndex; | |
| static const char* kUniform3Name; | |
| static const char* kUniform3NameWithArrayIndex; | |
| static const GLint kUniform1Size = 1; | |
| static const GLint kUniform2Size = 3; | |
| static const GLint kUniform3Size = 2; | |
| static const int kUniform1Precision = GL_LOW_FLOAT; | |
| static const int kUniform2Precision = GL_MEDIUM_INT; | |
| static const int kUniform3Precision = GL_HIGH_FLOAT; | |
| static const int kUniform1StaticUse = 1; | |
| static const int kUniform2StaticUse = 1; | |
| static const int kUniform3StaticUse = 1; | |
| static const GLint kUniform1FakeLocation = 0; // These are hard coded | |
| static const GLint kUniform2FakeLocation = 1; // to match | |
| static const GLint kUniform3FakeLocation = 2; // ProgramManager. | |
| static const GLint kUniform1RealLocation = 11; | |
| static const GLint kUniform2RealLocation = 22; | |
| static const GLint kUniform3RealLocation = 33; | |
| static const GLint kUniform1DesiredLocation = -1; | |
| static const GLint kUniform2DesiredLocation = -1; | |
| static const GLint kUniform3DesiredLocation = -1; | |
| static const GLenum kUniform1Type = GL_FLOAT_VEC4; | |
| static const GLenum kUniform2Type = GL_INT_VEC2; | |
| static const GLenum kUniform3Type = GL_FLOAT_VEC3; | |
| static const GLint kInvalidUniformLocation = 30; | |
| static const GLint kBadUniformIndex = 1000; | |
| static const char* kOutputVariable1Name; | |
| static const GLint kOutputVariable1Size = 0; | |
| static const GLenum kOutputVariable1Precision = GL_MEDIUM_FLOAT; | |
| static const bool kOutputVariable1StaticUse = true; | |
| static const GLint kOutputVariable1Location = -1; | |
| static const GLenum kOutputVariable1Type = GL_FLOAT_VEC4; | |
| static const size_t kNumAttribs; | |
| static const size_t kNumUniforms; | |
| ProgramManagerWithShaderTest() : shader_manager_(nullptr) {} | |
| protected: | |
| typedef TestHelper::AttribInfo AttribInfo; | |
| typedef TestHelper::UniformInfo UniformInfo; | |
| typedef TestHelper::ProgramOutputInfo ProgramOutputInfo; | |
| typedef enum { | |
| kVarUniform, | |
| kVarVarying, | |
| kVarAttribute, | |
| kVarOutput, | |
| } VarCategory; | |
| typedef struct { | |
| GLenum type; | |
| GLint size; | |
| GLenum precision; | |
| bool static_use; | |
| std::string name; | |
| VarCategory category; | |
| } VarInfo; | |
| void SetUp() override { | |
| // Need to be at least ES3 for UniformBlock related GL APIs. | |
| SetUpBase("OpenGL ES 3.0", ""); | |
| } | |
| Program* SetupDefaultProgram() { | |
| SetupShaderExpectations(kAttribs.data(), kNumAttribs, kUniforms.data(), | |
| kNumUniforms, {}, kServiceProgramId); | |
| Shader* vertex_shader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| Shader* fragment_shader = | |
| shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, | |
| GL_FRAGMENT_SHADER); | |
| EXPECT_TRUE(vertex_shader != nullptr); | |
| EXPECT_TRUE(fragment_shader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader, true); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader, true); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| EXPECT_TRUE(program != nullptr); | |
| program->AttachShader(&shader_manager_, vertex_shader); | |
| program->AttachShader(&shader_manager_, fragment_shader); | |
| program->Link(nullptr, this); | |
| return program; | |
| } | |
| void SetupShaderExpectations( | |
| AttribInfo* attribs, | |
| size_t num_attribs, | |
| UniformInfo* uniforms, | |
| size_t num_uniforms, | |
| base::span<ProgramOutputInfo> program_outputs, | |
| GLuint service_id) { | |
| TestHelper::SetupShaderExpectationsWithVaryings( | |
| gl_.get(), feature_info_.get(), attribs, num_attribs, uniforms, | |
| num_uniforms, nullptr, 0, program_outputs, service_id); | |
| } | |
| // Return true if link status matches expected_link_status | |
| bool LinkAsExpected(Program* program, | |
| bool expected_link_status, | |
| base::span<ProgramOutputInfo> program_outputs = {}) { | |
| GLuint service_id = program->service_id(); | |
| if (expected_link_status) { | |
| SetupShaderExpectations(kAttribs.data(), kNumAttribs, kUniforms.data(), | |
| kNumUniforms, program_outputs, service_id); | |
| } | |
| program->Link(nullptr, this); | |
| GLint link_status; | |
| program->GetProgramiv(GL_LINK_STATUS, &link_status); | |
| return (static_cast<bool>(link_status) == expected_link_status); | |
| } | |
| Program* SetupProgramForVariables( | |
| base::span<const VarInfo> vertex_variables, | |
| base::span<const VarInfo> fragment_variables, | |
| const int* const shader_version = nullptr) { | |
| // Set up shader | |
| AttributeMap vertex_attrib_map; | |
| UniformMap vertex_uniform_map; | |
| VaryingMap vertex_varying_map; | |
| OutputVariableList vertex_output_variable_list; | |
| for (const auto& vertex_variable : vertex_variables) { | |
| switch (vertex_variable.category) { | |
| case kVarAttribute: | |
| vertex_attrib_map[vertex_variable.name] = | |
| TestHelper::ConstructAttribute( | |
| vertex_variable.type, vertex_variable.size, | |
| vertex_variable.precision, vertex_variable.static_use, | |
| vertex_variable.name); | |
| break; | |
| case kVarUniform: | |
| vertex_uniform_map[vertex_variable.name] = | |
| TestHelper::ConstructUniform( | |
| vertex_variable.type, vertex_variable.size, | |
| vertex_variable.precision, vertex_variable.static_use, | |
| vertex_variable.name); | |
| break; | |
| case kVarVarying: | |
| vertex_varying_map[vertex_variable.name] = | |
| TestHelper::ConstructVarying( | |
| vertex_variable.type, vertex_variable.size, | |
| vertex_variable.precision, vertex_variable.static_use, | |
| vertex_variable.name); | |
| break; | |
| case kVarOutput: | |
| vertex_output_variable_list.push_back( | |
| TestHelper::ConstructOutputVariable( | |
| vertex_variable.type, vertex_variable.size, | |
| vertex_variable.precision, vertex_variable.static_use, | |
| vertex_variable.name)); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| AttributeMap frag_attrib_map; | |
| UniformMap frag_uniform_map; | |
| VaryingMap frag_varying_map; | |
| OutputVariableList frag_output_variable_list; | |
| for (const auto& fragment_variable : fragment_variables) { | |
| switch (fragment_variable.category) { | |
| case kVarAttribute: | |
| frag_attrib_map[fragment_variable.name] = | |
| TestHelper::ConstructAttribute( | |
| fragment_variable.type, fragment_variable.size, | |
| fragment_variable.precision, fragment_variable.static_use, | |
| fragment_variable.name); | |
| break; | |
| case kVarUniform: | |
| frag_uniform_map[fragment_variable.name] = | |
| TestHelper::ConstructUniform( | |
| fragment_variable.type, fragment_variable.size, | |
| fragment_variable.precision, fragment_variable.static_use, | |
| fragment_variable.name); | |
| break; | |
| case kVarVarying: | |
| frag_varying_map[fragment_variable.name] = | |
| TestHelper::ConstructVarying( | |
| fragment_variable.type, fragment_variable.size, | |
| fragment_variable.precision, fragment_variable.static_use, | |
| fragment_variable.name); | |
| break; | |
| case kVarOutput: | |
| frag_output_variable_list.push_back( | |
| TestHelper::ConstructOutputVariable( | |
| fragment_variable.type, fragment_variable.size, | |
| fragment_variable.precision, fragment_variable.static_use, | |
| fragment_variable.name)); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| } | |
| } | |
| // Check we can create shader. | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| // Check shader got created. | |
| EXPECT_TRUE(vshader != nullptr && fshader != nullptr); | |
| // Set Status | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true, nullptr, nullptr, | |
| shader_version, &vertex_attrib_map, | |
| &vertex_uniform_map, &vertex_varying_map, | |
| nullptr, &vertex_output_variable_list, nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| shader_version, &frag_attrib_map, | |
| &frag_uniform_map, &frag_varying_map, nullptr, | |
| &frag_output_variable_list, nullptr); | |
| // Set up program | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| EXPECT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| return program; | |
| } | |
| void TearDown() override { | |
| shader_manager_.Destroy(false); | |
| ProgramManagerTestBase::TearDown(); | |
| } | |
| static std::array<AttribInfo, 4> kAttribs; | |
| static std::array<UniformInfo, 3> kUniforms; | |
| ShaderManager shader_manager_; | |
| }; | |
| std::array<ProgramManagerWithShaderTest::AttribInfo, 4> | |
| ProgramManagerWithShaderTest::kAttribs = {{ | |
| { | |
| kAttrib1Name, | |
| kAttrib1Size, | |
| kAttrib1Type, | |
| kAttrib1Location, | |
| }, | |
| { | |
| kAttrib2Name, | |
| kAttrib2Size, | |
| kAttrib2Type, | |
| kAttrib2Location, | |
| }, | |
| { | |
| kAttrib3Name, | |
| kAttrib3Size, | |
| kAttrib3Type, | |
| kAttrib3Location, | |
| }, | |
| { | |
| kAttrib4Name, | |
| kAttrib4Size, | |
| kAttrib4Type, | |
| kAttrib4Location, | |
| }, | |
| }}; | |
| // GCC requires these declarations, but MSVC requires they not be present | |
| #ifndef COMPILER_MSVC | |
| const GLint ProgramManagerWithShaderTest::kNumVertexAttribs; | |
| const GLuint ProgramManagerWithShaderTest::kClientProgramId; | |
| const GLuint ProgramManagerWithShaderTest::kServiceProgramId; | |
| const GLuint ProgramManagerWithShaderTest::kVertexShaderClientId; | |
| const GLuint ProgramManagerWithShaderTest::kFragmentShaderClientId; | |
| const GLuint ProgramManagerWithShaderTest::kVertexShaderServiceId; | |
| const GLuint ProgramManagerWithShaderTest::kFragmentShaderServiceId; | |
| const GLint ProgramManagerWithShaderTest::kAttrib1Size; | |
| const GLint ProgramManagerWithShaderTest::kAttrib2Size; | |
| const GLint ProgramManagerWithShaderTest::kAttrib3Size; | |
| const GLint ProgramManagerWithShaderTest::kAttrib4Size; | |
| const GLint ProgramManagerWithShaderTest::kAttrib1Location; | |
| const GLint ProgramManagerWithShaderTest::kAttrib2Location; | |
| const GLint ProgramManagerWithShaderTest::kAttrib3Location; | |
| const GLint ProgramManagerWithShaderTest::kAttrib4Location; | |
| const GLenum ProgramManagerWithShaderTest::kAttrib1Type; | |
| const GLenum ProgramManagerWithShaderTest::kAttrib2Type; | |
| const GLenum ProgramManagerWithShaderTest::kAttrib3Type; | |
| const GLenum ProgramManagerWithShaderTest::kAttrib4Type; | |
| const GLint ProgramManagerWithShaderTest::kInvalidAttribLocation; | |
| const GLint ProgramManagerWithShaderTest::kBadAttribIndex; | |
| const GLint ProgramManagerWithShaderTest::kUniform1Size; | |
| const GLint ProgramManagerWithShaderTest::kUniform2Size; | |
| const GLint ProgramManagerWithShaderTest::kUniform3Size; | |
| const GLint ProgramManagerWithShaderTest::kUniform1FakeLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform2FakeLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform3FakeLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform1RealLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform2RealLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform3RealLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform1DesiredLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform2DesiredLocation; | |
| const GLint ProgramManagerWithShaderTest::kUniform3DesiredLocation; | |
| const GLenum ProgramManagerWithShaderTest::kUniform1Type; | |
| const GLenum ProgramManagerWithShaderTest::kUniform2Type; | |
| const GLenum ProgramManagerWithShaderTest::kUniform3Type; | |
| const GLint ProgramManagerWithShaderTest::kInvalidUniformLocation; | |
| const GLint ProgramManagerWithShaderTest::kBadUniformIndex; | |
| #endif | |
| const size_t ProgramManagerWithShaderTest::kNumAttribs = | |
| std::size(ProgramManagerWithShaderTest::kAttribs); | |
| std::array<ProgramManagerWithShaderTest::UniformInfo, 3> | |
| ProgramManagerWithShaderTest::kUniforms = {{ | |
| { | |
| kUniform1Name, | |
| kUniform1Size, | |
| kUniform1Type, | |
| kUniform1FakeLocation, | |
| kUniform1RealLocation, | |
| kUniform1DesiredLocation, | |
| kUniform1Name, | |
| }, | |
| { | |
| kUniform2Name, | |
| kUniform2Size, | |
| kUniform2Type, | |
| kUniform2FakeLocation, | |
| kUniform2RealLocation, | |
| kUniform2DesiredLocation, | |
| kUniform2NameWithArrayIndex, | |
| }, | |
| { | |
| kUniform3Name, | |
| kUniform3Size, | |
| kUniform3Type, | |
| kUniform3FakeLocation, | |
| kUniform3RealLocation, | |
| kUniform3DesiredLocation, | |
| kUniform3NameWithArrayIndex, | |
| }, | |
| }}; | |
| const size_t ProgramManagerWithShaderTest::kNumUniforms = | |
| std::size(ProgramManagerWithShaderTest::kUniforms); | |
| const char* ProgramManagerWithShaderTest::kAttrib1Name = "attrib1"; | |
| const char* ProgramManagerWithShaderTest::kAttrib2Name = "attrib2"; | |
| const char* ProgramManagerWithShaderTest::kAttrib3Name = "attrib3"; | |
| const char* ProgramManagerWithShaderTest::kAttrib4Name = "attrib4"; | |
| const char* ProgramManagerWithShaderTest::kUniform1Name = "uniform1"; | |
| const char* ProgramManagerWithShaderTest::kUniform2Name = "uniform2"; | |
| const char* ProgramManagerWithShaderTest::kUniform2NameWithArrayIndex = | |
| "uniform2[0]"; | |
| const char* ProgramManagerWithShaderTest::kUniform3Name = "uniform3"; | |
| const char* ProgramManagerWithShaderTest::kUniform3NameWithArrayIndex = | |
| "uniform3[0]"; | |
| const char* ProgramManagerWithShaderTest::kOutputVariable1Name = "outputVar1"; | |
| TEST_F(ProgramManagerWithShaderTest, GetAttribInfos) { | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| const Program::AttribInfoVector& infos = | |
| program->GetAttribInfos(); | |
| ASSERT_EQ(kNumAttribs, infos.size()); | |
| for (size_t ii = 0; ii < kNumAttribs; ++ii) { | |
| const Program::VertexAttrib& info = infos[ii]; | |
| const AttribInfo& expected = kAttribs[ii]; | |
| EXPECT_EQ(expected.size, info.size); | |
| EXPECT_EQ(expected.type, info.type); | |
| EXPECT_EQ(expected.location, info.location); | |
| EXPECT_STREQ(expected.name, info.name.c_str()); | |
| } | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, GetAttribInfo) { | |
| const GLint kValidIndex = 1; | |
| const GLint kInvalidIndex = 1000; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| const Program::VertexAttrib* info = | |
| program->GetAttribInfo(kValidIndex); | |
| ASSERT_TRUE(info != nullptr); | |
| EXPECT_EQ(kAttrib2Size, info->size); | |
| EXPECT_EQ(kAttrib2Type, info->type); | |
| EXPECT_EQ(kAttrib2Location, info->location); | |
| EXPECT_STREQ(kAttrib2Name, info->name.c_str()); | |
| EXPECT_TRUE(program->GetAttribInfo(kInvalidIndex) == nullptr); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, GetAttribInfoByLocation) { | |
| const GLint kInvalidLocation = 1000; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| // attrib2 is a vec2, takes 1 location | |
| const Program::VertexAttrib* expected_info = program->GetAttribInfo(1); | |
| EXPECT_EQ(1u, expected_info->location_count); | |
| const Program::VertexAttrib* info = | |
| program->GetAttribInfoByLocation(kAttrib2Location); | |
| EXPECT_EQ(expected_info, info); | |
| // attrib4 is a mat3x2, takes 3 locations (1 per column) | |
| expected_info = program->GetAttribInfo(3); | |
| EXPECT_EQ(3u, expected_info->location_count); | |
| info = program->GetAttribInfoByLocation(kAttrib4Location); | |
| EXPECT_EQ(expected_info, info); | |
| info = program->GetAttribInfoByLocation(kAttrib4Location + 1); | |
| EXPECT_EQ(expected_info, info); | |
| info = program->GetAttribInfoByLocation(kAttrib4Location + 2); | |
| EXPECT_EQ(expected_info, info); | |
| EXPECT_TRUE(program->GetAttribInfoByLocation(kInvalidLocation) == nullptr); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, GetAttribLocation) { | |
| const char* kInvalidName = "foo"; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_EQ(kAttrib2Location, program->GetAttribLocation(kAttrib2Name)); | |
| EXPECT_EQ(-1, program->GetAttribLocation(kInvalidName)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, VertexArrayMasks) { | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| std::vector<uint32_t> active_mask = program->vertex_input_active_mask(); | |
| ASSERT_EQ(1u, active_mask.size()); | |
| uint32_t expected = 0x3 << 0 | // attrib1 | |
| 0x3 << 2 | // attrib2 | |
| 0x3 << 4 | // attrib3 | |
| (0x3 * 0b010101) << 6; // attrib4 | |
| EXPECT_EQ(expected, active_mask[0]); | |
| std::vector<uint32_t> base_type_mask = program->vertex_input_base_type_mask(); | |
| ASSERT_EQ(1u, base_type_mask.size()); | |
| expected = SHADER_VARIABLE_FLOAT << 0 | // attrib1 | |
| SHADER_VARIABLE_FLOAT << 2 | // attrib2 | |
| SHADER_VARIABLE_INT << 4 | // attrib3 | |
| (SHADER_VARIABLE_FLOAT * 0b010101) << 6; // attrib4 | |
| EXPECT_EQ(expected, base_type_mask[0]); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, GetUniformInfo) { | |
| const GLint kInvalidIndex = 1000; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| const Program::UniformInfo* info = | |
| program->GetUniformInfo(0); | |
| ASSERT_TRUE(info != nullptr); | |
| EXPECT_EQ(kUniform1Size, info->size); | |
| EXPECT_EQ(kUniform1Type, info->type); | |
| EXPECT_EQ(kUniform1RealLocation, info->element_locations[0]); | |
| EXPECT_STREQ(kUniform1Name, info->name.c_str()); | |
| info = program->GetUniformInfo(1); | |
| ASSERT_TRUE(info != nullptr); | |
| EXPECT_EQ(kUniform2Size, info->size); | |
| EXPECT_EQ(kUniform2Type, info->type); | |
| EXPECT_EQ(kUniform2RealLocation, info->element_locations[0]); | |
| EXPECT_STREQ(kUniform2NameWithArrayIndex, info->name.c_str()); | |
| info = program->GetUniformInfo(2); | |
| // We emulate certain OpenGL drivers by supplying the name without | |
| // the array spec. Our implementation should correctly add the required spec. | |
| ASSERT_TRUE(info != nullptr); | |
| EXPECT_EQ(kUniform3Size, info->size); | |
| EXPECT_EQ(kUniform3Type, info->type); | |
| EXPECT_EQ(kUniform3RealLocation, info->element_locations[0]); | |
| EXPECT_STREQ(kUniform3NameWithArrayIndex, info->name.c_str()); | |
| EXPECT_TRUE(program->GetUniformInfo(kInvalidIndex) == nullptr); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, AttachDetachShader) { | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_FALSE(program->CanLink()); | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_FALSE(program->CanLink()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| EXPECT_TRUE(program->CanLink()); | |
| program->DetachShader(&shader_manager_, vshader); | |
| EXPECT_FALSE(program->CanLink()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->CanLink()); | |
| program->DetachShader(&shader_manager_, fshader); | |
| EXPECT_FALSE(program->CanLink()); | |
| EXPECT_FALSE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_FALSE(program->CanLink()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| EXPECT_TRUE(program->CanLink()); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, false); | |
| EXPECT_FALSE(program->CanLink()); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| EXPECT_TRUE(program->CanLink()); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, false); | |
| EXPECT_FALSE(program->CanLink()); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| EXPECT_TRUE(program->CanLink()); | |
| EXPECT_TRUE(program->IsShaderAttached(fshader)); | |
| program->DetachShader(&shader_manager_, fshader); | |
| EXPECT_FALSE(program->IsShaderAttached(fshader)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, GetUniformFakeLocation) { | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| // Emulate the situation that uniform3[1] isn't used and optimized out by | |
| // a driver, so it's location is -1. | |
| Program::UniformInfo* uniform = const_cast<Program::UniformInfo*>( | |
| program->GetUniformInfo(2)); | |
| ASSERT_TRUE(uniform != nullptr && kUniform3Size == 2); | |
| EXPECT_EQ(kUniform3Size, uniform->size); | |
| uniform->element_locations[1] = -1; | |
| EXPECT_EQ(kUniform1FakeLocation, | |
| program->GetUniformFakeLocation(kUniform1Name)); | |
| EXPECT_EQ(kUniform2FakeLocation, | |
| program->GetUniformFakeLocation(kUniform2Name)); | |
| EXPECT_EQ(kUniform3FakeLocation, | |
| program->GetUniformFakeLocation(kUniform3Name)); | |
| // Check we can get uniform2 as "uniform2" even though the name is | |
| // "uniform2[0]" | |
| EXPECT_EQ(kUniform2FakeLocation, | |
| program->GetUniformFakeLocation("uniform2")); | |
| // Check we can get uniform3 as "uniform3[0]" even though we simulated GL | |
| // returning "uniform3" | |
| EXPECT_EQ(kUniform3FakeLocation, | |
| program->GetUniformFakeLocation(kUniform3NameWithArrayIndex)); | |
| // Check that we can get the locations of the array elements > 1 | |
| EXPECT_EQ(ProgramManager::MakeFakeLocation(kUniform2FakeLocation, 1), | |
| program->GetUniformFakeLocation("uniform2[1]")); | |
| EXPECT_EQ(ProgramManager::MakeFakeLocation(kUniform2FakeLocation, 2), | |
| program->GetUniformFakeLocation("uniform2[2]")); | |
| EXPECT_EQ(-1, program->GetUniformFakeLocation("uniform2[3]")); | |
| EXPECT_EQ(-1, program->GetUniformFakeLocation("uniform3[1]")); | |
| EXPECT_EQ(-1, program->GetUniformFakeLocation("uniform3[2]")); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, GetUniformInfoByFakeLocation) { | |
| const GLint kInvalidLocation = 1234; | |
| const Program::UniformInfo* info; | |
| const Program* program = SetupDefaultProgram(); | |
| GLint real_location = -1; | |
| GLint array_index = -1; | |
| ASSERT_TRUE(program != nullptr); | |
| info = program->GetUniformInfoByFakeLocation( | |
| kUniform2FakeLocation, &real_location, &array_index); | |
| EXPECT_EQ(kUniform2RealLocation, real_location); | |
| EXPECT_EQ(0, array_index); | |
| ASSERT_TRUE(info != nullptr); | |
| EXPECT_EQ(kUniform2Type, info->type); | |
| real_location = -1; | |
| array_index = -1; | |
| info = program->GetUniformInfoByFakeLocation( | |
| kInvalidLocation, &real_location, &array_index); | |
| EXPECT_TRUE(info == nullptr); | |
| EXPECT_EQ(-1, real_location); | |
| EXPECT_EQ(-1, array_index); | |
| GLint loc = program->GetUniformFakeLocation("uniform2[2]"); | |
| info = program->GetUniformInfoByFakeLocation( | |
| loc, &real_location, &array_index); | |
| ASSERT_TRUE(info != nullptr); | |
| EXPECT_EQ(kUniform2RealLocation + 2 * 2, real_location); | |
| EXPECT_EQ(2, array_index); | |
| } | |
| // Ensure that when GL drivers correctly return gl_DepthRange, or other | |
| // builtin uniforms, our implementation passes them back to the client. | |
| TEST_F(ProgramManagerWithShaderTest, GLDriverReturnsGLUnderscoreUniform) { | |
| static const char* kUniform2Name = "gl_longNameWeCanCheckFor"; | |
| static ProgramManagerWithShaderTest::UniformInfo kUniforms[] = { | |
| { | |
| kUniform1Name, | |
| kUniform1Size, | |
| kUniform1Type, | |
| kUniform1FakeLocation, | |
| kUniform1RealLocation, | |
| kUniform1DesiredLocation, | |
| kUniform1Name, | |
| }, | |
| { | |
| kUniform2Name, | |
| kUniform2Size, | |
| kUniform2Type, | |
| kUniform2FakeLocation, | |
| -1, | |
| kUniform2DesiredLocation, | |
| kUniform2NameWithArrayIndex, | |
| }, | |
| { | |
| kUniform3Name, | |
| kUniform3Size, | |
| kUniform3Type, | |
| kUniform3FakeLocation, | |
| kUniform3RealLocation, | |
| kUniform3DesiredLocation, | |
| kUniform3NameWithArrayIndex, | |
| }, | |
| }; | |
| const size_t kNumUniforms = std::size(kUniforms); | |
| SetupShaderExpectations(kAttribs.data(), kNumAttribs, kUniforms, kNumUniforms, | |
| {}, kServiceProgramId); | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| program->Link(nullptr, this); | |
| GLint value = 0; | |
| program->GetProgramiv(GL_ACTIVE_ATTRIBUTES, &value); | |
| EXPECT_EQ(4, value); | |
| // Check that we didn't skip the "gl_" uniform. | |
| program->GetProgramiv(GL_ACTIVE_UNIFORMS, &value); | |
| EXPECT_EQ(3, value); | |
| // Check that our max length adds room for the array spec and is as long | |
| // as the "gl_" uniform we did not skip. | |
| program->GetProgramiv(GL_ACTIVE_UNIFORM_MAX_LENGTH, &value); | |
| EXPECT_EQ(strlen(kUniform2Name) + 4, static_cast<size_t>(value)); | |
| // Verify the uniform has a "real" location of -1 | |
| const auto* info = program->GetUniformInfo(kUniform2FakeLocation); | |
| EXPECT_EQ(-1, info->element_locations[0]); | |
| } | |
| // Test the bug comparing similar array names is fixed. | |
| TEST_F(ProgramManagerWithShaderTest, SimilarArrayNames) { | |
| static const char* kUniform2Name = "u_nameLong[0]"; | |
| static const char* kUniform3Name = "u_name[0]"; | |
| static const GLint kUniform2Size = 2; | |
| static const GLint kUniform3Size = 2; | |
| static ProgramManagerWithShaderTest::UniformInfo kUniforms[] = { | |
| { kUniform1Name, | |
| kUniform1Size, | |
| kUniform1Type, | |
| kUniform1FakeLocation, | |
| kUniform1RealLocation, | |
| kUniform1DesiredLocation, | |
| kUniform1Name, | |
| }, | |
| { kUniform2Name, | |
| kUniform2Size, | |
| kUniform2Type, | |
| kUniform2FakeLocation, | |
| kUniform2RealLocation, | |
| kUniform2DesiredLocation, | |
| kUniform2Name, | |
| }, | |
| { kUniform3Name, | |
| kUniform3Size, | |
| kUniform3Type, | |
| kUniform3FakeLocation, | |
| kUniform3RealLocation, | |
| kUniform3DesiredLocation, | |
| kUniform3Name, | |
| }, | |
| }; | |
| const size_t kNumUniforms = std::size(kUniforms); | |
| SetupShaderExpectations(kAttribs.data(), kNumAttribs, kUniforms, kNumUniforms, | |
| {}, kServiceProgramId); | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| program->Link(nullptr, this); | |
| // Check that we get the correct locations. | |
| EXPECT_EQ(kUniform2FakeLocation, | |
| program->GetUniformFakeLocation(kUniform2Name)); | |
| EXPECT_EQ(kUniform3FakeLocation, | |
| program->GetUniformFakeLocation(kUniform3Name)); | |
| } | |
| // Some GL drivers incorrectly return the wrong type. For example they return | |
| // GL_FLOAT_VEC2 when they should return GL_FLOAT_MAT2. Check we handle this. | |
| TEST_F(ProgramManagerWithShaderTest, GLDriverReturnsWrongTypeInfo) { | |
| static GLenum kAttrib2BadType = GL_FLOAT_VEC2; | |
| static GLenum kAttrib2GoodType = GL_FLOAT_MAT2; | |
| static GLenum kUniform2BadType = GL_FLOAT_VEC3; | |
| static GLenum kUniform2GoodType = GL_FLOAT_MAT3; | |
| AttributeMap attrib_map; | |
| UniformMap uniform_map; | |
| VaryingMap varying_map; | |
| OutputVariableList output_variable_list; | |
| attrib_map[kAttrib1Name] = TestHelper::ConstructAttribute( | |
| kAttrib1Type, kAttrib1Size, kAttrib1Precision, | |
| kAttribStaticUse, kAttrib1Name); | |
| attrib_map[kAttrib2Name] = TestHelper::ConstructAttribute( | |
| kAttrib2GoodType, kAttrib2Size, kAttrib2Precision, | |
| kAttribStaticUse, kAttrib2Name); | |
| attrib_map[kAttrib3Name] = TestHelper::ConstructAttribute( | |
| kAttrib3Type, kAttrib3Size, kAttrib3Precision, | |
| kAttribStaticUse, kAttrib3Name); | |
| uniform_map[kUniform1Name] = TestHelper::ConstructUniform( | |
| kUniform1Type, kUniform1Size, kUniform1Precision, | |
| kUniform1StaticUse, kUniform1Name); | |
| uniform_map[kUniform2Name] = TestHelper::ConstructUniform( | |
| kUniform2GoodType, kUniform2Size, kUniform2Precision, | |
| kUniform2StaticUse, kUniform2Name); | |
| uniform_map[kUniform3Name] = TestHelper::ConstructUniform( | |
| kUniform3Type, kUniform3Size, kUniform3Precision, | |
| kUniform3StaticUse, kUniform3Name); | |
| output_variable_list.push_back(TestHelper::ConstructOutputVariable( | |
| kOutputVariable1Type, kOutputVariable1Size, kOutputVariable1Precision, | |
| kOutputVariable1StaticUse, kOutputVariable1Name)); | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true, nullptr, nullptr, | |
| nullptr, &attrib_map, &uniform_map, &varying_map, | |
| nullptr, &output_variable_list, nullptr); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, &attrib_map, &uniform_map, &varying_map, | |
| nullptr, &output_variable_list, nullptr); | |
| static ProgramManagerWithShaderTest::AttribInfo kAttribs[] = { | |
| { kAttrib1Name, kAttrib1Size, kAttrib1Type, kAttrib1Location, }, | |
| { kAttrib2Name, kAttrib2Size, kAttrib2BadType, kAttrib2Location, }, | |
| { kAttrib3Name, kAttrib3Size, kAttrib3Type, kAttrib3Location, }, | |
| }; | |
| static ProgramManagerWithShaderTest::UniformInfo kUniforms[] = { | |
| { kUniform1Name, | |
| kUniform1Size, | |
| kUniform1Type, | |
| kUniform1FakeLocation, | |
| kUniform1RealLocation, | |
| kUniform1DesiredLocation, | |
| kUniform1Name, | |
| }, | |
| { kUniform2Name, | |
| kUniform2Size, | |
| kUniform2BadType, | |
| kUniform2FakeLocation, | |
| kUniform2RealLocation, | |
| kUniform2DesiredLocation, | |
| kUniform2NameWithArrayIndex, | |
| }, | |
| { kUniform3Name, | |
| kUniform3Size, | |
| kUniform3Type, | |
| kUniform3FakeLocation, | |
| kUniform3RealLocation, | |
| kUniform3DesiredLocation, | |
| kUniform3NameWithArrayIndex, | |
| }, | |
| }; | |
| static ProgramManagerWithShaderTest::ProgramOutputInfo kProgramOutputs[] = { | |
| { | |
| kOutputVariable1Name, kOutputVariable1Size, kOutputVariable1Type, | |
| 7, // color_name | |
| 0, // index | |
| }, | |
| }; | |
| const size_t kNumAttribs = std::size(kAttribs); | |
| const size_t kNumUniforms = std::size(kUniforms); | |
| SetupShaderExpectations(kAttribs, kNumAttribs, kUniforms, kNumUniforms, | |
| kProgramOutputs, kServiceProgramId); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| program->Link(nullptr, this); | |
| // Check that we got the good type, not the bad. | |
| // Check Attribs | |
| for (unsigned index = 0; index < kNumAttribs; ++index) { | |
| const Program::VertexAttrib* attrib_info = | |
| program->GetAttribInfo(index); | |
| ASSERT_TRUE(attrib_info != nullptr); | |
| size_t pos = attrib_info->name.find_first_of("[."); | |
| std::string top_name; | |
| if (pos == std::string::npos) | |
| top_name = attrib_info->name; | |
| else | |
| top_name = attrib_info->name.substr(0, pos); | |
| AttributeMap::const_iterator it = attrib_map.find(top_name); | |
| ASSERT_TRUE(it != attrib_map.end()); | |
| const sh::ShaderVariable* info; | |
| std::string original_name; | |
| EXPECT_TRUE(it->second.findInfoByMappedName( | |
| attrib_info->name, &info, &original_name)); | |
| EXPECT_EQ(info->type, attrib_info->type); | |
| EXPECT_EQ(static_cast<GLint>(info->getOutermostArraySize()), | |
| attrib_info->size); | |
| EXPECT_EQ(original_name, attrib_info->name); | |
| } | |
| // Check Uniforms | |
| for (unsigned index = 0; index < kNumUniforms; ++index) { | |
| const Program::UniformInfo* uniform_info = program->GetUniformInfo(index); | |
| ASSERT_TRUE(uniform_info != nullptr); | |
| size_t pos = uniform_info->name.find_first_of("[."); | |
| std::string top_name; | |
| if (pos == std::string::npos) | |
| top_name = uniform_info->name; | |
| else | |
| top_name = uniform_info->name.substr(0, pos); | |
| UniformMap::const_iterator it = uniform_map.find(top_name); | |
| ASSERT_TRUE(it != uniform_map.end()); | |
| const sh::ShaderVariable* info; | |
| std::string original_name; | |
| EXPECT_TRUE(it->second.findInfoByMappedName( | |
| uniform_info->name, &info, &original_name)); | |
| EXPECT_EQ(info->type, uniform_info->type); | |
| EXPECT_EQ(static_cast<GLint>(info->getOutermostArraySize()), | |
| uniform_info->size); | |
| EXPECT_EQ(original_name, uniform_info->name); | |
| } | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoUseCount) { | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_FALSE(program->CanLink()); | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| EXPECT_FALSE(vshader->InUse()); | |
| EXPECT_FALSE(fshader->InUse()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(vshader->InUse()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| EXPECT_TRUE(fshader->InUse()); | |
| EXPECT_TRUE(program->CanLink()); | |
| EXPECT_FALSE(program->InUse()); | |
| EXPECT_FALSE(program->IsDeleted()); | |
| manager_->UseProgram(program); | |
| EXPECT_TRUE(program->InUse()); | |
| manager_->UseProgram(program); | |
| EXPECT_TRUE(program->InUse()); | |
| manager_->MarkAsDeleted(&shader_manager_, program); | |
| EXPECT_TRUE(program->IsDeleted()); | |
| Program* info2 = manager_->GetProgram(kClientProgramId); | |
| EXPECT_EQ(program, info2); | |
| manager_->UnuseProgram(&shader_manager_, program); | |
| EXPECT_TRUE(program->InUse()); | |
| // this should delete the info. | |
| EXPECT_CALL(*gl_, DeleteProgram(kServiceProgramId)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| manager_->UnuseProgram(&shader_manager_, program); | |
| info2 = manager_->GetProgram(kClientProgramId); | |
| EXPECT_TRUE(info2 == nullptr); | |
| EXPECT_FALSE(vshader->InUse()); | |
| EXPECT_FALSE(fshader->InUse()); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoActiveTransformFeedbackCount) { | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_FALSE(program->IsActiveForTransformFeedback()); | |
| program->IncrementActiveTransformFeedbackCount(); | |
| EXPECT_TRUE(program->IsActiveForTransformFeedback()); | |
| program->IncrementActiveTransformFeedbackCount(); | |
| EXPECT_TRUE(program->IsActiveForTransformFeedback()); | |
| program->DecrementActiveTransformFeedbackCount(); | |
| EXPECT_TRUE(program->IsActiveForTransformFeedback()); | |
| program->DecrementActiveTransformFeedbackCount(); | |
| EXPECT_FALSE(program->IsActiveForTransformFeedback()); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoUseCount2) { | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_FALSE(program->CanLink()); | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| EXPECT_FALSE(vshader->InUse()); | |
| EXPECT_FALSE(fshader->InUse()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(vshader->InUse()); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| EXPECT_TRUE(fshader->InUse()); | |
| EXPECT_TRUE(program->CanLink()); | |
| EXPECT_FALSE(program->InUse()); | |
| EXPECT_FALSE(program->IsDeleted()); | |
| manager_->UseProgram(program); | |
| EXPECT_TRUE(program->InUse()); | |
| manager_->UseProgram(program); | |
| EXPECT_TRUE(program->InUse()); | |
| manager_->UnuseProgram(&shader_manager_, program); | |
| EXPECT_TRUE(program->InUse()); | |
| manager_->UnuseProgram(&shader_manager_, program); | |
| EXPECT_FALSE(program->InUse()); | |
| Program* info2 = manager_->GetProgram(kClientProgramId); | |
| EXPECT_EQ(program, info2); | |
| // this should delete the program. | |
| EXPECT_CALL(*gl_, DeleteProgram(kServiceProgramId)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| manager_->MarkAsDeleted(&shader_manager_, program); | |
| info2 = manager_->GetProgram(kClientProgramId); | |
| EXPECT_TRUE(info2 == nullptr); | |
| EXPECT_FALSE(vshader->InUse()); | |
| EXPECT_FALSE(fshader->InUse()); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoGetProgramInfo) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| program->GetProgramInfo(manager_.get(), &bucket); | |
| ProgramInfoHeader& header = bucket.GetDataAsSpan<ProgramInfoHeader>(0, 1u)[0]; | |
| EXPECT_EQ(1u, header.link_status); | |
| EXPECT_EQ(std::size(kAttribs), header.num_attribs); | |
| EXPECT_EQ(std::size(kUniforms), header.num_uniforms); | |
| const uint32_t num_inputs = header.num_attribs + header.num_uniforms; | |
| base::span<ProgramInput> inputs = | |
| bucket.GetDataAsSpan<ProgramInput>(sizeof(header), num_inputs); | |
| ASSERT_EQ(num_inputs, inputs.size()); | |
| size_t input_index = 0; | |
| // TODO(gman): Don't assume these are in order. | |
| for (uint32_t ii = 0; ii < header.num_attribs; ++ii) { | |
| const AttribInfo& expected = kAttribs[ii]; | |
| const ProgramInput& input = inputs[input_index++]; | |
| EXPECT_EQ(expected.size, input.size); | |
| EXPECT_EQ(expected.type, input.type); | |
| base::span<int32_t> location = | |
| bucket.GetDataAsSpan<int32_t>(input.location_offset, 1u); | |
| ASSERT_EQ(1u, location.size()); | |
| EXPECT_EQ(expected.location, location[0]); | |
| std::string name(base::as_string_view( | |
| bucket.GetDataAsByteSpan(input.name_offset, input.name_length))); | |
| EXPECT_STREQ(expected.name, name.c_str()); | |
| } | |
| // TODO(gman): Don't assume these are in order. | |
| for (uint32_t ii = 0; ii < header.num_uniforms; ++ii) { | |
| const UniformInfo& expected = kUniforms[ii]; | |
| const ProgramInput& input = inputs[input_index++]; | |
| EXPECT_EQ(expected.size, input.size); | |
| EXPECT_EQ(expected.type, input.type); | |
| base::span<int32_t> locations = bucket.GetDataAsSpan<int32_t>( | |
| input.location_offset, static_cast<size_t>(input.size)); | |
| ASSERT_EQ(static_cast<size_t>(input.size), locations.size()); | |
| for (int32_t jj = 0; jj < input.size; ++jj) { | |
| EXPECT_EQ(ProgramManager::MakeFakeLocation(expected.fake_location, jj), | |
| locations[jj]); | |
| } | |
| std::string name(base::as_string_view( | |
| bucket.GetDataAsByteSpan(input.name_offset, input.name_length))); | |
| EXPECT_STREQ(expected.good_name, name.c_str()); | |
| } | |
| EXPECT_EQ(num_inputs, input_index); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoGetUniformBlocksNone) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| // The program's previous link failed. | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_FALSE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(program->GetUniformBlocks(&bucket)); | |
| EXPECT_EQ(sizeof(UniformBlocksHeader), bucket.size()); | |
| EXPECT_EQ( | |
| 0u, | |
| bucket.GetDataAsSpan<UniformBlocksHeader>(0, 1u)[0].num_uniform_blocks); | |
| // Zero uniform blocks. | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_ACTIVE_UNIFORM_BLOCKS, _)) | |
| .WillOnce(SetArgPointee<2>(0)) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(program->GetUniformBlocks(&bucket)); | |
| EXPECT_EQ(sizeof(UniformBlocksHeader), bucket.size()); | |
| EXPECT_EQ( | |
| 0u, | |
| bucket.GetDataAsSpan<UniformBlocksHeader>(0, 1u)[0].num_uniform_blocks); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoGetUniformBlocksValid) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| struct Data { | |
| UniformBlocksHeader header; | |
| std::array<UniformBlockInfo, 2> entry; | |
| std::array<char, 4> name0; | |
| uint32_t indices0[2]; | |
| std::array<char, 8> name1; | |
| uint32_t indices1[1]; | |
| }; | |
| Data data; | |
| // The names needs to be of size 4*k-1 to avoid padding in the struct Data. | |
| // This is a testing only problem. | |
| auto kName = std::to_array<base::span<const char>>({ | |
| base::span_with_nul_from_cstring("cow"), | |
| base::span_with_nul_from_cstring("chicken"), | |
| }); | |
| const uint32_t kIndices0[] = { 1, 2 }; | |
| const uint32_t kIndices1[] = { 3 }; | |
| auto kIndices = | |
| std::to_array<base::span<const uint32_t>>({kIndices0, kIndices1}); | |
| data.header.num_uniform_blocks = 2; | |
| data.entry[0].binding = 0; | |
| data.entry[0].data_size = 8; | |
| data.entry[0].name_offset = ComputeOffset(&data, data.name0.data()); | |
| data.entry[0].name_length = data.name0.size(); | |
| data.entry[0].active_uniforms = std::size(data.indices0); | |
| data.entry[0].active_uniform_offset = ComputeOffset(&data, data.indices0); | |
| data.entry[0].referenced_by_vertex_shader = static_cast<uint32_t>(true); | |
| data.entry[0].referenced_by_fragment_shader = static_cast<uint32_t>(false); | |
| data.entry[1].binding = 1; | |
| data.entry[1].data_size = 4; | |
| data.entry[1].name_offset = ComputeOffset(&data, data.name1.data()); | |
| data.entry[1].name_length = data.name1.size(); | |
| data.entry[1].active_uniforms = std::size(data.indices1); | |
| data.entry[1].active_uniform_offset = ComputeOffset(&data, data.indices1); | |
| data.entry[1].referenced_by_vertex_shader = static_cast<uint32_t>(false); | |
| data.entry[1].referenced_by_fragment_shader = static_cast<uint32_t>(true); | |
| base::span(data.name0).copy_from(kName[0]); | |
| data.indices0[0] = kIndices[0][0]; | |
| data.indices0[1] = kIndices[0][1]; | |
| base::span(data.name1).copy_from(kName[1]); | |
| data.indices1[0] = kIndices[1][0]; | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_ACTIVE_UNIFORM_BLOCKS, _)) | |
| .WillOnce(SetArgPointee<2>(data.header.num_uniform_blocks)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, | |
| GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, _)) | |
| .WillOnce(SetArgPointee<2>( | |
| 1 + std::max(strlen(kName[0].data()), strlen(kName[1].data())))) | |
| .RetiresOnSaturation(); | |
| for (uint32_t ii = 0; ii < data.header.num_uniform_blocks; ++ii) { | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, GL_UNIFORM_BLOCK_BINDING, _)) | |
| .WillOnce(SetArgPointee<3>(data.entry[ii].binding)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, GL_UNIFORM_BLOCK_DATA_SIZE, _)) | |
| .WillOnce(SetArgPointee<3>(data.entry[ii].data_size)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, GL_UNIFORM_BLOCK_NAME_LENGTH, _)) | |
| .WillOnce(SetArgPointee<3>(data.entry[ii].name_length)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockName(kServiceProgramId, ii, | |
| data.entry[ii].name_length, _, _)) | |
| .WillOnce( | |
| DoAll(SetArgPointee<3>(strlen(kName[ii].data())), | |
| SetArrayArgument<4>(kName[ii].begin(), kName[ii].end()))) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, _)) | |
| .WillOnce(SetArgPointee<3>(data.entry[ii].active_uniforms)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, | |
| GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER, _)) | |
| .WillOnce(SetArgPointee<3>(data.entry[ii].referenced_by_vertex_shader)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, | |
| GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, _)) | |
| .WillOnce(SetArgPointee<3>( | |
| data.entry[ii].referenced_by_fragment_shader)) | |
| .RetiresOnSaturation(); | |
| } | |
| for (uint32_t ii = 0; ii < data.header.num_uniform_blocks; ++ii) { | |
| EXPECT_CALL(*(gl_.get()), GetActiveUniformBlockiv( | |
| kServiceProgramId, ii, | |
| GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, _)) | |
| .WillOnce(SetArrayArgument<3>(kIndices[ii].begin(), kIndices[ii].end())) | |
| .RetiresOnSaturation(); | |
| } | |
| program->GetUniformBlocks(&bucket); | |
| EXPECT_EQ(sizeof(Data), bucket.size()); | |
| EXPECT_EQ(bucket.GetDataAsByteSpan(0, sizeof(Data)), | |
| base::byte_span_from_ref(data)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, | |
| ProgramInfoGetTransformFeedbackVaryingsNone) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| // The program's previous link failed. | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, | |
| GL_TRANSFORM_FEEDBACK_BUFFER_MODE, | |
| _)) | |
| .WillOnce(SetArgPointee<2>(GL_INTERLEAVED_ATTRIBS)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_FALSE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(program->GetTransformFeedbackVaryings(&bucket)); | |
| EXPECT_EQ(sizeof(TransformFeedbackVaryingsHeader), bucket.size()); | |
| { | |
| TransformFeedbackVaryingsHeader& header = | |
| bucket.GetDataAsSpan<TransformFeedbackVaryingsHeader>(0, 1u)[0]; | |
| EXPECT_EQ(0u, header.num_transform_feedback_varyings); | |
| EXPECT_EQ(static_cast<uint32_t>(GL_INTERLEAVED_ATTRIBS), | |
| header.transform_feedback_buffer_mode); | |
| } | |
| // Zero transform feedback blocks. | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, | |
| GL_TRANSFORM_FEEDBACK_BUFFER_MODE, | |
| _)) | |
| .WillOnce(SetArgPointee<2>(GL_SEPARATE_ATTRIBS)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv( | |
| kServiceProgramId, GL_TRANSFORM_FEEDBACK_VARYINGS, _)) | |
| .WillOnce(SetArgPointee<2>(0)) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(program->GetTransformFeedbackVaryings(&bucket)); | |
| EXPECT_EQ(sizeof(TransformFeedbackVaryingsHeader), bucket.size()); | |
| { | |
| TransformFeedbackVaryingsHeader& header = | |
| bucket.GetDataAsSpan<TransformFeedbackVaryingsHeader>(0, 1u)[0]; | |
| EXPECT_EQ(static_cast<uint32_t>(GL_SEPARATE_ATTRIBS), | |
| header.transform_feedback_buffer_mode); | |
| EXPECT_EQ(0u, header.num_transform_feedback_varyings); | |
| } | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, | |
| ProgramInfoGetTransformFeedbackVaryingsValid) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| struct Data { | |
| TransformFeedbackVaryingsHeader header; | |
| std::array<TransformFeedbackVaryingInfo, 2> entry; | |
| std::array<char, 4> name0; | |
| std::array<char, 8> name1; | |
| }; | |
| Data data; | |
| // The names needs to be of size 4*k-1 to avoid padding in the struct Data. | |
| // This is a testing only problem. | |
| auto kName = std::to_array<base::span<const char>>({ | |
| base::span_with_nul_from_cstring("cow"), | |
| base::span_with_nul_from_cstring("chicken"), | |
| }); | |
| data.header.transform_feedback_buffer_mode = GL_INTERLEAVED_ATTRIBS; | |
| data.header.num_transform_feedback_varyings = 2; | |
| data.entry[0].size = 1; | |
| data.entry[0].type = GL_FLOAT_VEC2; | |
| data.entry[0].name_offset = ComputeOffset(&data, data.name0.data()); | |
| data.entry[0].name_length = data.name0.size(); | |
| data.entry[1].size = 2; | |
| data.entry[1].type = GL_FLOAT; | |
| data.entry[1].name_offset = ComputeOffset(&data, data.name1.data()); | |
| data.entry[1].name_length = data.name1.size(); | |
| base::span(data.name0).copy_from(kName[0]); | |
| base::span(data.name1).copy_from(kName[1]); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, | |
| GL_TRANSFORM_FEEDBACK_BUFFER_MODE, | |
| _)) | |
| .WillOnce(SetArgPointee<2>(GL_INTERLEAVED_ATTRIBS)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv( | |
| kServiceProgramId, GL_TRANSFORM_FEEDBACK_VARYINGS, _)) | |
| .WillOnce(SetArgPointee<2>(data.header.num_transform_feedback_varyings)) | |
| .RetiresOnSaturation(); | |
| GLsizei max_length = | |
| 1 + std::max(strlen(kName[0].data()), strlen(kName[1].data())); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, | |
| GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, _)) | |
| .WillOnce(SetArgPointee<2>(max_length)) | |
| .RetiresOnSaturation(); | |
| for (uint32_t ii = 0; ii < data.header.num_transform_feedback_varyings; | |
| ++ii) { | |
| EXPECT_CALL(*(gl_.get()), | |
| GetTransformFeedbackVarying(kServiceProgramId, ii, max_length, | |
| _, _, _, _)) | |
| .WillOnce( | |
| DoAll(SetArgPointee<3>(data.entry[ii].name_length - 1), | |
| SetArgPointee<4>(data.entry[ii].size), | |
| SetArgPointee<5>(data.entry[ii].type), | |
| SetArrayArgument<6>(kName[ii].begin(), kName[ii].end()))) | |
| .RetiresOnSaturation(); | |
| } | |
| program->GetTransformFeedbackVaryings(&bucket); | |
| EXPECT_EQ(sizeof(Data), bucket.size()); | |
| EXPECT_EQ(bucket.GetDataAsByteSpan(0, sizeof(Data)), | |
| base::byte_span_from_ref(data)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoGetUniformsES3None) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| // The program's previous link failed. | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_FALSE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(program->GetUniformsES3(&bucket)); | |
| EXPECT_EQ(sizeof(UniformsES3Header), bucket.size()); | |
| EXPECT_EQ(0u, bucket.GetDataAsSpan<UniformsES3Header>(0, 1u)[0].num_uniforms); | |
| // Zero uniform blocks. | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_ACTIVE_UNIFORMS, _)) | |
| .WillOnce(SetArgPointee<2>(0)) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(program->GetUniformsES3(&bucket)); | |
| EXPECT_EQ(sizeof(UniformsES3Header), bucket.size()); | |
| EXPECT_EQ(0u, bucket.GetDataAsSpan<UniformsES3Header>(0, 1u)[0].num_uniforms); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ProgramInfoGetUniformsES3Valid) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| struct Data { | |
| UniformsES3Header header; | |
| std::array<UniformES3Info, 2> entry; | |
| }; | |
| Data data; | |
| constexpr std::array<GLint, 2> kBlockIndex = {-1, 2}; | |
| constexpr std::array<GLint, 2> kOffset = {3, 4}; | |
| constexpr std::array<GLint, 2> kArrayStride = {7, 8}; | |
| constexpr std::array<GLint, 2> kMatrixStride = {9, 10}; | |
| constexpr std::array<GLint, 2> kIsRowMajor = {0, 1}; | |
| data.header.num_uniforms = 2; | |
| for (uint32_t ii = 0; ii < data.header.num_uniforms; ++ii) { | |
| data.entry[ii].block_index = kBlockIndex[ii]; | |
| data.entry[ii].offset = kOffset[ii]; | |
| data.entry[ii].array_stride = kArrayStride[ii]; | |
| data.entry[ii].matrix_stride = kMatrixStride[ii]; | |
| data.entry[ii].is_row_major = kIsRowMajor[ii]; | |
| } | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)) | |
| .RetiresOnSaturation(); | |
| EXPECT_CALL(*(gl_.get()), | |
| GetProgramiv(kServiceProgramId, GL_ACTIVE_UNIFORMS, _)) | |
| .WillOnce(SetArgPointee<2>(data.header.num_uniforms)) | |
| .RetiresOnSaturation(); | |
| const auto kPname = std::to_array<GLenum>({ | |
| GL_UNIFORM_BLOCK_INDEX, | |
| GL_UNIFORM_OFFSET, | |
| GL_UNIFORM_ARRAY_STRIDE, | |
| GL_UNIFORM_MATRIX_STRIDE, | |
| GL_UNIFORM_IS_ROW_MAJOR, | |
| }); | |
| auto kParams = std::to_array<const GLint*>({ | |
| kBlockIndex.data(), | |
| kOffset.data(), | |
| kArrayStride.data(), | |
| kMatrixStride.data(), | |
| kIsRowMajor.data(), | |
| }); | |
| const size_t kNumIterations = kPname.size(); | |
| for (size_t ii = 0; ii < kNumIterations; ++ii) { | |
| EXPECT_CALL(*(gl_.get()), | |
| GetActiveUniformsiv(kServiceProgramId, data.header.num_uniforms, | |
| _, kPname[ii], _)) | |
| .WillOnce(SetArrayArgument<4>( | |
| kParams[ii], UNSAFE_TODO(kParams[ii] + data.header.num_uniforms))) | |
| .RetiresOnSaturation(); | |
| } | |
| program->GetUniformsES3(&bucket); | |
| EXPECT_EQ(sizeof(Data), bucket.size()); | |
| EXPECT_EQ(bucket.GetDataAsByteSpan(0, sizeof(Data)), | |
| base::byte_span_from_ref(data)); | |
| } | |
| // Some drivers optimize out unused uniform array elements, so their | |
| // location would be -1. | |
| TEST_F(ProgramManagerWithShaderTest, UnusedUniformArrayElements) { | |
| CommonDecoder::Bucket bucket; | |
| const Program* program = SetupDefaultProgram(); | |
| ASSERT_TRUE(program != nullptr); | |
| // Emulate the situation that only the first element has a valid location. | |
| // TODO(zmo): Don't assume these are in order. | |
| for (size_t ii = 0; ii < std::size(kUniforms); ++ii) { | |
| Program::UniformInfo* uniform = const_cast<Program::UniformInfo*>( | |
| program->GetUniformInfo(ii)); | |
| ASSERT_TRUE(uniform != nullptr); | |
| EXPECT_EQ(static_cast<size_t>(kUniforms[ii].size), | |
| uniform->element_locations.size()); | |
| for (GLsizei jj = 1; jj < uniform->size; ++jj) | |
| uniform->element_locations[jj] = -1; | |
| } | |
| program->GetProgramInfo(manager_.get(), &bucket); | |
| ProgramInfoHeader& header = bucket.GetDataAsSpan<ProgramInfoHeader>(0, 1u)[0]; | |
| EXPECT_EQ(1u, header.link_status); | |
| EXPECT_EQ(std::size(kAttribs), header.num_attribs); | |
| EXPECT_EQ(std::size(kUniforms), header.num_uniforms); | |
| base::span<ProgramInput> inputs = bucket.GetDataAsSpan<ProgramInput>( | |
| sizeof(header), header.num_attribs + header.num_uniforms); | |
| ASSERT_EQ(header.num_attribs + header.num_uniforms, inputs.size()); | |
| base::span<ProgramInput> uniform_inputs = inputs.subspan(header.num_attribs); | |
| for (uint32_t ii = 0; ii < header.num_uniforms; ++ii) { | |
| const UniformInfo& expected = kUniforms[ii]; | |
| const ProgramInput& input = uniform_inputs[ii]; | |
| EXPECT_EQ(expected.size, input.size); | |
| base::span<int32_t> locations = bucket.GetDataAsSpan<int32_t>( | |
| input.location_offset, static_cast<size_t>(input.size)); | |
| ASSERT_EQ(static_cast<size_t>(input.size), locations.size()); | |
| EXPECT_EQ(ProgramManager::MakeFakeLocation(expected.fake_location, 0), | |
| locations[0]); | |
| for (int32_t jj = 1; jj < input.size; ++jj) { | |
| EXPECT_EQ(-1, locations[jj]); | |
| } | |
| } | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, BindAttribLocationConflicts) { | |
| // Set up shader | |
| AttributeMap attrib_map; | |
| for (uint32_t ii = 0; ii < kNumAttribs; ++ii) { | |
| const AttribInfo& attrib = kAttribs[ii]; | |
| attrib_map[attrib.name] = TestHelper::ConstructAttribute( | |
| attrib.type, attrib.size, GL_MEDIUM_FLOAT, kAttribStaticUse, | |
| attrib.name); | |
| } | |
| const char kAttribMatName[] = "matAttrib"; | |
| attrib_map[kAttribMatName] = TestHelper::ConstructAttribute( | |
| GL_FLOAT_MAT2, | |
| 1, | |
| GL_MEDIUM_FLOAT, | |
| kAttribStaticUse, | |
| kAttribMatName); | |
| // Check we can create shader. | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| // Check shader got created. | |
| ASSERT_TRUE(vshader != nullptr && fshader != nullptr); | |
| // Set Status | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true, nullptr, nullptr, | |
| nullptr, &attrib_map, nullptr, nullptr, nullptr, | |
| nullptr, nullptr); | |
| // Check attrib infos got copied. | |
| for (AttributeMap::const_iterator it = attrib_map.begin(); | |
| it != attrib_map.end(); ++it) { | |
| const sh::Attribute* variable_info = | |
| vshader->GetAttribInfo(it->first); | |
| ASSERT_TRUE(variable_info != nullptr); | |
| EXPECT_EQ(it->second.type, variable_info->type); | |
| EXPECT_EQ(it->second.getOutermostArraySize(), | |
| variable_info->getOutermostArraySize()); | |
| EXPECT_EQ(it->second.precision, variable_info->precision); | |
| EXPECT_EQ(it->second.staticUse, variable_info->staticUse); | |
| EXPECT_EQ(it->second.name, variable_info->name); | |
| } | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, &attrib_map, nullptr, nullptr, nullptr, | |
| nullptr, nullptr); | |
| // Set up program | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| EXPECT_FALSE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| program->SetAttribLocationBinding(kAttrib1Name, 0); | |
| EXPECT_FALSE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_CALL(*(gl_.get()), BindAttribLocation(_, 0, _)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| program->SetAttribLocationBinding("xxx", 0); | |
| EXPECT_FALSE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_CALL(*(gl_.get()), BindAttribLocation(_, 0, _)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| program->SetAttribLocationBinding(kAttrib2Name, 1); | |
| EXPECT_FALSE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_CALL(*(gl_.get()), BindAttribLocation(_, _, _)) | |
| .Times(2) | |
| .RetiresOnSaturation(); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| program->SetAttribLocationBinding(kAttrib2Name, 0); | |
| EXPECT_TRUE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| program->SetAttribLocationBinding(kAttribMatName, 1); | |
| program->SetAttribLocationBinding(kAttrib2Name, 3); | |
| EXPECT_CALL(*(gl_.get()), BindAttribLocation(_, _, _)) | |
| .Times(3) | |
| .RetiresOnSaturation(); | |
| EXPECT_FALSE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| program->SetAttribLocationBinding(kAttrib2Name, 2); | |
| EXPECT_TRUE(program->DetectAttribLocationBindingConflicts()); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, UniformsPrecisionMismatch) { | |
| // Set up shader | |
| UniformMap vertex_uniform_map; | |
| vertex_uniform_map["a"] = TestHelper::ConstructUniform( | |
| GL_FLOAT, 3, GL_MEDIUM_FLOAT, true, "a"); | |
| UniformMap frag_uniform_map; | |
| frag_uniform_map["a"] = TestHelper::ConstructUniform( | |
| GL_FLOAT, 3, GL_LOW_FLOAT, true, "a"); | |
| // Check we can create shader. | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| // Check shader got created. | |
| ASSERT_TRUE(vshader != nullptr && fshader != nullptr); | |
| // Set Status | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true, nullptr, nullptr, | |
| nullptr, nullptr, &vertex_uniform_map, nullptr, | |
| nullptr, nullptr, nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, &frag_uniform_map, nullptr, | |
| nullptr, nullptr, nullptr); | |
| // Set up program | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| std::string conflicting_name; | |
| EXPECT_TRUE(program->DetectUniformsMismatch(&conflicting_name)); | |
| EXPECT_EQ("a", conflicting_name); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| // If a varying has different type in the vertex and fragment | |
| // shader, linking should fail. | |
| TEST_F(ProgramManagerWithShaderTest, VaryingTypeMismatch) { | |
| const VarInfo kVertexVarying = | |
| { GL_FLOAT_VEC3, 1, GL_MEDIUM_FLOAT, true, "a", kVarVarying }; | |
| const VarInfo kFragmentVarying = | |
| { GL_FLOAT_VEC4, 1, GL_MEDIUM_FLOAT, true, "a", kVarVarying }; | |
| Program* program = | |
| SetupProgramForVariables(base::span_from_ref(kVertexVarying), | |
| base::span_from_ref(kFragmentVarying)); | |
| std::string conflicting_name; | |
| EXPECT_TRUE(program->DetectVaryingsMismatch(&conflicting_name)); | |
| EXPECT_EQ("a", conflicting_name); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| // If a varying has different array size in the vertex and fragment | |
| // shader, linking should fail. | |
| TEST_F(ProgramManagerWithShaderTest, VaryingArraySizeMismatch) { | |
| const VarInfo kVertexVarying = | |
| { GL_FLOAT, 2, GL_MEDIUM_FLOAT, true, "a", kVarVarying }; | |
| const VarInfo kFragmentVarying = | |
| { GL_FLOAT, 3, GL_MEDIUM_FLOAT, true, "a", kVarVarying }; | |
| Program* program = | |
| SetupProgramForVariables(base::span_from_ref(kVertexVarying), | |
| base::span_from_ref(kFragmentVarying)); | |
| std::string conflicting_name; | |
| EXPECT_TRUE(program->DetectVaryingsMismatch(&conflicting_name)); | |
| EXPECT_EQ("a", conflicting_name); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| // If a varying has different precision in the vertex and fragment | |
| // shader, linking should succeed. | |
| TEST_F(ProgramManagerWithShaderTest, VaryingPrecisionMismatch) { | |
| const VarInfo kVertexVarying = | |
| { GL_FLOAT, 2, GL_HIGH_FLOAT, true, "a", kVarVarying }; | |
| const VarInfo kFragmentVarying = | |
| { GL_FLOAT, 2, GL_MEDIUM_FLOAT, true, "a", kVarVarying }; | |
| Program* program = | |
| SetupProgramForVariables(base::span_from_ref(kVertexVarying), | |
| base::span_from_ref(kFragmentVarying)); | |
| std::string conflicting_name; | |
| EXPECT_FALSE(program->DetectVaryingsMismatch(&conflicting_name)); | |
| EXPECT_TRUE(conflicting_name.empty()); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| } | |
| // If a varying is statically used in fragment shader but not | |
| // declared in vertex shader, link should fail. | |
| TEST_F(ProgramManagerWithShaderTest, VaryingMissing) { | |
| const VarInfo kFragmentVarying = | |
| { GL_FLOAT, 3, GL_MEDIUM_FLOAT, true, "a", kVarVarying }; | |
| Program* program = | |
| SetupProgramForVariables({}, base::span_from_ref(kFragmentVarying)); | |
| std::string conflicting_name; | |
| EXPECT_TRUE(program->DetectVaryingsMismatch(&conflicting_name)); | |
| EXPECT_EQ("a", conflicting_name); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| // If a varying is declared but not statically used in fragment | |
| // shader, even if it's not declared in vertex shader, link should | |
| // succeed. | |
| TEST_F(ProgramManagerWithShaderTest, InactiveVarying) { | |
| const VarInfo kFragmentVarying = | |
| { GL_FLOAT, 3, GL_MEDIUM_FLOAT, false, "a", kVarVarying }; | |
| Program* program = | |
| SetupProgramForVariables({}, base::span_from_ref(kFragmentVarying)); | |
| std::string conflicting_name; | |
| EXPECT_FALSE(program->DetectVaryingsMismatch(&conflicting_name)); | |
| EXPECT_TRUE(conflicting_name.empty()); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| } | |
| // Uniforms and attributes are both global variables, thus sharing | |
| // the same namespace. Any name conflicts should cause link | |
| // failure. | |
| TEST_F(ProgramManagerWithShaderTest, AttribUniformNameConflict) { | |
| const VarInfo kVertexAttribute = | |
| { GL_FLOAT_VEC4, 1, GL_MEDIUM_FLOAT, true, "a", kVarAttribute }; | |
| const VarInfo kFragmentUniform = | |
| { GL_FLOAT_VEC4, 1, GL_MEDIUM_FLOAT, true, "a", kVarUniform }; | |
| Program* program = | |
| SetupProgramForVariables(base::span_from_ref(kVertexAttribute), | |
| base::span_from_ref(kFragmentUniform)); | |
| std::string conflicting_name; | |
| EXPECT_TRUE(program->DetectGlobalNameConflicts(&conflicting_name)); | |
| EXPECT_EQ("a", conflicting_name); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, FragmentOutputTypes) { | |
| // Set up program | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| nullptr, nullptr); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(vshader && fshader); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| { // No outputs. | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| nullptr, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| EXPECT_EQ(0u, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0u, program->fragment_output_written_mask()); | |
| } | |
| { // gl_FragColor | |
| OutputVariableList fragment_outputs; | |
| sh::OutputVariable var = TestHelper::ConstructOutputVariable( | |
| GL_FLOAT_VEC4, 0, GL_MEDIUM_FLOAT, true, "gl_FragColor"); | |
| var.location = -1; | |
| fragment_outputs.push_back(var); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| &fragment_outputs, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| EXPECT_EQ(0x3u, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0x3u, program->fragment_output_written_mask()); | |
| } | |
| { // gl_FragData | |
| OutputVariableList fragment_outputs; | |
| sh::OutputVariable var = TestHelper::ConstructOutputVariable( | |
| GL_FLOAT_VEC4, 8, GL_MEDIUM_FLOAT, true, "gl_FragData"); | |
| var.location = -1; | |
| fragment_outputs.push_back(var); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| &fragment_outputs, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| EXPECT_EQ(0xFFFFu, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0xFFFFu, program->fragment_output_written_mask()); | |
| } | |
| { // gl_FragColor, gl_FragDepth | |
| OutputVariableList fragment_outputs; | |
| sh::OutputVariable var = TestHelper::ConstructOutputVariable( | |
| GL_FLOAT_VEC4, 0, GL_MEDIUM_FLOAT, true, "gl_FragColor"); | |
| var.location = -1; | |
| fragment_outputs.push_back(var); | |
| var = TestHelper::ConstructOutputVariable( | |
| GL_FLOAT, 0, GL_MEDIUM_FLOAT, true, "gl_FragDepth"); | |
| var.location = -1; | |
| fragment_outputs.push_back(var); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| &fragment_outputs, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| EXPECT_EQ(0x3u, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0x3u, program->fragment_output_written_mask()); | |
| } | |
| { // Single user defined output. | |
| static ProgramOutputInfo kProgramOutputs[] = {{ | |
| "myOutput", // name | |
| 0, // size | |
| GL_UNSIGNED_INT_VEC4, // type | |
| 7, // color_name | |
| 0, // index | |
| }}; | |
| OutputVariableList fragment_outputs; | |
| sh::OutputVariable var = TestHelper::ConstructOutputVariable( | |
| GL_UNSIGNED_INT_VEC4, 0, GL_MEDIUM_INT, true, "myOutput"); | |
| var.location = -1; | |
| fragment_outputs.push_back(var); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| &fragment_outputs, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true, kProgramOutputs)); | |
| EXPECT_EQ(0x2u, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0x3u, program->fragment_output_written_mask()); | |
| } | |
| { // Single user defined output - no static use. | |
| static ProgramOutputInfo kProgramOutputs[] = {{ | |
| "myOutput", // name | |
| 0, // size | |
| GL_UNSIGNED_INT_VEC4, // type | |
| 7, // color_name | |
| 0, // index | |
| }}; | |
| OutputVariableList fragment_outputs; | |
| sh::OutputVariable var = TestHelper::ConstructOutputVariable( | |
| GL_UNSIGNED_INT_VEC4, 0, GL_MEDIUM_INT, false, "myOutput"); | |
| var.location = -1; | |
| fragment_outputs.push_back(var); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| &fragment_outputs, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true, kProgramOutputs)); | |
| EXPECT_EQ(0x2u, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0x3u, program->fragment_output_written_mask()); | |
| } | |
| { // Multiple user defined outputs. | |
| static ProgramOutputInfo program_outputs[] = { | |
| { | |
| "myOutput", // name | |
| 0, // size | |
| GL_UNSIGNED_INT_VEC4, // type | |
| 7, // color_name | |
| 0, // index | |
| }, | |
| { | |
| "myOutput1", // name | |
| 0, // size | |
| GL_FLOAT_VEC4, // type | |
| 7, // color_name | |
| 0, // index | |
| }, | |
| }; | |
| OutputVariableList fragment_outputs; | |
| sh::OutputVariable var = TestHelper::ConstructOutputVariable( | |
| GL_INT_VEC4, 0, GL_MEDIUM_INT, true, "myOutput"); | |
| var.location = 0; | |
| fragment_outputs.push_back(var); | |
| var = TestHelper::ConstructOutputVariable(GL_FLOAT_VEC4, 0, GL_MEDIUM_FLOAT, | |
| true, "myOutput1"); | |
| var.location = 2; | |
| fragment_outputs.push_back(var); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true, nullptr, nullptr, | |
| nullptr, nullptr, nullptr, nullptr, nullptr, | |
| &fragment_outputs, nullptr); | |
| EXPECT_TRUE(LinkAsExpected(program, true, program_outputs)); | |
| EXPECT_EQ(0x31u, program->fragment_output_type_mask()); | |
| EXPECT_EQ(0x33u, program->fragment_output_written_mask()); | |
| } | |
| } | |
| // Varyings go over 8 rows. | |
| TEST_F(ProgramManagerWithShaderTest, TooManyVaryings) { | |
| const VarInfo kVertexVaryings[] = { | |
| { GL_FLOAT_VEC4, 4, GL_MEDIUM_FLOAT, true, "a", kVarVarying }, | |
| { GL_FLOAT_VEC4, 5, GL_MEDIUM_FLOAT, true, "b", kVarVarying } | |
| }; | |
| const VarInfo kFragmentVaryings[] = { | |
| { GL_FLOAT_VEC4, 4, GL_MEDIUM_FLOAT, true, "a", kVarVarying }, | |
| { GL_FLOAT_VEC4, 5, GL_MEDIUM_FLOAT, true, "b", kVarVarying } | |
| }; | |
| Program* program = | |
| SetupProgramForVariables(kVertexVaryings, kFragmentVaryings); | |
| EXPECT_FALSE(program->CheckVaryingsPacking()); | |
| EXPECT_TRUE(LinkAsExpected(program, false)); | |
| } | |
| // Varyings go over 8 rows but some are inactive | |
| TEST_F(ProgramManagerWithShaderTest, TooManyInactiveVaryings) { | |
| const VarInfo kVertexVaryings[] = { | |
| { GL_FLOAT_VEC4, 4, GL_MEDIUM_FLOAT, true, "a", kVarVarying }, | |
| { GL_FLOAT_VEC4, 5, GL_MEDIUM_FLOAT, true, "b", kVarVarying } | |
| }; | |
| const VarInfo kFragmentVaryings[] = { | |
| { GL_FLOAT_VEC4, 4, GL_MEDIUM_FLOAT, false, "a", kVarVarying }, | |
| { GL_FLOAT_VEC4, 5, GL_MEDIUM_FLOAT, true, "b", kVarVarying } | |
| }; | |
| Program* program = | |
| SetupProgramForVariables(kVertexVaryings, kFragmentVaryings); | |
| EXPECT_TRUE(program->CheckVaryingsPacking()); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, BindUniformLocation) { | |
| const GLint kUniform1DesiredLocation = 10; | |
| const GLint kUniform2DesiredLocation = -1; | |
| const GLint kUniform3DesiredLocation = 5; | |
| Shader* vshader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| ASSERT_TRUE(vshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vshader, true); | |
| Shader* fshader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| ASSERT_TRUE(fshader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), fshader, true); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, vshader)); | |
| EXPECT_TRUE(program->AttachShader(&shader_manager_, fshader)); | |
| EXPECT_TRUE(program->SetUniformLocationBinding( | |
| kUniform1Name, kUniform1DesiredLocation)); | |
| EXPECT_TRUE(program->SetUniformLocationBinding( | |
| kUniform3Name, kUniform3DesiredLocation)); | |
| static ProgramManagerWithShaderTest::AttribInfo kAttribs[] = { | |
| { kAttrib1Name, kAttrib1Size, kAttrib1Type, kAttrib1Location, }, | |
| { kAttrib2Name, kAttrib2Size, kAttrib2Type, kAttrib2Location, }, | |
| { kAttrib3Name, kAttrib3Size, kAttrib3Type, kAttrib3Location, }, | |
| }; | |
| ProgramManagerWithShaderTest::UniformInfo kUniforms[] = { | |
| { kUniform1Name, | |
| kUniform1Size, | |
| kUniform1Type, | |
| kUniform1FakeLocation, | |
| kUniform1RealLocation, | |
| kUniform1DesiredLocation, | |
| kUniform1Name, | |
| }, | |
| { kUniform2Name, | |
| kUniform2Size, | |
| kUniform2Type, | |
| kUniform2FakeLocation, | |
| kUniform2RealLocation, | |
| kUniform2DesiredLocation, | |
| kUniform2NameWithArrayIndex, | |
| }, | |
| { kUniform3Name, | |
| kUniform3Size, | |
| kUniform3Type, | |
| kUniform3FakeLocation, | |
| kUniform3RealLocation, | |
| kUniform3DesiredLocation, | |
| kUniform3NameWithArrayIndex, | |
| }, | |
| }; | |
| const size_t kNumAttribs = std::size(kAttribs); | |
| const size_t kNumUniforms = std::size(kUniforms); | |
| SetupShaderExpectations(kAttribs, kNumAttribs, kUniforms, kNumUniforms, {}, | |
| kServiceProgramId); | |
| program->Link(nullptr, this); | |
| EXPECT_EQ(kUniform1DesiredLocation, | |
| program->GetUniformFakeLocation(kUniform1Name)); | |
| EXPECT_EQ(kUniform3DesiredLocation, | |
| program->GetUniformFakeLocation(kUniform3Name)); | |
| EXPECT_EQ(kUniform3DesiredLocation, | |
| program->GetUniformFakeLocation(kUniform3NameWithArrayIndex)); | |
| } | |
| TEST_F(ProgramManagerWithShaderTest, ZeroSizeUniformMarkedInvalid) { | |
| UniformInfo kInvalidUniforms[] = { | |
| { | |
| kUniform1Name, 0 /* invalid size */, kUniform1Type, | |
| kUniform1FakeLocation, kUniform1RealLocation, | |
| kUniform1DesiredLocation, kUniform1Name, | |
| }, | |
| }; | |
| const size_t kNumInvalidUniforms = std::size(kInvalidUniforms); | |
| SetupShaderExpectations(kAttribs.data(), kNumAttribs, kInvalidUniforms, | |
| kNumInvalidUniforms, {}, kServiceProgramId); | |
| Shader* vertex_shader = shader_manager_.CreateShader( | |
| kVertexShaderClientId, kVertexShaderServiceId, GL_VERTEX_SHADER); | |
| Shader* fragment_shader = shader_manager_.CreateShader( | |
| kFragmentShaderClientId, kFragmentShaderServiceId, GL_FRAGMENT_SHADER); | |
| EXPECT_TRUE(vertex_shader != nullptr); | |
| EXPECT_TRUE(fragment_shader != nullptr); | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader, true); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader, true); | |
| Program* program = | |
| manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program != nullptr); | |
| program->AttachShader(&shader_manager_, vertex_shader); | |
| program->AttachShader(&shader_manager_, fragment_shader); | |
| program->Link(nullptr, this); | |
| EXPECT_FALSE(program->IsValid()); | |
| } | |
| class ProgramManagerWithCacheTest : public ProgramManagerTestBase { | |
| 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()), | |
| vertex_shader_(nullptr), | |
| fragment_shader_(nullptr), | |
| program_(nullptr), | |
| shader_manager_(nullptr) {} | |
| protected: | |
| void SetupProgramManager() override { | |
| manager_ = std::make_unique<ProgramManager>( | |
| cache_.get(), kMaxVaryingVectors, kMaxDrawBuffers, | |
| kMaxDualSourceDrawBuffers, kMaxVertexAttribs, gpu_preferences_, | |
| feature_info_.get(), nullptr); | |
| } | |
| void SetUp() override { | |
| ProgramManagerTestBase::SetUp(); | |
| 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); | |
| vertex_shader_->set_source("lka asjf bjajsdfj"); | |
| fragment_shader_->set_source("lka asjf a fasgag 3rdsf3 bjajsdfj"); | |
| program_ = manager_->CreateProgram(kClientProgramId, kServiceProgramId); | |
| ASSERT_TRUE(program_ != nullptr); | |
| program_->AttachShader(&shader_manager_, vertex_shader_); | |
| program_->AttachShader(&shader_manager_, fragment_shader_); | |
| } | |
| void TearDown() override { | |
| vertex_shader_ = nullptr; | |
| fragment_shader_ = nullptr; | |
| shader_manager_.Destroy(false); | |
| program_ = nullptr; | |
| ProgramManagerTestBase::TearDown(); | |
| } | |
| void SetShadersCompiled() { | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader_, true); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader_, true); | |
| } | |
| void SetShadersCompiled(const std::string& compilation_options_string) { | |
| TestHelper::SetShaderStates(gl_.get(), vertex_shader_, true, | |
| compilation_options_string); | |
| TestHelper::SetShaderStates(gl_.get(), fragment_shader_, true, | |
| compilation_options_string); | |
| } | |
| void SetProgramCached() { | |
| cache_->LinkedProgramCacheSuccess( | |
| vertex_shader_->source(), fragment_shader_->source(), | |
| &program_->bind_attrib_location_map(), | |
| program_->effective_transform_feedback_varyings(), | |
| program_->effective_transform_feedback_buffer_mode()); | |
| } | |
| void SetExpectationsForProgramCached() { | |
| SetExpectationsForProgramCached(program_, | |
| vertex_shader_, | |
| fragment_shader_); | |
| } | |
| void SetExpectationsForProgramCached( | |
| Program* program, | |
| Shader* vertex_shader, | |
| Shader* fragment_shader) { | |
| EXPECT_CALL(*cache_.get(), | |
| SaveLinkedProgram( | |
| program->service_id(), vertex_shader, fragment_shader, | |
| &program->bind_attrib_location_map(), | |
| program_->effective_transform_feedback_varyings(), | |
| program_->effective_transform_feedback_buffer_mode(), _)) | |
| .Times(1); | |
| } | |
| void SetExpectationsForNotCachingProgram() { | |
| SetExpectationsForNotCachingProgram(program_, | |
| vertex_shader_, | |
| fragment_shader_); | |
| } | |
| void SetExpectationsForNotCachingProgram( | |
| Program* program, | |
| Shader* vertex_shader, | |
| Shader* fragment_shader) { | |
| EXPECT_CALL(*cache_.get(), | |
| SaveLinkedProgram( | |
| program->service_id(), vertex_shader, fragment_shader, | |
| &program->bind_attrib_location_map(), | |
| program_->effective_transform_feedback_varyings(), | |
| program_->effective_transform_feedback_buffer_mode(), _)) | |
| .Times(0); | |
| } | |
| void SetExpectationsForProgramLoad(ProgramCache::ProgramLoadResult result) { | |
| SetExpectationsForProgramLoad(kServiceProgramId, | |
| program_, | |
| vertex_shader_, | |
| fragment_shader_, | |
| result); | |
| } | |
| void SetExpectationsForProgramLoad( | |
| GLuint service_program_id, | |
| Program* program, | |
| Shader* vertex_shader, | |
| Shader* fragment_shader, | |
| ProgramCache::ProgramLoadResult result) { | |
| EXPECT_CALL(*cache_.get(), | |
| LoadLinkedProgram( | |
| service_program_id, vertex_shader, fragment_shader, | |
| &program->bind_attrib_location_map(), | |
| program_->effective_transform_feedback_varyings(), | |
| program_->effective_transform_feedback_buffer_mode(), _)) | |
| .WillOnce(Return(result)); | |
| } | |
| void SetExpectationsForProgramLoadSuccess() { | |
| SetExpectationsForProgramLoadSuccess(kServiceProgramId); | |
| } | |
| void SetExpectationsForProgramLoadSuccess(GLuint service_program_id) { | |
| TestHelper::SetupProgramSuccessExpectations(gl_.get(), feature_info_.get(), | |
| nullptr, 0, nullptr, 0, nullptr, | |
| 0, {}, service_program_id); | |
| } | |
| void SetExpectationsForProgramNotLoaded() { | |
| EXPECT_CALL( | |
| *cache_.get(), | |
| LoadLinkedProgram( | |
| program_->service_id(), vertex_shader_.get(), | |
| fragment_shader_.get(), &program_->bind_attrib_location_map(), | |
| program_->effective_transform_feedback_varyings(), | |
| program_->effective_transform_feedback_buffer_mode(), _)) | |
| .Times(Exactly(0)); | |
| } | |
| void SetExpectationsForProgramLink() { | |
| SetExpectationsForProgramLink(kServiceProgramId); | |
| } | |
| void SetExpectationsForProgramLink(GLuint service_program_id) { | |
| TestHelper::SetupShaderExpectations(gl_.get(), feature_info_.get(), nullptr, | |
| 0, nullptr, 0, service_program_id); | |
| if (gl::g_current_gl_driver->ext.b_GL_OES_get_program_binary) { | |
| EXPECT_CALL(*gl_.get(), ProgramParameteri( | |
| service_program_id, | |
| GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE)) | |
| .Times(1); | |
| } | |
| } | |
| void SetExpectationsForSuccessCompile( | |
| const Shader* 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), nullptr)) | |
| .Times(1); | |
| EXPECT_CALL(*gl_.get(), CompileShader(shader_id)).Times(1); | |
| EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_COMPILE_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_TRUE)); | |
| } | |
| void SetExpectationsForNoCompile(const Shader* 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), nullptr)) | |
| .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 Shader* 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), nullptr)) | |
| .Times(1); | |
| EXPECT_CALL(*gl_.get(), CompileShader(shader_id)).Times(1); | |
| EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_COMPILE_STATUS, _)) | |
| .WillOnce(SetArgPointee<2>(GL_FALSE)); | |
| EXPECT_CALL(*gl_.get(), GetShaderiv(shader_id, GL_INFO_LOG_LENGTH, _)) | |
| .WillOnce(SetArgPointee<2>(0)); | |
| EXPECT_CALL(*gl_.get(), GetShaderInfoLog(shader_id, 0, _, _)) | |
| .Times(1); | |
| } | |
| std::unique_ptr<MockProgramCache> cache_; | |
| // These shaders are owned by |shader_manager_|. | |
| raw_ptr<Shader> vertex_shader_; | |
| raw_ptr<Shader> fragment_shader_; | |
| // This program is owned by |manager_|. | |
| raw_ptr<Program> program_; | |
| 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, CacheProgramOnSuccessfulLink) { | |
| SetShadersCompiled(); | |
| SetExpectationsForProgramLink(); | |
| SetExpectationsForProgramCached(); | |
| EXPECT_TRUE(program_->Link(nullptr, this)); | |
| } | |
| TEST_F(ProgramManagerWithCacheTest, LoadProgramOnProgramCacheHit) { | |
| SetShadersCompiled(); | |
| SetProgramCached(); | |
| SetExpectationsForNoCompile(vertex_shader_); | |
| SetExpectationsForNoCompile(fragment_shader_); | |
| SetExpectationsForProgramLoad(ProgramCache::PROGRAM_LOAD_SUCCESS); | |
| SetExpectationsForNotCachingProgram(); | |
| SetExpectationsForProgramLoadSuccess(); | |
| EXPECT_TRUE(program_->Link(nullptr, this)); | |
| } | |
| TEST_F(ProgramManagerWithCacheTest, RelinkOnChangedCompileOptions) { | |
| SetShadersCompiled("a"); | |
| SetProgramCached(); | |
| SetExpectationsForProgramCached(); | |
| SetShadersCompiled("b"); | |
| SetExpectationsForProgramLink(); | |
| SetExpectationsForProgramNotLoaded(); | |
| EXPECT_TRUE(program_->Link(nullptr, this)); | |
| } | |
| // For some compilers, using make_tuple("a", "bb") would end up | |
| // instantiating make_tuple<char[1], char[2]>. This does not work. | |
| namespace { | |
| testing::tuple<const char*, const char*> make_gl_ext_tuple( | |
| const char* gl_version, | |
| const char* gl_extensions) { | |
| return testing::make_tuple(gl_version, gl_extensions); | |
| } | |
| } | |
| class ProgramManagerDualSourceBlendingTest | |
| : public ProgramManagerWithShaderTest, | |
| public testing::WithParamInterface< | |
| testing::tuple<const char*, const char*>> { | |
| public: | |
| ProgramManagerDualSourceBlendingTest() = default; | |
| protected: | |
| void SetUpWithFeatureInfo(FeatureInfo* feature_info) { | |
| const char* gl_version = testing::get<0>(GetParam()); | |
| const char* gl_extensions = testing::get<1>(GetParam()); | |
| SetUpBase(gl_version, gl_extensions, feature_info); | |
| } | |
| void SetUp() override { SetUpWithFeatureInfo(nullptr); } | |
| }; | |
| class ProgramManagerDualSourceBlendingES2Test | |
| : public ProgramManagerDualSourceBlendingTest {}; | |
| TEST_P(ProgramManagerDualSourceBlendingES2Test, UseSecondaryFragCoord) { | |
| DCHECK(feature_info_->feature_flags().ext_blend_func_extended); | |
| const VarInfo kFragmentVaryings[] = { | |
| {GL_FLOAT_VEC4, 0, GL_MEDIUM_FLOAT, true, "gl_SecondaryFragColorEXT", | |
| kVarOutput}, | |
| {GL_FLOAT_VEC4, 0, GL_MEDIUM_FLOAT, true, "gl_FragColor", kVarOutput}, | |
| }; | |
| int shader_version = 100; | |
| Program* program = | |
| SetupProgramForVariables({}, kFragmentVaryings, &shader_version); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| } | |
| TEST_P(ProgramManagerDualSourceBlendingES2Test, UseSecondaryFragData) { | |
| const VarInfo kFragmentVaryings[] = { | |
| {GL_FLOAT_VEC4, kMaxDualSourceDrawBuffers, GL_MEDIUM_FLOAT, true, | |
| "gl_SecondaryFragDataEXT", kVarOutput}, | |
| {GL_FLOAT_VEC4, kMaxDrawBuffers, GL_MEDIUM_FLOAT, true, "gl_FragData", | |
| kVarOutput}, | |
| }; | |
| int shader_version = 100; | |
| Program* program = | |
| SetupProgramForVariables({}, kFragmentVaryings, &shader_version); | |
| EXPECT_TRUE(LinkAsExpected(program, true)); | |
| } | |
| INSTANTIATE_TEST_SUITE_P( | |
| SupportedContexts, | |
| ProgramManagerDualSourceBlendingES2Test, | |
| testing::Values( | |
| make_gl_ext_tuple("OpenGL ES 3.1", | |
| "GL_EXT_draw_buffers GL_EXT_blend_func_extended"))); | |
| } // namespace gles2 | |
| } // namespace gpu |