Convert floating-point numbers to decimal digits in GLSL

+1 for interesting problem. Was curious so I tried to code this. I need the use of arrays so I chose #version 420 core. My app is rendering single quad covering screen with coordinates <-1,+1>. I am using whole ASCII 8×8 pixel 32×8 characters font texture I created some years ago:

font

The vertex is simple:

//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
layout(location=0) in vec4 vertex;
out vec2 pos;   // screen position <-1,+1>
void main()
    {
    pos=vertex.xy;
    gl_Position=vertex;
    }
//---------------------------------------------------------------------------

Fragment is a bit more complicated:

//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
in vec2 pos;                    // screen position <-1,+1>
out vec4 gl_FragColor;          // fragment output color
uniform sampler2D txr_font;     // ASCII 32x8 characters font texture unit
uniform float fxs,fys;          // font/screen resolution ratio
//---------------------------------------------------------------------------
const int _txtsiz=32;           // text buffer size
int txt[_txtsiz],txtsiz;        // text buffer and its actual size
vec4 col;                       // color interface for txt_print()
//---------------------------------------------------------------------------
void txt_decimal(float x)       // print float x into txt
    {
    int i,j,c;          // l is size of string
    float y,a;
    const float base=10;
    // handle sign
    if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
     else      { txt[txtsiz]='+'; txtsiz++; }
    // divide to int(x).fract(y) parts of number
    y=x; x=floor(x); y-=x;
    // handle integer part
    i=txtsiz;                   // start of integer part
    for (;txtsiz<_txtsiz;)
        {
        a=x;
        x=floor(x/base);
        a-=base*x;
        txt[txtsiz]=int(a)+'0'; txtsiz++;
        if (x<=0.0) break;
        }
    j=txtsiz-1;                 // end of integer part
    for (;i<j;i++,j--)      // reverse integer digits
        {
        c=txt[i]; txt[i]=txt[j]; txt[j]=c;
        }
    // handle fractional part
    for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
        {
        y*=base;
        a=floor(y);
        y-=a;
        txt[txtsiz]=int(a)+'0'; txtsiz++;
        if (y<=0.0) break;
        }
    txt[txtsiz]=0;  // string terminator
    }
//---------------------------------------------------------------------------
void txt_print(float x0,float y0)   // print txt at x0,y0 [chars]
    {
    int i;
    float x,y;
    // fragment position [chars] relative to x0,y0
    x=0.5*(1.0+pos.x)/fxs; x-=x0;
    y=0.5*(1.0-pos.y)/fys; y-=y0;
    // inside bbox?
    if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
    // get font texture position for target ASCII
    i=int(x);               // char index in txt
    x-=float(i);
    i=txt[i];
    x+=float(int(i&31));
    y+=float(int(i>>5));
    x/=32.0; y/=8.0;    // offset in char texture
    col=texture2D(txr_font,vec2(x,y));
    }
//---------------------------------------------------------------------------
void main()
    {
    col=vec4(0.0,1.0,0.0,1.0);  // background color
    txtsiz=0;
    txt[txtsiz]='F'; txtsiz++;
    txt[txtsiz]='l'; txtsiz++;
    txt[txtsiz]='o'; txtsiz++;
    txt[txtsiz]='a'; txtsiz++;
    txt[txtsiz]='t'; txtsiz++;
    txt[txtsiz]=':'; txtsiz++;
    txt[txtsiz]=' '; txtsiz++;
    txt_decimal(12.345);
    txt_print(1.0,1.0);
    gl_FragColor=col;
    }
//---------------------------------------------------------------------------

Here my CPU side uniforms:

    glUniform1i(glGetUniformLocation(prog_id,"txr_font"),0);
    glUniform1f(glGetUniformLocation(prog_id,"fxs"),(8.0)/float(xs));
    glUniform1f(glGetUniformLocation(prog_id,"fys"),(8.0)/float(ys));

where xs,ys is my screen resolution. Font is 8×8 in unit 0

Here output for the test fragment code:

screenshot

If your floating point accuracy is decreased due to HW implementation then you should consider printing in hex where no accuracy loss is present (using binary access). That could be converted to decadic base on integers later …

see:

[Edit2] old style GLSL shaders

I tried to port to old style GLSL and suddenly it works (before it would not compile with arrays present but when I think of it I was trying char[] which was the real reason).

//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
varying vec2 pos;   // screen position <-1,+1>
void main()
    {
    pos=gl_Vertex.xy;
    gl_Position=gl_Vertex;
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
varying vec2 pos;                   // screen position <-1,+1>
uniform sampler2D txr_font;     // ASCII 32x8 characters font texture unit
uniform float fxs,fys;          // font/screen resolution ratio
//---------------------------------------------------------------------------
const int _txtsiz=32;           // text buffer size
int txt[_txtsiz],txtsiz;        // text buffer and its actual size
vec4 col;                       // color interface for txt_print()
//---------------------------------------------------------------------------
void txt_decimal(float x)       // print float x into txt
    {
    int i,j,c;          // l is size of string
    float y,a;
    const float base=10.0;
    // handle sign
    if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
     else      { txt[txtsiz]='+'; txtsiz++; }
    // divide to int(x).fract(y) parts of number
    y=x; x=floor(x); y-=x;
    // handle integer part
    i=txtsiz;                   // start of integer part
    for (;txtsiz<_txtsiz;)
        {
        a=x;
        x=floor(x/base);
        a-=base*x;
        txt[txtsiz]=int(a)+'0'; txtsiz++;
        if (x<=0.0) break;
        }
    j=txtsiz-1;                 // end of integer part
    for (;i<j;i++,j--)      // reverse integer digits
        {
        c=txt[i]; txt[i]=txt[j]; txt[j]=c;
        }
    // handle fractional part
    for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
        {
        y*=base;
        a=floor(y);
        y-=a;
        txt[txtsiz]=int(a)+'0'; txtsiz++;
        if (y<=0.0) break;
        }
    txt[txtsiz]=0;  // string terminator
    }
//---------------------------------------------------------------------------
void txt_print(float x0,float y0)   // print txt at x0,y0 [chars]
    {
    int i;
    float x,y;
    // fragment position [chars] relative to x0,y0
    x=0.5*(1.0+pos.x)/fxs; x-=x0;
    y=0.5*(1.0-pos.y)/fys; y-=y0;
    // inside bbox?
    if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
    // get font texture position for target ASCII
    i=int(x);               // char index in txt
    x-=float(i);
    i=txt[i];
    x+=float(int(i-((i/32)*32)));
    y+=float(int(i/32));
    x/=32.0; y/=8.0;    // offset in char texture
    col=texture2D(txr_font,vec2(x,y));
    }
//---------------------------------------------------------------------------
void main()
    {
    col=vec4(0.0,1.0,0.0,1.0);  // background color
    txtsiz=0;
    txt[txtsiz]='F'; txtsiz++;
    txt[txtsiz]='l'; txtsiz++;
    txt[txtsiz]='o'; txtsiz++;
    txt[txtsiz]='a'; txtsiz++;
    txt[txtsiz]='t'; txtsiz++;
    txt[txtsiz]=':'; txtsiz++;
    txt[txtsiz]=' '; txtsiz++;
    txt_decimal(12.345);
    txt_print(1.0,1.0);
    gl_FragColor=col;
    }
//---------------------------------------------------------------------------

Leave a Comment