summaryrefslogtreecommitdiffstats
path: root/gpu
diff options
context:
space:
mode:
authorgman@chromium.org <gman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 17:58:32 +0000
committergman@chromium.org <gman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-07 17:58:32 +0000
commitd81e8c5fd686ff034a6e904b3f575675c2a05a1e (patch)
treee211aa0e8a7713cd379db67fd12fbcc026a6706e /gpu
parent1a5520603bc006aa0f73ef6762d2886b6719ee10 (diff)
downloadchromium_src-d81e8c5fd686ff034a6e904b3f575675c2a05a1e.zip
chromium_src-d81e8c5fd686ff034a6e904b3f575675c2a05a1e.tar.gz
chromium_src-d81e8c5fd686ff034a6e904b3f575675c2a05a1e.tar.bz2
Makes LinkProgram fail if both a vertex and fragment shader
are not present. Desktop GL doesn't fail this case because the fixed function pipeline can be a substitute for either TEST=unit tests BUG=none Review URL: http://codereview.chromium.org/2582002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49073 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'gpu')
-rw-r--r--gpu/command_buffer/service/gles2_cmd_decoder.cc13
-rw-r--r--gpu/command_buffer/service/gles2_cmd_decoder_unittest_2.cc25
-rw-r--r--gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc92
-rw-r--r--gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.h21
-rw-r--r--gpu/command_buffer/service/program_manager.cc35
-rw-r--r--gpu/command_buffer/service/program_manager.h11
-rw-r--r--gpu/command_buffer/service/program_manager_unittest.cc31
7 files changed, 197 insertions, 31 deletions
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 588be6e..dd0c665 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -2584,6 +2584,12 @@ error::Error GLES2DecoderImpl::HandleRegisterSharedIds(
void GLES2DecoderImpl::DoDrawArrays(
GLenum mode, GLint first, GLsizei count) {
+ // We have to check this here because the prototype for glDrawArrays
+ // is GLint not GLsizei.
+ if (first < 0) {
+ SetGLError(GL_INVALID_ENUM, "glDrawArrays: first < 0");
+ return;
+ }
if (IsDrawValid(first + count - 1)) {
bool simulated_attrib_0 = SimulateAttrib0(first + count - 1);
bool textures_set = SetBlackTextureForNonRenderableTextures();
@@ -2684,6 +2690,11 @@ void GLES2DecoderImpl::DoLinkProgram(GLuint program) {
if (!info) {
return;
}
+ if (!info->CanLink()) {
+ SetGLError(GL_INVALID_OPERATION,
+ "glLinkProgram: must have both vertex and fragment shader");
+ return;
+ }
glLinkProgram(info->service_id());
GLint success = 0;
glGetProgramiv(info->service_id(), GL_LINK_STATUS, &success);
@@ -3387,6 +3398,7 @@ void GLES2DecoderImpl::DoAttachShader(
if (!shader_info) {
return;
}
+ program_info->AttachShader(shader_info);
glAttachShader(program_info->service_id(), shader_info->service_id());
}
@@ -3402,6 +3414,7 @@ void GLES2DecoderImpl::DoDetachShader(
if (!shader_info) {
return;
}
+ program_info->DetachShader(shader_info);
glDetachShader(program_info->service_id(), shader_info->service_id());
}
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_2.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_2.cc
index bf89ac7..54964d8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_2.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_2.cc
@@ -34,7 +34,24 @@ class GLES2DecoderTest2 : public GLES2DecoderTestBase {
template <>
void GLES2DecoderTestBase::SpecializedSetup<LinkProgram, 0>(bool /* valid */) {
+ const GLuint kClientVertexShaderId = 5001;
+ const GLuint kServiceVertexShaderId = 6001;
+ const GLuint kClientFragmentShaderId = 5002;
+ const GLuint kServiceFragmentShaderId = 6002;
+ DoCreateShader(
+ GL_VERTEX_SHADER, kClientVertexShaderId, kServiceVertexShaderId);
+ DoCreateShader(
+ GL_FRAGMENT_SHADER, kClientFragmentShaderId, kServiceFragmentShaderId);
+
InSequence dummy;
+ EXPECT_CALL(*gl_,
+ AttachShader(kServiceProgramId, kServiceVertexShaderId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*gl_,
+ AttachShader(kServiceProgramId, kServiceFragmentShaderId))
+ .Times(1)
+ .RetiresOnSaturation();
EXPECT_CALL(*gl_, GetProgramiv(kServiceProgramId, GL_LINK_STATUS, _))
.WillOnce(SetArgumentPointee<2>(1));
EXPECT_CALL(*gl_, GetProgramiv(kServiceProgramId, GL_ACTIVE_ATTRIBUTES, _))
@@ -49,6 +66,14 @@ void GLES2DecoderTestBase::SpecializedSetup<LinkProgram, 0>(bool /* valid */) {
*gl_,
GetProgramiv(kServiceProgramId, GL_ACTIVE_UNIFORM_MAX_LENGTH, _))
.WillOnce(SetArgumentPointee<2>(0));
+
+ AttachShader attach_cmd;
+ attach_cmd.Init(client_program_id_, kClientVertexShaderId);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd));
+
+ attach_cmd.Init(client_program_id_, kClientFragmentShaderId);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd));
+
};
template <>
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
index a9cc632..bda68e8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
@@ -185,15 +185,7 @@ void GLES2DecoderTestBase::SetUp() {
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}
- {
- EXPECT_CALL(*gl_, CreateShader(_))
- .Times(1)
- .WillOnce(Return(kServiceShaderId))
- .RetiresOnSaturation();
- CreateShader cmd;
- cmd.Init(GL_VERTEX_SHADER, client_shader_id_);
- EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
- }
+ DoCreateShader(GL_VERTEX_SHADER, client_shader_id_, kServiceShaderId);
EXPECT_EQ(GL_NO_ERROR, GetGLError());
}
@@ -224,6 +216,17 @@ GLint GLES2DecoderTestBase::GetGLError() {
return static_cast<GLint>(*GetSharedMemoryAs<GLenum*>());
}
+void GLES2DecoderTestBase::DoCreateShader(
+ GLenum shader_type, GLuint client_id, GLuint service_id) {
+ EXPECT_CALL(*gl_, CreateShader(shader_type))
+ .Times(1)
+ .WillOnce(Return(service_id))
+ .RetiresOnSaturation();
+ CreateShader cmd;
+ cmd.Init(shader_type, client_id);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+}
+
void GLES2DecoderTestBase::SetBucketAsCString(
uint32 bucket_id, const char* str) {
uint32 size = str ? (strlen(str) + 1) : 0;
@@ -246,8 +249,14 @@ void GLES2DecoderTestBase::SetupShaderForUniform() {
static UniformInfo uniforms[] = {
{ "bar", 1, GL_INT, 1, },
};
+ const GLuint kClientVertexShaderId = 5001;
+ const GLuint kServiceVertexShaderId = 6001;
+ const GLuint kClientFragmentShaderId = 5002;
+ const GLuint kServiceFragmentShaderId = 6002;
SetupShader(attribs, arraysize(attribs), uniforms, arraysize(uniforms),
- client_program_id_, kServiceProgramId);
+ client_program_id_, kServiceProgramId,
+ kClientVertexShaderId, kServiceVertexShaderId,
+ kClientFragmentShaderId, kServiceFragmentShaderId);
EXPECT_CALL(*gl_, UseProgram(kServiceProgramId))
.Times(1)
@@ -388,7 +397,9 @@ void GLES2DecoderWithShaderTestBase::SetUp() {
{ kUniform3Name, kUniform3Size, kUniform3Type, kUniform3Location, },
};
SetupShader(attribs, arraysize(attribs), uniforms, arraysize(uniforms),
- client_program_id_, kServiceProgramId);
+ client_program_id_, kServiceProgramId,
+ client_vertex_shader_id_, kServiceVertexShaderId,
+ client_fragment_shader_id_, kServiceFragmentShaderId);
}
{
@@ -408,20 +419,28 @@ void GLES2DecoderWithShaderTestBase::TearDown() {
void GLES2DecoderTestBase::SetupShader(
GLES2DecoderTestBase::AttribInfo* attribs, size_t num_attribs,
GLES2DecoderTestBase::UniformInfo* uniforms, size_t num_uniforms,
- GLuint client_id, GLuint service_id) {
- LinkProgram cmd;
- cmd.Init(client_id);
-
+ GLuint program_client_id, GLuint program_service_id,
+ GLuint vertex_shader_client_id, GLuint vertex_shader_service_id,
+ GLuint fragment_shader_client_id, GLuint fragment_shader_service_id) {
{
InSequence s;
- EXPECT_CALL(*gl_, LinkProgram(service_id))
+
+ EXPECT_CALL(*gl_,
+ AttachShader(program_service_id, vertex_shader_service_id))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*gl_,
+ AttachShader(program_service_id, fragment_shader_service_id))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(*gl_, LinkProgram(program_service_id))
.Times(1)
.RetiresOnSaturation();
- EXPECT_CALL(*gl_, GetProgramiv(service_id, GL_LINK_STATUS, _))
+ EXPECT_CALL(*gl_, GetProgramiv(program_service_id, GL_LINK_STATUS, _))
.WillOnce(SetArgumentPointee<2>(1))
.RetiresOnSaturation();
EXPECT_CALL(*gl_,
- GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTES, _))
+ GetProgramiv(program_service_id, GL_ACTIVE_ATTRIBUTES, _))
.WillOnce(SetArgumentPointee<2>(num_attribs))
.RetiresOnSaturation();
size_t max_attrib_len = 0;
@@ -430,13 +449,13 @@ void GLES2DecoderTestBase::SetupShader(
max_attrib_len = std::max(max_attrib_len, len);
}
EXPECT_CALL(*gl_,
- GetProgramiv(service_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, _))
+ GetProgramiv(program_service_id, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, _))
.WillOnce(SetArgumentPointee<2>(max_attrib_len))
.RetiresOnSaturation();
for (size_t ii = 0; ii < num_attribs; ++ii) {
const AttribInfo& info = attribs[ii];
EXPECT_CALL(*gl_,
- GetActiveAttrib(service_id, ii, max_attrib_len, _, _, _, _))
+ GetActiveAttrib(program_service_id, ii, max_attrib_len, _, _, _, _))
.WillOnce(DoAll(
SetArgumentPointee<3>(strlen(info.name)),
SetArgumentPointee<4>(info.size),
@@ -445,14 +464,14 @@ void GLES2DecoderTestBase::SetupShader(
info.name + strlen(info.name) + 1)))
.RetiresOnSaturation();
if (!ProgramManager::IsInvalidPrefix(info.name, strlen(info.name))) {
- EXPECT_CALL(*gl_, GetAttribLocation(service_id,
+ EXPECT_CALL(*gl_, GetAttribLocation(program_service_id,
StrEq(info.name)))
.WillOnce(Return(info.location))
.RetiresOnSaturation();
}
}
EXPECT_CALL(*gl_,
- GetProgramiv(service_id, GL_ACTIVE_UNIFORMS, _))
+ GetProgramiv(program_service_id, GL_ACTIVE_UNIFORMS, _))
.WillOnce(SetArgumentPointee<2>(num_uniforms))
.RetiresOnSaturation();
size_t max_uniform_len = 0;
@@ -461,13 +480,13 @@ void GLES2DecoderTestBase::SetupShader(
max_uniform_len = std::max(max_uniform_len, len);
}
EXPECT_CALL(*gl_,
- GetProgramiv(service_id, GL_ACTIVE_UNIFORM_MAX_LENGTH, _))
+ GetProgramiv(program_service_id, GL_ACTIVE_UNIFORM_MAX_LENGTH, _))
.WillOnce(SetArgumentPointee<2>(max_uniform_len))
.RetiresOnSaturation();
for (size_t ii = 0; ii < num_uniforms; ++ii) {
const UniformInfo& info = uniforms[ii];
EXPECT_CALL(*gl_,
- GetActiveUniform(service_id, ii, max_uniform_len, _, _, _, _))
+ GetActiveUniform(program_service_id, ii, max_uniform_len, _, _, _, _))
.WillOnce(DoAll(
SetArgumentPointee<3>(strlen(info.name)),
SetArgumentPointee<4>(info.size),
@@ -476,7 +495,7 @@ void GLES2DecoderTestBase::SetupShader(
info.name + strlen(info.name) + 1)))
.RetiresOnSaturation();
if (!ProgramManager::IsInvalidPrefix(info.name, strlen(info.name))) {
- EXPECT_CALL(*gl_, GetUniformLocation(service_id,
+ EXPECT_CALL(*gl_, GetUniformLocation(program_service_id,
StrEq(info.name)))
.WillOnce(Return(info.location))
.RetiresOnSaturation();
@@ -484,7 +503,7 @@ void GLES2DecoderTestBase::SetupShader(
for (GLsizei jj = 1; jj < info.size; ++jj) {
std::string element_name(
std::string(info.name) + "[" + IntToString(jj) + "]");
- EXPECT_CALL(*gl_, GetUniformLocation(service_id,
+ EXPECT_CALL(*gl_, GetUniformLocation(program_service_id,
StrEq(element_name)))
.WillOnce(Return(info.location + jj))
.RetiresOnSaturation();
@@ -494,7 +513,23 @@ void GLES2DecoderTestBase::SetupShader(
}
}
- EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ DoCreateShader(
+ GL_VERTEX_SHADER, vertex_shader_client_id, vertex_shader_service_id);
+ DoCreateShader(
+ GL_FRAGMENT_SHADER, fragment_shader_client_id,
+ fragment_shader_service_id);
+
+ AttachShader attach_cmd;
+ attach_cmd.Init(program_client_id, vertex_shader_client_id);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd));
+
+ attach_cmd.Init(program_client_id, fragment_shader_client_id);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(attach_cmd));
+
+ LinkProgram link_cmd;
+ link_cmd.Init(program_client_id);
+
+ EXPECT_EQ(error::kNoError, ExecuteCmd(link_cmd));
}
void GLES2DecoderWithShaderTestBase::DoEnableVertexAttribArray(GLint index) {
@@ -576,6 +611,9 @@ void GLES2DecoderWithShaderTestBase::DeleteIndexBuffer() {
// GCC requires these declarations, but MSVC requires they not be present
#ifndef COMPILER_MSVC
+const GLuint GLES2DecoderWithShaderTestBase::kServiceVertexShaderId;
+const GLuint GLES2DecoderWithShaderTestBase::kServiceFragmentShaderId;
+
const GLsizei GLES2DecoderWithShaderTestBase::kNumVertices;
const GLsizei GLES2DecoderWithShaderTestBase::kNumIndices;
const int GLES2DecoderWithShaderTestBase::kValidIndexRangeStart;
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.h b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.h
index e4e7be2..d96ef99 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.h
@@ -162,6 +162,8 @@ class GLES2DecoderTestBase : public testing::Test {
return group_.program_manager()->GetProgramInfo(service_id);
}
+ void DoCreateShader(GLenum shader_type, GLuint client_id, GLuint service_id);
+
void SetBucketAsCString(uint32 bucket_id, const char* str);
struct AttribInfo {
@@ -178,9 +180,12 @@ class GLES2DecoderTestBase : public testing::Test {
GLint location;
};
- void SetupShader(AttribInfo* attribs, size_t num_attribs,
- UniformInfo* uniforms, size_t num_uniforms,
- GLuint client_id, GLuint service_id);
+ void SetupShader(
+ AttribInfo* attribs, size_t num_attribs,
+ UniformInfo* uniforms, size_t num_uniforms,
+ GLuint client_id, GLuint service_id,
+ GLuint vertex_shader_client_id, GLuint vertex_shader_service_id,
+ GLuint fragment_shader_client_id, GLuint fragment_shader_service_id);
// Setups up a shader for testing glUniform.
void SetupShaderForUniform();
@@ -278,9 +283,14 @@ class GLES2DecoderTestBase : public testing::Test {
class GLES2DecoderWithShaderTestBase : public GLES2DecoderTestBase {
public:
GLES2DecoderWithShaderTestBase()
- : GLES2DecoderTestBase() {
+ : GLES2DecoderTestBase(),
+ client_vertex_shader_id_(121),
+ client_fragment_shader_id_(122) {
}
+ static const GLuint kServiceVertexShaderId = 321;
+ static const GLuint kServiceFragmentShaderId = 322;
+
static const GLsizei kNumVertices = 100;
static const GLsizei kNumIndices = 10;
static const int kValidIndexRangeStart = 1;
@@ -345,6 +355,9 @@ class GLES2DecoderWithShaderTestBase : public GLES2DecoderTestBase {
void DeleteVertexBuffer();
void DeleteIndexBuffer();
+
+ GLuint client_vertex_shader_id_;
+ GLuint client_fragment_shader_id_;
};
} // namespace gles2
diff --git a/gpu/command_buffer/service/program_manager.cc b/gpu/command_buffer/service/program_manager.cc
index 77b0c96..2dcb368 100644
--- a/gpu/command_buffer/service/program_manager.cc
+++ b/gpu/command_buffer/service/program_manager.cc
@@ -12,6 +12,18 @@
namespace gpu {
namespace gles2 {
+static int ShaderTypeToIndex(GLenum shader_type) {
+ switch (shader_type) {
+ case GL_VERTEX_SHADER:
+ return 0;
+ case GL_FRAGMENT_SHADER:
+ return 1;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
bool ProgramManager::IsInvalidPrefix(const char* name, size_t length) {
static const char kInvalidPrefix[] = { 'g', 'l', '_' };
return (length >= sizeof(kInvalidPrefix) &&
@@ -246,12 +258,35 @@ void ProgramManager::ProgramInfo::GetProgramiv(GLenum pname, GLint* params) {
// Notice +1 to accomodate NULL terminator.
*params = max_uniform_name_length_ + 1;
break;
+ case GL_LINK_STATUS:
+ *params = valid_;
+ break;
default:
glGetProgramiv(service_id_, pname, params);
break;
}
}
+void ProgramManager::ProgramInfo::AttachShader(
+ ShaderManager::ShaderInfo* info) {
+ attached_shaders_[ShaderTypeToIndex(info->shader_type())] =
+ ShaderManager::ShaderInfo::Ref(info);
+}
+
+void ProgramManager::ProgramInfo::DetachShader(
+ ShaderManager::ShaderInfo* info) {
+ attached_shaders_[ShaderTypeToIndex(info->shader_type())] = NULL;
+}
+
+bool ProgramManager::ProgramInfo::CanLink() const {
+ for (int ii = 0; ii < kMaxAttachedShaders; ++ii) {
+ if (!attached_shaders_[ii]) {
+ return false;
+ }
+ }
+ return true;
+}
+
void ProgramManager::CreateProgramInfo(GLuint client_id, GLuint service_id) {
std::pair<ProgramInfoMap::iterator, bool> result =
program_infos_.insert(
diff --git a/gpu/command_buffer/service/program_manager.h b/gpu/command_buffer/service/program_manager.h
index 7104242..77cb39d 100644
--- a/gpu/command_buffer/service/program_manager.h
+++ b/gpu/command_buffer/service/program_manager.h
@@ -12,6 +12,7 @@
#include "base/logging.h"
#include "base/ref_counted.h"
#include "gpu/command_buffer/service/gl_utils.h"
+#include "gpu/command_buffer/service/shader_manager.h"
namespace gpu {
namespace gles2 {
@@ -29,6 +30,8 @@ class ProgramManager {
public:
typedef scoped_refptr<ProgramInfo> Ref;
+ static const int kMaxAttachedShaders = 2;
+
struct UniformInfo {
UniformInfo(GLsizei _size, GLenum _type, const std::string& _name)
: size(_size),
@@ -132,6 +135,11 @@ class ProgramManager {
return valid_;
}
+ void AttachShader(ShaderManager::ShaderInfo* info);
+ void DetachShader(ShaderManager::ShaderInfo* info);
+
+ bool CanLink() const;
+
private:
friend class base::RefCounted<ProgramInfo>;
friend class ProgramManager;
@@ -167,6 +175,9 @@ class ProgramManager {
// The program this ProgramInfo is tracking.
GLuint service_id_;
+ // Shaders by type of shader.
+ ShaderManager::ShaderInfo::Ref attached_shaders_[kMaxAttachedShaders];
+
// This is true if glLinkProgram was successful.
bool valid_;
};
diff --git a/gpu/command_buffer/service/program_manager_unittest.cc b/gpu/command_buffer/service/program_manager_unittest.cc
index 6767646..b683927 100644
--- a/gpu/command_buffer/service/program_manager_unittest.cc
+++ b/gpu/command_buffer/service/program_manager_unittest.cc
@@ -47,6 +47,7 @@ TEST_F(ProgramManagerTest, Basic) {
ProgramManager::ProgramInfo* info1 = manager_.GetProgramInfo(kClient1Id);
ASSERT_TRUE(info1 != NULL);
EXPECT_EQ(kService1Id, info1->service_id());
+ EXPECT_FALSE(info1->CanLink());
GLuint client_id = 0;
EXPECT_TRUE(manager_.GetClientId(info1->service_id(), &client_id));
EXPECT_EQ(kClient1Id, client_id);
@@ -357,6 +358,36 @@ TEST_F(ProgramManagerWithShaderTest, GetUniformInfo) {
EXPECT_TRUE(program_info->GetUniformInfo(kInvalidIndex) == NULL);
}
+TEST_F(ProgramManagerWithShaderTest, AttachDetachShader) {
+ ShaderManager shader_manager;
+ ProgramManager::ProgramInfo* program_info =
+ manager_.GetProgramInfo(kClientProgramId);
+ ASSERT_TRUE(program_info != NULL);
+ EXPECT_FALSE(program_info->CanLink());
+ const GLuint kVShaderClientId = 2001;
+ const GLuint kFShaderClientId = 2002;
+ const GLuint kVShaderServiceId = 3001;
+ const GLuint kFShaderServiceId = 3002;
+ shader_manager.CreateShaderInfo(
+ kVShaderClientId, kVShaderServiceId, GL_VERTEX_SHADER);
+ ShaderManager::ShaderInfo* vshader = shader_manager.GetShaderInfo(
+ kVShaderClientId);
+ shader_manager.CreateShaderInfo(
+ kFShaderClientId, kFShaderServiceId, GL_FRAGMENT_SHADER);
+ ShaderManager::ShaderInfo* fshader = shader_manager.GetShaderInfo(
+ kFShaderClientId);
+ program_info->AttachShader(vshader);
+ EXPECT_FALSE(program_info->CanLink());
+ program_info->AttachShader(fshader);
+ EXPECT_TRUE(program_info->CanLink());
+ program_info->DetachShader(vshader);
+ EXPECT_FALSE(program_info->CanLink());
+ program_info->AttachShader(vshader);
+ EXPECT_TRUE(program_info->CanLink());
+ program_info->DetachShader(fshader);
+ EXPECT_FALSE(program_info->CanLink());
+}
+
TEST_F(ProgramManagerWithShaderTest, GetUniformLocation) {
const ProgramManager::ProgramInfo* program_info =
manager_.GetProgramInfo(kClientProgramId);