Commit a4595b80 by Jamie Madill Committed by Commit Bot

WebGL: Validate simple rendering feedback loops.

This adds the most basic form of rendering feedback loop detection: when we're rendering to a texture that's also bound as an input. It doesn't filter by selected mipmap level or 3D texture slice, or do depth attachment validation. It also is missing checks for feedback loops against the default Framebuffer. BUG=angleproject:1685 Change-Id: Idb0ee2bfe1c35611544d132204c0da832c0f1c48 Reviewed-on: https://chromium-review.googlesource.com/425489 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org>
parent 008450ba
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "libANGLE/Framebuffer.h" #include "libANGLE/Framebuffer.h"
#include "common/BitSetIterator.h"
#include "common/Optional.h" #include "common/Optional.h"
#include "common/utilities.h" #include "common/utilities.h"
#include "libANGLE/Config.h" #include "libANGLE/Config.h"
...@@ -46,6 +47,7 @@ FramebufferState::FramebufferState() ...@@ -46,6 +47,7 @@ FramebufferState::FramebufferState()
mReadBufferState(GL_COLOR_ATTACHMENT0_EXT) mReadBufferState(GL_COLOR_ATTACHMENT0_EXT)
{ {
mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT; mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT;
mEnabledDrawBuffers.set(0);
} }
FramebufferState::FramebufferState(const Caps &caps) FramebufferState::FramebufferState(const Caps &caps)
...@@ -419,6 +421,15 @@ void Framebuffer::setDrawBuffers(size_t count, const GLenum *buffers) ...@@ -419,6 +421,15 @@ void Framebuffer::setDrawBuffers(size_t count, const GLenum *buffers)
std::copy(buffers, buffers + count, drawStates.begin()); std::copy(buffers, buffers + count, drawStates.begin());
std::fill(drawStates.begin() + count, drawStates.end(), GL_NONE); std::fill(drawStates.begin() + count, drawStates.end(), GL_NONE);
mDirtyBits.set(DIRTY_BIT_DRAW_BUFFERS); mDirtyBits.set(DIRTY_BIT_DRAW_BUFFERS);
mState.mEnabledDrawBuffers.reset();
for (size_t index = 0; index < count; ++index)
{
if (drawStates[index] != GL_NONE && mState.mColorAttachments[index].isAttached())
{
mState.mEnabledDrawBuffers.set(index);
}
}
} }
const FramebufferAttachment *Framebuffer::getDrawBuffer(size_t drawBuffer) const const FramebufferAttachment *Framebuffer::getDrawBuffer(size_t drawBuffer) const
...@@ -944,6 +955,9 @@ void Framebuffer::setAttachment(GLenum type, ...@@ -944,6 +955,9 @@ void Framebuffer::setAttachment(GLenum type,
mState.mColorAttachments[colorIndex].attach(type, binding, textureIndex, resource); mState.mColorAttachments[colorIndex].attach(type, binding, textureIndex, resource);
mDirtyBits.set(DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex); mDirtyBits.set(DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex);
BindResourceChannel(&mDirtyColorAttachmentBindings[colorIndex], resource); BindResourceChannel(&mDirtyColorAttachmentBindings[colorIndex], resource);
bool enabled = (type != GL_NONE && getDrawBufferState(colorIndex) != GL_NONE);
mState.mEnabledDrawBuffers.set(colorIndex, enabled);
} }
break; break;
} }
...@@ -976,4 +990,32 @@ bool Framebuffer::complete(const ContextState &state) ...@@ -976,4 +990,32 @@ bool Framebuffer::complete(const ContextState &state)
return (checkStatus(state) == GL_FRAMEBUFFER_COMPLETE); return (checkStatus(state) == GL_FRAMEBUFFER_COMPLETE);
} }
bool Framebuffer::formsRenderingFeedbackLoopWith(const State &state) const
{
const Program *program = state.getProgram();
// TODO(jmadill): Default framebuffer feedback loops.
if (mId == 0)
{
return false;
}
// The bitset will skip inactive draw buffers.
for (GLuint drawIndex : angle::IterateBitSet(mState.mEnabledDrawBuffers))
{
const FramebufferAttachment *attachment = getDrawBuffer(drawIndex);
if (attachment && attachment->type() == GL_TEXTURE)
{
// Validate the feedback loop.
if (program->samplesFromTexture(state, attachment->id()))
{
return true;
}
}
}
// TODO(jmadill): Validate depth-stencil feedback loop.
return false;
}
} // namespace gl } // namespace gl
...@@ -92,6 +92,7 @@ class FramebufferState final : angle::NonCopyable ...@@ -92,6 +92,7 @@ class FramebufferState final : angle::NonCopyable
std::vector<GLenum> mDrawBufferStates; std::vector<GLenum> mDrawBufferStates;
GLenum mReadBufferState; GLenum mReadBufferState;
std::bitset<IMPLEMENTATION_MAX_DRAW_BUFFERS> mEnabledDrawBuffers;
}; };
class Framebuffer final : public LabeledObject, public angle::SignalReceiver class Framebuffer final : public LabeledObject, public angle::SignalReceiver
...@@ -212,6 +213,8 @@ class Framebuffer final : public LabeledObject, public angle::SignalReceiver ...@@ -212,6 +213,8 @@ class Framebuffer final : public LabeledObject, public angle::SignalReceiver
// angle::SignalReceiver implementation // angle::SignalReceiver implementation
void signal(angle::SignalToken token) override; void signal(angle::SignalToken token) override;
bool formsRenderingFeedbackLoopWith(const State &state) const;
private: private:
void detachResourceById(GLenum resourceType, GLuint resourceId); void detachResourceById(GLenum resourceType, GLuint resourceId);
void detachMatchingAttachment(FramebufferAttachment *attachment, void detachMatchingAttachment(FramebufferAttachment *attachment,
......
...@@ -3126,4 +3126,27 @@ void Program::getUniformInternal(GLint location, DestT *dataOut) const ...@@ -3126,4 +3126,27 @@ void Program::getUniformInternal(GLint location, DestT *dataOut) const
UNREACHABLE(); UNREACHABLE();
} }
} }
bool Program::samplesFromTexture(const gl::State &state, GLuint textureID) const
{
// Must be called after samplers are validated.
ASSERT(mCachedValidateSamplersResult.valid() && mCachedValidateSamplersResult.value());
for (const auto &binding : mState.mSamplerBindings)
{
GLenum textureType = binding.textureType;
for (const auto &unit : binding.boundTextureUnits)
{
GLenum programTextureID = state.getSamplerTextureId(unit, textureType);
if (programTextureID == textureID)
{
// TODO(jmadill): Check for appropriate overlap.
return true;
}
}
}
return false;
}
} // namespace gl } // namespace gl
...@@ -390,6 +390,7 @@ class Program final : angle::NonCopyable, public LabeledObject ...@@ -390,6 +390,7 @@ class Program final : angle::NonCopyable, public LabeledObject
void validate(const Caps &caps); void validate(const Caps &caps);
bool validateSamplers(InfoLog *infoLog, const Caps &caps); bool validateSamplers(InfoLog *infoLog, const Caps &caps);
bool isValidated() const; bool isValidated() const;
bool samplesFromTexture(const gl::State &state, GLuint textureID) const;
const AttributesMask &getActiveAttribLocationsMask() const const AttributesMask &getActiveAttribLocationsMask() const
{ {
......
...@@ -3221,6 +3221,18 @@ bool ValidateDrawBase(ValidationContext *context, GLenum mode, GLsizei count) ...@@ -3221,6 +3221,18 @@ bool ValidateDrawBase(ValidationContext *context, GLenum mode, GLsizei count)
} }
} }
// Detect rendering feedback loops for WebGL.
if (context->getExtensions().webglCompatibility)
{
if (framebuffer->formsRenderingFeedbackLoopWith(state))
{
context->handleError(
Error(GL_INVALID_OPERATION,
"Rendering feedback loop formed between Framebuffer and active Texture."));
return false;
}
}
// No-op if zero count // No-op if zero count
return (count > 0); return (count > 0);
} }
......
...@@ -452,6 +452,82 @@ TEST_P(WebGL2CompatibilityTest, DrawArraysBufferOutOfBoundsInstanced) ...@@ -452,6 +452,82 @@ TEST_P(WebGL2CompatibilityTest, DrawArraysBufferOutOfBoundsInstanced)
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
} }
// Tests that a rendering feedback loop triggers a GL error under WebGL.
// Based on WebGL test conformance/renderbuffers/feedback-loop.html.
TEST_P(WebGLCompatibilityTest, RenderingFeedbackLoop)
{
const std::string vertexShader =
"attribute vec4 a_position;\n"
"varying vec2 v_texCoord;\n"
"void main() {\n"
" gl_Position = a_position;\n"
" v_texCoord = (a_position.xy * 0.5) + 0.5;\n"
"}\n";
const std::string fragmentShader =
"precision mediump float;\n"
"varying vec2 v_texCoord;\n"
"uniform sampler2D u_texture;\n"
"void main() {\n"
" // Shader swizzles color channels so we can tell if the draw succeeded.\n"
" gl_FragColor = texture2D(u_texture, v_texCoord).gbra;\n"
"}\n";
GLTexture texture;
glBindTexture(GL_TEXTURE_2D, texture.get());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
ASSERT_GL_NO_ERROR();
GLFramebuffer framebuffer;
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.get());
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.get(), 0);
ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
ANGLE_GL_PROGRAM(program, vertexShader, fragmentShader);
GLint uniformLoc = glGetUniformLocation(program.get(), "u_texture");
ASSERT_NE(-1, uniformLoc);
glUseProgram(program.get());
glUniform1i(uniformLoc, 0);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
ASSERT_GL_NO_ERROR();
// Drawing with a texture that is also bound to the current framebuffer should fail
glBindTexture(GL_TEXTURE_2D, texture.get());
drawQuad(program.get(), "a_position", 0.5f, 1.0f, true);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// Ensure that the texture contents did not change after the previous render
glBindFramebuffer(GL_FRAMEBUFFER, 0);
drawQuad(program.get(), "a_position", 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
// Drawing when texture is bound to an inactive uniform should succeed
GLTexture texture2;
glBindTexture(GL_TEXTURE_2D, texture2.get());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer.get());
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture.get());
drawQuad(program.get(), "a_position", 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}
// Use this to select which configurations (e.g. which renderer, which GLES major version) these // Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against. // tests should be run against.
ANGLE_INSTANTIATE_TEST(WebGLCompatibilityTest, ANGLE_INSTANTIATE_TEST(WebGLCompatibilityTest,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment