pretty

Friday 13 December 2013

On Android NDK and Activity Lifecycle

After activity restarts calls to native methods may fail.

This can happen because shared library does not get reloaded. There is no symmetric method to System.loadLibrary(String libName) to manually unload or reload library. The shared library on Android will be unloaded when there are no processes that use it. Even when activity finishes its lifecycle and its onDestroy() method is called the host process of activity may still run. From the Android docs "the process hosting the activity may killed by the system at any time" after activity is finished. I think it would be killed when OS will need more memory for other tasks.

So, if the old process still sticks around so does the shared library. After activity starts again, its onCreate() called etc, it attaches to the same running process and deals with the same shared library instance. The shared library's data section where all static and global variables are stored is still in the state it was carrying old values.

Now, to highlight the possible problem, suppose we have a Singleton class on a native side. We call in to a native function to create it from activity's onCreate method. The native method may look like this.

CSomeSingleton* CSomeSingleton::GetInstance()
{
    if (m_Instance == NULL)
        m_Instance = new CSomeSingleton();
    return m_Instance;
}


In activity's onDestroy we call in to another native method that destroys it. Suppose it looks like this.

void CSomeSingleton::FreeInstance()
{
    delete m_Instance;
}


After acivity restarts and attaches to same process the problem arises. m_Instance will not be NULL, it will point to invalid address in memory.

There are 3 variants to unravel this issue.

1. Do not deinitialize your native-side objects at all (in activity's onDestroy()). When new activity instance will start it would attach to running process with valid pointers and global variables. If the process would be killed by the time, it would be started again, reloading the shared library, reinitializing the data section.

2. Find all places where the global/static variables where left in inconsistent state. For the above example that would be

void CSomeSingleton::FreeInstance()
{
    delete m_Instance;
    m_Instance = NULL;
}


3. Call System.exit(0) in onDestory() to stop the process and thus unload the shared library. Considered as a bad practice, because you may have yet another component that is running in that process, and this component may be not in the appropriate state to stop its execution (because it may have some unsaved data).

Thursday 31 October 2013

How to execute delegate synchronously on Windows Phone 8

On WP8 Dispatcher class does not have the Invoke() method to execute delegate synchronously, like it does in .NET Framework 4.5. In fact on WP8 platform this class has only three methods, two overloads of BeginInvoke() for asynchronous delegate execution, and CheckAccess().

To invoke delegate synchronously we can wrap Dispatcher object in DispatcherSynchronizationContext. The Send() method of DispatcherSynchronizationContext should be used to invoke delegate.

SendOrPostCallback used as the first argument in Send() is just to denote delegate type, the delegate used must return void and take one argument.
public delegate void SendOrPostCallback(object state);
Practically it is the same as if it would be Action<T>.

For example here is a code snippet were I launch delegate to be executed on UI thread, as I use Deployment.Current.Dispatcher. The delegate is invoked from non-UI thread which is about to get the display scale factor.

using System.Windows.Threading;

...

private double m_ScaleFactor = 1.0;

private void DelegateForScaleFactor(object state)
{
    m_ScaleFactor = 
     ((double)Application.Current.Host.Content.ScaleFactor / (double)100);
}

public double GetScaleFactor()
{
    DispatcherSynchronizationContext SyncContext = 
     new DispatcherSynchronizationContext(Deployment.Current.Dispatcher);
    SyncContext.Send(
     new System.Threading.SendOrPostCallback(DelegateForScaleFactor), null);
    return m_ScaleFactor;
}


Wednesday 25 September 2013

Draw beautiful font outlines with shaders


1. Introduction.

In this post I will describe how to add font outlines to anti-alised fonts using GLSL shaders. The described approach is not based on commonly used signed distance fields method. All that is needed is a font characters textures and quads to render them on.




2. Algorithm.

The process of adding outline to a character consists of two steps.

1. Make a character bolder.
2. Make this newly acquired boldness to be an outline.

This will be done in a fragment shader. The vertex shader will only fill in a current texture coordinate to be used in fragment shader.
const char* VertexShader = "void main()\
{\
    gl_TexCoord[0] = gl_MultiTexCoord0;\
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
}";
Now for the fragment shader.
const char* FragmentShader = "uniform vec4 FontColor;\
    uniform vec4 OutlineColor;\
    uniform sampler2D Tex;\
\           
void main()\
{\
vec2 TexCoord = gl_TexCoord[0].st;\
vec2 Offset = 1.0 / textureSize(Tex, 0);\
\
vec4 n = texture2D(Tex, vec2(TexCoord.x, TexCoord.y - Offset.y));\
vec4 e = texture2D(Tex, vec2(TexCoord.x + Offset.x, TexCoord.y));\
vec4 s = texture2D(Tex, vec2(TexCoord.x, TexCoord.y + Offset.y));\
vec4 w = texture2D(Tex, vec2(TexCoord.x - Offset.x, TexCoord.y));\
\
vec4 TexColor = texture2D(Tex, TexCoord);\
float GrowedAlpha = TexColor.a;\
GrowedAlpha = mix(GrowedAlpha, 1.0, s.a);\
GrowedAlpha = mix(GrowedAlpha, 1.0, w.a);\
GrowedAlpha = mix(GrowedAlpha, 1.0, n.a);\
GrowedAlpha = mix(GrowedAlpha, 1.0, e.a);\
\
vec4 OutlineColorWithNewAlpha = OutlineColor;\
OutlineColorWithNewAlpha.a = GrowedAlpha;\
vec4 CharColor = TexColor * FontColor;\
\
gl_FragColor = mix(OutlineColorWithNewAlpha, CharColor, CharColor.a);\
}";

