Search code examples
windowsformstextdirectx-11slimdx

SlimDX DirectX11 and Text Sprites


I have a SlimDX RenderForm (which inherits System.Windows.Forms.Form) in which I have a Panel on which I render a map. I want to draw the names of cities over the map. The text should not change size as a user zooms in and out of the map, but simply spread further apart and closer together. I have thought of 5 ways to do this, and each is presenting a problem:

  1. Use sprites to draw the texts. Problem: DirectX11 and Sprites don't mix.

  2. Wait for the DirectX11 to to finish drawing, then manually draw the text onto the panel myself using GDI. Problem: processing as many texts as I have takes a little too long, and the texts have an annoying flash on every repaint.

  3. Create a custom class based on Label, set the class to allow for transparent background, set the Panel being drawn as its parent, and just move those labels around as the user zooms. Problem: Giving the label a transparent background does not actually make it transparent. It just means it's borrowing the background color of the Panel DirectX is drawing on and setting the custom class's background to that. I've also tried overriding the OnPaintBackground function to do nothing, but that just gives the label a black background.

  4. Give the Form a TransparencyKey, and give the panels a background of that color. Then, BEHIND those panels, make entirely new RenderForms on which the DirectX drawing takes place, and place the texts in Labels which are on the now transparent Panels. Problem: Clicking on the map space gives the RenderForms in the back Focus, which means the texts disappear behind those Forms when clicking into that space. I could give the main Form AlwaysOnTop, but I really don't want to do that, because the user might want to do something else while my app is running. Plus, if I bring up another window, then go back to my app, those forms don't show up behind my main Form anymore.

  5. Do something similar to 3, and manipulate the Region property to match the text's GraphicsPath. Problem: when I move them around, it sometimes does not honor the Region I've made, and goes back to a black background.

Is there any solution I've missed, or any way to make any of these work?

EDIT:

Based on the suggestion of an answer given, I am now trying to use the GeometryShader to essentially make a sprite generator. But when I only have 6 different sprites, it takes too long to draw. It's acceptable with only 1 sprite, but when I try to add more, it becomes too much.


