Handle Clear* commands for side-by-side framebuffers
Clear* commands for side-by-side framebuffers require special handling
because only the scissor rectangle of the first viewport is used in the
scissor test as defined in the OpenGL 4.1+ specs.
To enable clearing of each view of a side-by-side framebuffer all views
are iterated over, the corresponding scissor rectangle is set as first,
and a Clear* call is made to the driver. Afterwards the scissor state is
restored.
BUG=angleproject:2062
TEST=angle_end2end_tests
Change-Id: I138663ea61b4f0c9302872108e7dfbadf451b3ec
Reviewed-on: https://chromium-review.googlesource.com/590233
Commit-Queue: Martin Radev <mradev@nvidia.com>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index a53aa7d..4a9a413 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -103,6 +103,12 @@
}
}
+bool RequiresMultipleClears(const FramebufferAttachment *attachment)
+{
+ return attachment != nullptr &&
+ attachment->getMultiviewLayout() == GL_FRAMEBUFFER_MULTIVIEW_SIDE_BY_SIDE_ANGLE;
+}
+
} // namespace
FramebufferGL::FramebufferGL(const FramebufferState &state,
@@ -213,7 +219,16 @@
{
syncClearState(context, mask);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
- mFunctions->clear(mask);
+
+ if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+ {
+ genericSideBySideClear(context, ClearCommandType::Clear, mask, GL_NONE, 0, nullptr, 0.0f,
+ 0);
+ }
+ else
+ {
+ mFunctions->clear(mask);
+ }
return gl::NoError();
}
@@ -225,7 +240,17 @@
{
syncClearBufferState(context, buffer, drawbuffer);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
- mFunctions->clearBufferfv(buffer, drawbuffer, values);
+
+ if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+ {
+ genericSideBySideClear(context, ClearCommandType::ClearBufferfv,
+ static_cast<GLbitfield>(0u), buffer, drawbuffer,
+ reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
+ }
+ else
+ {
+ mFunctions->clearBufferfv(buffer, drawbuffer, values);
+ }
return gl::NoError();
}
@@ -237,7 +262,17 @@
{
syncClearBufferState(context, buffer, drawbuffer);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
- mFunctions->clearBufferuiv(buffer, drawbuffer, values);
+
+ if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+ {
+ genericSideBySideClear(context, ClearCommandType::ClearBufferuiv,
+ static_cast<GLbitfield>(0u), buffer, drawbuffer,
+ reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
+ }
+ else
+ {
+ mFunctions->clearBufferuiv(buffer, drawbuffer, values);
+ }
return gl::NoError();
}
@@ -249,7 +284,17 @@
{
syncClearBufferState(context, buffer, drawbuffer);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
- mFunctions->clearBufferiv(buffer, drawbuffer, values);
+
+ if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+ {
+ genericSideBySideClear(context, ClearCommandType::ClearBufferiv,
+ static_cast<GLbitfield>(0u), buffer, drawbuffer,
+ reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
+ }
+ else
+ {
+ mFunctions->clearBufferiv(buffer, drawbuffer, values);
+ }
return gl::NoError();
}
@@ -262,11 +307,70 @@
{
syncClearBufferState(context, buffer, drawbuffer);
mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
- mFunctions->clearBufferfi(buffer, drawbuffer, depth, stencil);
+
+ if (RequiresMultipleClears(mState.getFirstNonNullAttachment()))
+ {
+ genericSideBySideClear(context, ClearCommandType::ClearBufferfi,
+ static_cast<GLbitfield>(0u), buffer, drawbuffer, nullptr, depth,
+ stencil);
+ }
+ else
+ {
+ mFunctions->clearBufferfi(buffer, drawbuffer, depth, stencil);
+ }
return gl::NoError();
}
+void FramebufferGL::genericSideBySideClear(const gl::Context *context,
+ ClearCommandType clearCommandType,
+ GLbitfield mask,
+ GLenum buffer,
+ GLint drawbuffer,
+ const uint8_t *values,
+ GLfloat depth,
+ GLint stencil)
+{
+ // For side-by-side framebuffers we have to go over each view and set its scissor rectangle
+ // as the first one and just then call Clear*. This is necessary because Clear* commands use
+ // only the first viewport's scissor rectangle.
+ const auto &scissorBase = context->getGLState().getScissor();
+ const FramebufferAttachment *attachment = mState.getFirstNonNullAttachment();
+ ASSERT(attachment != nullptr);
+ const auto &viewportOffsets = attachment->getMultiviewViewportOffsets();
+
+ for (size_t i = 0u; i < viewportOffsets.size(); ++i)
+ {
+ gl::Rectangle scissor(scissorBase.x + viewportOffsets[i].x,
+ scissorBase.y + viewportOffsets[i].y, scissorBase.width,
+ scissorBase.height);
+ mStateManager->setScissorIndexed(0u, scissor);
+ switch (clearCommandType)
+ {
+ case ClearCommandType::Clear:
+ mFunctions->clear(mask);
+ break;
+ case ClearCommandType::ClearBufferfv:
+ mFunctions->clearBufferfv(buffer, drawbuffer,
+ reinterpret_cast<const GLfloat *>(values));
+ break;
+ case ClearCommandType::ClearBufferuiv:
+ mFunctions->clearBufferuiv(buffer, drawbuffer,
+ reinterpret_cast<const GLuint *>(values));
+ break;
+ case ClearCommandType::ClearBufferiv:
+ mFunctions->clearBufferiv(buffer, drawbuffer,
+ reinterpret_cast<const GLint *>(values));
+ break;
+ case ClearCommandType::ClearBufferfi:
+ mFunctions->clearBufferfi(buffer, drawbuffer, depth, stencil);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+}
+
GLenum FramebufferGL::getImplementationColorReadFormat(const gl::Context *context) const
{
const auto *readAttachment = mState.getReadAttachment();
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.h b/src/libANGLE/renderer/gl/FramebufferGL.h
index ea6f4af..64b21b6 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.h
+++ b/src/libANGLE/renderer/gl/FramebufferGL.h
@@ -95,6 +95,17 @@
void maskOutInactiveOutputDrawBuffers(gl::DrawBufferMask maxSet);
private:
+ // Enum containing the different types of Clear* commands.
+ enum class ClearCommandType
+ {
+ Clear,
+ ClearBufferfv,
+ ClearBufferuiv,
+ ClearBufferiv,
+ ClearBufferfi
+ };
+
+ private:
void syncClearState(const gl::Context *context, GLbitfield mask);
void syncClearBufferState(const gl::Context *context, GLenum buffer, GLint drawBuffer);
@@ -118,6 +129,15 @@
GLubyte *pixels,
bool readLastRowSeparately) const;
+ void genericSideBySideClear(const gl::Context *context,
+ ClearCommandType clearCommandType,
+ GLbitfield mask,
+ GLenum buffer,
+ GLint drawbuffer,
+ const uint8_t *values,
+ GLfloat depth,
+ GLint stencil);
+
const FunctionsGL *mFunctions;
StateManagerGL *mStateManager;
const WorkaroundsGL &mWorkarounds;
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp
index 98561f9..dbaad8b 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -1069,6 +1069,18 @@
}
}
+void StateManagerGL::setScissorIndexed(GLuint index, const gl::Rectangle &scissor)
+{
+ ASSERT(mFunctions->scissorIndexed != nullptr);
+ ASSERT(static_cast<size_t>(index) < mScissors.size());
+ if (mScissors[index] != scissor)
+ {
+ mScissors[index] = scissor;
+ mFunctions->scissorIndexed(index, scissor.x, scissor.y, scissor.width, scissor.height);
+ mLocalDirtyBits.set(gl::State::DIRTY_BIT_SCISSOR);
+ }
+}
+
void StateManagerGL::setViewport(const gl::Rectangle &viewport)
{
if (!AllRectanglesMatch(viewport, mViewports))
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.h b/src/libANGLE/renderer/gl/StateManagerGL.h
index 57b8272..f1c4694 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.h
+++ b/src/libANGLE/renderer/gl/StateManagerGL.h
@@ -76,6 +76,7 @@
void setScissorTestEnabled(bool enabled);
void setScissor(const gl::Rectangle &scissor);
+ void setScissorIndexed(GLuint index, const gl::Rectangle &scissor);
void setScissorArrayv(GLuint first, const std::vector<gl::Rectangle> &viewports);
void setViewport(const gl::Rectangle &viewport);
diff --git a/src/tests/gl_tests/FramebufferMultiviewTest.cpp b/src/tests/gl_tests/FramebufferMultiviewTest.cpp
index f6c4a90..d65705a 100644
--- a/src/tests/gl_tests/FramebufferMultiviewTest.cpp
+++ b/src/tests/gl_tests/FramebufferMultiviewTest.cpp
@@ -14,15 +14,24 @@
namespace
{
-GLuint CreateTexture2D(GLenum internalFormat, GLenum format, GLenum type)
+GLuint CreateTexture2D(GLenum internalFormat,
+ GLenum format,
+ GLenum type,
+ GLsizei width,
+ GLsizei height)
{
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
- glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0, format, type, nullptr);
+ glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, nullptr);
return tex;
}
+GLuint CreateTexture2D(GLenum internalFormat, GLenum format, GLenum type)
+{
+ return CreateTexture2D(internalFormat, format, type, 1, 1);
+}
+
} // namespace
class FramebufferMultiviewTest : public ANGLETest
@@ -481,4 +490,56 @@
EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
}
+// Test that glClear clears only the contents of each view.
+TEST_P(FramebufferMultiviewTest, SideBySideClear)
+{
+ if (!requestMultiviewExtension())
+ {
+ return;
+ }
+
+ mTexture2D = CreateTexture2D(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4, 2);
+ const GLint viewportOffsets[4] = {1, 0, 3, 0};
+ glFramebufferTextureMultiviewSideBySideANGLE(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, mTexture2D,
+ 0, 2, &viewportOffsets[0]);
+
+ // Create and bind a normal framebuffer to access the 2D texture.
+ GLuint fbo;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
+
+ // Clear the contents of the texture.
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // Bind and specify viewport/scissor dimensions for each view.
+ glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
+ glViewport(0, 0, 1, 2);
+ glScissor(0, 0, 1, 2);
+
+ glClearColor(1, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ // column 0
+ EXPECT_PIXEL_EQ(0, 0, 0, 0, 0, 0);
+ EXPECT_PIXEL_EQ(0, 1, 0, 0, 0, 0);
+
+ // column 1
+ EXPECT_PIXEL_EQ(1, 0, 255, 0, 0, 0);
+ EXPECT_PIXEL_EQ(1, 1, 255, 0, 0, 0);
+
+ // column 2
+ EXPECT_PIXEL_EQ(2, 0, 0, 0, 0, 0);
+ EXPECT_PIXEL_EQ(2, 1, 0, 0, 0, 0);
+
+ // column 3
+ EXPECT_PIXEL_EQ(3, 0, 255, 0, 0, 0);
+ EXPECT_PIXEL_EQ(3, 1, 255, 0, 0, 0);
+
+ glDeleteFramebuffers(1, &fbo);
+}
+
ANGLE_INSTANTIATE_TEST(FramebufferMultiviewTest, ES3_OPENGL());
\ No newline at end of file