diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index c8e681417..b9d68c3d4 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -277,17 +277,17 @@ GL::Mesh mesh{data.primitive()}; GL::Buffer vertices; vertices.setData(data.vertexData()); -/* Set up the position attribute */ -Shaders::Phong::Position position; -auto positionFormat = data.attributeFormat(Trade::MeshAttribute::Position); -if(positionFormat == VertexFormat::Vector2) - position = {Shaders::Phong::Position::Components::Two}; -else if(positionFormat == VertexFormat::Vector3) - position = {Shaders::Phong::Position::Components::Three}; -else Fatal{} << "Huh?"; +/* Set up the position and normal attributes */ mesh.addVertexBuffer(vertices, data.attributeOffset(Trade::MeshAttribute::Position), - data.attributeStride(Trade::MeshAttribute::Position), position); + data.attributeStride(Trade::MeshAttribute::Position), + GL::DynamicAttribute{Shaders::Phong::Position{}, + data.attributeFormat(Trade::MeshAttribute::Position)}); +mesh.addVertexBuffer(vertices, + data.attributeOffset(Trade::MeshAttribute::Normal), + data.attributeStride(Trade::MeshAttribute::Normal), + GL::DynamicAttribute{Shaders::Phong::Normal{}, + data.attributeFormat(Trade::MeshAttribute::Normal)}); // Set up other attributes ... diff --git a/src/Magnum/GL/Attribute.cpp b/src/Magnum/GL/Attribute.cpp index 8fc5b39b2..9233df4fa 100644 --- a/src/Magnum/GL/Attribute.cpp +++ b/src/Magnum/GL/Attribute.cpp @@ -28,6 +28,8 @@ #include #include +#include "Magnum/VertexFormat.h" + namespace Magnum { namespace GL { Debug& operator<<(Debug& debug, const DynamicAttribute::Kind value) { @@ -467,4 +469,60 @@ Debug& operator<<(Debug& debug, const Attribute>::DataTyp } +DynamicAttribute::DynamicAttribute(const Kind kind, UnsignedInt location, const VertexFormat format, GLint maxComponents): _kind{kind}, _location{location}, _components{Components(vertexFormatComponentCount(format))} { + /* Translate component type to a GL-specific value */ + switch(vertexFormatComponentFormat(format)) { + #define _c(format) \ + case VertexFormat::format: \ + _dataType = DataType::format; \ + break; + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(Float) + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + _c(Half) + #endif + #ifndef MAGNUM_TARGET_GLES + _c(Double) + #endif + #undef _c + + /* Nothing else expected to be returned from + vertexFormatComponentFormat() */ + default: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* If the type is normalized, switch the type to GenericNormalized (if not + already), and check that the attribute isn't expected to be integral or + long */ + if(isVertexFormatNormalized(format)) { + CORRADE_ASSERT(kind == Kind::Generic || kind == Kind::GenericNormalized, + "GL::DynamicAttribute: can't use" << format << "for a" << kind << "attribute", ); + _kind = Kind::GenericNormalized; + /* Otherwise check that non-normalized types aren't used for attributes + that are expected to be normalized. Float is an exception. */ + } else if(_dataType != DataType::Float) { + CORRADE_ASSERT(kind != Kind::GenericNormalized, + "GL::DynamicAttribute: can't use" << format << "for a normalized attribute", ); + /* Finally, float data types can't be used for integer attributes */ + } else { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(kind != Kind::Integral, + "GL::DynamicAttribute: can't use" << format << "for an integral attribute", ); + #endif + } + + #ifndef CORRADE_NO_DEBUG + /* Should pass also if maxComponents is GL_BGRA */ + CORRADE_ASSERT(GLint(_components) <= maxComponents, + "GL::DynamicAttribute: can't use" << format << "for a" << maxComponents << Debug::nospace << "-component attribute", ); + #else + static_cast(maxComponents); + #endif +} + }} diff --git a/src/Magnum/GL/Attribute.h b/src/Magnum/GL/Attribute.h index ae497691d..4dd3114c0 100644 --- a/src/Magnum/GL/Attribute.h +++ b/src/Magnum/GL/Attribute.h @@ -346,7 +346,7 @@ location and base type. Note that unlike the compile-time specification, this class doesn't do any sanity verification and leaves most of the responsibility on the user. */ -class DynamicAttribute { +class MAGNUM_GL_EXPORT DynamicAttribute { public: /** * @brief Attribute kind @@ -543,6 +543,31 @@ class DynamicAttribute { */ template constexpr /*implicit*/ DynamicAttribute(const Attribute& attribute); + /** + * @brief Construct from a generic mesh attribute type + * @m_since_latest + * + * The @p type is expected to be compatible with @p kind --- i.e., + * normalized or floating-point for @ref Kind::GenericNormalized, + * non-normalized for @ref Kind::Integral / @ref Kind::Long and + * integral for @ref Kind::Integral. + */ + explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format): DynamicAttribute{kind, location, format, 4} {} + + /** + * @brief Construct from a compile-time attribute with a generic mesh attribute type override + * @m_since_latest + * + * Extracts kind and location from passed @ref Attribute type and calls + * @ref DynamicAttribute(Kind, UnsignedInt, VertexFormat). Expects that + * @p type's component count is not larger than the component count + * defined in the @p Attribute type. Note that only the + * compile-time-defined properties of the @p Attribute type are used, + * the instance-specific data type, options and component count is + * ignored. + */ + template explicit DynamicAttribute(const Attribute&, VertexFormat format); + /** @brief Attribute kind */ constexpr Kind kind() const { return _kind; } @@ -556,6 +581,10 @@ class DynamicAttribute { constexpr DataType dataType() const { return _dataType; } private: + /* Used by the constructor taking Attribute, defined in cpp to avoid + a dependency on for the assertion */ + explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format, GLint maxComponents); + Kind _kind; UnsignedInt _location; Components _components; @@ -892,6 +921,8 @@ template struct Attribute>: Attribute constexpr DynamicAttribute::DynamicAttribute(const Attribute& attribute): _kind{Implementation::kindFor(attribute.dataOptions())}, _location{location_}, _components{Components(GLint(attribute.components()))}, _dataType{DataType(GLenum(attribute.dataType()))} {} +template DynamicAttribute::DynamicAttribute(const Attribute& attribute, const VertexFormat format): DynamicAttribute{Implementation::kindFor(attribute.dataOptions()), location_, format, GLint(Implementation::Attribute::DefaultComponents)} {} + }} #endif diff --git a/src/Magnum/GL/CMakeLists.txt b/src/Magnum/GL/CMakeLists.txt index 1e61c5cda..fdaec99fc 100644 --- a/src/Magnum/GL/CMakeLists.txt +++ b/src/Magnum/GL/CMakeLists.txt @@ -27,7 +27,6 @@ set(MagnumGL_SRCS AbstractObject.cpp AbstractQuery.cpp AbstractShaderProgram.cpp - Attribute.cpp Buffer.cpp Context.cpp DefaultFramebuffer.cpp @@ -56,6 +55,7 @@ set(MagnumGL_SRCS set(MagnumGL_GracefulAssert_SRCS AbstractFramebuffer.cpp AbstractTexture.cpp + Attribute.cpp CubeMapTexture.cpp Mesh.cpp MeshView.cpp diff --git a/src/Magnum/GL/Test/AttributeTest.cpp b/src/Magnum/GL/Test/AttributeTest.cpp index 3cc14a565..a745c2a13 100644 --- a/src/Magnum/GL/Test/AttributeTest.cpp +++ b/src/Magnum/GL/Test/AttributeTest.cpp @@ -27,6 +27,7 @@ #include #include +#include "Magnum/VertexFormat.h" #include "Magnum/GL/Attribute.h" namespace Magnum { namespace GL { namespace Test { namespace { @@ -53,6 +54,23 @@ struct AttributeTest: TestSuite::Tester { void attributeMatrixNxNd(); void attributeMatrixMxNd(); + void attributeFromGenericFormat(); + #ifndef MAGNUM_TARGET_GLES2 + void attributeFromGenericFormatIntegral(); + #endif + #ifndef MAGNUM_TARGET_GLES + void attributeFromGenericFormatLong(); + #endif + void attributeFromGenericFormatEnableNormalized(); + void attributeFromGenericFormatUnexpectedForNormalizedKind(); + #ifndef MAGNUM_TARGET_GLES2 + void attributeFromGenericFormatUnexpectedForIntegralKind(); + #endif + #ifndef MAGNUM_TARGET_GLES + void attributeFromGenericFormatUnexpectedForLongKind(); + #endif + void attributeFromGenericFormatTooManyComponents(); + void debugComponents1(); void debugComponents2(); void debugComponents3(); @@ -98,6 +116,23 @@ AttributeTest::AttributeTest() { &AttributeTest::attributeMatrixNxNd, &AttributeTest::attributeMatrixMxNd, + &AttributeTest::attributeFromGenericFormat, + #ifndef MAGNUM_TARGET_GLES2 + &AttributeTest::attributeFromGenericFormatIntegral, + #endif + #ifndef MAGNUM_TARGET_GLES + &AttributeTest::attributeFromGenericFormatLong, + #endif + &AttributeTest::attributeFromGenericFormatEnableNormalized, + &AttributeTest::attributeFromGenericFormatUnexpectedForNormalizedKind, + #ifndef MAGNUM_TARGET_GLES2 + &AttributeTest::attributeFromGenericFormatUnexpectedForIntegralKind, + #endif + #ifndef MAGNUM_TARGET_GLES + &AttributeTest::attributeFromGenericFormatUnexpectedForLongKind, + #endif + &AttributeTest::attributeFromGenericFormatTooManyComponents, + &AttributeTest::debugComponents1, &AttributeTest::debugComponents2, &AttributeTest::debugComponents3, @@ -491,6 +526,120 @@ void AttributeTest::attributeMatrixMxNd() { #endif } +void AttributeTest::attributeFromGenericFormat() { + DynamicAttribute a{DynamicAttribute::Kind::Generic, 3, + VertexFormat::UnsignedShort}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::UnsignedShort); + + /* Check that compile-time attribs work too */ + DynamicAttribute a2{Attribute<7, Vector3>{}, + VertexFormat::UnsignedShort}; + CORRADE_COMPARE(a2.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(a2.location(), 7); + CORRADE_COMPARE(a2.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(a2.dataType(), DynamicAttribute::DataType::UnsignedShort); + + DynamicAttribute b{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Vector2bNormalized}; + CORRADE_COMPARE(b.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(b.location(), 3); + CORRADE_COMPARE(b.components(), DynamicAttribute::Components::Two); + CORRADE_COMPARE(b.dataType(), DynamicAttribute::DataType::Byte); + + DynamicAttribute c{DynamicAttribute::Kind::Generic, 3, + VertexFormat::Vector4ui}; + CORRADE_COMPARE(c.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(c.location(), 3); + CORRADE_COMPARE(c.components(), DynamicAttribute::Components::Four); + CORRADE_COMPARE(c.dataType(), DynamicAttribute::DataType::UnsignedInt); + + /* This one shouldn't fail even though the normalization is (probably?) + ignored. Not exactly sure. */ + DynamicAttribute d{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Float}; + CORRADE_COMPARE(d.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(d.location(), 3); + CORRADE_COMPARE(d.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(d.dataType(), DynamicAttribute::DataType::Float); +} + +#ifndef MAGNUM_TARGET_GLES2 +void AttributeTest::attributeFromGenericFormatIntegral() { + DynamicAttribute a{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector3s}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Integral); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Three); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::Short); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void AttributeTest::attributeFromGenericFormatLong() { + DynamicAttribute a{DynamicAttribute::Kind::Long, 15, + VertexFormat::Vector2d}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Long); + CORRADE_COMPARE(a.location(), 15); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Two); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::Double); +} +#endif + +void AttributeTest::attributeFromGenericFormatEnableNormalized() { + DynamicAttribute a{DynamicAttribute::Kind::Generic, 3, + VertexFormat::Vector3ubNormalized}; + /* Generic is automatically switched to GenericNormalized */ + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Three); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::UnsignedByte); +} + +void AttributeTest::attributeFromGenericFormatUnexpectedForNormalizedKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Int}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Int for a normalized attribute\n"); +} + +#ifndef MAGNUM_TARGET_GLES2 +void AttributeTest::attributeFromGenericFormatUnexpectedForIntegralKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector2bNormalized}; + DynamicAttribute{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector3}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Vector2bNormalized for a GL::DynamicAttribute::Kind::Integral attribute\n" + "GL::DynamicAttribute: can't use VertexFormat::Vector3 for an integral attribute\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void AttributeTest::attributeFromGenericFormatUnexpectedForLongKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::Long, 3, + VertexFormat::UnsignedShortNormalized}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::UnsignedShortNormalized for a GL::DynamicAttribute::Kind::Long attribute\n"); +} +#endif + +void AttributeTest::attributeFromGenericFormatTooManyComponents() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{Attribute<7, Vector2>{}, VertexFormat::Vector3}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Vector3 for a 2-component attribute\n"); +} + void AttributeTest::debugComponents1() { typedef Attribute<3, Float> Attribute; diff --git a/src/Magnum/GL/Test/CMakeLists.txt b/src/Magnum/GL/Test/CMakeLists.txt index 2233b0711..1dface78d 100644 --- a/src/Magnum/GL/Test/CMakeLists.txt +++ b/src/Magnum/GL/Test/CMakeLists.txt @@ -23,7 +23,7 @@ # DEALINGS IN THE SOFTWARE. # -corrade_add_test(GLAttributeTest AttributeTest.cpp LIBRARIES MagnumGL) +corrade_add_test(GLAttributeTest AttributeTest.cpp LIBRARIES MagnumGLTestLib) corrade_add_test(GLAbstractShaderProgramTest AbstractShaderProgramTest.cpp LIBRARIES MagnumGL) corrade_add_test(GLBufferTest BufferTest.cpp LIBRARIES MagnumGL) corrade_add_test(GLContextTest ContextTest.cpp LIBRARIES MagnumGL) diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 468458483..825deed27 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -373,7 +373,11 @@ cases, sometimes you may want to minimize the import time of a large model or the imported data may be already in a well-optimized layout and format that you want to preserve. The @ref MeshData class internally stores a contiguous blob of data, which you can directly upload, and then use provided metadata to let -the GPU know of the format and layout: +the GPU know of the format and layout. Because there's a lot of possible types +of each attribute (floats, packed integers, ...), the @ref GL::DynamicAttribute +can accept a pair of @ref GL::Attribute defined by the shader and the actual +@ref VertexFormat, figuring out all properties such as component count and +element data type without having to explicitly handle all relevant types: @snippet MagnumTrade.cpp MeshData-usage-advanced