Solution

  • I've solved this problem in my own engine. I essentially create a particle system where the particle vertices contain the world position, color, character index, size, and size mode. This point particle is then converted to a sprite in the geometry shader. The two modes are constant world size (the sprite is a constant size in world coordinates) or constant screen size (the sprite is constant size in screen coordinates). You then need to write a method that takes in a string and outputs this list of particles for each vertex. If you do it correctly, the geometry shader does all of the work for you and you don't need to do any vertex buffer updates to position the text even when the camera moves.

    As far as the sprite batching, there is no need to worry about draw order. Discard the transparent pixels around each pixel to prevent a depth write.

    Be careful trying to place transparent .Net controls on the panel you are drawing to. This will require the control to redraw every time you present your scene which causes .Net to limit your frame rate (really bad!). If needed I place controls on my d3d panel, but they are opaque this not requiring constant painting.

    Hope that helps. Let me know if you need more detail.

    -- Update --

    Let me define 2 terms. The "character sprite" is a small sprite that contains the image of a single character. The "string sprite" is the collection of character sprites that move and behave as one large sprite with the appearance of several characters.

    The user defines the position of the string sprite in world space. Each character in the string is used to define the character sprites. Specifically, each character sprite is given a screen-space offset from the position of the string sprite. This allows all of the sprites to be positioned in 3d space without all of the characters being on top of each other in screen-space.

    Each character sprite is a point vertex with "position" (float3), "texture coordinates" (float2), "texture number" (int), "screenspace offset" (float2), and "color" (float4). "Position" is the same for all character sprites in a string sprite." The texture for each character can either be defined in a single image where each character sprite is given the proper texture coordinates (what I do) or in a texture array.

    enter image description here

    -- Update --

    Here is an image that I use for text (low quality).

    enter image description here

    Here is the table of texture coordinates that are assigned to a character sprite based on its character.

    Char    ASCII   StrtPix EndPix  PixWdth StartUTexCoord  EndUTexCoord
    
        32  0   0   40  0   0
    !   33  16  34  18  0.002222222 0.004722222
    "   34  56  97  41  0.007777778 0.013472222
    #   35  113 197 84  0.015694444 0.027361111
    $   36  212 279 67  0.029444444 0.03875
    %   37  290 412 122 0.040277778 0.057222222
    &   38  423 512 89  0.058750000 0.071111111
    \   39  512 567 55  0.071111111 0.07875
    (   40  572 611 39  0.079444444 0.084861111
    )   41  625 664 39  0.086805556 0.092222222
    *   42  681 743 62  0.094583333 0.103194444
    +   43  761 842 81  0.105694444 0.116944444
    ,   44  859 888 29  0.119305556 0.123333333
    -   45  902 945 43  0.125277778 0.13125
    .   46  981 997 16  0.136250000 0.138472222
    /   47  991 1048    57  0.137638889 0.145555556
    0   48  1122    1188    66  0.155833333 0.165
    1   49  1142    1194    52  0.158611111 0.165833333
    2   50  1214    1278    64  0.168611111 0.1775
    3   51  1292    1355    63  0.179444444 0.188194444
    4   52  1365    1436    71  0.189583333 0.199444444
    5   53  1450    1511    61  0.201388889 0.209861111
    6   54  1524    1591    67  0.211666667 0.220972222
    7   55  1602    1668    66  0.222500000 0.231666667
    8   56  1680    1748    68  0.233333333 0.242777778
    9   57  1756    1824    68  0.243888889 0.253333333
    :   58  1849    1866    17  0.256805556 0.259166667
    ;   59  1896    1925    29  0.263333333 0.267361111
    <   60  1954    2027    73  0.271388889 0.281527778
    =   61  2054    2129    75  0.285277778 0.295694444
    >   62  2156    2229    73  0.299444444 0.309583333
    ?   63  2250    2305    55  0.312500000 0.320138889
    @   64  2319    2425    106 0.322083333 0.336805556
    A   65  2434    2518    84  0.338055556 0.349722222
    B   66  2528    2599    71  0.351111111 0.360972222
    C   67  2607    2685    78  0.362083333 0.372916667
    D   68  2698    2779    81  0.374722222 0.385972222
    E   69  2792    2856    64  0.387777778 0.396666667
    F   70  2871    2932    61  0.398750000 0.407222222
    G   71  2936    3020    84  0.407777778 0.419444444
    H   72  3036    3109    73  0.421666667 0.431805556
    I   73  3126    3165    39  0.434166667 0.439583333
    J   74  3172    3217    45  0.440555556 0.446805556
    K   75  3236    3312    76  0.449444444 0.46
    L   76  3321    3381    60  0.461250000 0.469583333
    M   77  3389    3473    84  0.470694444 0.482361111
    N   78  3492    3565    73  0.485000000 0.495138889
    O   79  3579    3667    88  0.497083333 0.509305556
    P   80  3682    3744    62  0.511388889 0.52
    Q   81  3751    3841    90  0.520972222 0.533472222
    R   82  3853    3932    79  0.535138889 0.546111111
    S   83  3935    4008    73  0.546527778 0.556666667
    T   84  4011    4091    80  0.557083333 0.568194444
    U   85  4097    4170    73  0.569027778 0.579166667
    V   86  4178    4264    86  0.580277778 0.592222222
    W   87  4266    4383    117 0.592500000 0.60875
    X   88  4389    4468    79  0.609583333 0.620555556
    Y   89  4469    4547    78  0.620694444 0.631527778
    Z   90  4552    4625    73  0.632222222 0.642361111
    [   91  4642    4677    35  0.644722222 0.649583333
    \   92  4687    4744    57  0.650972222 0.658888889
    ]   93  4749    4793    44  0.659583333 0.665694444
    ^   94  4805    4886    81  0.667361111 0.678611111
    _   95  4893    4975    82  0.679583333 0.690972222
    '   96  4982    4998    16  0.691944444 0.694166667
    a   97  5010    5071    61  0.695833333 0.704305556
    b   98  5089    5152    63  0.706805556 0.715555556
    c   99  5161    5219    58  0.716805556 0.724861111
    d   100 5226    5287    61  0.725833333 0.734305556
    e   101 5303    5367    64  0.736527778 0.745416667
    f   102 5373    5418    45  0.746250000 0.7525
    g   103 5418    5479    61  0.752500000 0.760972222
    h   104 5499    5559    60  0.763750000 0.772083333
    i   105 5577    5597    13  0.774583333 0.776378888
    j   106 5595    5634    39  0.777083333 0.7825
    k   107 5652    5715    63  0.785000000 0.79375
    l   108 5725    5738    13  0.795138889 0.796944444
    m   109 5757    5860    103 0.799583333 0.813888889
    n   110 5878    5937    59  0.816388889 0.824583333
    o   111 5952    6016    64  0.826666667 0.835555556
    p   112 6030    6093    63  0.837500000 0.84625
    q   113 6103    6165    62  0.847638889 0.85625
    r   114 6184    6229    45  0.858888889 0.865138889
    s   115 6232    6287    55  0.865555556 0.873194444
    t   116 6293    6338    45  0.874027778 0.880277778
    u   117 6348    6407    59  0.881666667 0.889861111
    v   118 6419    6488    69  0.891527778 0.901111111
    w   119 6493    6588    95  0.901805556 0.915
    x   120 6593    6661    68  0.915694444 0.925138889
    y   121 6666    6735    69  0.925833333 0.935416667
    z   122 6742    6798    56  0.936388889 0.944166667
    {   123 6810    6869    59  0.945833333 0.954027778
    |   124 6900    6913    13  0.958333333 0.960138889
    }   125 6944    7003    59  0.964444444 0.972638889
    ~   126 7022    7104    82  0.975277778 0.986666667
    

    Here is a snippet from the HLSL geometry shader I use. With this shader, and the appropriate vertex and pixel shaders, I can render entire paragraphs of text with virtually to impact on performance. >1,000 characters is unnoticable.

    struct VertexOutput
    {
        float4 Pos  : SV_Position;
        float4 Color    : COLOR;
        float2 TexCoords    : TEXCOORD;
        float4 Size : TEXCOORD1;
    };
    
    struct GeometryOutput
    {
        float4 Pos  : SV_Position;
        float4 Color    : COLOR;
        float2 TexCoords    : TEXCOORDS;
    };
    
    struct Camera
    {
        float4x4 ViewProjection;
        float4x4 Projection;
        float4x4 View;
        float4 Position;
        float4 LookAt;
    };
    
    texture2D <float> TextTextures : register(t6);
    
    [maxvertexcount(4)]
    void GShader( point VertexOutput Input[1], inout TriangleStream<GeometryOutput> OutputStream )
    {
        if(Input[0].TexCoords.x != Input[0].TexCoords.y)
        {
            GeometryOutput Output1 = (GeometryOutput)0;
            GeometryOutput Output2 = (GeometryOutput)0;
            GeometryOutput Output3 = (GeometryOutput)0;
            GeometryOutput Output4 = (GeometryOutput)0;
    
            if(Input[0].Pos.w == 1)
                Output1.Pos = mul(Input[0].Pos, Cameras[0].ViewProjection);
            else
            {
                Input[0].Pos.w = 1;
                Output1.Pos = mul(Input[0].Pos, Cameras[0].ViewProjection);
                Output1.Pos.xyz /= Output1.Pos.w;
                Output1.Pos.w = 1;
            }
            Output2.Pos = Output1.Pos;
            Output3.Pos = Output1.Pos;
            Output4.Pos = Output1.Pos;
    
            Output1.Pos.x += (Input[0].Size.z - Input[0].Size.x) / Resolution.x/2;
            Output2.Pos.x += (Input[0].Size.z + Input[0].Size.x) / Resolution.x/2;
            Output3.Pos.x += (Input[0].Size.z - Input[0].Size.x) / Resolution.x/2;
            Output4.Pos.x += (Input[0].Size.z + Input[0].Size.x) / Resolution.x/2;
    
            Output1.Pos.y += (Input[0].Size.w - Input[0].Size.y)  / Resolution.y/2;
            Output2.Pos.y += (Input[0].Size.w - Input[0].Size.y)  / Resolution.y/2;
            Output3.Pos.y += (Input[0].Size.w + Input[0].Size.y)  / Resolution.y/2;
            Output4.Pos.y += (Input[0].Size.w + Input[0].Size.y)  / Resolution.y/2;
    
            Output1.Color = Input[0].Color;
            Output2.Color = Input[0].Color;
            Output3.Color = Input[0].Color;
            Output4.Color = Input[0].Color;
    
            Output1.TexCoords = float2(Input[0].TexCoords.x,1);
            Output2.TexCoords = float2(Input[0].TexCoords.y,1);
            Output3.TexCoords = float2(Input[0].TexCoords.x,0);
            Output4.TexCoords = float2(Input[0].TexCoords.y,0);
    
            OutputStream.Append( Output1 );
            OutputStream.Append( Output3 );
            OutputStream.Append( Output2 );
            OutputStream.Append( Output4 );
        }
    }