Commit 46eaa946 by Sami Väisänen Committed by Commit Bot

Support CHROMIUM_path_rendering fragment operations

This brings two new APIs, BindFragmentInputLocation and ProgramPathFragmentInputGen that together dictate how the fragment shader varyings are used. BUG=angleproject:1382 Change-Id: I4b52fd8a3555235a73aecd4f3dba2d500789cbb0 Reviewed-on: https://chromium-review.googlesource.com/357071Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Reviewed-by: 's avatarSami Väisänen <svaisanen@nvidia.com> Commit-Queue: Sami Väisänen <svaisanen@nvidia.com>
parent 5b130489
...@@ -971,6 +971,15 @@ typedef void(GL_APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDCHROMIUMPROC)( ...@@ -971,6 +971,15 @@ typedef void(GL_APIENTRYP PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDCHROMIUMPROC)(
GLenum coverMode, GLenum coverMode,
GLenum transformType, GLenum transformType,
const GLfloat *transformValues); const GLfloat *transformValues);
typedef void(GL_APIENTRY PFNGLBINDFRAGMENTINPUTLOCATIONCHROMIUMPROC)(GLuint program,
GLint location,
const GLchar *name);
typedef void(GL_APIENTRYP PFNGLPROGRAMPATHFRAGMENTINPUTGENCHROMIUMPROC)(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs);
#ifdef GL_GLEXT_PROTOTYPES #ifdef GL_GLEXT_PROTOTYPES
GL_APICALL void GL_APIENTRY glMatrixLoadfCHROMIUM(GLenum matrixMode, const GLfloat *m); GL_APICALL void GL_APIENTRY glMatrixLoadfCHROMIUM(GLenum matrixMode, const GLfloat *m);
GL_APICALL void GL_APIENTRY glMatrixLoadIdentityCHROMIUM(GLenum matrixMode); GL_APICALL void GL_APIENTRY glMatrixLoadIdentityCHROMIUM(GLenum matrixMode);
...@@ -1052,6 +1061,15 @@ glStencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths, ...@@ -1052,6 +1061,15 @@ glStencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths,
GLenum transformType, GLenum transformType,
const GLfloat *transformValues); const GLfloat *transformValues);
GL_APICALL void GL_APIENTRY glBindFragmentInputLocationCHROMIUM(GLuint program,
GLint location,
const GLchar *name);
GL_APICALL void GL_APIENTRY glProgramPathFragmentInputGenCHROMIUM(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs);
#endif #endif
#endif /* GL_CHROMIUM_path_rendering */ #endif /* GL_CHROMIUM_path_rendering */
......
...@@ -34,10 +34,10 @@ class NonCopyable ...@@ -34,10 +34,10 @@ class NonCopyable
}; };
extern const uintptr_t DirtyPointer; extern const uintptr_t DirtyPointer;
} } // namespace angle
template <typename T, size_t N> template <typename T, size_t N>
inline size_t ArraySize(T(&)[N]) constexpr inline size_t ArraySize(T (&)[N])
{ {
return N; return N;
} }
......
...@@ -9,8 +9,9 @@ ...@@ -9,8 +9,9 @@
#include "string_utils.h" #include "string_utils.h"
#include <algorithm>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
...@@ -157,4 +158,25 @@ Optional<std::vector<wchar_t>> WidenString(size_t length, const char *cString) ...@@ -157,4 +158,25 @@ Optional<std::vector<wchar_t>> WidenString(size_t length, const char *cString)
return Optional<std::vector<wchar_t>>(wcstring); return Optional<std::vector<wchar_t>>(wcstring);
} }
bool BeginsWith(const std::string &str, const char *prefix)
{
return strncmp(str.c_str(), prefix, strlen(prefix)) == 0;
}
bool BeginsWith(const char *str, const char *prefix)
{
return strncmp(str, prefix, strlen(prefix)) == 0;
}
bool EndsWith(const std::string &str, const char *suffix)
{
const auto len = strlen(suffix);
if (len > str.size())
return false;
const char *end = str.c_str() + str.size() - len;
return memcmp(end, suffix, len) == 0;
}
} // namespace angle } // namespace angle
...@@ -47,6 +47,21 @@ bool HexStringToUInt(const std::string &input, unsigned int *uintOut); ...@@ -47,6 +47,21 @@ bool HexStringToUInt(const std::string &input, unsigned int *uintOut);
bool ReadFileToString(const std::string &path, std::string *stringOut); bool ReadFileToString(const std::string &path, std::string *stringOut);
Optional<std::vector<wchar_t>> WidenString(size_t length, const char *cString); Optional<std::vector<wchar_t>> WidenString(size_t length, const char *cString);
// Check if the string str begins with the given prefix.
// Prefix may not be NULL and needs to be NULL terminated.
// The comparison is case sensitive.
bool BeginsWith(const std::string &str, const char *prefix);
// Check if the string str begins with the given prefix.
// str and prefix may not be NULL and need to be NULL terminated.
// The comparison is case sensitive.
bool BeginsWith(const char *str, const char *prefix);
// Check if the string str ends with the given suffix.
// Suffix may not be NUL and needs to be NULL terminated.
// The comparison is case sensitive.
bool EndsWith(const std::string& str, const char* suffix);
} }
#endif // LIBANGLE_STRING_UTILS_H_ #endif // LIBANGLE_STRING_UTILS_H_
...@@ -138,4 +138,26 @@ TEST(StringUtilsTest, HexStringToUIntBasic) ...@@ -138,4 +138,26 @@ TEST(StringUtilsTest, HexStringToUIntBasic)
// Note: ReadFileToString is harder to test // Note: ReadFileToString is harder to test
TEST(StringUtilsTest, BeginsEndsWith)
{
ASSERT_FALSE(BeginsWith("foo", "bar"));
ASSERT_FALSE(BeginsWith("", "foo"));
ASSERT_FALSE(BeginsWith("foo", "foobar"));
ASSERT_TRUE(BeginsWith("foobar", "foo"));
ASSERT_TRUE(BeginsWith("foobar", ""));
ASSERT_TRUE(BeginsWith("foo", "foo"));
ASSERT_TRUE(BeginsWith("", ""));
ASSERT_FALSE(EndsWith("foo", "bar"));
ASSERT_FALSE(EndsWith("", "bar"));
ASSERT_FALSE(EndsWith("foo", "foobar"));
ASSERT_TRUE(EndsWith("foobar", "bar"));
ASSERT_TRUE(EndsWith("foobar", ""));
ASSERT_TRUE(EndsWith("bar", "bar"));
ASSERT_TRUE(EndsWith("", ""));
}
} }
\ No newline at end of file
...@@ -1540,6 +1540,7 @@ void Context::coverFillPathInstanced(GLsizei numPaths, ...@@ -1540,6 +1540,7 @@ void Context::coverFillPathInstanced(GLsizei numPaths,
mImplementation->coverFillPathInstanced(pathObjects, coverMode, transformType, transformValues); mImplementation->coverFillPathInstanced(pathObjects, coverMode, transformType, transformValues);
} }
void Context::coverStrokePathInstanced(GLsizei numPaths, void Context::coverStrokePathInstanced(GLsizei numPaths,
GLenum pathNameType, GLenum pathNameType,
const void *paths, const void *paths,
...@@ -1557,6 +1558,7 @@ void Context::coverStrokePathInstanced(GLsizei numPaths, ...@@ -1557,6 +1558,7 @@ void Context::coverStrokePathInstanced(GLsizei numPaths,
mImplementation->coverStrokePathInstanced(pathObjects, coverMode, transformType, mImplementation->coverStrokePathInstanced(pathObjects, coverMode, transformType,
transformValues); transformValues);
} }
void Context::stencilFillPathInstanced(GLsizei numPaths, void Context::stencilFillPathInstanced(GLsizei numPaths,
GLenum pathNameType, GLenum pathNameType,
const void *paths, const void *paths,
...@@ -1575,6 +1577,7 @@ void Context::stencilFillPathInstanced(GLsizei numPaths, ...@@ -1575,6 +1577,7 @@ void Context::stencilFillPathInstanced(GLsizei numPaths,
mImplementation->stencilFillPathInstanced(pathObjects, fillMode, mask, transformType, mImplementation->stencilFillPathInstanced(pathObjects, fillMode, mask, transformType,
transformValues); transformValues);
} }
void Context::stencilStrokePathInstanced(GLsizei numPaths, void Context::stencilStrokePathInstanced(GLsizei numPaths,
GLenum pathNameType, GLenum pathNameType,
const void *paths, const void *paths,
...@@ -1593,6 +1596,7 @@ void Context::stencilStrokePathInstanced(GLsizei numPaths, ...@@ -1593,6 +1596,7 @@ void Context::stencilStrokePathInstanced(GLsizei numPaths,
mImplementation->stencilStrokePathInstanced(pathObjects, reference, mask, transformType, mImplementation->stencilStrokePathInstanced(pathObjects, reference, mask, transformType,
transformValues); transformValues);
} }
void Context::stencilThenCoverFillPathInstanced(GLsizei numPaths, void Context::stencilThenCoverFillPathInstanced(GLsizei numPaths,
GLenum pathNameType, GLenum pathNameType,
const void *paths, const void *paths,
...@@ -1612,6 +1616,7 @@ void Context::stencilThenCoverFillPathInstanced(GLsizei numPaths, ...@@ -1612,6 +1616,7 @@ void Context::stencilThenCoverFillPathInstanced(GLsizei numPaths,
mImplementation->stencilThenCoverFillPathInstanced(pathObjects, coverMode, fillMode, mask, mImplementation->stencilThenCoverFillPathInstanced(pathObjects, coverMode, fillMode, mask,
transformType, transformValues); transformType, transformValues);
} }
void Context::stencilThenCoverStrokePathInstanced(GLsizei numPaths, void Context::stencilThenCoverStrokePathInstanced(GLsizei numPaths,
GLenum pathNameType, GLenum pathNameType,
const void *paths, const void *paths,
...@@ -1632,6 +1637,24 @@ void Context::stencilThenCoverStrokePathInstanced(GLsizei numPaths, ...@@ -1632,6 +1637,24 @@ void Context::stencilThenCoverStrokePathInstanced(GLsizei numPaths,
transformType, transformValues); transformType, transformValues);
} }
void Context::bindFragmentInputLocation(GLuint program, GLint location, const GLchar *name)
{
auto *programObject = getProgram(program);
programObject->bindFragmentInputLocation(location, name);
}
void Context::programPathFragmentInputGen(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
auto *programObject = getProgram(program);
programObject->pathFragmentInputGen(location, genMode, components, coeffs);
}
void Context::handleError(const Error &error) void Context::handleError(const Error &error)
{ {
if (error.isError()) if (error.isError())
......
...@@ -539,6 +539,12 @@ class Context final : public ValidationContext ...@@ -539,6 +539,12 @@ class Context final : public ValidationContext
GLenum coverMode, GLenum coverMode,
GLenum transformType, GLenum transformType,
const GLfloat *transformValues); const GLfloat *transformValues);
void bindFragmentInputLocation(GLuint program, GLint location, const GLchar *name);
void programPathFragmentInputGen(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs);
void handleError(const Error &error) override; void handleError(const Error &error) override;
......
...@@ -440,6 +440,82 @@ void Program::bindUniformLocation(GLuint index, const char *name) ...@@ -440,6 +440,82 @@ void Program::bindUniformLocation(GLuint index, const char *name)
mUniformBindings.bindLocation(index, ParseUniformName(name, nullptr)); mUniformBindings.bindLocation(index, ParseUniformName(name, nullptr));
} }
void Program::bindFragmentInputLocation(GLint index, const char *name)
{
mFragmentInputBindings.bindLocation(index, name);
}
BindingInfo Program::getFragmentInputBindingInfo(GLint index) const
{
BindingInfo ret;
ret.type = GL_NONE;
ret.valid = false;
const Shader *fragmentShader = mState.getAttachedFragmentShader();
ASSERT(fragmentShader);
// Find the actual fragment shader varying we're interested in
const std::vector<sh::Varying> &inputs = fragmentShader->getVaryings();
for (const auto &binding : mFragmentInputBindings)
{
if (binding.second != static_cast<GLuint>(index))
continue;
ret.valid = true;
std::string originalName = binding.first;
unsigned int index = ParseAndStripArrayIndex(&originalName);
for (const auto &in : inputs)
{
if (in.name == originalName)
{
if (in.isArray())
{
// The client wants to bind either "name" or "name[0]".
// GL ES 3.1 spec refers to active array names with language such as:
// "if the string identifies the base name of an active array, where the
// string would exactly match the name of the variable if the suffix "[0]"
// were appended to the string".
if (index == GL_INVALID_INDEX)
index = 0;
ret.name = in.mappedName + "[" + std::to_string(index) + "]";
}
else
{
ret.name = in.mappedName;
}
ret.type = in.type;
return ret;
}
}
}
return ret;
}
void Program::pathFragmentInputGen(GLint index,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
// If the location is -1 then the command is silently ignored
if (index == -1)
return;
const auto &binding = getFragmentInputBindingInfo(index);
// If the input doesn't exist then then the command is silently ignored
// This could happen through optimization for example, the shader translator
// decides that a variable is not actually being used and optimizes it away.
if (binding.name.empty())
return;
mProgram->setPathFragmentInputGen(binding.name, genMode, components, coeffs);
}
// Links the HLSL code of the vertex and pixel shader by matching up their varyings, // Links the HLSL code of the vertex and pixel shader by matching up their varyings,
// compiling them into binaries, determining the attribute mappings, and collecting // compiling them into binaries, determining the attribute mappings, and collecting
// a list of uniforms // a list of uniforms
...@@ -1623,16 +1699,17 @@ GLenum Program::getTransformFeedbackBufferMode() const ...@@ -1623,16 +1699,17 @@ GLenum Program::getTransformFeedbackBufferMode() const
return mState.mTransformFeedbackBufferMode; return mState.mTransformFeedbackBufferMode;
} }
// static
bool Program::linkVaryings(InfoLog &infoLog, bool Program::linkVaryings(InfoLog &infoLog,
const Shader *vertexShader, const Shader *vertexShader,
const Shader *fragmentShader) const Shader *fragmentShader) const
{ {
ASSERT(vertexShader->getShaderVersion() == fragmentShader->getShaderVersion()); ASSERT(vertexShader->getShaderVersion() == fragmentShader->getShaderVersion());
const std::vector<sh::Varying> &vertexVaryings = vertexShader->getVaryings(); const std::vector<sh::Varying> &vertexVaryings = vertexShader->getVaryings();
const std::vector<sh::Varying> &fragmentVaryings = fragmentShader->getVaryings(); const std::vector<sh::Varying> &fragmentVaryings = fragmentShader->getVaryings();
std::map<GLuint, std::string> staticFragmentInputLocations;
for (const sh::Varying &output : fragmentVaryings) for (const sh::Varying &output : fragmentVaryings)
{ {
bool matched = false; bool matched = false;
...@@ -1665,6 +1742,29 @@ bool Program::linkVaryings(InfoLog &infoLog, ...@@ -1665,6 +1742,29 @@ bool Program::linkVaryings(InfoLog &infoLog,
infoLog << "Fragment varying " << output.name << " does not match any vertex varying"; infoLog << "Fragment varying " << output.name << " does not match any vertex varying";
return false; return false;
} }
// Check for aliased path rendering input bindings (if any).
// If more than one binding refer statically to the same
// location the link must fail.
if (!output.staticUse)
continue;
const auto inputBinding = mFragmentInputBindings.getBinding(output.name);
if (inputBinding == -1)
continue;
const auto it = staticFragmentInputLocations.find(inputBinding);
if (it == std::end(staticFragmentInputLocations))
{
staticFragmentInputLocations.insert(std::make_pair(inputBinding, output.name));
}
else
{
infoLog << "Binding for fragment input " << output.name << " conflicts with "
<< it->second;
return false;
}
} }
// TODO(jmadill): verify no unmatched vertex varyings? // TODO(jmadill): verify no unmatched vertex varyings?
......
...@@ -137,6 +137,24 @@ struct VariableLocation ...@@ -137,6 +137,24 @@ struct VariableLocation
bool ignored; bool ignored;
}; };
// Information about a variable binding.
// Currently used by CHROMIUM_path_rendering
struct BindingInfo
{
// The type of binding, for example GL_FLOAT_VEC3.
// This can be GL_NONE if the variable is optimized away.
GLenum type;
// This is the name of the variable in
// the translated shader program. Note that
// this can be empty in the case where the
// variable has been optimized away.
std::string name;
// True if the binding is valid, otherwise false.
bool valid;
};
class ProgramState final : angle::NonCopyable class ProgramState final : angle::NonCopyable
{ {
public: public:
...@@ -229,6 +247,14 @@ class Program final : angle::NonCopyable, public LabeledObject ...@@ -229,6 +247,14 @@ class Program final : angle::NonCopyable, public LabeledObject
void bindAttributeLocation(GLuint index, const char *name); void bindAttributeLocation(GLuint index, const char *name);
void bindUniformLocation(GLuint index, const char *name); void bindUniformLocation(GLuint index, const char *name);
// CHROMIUM_path_rendering
BindingInfo getFragmentInputBindingInfo(GLint index) const;
void bindFragmentInputLocation(GLint index, const char *name);
void pathFragmentInputGen(GLint index,
GLenum genMode,
GLint components,
const GLfloat *coeffs);
Error link(const ContextState &data); Error link(const ContextState &data);
bool isLinked() const; bool isLinked() const;
...@@ -352,9 +378,7 @@ class Program final : angle::NonCopyable, public LabeledObject ...@@ -352,9 +378,7 @@ class Program final : angle::NonCopyable, public LabeledObject
const Bindings &attributeBindings, const Bindings &attributeBindings,
const Shader *vertexShader); const Shader *vertexShader);
bool linkUniformBlocks(InfoLog &infoLog, const Caps &caps); bool linkUniformBlocks(InfoLog &infoLog, const Caps &caps);
static bool linkVaryings(InfoLog &infoLog, bool linkVaryings(InfoLog &infoLog, const Shader *vertexShader, const Shader *fragmentShader) const;
const Shader *vertexShader,
const Shader *fragmentShader);
bool linkUniforms(gl::InfoLog &infoLog, const gl::Caps &caps, const Bindings &uniformBindings); bool linkUniforms(gl::InfoLog &infoLog, const gl::Caps &caps, const Bindings &uniformBindings);
bool indexUniforms(gl::InfoLog &infoLog, const gl::Caps &caps, const Bindings &uniformBindings); bool indexUniforms(gl::InfoLog &infoLog, const gl::Caps &caps, const Bindings &uniformBindings);
bool areMatchingInterfaceBlocks(gl::InfoLog &infoLog, const sh::InterfaceBlock &vertexInterfaceBlock, bool areMatchingInterfaceBlocks(gl::InfoLog &infoLog, const sh::InterfaceBlock &vertexInterfaceBlock,
...@@ -430,6 +454,9 @@ class Program final : angle::NonCopyable, public LabeledObject ...@@ -430,6 +454,9 @@ class Program final : angle::NonCopyable, public LabeledObject
Bindings mAttributeBindings; Bindings mAttributeBindings;
Bindings mUniformBindings; Bindings mUniformBindings;
// CHROMIUM_path_rendering
Bindings mFragmentInputBindings;
bool mLinked; bool mLinked;
bool mDeleteStatus; // Flag to indicate that the program can be deleted when no longer in use bool mDeleteStatus; // Flag to indicate that the program can be deleted when no longer in use
......
...@@ -79,6 +79,12 @@ class ProgramImpl : angle::NonCopyable ...@@ -79,6 +79,12 @@ class ProgramImpl : angle::NonCopyable
// Returns false for inactive members. // Returns false for inactive members.
virtual bool getUniformBlockMemberInfo(const std::string &memberUniformName, virtual bool getUniformBlockMemberInfo(const std::string &memberUniformName,
sh::BlockMemberInfo *memberInfoOut) const = 0; sh::BlockMemberInfo *memberInfoOut) const = 0;
// CHROMIUM_path_rendering
// Set parameters to control fragment shader input variable interpolation
virtual void setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs) = 0;
protected: protected:
const gl::ProgramState &mState; const gl::ProgramState &mState;
......
...@@ -56,6 +56,8 @@ class MockProgramImpl : public rx::ProgramImpl ...@@ -56,6 +56,8 @@ class MockProgramImpl : public rx::ProgramImpl
MOCK_METHOD2(setUniformBlockBinding, void(GLuint, GLuint)); MOCK_METHOD2(setUniformBlockBinding, void(GLuint, GLuint));
MOCK_CONST_METHOD2(getUniformBlockSize, bool(const std::string &, size_t *)); MOCK_CONST_METHOD2(getUniformBlockSize, bool(const std::string &, size_t *));
MOCK_CONST_METHOD2(getUniformBlockMemberInfo, bool(const std::string &, sh::BlockMemberInfo *)); MOCK_CONST_METHOD2(getUniformBlockMemberInfo, bool(const std::string &, sh::BlockMemberInfo *));
MOCK_METHOD4(setPathFragmentInputGen,
void(const std::string &, GLenum, GLint, const GLfloat *));
MOCK_METHOD0(destroy, void()); MOCK_METHOD0(destroy, void());
}; };
......
...@@ -2309,4 +2309,13 @@ bool ProgramD3D::getUniformBlockMemberInfo(const std::string &memberUniformName, ...@@ -2309,4 +2309,13 @@ bool ProgramD3D::getUniformBlockMemberInfo(const std::string &memberUniformName,
*memberInfoOut = infoIter->second; *memberInfoOut = infoIter->second;
return true; return true;
} }
void ProgramD3D::setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
UNREACHABLE();
}
} // namespace rx } // namespace rx
...@@ -174,6 +174,10 @@ class ProgramD3D : public ProgramImpl ...@@ -174,6 +174,10 @@ class ProgramD3D : public ProgramImpl
bool getUniformBlockSize(const std::string &blockName, size_t *sizeOut) const override; bool getUniformBlockSize(const std::string &blockName, size_t *sizeOut) const override;
bool getUniformBlockMemberInfo(const std::string &memberUniformName, bool getUniformBlockMemberInfo(const std::string &memberUniformName,
sh::BlockMemberInfo *memberInfoOut) const override; sh::BlockMemberInfo *memberInfoOut) const override;
void setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs) override;
void initializeUniformStorage(); void initializeUniformStorage();
gl::Error applyUniforms(GLenum drawMode); gl::Error applyUniforms(GLenum drawMode);
......
...@@ -56,7 +56,8 @@ ShaderImpl *ContextGL::createShader(const gl::ShaderState &data) ...@@ -56,7 +56,8 @@ ShaderImpl *ContextGL::createShader(const gl::ShaderState &data)
ProgramImpl *ContextGL::createProgram(const gl::ProgramState &data) ProgramImpl *ContextGL::createProgram(const gl::ProgramState &data)
{ {
return new ProgramGL(data, getFunctions(), getWorkaroundsGL(), getStateManager()); return new ProgramGL(data, getFunctions(), getWorkaroundsGL(), getStateManager(),
getExtensions().pathRendering);
} }
FramebufferImpl *ContextGL::createFramebuffer(const gl::FramebufferState &data) FramebufferImpl *ContextGL::createFramebuffer(const gl::FramebufferState &data)
......
...@@ -439,6 +439,7 @@ FunctionsGL::FunctionsGL() ...@@ -439,6 +439,7 @@ FunctionsGL::FunctionsGL()
stencilStrokePathInstancedNV(nullptr), stencilStrokePathInstancedNV(nullptr),
stencilThenCoverFillPathInstancedNV(nullptr), stencilThenCoverFillPathInstancedNV(nullptr),
stencilThenCoverStrokePathInstancedNV(nullptr), stencilThenCoverStrokePathInstancedNV(nullptr),
programPathFragmentInputGenNV(nullptr),
bindFragDataLocationIndexed(nullptr), bindFragDataLocationIndexed(nullptr),
bindSampler(nullptr), bindSampler(nullptr),
...@@ -846,10 +847,13 @@ void FunctionsGL::initializeProcsDesktopGL() ...@@ -846,10 +847,13 @@ void FunctionsGL::initializeProcsDesktopGL()
// Even though extensions are written against specific versions of GL, many drivers expose the extensions // Even though extensions are written against specific versions of GL, many drivers expose the extensions
// in even older versions. Always try loading the extensions regardless of GL version. // in even older versions. Always try loading the extensions regardless of GL version.
// EXT_direct_state_access (loading only functions relevant to GL_NV_path_rendering here) // GL_ARB_program_interface_query (loading only functions relevant to GL_NV_path_rendering here)
AssignGLExtensionEntryPoint(extensions, "GL_EXT_direct_state_access", loadProcAddress("glMatrixLoadfEXT"), &matrixLoadEXT); AssignGLExtensionEntryPoint(extensions, "GL_ARB_program_interface_query", loadProcAddress("glGetProgramInterfaceiv"), &getProgramInterfaceiv);
AssignGLExtensionEntryPoint(extensions, "GL_ARB_program_interface_query", loadProcAddress("glGetProgramResourceName"), &getProgramResourceName);
AssignGLExtensionEntryPoint(extensions, "GL_ARB_program_interface_query", loadProcAddress("glGetProgramResourceiv"), &getProgramResourceiv);
// GL_NV_path_rendering // GL_NV_path_rendering
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glMatrixLoadfEXT"), &matrixLoadEXT);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glGenPathsNV"), &genPathsNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glGenPathsNV"), &genPathsNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glDeletePathsNV"), &delPathsNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glDeletePathsNV"), &delPathsNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glPathCommandsNV"), &pathCommandsNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glPathCommandsNV"), &pathCommandsNV);
...@@ -871,7 +875,7 @@ void FunctionsGL::initializeProcsDesktopGL() ...@@ -871,7 +875,7 @@ void FunctionsGL::initializeProcsDesktopGL()
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilStrokePathInstancedNV"), &stencilStrokePathInstancedNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilStrokePathInstancedNV"), &stencilStrokePathInstancedNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverFillPathInstancedNV"), &stencilThenCoverFillPathInstancedNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverFillPathInstancedNV"), &stencilThenCoverFillPathInstancedNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverStrokePathInstancedNV"), &stencilThenCoverStrokePathInstancedNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverStrokePathInstancedNV"), &stencilThenCoverStrokePathInstancedNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glProgramPathFragmentInputGenNV"), &programPathFragmentInputGenNV);
// GL_NV_framebuffer_mixed_samples // GL_NV_framebuffer_mixed_samples
AssignGLExtensionEntryPoint(extensions, "GL_NV_framebuffer_mixed_samples", loadProcAddress("glCoverageModulationNV"), &coverageModulationNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_framebuffer_mixed_samples", loadProcAddress("glCoverageModulationNV"), &coverageModulationNV);
...@@ -1771,10 +1775,9 @@ void FunctionsGL::initializeProcsGLES() ...@@ -1771,10 +1775,9 @@ void FunctionsGL::initializeProcsGLES()
profile = 0; profile = 0;
// clang-format off // clang-format off
// EXT_direct_state_access (loading only functions relevant to GL_NV_path_rendering here)
AssignGLExtensionEntryPoint(extensions, "GL_EXT_direct_state_access", loadProcAddress("glMatrixLoadfEXT"), &matrixLoadEXT);
// GL_NV_path_rendering // GL_NV_path_rendering
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glMatrixLoadfEXT"), &matrixLoadEXT);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glGenPathsNV"), &genPathsNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glGenPathsNV"), &genPathsNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glDeletePathsNV"), &delPathsNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glDeletePathsNV"), &delPathsNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glPathCommandsNV"), &pathCommandsNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glPathCommandsNV"), &pathCommandsNV);
...@@ -1796,7 +1799,7 @@ void FunctionsGL::initializeProcsGLES() ...@@ -1796,7 +1799,7 @@ void FunctionsGL::initializeProcsGLES()
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilStrokePathInstancedNV"), &stencilStrokePathInstancedNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilStrokePathInstancedNV"), &stencilStrokePathInstancedNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverFillPathInstancedNV"), &stencilThenCoverFillPathInstancedNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverFillPathInstancedNV"), &stencilThenCoverFillPathInstancedNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverStrokePathInstancedNV"), &stencilThenCoverStrokePathInstancedNV); AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glStencilThenCoverStrokePathInstancedNV"), &stencilThenCoverStrokePathInstancedNV);
AssignGLExtensionEntryPoint(extensions, "GL_NV_path_rendering", loadProcAddress("glProgramPathFragmentInputGenNV"), &programPathFragmentInputGenNV);
// GL_OES_texture_3D // GL_OES_texture_3D
AssignGLExtensionEntryPoint(extensions, "GL_OES_texture_3D", loadProcAddress("glTexImage3DOES"), &texImage3D); AssignGLExtensionEntryPoint(extensions, "GL_OES_texture_3D", loadProcAddress("glTexImage3DOES"), &texImage3D);
......
...@@ -392,10 +392,8 @@ class FunctionsGL ...@@ -392,10 +392,8 @@ class FunctionsGL
PFNGLTEXIMAGE3DMULTISAMPLEPROC texImage3DMultisample; PFNGLTEXIMAGE3DMULTISAMPLEPROC texImage3DMultisample;
PFNGLWAITSYNCPROC waitSync; PFNGLWAITSYNCPROC waitSync;
// EXT_direct_state_access. Needed by NV_path_rendering.
PFNGLMATRIXLOADFEXTPROC matrixLoadEXT;
// NV_path_rendering (originally written against 3.2 compatibility profile) // NV_path_rendering (originally written against 3.2 compatibility profile)
PFNGLMATRIXLOADFEXTPROC matrixLoadEXT;
PFNGLGENPATHSNVPROC genPathsNV; PFNGLGENPATHSNVPROC genPathsNV;
PFNGLDELETEPATHSNVPROC delPathsNV; PFNGLDELETEPATHSNVPROC delPathsNV;
PFNGLPATHCOMMANDSNVPROC pathCommandsNV; PFNGLPATHCOMMANDSNVPROC pathCommandsNV;
...@@ -405,20 +403,19 @@ class FunctionsGL ...@@ -405,20 +403,19 @@ class FunctionsGL
PFNGLGETPATHPARAMETERFVNVPROC getPathParameterfNV; PFNGLGETPATHPARAMETERFVNVPROC getPathParameterfNV;
PFNGLGETPATHPARAMETERIVNVPROC getPathParameteriNV; PFNGLGETPATHPARAMETERIVNVPROC getPathParameteriNV;
PFNGLPATHSTENCILFUNCNVPROC pathStencilFuncNV; PFNGLPATHSTENCILFUNCNVPROC pathStencilFuncNV;
PFNGLSTENCILFILLPATHNVPROC stencilFillPathNV; PFNGLSTENCILFILLPATHNVPROC stencilFillPathNV;
PFNGLSTENCILSTROKEPATHNVPROC stencilStrokePathNV; PFNGLSTENCILSTROKEPATHNVPROC stencilStrokePathNV;
PFNGLCOVERFILLPATHNVPROC coverFillPathNV; PFNGLCOVERFILLPATHNVPROC coverFillPathNV;
PFNGLCOVERSTROKEPATHNVPROC coverStrokePathNV; PFNGLCOVERSTROKEPATHNVPROC coverStrokePathNV;
PFNGLSTENCILTHENCOVERFILLPATHNVPROC stencilThenCoverFillPathNV; PFNGLSTENCILTHENCOVERFILLPATHNVPROC stencilThenCoverFillPathNV;
PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC stencilThenCoverStrokePathNV; PFNGLSTENCILTHENCOVERSTROKEPATHNVPROC stencilThenCoverStrokePathNV;
PFNGLCOVERFILLPATHINSTANCEDNVPROC coverFillPathInstancedNV; PFNGLCOVERFILLPATHINSTANCEDNVPROC coverFillPathInstancedNV;
PFNGLCOVERSTROKEPATHINSTANCEDNVPROC coverStrokePathInstancedNV; PFNGLCOVERSTROKEPATHINSTANCEDNVPROC coverStrokePathInstancedNV;
PFNGLSTENCILFILLPATHINSTANCEDNVPROC stencilFillPathInstancedNV; PFNGLSTENCILFILLPATHINSTANCEDNVPROC stencilFillPathInstancedNV;
PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC stencilStrokePathInstancedNV; PFNGLSTENCILSTROKEPATHINSTANCEDNVPROC stencilStrokePathInstancedNV;
PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC stencilThenCoverFillPathInstancedNV; PFNGLSTENCILTHENCOVERFILLPATHINSTANCEDNVPROC stencilThenCoverFillPathInstancedNV;
PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC stencilThenCoverStrokePathInstancedNV; PFNGLSTENCILTHENCOVERSTROKEPATHINSTANCEDNVPROC stencilThenCoverStrokePathInstancedNV;
PFNGLPROGRAMPATHFRAGMENTINPUTGENNVPROC programPathFragmentInputGenNV;
// 3.3 // 3.3
PFNGLBINDFRAGDATALOCATIONINDEXEDPROC bindFragDataLocationIndexed; PFNGLBINDFRAGDATALOCATIONINDEXEDPROC bindFragDataLocationIndexed;
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include "libANGLE/renderer/gl/ProgramGL.h" #include "libANGLE/renderer/gl/ProgramGL.h"
#include "common/angleutils.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/string_utils.h"
#include "common/utilities.h" #include "common/utilities.h"
#include "libANGLE/renderer/gl/FunctionsGL.h" #include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/ShaderGL.h" #include "libANGLE/renderer/gl/ShaderGL.h"
...@@ -23,11 +25,13 @@ namespace rx ...@@ -23,11 +25,13 @@ namespace rx
ProgramGL::ProgramGL(const gl::ProgramState &data, ProgramGL::ProgramGL(const gl::ProgramState &data,
const FunctionsGL *functions, const FunctionsGL *functions,
const WorkaroundsGL &workarounds, const WorkaroundsGL &workarounds,
StateManagerGL *stateManager) StateManagerGL *stateManager,
bool enablePathRendering)
: ProgramImpl(data), : ProgramImpl(data),
mFunctions(functions), mFunctions(functions),
mWorkarounds(workarounds), mWorkarounds(workarounds),
mStateManager(stateManager), mStateManager(stateManager),
mEnablePathRendering(enablePathRendering),
mProgramID(0) mProgramID(0)
{ {
ASSERT(mFunctions); ASSERT(mFunctions);
...@@ -382,6 +386,26 @@ bool ProgramGL::getUniformBlockMemberInfo(const std::string &memberUniformName, ...@@ -382,6 +386,26 @@ bool ProgramGL::getUniformBlockMemberInfo(const std::string &memberUniformName,
return true; return true;
} }
void ProgramGL::setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
ASSERT(mEnablePathRendering);
for (const auto &input : mPathRenderingFragmentInputs)
{
if (input.name == inputName)
{
mFunctions->programPathFragmentInputGenNV(mProgramID, input.location, genMode,
components, coeffs);
ASSERT(mFunctions->getError() == GL_NO_ERROR);
return;
}
}
}
void ProgramGL::preLink() void ProgramGL::preLink()
{ {
// Reset the program state // Reset the program state
...@@ -389,6 +413,7 @@ void ProgramGL::preLink() ...@@ -389,6 +413,7 @@ void ProgramGL::preLink()
mUniformBlockRealLocationMap.clear(); mUniformBlockRealLocationMap.clear();
mSamplerBindings.clear(); mSamplerBindings.clear();
mUniformIndexToSamplerIndex.clear(); mUniformIndexToSamplerIndex.clear();
mPathRenderingFragmentInputs.clear();
} }
bool ProgramGL::checkLinkStatus(gl::InfoLog &infoLog) bool ProgramGL::checkLinkStatus(gl::InfoLog &infoLog)
...@@ -478,6 +503,70 @@ void ProgramGL::postLink() ...@@ -478,6 +503,70 @@ void ProgramGL::postLink()
samplerBinding.boundTextureUnits.resize(linkedUniform.elementCount(), 0); samplerBinding.boundTextureUnits.resize(linkedUniform.elementCount(), 0);
mSamplerBindings.push_back(samplerBinding); mSamplerBindings.push_back(samplerBinding);
} }
// Discover CHROMIUM_path_rendering fragment inputs if enabled.
if (!mEnablePathRendering)
return;
GLint numFragmentInputs = 0;
mFunctions->getProgramInterfaceiv(mProgramID, GL_FRAGMENT_INPUT_NV, GL_ACTIVE_RESOURCES,
&numFragmentInputs);
if (numFragmentInputs <= 0)
return;
GLint maxNameLength = 0;
mFunctions->getProgramInterfaceiv(mProgramID, GL_FRAGMENT_INPUT_NV, GL_MAX_NAME_LENGTH,
&maxNameLength);
ASSERT(maxNameLength);
for (GLint i = 0; i < numFragmentInputs; ++i)
{
std::string name;
name.resize(maxNameLength);
GLsizei nameLen = 0;
mFunctions->getProgramResourceName(mProgramID, GL_FRAGMENT_INPUT_NV, i, maxNameLength,
&nameLen, &name[0]);
name.resize(nameLen);
// Ignore built-ins
if (angle::BeginsWith(name, "gl_"))
continue;
const GLenum kQueryProperties[] = {GL_LOCATION, GL_ARRAY_SIZE};
GLint queryResults[ArraySize(kQueryProperties)];
GLsizei queryLength = 0;
mFunctions->getProgramResourceiv(mProgramID, GL_FRAGMENT_INPUT_NV, i,
ArraySize(kQueryProperties), kQueryProperties,
ArraySize(queryResults), &queryLength, queryResults);
ASSERT(queryLength == ArraySize(kQueryProperties));
PathRenderingFragmentInput input;
input.name = name;
input.location = queryResults[0];
mPathRenderingFragmentInputs.push_back(std::move(input));
// If the input is an array it's denoted by [0] suffix on the variable
// name. We'll then create an entry per each array index where index > 0
if (angle::EndsWith(name, "[0]"))
{
// drop the suffix
name.resize(name.size() - 3);
const auto arraySize = queryResults[1];
const auto baseLocation = queryResults[0];
for (GLint i = 1; i < arraySize; ++i)
{
PathRenderingFragmentInput input;
input.name = name + "[" + std::to_string(i) + "]";
input.location = baseLocation + i;
mPathRenderingFragmentInputs.push_back(std::move(input));
}
}
}
} }
} // namespace rx } // namespace rx
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
#ifndef LIBANGLE_RENDERER_GL_PROGRAMGL_H_ #ifndef LIBANGLE_RENDERER_GL_PROGRAMGL_H_
#define LIBANGLE_RENDERER_GL_PROGRAMGL_H_ #define LIBANGLE_RENDERER_GL_PROGRAMGL_H_
#include <string>
#include <vector>
#include "libANGLE/renderer/gl/WorkaroundsGL.h" #include "libANGLE/renderer/gl/WorkaroundsGL.h"
#include "libANGLE/renderer/ProgramImpl.h" #include "libANGLE/renderer/ProgramImpl.h"
...@@ -30,7 +33,8 @@ class ProgramGL : public ProgramImpl ...@@ -30,7 +33,8 @@ class ProgramGL : public ProgramImpl
ProgramGL(const gl::ProgramState &data, ProgramGL(const gl::ProgramState &data,
const FunctionsGL *functions, const FunctionsGL *functions,
const WorkaroundsGL &workarounds, const WorkaroundsGL &workarounds,
StateManagerGL *stateManager); StateManagerGL *stateManager,
bool enablePathRendering);
~ProgramGL() override; ~ProgramGL() override;
LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override; LinkResult load(gl::InfoLog &infoLog, gl::BinaryInputStream *stream) override;
...@@ -68,6 +72,11 @@ class ProgramGL : public ProgramImpl ...@@ -68,6 +72,11 @@ class ProgramGL : public ProgramImpl
bool getUniformBlockMemberInfo(const std::string &memberUniformName, bool getUniformBlockMemberInfo(const std::string &memberUniformName,
sh::BlockMemberInfo *memberInfoOut) const override; sh::BlockMemberInfo *memberInfoOut) const override;
void setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs) override;
GLuint getProgramID() const; GLuint getProgramID() const;
const std::vector<SamplerBindingGL> &getAppliedSamplerUniforms() const; const std::vector<SamplerBindingGL> &getAppliedSamplerUniforms() const;
...@@ -92,6 +101,15 @@ class ProgramGL : public ProgramImpl ...@@ -92,6 +101,15 @@ class ProgramGL : public ProgramImpl
// A map from a mData.getUniforms() index to a mSamplerBindings index. // A map from a mData.getUniforms() index to a mSamplerBindings index.
std::vector<size_t> mUniformIndexToSamplerIndex; std::vector<size_t> mUniformIndexToSamplerIndex;
struct PathRenderingFragmentInput
{
std::string name;
GLint location;
};
std::vector<PathRenderingFragmentInput> mPathRenderingFragmentInputs;
bool mEnablePathRendering;
GLuint mProgramID; GLuint mProgramID;
}; };
......
...@@ -682,13 +682,20 @@ void GenerateCaps(const FunctionsGL *functions, gl::Caps *caps, gl::TextureCapsM ...@@ -682,13 +682,20 @@ void GenerateCaps(const FunctionsGL *functions, gl::Caps *caps, gl::TextureCapsM
functions->hasGLExtension("GL_NV_framebuffer_mixed_samples") || functions->hasGLExtension("GL_NV_framebuffer_mixed_samples") ||
functions->hasGLESExtension("GL_NV_framebuffer_mixed_samples"); functions->hasGLESExtension("GL_NV_framebuffer_mixed_samples");
// if NV_path_rendering is to be supported then EXT_direct_state_access
// must also be available. NV_path_rendering needs some of the matrix loads // NV_path_rendering
// from this extension. // We also need interface query which is available in
extensions->pathRendering = (functions->hasGLExtension("GL_NV_path_rendering") && // >= 4.3 core or ARB_interface_query or >= GLES 3.1
functions->hasGLExtension("GL_EXT_direct_state_access")) || const bool canEnableGLPathRendering =
(functions->hasGLESExtension("GL_NV_path_rendering") && functions->hasGLExtension("GL_NV_path_rendering") &&
functions->hasGLESExtension("GL_EXT_direct_state_access")); (functions->hasGLExtension("GL_ARB_program_interface_query") ||
functions->isAtLeastGL(gl::Version(4, 3)));
const bool canEnableESPathRendering =
functions->hasGLESExtension("GL_NV_path_rendering") &&
functions->isAtLeastGLES(gl::Version(3, 1));
extensions->pathRendering = canEnableGLPathRendering || canEnableESPathRendering;
} }
void GenerateWorkarounds(const FunctionsGL *functions, WorkaroundsGL *workarounds) void GenerateWorkarounds(const FunctionsGL *functions, WorkaroundsGL *workarounds)
......
...@@ -201,4 +201,12 @@ bool ProgramVk::getUniformBlockMemberInfo(const std::string &memberUniformName, ...@@ -201,4 +201,12 @@ bool ProgramVk::getUniformBlockMemberInfo(const std::string &memberUniformName,
return bool(); return bool();
} }
void ProgramVk::setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
UNIMPLEMENTED();
}
} // namespace rx } // namespace rx
...@@ -88,6 +88,11 @@ class ProgramVk : public ProgramImpl ...@@ -88,6 +88,11 @@ class ProgramVk : public ProgramImpl
// Returns false for inactive members. // Returns false for inactive members.
bool getUniformBlockMemberInfo(const std::string &memberUniformName, bool getUniformBlockMemberInfo(const std::string &memberUniformName,
sh::BlockMemberInfo *memberInfoOut) const override; sh::BlockMemberInfo *memberInfoOut) const override;
void setPathFragmentInputGen(const std::string &inputName,
GLenum genMode,
GLint components,
const GLfloat *coeffs) override;
}; };
} // namespace rx } // namespace rx
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "libANGLE/Uniform.h" #include "libANGLE/Uniform.h"
#include "common/mathutil.h" #include "common/mathutil.h"
#include "common/string_utils.h"
#include "common/utilities.h" #include "common/utilities.h"
namespace gl namespace gl
...@@ -2874,4 +2875,146 @@ bool ValidateStencilThenCoverStrokePathInstanced(Context *context, ...@@ -2874,4 +2875,146 @@ bool ValidateStencilThenCoverStrokePathInstanced(Context *context,
return true; return true;
} }
bool ValidateBindFragmentInputLocation(Context *context,
GLuint program,
GLint location,
const GLchar *name)
{
if (!context->getExtensions().pathRendering)
{
context->handleError(
Error(GL_INVALID_OPERATION, "GL_CHROMIUM_path_rendering is not available."));
return false;
}
const GLint MaxLocation = context->getCaps().maxVaryingVectors * 4;
if (location >= MaxLocation)
{
context->handleError(Error(GL_INVALID_VALUE, "Location exceeds max varying."));
return false;
}
const auto *programObject = context->getProgram(program);
if (!programObject)
{
context->handleError(Error(GL_INVALID_OPERATION, "No such program."));
return false;
}
if (!name)
{
context->handleError(Error(GL_INVALID_VALUE, "No name given."));
return false;
}
if (angle::BeginsWith(name, "gl_"))
{
context->handleError(Error(GL_INVALID_OPERATION, "Cannot bind a built-in variable."));
return false;
}
return true;
}
bool ValidateProgramPathFragmentInputGen(Context *context,
GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
if (!context->getExtensions().pathRendering)
{
context->handleError(
Error(GL_INVALID_OPERATION, "GL_CHROMIUM_path_rendering is not available."));
return false;
}
const auto *programObject = context->getProgram(program);
if (!programObject || programObject->isFlaggedForDeletion())
{
context->handleError(Error(GL_INVALID_OPERATION, "No such program."));
return false;
}
if (!programObject->isLinked())
{
context->handleError(Error(GL_INVALID_OPERATION, "Program is not linked."));
return false;
}
switch (genMode)
{
case GL_NONE:
if (components != 0)
{
context->handleError(Error(GL_INVALID_VALUE, "Invalid components."));
return false;
}
break;
case GL_OBJECT_LINEAR_CHROMIUM:
case GL_EYE_LINEAR_CHROMIUM:
case GL_CONSTANT_CHROMIUM:
if (components < 1 || components > 4)
{
context->handleError(Error(GL_INVALID_VALUE, "Invalid components."));
return false;
}
if (!coeffs)
{
context->handleError(Error(GL_INVALID_VALUE, "No coefficients array given."));
return false;
}
break;
default:
context->handleError(Error(GL_INVALID_ENUM, "Invalid gen mode."));
return false;
}
// If the location is -1 then the command is silently ignored
// and no further validation is needed.
if (location == -1)
return true;
const auto &binding = programObject->getFragmentInputBindingInfo(location);
if (!binding.valid)
{
context->handleError(Error(GL_INVALID_OPERATION, "No such binding."));
return false;
}
if (binding.type != GL_NONE)
{
GLint expectedComponents = 0;
switch (binding.type)
{
case GL_FLOAT:
expectedComponents = 1;
break;
case GL_FLOAT_VEC2:
expectedComponents = 2;
break;
case GL_FLOAT_VEC3:
expectedComponents = 3;
break;
case GL_FLOAT_VEC4:
expectedComponents = 4;
break;
default:
context->handleError(Error(GL_INVALID_OPERATION,
"Fragment input type is not a floating point scalar or vector."));
return false;
}
if (expectedComponents != components && genMode != GL_NONE)
{
context->handleError(Error(GL_INVALID_OPERATION, "Unexpected number of components"));
return false;
}
}
return true;
}
} // namespace gl } // namespace gl
...@@ -273,6 +273,16 @@ bool ValidateStencilThenCoverStrokePathInstanced(Context *context, ...@@ -273,6 +273,16 @@ bool ValidateStencilThenCoverStrokePathInstanced(Context *context,
GLenum coverMode, GLenum coverMode,
GLenum transformType, GLenum transformType,
const GLfloat *transformValues); const GLfloat *transformValues);
bool ValidateBindFragmentInputLocation(Context *context,
GLuint program,
GLint location,
const GLchar *name);
bool ValidateProgramPathFragmentInputGen(Context *context,
GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs);
} // namespace gl } // namespace gl
......
...@@ -1847,4 +1847,47 @@ StencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths, ...@@ -1847,4 +1847,47 @@ StencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths,
} }
} }
ANGLE_EXPORT void GL_APIENTRY BindFragmentInputLocationCHROMIUM(GLuint program,
GLint location,
const GLchar *name)
{
EVENT("(GLuint program = %u, GLint location = %d, const GLchar *name = %p)", program, location,
name);
Context *context = GetValidGlobalContext();
if (context)
{
if (!context->skipValidation() &&
!ValidateBindFragmentInputLocation(context, program, location, name))
{
return;
}
context->bindFragmentInputLocation(program, location, name);
}
}
ANGLE_EXPORT void GL_APIENTRY ProgramPathFragmentInputGenCHROMIUM(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
EVENT(
"(GLuint program = %u, GLint location %d, GLenum genMode = %u, GLint components = %d, "
"const GLfloat * coeffs = %p)",
program, location, genMode, components, coeffs);
Context *context = GetValidGlobalContext();
if (context)
{
if (!context->skipValidation() &&
!ValidateProgramPathFragmentInputGen(context, program, location, genMode, components,
coeffs))
{
return;
}
context->programPathFragmentInputGen(program, location, genMode, components, coeffs);
}
}
} // gl } // gl
...@@ -229,6 +229,14 @@ StencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths, ...@@ -229,6 +229,14 @@ StencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths,
GLenum coverMode, GLenum coverMode,
GLenum transformType, GLenum transformType,
const GLfloat *transformValues); const GLfloat *transformValues);
ANGLE_EXPORT void GL_APIENTRY BindFragmentInputLocationCHROMIUM(GLuint program,
GLint location,
const GLchar *name);
ANGLE_EXPORT void GL_APIENTRY ProgramPathFragmentInputGenCHROMIUM(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs);
} // namespace gl } // namespace gl
......
...@@ -1749,3 +1749,19 @@ void GL_APIENTRY glStencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths, ...@@ -1749,3 +1749,19 @@ void GL_APIENTRY glStencilThenCoverStrokePathInstancedCHROMIUM(GLsizei numPaths,
reference, mask, coverMode, transformType, reference, mask, coverMode, transformType,
transformValues); transformValues);
} }
void GL_APIENTRY glBindFragmentInputLocationCHROMIUM(GLuint program,
GLint location,
const GLchar *name)
{
gl::BindFragmentInputLocationCHROMIUM(program, location, name);
}
void GL_APIENTRY glProgramPathFragmentInputGenCHROMIUM(GLuint program,
GLint location,
GLenum genMode,
GLint components,
const GLfloat *coeffs)
{
gl::ProgramPathFragmentInputGenCHROMIUM(program, location, genMode, components, coeffs);
}
\ No newline at end of file
...@@ -228,6 +228,8 @@ EXPORTS ...@@ -228,6 +228,8 @@ EXPORTS
glStencilFillPathInstancedCHROMIUM @340 glStencilFillPathInstancedCHROMIUM @340
glStencilThenCoverFillPathInstancedCHROMIUM @341 glStencilThenCoverFillPathInstancedCHROMIUM @341
glStencilThenCoverStrokePathInstancedCHROMIUM @342 glStencilThenCoverStrokePathInstancedCHROMIUM @342
glBindFragmentInputLocationCHROMIUM @343
glProgramPathFragmentInputGenCHROMIUM @344
; GLES 3.0 Functions ; GLES 3.0 Functions
glReadBuffer @180 glReadBuffer @180
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
#include "test_utils/ANGLETest.h" #include "test_utils/ANGLETest.h"
#include "shader_utils.h" #include "shader_utils.h"
#include "common/angleutils.h"
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <cstddef> #include <cstddef>
...@@ -33,7 +35,7 @@ bool CheckPixels(GLint x, ...@@ -33,7 +35,7 @@ bool CheckPixels(GLint x,
{ {
const auto px = x + xx; const auto px = x + xx;
const auto py = y + yy; const auto py = y + yy;
EXPECT_PIXEL_COLOR_EQ(px, py, color); EXPECT_PIXEL_NEAR(px, py, color.R, color.G, color.B, color.A, tolerance);
} }
} }
...@@ -212,15 +214,15 @@ TEST_P(CHROMIUMPathRenderingTest, TestGenDelete) ...@@ -212,15 +214,15 @@ TEST_P(CHROMIUMPathRenderingTest, TestGenDelete)
// it has actually been specified with a path data. // it has actually been specified with a path data.
path = glGenPathsCHROMIUM(1); path = glGenPathsCHROMIUM(1);
EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE); ASSERT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE);
// specify the data. // specify the data.
GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM}; GLubyte commands[] = {GL_MOVE_TO_CHROMIUM, GL_CLOSE_PATH_CHROMIUM};
GLfloat coords[] = {50.0f, 50.0f}; GLfloat coords[] = {50.0f, 50.0f};
glPathCommandsCHROMIUM(path, 2, commands, 2, GL_FLOAT, coords); glPathCommandsCHROMIUM(path, 2, commands, 2, GL_FLOAT, coords);
EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE); ASSERT_TRUE(glIsPathCHROMIUM(path) == GL_TRUE);
glDeletePathsCHROMIUM(path, 1); glDeletePathsCHROMIUM(path, 1);
EXPECT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE); ASSERT_TRUE(glIsPathCHROMIUM(path) == GL_FALSE);
} }
// Test incorrect path creation and deletion and expect GL errors. // Test incorrect path creation and deletion and expect GL errors.
...@@ -1029,6 +1031,934 @@ TEST_P(CHROMIUMPathRenderingDrawTest, TestPathRenderingThenFunctionsInstanced) ...@@ -1029,6 +1031,934 @@ TEST_P(CHROMIUMPathRenderingDrawTest, TestPathRenderingThenFunctionsInstanced)
verifyTestPatternStroke(kShapeSize, kShapeSize); verifyTestPatternStroke(kShapeSize, kShapeSize);
} }
// This class implements a test that draws a grid of v-shapes. The grid is
// drawn so that even rows (from the bottom) are drawn with DrawArrays and odd
// rows are drawn with path rendering. It can be used to test various texturing
// modes, comparing how the fill would work in normal GL rendering and how to
// setup same sort of fill with path rendering.
// The texturing test is parametrized to run the test with and without
// ANGLE name hashing.
class CHROMIUMPathRenderingWithTexturingTest : public ANGLETest
{
protected:
CHROMIUMPathRenderingWithTexturingTest() : mProgram(0)
{
setWindowWidth(kResolution);
setWindowHeight(kResolution);
setConfigRedBits(8);
setConfigGreenBits(8);
setConfigBlueBits(8);
setConfigAlphaBits(8);
setConfigDepthBits(8);
setConfigStencilBits(8);
}
bool isApplicable() const { return extensionEnabled("GL_CHROMIUM_path_rendering"); }
void TearDown() override
{
if (mProgram)
{
glDeleteProgram(mProgram);
ASSERT_GL_NO_ERROR();
}
ANGLETest::TearDown();
}
void SetUp() override
{
ANGLETest::SetUp();
mBindUniformLocation = reinterpret_cast<PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC>(
eglGetProcAddress("glBindUniformLocationCHROMIUM"));
}
// Sets up the GL program state for the test.
// Vertex shader needs at least following variables:
// uniform mat4 view_matrix;
// uniform mat? color_matrix; (accessible with kColorMatrixLocation)
// uniform vec2 model_translate;
// attribute vec2 position;
// varying vec4 color;
//
// Fragment shader needs at least following variables:
// varying vec4 color;
//
// (? can be anything)
void compileProgram(const char *vertexShaderSource, const char *fragmentShaderSource)
{
glViewport(0, 0, kResolution, kResolution);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glStencilMask(0xffffffff);
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
ASSERT_GL_NO_ERROR();
GLuint vShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
GLuint fShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);
ASSERT_NE(0u, vShader);
ASSERT_NE(0u, fShader);
mProgram = glCreateProgram();
glAttachShader(mProgram, vShader);
glAttachShader(mProgram, fShader);
glDeleteShader(vShader);
glDeleteShader(fShader);
ASSERT_GL_NO_ERROR();
}
void bindProgram()
{
glBindAttribLocation(mProgram, kPositionLocation, "position");
mBindUniformLocation(mProgram, kViewMatrixLocation, "view_matrix");
mBindUniformLocation(mProgram, kColorMatrixLocation, "color_matrix");
mBindUniformLocation(mProgram, kModelTranslateLocation, "model_translate");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorFragmentInputLocation, "color");
ASSERT_GL_NO_ERROR();
}
bool linkProgram()
{
glLinkProgram(mProgram);
GLint linked = 0;
glGetProgramiv(mProgram, GL_LINK_STATUS, &linked);
if (linked)
{
glUseProgram(mProgram);
}
return (linked == 1);
}
void drawTestPattern()
{
// This v-shape is used both for DrawArrays and path rendering.
static const GLfloat kVertices[] = {75.0f, 75.0f, 50.0f, 25.5f, 50.0f, 50.0f, 25.0f, 75.0f};
GLuint vbo = 0;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(kPositionLocation);
glVertexAttribPointer(kPositionLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
// Setup state for drawing the shape with path rendering.
glPathStencilFuncCHROMIUM(GL_ALWAYS, 0, 0x7F);
glStencilFunc(GL_LESS, 0, 0x7F);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
glMatrixLoadfCHROMIUM(GL_PATH_PROJECTION_CHROMIUM, kProjectionMatrix);
glMatrixLoadIdentityCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM);
static const GLubyte kCommands[] = {GL_MOVE_TO_CHROMIUM, GL_LINE_TO_CHROMIUM,
GL_LINE_TO_CHROMIUM, GL_LINE_TO_CHROMIUM,
GL_CLOSE_PATH_CHROMIUM};
static const GLfloat kCoords[] = {
kVertices[0], kVertices[1], kVertices[2], kVertices[3],
kVertices[6], kVertices[7], kVertices[4], kVertices[5],
};
GLuint path = glGenPathsCHROMIUM(1);
glPathCommandsCHROMIUM(path, static_cast<GLsizei>(ArraySize(kCommands)), kCommands,
static_cast<GLsizei>(ArraySize(kCoords)), GL_FLOAT, kCoords);
ASSERT_GL_NO_ERROR();
GLfloat path_model_translate[16] = {
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
// Draws the shapes. Every even row from the bottom is drawn with
// DrawArrays, odd row with path rendering. The shader program is
// the same for the both draws.
for (int j = 0; j < kTestRows; ++j)
{
for (int i = 0; i < kTestColumns; ++i)
{
if (j % 2 == 0)
{
glDisable(GL_STENCIL_TEST);
glUniform2f(kModelTranslateLocation, i * kShapeWidth, j * kShapeHeight);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
else
{
glEnable(GL_STENCIL_TEST);
path_model_translate[12] = i * kShapeWidth;
path_model_translate[13] = j * kShapeHeight;
glMatrixLoadfCHROMIUM(GL_PATH_MODELVIEW_CHROMIUM, path_model_translate);
glStencilThenCoverFillPathCHROMIUM(path, GL_COUNT_UP_CHROMIUM, 0x7F,
GL_BOUNDING_BOX_CHROMIUM);
}
}
}
ASSERT_GL_NO_ERROR();
glDisableVertexAttribArray(kPositionLocation);
glDeleteBuffers(1, &vbo);
glDeletePathsCHROMIUM(path, 1);
ASSERT_GL_NO_ERROR();
}
enum
{
kShapeWidth = 75,
kShapeHeight = 75,
kTestRows = kResolution / kShapeHeight,
kTestColumns = kResolution / kShapeWidth,
};
typedef void(GL_APIENTRYP PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC)(GLuint mProgram,
GLint location,
const GLchar *name);
PFNGLBINDUNIFORMLOCATIONCHROMIUMPROC mBindUniformLocation = nullptr;
GLuint mProgram;
// This uniform be can set by the test. It should be used to set the color for
// drawing with DrawArrays.
static const GLint kColorMatrixLocation = 4;
// This fragment input can be set by the test. It should be used to set the
// color for drawing with path rendering.
static const GLint kColorFragmentInputLocation = 7;
static const GLint kModelTranslateLocation = 3;
static const GLint kPositionLocation = 0;
static const GLint kViewMatrixLocation = 7;
};
// Test success and error cases for binding fragment input location.
TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestBindFragmentInputLocation)
{
if (!isApplicable())
return;
// original NV_path_rendering specification doesn't define whether the
// fragment shader input variables should be defined in the vertex shader or
// not. In fact it doesn't even require a vertex shader.
// However the GLES3.1 spec basically says that fragment inputs are
// either built-ins or come from the previous shader stage.
// (§ 14.1, Fragment Shader Variables).
// Additionally there are many places that are based on the assumption of having
// a vertex shader (command buffer, angle) so we're going to stick to the same
// semantics and require a vertex shader and to have the vertex shader define the
// varying fragment shader input.
// clang-format off
const char* kVertexShaderSource =
"varying vec4 color;\n"
"void main() {}\n";
const char* kFragmentShaderSource =
"precision mediump float;\n"
"varying vec4 color;\n"
"void main() {\n"
" gl_FragColor = vec4(1.0);\n"
"}\n";
// clang-format on
compileProgram(kVertexShaderSource, kFragmentShaderSource);
enum kBindLocations
{
kColorLocation = 5,
kFragColorLocation = 6
};
// successful bind.
glBindFragmentInputLocationCHROMIUM(mProgram, kColorLocation, "color");
ASSERT_GL_NO_ERROR();
// any name can be bound and names that do not actually exist in the program after
// linking are ignored.
glBindFragmentInputLocationCHROMIUM(mProgram, kColorLocation, "doesnt_exist");
ASSERT_GL_NO_ERROR();
// illegal program
glBindFragmentInputLocationCHROMIUM(mProgram + 1, kColorLocation, "color");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
// illegal bind (built-in)
glBindFragmentInputLocationCHROMIUM(mProgram, kFragColorLocation, "gl_FragColor");
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
glBindFragmentInputLocationCHROMIUM(mProgram, kFragColorLocation, NULL);
EXPECT_GL_ERROR(GL_INVALID_VALUE);
glBindFragmentInputLocationCHROMIUM(mProgram, 0xffffff, "color");
EXPECT_GL_ERROR(GL_INVALID_VALUE);
ASSERT_TRUE(linkProgram() == true);
const GLfloat kCoefficients16[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorLocation, GL_EYE_LINEAR_CHROMIUM, 4,
kCoefficients16);
ASSERT_GL_NO_ERROR();
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_EYE_LINEAR_CHROMIUM, 4, kCoefficients16);
ASSERT_GL_NO_ERROR();
}
// Test fragment input interpolation in CHROMIUM_EYE coordinates.
TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestProgramPathFragmentInputGenCHROMIUM_EYE)
{
if (!isApplicable())
return;
// clang-format off
const char *kVertexShaderSource =
"uniform mat4 view_matrix;\n"
"uniform mat4 color_matrix;\n"
"uniform vec2 model_translate;\n"
"attribute vec2 position;\n"
"varying vec3 color;\n"
"void main() {\n"
" vec4 p = vec4(model_translate + position, 1.0, 1.0);\n"
" color = (color_matrix * p).rgb;\n"
" gl_Position = view_matrix * p;\n"
"}\n";
const char *kFragmentShaderSource =
"precision mediump float;\n"
"varying vec3 color;\n"
"void main() {\n"
" gl_FragColor = vec4(color, 1.0);\n"
"}\n";
// clang-format on
compileProgram(kVertexShaderSource, kFragmentShaderSource);
bindProgram();
ASSERT_TRUE(linkProgram() == true);
glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
static const GLfloat kColorMatrix[16] = {
1.0f / kResolution, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f / kResolution, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f};
glUniformMatrix4fv(kColorMatrixLocation, 1, GL_FALSE, kColorMatrix);
// This is the functionality we are testing: ProgramPathFragmentInputGen
// does the same work as the color transform in vertex shader.
static const GLfloat kColorCoefficients[12] = {
1.0f / kResolution, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f / kResolution,
0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f};
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation,
GL_EYE_LINEAR_CHROMIUM, 3, kColorCoefficients);
ASSERT_GL_NO_ERROR();
drawTestPattern();
const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
for (int j = 0; j < kTestRows; ++j)
{
for (int i = 0; i < kTestColumns; ++i)
{
for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
{
const float fx = kFillCoords[k];
const float fy = kFillCoords[k + 1];
const float px = i * kShapeWidth;
const float py = j * kShapeHeight;
angle::GLColor color;
color.R = std::roundf((px + fx) / kResolution * 255.0f);
color.G = std::roundf((py + fy) / kResolution * 255.0f);
color.B = 0;
color.A = 255;
CheckPixels(px + fx, py + fy, 1, 1, 2, color);
}
}
}
}
// Test fragment input interpolation in CHROMIUM_OBJECT coordinates.
TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestProgramPathFragmentInputGenCHROMIUM_OBJECT)
{
if (!isApplicable())
return;
// clang-format off
const char *kVertexShaderSource =
"uniform mat4 view_matrix;\n"
"uniform mat4 color_matrix;\n"
"uniform vec2 model_translate;\n"
"attribute vec2 position;\n"
"varying vec3 color;\n"
"void main() {\n"
" color = (color_matrix * vec4(position, 1.0, 1.0)).rgb;\n"
" vec4 p = vec4(model_translate + position, 1.0, 1.0);\n"
" gl_Position = view_matrix * p;\n"
"}";
const char *kFragmentShaderSource =
"precision mediump float;\n"
"varying vec3 color;\n"
"void main() {\n"
" gl_FragColor = vec4(color.rgb, 1.0);\n"
"}";
// clang-format on
compileProgram(kVertexShaderSource, kFragmentShaderSource);
bindProgram();
ASSERT_TRUE(linkProgram() == true);
glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
static const GLfloat kColorMatrix[16] = {
1.0f / kShapeWidth, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f / kShapeHeight, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f };
glUniformMatrix4fv(kColorMatrixLocation, 1, GL_FALSE, kColorMatrix);
// This is the functionality we are testing: ProgramPathFragmentInputGen
// does the same work as the color transform in vertex shader.
static const GLfloat kColorCoefficients[9] = {
1.0f / kShapeWidth, 0.0f, 0.0f, 0.0f, 1.0f / kShapeHeight, 0.0f, 0.0f, 0.0f, 0.0f};
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation,
GL_OBJECT_LINEAR_CHROMIUM, 3, kColorCoefficients);
ASSERT_GL_NO_ERROR();
drawTestPattern();
const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
for (int j = 0; j < kTestRows; ++j)
{
for (int i = 0; i < kTestColumns; ++i)
{
for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
{
const float fx = kFillCoords[k];
const float fy = kFillCoords[k + 1];
const float px = i * kShapeWidth;
const float py = j * kShapeHeight;
angle::GLColor color;
color.R = std::roundf(fx / kShapeWidth * 255.0f);
color.G = std::roundf(fy / kShapeHeight * 255.0f);
color.B = 0;
color.A = 255;
CheckPixels(px + fx, py + fy, 1, 1, 2, color);
}
}
}
}
// Test success and error cases for setting interpolation parameters.
TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestProgramPathFragmentInputGenArgs)
{
if (!isApplicable())
return;
// clang-format off
const char *kVertexShaderSource =
"varying vec2 vec2_var;\n"
"varying vec3 vec3_var;\n"
"varying vec4 vec4_var;\n"
"varying float float_var;\n"
"varying mat2 mat2_var;\n"
"varying mat3 mat3_var;\n"
"varying mat4 mat4_var;\n"
"attribute float avoid_opt;\n"
"void main() {\n"
" vec2_var = vec2(1.0, 2.0 + avoid_opt);\n"
" vec3_var = vec3(1.0, 2.0, 3.0 + avoid_opt);\n"
" vec4_var = vec4(1.0, 2.0, 3.0, 4.0 + avoid_opt);\n"
" float_var = 5.0 + avoid_opt;\n"
" mat2_var = mat2(2.0 + avoid_opt);\n"
" mat3_var = mat3(3.0 + avoid_opt);\n"
" mat4_var = mat4(4.0 + avoid_opt);\n"
" gl_Position = vec4(1.0);\n"
"}";
const char* kFragmentShaderSource =
"precision mediump float;\n"
"varying vec2 vec2_var;\n"
"varying vec3 vec3_var;\n"
"varying vec4 vec4_var;\n"
"varying float float_var;\n"
"varying mat2 mat2_var;\n"
"varying mat3 mat3_var;\n"
"varying mat4 mat4_var;\n"
"void main() {\n"
" gl_FragColor = vec4(vec2_var, 0, 0) + vec4(vec3_var, 0) + vec4_var + "
" vec4(float_var) + "
" vec4(mat2_var[0][0], mat3_var[1][1], mat4_var[2][2], 1);\n"
"}";
// clang-format on
enum
{
kVec2Location = 0,
kVec3Location,
kVec4Location,
kFloatLocation,
kMat2Location,
kMat3Location,
kMat4Location,
};
struct
{
GLint location;
const char *name;
GLint components;
} variables[] = {
{kVec2Location, "vec2_var", 2},
{kVec3Location, "vec3_var", 3},
{kVec4Location, "vec4_var", 4},
{kFloatLocation, "float_var", 1},
// If a varying is not single-precision floating-point scalar or
// vector, it always causes an invalid operation.
{kMat2Location, "mat2_var", -1},
{kMat3Location, "mat3_var", -1},
{kMat4Location, "mat4_var", -1},
};
compileProgram(kVertexShaderSource, kFragmentShaderSource);
for (size_t i = 0; i < ArraySize(variables); ++i)
{
glBindFragmentInputLocationCHROMIUM(mProgram, variables[i].location, variables[i].name);
}
// test that using invalid (not linked) program is an invalid operation.
// See similar calls at the end of the test for discussion about the arguments.
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_NONE, 0, NULL);
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
ASSERT_TRUE(linkProgram() == true);
const GLfloat kCoefficients16[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f};
const GLenum kGenModes[] = {GL_NONE, GL_EYE_LINEAR_CHROMIUM, GL_OBJECT_LINEAR_CHROMIUM,
GL_CONSTANT_CHROMIUM};
for (size_t variable = 0; variable < ArraySize(variables); ++variable)
{
for (GLint components = 0; components <= 4; ++components)
{
for (size_t genmode = 0; genmode < ArraySize(kGenModes); ++genmode)
{
glProgramPathFragmentInputGenCHROMIUM(mProgram, variables[variable].location,
kGenModes[genmode], components,
kCoefficients16);
if (components == 0 && kGenModes[genmode] == GL_NONE)
{
if (variables[variable].components == -1)
{
// Clearing a fragment input that is not single-precision floating
// point scalar or vector is an invalid operation.
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
else
{
// Clearing a valid fragment input is ok.
ASSERT_GL_NO_ERROR();
}
}
else if (components == 0 || kGenModes[genmode] == GL_NONE)
{
ASSERT_GL_ERROR(GL_INVALID_VALUE);
}
else
{
if (components == variables[variable].components)
{
// Setting a generator for a single-precision floating point
// scalar or vector fragment input is ok.
ASSERT_GL_NO_ERROR();
}
else
{
// Setting a generator when components do not match is an invalid operation.
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
}
}
}
}
enum
{
kValidGenMode = GL_CONSTANT_CHROMIUM,
kValidComponents = 3,
kInvalidGenMode = 0xAB,
kInvalidComponents = 5,
};
// The location == -1 would mean fragment input was optimized away. At the
// time of writing, -1 can not happen because the only way to obtain the
// location numbers is through bind. Test just to be consistent.
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kValidComponents,
kCoefficients16);
ASSERT_GL_NO_ERROR();
// Test that even though the spec says location == -1 causes the operation to
// be skipped, the verification of other parameters is still done. This is a
// GL policy.
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kValidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_ENUM);
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kInvalidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_ENUM);
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kInvalidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_VALUE);
glDeleteProgram(mProgram);
// Test that using invalid (deleted) program is an invalid operation.
EXPECT_FALSE(glIsProgram(mProgram) == GL_FALSE);
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kValidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kValidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kInvalidGenMode, kInvalidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, kValidGenMode, kInvalidComponents,
kCoefficients16);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
mProgram = 0u;
}
// Test that having input statically aliased fragment inputs the linking fails
// and then succeeds when the conflict is resolved.
TEST_P(CHROMIUMPathRenderingWithTexturingTest, TestConflictingBind)
{
if (!isApplicable())
return;
// clang-format off
const char* kVertexShaderSource =
"attribute vec4 position;\n"
"varying vec4 colorA;\n"
"varying vec4 colorB;\n"
"void main() {\n"
" gl_Position = position;\n"
" colorA = position + vec4(1);\n"
" colorB = position + vec4(2);\n"
"}";
const char* kFragmentShaderSource =
"precision mediump float;\n"
"varying vec4 colorA;\n"
"varying vec4 colorB;\n"
"void main() {\n"
" gl_FragColor = colorA + colorB;\n"
"}";
// clang-format on
const GLint kColorALocation = 3;
const GLint kColorBLocation = 4;
compileProgram(kVertexShaderSource, kFragmentShaderSource);
glBindFragmentInputLocationCHROMIUM(mProgram, kColorALocation, "colorA");
// Bind colorB to location a, causing conflicts. Linking should fail.
glBindFragmentInputLocationCHROMIUM(mProgram, kColorALocation, "colorB");
// Should fail now.
ASSERT_TRUE(linkProgram() == false);
ASSERT_GL_NO_ERROR();
// Resolve the bind conflict.
glBindFragmentInputLocationCHROMIUM(mProgram, kColorBLocation, "colorB");
ASSERT_TRUE(linkProgram() == true);
ASSERT_GL_NO_ERROR();
}
// Test binding with array variables, using zero indices. Tests that
// binding colorA[0] with explicit "colorA[0]" as well as "colorA" produces
// a correct location that can be used with PathProgramFragmentInputGen.
// For path rendering, colorA[0] is bound to a location. The input generator for
// the location is set to produce vec4(0, 0.1, 0, 0.1).
// The default varying, color, is bound to a location and its generator
// will produce vec4(10.0). The shader program produces green pixels.
// For vertex-based rendering, the vertex shader produces the same effect as
// the input generator for path rendering.
TEST_P(CHROMIUMPathRenderingWithTexturingTest, BindFragmentInputArray)
{
if (!isApplicable())
return;
//clang-format off
const char* kVertexShaderSource =
"uniform mat4 view_matrix;\n"
"uniform mat4 color_matrix;\n"
"uniform vec2 model_translate;\n"
"attribute vec2 position;\n"
"varying vec4 color;\n"
"varying vec4 colorA[4];\n"
"void main() {\n"
" vec4 p = vec4(model_translate + position, 1, 1);\n"
" gl_Position = view_matrix * p;\n"
" colorA[0] = vec4(0.0, 0.1, 0, 0.1);\n"
" colorA[1] = vec4(0.2);\n"
" colorA[2] = vec4(0.3);\n"
" colorA[3] = vec4(0.4);\n"
" color = vec4(10.0);\n"
"}";
const char* kFragmentShaderSource =
"precision mediump float;\n"
"varying vec4 color;\n"
"varying vec4 colorA[4];\n"
"void main() {\n"
" gl_FragColor = colorA[0] * color;\n"
"}";
// clang-format on
const GLint kColorA0Location = 4;
const GLint kUnusedLocation = 5;
const GLfloat kColorA0[] = {0.0f, 0.1f, 0.0f, 0.1f};
const GLfloat kColor[] = {10.0f, 10.0f, 10.0f, 10.0f};
const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
for (int pass = 0; pass < 2; ++pass)
{
compileProgram(kVertexShaderSource, kFragmentShaderSource);
if (pass == 0)
{
glBindFragmentInputLocationCHROMIUM(mProgram, kUnusedLocation, "colorA[0]");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorA0Location, "colorA");
}
else
{
glBindFragmentInputLocationCHROMIUM(mProgram, kUnusedLocation, "colorA");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorA0Location, "colorA[0]");
}
bindProgram();
ASSERT_TRUE(linkProgram() == true);
glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA0Location, GL_CONSTANT_CHROMIUM, 4, kColorA0);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation, GL_CONSTANT_CHROMIUM, 4, kColor);
ASSERT_GL_NO_ERROR();
drawTestPattern();
for (int j = 0; j < kTestRows; ++j)
{
for (int i = 0; i < kTestColumns; ++i)
{
for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
{
const float fx = kFillCoords[k];
const float fy = kFillCoords[k + 1];
const float px = i * kShapeWidth;
const float py = j * kShapeHeight;
angle::GLColor color;
color.R = 0;
color.G = 255;
color.B = 0;
color.A = 255;
CheckPixels(px + fx, py + fy, 1, 1, 2, color);
}
}
}
}
}
// Test binding array variables. This is like BindFragmentInputArray.
// Currently disabled since it seems there's a driver bug with the
// older drivers. This should work with driver >= 364.12
TEST_P(CHROMIUMPathRenderingWithTexturingTest,
DISABLED_BindFragmentInputArrayNonZeroIndex)
{
if (!isApplicable())
return;
// clang-format off
const char* kVertexShaderSource =
"uniform mat4 view_matrix;\n"
"uniform mat4 color_matrix;\n"
"uniform vec2 model_translate;\n"
"attribute vec2 position;\n"
"varying vec4 color;\n"
"varying vec4 colorA[4];\n"
"void main() {\n"
" vec4 p = vec4(model_translate + position, 1, 1);\n"
" gl_Position = view_matrix * p;\n"
" colorA[0] = vec4(0, 0.1, 0, 0.1);\n"
" colorA[1] = vec4(0, 1, 0, 1);\n"
" colorA[2] = vec4(0, 0.8, 0, 0.8);\n"
" colorA[3] = vec4(0, 0.5, 0, 0.5);\n"
" color = vec4(0.2);\n"
"}\n";
const char* kFragmentShaderSource =
"precision mediump float;\n"
"varying vec4 colorA[4];\n"
"varying vec4 color;\n"
"void main() {\n"
" gl_FragColor = (colorA[0] * colorA[1]) +\n"
" colorA[2] + (colorA[3] * color);\n"
"}\n";
// clang-format on
const GLint kColorA0Location = 4;
const GLint kColorA1Location = 1;
const GLint kColorA2Location = 2;
const GLint kColorA3Location = 3;
const GLint kUnusedLocation = 5;
const GLfloat kColorA0[] = {0.0f, 0.1f, 0.0f, 0.1f};
const GLfloat kColorA1[] = {0.0f, 1.0f, 0.0f, 1.0f};
const GLfloat kColorA2[] = {0.0f, 0.8f, 0.0f, 0.8f};
const GLfloat kColorA3[] = {0.0f, 0.5f, 0.0f, 0.5f};
const GLfloat kColor[] = {0.2f, 0.2f, 0.2f, 0.2f};
const GLfloat kFillCoords[6] = {59.0f, 50.0f, 50.0f, 28.0f, 66.0f, 63.0f};
compileProgram(kVertexShaderSource, kFragmentShaderSource);
glBindFragmentInputLocationCHROMIUM(mProgram, kUnusedLocation, "colorA[0]");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorA1Location, "colorA[1]");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorA2Location, "colorA[2]");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorA3Location, "colorA[3]");
glBindFragmentInputLocationCHROMIUM(mProgram, kColorA0Location, "colorA");
ASSERT_GL_NO_ERROR();
bindProgram();
ASSERT_TRUE(linkProgram() == true);
glUniformMatrix4fv(kViewMatrixLocation, 1, GL_FALSE, kProjectionMatrix);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA0Location, GL_CONSTANT_CHROMIUM, 4, kColorA0);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA1Location, GL_CONSTANT_CHROMIUM, 4, kColorA1);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA2Location, GL_CONSTANT_CHROMIUM, 4, kColorA2);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorA3Location, GL_CONSTANT_CHROMIUM, 4, kColorA3);
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorFragmentInputLocation,
GL_CONSTANT_CHROMIUM, 4, kColor);
ASSERT_GL_NO_ERROR();
drawTestPattern();
for (int j = 0; j < kTestRows; ++j)
{
for (int i = 0; i < kTestColumns; ++i)
{
for (size_t k = 0; k < ArraySize(kFillCoords); k += 2)
{
const float fx = kFillCoords[k];
const float fy = kFillCoords[k + 1];
const float px = i * kShapeWidth;
const float py = j * kShapeHeight;
angle::GLColor color;
color.R = 0;
color.G = 255;
color.B = 0;
color.A = 255;
CheckPixels(px + fx, py + fy, 1, 1, 2, color);
}
}
}
}
TEST_P(CHROMIUMPathRenderingWithTexturingTest, UnusedFragmentInputUpdate)
{
if (!isApplicable())
return;
// clang-format off
const char* kVertexShaderString =
"attribute vec4 a_position;\n"
"void main() {\n"
" gl_Position = a_position;\n"
"}";
const char* kFragmentShaderString =
"precision mediump float;\n"
"uniform vec4 u_colorA;\n"
"uniform float u_colorU;\n"
"uniform vec4 u_colorC;\n"
"void main() {\n"
" gl_FragColor = u_colorA + u_colorC;\n"
"}";
// clang-format on
const GLint kColorULocation = 1;
const GLint kNonexistingLocation = 5;
const GLint kUnboundLocation = 6;
compileProgram(kVertexShaderString, kFragmentShaderString);
glBindFragmentInputLocationCHROMIUM(mProgram, kColorULocation, "u_colorU");
// The non-existing input should behave like existing but optimized away input.
glBindFragmentInputLocationCHROMIUM(mProgram, kNonexistingLocation, "nonexisting");
// Let A and C be assigned automatic locations.
ASSERT_TRUE(linkProgram() == true);
const GLfloat kColor[16] = {};
// No errors on bound locations, since caller does not know
// if the driver optimizes them away or not.
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorULocation, GL_CONSTANT_CHROMIUM, 1, kColor);
ASSERT_GL_NO_ERROR();
// No errors on bound locations of names that do not exist
// in the shader. Otherwise it would be inconsistent wrt the
// optimization case.
glProgramPathFragmentInputGenCHROMIUM(mProgram, kNonexistingLocation, GL_CONSTANT_CHROMIUM, 1, kColor);
ASSERT_GL_NO_ERROR();
// The above are equal to updating -1.
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_CONSTANT_CHROMIUM, 1, kColor);
ASSERT_GL_NO_ERROR();
// No errors when updating with other type either.
// The type can not be known with the non-existing case.
glProgramPathFragmentInputGenCHROMIUM(mProgram, kColorULocation, GL_CONSTANT_CHROMIUM, 4, kColor);
ASSERT_GL_NO_ERROR();
glProgramPathFragmentInputGenCHROMIUM(mProgram, kNonexistingLocation, GL_CONSTANT_CHROMIUM, 4, kColor);
ASSERT_GL_NO_ERROR();
glProgramPathFragmentInputGenCHROMIUM(mProgram, -1, GL_CONSTANT_CHROMIUM, 4, kColor);
ASSERT_GL_NO_ERROR();
// Updating an unbound, non-existing location still causes an error.
glProgramPathFragmentInputGenCHROMIUM(mProgram, kUnboundLocation, GL_CONSTANT_CHROMIUM, 4, kColor);
ASSERT_GL_ERROR(GL_INVALID_OPERATION);
}
} // namespace } // namespace
ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingTest, ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingTest,
...@@ -1041,3 +1971,9 @@ ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingDrawTest, ...@@ -1041,3 +1971,9 @@ ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingDrawTest,
ES2_OPENGLES(), ES2_OPENGLES(),
ES3_OPENGL(), ES3_OPENGL(),
ES3_OPENGLES()); ES3_OPENGLES());
ANGLE_INSTANTIATE_TEST(CHROMIUMPathRenderingWithTexturingTest,
ES2_OPENGL(),
ES2_OPENGLES(),
ES3_OPENGL(),
ES3_OPENGLES());
\ No newline at end of file
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