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:
Use sprites to draw the texts. Problem: DirectX11 and Sprites don't mix.
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.
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.
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.
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.
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.
-- Update --
Here is an image that I use for text (low quality).
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 );
}
}