Commit e4109f27 by James Darpinian Committed by Commit Bot

WebGL: validate texture format matches sampler type

WebGL requires that drawing produces INVALID_OPERATION if a texture's format doesn't match the sampler type it is bound to. This is a little confusing because samplers have two attributes that could be called "type": addressing mode (2D/3D/Cube), and component format (float/signed/unsigned/shadow). ANGLE already handled checking the addressing mode; this change adds checking for the component format. Fixes WebGL conformance test conformance2/uniforms/incompatible-texture-type-for-sampler.html Bug: chromium:809237 Change-Id: I52ebfecd92625e3ee10274cb5f548d7e53de72dd Reviewed-on: https://chromium-review.googlesource.com/c/1377611Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarYuly Novikov <ynovikov@chromium.org> Commit-Queue: James Darpinian <jdarpinian@chromium.org>
parent 225f08bf
......@@ -122,5 +122,5 @@
"proc table:src/libGLESv2/proc_table_data.json":
"68772eb4798f438df2eb997394999855",
"uniform type:src/common/gen_uniform_type_table.py":
"59cb4ffd0f584c4bd37f2f4ff59a2b93"
"e185802e66950dfc5fc7a8fc19751206"
}
\ No newline at end of file
......@@ -138,7 +138,7 @@ const UniformTypeInfo &GetUniformTypeInfo(GLenum uniformType)
}} // namespace gl
"""
type_info_data_template = """{{{type}, {component_type}, {texture_type}, {transposed_type}, {bool_type}, {rows}, {columns}, {components}, {component_size}, {internal_size}, {external_size}, {is_sampler}, {is_matrix}, {is_image} }}"""
type_info_data_template = """{{{type}, {component_type}, {texture_type}, {transposed_type}, {bool_type}, {sampler_format}, {rows}, {columns}, {components}, {component_size}, {internal_size}, {external_size}, {is_sampler}, {is_matrix}, {is_image} }}"""
type_index_case_template = """case {enum_value}: return {index_value};"""
def cpp_bool(value):
......@@ -181,6 +181,18 @@ def get_bool_type(uniform_type):
else:
return "GL_NONE"
def get_sampler_format(uniform_type):
if not "_SAMPLER_" in uniform_type:
return "SamplerFormat::InvalidEnum"
elif "_SHADOW" in uniform_type:
return "SamplerFormat::Shadow"
elif "GL_UNSIGNED_INT_SAMPLER_" in uniform_type:
return "SamplerFormat::Unsigned"
elif "GL_INT_SAMPLER_" in uniform_type:
return "SamplerFormat::Signed"
else:
return "SamplerFormat::Float"
def get_rows(uniform_type):
if uniform_type == "GL_NONE":
return "0"
......@@ -239,6 +251,7 @@ def gen_type_info(uniform_type):
texture_type = get_texture_type(uniform_type),
transposed_type = get_transposed_type(uniform_type),
bool_type = get_bool_type(uniform_type),
sampler_format = get_sampler_format(uniform_type),
rows = get_rows(uniform_type),
columns = get_columns(uniform_type),
components = get_components(uniform_type),
......
......@@ -85,6 +85,17 @@ unsigned int ArraySizeProduct(const std::vector<unsigned int> &arraySizes);
// GL_INVALID_INDEX and write the length of the original string.
unsigned int ParseArrayIndex(const std::string &name, size_t *nameLengthWithoutArrayIndexOut);
enum class SamplerFormat : uint8_t
{
Float = 0,
Unsigned = 1,
Signed = 2,
Shadow = 3,
InvalidEnum = 4,
EnumCount = 4,
};
struct UniformTypeInfo final : angle::NonCopyable
{
constexpr UniformTypeInfo(GLenum type,
......@@ -92,6 +103,7 @@ struct UniformTypeInfo final : angle::NonCopyable
GLenum textureType,
GLenum transposedMatrixType,
GLenum boolVectorType,
SamplerFormat samplerFormat,
int rowCount,
int columnCount,
int componentCount,
......@@ -106,6 +118,7 @@ struct UniformTypeInfo final : angle::NonCopyable
textureType(textureType),
transposedMatrixType(transposedMatrixType),
boolVectorType(boolVectorType),
samplerFormat(samplerFormat),
rowCount(rowCount),
columnCount(columnCount),
componentCount(componentCount),
......@@ -122,6 +135,7 @@ struct UniformTypeInfo final : angle::NonCopyable
GLenum textureType;
GLenum transposedMatrixType;
GLenum boolVectorType;
SamplerFormat samplerFormat;
int rowCount;
int columnCount;
int componentCount;
......
......@@ -421,6 +421,7 @@ MSG kRenderableInternalFormat = "SizedInternalformat must be color-renderable =
MSG kRenderbufferNotBound = "A renderbuffer must be bound.";
MSG kResourceMaxRenderbufferSize = "Desired resource size is greater than max renderbuffer size.";
MSG kResourceMaxTextureSize = "Desired resource size is greater than max texture size.";
MSG kSamplerFormatMismatch = "Mismatch between texture format and sampler type (signed/unsigned/float/shadow).";
MSG kSamplerUniformValueOutOfRange = "Sampler uniform value out of range.";
MSG kSamplesOutOfRange = "Samples must not be greater than maximum supported value for the format.";
MSG kSamplesZero = "Samples may not be zero.";
......
......@@ -414,10 +414,10 @@ angle::Result MemoryProgramCache::Deserialize(const Context *context,
for (unsigned int samplerIndex = 0; samplerIndex < samplerCount; ++samplerIndex)
{
TextureType textureType = stream.readEnum<TextureType>();
SamplerFormat format = stream.readEnum<SamplerFormat>();
size_t bindingCount = stream.readInt<size_t>();
bool unreferenced = stream.readBool();
state->mSamplerBindings.emplace_back(
SamplerBinding(textureType, bindingCount, unreferenced));
state->mSamplerBindings.emplace_back(textureType, format, bindingCount, unreferenced);
}
unsigned int imageRangeLow = stream.readInt<unsigned int>();
......@@ -611,6 +611,7 @@ void MemoryProgramCache::Serialize(const Context *context,
for (const auto &samplerBinding : state.getSamplerBindings())
{
stream.writeEnum(samplerBinding.textureType);
stream.writeEnum(samplerBinding.format);
stream.writeInt(samplerBinding.boundTextureUnits.size());
stream.writeInt(samplerBinding.unreferenced);
}
......
......@@ -724,8 +724,14 @@ VariableLocation::VariableLocation(unsigned int arrayIndex, unsigned int index)
}
// SamplerBindings implementation.
SamplerBinding::SamplerBinding(TextureType textureTypeIn, size_t elementCount, bool unreferenced)
: textureType(textureTypeIn), boundTextureUnits(elementCount, 0), unreferenced(unreferenced)
SamplerBinding::SamplerBinding(TextureType textureTypeIn,
SamplerFormat formatIn,
size_t elementCount,
bool unreferenced)
: textureType(textureTypeIn),
format(formatIn),
boundTextureUnits(elementCount, 0),
unreferenced(unreferenced)
{}
SamplerBinding::SamplerBinding(const SamplerBinding &other) = default;
......@@ -1356,6 +1362,8 @@ void ProgramState::updateTransformFeedbackStrides()
void ProgramState::updateActiveSamplers()
{
mActiveSamplerRefCounts.fill(0);
for (SamplerBinding &samplerBinding : mSamplerBindings)
{
if (samplerBinding.unreferenced)
......@@ -1363,8 +1371,22 @@ void ProgramState::updateActiveSamplers()
for (GLint textureUnit : samplerBinding.boundTextureUnits)
{
mActiveSamplerRefCounts[textureUnit]++;
mActiveSamplerTypes[textureUnit] = getSamplerUniformTextureType(textureUnit);
if (++mActiveSamplerRefCounts[textureUnit] == 1)
{
mActiveSamplerTypes[textureUnit] = samplerBinding.textureType;
mActiveSamplerFormats[textureUnit] = samplerBinding.format;
}
else
{
if (mActiveSamplerTypes[textureUnit] != samplerBinding.textureType)
{
mActiveSamplerTypes[textureUnit] = TextureType::InvalidEnum;
}
if (mActiveSamplerFormats[textureUnit] != samplerBinding.format)
{
mActiveSamplerFormats[textureUnit] = SamplerFormat::InvalidEnum;
}
}
mActiveSamplersMask.set(textureUnit);
}
}
......@@ -2895,7 +2917,8 @@ void Program::linkSamplerAndImageBindings(GLuint *combinedImageUniforms)
const auto &samplerUniform = mState.mUniforms[samplerIndex];
TextureType textureType = SamplerTypeToTextureType(samplerUniform.type);
unsigned int elementCount = samplerUniform.getBasicTypeElementCount();
mState.mSamplerBindings.emplace_back(textureType, elementCount, false);
SamplerFormat format = samplerUniform.typeInfo->samplerFormat;
mState.mSamplerBindings.emplace_back(textureType, format, elementCount, false);
}
}
......@@ -3896,30 +3919,45 @@ void Program::updateSamplerUniform(Context *context,
newRefCount++;
// Check for binding type change.
TextureType &newSamplerType = mState.mActiveSamplerTypes[newTextureUnit];
TextureType &oldSamplerType = mState.mActiveSamplerTypes[oldTextureUnit];
TextureType &newSamplerType = mState.mActiveSamplerTypes[newTextureUnit];
TextureType &oldSamplerType = mState.mActiveSamplerTypes[oldTextureUnit];
SamplerFormat &newSamplerFormat = mState.mActiveSamplerFormats[newTextureUnit];
SamplerFormat &oldSamplerFormat = mState.mActiveSamplerFormats[oldTextureUnit];
if (newRefCount == 1)
{
newSamplerType = samplerBinding.textureType;
newSamplerType = samplerBinding.textureType;
newSamplerFormat = samplerBinding.format;
mState.mActiveSamplersMask.set(newTextureUnit);
}
else if (newSamplerType != samplerBinding.textureType)
else
{
// Conflict detected. Ensure we reset it properly.
newSamplerType = TextureType::InvalidEnum;
if (newSamplerType != samplerBinding.textureType)
{
// Conflict detected. Ensure we reset it properly.
newSamplerType = TextureType::InvalidEnum;
}
if (newSamplerFormat != samplerBinding.format)
{
newSamplerFormat = SamplerFormat::InvalidEnum;
}
}
// Unset previously active sampler.
if (oldRefCount == 0)
{
oldSamplerType = TextureType::InvalidEnum;
oldSamplerType = TextureType::InvalidEnum;
oldSamplerFormat = SamplerFormat::InvalidEnum;
mState.mActiveSamplersMask.reset(oldTextureUnit);
}
else if (oldSamplerType == TextureType::InvalidEnum)
else
{
// Previous conflict. Check if this new change fixed the conflict.
oldSamplerType = mState.getSamplerUniformTextureType(oldTextureUnit);
if (oldSamplerType == TextureType::InvalidEnum ||
oldSamplerFormat == SamplerFormat::InvalidEnum)
{
// Previous conflict. Check if this new change fixed the conflict.
mState.setSamplerUniformTextureTypeAndFormat(oldTextureUnit);
}
}
// Notify context.
......@@ -3934,9 +3972,11 @@ void Program::updateSamplerUniform(Context *context,
mCachedValidateSamplersResult.reset();
}
TextureType ProgramState::getSamplerUniformTextureType(size_t textureUnitIndex) const
void ProgramState::setSamplerUniformTextureTypeAndFormat(size_t textureUnitIndex)
{
TextureType foundType = TextureType::InvalidEnum;
bool foundBinding = false;
TextureType foundType = TextureType::InvalidEnum;
SamplerFormat foundFormat = SamplerFormat::InvalidEnum;
for (const SamplerBinding &binding : mSamplerBindings)
{
......@@ -3949,19 +3989,29 @@ TextureType ProgramState::getSamplerUniformTextureType(size_t textureUnitIndex)
{
if (textureUnit == textureUnitIndex)
{
if (foundType == TextureType::InvalidEnum)
if (!foundBinding)
{
foundType = binding.textureType;
foundBinding = true;
foundType = binding.textureType;
foundFormat = binding.format;
}
else if (foundType != binding.textureType)
else
{
return TextureType::InvalidEnum;
if (foundType != binding.textureType)
{
foundType = TextureType::InvalidEnum;
}
if (foundFormat != binding.format)
{
foundFormat = SamplerFormat::InvalidEnum;
}
}
}
}
}
return foundType;
mActiveSamplerTypes[textureUnitIndex] = foundType;
mActiveSamplerFormats[textureUnitIndex] = foundFormat;
}
template <typename T>
......
......@@ -23,6 +23,7 @@
#include "common/Optional.h"
#include "common/angleutils.h"
#include "common/mathutil.h"
#include "common/utilities.h"
#include "libANGLE/Constants.h"
#include "libANGLE/Debug.h"
......@@ -209,13 +210,18 @@ struct BindingInfo
// This small structure encapsulates binding sampler uniforms to active GL textures.
struct SamplerBinding
{
SamplerBinding(TextureType textureTypeIn, size_t elementCount, bool unreferenced);
SamplerBinding(TextureType textureTypeIn,
SamplerFormat formatIn,
size_t elementCount,
bool unreferenced);
SamplerBinding(const SamplerBinding &other);
~SamplerBinding();
// Necessary for retrieving active textures from the GL state.
TextureType textureType;
SamplerFormat format;
// List of all textures bound to this sampler, of type textureType.
std::vector<GLuint> boundTextureUnits;
......@@ -359,6 +365,10 @@ class ProgramState final : angle::NonCopyable
bool hasAttachedShader() const;
const ActiveTextureMask &getActiveSamplersMask() const { return mActiveSamplersMask; }
SamplerFormat getSamplerFormatForTextureUnitIndex(size_t textureUnitIndex) const
{
return mActiveSamplerFormats[textureUnitIndex];
}
private:
friend class MemoryProgramCache;
......@@ -369,7 +379,7 @@ class ProgramState final : angle::NonCopyable
void updateActiveImages();
// Scans the sampler bindings for type conflicts with sampler 'textureUnitIndex'.
TextureType getSamplerUniformTextureType(size_t textureUnitIndex) const;
void setSamplerUniformTextureTypeAndFormat(size_t textureUnitIndex);
std::string mLabel;
......@@ -456,6 +466,7 @@ class ProgramState final : angle::NonCopyable
ActiveTextureMask mActiveSamplersMask;
ActiveTextureArray<uint32_t> mActiveSamplerRefCounts;
ActiveTextureArray<TextureType> mActiveSamplerTypes;
ActiveTextureArray<SamplerFormat> mActiveSamplerFormats;
// Cached mask of active images.
ActiveTextureMask mActiveImagesMask;
......
......@@ -281,6 +281,7 @@ State::State(ContextID contextIn,
mVertexArray(nullptr),
mActiveSampler(0),
mActiveTexturesCache{},
mTexturesIncompatibleWithSamplers(0),
mPrimitiveRestart(false),
mDebug(debug),
mMultiSampling(false),
......@@ -536,6 +537,10 @@ ANGLE_INLINE void State::updateActiveTextureState(const Context *context,
}
}
mTexturesIncompatibleWithSamplers[textureIndex] =
!texture->getTextureState().compatibleWithSamplerFormat(
mProgram->getState().getSamplerFormatForTextureUnitIndex(textureIndex));
mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
}
......
......@@ -601,6 +601,11 @@ class State : angle::NonCopyable
using BufferBindingSetter = void (State::*)(const Context *, Buffer *);
ANGLE_INLINE bool validateSamplerFormats() const
{
return (mTexturesIncompatibleWithSamplers & mProgram->getActiveSamplersMask()).none();
}
private:
friend class Context;
......@@ -734,6 +739,8 @@ class State : angle::NonCopyable
ActiveTexturePointerArray mActiveTexturesCache;
std::vector<angle::ObserverBinding> mCompleteTextureBindings;
ActiveTextureMask mTexturesIncompatibleWithSamplers;
SamplerBindingVector mSamplers;
// It would be nice to merge the image and observer binding. Same for textures.
......
......@@ -106,7 +106,9 @@ TextureState::TextureState(TextureType type)
mImageDescs((IMPLEMENTATION_MAX_TEXTURE_LEVELS + 1) * (type == TextureType::CubeMap ? 6 : 1)),
mCropRect(0, 0, 0, 0),
mGenerateMipmapHint(GL_FALSE),
mInitState(InitState::MayNeedInit)
mInitState(InitState::MayNeedInit),
mCachedSamplerFormat(SamplerFormat::InvalidEnum),
mCachedSamplerFormatValid(false)
{}
TextureState::~TextureState() {}
......@@ -239,6 +241,33 @@ GLenum TextureState::getGenerateMipmapHint() const
return mGenerateMipmapHint;
}
SamplerFormat TextureState::computeRequiredSamplerFormat() const
{
const ImageDesc &baseImageDesc = getImageDesc(getBaseImageTarget(), getEffectiveBaseLevel());
if ((baseImageDesc.format.info->format == GL_DEPTH_COMPONENT ||
baseImageDesc.format.info->format == GL_DEPTH_STENCIL) &&
mSamplerState.getCompareMode() != GL_NONE)
{
return SamplerFormat::Shadow;
}
else
{
switch (baseImageDesc.format.info->componentType)
{
case GL_UNSIGNED_NORMALIZED:
case GL_SIGNED_NORMALIZED:
case GL_FLOAT:
return SamplerFormat::Float;
case GL_INT:
return SamplerFormat::Signed;
case GL_UNSIGNED_INT:
return SamplerFormat::Unsigned;
default:
return SamplerFormat::InvalidEnum;
}
}
}
bool TextureState::computeSamplerCompleteness(const SamplerState &samplerState,
const State &data) const
{
......@@ -964,6 +993,7 @@ void Texture::signalDirtyStorage(const Context *context, InitState initState)
{
mState.mInitState = initState;
invalidateCompletenessCache();
mState.mCachedSamplerFormatValid = false;
onStateChange(context, angle::SubjectMessage::STORAGE_CHANGED);
}
......@@ -971,6 +1001,7 @@ void Texture::signalDirtyState(const Context *context, size_t dirtyBit)
{
mDirtyBits.set(dirtyBit);
invalidateCompletenessCache();
mState.mCachedSamplerFormatValid = false;
onStateChange(context, angle::SubjectMessage::DEPENDENT_DIRTY_BITS);
}
......
......@@ -15,6 +15,7 @@
#include "angle_gl.h"
#include "common/Optional.h"
#include "common/debug.h"
#include "common/utilities.h"
#include "libANGLE/Caps.h"
#include "libANGLE/Constants.h"
#include "libANGLE/Debug.h"
......@@ -109,6 +110,17 @@ struct TextureState final : private angle::NonCopyable
bool isCubeComplete() const;
ANGLE_INLINE bool compatibleWithSamplerFormat(SamplerFormat format) const
{
if (!mCachedSamplerFormatValid)
{
mCachedSamplerFormat = computeRequiredSamplerFormat();
mCachedSamplerFormatValid = true;
}
// Incomplete textures are compatible with any sampler format.
return mCachedSamplerFormat == SamplerFormat::InvalidEnum || format == mCachedSamplerFormat;
}
const ImageDesc &getImageDesc(TextureTarget target, size_t level) const;
const ImageDesc &getImageDesc(const ImageIndex &imageIndex) const;
......@@ -139,6 +151,7 @@ struct TextureState final : private angle::NonCopyable
bool computeSamplerCompleteness(const SamplerState &samplerState, const State &data) const;
bool computeMipmapCompleteness() const;
bool computeLevelCompleteness(TextureTarget target, size_t level) const;
SamplerFormat computeRequiredSamplerFormat() const;
TextureTarget getBaseImageTarget() const;
......@@ -184,6 +197,9 @@ struct TextureState final : private angle::NonCopyable
GLenum mGenerateMipmapHint;
InitState mInitState;
mutable SamplerFormat mCachedSamplerFormat;
mutable bool mCachedSamplerFormatValid;
};
bool operator==(const TextureState &a, const TextureState &b);
......
......@@ -2727,6 +2727,11 @@ const char *ValidateDrawStates(Context *context)
// Do some additonal WebGL-specific validation
if (extensions.webglCompatibility)
{
if (!state.validateSamplerFormats())
{
return kSamplerFormatMismatch;
}
const TransformFeedback *transformFeedbackObject = state.getCurrentTransformFeedback();
if (transformFeedbackObject != nullptr && transformFeedbackObject->isActive() &&
transformFeedbackObject->buffersBoundForOtherUse())
......
......@@ -3075,6 +3075,67 @@ void main()
EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Simultaneous pack buffer binding should fail";
}
// Test sampler format validation caching works.
TEST_P(WebGL2ValidationStateChangeTest, SamplerFormatCache)
{
constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform sampler2D sampler;
out vec4 colorOut;
void main()
{
colorOut = texture(sampler, vec2(0));
})";
std::vector<std::string> tfVaryings = {"gl_Position"};
ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
glUseProgram(program);
std::array<GLColor, 4> colors = {
{GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
const auto &quadVertices = GetQuadVertices();
GLBuffer arrayBuffer;
glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer);
glBufferData(GL_ARRAY_BUFFER, quadVertices.size() * sizeof(Vector3), quadVertices.data(),
GL_STATIC_DRAW);
GLint samplerLoc = glGetUniformLocation(program, "sampler");
ASSERT_NE(-1, samplerLoc);
glUniform1i(samplerLoc, 0);
GLint positionLoc = glGetAttribLocation(program, essl3_shaders::PositionAttrib());
ASSERT_NE(-1, positionLoc);
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(positionLoc);
ASSERT_GL_NO_ERROR();
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR();
// TexImage2D should update the sampler format cache.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 2, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE,
colors.data());
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Sampling integer texture with a float sampler.";
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 2, 2, 0, GL_DEPTH_COMPONENT,
GL_UNSIGNED_INT, colors.data());
glDrawArrays(GL_TRIANGLES, 0, 6);
ASSERT_GL_NO_ERROR() << "Depth texture with no compare mode.";
// TexParameteri should update the sampler format cache.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glDrawArrays(GL_TRIANGLES, 0, 6);
EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Depth texture with compare mode set.";
}
// Tests that we retain the correct draw mode settings with transform feedback changes.
TEST_P(ValidationStateChangeTest, TransformFeedbackDrawModes)
{
......
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