Browse Source

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.
pull/278/head
Vladimír Vondruš 13 years ago
parent
commit
b5743c9447
  1. 1
      PKGBUILD-es2
  2. 1
      PKGBUILD-es2desktop
  3. 1
      PKGBUILD-es3
  4. 2
      src/Text/DistanceFieldGlyphCache.cpp
  5. 128
      src/TextureTools/DistanceField.cpp
  6. 20
      src/TextureTools/DistanceField.h
  7. 86
      src/TextureTools/DistanceFieldShader.frag
  8. 8
      src/TextureTools/DistanceFieldShader.vert

1
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

1
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

1
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

2
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) {

128
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<Extensions::GL::ARB::explicit_uniform_location>())
#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<Extensions::GL::ARB::shading_language_420pack>())
#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();
}
}}

20
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
}}

86
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
}
}
}

8
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
}

Loading…
Cancel
Save