Fragmend shader 3 inputs: FontColor, OutlineColor (these colors would be set by glUniform4f later on), Tex (the character texture bound to sampler).

In the first two lines we take the current texture coordinate and the minimum offset to neighboring coordinates. Next we take the colors of a 4 neigboring texture fragments, to the north, east, south and west of current location. We would need just the alpha values of these colors in the next block that makes heavy use of GLSL mix() function.

In the subsequent block the character is being nurtured. The coordinate with the alpha value of 0 will remain the same only if all of four neighbours have 0 alpha's. Suppose that the current fragment's alpha is 0 while northern fragment's alpha is 0.7 and eastern 0.1. The image shows that in this case GrowedAlpha value will be 0.73. This newly calculated alpha value is assigned to the outline color's alpha value.


In the final part of this shader we need to assign the new color to the fragment through writing it to gl_FragColor. If the font's texture color in this fragment is more transparent we should output the color that is closer to the outline color, because such fragments are closer to character edges. This is done using again glsl mix() linear interpolation function. It interpolates between character color and outline color so that transition between character color and outline would be smooth, without jaggedness.

Note, fully transparent fragments in character texture will be assigned the color of outline as it is. But the outline's alpha in such fragment would be 0, so fragment will remain fully transparent, as it should.


3. Implementation details.

To generate textures from the TTF fonts I propose to take well-known NeHe's Lesson #43 by Sven Olsen for a test. It uses FreeType library to generate char bitmaps from the given font, then it puts these in display lists of textured quads. The text is rendered through glCallLists() function.

First thing we need to do is to put our shader in Lesson43.cpp. I'll use GLEW library to get Open GL extension functions that are necessary for compiling and attaching shaders.
...
#include "GL/glew.h"
GLuint VertexShader, FragmentShader, Program;


void PrintShadeLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
        printf("%s\n",infoLog);
        OutputDebugString(infoLog);
        free(infoLog);
    }
}

void PrintProgramLog(GLuint obj)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;

    glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);

    if (infologLength > 0)
    {
        infoLog = (char *)malloc(infologLength);
        glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
        printf("%s\n",infoLog);
        OutputDebugString(infoLog);
        free(infoLog);
    }
}

