blob: 27ea7a3f866edc2297c4254c2d4b9487d6cbdda3 [file] [edit]
//
// Copyright 2026 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "gtest/gtest.h"
#include "common/unsafe_buffers.h"
#include "libANGLE/Caps.h"
#include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/QueryGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h"
namespace rx
{
namespace
{
void GL_APIENTRY StubBeginQuery(GLenum, GLuint) {}
void GL_APIENTRY StubEndQuery(GLenum) {}
GLenum GL_APIENTRY StubGetError()
{
return GL_NO_ERROR;
}
void GL_APIENTRY StubGetIntegerv(GLenum pname, GLint *data)
{
if (pname == GL_VIEWPORT || pname == GL_SCISSOR_BOX)
{
ANGLE_UNSAFE_BUFFERS({
data[0] = 0;
data[1] = 0;
data[2] = 0;
data[3] = 0;
});
}
}
// Minimal FunctionsGL that never calls into a real driver. Only the entry
// points touched by StateManagerGL::beginQuery/endQuery are populated.
class TestFunctionsGL : public FunctionsGL
{
public:
TestFunctionsGL()
{
standard = STANDARD_GL_ES;
version = gl::Version(3, 0);
profile = 0;
beginQuery = StubBeginQuery;
endQuery = StubEndQuery;
getError = StubGetError;
getIntegerv = StubGetIntegerv;
}
private:
void *loadProcAddress(const std::string &function) const override { return nullptr; }
};
// QueryGL implementation that records pause/resume calls and can be forced to
// fail resume() so that error-propagation paths in StateManagerGL are covered.
class TestQueryGL : public QueryGL
{
public:
explicit TestQueryGL(gl::QueryType type) : QueryGL(type) {}
angle::Result begin(const gl::Context *) override { return angle::Result::Continue; }
angle::Result end(const gl::Context *) override { return angle::Result::Continue; }
angle::Result queryCounter(const gl::Context *) override { return angle::Result::Continue; }
angle::Result getResult(const gl::Context *, GLint *) override
{
return angle::Result::Continue;
}
angle::Result getResult(const gl::Context *, GLuint *) override
{
return angle::Result::Continue;
}
angle::Result getResult(const gl::Context *, GLint64 *) override
{
return angle::Result::Continue;
}
angle::Result getResult(const gl::Context *, GLuint64 *) override
{
return angle::Result::Continue;
}
angle::Result isResultAvailable(const gl::Context *, bool *) override
{
return angle::Result::Continue;
}
angle::Result pause(const gl::Context *) override
{
mPauseCount++;
return angle::Result::Continue;
}
angle::Result resume(const gl::Context *) override
{
mResumeCount++;
return mResumeResult;
}
void setResumeResult(angle::Result result) { mResumeResult = result; }
int pauseCount() const { return mPauseCount; }
int resumeCount() const { return mResumeCount; }
private:
angle::Result mResumeResult = angle::Result::Continue;
int mPauseCount = 0;
int mResumeCount = 0;
};
gl::Caps MakeMinimalCaps()
{
gl::Caps caps;
caps.maxDrawBuffers = 1;
caps.maxVertexAttributes = 1;
caps.maxVertexAttribBindings = 1;
return caps;
}
class StateManagerGLTest : public ::testing::Test
{
protected:
StateManagerGLTest()
: mCaps(MakeMinimalCaps()), mStateManager(&mFunctions, mCaps, mExtensions, mFeatures)
{}
StateManagerGL &stateManager() { return mStateManager; }
private:
TestFunctionsGL mFunctions;
gl::Caps mCaps;
gl::Extensions mExtensions;
angle::FeaturesGL mFeatures;
StateManagerGL mStateManager;
};
// When resume() reports an error for a paused query, StateManagerGL must still
// drop its reference so that a later resumeAllQueries() call does not touch the
// query again after it may have been destroyed.
TEST_F(StateManagerGLTest, ResumeAllQueriesClearsPausedQueryOnError)
{
TestQueryGL query(gl::QueryType::AnySamples);
stateManager().beginQuery(gl::QueryType::AnySamples, &query, 1);
EXPECT_EQ(angle::Result::Continue, stateManager().pauseAllQueries(nullptr));
EXPECT_EQ(1, query.pauseCount());
query.setResumeResult(angle::Result::Stop);
EXPECT_EQ(angle::Result::Stop, stateManager().resumeAllQueries(nullptr));
EXPECT_EQ(1, query.resumeCount());
// The query is no longer tracked; a subsequent resume must not reach it.
query.setResumeResult(angle::Result::Continue);
EXPECT_EQ(angle::Result::Continue, stateManager().resumeAllQueries(nullptr));
EXPECT_EQ(1, query.resumeCount());
}
// If one paused query fails to resume, StateManagerGL must still drop its
// references to every other paused query rather than leaving them behind.
TEST_F(StateManagerGLTest, ResumeAllQueriesClearsAllPausedQueriesOnError)
{
TestQueryGL failingQuery(gl::QueryType::AnySamples);
TestQueryGL otherQuery(gl::QueryType::TransformFeedbackPrimitivesWritten);
stateManager().beginQuery(failingQuery.getType(), &failingQuery, 1);
stateManager().beginQuery(otherQuery.getType(), &otherQuery, 2);
EXPECT_EQ(angle::Result::Continue, stateManager().pauseAllQueries(nullptr));
failingQuery.setResumeResult(angle::Result::Stop);
EXPECT_EQ(angle::Result::Stop, stateManager().resumeAllQueries(nullptr));
// Neither query should be reached by a subsequent resume.
int failingResumeCount = failingQuery.resumeCount();
int otherResumeCount = otherQuery.resumeCount();
EXPECT_EQ(angle::Result::Continue, stateManager().resumeAllQueries(nullptr));
EXPECT_EQ(failingResumeCount, failingQuery.resumeCount());
EXPECT_EQ(otherResumeCount, otherQuery.resumeCount());
}
// resumeQuery() must clear its paused-query reference even when resume() fails.
TEST_F(StateManagerGLTest, ResumeQueryClearsPausedQueryOnError)
{
TestQueryGL query(gl::QueryType::AnySamples);
stateManager().beginQuery(gl::QueryType::AnySamples, &query, 1);
EXPECT_EQ(angle::Result::Continue,
stateManager().pauseQuery(nullptr, gl::QueryType::AnySamples));
query.setResumeResult(angle::Result::Stop);
EXPECT_EQ(angle::Result::Stop, stateManager().resumeQuery(nullptr, gl::QueryType::AnySamples));
EXPECT_EQ(1, query.resumeCount());
query.setResumeResult(angle::Result::Continue);
EXPECT_EQ(angle::Result::Continue,
stateManager().resumeQuery(nullptr, gl::QueryType::AnySamples));
EXPECT_EQ(1, query.resumeCount());
}
} // namespace
} // namespace rx