From b5743c94475c7af5aefc6d561778c85ff32b3bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 12 Jun 2013 13:52:00 +0200 Subject: [PATCH] TextureTools: ported distanceField() to OpenGL 2.1 and OpenGL ES. * Older GLSL doesn't have texelFetch() and related things, working around it by using classical texture() and normalized floating-point coordinates. But that needs to have Texture::imageSize() passed, which is not available in OpenGL ES, thus the user must specify it explicitly there. On desktop OpenGL that parameter is ignored. * Older GLSL doesn't have gl_VertexID, thus vertex buffer must be created and vertex data passed expliticly. * GLSL ES 2.0 doesn't have one-component texture format and TextureFormat::Luminance probably isn't renderable anywhere, thus TextureFormat::RGB should be used, although it is inefficient. * Checking for framebuffer completeness, if not complete, nothing is done. * Re-eabled building of TextureTools library in all ES PKGBUILDs. --- PKGBUILD-es2 | 1 - PKGBUILD-es2desktop | 1 - PKGBUILD-es3 | 1 - src/Text/DistanceFieldGlyphCache.cpp | 2 +- src/TextureTools/DistanceField.cpp | 128 ++++++++++++++++++---- src/TextureTools/DistanceField.h | 20 +++- src/TextureTools/DistanceFieldShader.frag | 86 ++++++++++++--- src/TextureTools/DistanceFieldShader.vert | 8 ++ 8 files changed, 208 insertions(+), 39 deletions(-) diff --git a/PKGBUILD-es2 b/PKGBUILD-es2 index e4c937d64..8daa7c23c 100644 --- a/PKGBUILD-es2 +++ b/PKGBUILD-es2 @@ -27,7 +27,6 @@ build() { -DTARGET_GLES=ON \ -DTARGET_GLES2=ON \ -DWITH_TEXT=OFF \ - -DWITH_TEXTURETOOLS=OFF \ -DWITH_MAGNUMINFO=OFF \ -DWITH_XEGLAPPLICATION=ON make diff --git a/PKGBUILD-es2desktop b/PKGBUILD-es2desktop index 55fa9ccb9..3f58adf14 100644 --- a/PKGBUILD-es2desktop +++ b/PKGBUILD-es2desktop @@ -28,7 +28,6 @@ build() { -DTARGET_GLES2=ON \ -DTARGET_DESKTOP_GLES=ON \ -DWITH_TEXT=OFF \ - -DWITH_TEXTURETOOLS=OFF \ -DWITH_MAGNUMINFO=OFF \ -DWITH_XEGLAPPLICATION=ON make diff --git a/PKGBUILD-es3 b/PKGBUILD-es3 index e567c82a8..4ff7500c4 100644 --- a/PKGBUILD-es3 +++ b/PKGBUILD-es3 @@ -27,7 +27,6 @@ build() { -DTARGET_GLES=ON \ -DTARGET_GLES2=OFF \ -DWITH_TEXT=OFF \ - -DWITH_TEXTURETOOLS=OFF \ -DWITH_MAGNUMINFO=OFF \ -DWITH_XEGLAPPLICATION=ON make diff --git a/src/Text/DistanceFieldGlyphCache.cpp b/src/Text/DistanceFieldGlyphCache.cpp index 73ffcd4b7..0bda02670 100644 --- a/src/Text/DistanceFieldGlyphCache.cpp +++ b/src/Text/DistanceFieldGlyphCache.cpp @@ -57,7 +57,7 @@ void DistanceFieldGlyphCache::setImage(const Vector2i& offset, Image2D* const im ->setImage(0, internalFormat, image); /* Create distance field from input texture */ - TextureTools::distanceField(&input, &_texture, Rectanglei::fromSize(offset*scale, image->size()*scale), radius); + TextureTools::distanceField(&input, &_texture, Rectanglei::fromSize(offset*scale, image->size()*scale), radius, image->size()); } void DistanceFieldGlyphCache::setDistanceFieldImage(const Vector2i& offset, Image2D* const image) { diff --git a/src/TextureTools/DistanceField.cpp b/src/TextureTools/DistanceField.cpp index fbc1eeee6..521c86beb 100644 --- a/src/TextureTools/DistanceField.cpp +++ b/src/TextureTools/DistanceField.cpp @@ -39,6 +39,8 @@ namespace { class DistanceFieldShader: public AbstractShaderProgram { public: + typedef Attribute<0, Vector2> Position; + enum: Int { TextureLayer = 8 }; @@ -50,62 +52,148 @@ class DistanceFieldShader: public AbstractShaderProgram { return this; } - DistanceFieldShader* setScaling(Vector2 scaling) { + DistanceFieldShader* setScaling(const Vector2& scaling) { setUniform(scalingUniform, scaling); return this; } + DistanceFieldShader* setImageSizeInverted(const Vector2& size) { + setUniform(imageSizeInvertedUniform, size); + return this; + } + private: - static const Int radiusUniform = 0, - scalingUniform = 1; + Int radiusUniform, + scalingUniform, + imageSizeInvertedUniform; }; -DistanceFieldShader::DistanceFieldShader() { - MAGNUM_ASSERT_VERSION_SUPPORTED(Version::GL330); - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::explicit_attrib_location); - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::explicit_uniform_location); - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::shading_language_420pack); - - /** @todo compatibility! */ - +DistanceFieldShader::DistanceFieldShader(): radiusUniform(0), scalingUniform(1) { Utility::Resource rs("MagnumTextureTools"); - Shader vert(Version::GL330, Shader::Type::Vertex); - vert.addSource(rs.get("DistanceFieldShader.vert")); + #ifndef MAGNUM_TARGET_GLES + const Version v = Context::current()->supportedVersion({Version::GL320, Version::GL300, Version::GL210}); + #else + const Version v = Context::current()->supportedVersion({Version::GLES300, Version::GLES200}); + #endif + + Shader vert(v, Shader::Type::Vertex); + vert.addSource(rs.get("compatibility.glsl")) + .addSource(rs.get("DistanceFieldShader.vert")); CORRADE_INTERNAL_ASSERT_OUTPUT(vert.compile()); attachShader(vert); - Shader frag(Version::GL330, Shader::Type::Fragment); + Shader frag(v, Shader::Type::Fragment); frag.addSource(rs.get("compatibility.glsl")) .addSource(rs.get("DistanceFieldShader.frag")); CORRADE_INTERNAL_ASSERT_OUTPUT(frag.compile()); attachShader(frag); + /* Older GLSL doesn't have gl_VertexID, vertices must be supplied explicitly */ + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isVersionSupported(Version::GL300)) + #else + if(!Context::current()->isVersionSupported(Version::GLES300)) + #endif + { + bindAttributeLocation(Position::Location, "position"); + } + CORRADE_INTERNAL_ASSERT_OUTPUT(link()); -} + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + #endif + { + radiusUniform = uniformLocation("radius"); + scalingUniform = uniformLocation("scaling"); + + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isVersionSupported(Version::GL300)) + #else + if(!Context::current()->isVersionSupported(Version::GLES300)) + #endif + { + imageSizeInvertedUniform = uniformLocation("imageSizeInverted"); + } + } + + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isExtensionSupported()) + #endif + { + setUniform(uniformLocation("textureData"), TextureLayer); + } } -void distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius) { - MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::EXT::framebuffer_object); +} +#ifndef MAGNUM_TARGET_GLES +void distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius, const Vector2i&) +#else +void distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, const Int radius, const Vector2i& imageSize) +#endif +{ + #ifndef MAGNUM_TARGET_GLES + MAGNUM_ASSERT_EXTENSION_SUPPORTED(Extensions::GL::ARB::framebuffer_object); + #endif /** @todo Disable depth test, blending and then enable it back (if was previously) */ + #ifndef MAGNUM_TARGET_GLES + Vector2i imageSize = input->imageSize(0); + #endif + Framebuffer framebuffer(rectangle); framebuffer.attachTexture2D(Framebuffer::ColorAttachment(0), output, 0); framebuffer.bind(FramebufferTarget::Draw); + framebuffer.clear(FramebufferClear::Color); + + const Framebuffer::Status status = framebuffer.checkStatus(FramebufferTarget::Draw); + if(status != Framebuffer::Status::Complete) { + Error() << "TextureTools::distanceField(): cannot render to given output texture, unexpected framebuffer status" + << status; + return; + } DistanceFieldShader shader; shader.setRadius(radius) - ->setScaling(Vector2(input->imageSize(0))/rectangle.size()) + ->setScaling(Vector2(imageSize)/rectangle.size()) ->use(); input->bind(DistanceFieldShader::TextureLayer); + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isVersionSupported(Version::GL300)) + #else + if(!Context::current()->isVersionSupported(Version::GLES300)) + #endif + { + shader.setImageSizeInverted(Vector2(1)/imageSize); + } + Mesh mesh; mesh.setPrimitive(Mesh::Primitive::Triangles) - ->setVertexCount(3) - ->draw(); + ->setVertexCount(3); + + /* Older GLSL doesn't have gl_VertexID, vertices must be supplied explicitly */ + Buffer buffer; + #ifndef MAGNUM_TARGET_GLES + if(!Context::current()->isVersionSupported(Version::GL300)) + #else + if(!Context::current()->isVersionSupported(Version::GLES300)) + #endif + { + constexpr Vector2 triangle[] = { + Vector2(-1.0, 1.0), + Vector2(-1.0, -3.0), + Vector2( 3.0, 1.0) + }; + buffer.setData(triangle, Buffer::Usage::StaticDraw); + mesh.addVertexBuffer(&buffer, 0, DistanceFieldShader::Position()); + } + + /* Draw the mesh */ + mesh.draw(); } }} diff --git a/src/TextureTools/DistanceField.h b/src/TextureTools/DistanceField.h index dfe8c409d..fba73a37a 100644 --- a/src/TextureTools/DistanceField.h +++ b/src/TextureTools/DistanceField.h @@ -28,6 +28,9 @@ * @brief Function Magnum::TextureTools::distanceField() */ +#ifndef MAGNUM_TARGET_GLES +#include "Math/Vector2.h" +#endif #include "Magnum.h" #include "TextureTools/magnumTextureToolsVisibility.h" @@ -40,6 +43,8 @@ namespace Magnum { namespace TextureTools { @param output Output texture @param rectangle Rectangle in output texture where to render @param radius Max lookup radius in input texture +@param imageSize Input texture size. Needed only in OpenGL ES, in desktop + OpenGL the information is gathered automatically using Texture::imageSize(). Converts binary image (stored in red channel of @p input) to signed distance field (stored in red channel in @p rectangle of @p output). The purpose of this @@ -66,8 +71,21 @@ and Special Effects, SIGGRAPH 2007, http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf* @attention This is GPU-only implementation, so it expects active context. + +@note If internal format of @p output texture is not renderable, this function + prints message to error output and does nothing. In desktop OpenGL and + OpenGL ES 3.0 it's common to render to @ref TextureFormat "TextureFormat::R8". + In OpenGL ES 2.0 you can use @ref TextureFormat "TextureFormat::Red" if + @es_extension{EXT,texture_rg} is available, if not, the smallest but still + inefficient supported format is in most cases @ref TextureFormat "TextureFormat::RGB", + rendering to @ref TextureFormat "TextureFormat::Luminance" is not supported + in most cases. */ -void MAGNUM_TEXTURETOOLS_EXPORT distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, Int radius); +#ifndef MAGNUM_TARGET_GLES +void MAGNUM_TEXTURETOOLS_EXPORT distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, Int radius, const Vector2i& imageSize = Vector2i()); +#else +void MAGNUM_TEXTURETOOLS_EXPORT distanceField(Texture2D* input, Texture2D* output, const Rectanglei& rectangle, Int radius, const Vector2i& imageSize); +#endif }} diff --git a/src/TextureTools/DistanceFieldShader.frag b/src/TextureTools/DistanceFieldShader.frag index 9191f9525..c19273b79 100644 --- a/src/TextureTools/DistanceFieldShader.frag +++ b/src/TextureTools/DistanceFieldShader.frag @@ -22,40 +22,90 @@ DEALINGS IN THE SOFTWARE. */ +#ifndef NEW_GLSL +#define in varying +#define value gl_FragColor.x +#define const +#define texture texture2D +#endif + +#if (defined(GL_ES) && __VERSION__ >= 300) || (!defined(GL_ES) && __VERSION__ >= 150) +#define TEXELFETCH_USABLE +#endif + +#ifdef EXPLICIT_UNIFORM_LOCATION layout(location = 0) uniform int radius; layout(location = 1) uniform vec2 scaling; -layout(binding = 8) uniform sampler2D texture; - -layout(pixel_center_integer) in vec4 gl_FragCoord; - -out float value; +layout(binding = 8) uniform sampler2D textureData; +#else +uniform lowp int radius; +uniform mediump vec2 scaling; +uniform lowp sampler2D textureData; +#endif + +#ifdef TEXELFETCH_USABLE +layout(pixel_center_integer) in mediump vec4 gl_FragCoord; +#else +#ifdef EXPLICIT_UNIFORM_LOCATION +layout(location = 2) uniform vec2 imageSizeInverted; +#else +uniform mediump vec2 imageSizeInverted; +#endif +#endif + +#ifdef NEW_GLSL +out lowp float value; +#endif + +#ifdef TEXELFETCH_USABLE +mediump ivec2 rotate(const mediump ivec2 vec) { + return mediump ivec2(-vec.y, vec.x); +} -ivec2 rotate(const ivec2 vec) { - return ivec2(-vec.y, vec.x); +bool hasValue(const mediump ivec2 position, const mediump ivec2 offset) { + return texelFetch(textureData, position+offset, 0).r > 0.5; +} +#else +mediump vec2 rotate(const mediump vec2 vec) { + return mediump vec2(-vec.y, vec.x); } -bool hasValue(const ivec2 position, const ivec2 offset) { - return texelFetch(texture, position+offset, 0).r > 0.5; +bool hasValue(const mediump vec2 position, const mediump vec2 offset) { + return texture(textureData, position+offset).r > 0.5; } +#endif void main() { - const ivec2 position = ivec2(gl_FragCoord.xy*scaling); + #ifdef TEXELFETCH_USABLE + const mediump ivec2 position = ivec2(gl_FragCoord.xy*scaling); + #else + const mediump vec2 position = gl_FragCoord.xy*scaling*imageSizeInverted; + #endif /* If pixel at the position is inside (1), we are looking for nearest pixel outside and the value will be positive (> 0.5). If it is outside (0), we are looking for nearest pixel inside and the value will be negative (< 0.5). */ + #ifdef TEXELFETCH_USABLE const bool isInside = hasValue(position, ivec2(0, 0)); - const float sign = isInside ? 1.0 : -1.0; + #else + const bool isInside = hasValue(position, vec2(0.0, 0.0)); + #endif + const highp float sign = isInside ? 1.0 : -1.0; /* Minimal found distance is just out of the radius (i.e. infinity) */ - float minDistanceSquared = float((radius+1)*(radius+1)); + highp float minDistanceSquared = float((radius+1)*(radius+1)); /* Go in circles around the point and find nearest value */ int radiusLimit = radius; for(int i = 1; i <= radiusLimit; ++i) { for(int j = 0, jmax = i*2; j != jmax; ++j) { - const ivec2 offset = {-i+j, i}; + #ifdef TEXELFETCH_USABLE + const lowp ivec2 offset = ivec2(-i+j, i); + #else + const lowp vec2 pixelOffset = vec2(float(-i+j), float(i)); + const lowp vec2 offset = pixelOffset*imageSizeInverted; + #endif /* If any of the four values is opposite of what is on the pixel, we found nearest value */ @@ -63,7 +113,11 @@ void main() { hasValue(position, rotate(offset)) == !isInside || hasValue(position, rotate(rotate(offset))) == !isInside || hasValue(position, rotate(rotate(rotate(offset)))) == !isInside) { - const float distanceSquared = dot(vec2(offset), vec2(offset)); + #ifdef TEXELFETCH_USABLE + const mediump float distanceSquared = dot(vec2(offset), vec2(offset)); + #else + const mediump float distanceSquared = dot(pixelOffset, pixelOffset); + #endif /* Set smaller distance, if found, or continue with lookup for smaller */ @@ -73,7 +127,11 @@ void main() { /* Set radius limit to max radius which can contain smaller value, e.g. for distance 3.5 we can find smaller value even in radius 3 */ + #ifdef NEW_GLSL radiusLimit = min(radius, int(floor(length(vec2(offset))))); + #else + radiusLimit = int(min(float(radius), floor(length(vec2(offset))))); + #endif } } } diff --git a/src/TextureTools/DistanceFieldShader.vert b/src/TextureTools/DistanceFieldShader.vert index a412a0a20..c7970fe47 100644 --- a/src/TextureTools/DistanceFieldShader.vert +++ b/src/TextureTools/DistanceFieldShader.vert @@ -22,7 +22,15 @@ DEALINGS IN THE SOFTWARE. */ +#ifndef NEW_GLSL +attribute lowp vec4 position; +#endif + void main() { + #ifdef NEW_GLSL gl_Position = vec4((gl_VertexID == 2) ? 3.0 : -1.0, (gl_VertexID == 1) ? -3.0 : 1.0, 0.0, 1.0); + #else + gl_Position = position; + #endif }