void CreateShaders()
{
 glewInit();
 
 VertexShader = glCreateShader(GL_VERTEX_SHADER);
 FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
 
 const char* SourceVertexShader = "void main()\
 {\
 gl_TexCoord[0] = gl_MultiTexCoord0;\
 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;\
 }";
 
 const char* SourceFragmentShader = "uniform vec4 FontColor;\
 uniform vec4 OutlineColor;\
 uniform sampler2D Tex; \
 void main()\
 {\
 vec2 TexCoord = gl_TexCoord[0].st;\
 vec2 Offset = 1.0 / textureSize(Tex, 0);\
 \
 vec4 n = texture2D(Tex, vec2(TexCoord.x, TexCoord.y - Offset.y));\
 vec4 e = texture2D(Tex, vec2(TexCoord.x + Offset.x, TexCoord.y));\
 vec4 s = texture2D(Tex, vec2(TexCoord.x, TexCoord.y + Offset.y));\
 vec4 w = texture2D(Tex, vec2(TexCoord.x - Offset.x, TexCoord.y));\
 vec4 TexColor = texture2D(Tex, TexCoord);\
 \
 float GrowedAlpha = TexColor.a;\
 GrowedAlpha = mix(GrowedAlpha, 1.0, s.a);\
 GrowedAlpha = mix(GrowedAlpha, 1.0, w.a);\
 GrowedAlpha = mix(GrowedAlpha, 1.0, n.a);\
 GrowedAlpha = mix(GrowedAlpha, 1.0, e.a);\
 \
 vec4 OutlineColorWithNewAlpha = OutlineColor;\
 OutlineColorWithNewAlpha.a = GrowedAlpha;\
 vec4 CharColor = TexColor * FontColor;\
 \
 gl_FragColor = mix(OutlineColorWithNewAlpha, CharColor, CharColor.a);\
 }";
 
 glShaderSource(VertexShader, 1, &SourceVertexShader, NULL);
 glShaderSource(FragmentShader, 1, &SourceFragmentShader, NULL);
 
 glCompileShader(VertexShader);
 glCompileShader(FragmentShader);
 
 PrintShadeLog(VertexShader);
 PrintShadeLog(FragmentShader);
 
 Program = glCreateProgram();
 glAttachShader(Program,VertexShader);
 glAttachShader(Program,FragmentShader);
 glLinkProgram(Program);
 PrintProgramLog(Program);
 glUseProgram(Program);
 
 GLint FontColor = glGetUniformLocation(Program, "FontColor");
 glUniform4f(FontColor, 1.0, 0.0, 0.0, 1.0);
 
 GLint OutlineColor = glGetUniformLocation(Program, "OutlineColor");
 glUniform4f(OutlineColor, 40./255., 215./255., 92./255., 1.0);
}

You can invoke the CreateShaders() function at the end of CreateGLWindow() in Lesson43.cpp.
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
 ...
 CreateShaders();
 return TRUE;
} 
Now running the project you will see something like


The problem with this ugly result is related to a glyph (character) texture size. Depending on a font and specific character, there might be no spare space in texture for the outline. The above image shows the Arial Black italic font with no extra spacing above and below glyphs. We need to make Freetype library to add extra 1-pixel padding for every glyph's texture.

Open the freetype.cpp source file in Lesson43 and add the following function.
void AddPadding(FT_Bitmap& bitmap)
{
 int NewWidth = bitmap.width + 2;
 int NewHeight = bitmap.rows + 2;

 unsigned char* OldBuffer = bitmap.buffer;
 bitmap.buffer = new unsigned char [NewWidth * NewHeight];
 memset(bitmap.buffer, 0, NewWidth * NewHeight);

 for(int j = 0; j < bitmap.rows; j++) 
 {
  for(int i = 0; i < bitmap.width; i++) 
  {
   int IndexNew = (i+1)+((j+1)*NewWidth);
   int IndexOld = i+(j*bitmap.width);

   bitmap.buffer[IndexNew] = OldBuffer[IndexOld];
  }
 }

 bitmap.width = NewWidth;
 bitmap.rows = NewHeight;
}

It should be called from make_dlist() function.

void make_dlist ( FT_Face face, char ch, GLuint list_base, GLuint * tex_base ) {
...

 //This reference will make accessing the bitmap easier
 FT_Bitmap& bitmap=bitmap_glyph->bitmap;

 AddPadding(bitmap); // <-- Add extra 1 pixel padding
                    // on each side of texture
 //Use our helper function to get the widths of
 //the bitmap data that we will need in order to create
 //our texture.
 int width = next_p2( bitmap.width );
...
The result.

Friday 29 March 2013

Extracting tesselation from OpenGL Sample Implementation


What can be easier than ask your renderer to draw a polygon for you. However, that's not the case with OpenGL ES library. The most sophisticated object it can basically visualize is a triangle. So if you want a polygon you should compose it from lots of triangles. The process is known as tesselation or triangulation.

On most desktop systems there is a separate OpenGL-companion library named OpenGL Utility library (GLU), it is able to perform polygon tesselation. This library is also available for iPhone.

If your target system is known to have GLU you can link to this library and use tesselation functionality from it. See an example provided by Michael Fötsch, he linked to GLU for tesselation from his Direct3D application. If your target system does not have GLU you might consider writing your own tesselator or use one of the existing.

One of the best choices would be to extract tesselation functionality from OpenGL Sample Implementation.

It stood the test of time. Also, tesselation procedure from OpenGL Sample Implementation doesn't require external dependencies. You can just include sources in your project. Here are the steps how to do it for c++ project in Visual Studio.

Step 1.
Download OpenGL Sample Implementation. There are two versions there I have chosen the latest ogl-sample.20000807.tgz. We just need to isolate the tesselator from it. Create \libtess folder in your project's source directory. Copy all files from \ogl-sample.20000807\main\gfx\lib\glu\libtess to your \libtess folder.

Step 2.
Copy \ogl-sample.20000807\main\gfx\lib\glu\include\gluos.h to your peoject's \libtess as well.

Step 3.
Comment away the #include from tess.h and mesh.h in \libtess. This header brings a lot of dependencies we don't want. In mesh.h add #include "gluos.h" instead.

Step 4.
Now if you'll try to compile OGLTesselator you would probably see a lot of errors like

"error C2146: syntax error : missing ';' before identifier 'coords'".

This is due to a lot of missing OpenGL defines and typedefs. For instance, GLdouble define is not visible from mesh.h. These defines may be found in gl.h header file, however, this file is not in OpenGL Sample Implementation. You can find it in Windows SDK, also it is usually supplied with Visual Studio. This file contains a lot of declarations for functions unrelated to our task. So you might want to copy just the #defines and typedefs from this file. Put them to \libtess\gluos.h that you have just copied to \libtess dir.

Make sure that _WIN32 macro is disabled if you don't want windows.h dependencies, so that GLAPI and WINGDIAPI would be disabled.

Step 5.
Open tess.h and after
struct GLUtesselator {
...
};
add
typedef struct GLUtesselator GLUtesselator;
This is to enable GLUtesselator object to be declared without the struct keyword in libtess c-language files.

Step 6.
Now we have error C2371 related to undefined gluTess*() functions. Just after the above typedef in tess.h add these functions definitions (with c-linkage).
#ifdef __cplusplus
   extern "C" {
#endif
 
void GLAPI gluTessBeginPolygon(GLUtesselator* tess, void *data);

void GLAPI gluTessBeginContour(GLUtesselator *tess);

void GLAPI gluTessEndContour(GLUtesselator *tess);

void GLAPI gluTessEndPolygon(GLUtesselator *tess);

GLUtesselator * GLAPI gluNewTess(void);

void GLAPI 
gluTessCallback(GLUtesselator *tess, GLenum which, void (GLAPI *fn)());

void GLAPI 
gluTessProperty(GLUtesselator *tess, GLenum which, GLdouble value);

void GLAPI 
gluTessNormal(GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z);

void GLAPI 
gluTessVertex(GLUtesselator *tess, GLdouble coords[3], void *data);

void GLAPI gluDeleteTess(GLUtesselator *tess );

#ifdef __cplusplus
   }
#endif
Now remove priority-heap.c (in \libtess) from your project. GLU tesselator build should succeed.

Step 7.
A simple test for tesselator, with a polygon forming the shape of letter 'E'.
#include "OGLTesselator.h" 
#include "Libtess\gluos.h"
#include "Libtess\tess.h"
#include <assert.h> 
#include <iostream>

GLenum g_ErrorCode = 0;

void VertexCallback(GLvoid* index)
{
 static int Count = 0;
 std::cout << (int)index << (++Count % 3 == 0 ? '\n' : ',');
}

void ErrorCallback(GLenum errorCode)
{
 g_ErrorCode = errorCode;
}

void EdgeFlagCallback(GLboolean flag) 
{
 // Indicates which edges lie on the polygon boundary 
 // (so to enable us to draw outlines), also 
 // if this callback is provided triangle fans and strips are
 // converted to independent triangles
}

void TesselatorTest()
{
 double data[] = {
  -128, -23, 0, 
  -128, 23, 0,
  -94, 23, 0,
  -94, 15, 0,
  -119, 15, 0,
  -119, 5, 0,
  -96, 5, 0,
  -96, -3, 0,
  -119, -3, 0,
  -119, -15, 0,
  -93, -15, 0,
  -93, -23, 0
 };

 GLUtesselator* tesselator = gluNewTess();
 gluTessCallback(tesselator, GLU_TESS_VERTEX, 
 (GLvoid (GLAPI *) ()) &VertexCallback);
 gluTessCallback(tesselator, GLU_TESS_EDGE_FLAG,
 (GLvoid (GLAPI *) ())(&EdgeFlagCallback)); 
 gluTessCallback(tesselator, GLU_TESS_ERROR, 
  (GLvoid (GLAPI *) ()) &ErrorCallback);
    
 gluTessBeginPolygon(tesselator, NULL);
 gluTessBeginContour(tesselator);

 for (int i = 0; i < 12; ++i) 
     gluTessVertex(tesselator, &data[i * 3], (void*)i);

 gluTessEndContour(tesselator);
 gluTessEndPolygon(tesselator);
 gluDeleteTess(tesselator);

 assert(g_ErrorCode == 0);
}