This tutorial is part of a Collection: 04. DirectX 12 - Braynzar Soft Tutorials
rate up
1
rate down
16348
views
bookmark
11. Drawing text in DirectX 12

In this tutorial we will learn how to draw some bitmap text in directx 12. We will also learn how to use a high precision timer to get our frames per second as well as keep the timing of our game logic consistent, where things move based on time rather than based on how fast the computer runs as in the previous tutorials.

BzTuts11.rar 356.08 kb
1599 downloads
####Introduction#### This is going to be the last DirectX 12 tutorial i'm going to write. We will learn how to draw text in DirectX 12 in this tutorial using a bitmap font. To show the text off, we will display the frames per second using the QueryPerformanceTimer API. We will learn how to create a bitmap font using a tool called Hiero, which also outputs a fnt file we can read to know how to render each character in the font, such as placement offset and texture coordinates. There are a lot of tools you can use to create bitmap fonts, and you could definitely create your own even, but I chose Hiero because it did exactly what we need, and also has an option for a distance field effect, which we will talk about after the tutorial, which you can use to make your text render smoothly at pretty much any scale. Let's start with creating a bitmap font. ####Hiero#### Hiero font generator is a tool build in Java which is included in the libgxd nightly build. LibGDX is a cross-platform game development framework build in Java. We will only be needing the Hiero font generator from this. First download and extract the latest LibGDX nightly build from .[https://libgdx.badlogicgames.com/nightlies/][here] into a new folder. The archive should be called something like *libgdx-nightly-latest.zip*. Once you have extracted the archive, go into the new folder you just extracted the archive into. Create a new text document, name it something like hiero.bat. This will be the batch file we execute to run Hiero. Open the bat file in a text editor and paste these contents into it: java -cp gdx.jar;gdx-natives.jar;gdx-backend-lwjgl.jar;gdx-backend-lwjgl-natives.jar;extensionsgdx-freetypegdx-freetype.jar;extensionsgdx-freetypegdx-freetype-natives.jar;extensionsgdx-toolsgdx-tools.jar com.badlogic.gdx.tools.hiero.Hiero You will have to make sure you have the Java runtime environment (jre) installed in order to run Hiero. If you still need to install the Java runtime, you can download the installer .[http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html][here]. After you have jre installed, and have created the bat file above, you should be able to execute the bat file by double clicking on it. If everything went well, Hiero will launch, and you will be presented with a window like this: +[http://www.braynzarsoft.net/image/100270][Hiero Font Generator] ####Generating the Bitmap Font#### Now that we have Hiero up and running, let's generate a bitmap font. The result of this will be 2 files, one containing information about the font and characters, and one containing the font image. First select the font you want in the top right combo box. In this tutorial, we will use Arial. Below the combo box, select the "Java" radio button for "Rendering". This will enable the "Effects" combo box on the top right. For this tutorial, we will not be using any effects, but you may want to implement distance field once you have your text rendering. Next select the "Glyph Cache" radio button above the font preview box at the bottom. By default "Sample Text" is selected. This will give us more options for the output image. You will see "Page width" and "Page height" to the right of the font preview box, under where you have selected "Glyph Cache". We will be making a 512x512 font image, so set these both to 512. No back up to the top right, right above where you set the rendering to "Java", you will see a size input. What you want to do is increment the size to just before "Pages" is 2 (you will see "Pages" above where you set the width and height of the output image). This will increase the size of the font as large as can possibly fit on one page (all the characters fit in the 512x512 image). With the Arial font i was able to increase the size to 73 before some characters had to be moved to the next page, where you would have two 512x512 font images instead of just one. On the bottom right is 4 input boxes for padding, for left, top, right and bottom. We want to give each character a little padding in the final output image so we have less a chance of sampling surrounding characters in the shaders when sampling the texture for a character. When the quad on the screen that we are drawing a character is too small, sampling from the texture is less accurate and may end up getting values from the next character. Let's set all these to 5 pixels for padding. That should be it for setting up. Here's a screen of what you would see if you followed along: +[http://www.braynzarsoft.net/image/100271][Hiero with Arial Font Configuration] Let's get our bitmap font files now. Click on "File" in the top menu, Then click "Save BMFont files (text)...". Choose a directory and name for the output files. I named it Arial.fnt. When you click save, it will save a .fnt file containing information about rendering the font, and .png containing the font image. The resulting bitmap looks like this: +[http://www.braynzarsoft.net/image/100274][Arial Bitmap Image] ####The Angel Code font format#### The output .fnt file that Hiero spit out above contains information about reading and rendering the font in **Angel Code font format**. Below is the .fnt file that was output from Hiero from the above configuration: info face="Arial" size=73 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=5,5,5,5 spacing=0,0 common lineHeight=95 base=68 scaleW=512 scaleH=512 pages=1 packed=0 page id=0 file="Arial.png" chars count=97 char id=0 x=392 y=340 width=47 height=57 xoffset=-6 yoffset=16 xadvance=65 page=0 chnl=0 char id=10 x=0 y=0 width=0 height=0 xoffset=-6 yoffset=0 xadvance=10 page=0 chnl=0 char id=32 x=0 y=0 width=0 height=0 xoffset=-6 yoffset=0 xadvance=30 page=0 chnl=0 char id=33 x=152 y=340 width=17 height=64 xoffset=-6 yoffset=9 xadvance=32 page=0 chnl=0 char id=34 x=301 y=456 width=31 height=30 xoffset=-6 yoffset=9 xadvance=36 page=0 chnl=0 char id=35 x=231 y=340 width=51 height=64 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=36 x=287 y=0 width=46 height=74 xoffset=-6 yoffset=6 xadvance=51 page=0 chnl=0 char id=37 x=303 y=81 width=67 height=66 xoffset=-6 yoffset=8 xadvance=75 page=0 chnl=0 char id=38 x=370 y=81 width=54 height=66 xoffset=-6 yoffset=8 xadvance=59 page=0 chnl=0 char id=39 x=332 y=456 width=17 height=30 xoffset=-6 yoffset=9 xadvance=24 page=0 chnl=0 char id=40 x=184 y=0 width=27 height=79 xoffset=-6 yoffset=9 xadvance=34 page=0 chnl=0 char id=41 x=211 y=0 width=28 height=79 xoffset=-6 yoffset=9 xadvance=34 page=0 chnl=0 char id=42 x=266 y=456 width=35 height=33 xoffset=-6 yoffset=9 xadvance=38 page=0 chnl=0 char id=43 x=136 y=456 width=45 height=45 xoffset=-6 yoffset=19 xadvance=53 page=0 chnl=0 char id=44 x=349 y=456 width=17 height=28 xoffset=-6 yoffset=56 xadvance=30 page=0 chnl=0 char id=45 x=454 y=456 width=31 height=17 xoffset=-6 yoffset=40 xadvance=34 page=0 chnl=0 char id=46 x=437 y=456 width=17 height=17 xoffset=-6 yoffset=56 xadvance=30 page=0 chnl=0 char id=47 x=169 y=340 width=31 height=64 xoffset=-6 yoffset=9 xadvance=30 page=0 chnl=0 char id=48 x=314 y=147 width=46 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=49 x=29 y=340 width=31 height=64 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=50 x=60 y=340 width=46 height=64 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=51 x=129 y=147 width=46 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=52 x=282 y=340 width=48 height=63 xoffset=-6 yoffset=10 xadvance=51 page=0 chnl=0 char id=53 x=106 y=340 width=46 height=64 xoffset=-6 yoffset=10 xadvance=51 page=0 chnl=0 char id=54 x=175 y=147 width=47 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=55 x=330 y=340 width=45 height=63 xoffset=-6 yoffset=10 xadvance=51 page=0 chnl=0 char id=56 x=222 y=147 width=46 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=57 x=268 y=147 width=46 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=58 x=484 y=340 width=17 height=50 xoffset=-6 yoffset=23 xadvance=30 page=0 chnl=0 char id=59 x=375 y=340 width=17 height=61 xoffset=-6 yoffset=23 xadvance=30 page=0 chnl=0 char id=60 x=44 y=456 width=46 height=45 xoffset=-6 yoffset=20 xadvance=53 page=0 chnl=0 char id=61 x=220 y=456 width=46 height=33 xoffset=-6 yoffset=25 xadvance=53 page=0 chnl=0 char id=62 x=90 y=456 width=46 height=45 xoffset=-6 yoffset=20 xadvance=53 page=0 chnl=0 char id=63 x=360 y=147 width=46 height=65 xoffset=-6 yoffset=8 xadvance=51 page=0 chnl=0 char id=64 x=107 y=0 width=77 height=80 xoffset=-6 yoffset=8 xadvance=84 page=0 chnl=0 char id=65 x=406 y=147 width=61 height=64 xoffset=-6 yoffset=9 xadvance=59 page=0 chnl=0 char id=66 x=0 y=212 width=50 height=64 xoffset=-6 yoffset=9 xadvance=59 page=0 chnl=0 char id=67 x=436 y=0 width=55 height=66 xoffset=-6 yoffset=8 xadvance=63 page=0 chnl=0 char id=68 x=50 y=212 width=54 height=64 xoffset=-6 yoffset=9 xadvance=63 page=0 chnl=0 char id=69 x=104 y=212 width=50 height=64 xoffset=-6 yoffset=9 xadvance=59 page=0 chnl=0 char id=70 x=154 y=212 width=46 height=64 xoffset=-6 yoffset=9 xadvance=55 page=0 chnl=0 char id=71 x=0 y=81 width=59 height=66 xoffset=-6 yoffset=8 xadvance=67 page=0 chnl=0 char id=72 x=200 y=212 width=52 height=64 xoffset=-6 yoffset=9 xadvance=63 page=0 chnl=0 char id=73 x=467 y=147 width=18 height=64 xoffset=-6 yoffset=9 xadvance=30 page=0 chnl=0 char id=74 x=424 y=81 width=40 height=65 xoffset=-6 yoffset=9 xadvance=47 page=0 chnl=0 char id=75 x=252 y=212 width=53 height=64 xoffset=-6 yoffset=9 xadvance=59 page=0 chnl=0 char id=76 x=305 y=212 width=44 height=64 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=77 x=349 y=212 width=60 height=64 xoffset=-6 yoffset=9 xadvance=71 page=0 chnl=0 char id=78 x=409 y=212 width=52 height=64 xoffset=-6 yoffset=9 xadvance=63 page=0 chnl=0 char id=79 x=59 y=81 width=60 height=66 xoffset=-6 yoffset=8 xadvance=67 page=0 chnl=0 char id=80 x=461 y=212 width=50 height=64 xoffset=-6 yoffset=9 xadvance=59 page=0 chnl=0 char id=81 x=333 y=0 width=59 height=70 xoffset=-6 yoffset=8 xadvance=67 page=0 chnl=0 char id=82 x=0 y=276 width=55 height=64 xoffset=-6 yoffset=9 xadvance=63 page=0 chnl=0 char id=83 x=119 y=81 width=52 height=66 xoffset=-6 yoffset=8 xadvance=59 page=0 chnl=0 char id=84 x=55 y=276 width=52 height=64 xoffset=-6 yoffset=9 xadvance=55 page=0 chnl=0 char id=85 x=0 y=147 width=52 height=65 xoffset=-6 yoffset=9 xadvance=63 page=0 chnl=0 char id=86 x=107 y=276 width=61 height=64 xoffset=-6 yoffset=9 xadvance=59 page=0 chnl=0 char id=87 x=168 y=276 width=81 height=64 xoffset=-6 yoffset=9 xadvance=82 page=0 chnl=0 char id=88 x=249 y=276 width=58 height=64 xoffset=-6 yoffset=9 xadvance=57 page=0 chnl=0 char id=89 x=307 y=276 width=58 height=64 xoffset=-6 yoffset=9 xadvance=57 page=0 chnl=0 char id=90 x=365 y=276 width=53 height=64 xoffset=-6 yoffset=9 xadvance=55 page=0 chnl=0 char id=91 x=239 y=0 width=24 height=79 xoffset=-6 yoffset=9 xadvance=30 page=0 chnl=0 char id=92 x=200 y=340 width=31 height=64 xoffset=-6 yoffset=9 xadvance=30 page=0 chnl=0 char id=93 x=263 y=0 width=24 height=79 xoffset=-6 yoffset=9 xadvance=30 page=0 chnl=0 char id=94 x=181 y=456 width=39 height=39 xoffset=-6 yoffset=9 xadvance=43 page=0 chnl=0 char id=95 x=0 y=506 width=55 height=17 xoffset=-6 yoffset=71 xadvance=51 page=0 chnl=0 char id=96 x=414 y=456 width=23 height=21 xoffset=-6 yoffset=9 xadvance=34 page=0 chnl=0 char id=97 x=439 y=340 width=45 height=52 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=98 x=464 y=81 width=44 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=99 x=0 y=404 width=44 height=52 xoffset=-6 yoffset=22 xadvance=47 page=0 chnl=0 char id=100 x=52 y=147 width=44 height=65 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=101 x=44 y=404 width=46 height=52 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=102 x=96 y=147 width=33 height=65 xoffset=-6 yoffset=8 xadvance=31 page=0 chnl=0 char id=103 x=392 y=0 width=44 height=67 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=104 x=418 y=276 width=42 height=64 xoffset=-6 yoffset=9 xadvance=51 page=0 chnl=0 char id=105 x=485 y=147 width=17 height=64 xoffset=-6 yoffset=9 xadvance=26 page=0 chnl=0 char id=106 x=62 y=0 width=29 height=80 xoffset=-6 yoffset=9 xadvance=26 page=0 chnl=0 char id=107 x=460 y=276 width=41 height=64 xoffset=-6 yoffset=9 xadvance=47 page=0 chnl=0 char id=108 x=491 y=0 width=17 height=64 xoffset=-6 yoffset=9 xadvance=26 page=0 chnl=0 char id=109 x=178 y=404 width=61 height=51 xoffset=-6 yoffset=22 xadvance=70 page=0 chnl=0 char id=110 x=239 y=404 width=42 height=51 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=111 x=90 y=404 width=46 height=52 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=112 x=171 y=81 width=44 height=66 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=113 x=215 y=81 width=44 height=66 xoffset=-6 yoffset=22 xadvance=51 page=0 chnl=0 char id=114 x=323 y=404 width=31 height=50 xoffset=-6 yoffset=23 xadvance=34 page=0 chnl=0 char id=115 x=136 y=404 width=42 height=52 xoffset=-6 yoffset=22 xadvance=47 page=0 chnl=0 char id=116 x=0 y=340 width=29 height=64 xoffset=-6 yoffset=10 xadvance=30 page=0 chnl=0 char id=117 x=281 y=404 width=42 height=51 xoffset=-6 yoffset=23 xadvance=51 page=0 chnl=0 char id=118 x=354 y=404 width=45 height=50 xoffset=-6 yoffset=23 xadvance=47 page=0 chnl=0 char id=119 x=399 y=404 width=65 height=50 xoffset=-6 yoffset=23 xadvance=63 page=0 chnl=0 char id=120 x=464 y=404 width=46 height=50 xoffset=-6 yoffset=23 xadvance=45 page=0 chnl=0 char id=121 x=259 y=81 width=44 height=66 xoffset=-6 yoffset=23 xadvance=45 page=0 chnl=0 char id=122 x=0 y=456 width=44 height=50 xoffset=-6 yoffset=23 xadvance=46 page=0 chnl=0 char id=123 x=0 y=0 width=31 height=81 xoffset=-6 yoffset=8 xadvance=34 page=0 chnl=0 char id=124 x=91 y=0 width=16 height=80 xoffset=-6 yoffset=9 xadvance=27 page=0 chnl=0 char id=125 x=31 y=0 width=31 height=81 xoffset=-6 yoffset=8 xadvance=34 page=0 chnl=0 char id=126 x=366 y=456 width=48 height=24 xoffset=-6 yoffset=30 xadvance=53 page=0 chnl=0 kernings count=96 kerning first=49 second=49 amount=-5 kerning first=87 second=59 amount=-1 kerning first=84 second=65 amount=-5 kerning first=84 second=79 amount=-1 kerning first=65 second=87 amount=-3 kerning first=82 second=84 amount=-1 kerning first=76 second=86 amount=-5 kerning first=82 second=87 amount=-1 kerning first=86 second=105 amount=-1 kerning first=89 second=97 amount=-5 kerning first=86 second=101 amount=-4 kerning first=102 second=102 amount=-1 kerning first=84 second=105 amount=-3 kerning first=84 second=111 amount=-8 kerning first=89 second=112 amount=-5 kerning first=89 second=113 amount=-7 kerning first=84 second=114 amount=-3 kerning first=87 second=117 amount=-1 kerning first=65 second=118 amount=-1 kerning first=65 second=119 amount=-1 kerning first=84 second=121 amount=-4 kerning first=87 second=45 amount=-1 kerning first=86 second=121 amount=-3 kerning first=89 second=44 amount=-9 kerning first=118 second=46 amount=-5 kerning first=87 second=58 amount=-1 kerning first=89 second=118 amount=-4 kerning first=65 second=32 amount=-4 kerning first=86 second=44 amount=-7 kerning first=89 second=105 amount=-3 kerning first=76 second=32 amount=-3 kerning first=86 second=114 amount=-3 kerning first=80 second=46 amount=-9 kerning first=70 second=44 amount=-8 kerning first=84 second=97 amount=-8 kerning first=84 second=32 amount=-1 kerning first=80 second=65 amount=-5 kerning first=76 second=87 amount=-5 kerning first=84 second=58 amount=-8 kerning first=89 second=111 amount=-7 kerning first=80 second=44 amount=-9 kerning first=65 second=86 amount=-5 kerning first=89 second=101 amount=-7 kerning first=84 second=115 amount=-8 kerning first=86 second=59 amount=-3 kerning first=86 second=117 amount=-3 kerning first=86 second=111 amount=-4 kerning first=89 second=117 amount=-4 kerning first=86 second=65 amount=-5 kerning first=89 second=58 amount=-4 kerning first=65 second=84 amount=-5 kerning first=86 second=58 amount=-3 kerning first=89 second=45 amount=-7 kerning first=119 second=44 amount=-4 kerning first=121 second=46 amount=-5 kerning first=89 second=65 amount=-5 kerning first=84 second=101 amount=-8 kerning first=118 second=44 amount=-5 kerning first=32 second=84 amount=-1 kerning first=70 second=46 amount=-8 kerning first=86 second=97 amount=-5 kerning first=82 second=89 amount=-1 kerning first=80 second=32 amount=-1 kerning first=87 second=111 amount=-1 kerning first=65 second=121 amount=-1 kerning first=87 second=114 amount=-1 kerning first=87 second=101 amount=-1 kerning first=84 second=46 amount=-8 kerning first=84 second=59 amount=-8 kerning first=32 second=89 amount=-1 kerning first=70 second=65 amount=-4 kerning first=87 second=121 amount=-1 kerning first=76 second=121 amount=-3 kerning first=89 second=59 amount=-5 kerning first=84 second=117 amount=-3 kerning first=121 second=44 amount=-5 kerning first=87 second=46 amount=-4 kerning first=82 second=86 amount=-1 kerning first=114 second=44 amount=-4 kerning first=87 second=65 amount=-3 kerning first=119 second=46 amount=-4 kerning first=87 second=44 amount=-4 kerning first=86 second=45 amount=-4 kerning first=89 second=32 amount=-1 kerning first=86 second=46 amount=-7 kerning first=76 second=84 amount=-5 kerning first=84 second=44 amount=-8 kerning first=65 second=89 amount=-5 kerning first=84 second=99 amount=-8 kerning first=32 second=65 amount=-4 kerning first=89 second=46 amount=-9 kerning first=84 second=119 amount=-4 kerning first=87 second=97 amount=-3 kerning first=114 second=46 amount=-4 kerning first=76 second=89 amount=-5 kerning first=84 second=45 amount=-4 There are three parts to the format; Information that applies to the font as a whole, information that applies to individual characters, and information that applies to kernings, which will be explained last. ##Font information## Lines start with: - **info** - *This is information about the font as a whole* - **common** - *This is information that is common between each character* - **page** - *This is information about the font image file(s). There will be one "page" line for each font image.* Info line - **face** - *Name of the font* - **size** - *Size of font in pixels. We will not use this, but you might have bitmap fonts for a couple different sizes. When we draw the font at 100% scale, a character that is 50 pixels wide will take up 50 pixels on the screen.* - **bold** - *0 if not bold, 1 if bold* - **italic** - *0 if not italic, 1 if italic* - **charset** - *not used* - **unicode** - *not used* - **stretchH** - *Height stretch of the font, 100%* - **smooth** - *not used* - **aa** - *not used* - **padding** - *top,right,bottom,left padding in pixels* - **spacing** - *not used* Common line - **lineHeight** - *How far to move down to the next line in pixels* - **base** - *The actual height of each character in pixels. We won't actually use this since each character has it's own height anyway.* - **scaleW** - *Width of the texture (font image)* - **scaleH** - *Height of the texture* - **pages** - *The number of font image files* - **packed** - *not used* Page lines (for each font image) - **id** - *The id of the texture* - **file** - *The filename of the font image (texture)* ##Character information## Each character line starts with "char", and contains information for one character in the font. - **id** - *The id of the character. We should be able to directly convert the "char" to an "int" and find the character who's id matches that int* - **x** - *The "U" texture coordinate* - **y** - *The "V" texture coordinate* - **width** - *The width in pixels of the character on the texture* - **height** - *The height of the character on the texture* - **xoffset** - *How much to offset the character in pixels from current x position when rendering (not when reading the character from the texture)* - **yoffset** - *How much to offset the character from the top of the line when rendering (not when reading from the texture)* - **xadvance** - *How many pixels to advance the current x position to the next character when rendering* - **page** - *The page id (font image file, or texture) containing the character* - **chnl** - *You can encode characters in any of the rgba channels. We are only using one channel so do not use this, but you could encode regular font on the r channel, bold font on the g channel, italic font on the b channel, and bold/italic on the a channel* ##Kerning information## Some characters when next to certain other characters, need to be offset horrizontally, for example "W" and "o". The "o" should sit under the right side of the "W", Wo, which means that the o needs to be offset to the left a little when next to W, otherwise there will be an unnatural space between W and o. Each kerning line starts with "kerning". - **first** - *The first characters id, or previous (left) character* - **second** - *The second characters id, or current (right) character* - **amount** - *The amount in pixels to offset the current, or second character on the x axis by* Here's a visual of the important factors to the font. The top "Wo" is how the characters might be positioned in the texture. The bottom "Wo" are how the characters should be rendered. +[http://www.braynzarsoft.net/image/100272][Font from texture to render] ####Drawing the Font#### We will take advantage of instancing to draw the font. This way, our vertex buffer can have a single vertex per character we want to draw. The vertex will contain the x and y screen coordinates, the width and height of the screen coordinates, the uv texture coordinates, and the u-width and v-width texture coordinates. The way we do this is when it comes time to draw, we tell the GPU to draw 4 vertices, because each character needs to be rendered to a quad. We tell it to draw numCharacter instances, so that the vertex shader is ran 4 times for each character. We will use a system value to get the vertex ID, SV_VertexID. This ID will be 0-3, depending on the id, we will know whether we are the top left, top right, bottom right, or bottom left vertex in the quad. If it sounds confusing, just read on until you get to the code, hopefully seeing the code will clear it up for you. One more thing to mention is that some of the values in the font file are in pixels, and must be converted into the coordinate space (0 to 1), (-1 to 1) or (1 to -1), but we will explain it more when we get to loading the file in. Now that we have creating the bitmap font and knowing how to read it out of the way, we can get on to the code. I want to forewarn you that this tutorials code is a little more "sloppy" than usual, but you should be able to get the idea of how to render font, then create a font module in your own application. ####New Structures#### We have a couple new structures in this tutorial. The first of which is the new vertex structure. ##TextVertex structure## This vertex structure has 3 float4's, one for position, one for texture coordinates, and one for color. This vertex structure actually only contains instance data. As mentioned above, one vertex is one character, so we need the x,y position of the top left of the characters quad, the width and height of the quad, the u,v of the top left of the character in the texture, and the width and height of the character in the texture, as well as the color of the character. You could just put color in a constant buffer if you don't expect any of the characters to be different colors, but i didn't for simplicity, we do not need a constant buffer for the text in this tutorial. The position is in screen space, which means they have to be converted to -1 to 1 on the x axis, and 1 to -1 on the y axis, and contains the top left x,y and the width and height of the quad to be rendered to, (x,y,width,height). The texture coordinate is in texture space, also has to be converted, which means 0-1 on both the u and v axes. The texCoord then looks like (u,v,uwidth,uheight) where uwidth and uheight are the width and height of the character on the texture. Finally we have color, which is in the form of RGBA. struct TextVertex { TextVertex(float r, float g, float b, float a, float u, float v, float tw, float th, float x, float y, float w, float h ) : color(r, g, b, a), texCoord(u, v, tw, th), pos(x, y, w, h) {} XMFLOAT4 pos; XMFLOAT4 texCoord; XMFLOAT4 color; }; ##FontChar structure## We need to have a structure containing information about each character in the font, which is what the FontChar structure does. We will have an array of these objects, one for each character in the font. I've commented the code so i won't explain it all here, but the id of the character is the same value that a char converted to int is, you should be able to directly cast a char to int to get the id of a char. struct FontChar { // the unicode id int id; // these need to be converted to texture coordinates // (where 0.0 is 0 and 1.0 is textureWidth of the font) float u; // u texture coordinate float v; // v texture coordinate float twidth; // width of character on texture float theight; // height of character on texture float width; // width of character in screen coords float height; // height of character in screen coords // these need to be normalized based on size of font float xoffset; // offset from current cursor pos to left side of character float yoffset; // offset from top of line to top of character float xadvance; // how far to move to right for next character }; ##FontKerning structure## The FontKerning structure contains information about kernings. I've already talked about kernings above, so I won't repeat here. struct FontKerning { int firstid; // the first character int secondid; // the second character float amount; // the amount to add/subtract to second characters x }; ##Font structure## Finally we have our font structure. This structure contains a couple methods, an array for both characters and kernings, and other members that describe the font. We also store a pointer to the font texture resource (SRV) and a handle to the SRV, so we can easily set the bound SRV for the font when needed. The GetKerning() method takes in the left character, the right character (where the right character is the current character you are working on), and loops through the kernings array. If it finds a match, it returns the kerning amount, or how much you should offset the character horrizontally, otherwise it returns 0, meaning there is no need to offset the right (current) character. The GetChar() is another simple method which just loops through all the characters and tries to find a matching character. If no character is found, it just returns a null pointer, and we should either skip that character or use a replacement character. A replacement character is the better way to go, but in this tutorial, we just skip the character. struct Font { std::wstring name; // name of the font std::wstring fontImage; int size; // size of font, lineheight and baseheight will be based on this as if this is a single unit (1.0) float lineHeight; // how far to move down to next line, will be normalized float baseHeight; // height of all characters, will be normalized int textureWidth; // width of the font texture int textureHeight; // height of the font texture int numCharacters; // number of characters in the font FontChar* CharList; // list of characters int numKernings; // the number of kernings FontKerning* KerningsList; // list to hold kerning values ID3D12Resource* textureBuffer; // the font texture resource D3D12_GPU_DESCRIPTOR_HANDLE srvHandle; // the font srv // these are how much the character is padded in the texture. We // add padding to give sampling a little space so it does not accidentally // padd the surrounding characters. We will need to subtract these paddings // from the actual spacing between characters to remove the gaps you would otherwise see float leftpadding; float toppadding; float rightpadding; float bottompadding; // this will return the amount of kerning we need to use for two characters float GetKerning(wchar_t first, wchar_t second) { for (int i = 0; i < numKernings; ++i) { if ((wchar_t)KerningsList[i].firstid == first && (wchar_t)KerningsList[i].secondid == second) return KerningsList[i].amount; } return 0.0f; } // this will return a FontChar given a wide character FontChar* GetChar(wchar_t c) { for (int i = 0; i < numCharacters; ++i) { if (c == (wchar_t)CharList[i].id) return &CharList[i]; } return nullptr; } }; ##Timer structure## In this tutorial, we will implement a timer. We will use this timer for two things. The first thing we use the timer for is for a Frames Per Second, or FPS count, so we have some useful text to render. The second thing, is to time the updates to the game logic correctly. Previously, the cubes would spin around as fast as possible. If you had a fast computer, they would spin faster than if you had a slow computer. Now that we are implementing a timer, we can make sure the cubes spin at the exact same speed however fast or slow your computer is. If you have low FPS, the cubes will move much further each frame than if you have a high FPS. We do this by passing the last frame time as a delta to the update function. We can then multiply the degrees to spin the cubes by the delta. So if your last frame took .5 seconds, the cube will only spin half as far that frame than if your last frame to 1 second. We are going to use the QueryPerformanceCounter API to implement the timer. We could have used std::chrono instead, which is platform independent, but on windows, chrono uses QueryPerformanceCounter anyway, so we just skp the middle guy here. I ran some tests on how fast getting the current timestamp was between chrono and QueryPerformanceCounter and found QueryPerformanceCounter to be quite a bit fast (although we are talking microseconds, so it's really negligible). Whe we construct the Timer class, it gets the current timestamp, We can either be using any metric we want, i chose milliseconds because thats the most common, but you can change it by manipulating the timerFrequency. GetFrameDelta() is the method that does the fps work and returns the time a frame took, so we only want to call this method once per frame. struct Timer { double timerFrequency = 0.0; long long lastFrameTime = 0; long long lastSecond = 0; double frameDelta = 0; int fps = 0; Timer() { LARGE_INTEGER li; QueryPerformanceFrequency(&li); // seconds //timerFrequency = double(li.QuadPart); // milliseconds timerFrequency = double(li.QuadPart) / 1000.0; // microseconds //timerFrequency = double(li.QuadPart) / 1000000.0; QueryPerformanceCounter(&li); lastFrameTime = li.QuadPart; } // Call this once per frame double GetFrameDelta() { LARGE_INTEGER li; QueryPerformanceCounter(&li); frameDelta = double(li.QuadPart - lastFrameTime) / timerFrequency; if (frameDelta > 0) fps = 1000 / frameDelta; lastFrameTime = li.QuadPart; return frameDelta; } }; ####New Globals#### We have a couple new globals, which include two new function prototypes. The first new global is a new pipeline state object for rendering the text. The font vertex and pixel shaders, and blending state are different than the other PSO we created for rendering our cubes, so we need a separate PSO for the text. We Have a Font object for the arial font we will load. The maxNumTextCharacters is so we can size our vertex buffer resource large enough to hold all of our text. I chose 1024, and if you ever go over that number, you could delete the current vertex buffer resource and create a new one with enough room for the text you need, or you can just set this really high. We actually will need 3 vertex buffer resources, one for each frame, so when we are modifying the vertex buffer, we do not overwrite the last frames vertex buffer while it might be in use by the shaders. Since we are modifying the vertex buffer often (we are setting it every frame), we can use an upload heap. Along with 3 vertex buffer resources, we need 3 vertex buffer views and 3 vertex buffer GPU virtual addresses, so that we can write to the vertex buffer. We will create a Timer object so that we can get the last frames time to complete, which we can use to update our game logic based on time, as well as get the FPS. Finally we have our new function prototypes. We have one function which loads in a bitmap font, and another which draws text using a provided font. ID3D12PipelineState* textPSO; // pso containing a pipeline state Font arialFont; // this will store our arial font information int maxNumTextCharacters = 1024; // the maximum number of characters you can render during a frame. This is just used to make sure // there is enough memory allocated for the text vertex buffer each frame ID3D12Resource* textVertexBuffer[frameBufferCount]; D3D12_VERTEX_BUFFER_VIEW textVertexBufferView[frameBufferCount]; // a view for our text vertex buffer UINT8* textVBGPUAddress[frameBufferCount]; // this is a pointer to each of the text constant buffers // create an instance of timer Timer timer; Font LoadFont(LPCWSTR filename, int windowWidth, int windowHeight); // load a font void RenderText(Font font, std::wstring text, XMFLOAT2 pos, XMFLOAT2 scale = XMFLOAT2(1.0f, 1.0f), XMFLOAT2 padding = XMFLOAT2(0.5f, 0.0f), XMFLOAT4 color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f)); ####Updated Update() signature#### We will be passing in a delta argument to this function now so that we can update our game logic based on time void Update(double delta); // update the game logic ####Updated mainloop() function#### In our main loop, we have an extra line, and a modified line. The extra line gets the time the last frame took to complete. We pass this delta variable to the update function. void mainloop() { MSG msg; ZeroMemory(&msg, sizeof(MSG)); while (Running) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { // run game code // we can use delta to update our game logic double delta = timer.GetFrameDelta(); Update(delta); // update the game logic Render(); // execute the command queue (rendering the scene is the result of the gpu executing the command lists) } } } ####The Text PSO#### The first new thing in our InitD3D() function is creating the Text PSO. This involves compiling the text vertex and pixel shaders, creating a blend state for the text PSO, and finally the actual PSO. We've done all this in previous tutorials except for the blend state, so i will explain the blend state here. The D3D12_BLEND_DESC structure looks like this: typedef struct .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770339(v=vs.85).aspx][D3D12_BLEND_DESC] { BOOL AlphaToCoverageEnable; BOOL IndependentBlendEnable; D3D12_RENDER_TARGET_BLEND_DESC RenderTarget[8]; } D3D12_BLEND_DESC; - **AlphaToCoverageEnable** - *We set this to false. It's an extra operation which samples textures at a higher resolution to find out how much a pixel on the screen actually covers, and is able to blend the value of a pixel more correctly with the pixels behind it. It is useful for foliage and grass, where the edge of a blade of grass for example is defined by the alpha value. It gives you much better results, but takes a hit to performance. You can read more about it .[https://msdn.microsoft.com/en-us/library/windows/desktop/bb205072(v=vs.85).aspx#Alpha_To_Coverage][here], .[https://en.wikipedia.org/wiki/Alpha_to_coverage][here], and .[https://www.opengl.org/discussion_boards/showthread.php/176078-Alpha-to-coverage-how-does-it-actualy-work][here].* - **IndependentBlendEnable** - *Determines if blending should be done for each render target independently. We only have one render target, so we set this to FALSE. If you set this to FALSE, only the rendertarget[0] members are used in blending, all the other render targets members are ignored.* - **RenderTarget[8]** - *An array of D3D12_RENDER_TARGET_BLEND_DESC, one for each render target bound. We only have one.* The D3D12_RENDER_TARGET_BLEND_DESC structure is where the actual blending state parameters for a render target are (the other two blending parameters in D3D12_BLEND_DESC just say whether to do an operation or not). typedef struct D3D12_RENDER_TARGET_BLEND_DESC { BOOL BlendEnable; BOOL LogicOpEnable; D3D12_BLEND SrcBlend; D3D12_BLEND DestBlend; D3D12_BLEND_OP BlendOp; D3D12_BLEND SrcBlendAlpha; D3D12_BLEND DestBlendAlpha; D3D12_BLEND_OP BlendOpAlpha; D3D12_LOGIC_OP LogicOp; UINT8 RenderTargetWriteMask; } D3D12_RENDER_TARGET_BLEND_DESC; - **BlendEnable** - *Whether the blending operation should be performed or not* - **LogicOpEnable** - *Whether a logical operation on the incoming RGBA and the RGBA value currently on the render target is performed. We don't need this.* - **SrcBlend** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770338(v=vs.85).aspx][D3D12_BLEND] enumeration. The value of the source component for the RGB channel* - **DestBlend** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770338(v=vs.85).aspx][D3D12_BLEND] enumeration. The value of the destination component for the RGB channel* - **BlendOp** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770340(v=vs.85).aspx][D3D12_BLEND_OP] enumeration. The operation to perform on the source and destination components for the RGB channel* - **SrcBlendAlpha** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770338(v=vs.85).aspx][D3D12_BLEND] enumeration. The value of the source component for the Alpha channel* - **DestBlendAlpha** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770338(v=vs.85).aspx][D3D12_BLEND] enumeration. The value of the destination component for the Alpha channel* - **BlendOpAlpha** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770340(v=vs.85).aspx][D3D12_BLEND_OP] enumeration. The operation to perform on the source and destination components for the Alpha channel* - **LogicOp** - *A .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770379(v=vs.85).aspx][D3D12_LOGIC_OP] enumeration. The operation to perform when logical operation is enabled* - **RenderTargetWriteMask** - *A combination of .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn770347(v=vs.85).aspx][D3D12_COLOR_WRITE_ENABLE] enumeration values ORed (|) together to specify a write mask. This specifies which components of a pixel are writable to. We specify D3D12_COLOR_WRITE_ENABLE_ALL because we want to write to all channels.* There are two algorithms DirectX performs when blending. The first is for the color channel, RGB, and the second for the alpha channel, A. This is the formula DirectX uses to get the final color values (RGB) from a blending operation: The source is sourceRGB * blendFactor and the destination is destinationRGB * blendFactor (source * SrcBlend) BlendOp (destination * DestBlend) This is the forumula DirectX uses to get the final alpha value (A) from a blending operation: Like for RGB, the blend factor for the source alpha channel is sourceA * blendFactor and destination alpha is destinationA * blendFactor (source * SrcBlendAlpha) BlendOpAlpha (destination * DestBlendAlpha) The blend factor can be set with the command .[https://msdn.microsoft.com/en-us/library/windows/desktop/dn903886(v=vs.85).aspx][OMSetBlendFactor()]. If this is not specified, DirectX will use [1,1,1,1]. For our text, we want the color on the destination to be blended with whats already on the render target. When alpha on the text is 0, we want to fully show the color already on the render target, and when it is 1, we want to fully show the text color, and anything in between is blended between the text color and the color already on the render target, so our final blend formula looks like this: rgb = ((sourceRGB * blendFactor(1.0)) * D3D12_BLEND_SRC_ALPHA) D3D12_BLEND_OP_ADD(+) ((destinationRGB * blendFactor(1.0)) * D3D12_BLEND_ONE(1.0)) a = ((sourceA * blendFactor(1.0)) * D3D12_BLEND_SRC_ALPHA) D3D12_BLEND_OP_ADD(+) ((destinationA * blendFactor(1.0)) * D3D12_BLEND_ONE(1.0)) Now let's look at the text input element description. We have three input elements, one for position and position width/height, one for texture coordinates and texture coordinate width/height, and one for color. You will notice how we are setting the InputSlotClass to D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA. That means this data is per instance, rather than per vertex. So the first 4 vertices will use the same input data, the second 4 vertices will use the next instance data, and so on. The last parameter, InstanceDataStepRate, specifies how many instances must be drawn before moving to the next instance data (the next vertex in the vertex buffer). Since we have one vertex in the vertex buffer per instance, we want to set this to 1, so that each instance (character) we draw will get the next vertex in the vertex buffer. If we set this to 0, then all the characters will use the same vertex in the vertex buffer // Text PSO // compile vertex shader ID3DBlob* textVertexShader; // d3d blob for holding vertex shader bytecode hr = D3DCompileFromFile(L"TextVertexShader.hlsl", nullptr, nullptr, "main", "vs_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &textVertexShader, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); Running = false; return false; } // fill out a shader bytecode structure, which is basically just a pointer // to the shader bytecode and the size of the shader bytecode D3D12_SHADER_BYTECODE textVertexShaderBytecode = {}; textVertexShaderBytecode.BytecodeLength = textVertexShader->GetBufferSize(); textVertexShaderBytecode.pShaderBytecode = textVertexShader->GetBufferPointer(); // compile pixel shader ID3DBlob* textPixelShader; hr = D3DCompileFromFile(L"TextPixelShader.hlsl", nullptr, nullptr, "main", "ps_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &textPixelShader, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); Running = false; return false; } // fill out shader bytecode structure for pixel shader D3D12_SHADER_BYTECODE textPixelShaderBytecode = {}; textPixelShaderBytecode.BytecodeLength = textPixelShader->GetBufferSize(); textPixelShaderBytecode.pShaderBytecode = textPixelShader->GetBufferPointer(); // create input layout // The input layout is used by the Input Assembler so that it knows // how to read the vertex data bound to it. D3D12_INPUT_ELEMENT_DESC textInputLayout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 16, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 } }; // fill out an input layout description structure D3D12_INPUT_LAYOUT_DESC textInputLayoutDesc = {}; // we can get the number of elements in an array by "sizeof(array) / sizeof(arrayElementType)" textInputLayoutDesc.NumElements = sizeof(textInputLayout) / sizeof(D3D12_INPUT_ELEMENT_DESC); textInputLayoutDesc.pInputElementDescs = textInputLayout; // create the text pipeline state object (PSO) D3D12_GRAPHICS_PIPELINE_STATE_DESC textpsoDesc = {}; textpsoDesc.InputLayout = textInputLayoutDesc; textpsoDesc.pRootSignature = rootSignature; textpsoDesc.VS = textVertexShaderBytecode; textpsoDesc.PS = textPixelShaderBytecode; textpsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; textpsoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; textpsoDesc.SampleDesc = sampleDesc; textpsoDesc.SampleMask = 0xffffffff; textpsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); D3D12_BLEND_DESC textBlendStateDesc = {}; textBlendStateDesc.AlphaToCoverageEnable = FALSE; textBlendStateDesc.IndependentBlendEnable = FALSE; textBlendStateDesc.RenderTarget[0].BlendEnable = TRUE; textBlendStateDesc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; textBlendStateDesc.RenderTarget[0].DestBlend = D3D12_BLEND_ONE; textBlendStateDesc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; textBlendStateDesc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA; textBlendStateDesc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; textBlendStateDesc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; textBlendStateDesc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; textpsoDesc.BlendState = textBlendStateDesc; textpsoDesc.NumRenderTargets = 1; D3D12_DEPTH_STENCIL_DESC textDepthStencilDesc= CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT); textDepthStencilDesc.DepthEnable = false; textpsoDesc.DepthStencilState = textDepthStencilDesc; // create the text pso hr = device->CreateGraphicsPipelineState(&textpsoDesc, IID_PPV_ARGS(&textPSO)); if (FAILED(hr)) { Running = false; return false; } ####Loading the bitmap font#### The next new thing in our InitD3D() function is loading the arial font. Once we have the .fnt file loaded, we load in the font texture for the .fnt file: We will talk about the LoadFont() function later. We have covered loading in a texture from file in the previous tutorial, so i will not explain it here. // Load Font arialFont = LoadFont(L"Arial.fnt", Width, Height); // Load the image from file D3D12_RESOURCE_DESC fontTextureDesc; int fontImageBytesPerRow; BYTE* fontImageData; int fontImageSize = LoadImageDataFromFile(&fontImageData, fontTextureDesc, arialFont.fontImage.c_str(), fontImageBytesPerRow); // make sure we have data if (fontImageData <= 0) { Running = false; return false; } // create the font texture resource hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &fontTextureDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&arialFont.textureBuffer)); if (FAILED(hr)) { Running = false; return false; } arialFont.textureBuffer->SetName(L"Font Texture Buffer Resource Heap"); ID3D12Resource* fontTextureBufferUploadHeap; UINT64 fontTextureUploadBufferSize; device->GetCopyableFootprints(&fontTextureDesc, 0, 1, 0, nullptr, nullptr, nullptr, &fontTextureUploadBufferSize); // create an upload heap to copy the texture to the gpu hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(fontTextureUploadBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&fontTextureBufferUploadHeap)); if (FAILED(hr)) { Running = false; return false; } fontTextureBufferUploadHeap->SetName(L"Font Texture Buffer Upload Resource Heap"); // store font image in upload heap D3D12_SUBRESOURCE_DATA fontTextureData = {}; fontTextureData.pData = &fontImageData[0]; // pointer to our image data fontTextureData.RowPitch = fontImageBytesPerRow; // size of all our triangle vertex data fontTextureData.SlicePitch = fontImageBytesPerRow * fontTextureDesc.Height; // also the size of our triangle vertex data // Now we copy the upload buffer contents to the default heap UpdateSubresources(commandList, arialFont.textureBuffer, fontTextureBufferUploadHeap, 0, 0, 1, &fontTextureData); // transition the texture default heap to a pixel shader resource (we will be sampling from this heap in the pixel shader to get the color of pixels) commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(arialFont.textureBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)); // create an srv for the font D3D12_SHADER_RESOURCE_VIEW_DESC fontsrvDesc = {}; fontsrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; fontsrvDesc.Format = fontTextureDesc.Format; fontsrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; fontsrvDesc.Texture2D.MipLevels = 1; // we need to get the next descriptor location in the descriptor heap to store this srv srvHandleSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); arialFont.srvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mainDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), 1, srvHandleSize); CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(mainDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), 1, srvHandleSize); device->CreateShaderResourceView(arialFont.textureBuffer, &fontsrvDesc, srvHandle); ####Create a text vertex buffer resource for each frame#### Here we are just creating 3 committed resources for each frame. These committed resources will hold our vertex data for the text. Since we are going to modify the text often, we just use an upload heap. // create text vertex buffer committed resources for (int i = 0; i < frameBufferCount; ++i) { // create upload heap. We will fill this with data for our text ID3D12Resource* vBufferUploadHeap; hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // upload heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(maxNumTextCharacters * sizeof(TextVertex)), // resource description for a buffer D3D12_RESOURCE_STATE_GENERIC_READ, // GPU will read from this buffer and copy its contents to the default heap nullptr, IID_PPV_ARGS(&textVertexBuffer[i])); if (FAILED(hr)) { Running = false; return false; } textVertexBuffer[i]->SetName(L"Text Vertex Buffer Upload Resource Heap"); CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU. (so end is less than or equal to begin) // map the resource heap to get a gpu virtual address to the beginning of the heap hr = textVertexBuffer[i]->Map(0, &readRange, reinterpret_cast<void**>(&textVBGPUAddress[i])); } ####Get the test vertex buffer views#### Here we just get the vertex buffer view for each frame, so that we can bind the correct vertex buffer to the pipeline when we need // set the text vertex buffer view for each frame for (int i = 0; i < frameBufferCount; ++i) { textVertexBufferView[i].BufferLocation = textVertexBuffer[i]->GetGPUVirtualAddress(); textVertexBufferView[i].StrideInBytes = sizeof(TextVertex); textVertexBufferView[i].SizeInBytes = maxNumTextCharacters * sizeof(TextVertex); } ####Updated Update() function#### We have updated our game logic to make use of the delta variable. Basically the delta is the number of seconds the previous frame took to complete. We can multiply our rotation amount by this delta variable, so that if the last frame took a long time to complete, the cubes will rotate further this frame, and if the last frame completed very quickly, the cubes will only rotate a little this frame. XMMATRIX rotXMat = XMMatrixRotationX(0.001f * delta); XMMATRIX rotYMat = XMMatrixRotationY(0.002f * delta); XMMATRIX rotZMat = XMMatrixRotationZ(0.003f * delta); ... rotXMat = XMMatrixRotationX(0.003f * delta); rotYMat = XMMatrixRotationY(0.002f * delta); rotZMat = XMMatrixRotationZ(0.001f * delta); ####Updated UpdatePipeline() function#### Here we will call the function to draw our text. We provide the font we want to use, the string we wish to draw, the position of the text, and the scale of the text. scale of (1.0,1.0) would be 100%, we are drawing it at (2.0,2.0) so that it is drawn an 200%, or twice as large. We have other parameters to this function, which we are just using the defaults we defined in the function prototype. // draw the text RenderText(arialFont, std::wstring(L"FPS: ") + std::to_wstring(timer.fps), XMFLOAT2(0.02f, 0.01f), XMFLOAT2(2.0f, 2.0f)); ####The LoadFont() function#### Let's just quickly look at the LoadFont() function before we take a look at the RenderText() function. There are 4 parts to this function. The first part just loads in the .fnt file, while the other three parts parse it. The second part gets the general font information, like name, size, and lineheight. This function needs to take in the width and height of the window in pixels, in order to correctly convert the font information from pixels to screen space. Take a look at padding. We need to convert the padding to screen space from pixels, so we take the amount of padding in pixels, and divide that by either the width or height of the window, so we will have a percentage of how much of the window the padding takes up. The next part is the characters section. We need to read each characters information in. Part of the general information will give us the number of characters and the number of kernings. We can initialize the character array and kernings array before we read them in so we have memory to store them in. Now you will notice we have two widths and two heights for each character, they are "width, height, twidth, and theight". width and height are the widths and heights of the characters in screen space. Our screen may not be the same size or aspect ratio as the font image, so we have a different width and height for the texture coordinates, twidth and theight. The width and height are divided by the screen width and screen height, while the twidth and theight are divided by the texture width and height, so that the texture width and height are in texture space, being 0-1. Finally we have the kernings information. Basically we are just doing the same as above, reading in each kerning line and storing it in the kerning array for the font. Font LoadFont(LPCWSTR filename, int windowWidth, int windowHeight) { std::wifstream fs; fs.open(filename); Font font; std::wstring tmp; int startpos; // extract font name fs >> tmp >> tmp; // info face="Arial" startpos = tmp.find(L""") + 1; font.name = tmp.substr(startpos, tmp.size() - startpos - 1); // get font size fs >> tmp; // size=73 startpos = tmp.find(L"=") + 1; font.size = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // bold, italic, charset, unicode, stretchH, smooth, aa, padding, spacing fs >> tmp >> tmp >> tmp >> tmp >> tmp >> tmp >> tmp; // bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 // get padding fs >> tmp; // padding=5,5,5,5 startpos = tmp.find(L"=") + 1; tmp = tmp.substr(startpos, tmp.size() - startpos); // 5,5,5,5 // get up padding startpos = tmp.find(L",") + 1; font.toppadding = std::stoi(tmp.substr(0, startpos)) / (float)windowWidth; // get right padding tmp = tmp.substr(startpos, tmp.size() - startpos); startpos = tmp.find(L",") + 1; font.rightpadding = std::stoi(tmp.substr(0, startpos)) / (float)windowWidth; // get down padding tmp = tmp.substr(startpos, tmp.size() - startpos); startpos = tmp.find(L",") + 1; font.bottompadding = std::stoi(tmp.substr(0, startpos)) / (float)windowWidth; // get left padding tmp = tmp.substr(startpos, tmp.size() - startpos); font.leftpadding = std::stoi(tmp) / (float)windowWidth; fs >> tmp; // spacing=0,0 // get lineheight (how much to move down for each line), and normalize (between 0.0 and 1.0 based on size of font) fs >> tmp >> tmp; // common lineHeight=95 startpos = tmp.find(L"=") + 1; font.lineHeight = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowHeight; // get base height (height of all characters), and normalize (between 0.0 and 1.0 based on size of font) fs >> tmp; // base=68 startpos = tmp.find(L"=") + 1; font.baseHeight = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowHeight; // get texture width fs >> tmp; // scaleW=512 startpos = tmp.find(L"=") + 1; font.textureWidth = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get texture height fs >> tmp; // scaleH=512 startpos = tmp.find(L"=") + 1; font.textureHeight = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get pages, packed, page id fs >> tmp >> tmp; // pages=1 packed=0 fs >> tmp >> tmp; // page id=0 // get texture filename std::wstring wtmp; fs >> wtmp; // file="Arial.png" startpos = wtmp.find(L""") + 1; font.fontImage = wtmp.substr(startpos, wtmp.size() - startpos - 1); // get number of characters fs >> tmp >> tmp; // chars count=97 startpos = tmp.find(L"=") + 1; font.numCharacters = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // initialize the character list font.CharList = new FontChar[font.numCharacters]; for (int c = 0; c < font.numCharacters; ++c) { // get unicode id fs >> tmp >> tmp; // char id=0 startpos = tmp.find(L"=") + 1; font.CharList[c].id = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get x fs >> tmp; // x=392 startpos = tmp.find(L"=") + 1; font.CharList[c].u = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)font.textureWidth; // get y fs >> tmp; // y=340 startpos = tmp.find(L"=") + 1; font.CharList[c].v = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)font.textureHeight; // get width fs >> tmp; // width=47 startpos = tmp.find(L"=") + 1; tmp = tmp.substr(startpos, tmp.size() - startpos); font.CharList[c].width = (float)std::stoi(tmp) / (float)windowWidth; font.CharList[c].twidth = (float)std::stoi(tmp) / (float)font.textureWidth; // get height fs >> tmp; // height=57 startpos = tmp.find(L"=") + 1; tmp = tmp.substr(startpos, tmp.size() - startpos); font.CharList[c].height = (float)std::stoi(tmp) / (float)windowHeight; font.CharList[c].theight = (float)std::stoi(tmp) / (float)font.textureHeight; // get xoffset fs >> tmp; // xoffset=-6 startpos = tmp.find(L"=") + 1; font.CharList[c].xoffset = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowWidth; // get yoffset fs >> tmp; // yoffset=16 startpos = tmp.find(L"=") + 1; font.CharList[c].yoffset = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowHeight; // get xadvance fs >> tmp; // xadvance=65 startpos = tmp.find(L"=") + 1; font.CharList[c].xadvance = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowWidth; // get page // get channel fs >> tmp >> tmp; // page=0 chnl=0 } // get number of kernings fs >> tmp >> tmp; // kernings count=96 startpos = tmp.find(L"=") + 1; font.numKernings = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // initialize the kernings list font.KerningsList = new FontKerning[font.numKernings]; for (int k = 0; k < font.numKernings; ++k) { // get first character fs >> tmp >> tmp; // kerning first=87 startpos = tmp.find(L"=") + 1; font.KerningsList[k].firstid = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get second character fs >> tmp; // second=45 startpos = tmp.find(L"=") + 1; font.KerningsList[k].secondid = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get amount fs >> tmp; // amount=-1 startpos = tmp.find(L"=") + 1; int t = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)); font.KerningsList[k].amount = (float)t / (float)windowWidth; } return font; } ####Rendering the text#### Here we actually create the vertex buffer and commands to render the text, in the function RenderText(). The first part of this function sets up the pipeline to get ready to draw the text. We need to draw over everything that is already on the render target, so we clear the depth buffer. The next thing we do is set the text PSO. We set the triangle topology to trianglestrip. We do this so that we can easily draw a quad using just 4 vertices. The first three vertices will draw the first triangle, while the last vertex, and the previous 2 from the previous triangle, will draw the second triangle. We need to set the correct vertex buffer resource for the frame we are on, so when we draw the text, we are reading from the correct vertex buffer. The final part of the pipeline setup is we set the root descriptor table. This is just a pointer to the font texture's SRV. Next we set up a couple variables. The first two are the position of the top and left of the text in screen space. We want this function to work so that when we pass 0,0 in for the position, that it will start in the top left corner, and 1,1 will be the bottom right corner. We need to convert that input into screen space, which is -1 to 1 on the x axis, from left to right, and 1 to -1 on the y axis, from top to bottom. After that we set the initial x and y position. each character will increase x xadvance amount, and each newline (n) will increase the y lineheight amount, and reset x back to topLeftScreenX. Then we get the vertical and horrizontal padding, so that we can subtract that amount from x and y when setting the characters position, otherwise our characters will be spaced too much. After that we set the vertex array to be the start of our vertex buffer resource, so that we modify the vertex buffer resource directly. We increment vert by the size of TextVertex for each character, so we move on to the next vertex in the vertex buffer. We do not need to worry about clearing the vertex buffer because we are only going to draw as many text characters as we are writing to the vertex buffer. We need to keep track of the last characters id, so that we can find the kerning amount when needed. Next we loop through the characters in the string, find the character in the font, find the amount of kerning in the font (if we do not find the character in the font, we just skip it, but it would be a better idea to use a default character, such as a block, which we do have in this arial font, i'll leave that up to you). When setting the vertices x and y position, we need to take kerning and offsets into account, which we do. We also need to take scale into account when setting the width and height of the character. Next we increase the number of characters drawn, increment x, which we do by adding xadvance for the current character to, subtracting the horrizontal padding, and multiplying by the scale, and finally set the value of lastChar. Once we are finished updating our vertex buffer, we draw the text. We call DrawInstanced, where the first parameter is the number of vertices per instance we want to draw, we are drawing a quad so we say 4 vertices, the number of instances we want to draw, which is the number of characters, and leave the last two parameters at 0. void RenderText(Font font, std::wstring text, XMFLOAT2 pos, XMFLOAT2 scale, XMFLOAT2 padding, XMFLOAT4 color) { // clear the depth buffer so we can draw over everything commandList->ClearDepthStencilView(dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); // set the text pipeline state object commandList->SetPipelineState(textPSO); // this way we only need 4 vertices per quad rather than 6 if we were to use a triangle list topology commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); // set the text vertex buffer commandList->IASetVertexBuffers(0, 1, &textVertexBufferView[frameIndex]); // bind the text srv. We will assume the correct descriptor heap and table are currently bound and set commandList->SetGraphicsRootDescriptorTable(1, font.srvHandle); int numCharacters = 0; float topLeftScreenX = (pos.x * 2.0f) - 1.0f; float topLeftScreenY = ((1.0f - pos.y) * 2.0f) - 1.0f; float x = topLeftScreenX; float y = topLeftScreenY; float horrizontalPadding = (font.leftpadding + font.rightpadding) * padding.x; float verticalPadding = (font.toppadding + font.bottompadding) * padding.y; // cast the gpu virtual address to a textvertex, so we can directly store our vertices there TextVertex* vert = (TextVertex*)textVBGPUAddress[frameIndex]; wchar_t lastChar = -1; // no last character to start with for (int i = 0; i < text.size(); ++i) { wchar_t c = text[i]; FontChar* fc = font.GetChar(c); // character not in font char set if (fc == nullptr) continue; // end of string if (c == L'') break; // new line if (c == L'n') { x = topLeftScreenX; y -= (font.lineHeight + verticalPadding) * scale.y; continue; } // don't overflow the buffer. In your app if this is true, you can implement a resize of your text vertex buffer if (numCharacters >= maxNumTextCharacters) break; float kerning = 0.0f; if (i > 0) kerning = font.GetKerning(lastChar, c); vert[numCharacters] = TextVertex(color.x, color.y, color.z, color.w, fc->u, fc->v, fc->twidth, fc->theight, x + ((fc->xoffset + kerning) * scale.x), y - (fc->yoffset * scale.y), fc->width * scale.x, fc->height * scale.y); numCharacters++; // remove horrizontal padding and advance to next char position x += (fc->xadvance - horrizontalPadding) * scale.x; lastChar = c; } // we are going to have 4 vertices per character (trianglestrip to make quad), and each instance is one character commandList->DrawInstanced(4, numCharacters, 0, 0); } ####The Text Vertex Shader#### Our vertex shader takes in two parameters, one VS_INPUT structure, and one vertex id. The vertex id is a system value we can get by using the SV_VertexID semantic. Now the second line of code in the shader is a pretty cool trick. We are using bitwise operators to get which vertex we are actually using. I've put a comment above the line so you can visualize what the value of uv will be for each vertexid. Basically what we want is to know if the vertex is the top left, top right, bottom right, or bottom left vertex in the quad. The first vertex for example, with id 0, will be the top left. The integer 0 in binary is represented by 0000. The x value of uv is vertexID & 1, which means we are comparing 0000 with 0001. the result is going to be 0000, or just 0 because one is 0 and the other is 1. The second parameter is also going to be 0 for id 0. We are doing (vertexID >> 1) & 1, which means we are first right shifting the vertex id by 1 bit, then comparing that with 1 again. so 0000 shifted to the right is still 0000, and ANDing that with 1 is also 0000, or 0. Lets jump to the third vertex, with id 2. Integer 2 is represented by 0010 in binary. The x value of uv is the result of ANDing 0010 with 0001, which is 0000, or 0. The y value is where you can see what is actually happening here. we first shift vertexID to the right one bit, so it goes from 0010, to 0001, you can see the 1 moved over one place. Now we AND that result with 0001, which gives us 0001 & 0001, where the result is now 0001, or 1. Now that we have the uv float2 variable, we know which vertex we are at. the top left vertex does not add the width and height to its position, so we multiply the width and height by uv.x and uv.y, which will give us a 0 width and 0 height, which we add to the vertex position. The bottom right vertex needs to add the width and height to the characters x and y position, so the uv, being 1,1 for the bottom right vertex, will multiply width and height by uv.x (1) and uv.y (1), and add those to the characters position, which gives us the position of the bottom right vertex. We do the same for the texture coordinate, and the result of the 4 vertices for the instance (character) is a quad with the correct width, height, position, and texture coordinate. The entire character is a solid color here, but you certainly sample from another texture in the pixel shader to texture your font, another thing i'll leave up to you for an exercise. We return these values from the vertex shader, for input to the pixel shader. struct VS_INPUT { float4 pos : POSITION; float4 texCoord: TEXCOORD; float4 color: COLOR; }; struct VS_OUTPUT { float4 pos: SV_POSITION; float4 color: COLOR; float2 texCoord: TEXCOORD; }; VS_OUTPUT main(VS_INPUT input, uint vertexID : SV_VertexID) { VS_OUTPUT output; // vert id 0 = 0000, uv = (0, 0) // vert id 1 = 0001, uv = (1, 0) // vert id 2 = 0010, uv = (0, 1) // vert id 3 = 0011, uv = (1, 1) float2 uv = float2(vertexID & 1, (vertexID >> 1) & 1); // set the position for the vertex based on which vertex it is (uv) output.pos = float4(input.pos.x + (input.pos.z * uv.x), input.pos.y - (input.pos.w * uv.y), 0, 1); output.color = input.color; // set the texture coordinate based on which vertex it is (uv) output.texCoord = float2(input.texCoord.x + (input.texCoord.z * uv.x), input.texCoord.y + (input.texCoord.w * uv.y)); return output; } ####The Text Pixel Shader#### We have our texture2d and a samplerstate at the top, for our text's texture and sampler. We are taking in the position, color and texture coordinate of the current pixel fragement as input to the text's pixel shader. All we are doing here is setting the pixel fragments color to the color of the character, and setting the alpha to the character colors alpha multiplied by the sampled texel's alpha from the font image. Basically we only need the alpha channel from the font, unless you had colored or textured the font, you could definitely grab the color of the font here too from the font image. If the pixel fragment was on the actual character, the alpha is going to be 1 or close to 1, and if it was not on the character, the alpha is going to be 0 or close to 0 (i say close to because when you sample at different scales, sometimes you will get an average of the area around the texel on the image). If the alpha sampled is 0, then we will just see what is behind the text, if it is 1, then we will see the color of the text. Texture2D t1 : register(t0); SamplerState s1 : register(s0); struct VS_OUTPUT { float4 pos: SV_POSITION; float4 color: COLOR; float2 texCoord: TEXCOORD; }; float4 main(VS_OUTPUT input) : SV_TARGET { return float4(input.color.rgb, input.color.a * t1.Sample(s1, input.texCoord).a); } ####Goodbye~#### I want to thank everyone for supporting and reading my tutorials through the years. This will be the last tutorial i plan on writing, but the tutorials section is open for any of you to write your own tutorials. I encourage you to write a tutorial or two, it will give you practice and you will learn a lot about whatever you are writing the tutorial on. It can honestly be about anything, i would just be happy to see others participating on Braynzar Soft~ If you have any questions feel free to use the questions section or ask over on gamedev, i visit gamedev fequently and might be able to answer your question if nobody there has already answered it. ####The Code#### ##TextVertexShader.hlsl## struct VS_INPUT { float4 pos : POSITION; float4 texCoord: TEXCOORD; float4 color: COLOR; }; struct VS_OUTPUT { float4 pos: SV_POSITION; float4 color: COLOR; float2 texCoord: TEXCOORD; }; VS_OUTPUT main(VS_INPUT input, uint vertexID : SV_VertexID) { VS_OUTPUT output; // vert id 0 = 0000, uv = (0, 0) // vert id 1 = 0001, uv = (1, 0) // vert id 2 = 0010, uv = (0, 1) // vert id 3 = 0011, uv = (1, 1) float2 uv = float2(vertexID & 1, (vertexID >> 1) & 1); // set the position for the vertex based on which vertex it is (uv) output.pos = float4(input.pos.x + (input.pos.z * uv.x), input.pos.y - (input.pos.w * uv.y), 0, 1); output.color = input.color; // set the texture coordinate based on which vertex it is (uv) output.texCoord = float2(input.texCoord.x + (input.texCoord.z * uv.x), input.texCoord.y + (input.texCoord.w * uv.y)); return output; } ##TextPixelShader.hlsl## Texture2D t1 : register(t0); SamplerState s1 : register(s0); struct VS_OUTPUT { float4 pos: SV_POSITION; float4 color: COLOR; float2 texCoord: TEXCOORD; }; float4 main(VS_OUTPUT input) : SV_TARGET { return float4(input.color.rgb, input.color.a * t1.Sample(s1, input.texCoord).a); } ##stdafx.h## #pragma once #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers. #endif #include <windows.h> #include <d3d12.h> #include <dxgi1_4.h> #include <D3Dcompiler.h> #include <DirectXMath.h> #include "d3dx12.h" #include <string> #include <wincodec.h> #include <iostream> #include <fstream> // this will only call release if an object exists (prevents exceptions calling release on non existant objects) #define SAFE_RELEASE(p) { if ( (p) ) { (p)->Release(); (p) = 0; } } using namespace DirectX; // we will be using the directxmath library // Handle to the window HWND hwnd = NULL; // name of the window (not the title) LPCTSTR WindowName = L"BzTutsApp"; // title of the window LPCTSTR WindowTitle = L"Bz Window"; // width and height of the window int Width = 800; int Height = 600; // is window full screen? bool FullScreen = false; // we will exit the program when this becomes false bool Running = true; // create a window bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, bool fullscreen); // main application loop void mainloop(); // callback function for windows messages LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // direct3d stuff const int frameBufferCount = 3; // number of buffers we want, 2 for double buffering, 3 for tripple buffering ID3D12Device* device; // direct3d device IDXGISwapChain3* swapChain; // swapchain used to switch between render targets ID3D12CommandQueue* commandQueue; // container for command lists ID3D12DescriptorHeap* rtvDescriptorHeap; // a descriptor heap to hold resources like the render targets ID3D12Resource* renderTargets[frameBufferCount]; // number of render targets equal to buffer count ID3D12CommandAllocator* commandAllocator[frameBufferCount]; // we want enough allocators for each buffer * number of threads (we only have one thread) ID3D12GraphicsCommandList* commandList; // a command list we can record commands into, then execute them to render the frame ID3D12Fence* fence[frameBufferCount]; // an object that is locked while our command list is being executed by the gpu. We need as many //as we have allocators (more if we want to know when the gpu is finished with an asset) HANDLE fenceEvent; // a handle to an event when our fence is unlocked by the gpu UINT64 fenceValue[frameBufferCount]; // this value is incremented each frame. each fence will have its own value int frameIndex; // current rtv we are on int rtvDescriptorSize; // size of the rtv descriptor on the device (all front and back buffers will be the same size) // function declarations bool InitD3D(); // initializes direct3d 12 void Update(double delta); // update the game logic void UpdatePipeline(); // update the direct3d pipeline (update command lists) void Render(); // execute the command list void Cleanup(); // release com ojects and clean up memory void WaitForPreviousFrame(); // wait until gpu is finished with command list ID3D12PipelineState* pipelineStateObject; // pso containing a pipeline state ID3D12RootSignature* rootSignature; // root signature defines data shaders will access D3D12_VIEWPORT viewport; // area that output from rasterizer will be stretched to. D3D12_RECT scissorRect; // the area to draw in. pixels outside that area will not be drawn onto ID3D12Resource* vertexBuffer; // a default buffer in GPU memory that we will load vertex data for our triangle into ID3D12Resource* indexBuffer; // a default buffer in GPU memory that we will load index data for our triangle into D3D12_VERTEX_BUFFER_VIEW vertexBufferView; // a structure containing a pointer to the vertex data in gpu memory // the total size of the buffer, and the size of each element (vertex) D3D12_INDEX_BUFFER_VIEW indexBufferView; // a structure holding information about the index buffer ID3D12Resource* depthStencilBuffer; // This is the memory for our depth buffer. it will also be used for a stencil buffer in a later tutorial ID3D12DescriptorHeap* dsDescriptorHeap; // This is a heap for our depth/stencil buffer descriptor // this is the structure of our constant buffer. struct ConstantBufferPerObject { XMFLOAT4X4 wvpMat; }; // Constant buffers must be 256-byte aligned which has to do with constant reads on the GPU. // We are only able to read at 256 byte intervals from the start of a resource heap, so we will // make sure that we add padding between the two constant buffers in the heap (one for cube1 and one for cube2) // Another way to do this would be to add a float array in the constant buffer structure for padding. In this case // we would need to add a float padding[50]; after the wvpMat variable. This would align our structure to 256 bytes (4 bytes per float) // The reason i didn't go with this way, was because there would actually be wasted cpu cycles when memcpy our constant // buffer data to the gpu virtual address. currently we memcpy the size of our structure, which is 16 bytes here, but if we // were to add the padding array, we would memcpy 64 bytes if we memcpy the size of our structure, which is 50 wasted bytes // being copied. int ConstantBufferPerObjectAlignedSize = (sizeof(ConstantBufferPerObject) + 255) & ~255; ConstantBufferPerObject cbPerObject; // this is the constant buffer data we will send to the gpu // (which will be placed in the resource we created above) ID3D12Resource* constantBufferUploadHeaps[frameBufferCount]; // this is the memory on the gpu where constant buffers for each frame will be placed UINT8* cbvGPUAddress[frameBufferCount]; // this is a pointer to each of the constant buffer resource heaps XMFLOAT4X4 cameraProjMat; // this will store our projection matrix XMFLOAT4X4 cameraViewMat; // this will store our view matrix XMFLOAT4 cameraPosition; // this is our cameras position vector XMFLOAT4 cameraTarget; // a vector describing the point in space our camera is looking at XMFLOAT4 cameraUp; // the worlds up vector XMFLOAT4X4 cube1WorldMat; // our first cubes world matrix (transformation matrix) XMFLOAT4X4 cube1RotMat; // this will keep track of our rotation for the first cube XMFLOAT4 cube1Position; // our first cubes position in space XMFLOAT4X4 cube2WorldMat; // our first cubes world matrix (transformation matrix) XMFLOAT4X4 cube2RotMat; // this will keep track of our rotation for the second cube XMFLOAT4 cube2PositionOffset; // our second cube will rotate around the first cube, so this is the position offset from the first cube int numCubeIndices; // the number of indices to draw the cube ID3D12Resource* textureBuffer; // the resource heap containing our texture int LoadImageDataFromFile(BYTE** imageData, D3D12_RESOURCE_DESC& resourceDescription, LPCWSTR filename, int &bytesPerRow); DXGI_FORMAT GetDXGIFormatFromWICFormat(WICPixelFormatGUID& wicFormatGUID); WICPixelFormatGUID GetConvertToWICFormat(WICPixelFormatGUID& wicFormatGUID); int GetDXGIFormatBitsPerPixel(DXGI_FORMAT& dxgiFormat); ID3D12DescriptorHeap* mainDescriptorHeap; UINT srvHandleSize; struct FontChar { // the unicode id int id; // these need to be converted to texture coordinates // (where 0.0 is 0 and 1.0 is textureWidth of the font) float u; // u texture coordinate float v; // v texture coordinate float twidth; // width of character on texture float theight; // height of character on texture float width; // width of character in screen coords float height; // height of character in screen coords // these need to be normalized based on size of font float xoffset; // offset from current cursor pos to left side of character float yoffset; // offset from top of line to top of character float xadvance; // how far to move to right for next character }; struct FontKerning { int firstid; // the first character int secondid; // the second character float amount; // the amount to add/subtract to second characters x }; struct Font { std::wstring name; // name of the font std::wstring fontImage; int size; // size of font, lineheight and baseheight will be based on this as if this is a single unit (1.0) float lineHeight; // how far to move down to next line, will be normalized float baseHeight; // height of all characters, will be normalized int textureWidth; // width of the font texture int textureHeight; // height of the font texture int numCharacters; // number of characters in the font FontChar* CharList; // list of characters int numKernings; // the number of kernings FontKerning* KerningsList; // list to hold kerning values ID3D12Resource* textureBuffer; // the font texture resource D3D12_GPU_DESCRIPTOR_HANDLE srvHandle; // the font srv // these are how much the character is padded in the texture. We // add padding to give sampling a little space so it does not accidentally // padd the surrounding characters. We will need to subtract these paddings // from the actual spacing between characters to remove the gaps you would otherwise see float leftpadding; float toppadding; float rightpadding; float bottompadding; // this will return the amount of kerning we need to use for two characters float GetKerning(wchar_t first, wchar_t second) { for (int i = 0; i < numKernings; ++i) { if ((wchar_t)KerningsList[i].firstid == first && (wchar_t)KerningsList[i].secondid == second) return KerningsList[i].amount; } return 0.0f; } // this will return a FontChar given a wide character FontChar* GetChar(wchar_t c) { for (int i = 0; i < numCharacters; ++i) { if (c == (wchar_t)CharList[i].id) return &CharList[i]; } return nullptr; } }; struct Timer { double timerFrequency = 0.0; long long lastFrameTime = 0; long long lastSecond = 0; double frameDelta = 0; int fps = 0; Timer() { LARGE_INTEGER li; QueryPerformanceFrequency(&li); // seconds //timerFrequency = double(li.QuadPart); // milliseconds timerFrequency = double(li.QuadPart) / 1000.0; // microseconds //timerFrequency = double(li.QuadPart) / 1000000.0; QueryPerformanceCounter(&li); lastFrameTime = li.QuadPart; } // Call this once per frame double GetFrameDelta() { LARGE_INTEGER li; QueryPerformanceCounter(&li); frameDelta = double(li.QuadPart - lastFrameTime) / timerFrequency; if (frameDelta > 0) fps = 1000 / frameDelta; lastFrameTime = li.QuadPart; return frameDelta; } }; ID3D12PipelineState* textPSO; // pso containing a pipeline state Font arialFont; // this will store our arial font information int maxNumTextCharacters = 1024; // the maximum number of characters you can render during a frame. This is just used to make sure // there is enough memory allocated for the text vertex buffer each frame ID3D12Resource* textVertexBuffer[frameBufferCount]; D3D12_VERTEX_BUFFER_VIEW textVertexBufferView[frameBufferCount]; // a view for our text vertex buffer UINT8* textVBGPUAddress[frameBufferCount]; // this is a pointer to each of the text constant buffers // create an instance of timer Timer timer; Font LoadFont(LPCWSTR filename, int windowWidth, int windowHeight); // load a font void RenderText(Font font, std::wstring text, XMFLOAT2 pos, XMFLOAT2 scale = XMFLOAT2(1.0f, 1.0f), XMFLOAT2 padding = XMFLOAT2(0.5f, 0.0f), XMFLOAT4 color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f)); ##main.cpp## #include "stdafx.h" struct Vertex { Vertex(float x, float y, float z, float u, float v) : pos(x, y, z), texCoord(u, v) {} XMFLOAT3 pos; XMFLOAT2 texCoord; }; struct TextVertex { TextVertex(float r, float g, float b, float a, float u, float v, float tw, float th, float x, float y, float w, float h ) : color(r, g, b, a), texCoord(u, v, tw, th), pos(x, y, w, h) {} XMFLOAT4 pos; XMFLOAT4 texCoord; XMFLOAT4 color; }; int WINAPI WinMain(HINSTANCE hInstance, //Main windows function HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { // create the window if (!InitializeWindow(hInstance, nShowCmd, FullScreen)) { MessageBox(0, L"Window Initialization - Failed", L"Error", MB_OK); return 1; } // initialize direct3d if (!InitD3D()) { MessageBox(0, L"Failed to initialize direct3d 12", L"Error", MB_OK); Cleanup(); return 1; } // start the main loop mainloop(); // we want to wait for the gpu to finish executing the command list before we start releasing everything WaitForPreviousFrame(); // close the fence event CloseHandle(fenceEvent); // clean up everything Cleanup(); return 0; } // create and show the window bool InitializeWindow(HINSTANCE hInstance, int ShowWnd, bool fullscreen) { if (fullscreen) { HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi = { sizeof(mi) }; GetMonitorInfo(hmon, &mi); Width = mi.rcMonitor.right - mi.rcMonitor.left; Height = mi.rcMonitor.bottom - mi.rcMonitor.top; } WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = NULL; wc.cbWndExtra = NULL; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2); wc.lpszMenuName = NULL; wc.lpszClassName = WindowName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if (!RegisterClassEx(&wc)) { MessageBox(NULL, L"Error registering class", L"Error", MB_OK | MB_ICONERROR); return false; } hwnd = CreateWindowEx(NULL, WindowName, WindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, Width, Height, NULL, NULL, hInstance, NULL); if (!hwnd) { MessageBox(NULL, L"Error creating window", L"Error", MB_OK | MB_ICONERROR); return false; } if (fullscreen) { SetWindowLong(hwnd, GWL_STYLE, 0); } ShowWindow(hwnd, ShowWnd); UpdateWindow(hwnd); return true; } void mainloop() { MSG msg; ZeroMemory(&msg, sizeof(MSG)); while (Running) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) break; TranslateMessage(&msg); DispatchMessage(&msg); } else { // run game code // we can use delta to update our game logic double delta = timer.GetFrameDelta(); Update(delta); // update the game logic Render(); // execute the command queue (rendering the scene is the result of the gpu executing the command lists) } } } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_KEYDOWN: if (wParam == VK_ESCAPE) { if (MessageBox(0, L"Are you sure you want to exit?", L"Really?", MB_YESNO | MB_ICONQUESTION) == IDYES) { Running = false; DestroyWindow(hwnd); } } return 0; case WM_DESTROY: // x button on top right corner of window was pressed Running = false; PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam); } bool InitD3D() { HRESULT hr; // -- Create the Device -- // IDXGIFactory4* dxgiFactory; hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)); if (FAILED(hr)) { return false; } IDXGIAdapter1* adapter; // adapters are the graphics card (this includes the embedded graphics on the motherboard) int adapterIndex = 0; // we'll start looking for directx 12 compatible graphics devices starting at index 0 bool adapterFound = false; // set this to true when a good one was found // find first hardware gpu that supports d3d 12 while (dxgiFactory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { // we dont want a software device continue; } // we want a device that is compatible with direct3d 12 (feature level 11 or higher) hr = D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr); if (SUCCEEDED(hr)) { adapterFound = true; break; } adapterIndex++; } if (!adapterFound) { Running = false; return false; } // Create the device hr = D3D12CreateDevice( adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&device) ); if (FAILED(hr)) { Running = false; return false; } // -- Create a direct command queue -- // D3D12_COMMAND_QUEUE_DESC cqDesc = {}; cqDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; cqDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; // direct means the gpu can directly execute this command queue hr = device->CreateCommandQueue(&cqDesc, IID_PPV_ARGS(&commandQueue)); // create the command queue if (FAILED(hr)) { Running = false; return false; } // -- Create the Swap Chain (double/tripple buffering) -- // DXGI_MODE_DESC backBufferDesc = {}; // this is to describe our display mode backBufferDesc.Width = Width; // buffer width backBufferDesc.Height = Height; // buffer height backBufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // format of the buffer (rgba 32 bits, 8 bits for each chanel) // describe our multi-sampling. We are not multi-sampling, so we set the count to 1 (we need at least one sample of course) DXGI_SAMPLE_DESC sampleDesc = {}; sampleDesc.Count = 1; // multisample count (no multisampling, so we just put 1, since we still need 1 sample) // Describe and create the swap chain. DXGI_SWAP_CHAIN_DESC swapChainDesc = {}; swapChainDesc.BufferCount = frameBufferCount; // number of buffers we have swapChainDesc.BufferDesc = backBufferDesc; // our back buffer description swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // this says the pipeline will render to this swap chain swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // dxgi will discard the buffer (data) after we call present swapChainDesc.OutputWindow = hwnd; // handle to our window swapChainDesc.SampleDesc = sampleDesc; // our multi-sampling description swapChainDesc.Windowed = !FullScreen; // set to true, then if in fullscreen must call SetFullScreenState with true for full screen to get uncapped fps IDXGISwapChain* tempSwapChain; dxgiFactory->CreateSwapChain( commandQueue, // the queue will be flushed once the swap chain is created &swapChainDesc, // give it the swap chain description we created above &tempSwapChain // store the created swap chain in a temp IDXGISwapChain interface ); swapChain = static_cast<IDXGISwapChain3*>(tempSwapChain); frameIndex = swapChain->GetCurrentBackBufferIndex(); // -- Create the Back Buffers (render target views) Descriptor Heap -- // // describe an rtv descriptor heap and create D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; rtvHeapDesc.NumDescriptors = frameBufferCount; // number of descriptors for this heap. rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; // this heap is a render target view heap // This heap will not be directly referenced by the shaders (not shader visible), as this will store the output from the pipeline // otherwise we would set the heap's flag to D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; hr = device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvDescriptorHeap)); if (FAILED(hr)) { Running = false; return false; } // get the size of a descriptor in this heap (this is a rtv heap, so only rtv descriptors should be stored in it. // descriptor sizes may vary from device to device, which is why there is no set size and we must ask the // device to give us the size. we will use this size to increment a descriptor handle offset rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); // get a handle to the first descriptor in the descriptor heap. a handle is basically a pointer, // but we cannot literally use it like a c++ pointer. CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); // Create a RTV for each buffer (double buffering is two buffers, tripple buffering is 3). for (int i = 0; i < frameBufferCount; i++) { // first we get the n'th buffer in the swap chain and store it in the n'th // position of our ID3D12Resource array hr = swapChain->GetBuffer(i, IID_PPV_ARGS(&renderTargets[i])); if (FAILED(hr)) { Running = false; return false; } // the we "create" a render target view which binds the swap chain buffer (ID3D12Resource[n]) to the rtv handle device->CreateRenderTargetView(renderTargets[i], nullptr, rtvHandle); // we increment the rtv handle by the rtv descriptor size we got above rtvHandle.Offset(1, rtvDescriptorSize); } // -- Create the Command Allocators -- // for (int i = 0; i < frameBufferCount; i++) { hr = device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator[i])); if (FAILED(hr)) { Running = false; return false; } } // -- Create a Command List -- // // create the command list with the first allocator hr = device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator[frameIndex], NULL, IID_PPV_ARGS(&commandList)); if (FAILED(hr)) { Running = false; return false; } // -- Create a Fence & Fence Event -- // // create the fences for (int i = 0; i < frameBufferCount; i++) { hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence[i])); if (FAILED(hr)) { Running = false; return false; } fenceValue[i] = 0; // set the initial fence value to 0 } // create a handle to a fence event fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); if (fenceEvent == nullptr) { Running = false; return false; } // create root signature // create a root descriptor, which explains where to find the data for this root parameter D3D12_ROOT_DESCRIPTOR rootCBVDescriptor; rootCBVDescriptor.RegisterSpace = 0; rootCBVDescriptor.ShaderRegister = 0; // create a descriptor range (descriptor table) and fill it out // this is a range of descriptors inside a descriptor heap D3D12_DESCRIPTOR_RANGE descriptorTableRanges[1]; // only one range right now descriptorTableRanges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; // this is a range of shader resource views (descriptors) descriptorTableRanges[0].NumDescriptors = 1; // we only have one texture right now, so the range is only 1 descriptorTableRanges[0].BaseShaderRegister = 0; // start index of the shader registers in the range descriptorTableRanges[0].RegisterSpace = 0; // space 0. can usually be zero descriptorTableRanges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; // this appends the range to the end of the root signature descriptor tables // create a descriptor table D3D12_ROOT_DESCRIPTOR_TABLE descriptorTable; descriptorTable.NumDescriptorRanges = _countof(descriptorTableRanges); // we only have one range descriptorTable.pDescriptorRanges = &descriptorTableRanges[0]; // the pointer to the beginning of our ranges array // create a root parameter for the root descriptor and fill it out D3D12_ROOT_PARAMETER rootParameters[2]; // two root parameters rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; // this is a constant buffer view root descriptor rootParameters[0].Descriptor = rootCBVDescriptor; // this is the root descriptor for this root parameter rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; // our pixel shader will be the only shader accessing this parameter for now // fill out the parameter for our descriptor table. Remember it's a good idea to sort parameters by frequency of change. Our constant // buffer will be changed multiple times per frame, while our descriptor table will not be changed at all (in this tutorial) rootParameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; // this is a descriptor table rootParameters[1].DescriptorTable = descriptorTable; // this is our descriptor table for this root parameter rootParameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; // our pixel shader will be the only shader accessing this parameter for now // create a static sampler D3D12_STATIC_SAMPLER_DESC sampler = {}; sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT; sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER; sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER; sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER; sampler.MipLODBias = 0; sampler.MaxAnisotropy = 0; sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; sampler.MinLOD = 0.0f; sampler.MaxLOD = D3D12_FLOAT32_MAX; sampler.ShaderRegister = 0; sampler.RegisterSpace = 0; sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Init(_countof(rootParameters), // we have 2 root parameters rootParameters, // a pointer to the beginning of our root parameters array 1, // we have one static sampler &sampler, // a pointer to our static sampler (array) D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | // we can deny shader stages here for better performance D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS); ID3DBlob* errorBuff; // a buffer holding the error data if any ID3DBlob* signature; hr = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); return false; } hr = device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)); if (FAILED(hr)) { return false; } // create vertex and pixel shaders // when debugging, we can compile the shader files at runtime. // but for release versions, we can compile the hlsl shaders // with fxc.exe to create .cso files, which contain the shader // bytecode. We can load the .cso files at runtime to get the // shader bytecode, which of course is faster than compiling // them at runtime // compile vertex shader ID3DBlob* vertexShader; // d3d blob for holding vertex shader bytecode hr = D3DCompileFromFile(L"VertexShader.hlsl", nullptr, nullptr, "main", "vs_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &vertexShader, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); Running = false; return false; } // fill out a shader bytecode structure, which is basically just a pointer // to the shader bytecode and the size of the shader bytecode D3D12_SHADER_BYTECODE vertexShaderBytecode = {}; vertexShaderBytecode.BytecodeLength = vertexShader->GetBufferSize(); vertexShaderBytecode.pShaderBytecode = vertexShader->GetBufferPointer(); // compile pixel shader ID3DBlob* pixelShader; hr = D3DCompileFromFile(L"PixelShader.hlsl", nullptr, nullptr, "main", "ps_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &pixelShader, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); Running = false; return false; } // fill out shader bytecode structure for pixel shader D3D12_SHADER_BYTECODE pixelShaderBytecode = {}; pixelShaderBytecode.BytecodeLength = pixelShader->GetBufferSize(); pixelShaderBytecode.pShaderBytecode = pixelShader->GetBufferPointer(); // create input layout // The input layout is used by the Input Assembler so that it knows // how to read the vertex data bound to it. D3D12_INPUT_ELEMENT_DESC inputLayout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } }; // fill out an input layout description structure D3D12_INPUT_LAYOUT_DESC inputLayoutDesc = {}; // we can get the number of elements in an array by "sizeof(array) / sizeof(arrayElementType)" inputLayoutDesc.NumElements = sizeof(inputLayout) / sizeof(D3D12_INPUT_ELEMENT_DESC); inputLayoutDesc.pInputElementDescs = inputLayout; // create a pipeline state object (PSO) // In a real application, you will have many pso's. for each different shader // or different combinations of shaders, different blend states or different rasterizer states, // different topology types (point, line, triangle, patch), or a different number // of render targets you will need a pso // VS is the only required shader for a pso. You might be wondering when a case would be where // you only set the VS. It's possible that you have a pso that only outputs data with the stream // output, and not on a render target, which means you would not need anything after the stream // output. D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; // a structure to define a pso psoDesc.InputLayout = inputLayoutDesc; // the structure describing our input layout psoDesc.pRootSignature = rootSignature; // the root signature that describes the input data this pso needs psoDesc.VS = vertexShaderBytecode; // structure describing where to find the vertex shader bytecode and how large it is psoDesc.PS = pixelShaderBytecode; // same as VS but for pixel shader psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; // type of topology we are drawing psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; // format of the render target psoDesc.SampleDesc = sampleDesc; // must be the same sample description as the swapchain and depth/stencil buffer psoDesc.SampleMask = 0xffffffff; // sample mask has to do with multi-sampling. 0xffffffff means point sampling is done psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); // a default rasterizer state. psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT); // a default blent state. psoDesc.NumRenderTargets = 1; // we are only binding one render target psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT); // a default depth stencil state // create the pso hr = device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineStateObject)); if (FAILED(hr)) { Running = false; return false; } // Text PSO // compile vertex shader ID3DBlob* textVertexShader; // d3d blob for holding vertex shader bytecode hr = D3DCompileFromFile(L"TextVertexShader.hlsl", nullptr, nullptr, "main", "vs_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &textVertexShader, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); Running = false; return false; } // fill out a shader bytecode structure, which is basically just a pointer // to the shader bytecode and the size of the shader bytecode D3D12_SHADER_BYTECODE textVertexShaderBytecode = {}; textVertexShaderBytecode.BytecodeLength = textVertexShader->GetBufferSize(); textVertexShaderBytecode.pShaderBytecode = textVertexShader->GetBufferPointer(); // compile pixel shader ID3DBlob* textPixelShader; hr = D3DCompileFromFile(L"TextPixelShader.hlsl", nullptr, nullptr, "main", "ps_5_0", D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, 0, &textPixelShader, &errorBuff); if (FAILED(hr)) { OutputDebugStringA((char*)errorBuff->GetBufferPointer()); Running = false; return false; } // fill out shader bytecode structure for pixel shader D3D12_SHADER_BYTECODE textPixelShaderBytecode = {}; textPixelShaderBytecode.BytecodeLength = textPixelShader->GetBufferSize(); textPixelShaderBytecode.pShaderBytecode = textPixelShader->GetBufferPointer(); // create input layout // The input layout is used by the Input Assembler so that it knows // how to read the vertex data bound to it. D3D12_INPUT_ELEMENT_DESC textInputLayout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 16, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 32, D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA, 1 } }; // fill out an input layout description structure D3D12_INPUT_LAYOUT_DESC textInputLayoutDesc = {}; // we can get the number of elements in an array by "sizeof(array) / sizeof(arrayElementType)" textInputLayoutDesc.NumElements = sizeof(textInputLayout) / sizeof(D3D12_INPUT_ELEMENT_DESC); textInputLayoutDesc.pInputElementDescs = textInputLayout; // create the text pipeline state object (PSO) D3D12_GRAPHICS_PIPELINE_STATE_DESC textpsoDesc = {}; textpsoDesc.InputLayout = textInputLayoutDesc; textpsoDesc.pRootSignature = rootSignature; textpsoDesc.VS = textVertexShaderBytecode; textpsoDesc.PS = textPixelShaderBytecode; textpsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; textpsoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; textpsoDesc.SampleDesc = sampleDesc; textpsoDesc.SampleMask = 0xffffffff; textpsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); D3D12_BLEND_DESC textBlendStateDesc = {}; textBlendStateDesc.AlphaToCoverageEnable = FALSE; textBlendStateDesc.IndependentBlendEnable = FALSE; textBlendStateDesc.RenderTarget[0].BlendEnable = TRUE; textBlendStateDesc.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA; textBlendStateDesc.RenderTarget[0].DestBlend = D3D12_BLEND_ONE; textBlendStateDesc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD; textBlendStateDesc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA; textBlendStateDesc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ONE; textBlendStateDesc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD; textBlendStateDesc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL; textpsoDesc.BlendState = textBlendStateDesc; textpsoDesc.NumRenderTargets = 1; D3D12_DEPTH_STENCIL_DESC textDepthStencilDesc= CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT); textDepthStencilDesc.DepthEnable = false; textpsoDesc.DepthStencilState = textDepthStencilDesc; // create the text pso hr = device->CreateGraphicsPipelineState(&textpsoDesc, IID_PPV_ARGS(&textPSO)); if (FAILED(hr)) { Running = false; return false; } // Create vertex buffer // a cube Vertex vList[] = { // front face { -0.5f, 0.5f, -0.5f, 0.0f, 0.0f }, { 0.5f, -0.5f, -0.5f, 1.0f, 1.0f }, { -0.5f, -0.5f, -0.5f, 0.0f, 1.0f }, { 0.5f, 0.5f, -0.5f, 1.0f, 0.0f }, // right side face { 0.5f, -0.5f, -0.5f, 0.0f, 1.0f }, { 0.5f, 0.5f, 0.5f, 1.0f, 0.0f }, { 0.5f, -0.5f, 0.5f, 1.0f, 1.0f }, { 0.5f, 0.5f, -0.5f, 0.0f, 0.0f }, // left side face { -0.5f, 0.5f, 0.5f, 0.0f, 0.0f }, { -0.5f, -0.5f, -0.5f, 1.0f, 1.0f }, { -0.5f, -0.5f, 0.5f, 0.0f, 1.0f }, { -0.5f, 0.5f, -0.5f, 1.0f, 0.0f }, // back face { 0.5f, 0.5f, 0.5f, 0.0f, 0.0f }, { -0.5f, -0.5f, 0.5f, 1.0f, 1.0f }, { 0.5f, -0.5f, 0.5f, 0.0f, 1.0f }, { -0.5f, 0.5f, 0.5f, 1.0f, 0.0f }, // top face { -0.5f, 0.5f, -0.5f, 0.0f, 1.0f }, { 0.5f, 0.5f, 0.5f, 1.0f, 0.0f }, { 0.5f, 0.5f, -0.5f, 1.0f, 1.0f }, { -0.5f, 0.5f, 0.5f, 0.0f, 0.0f }, // bottom face { 0.5f, -0.5f, 0.5f, 0.0f, 0.0f }, { -0.5f, -0.5f, -0.5f, 1.0f, 1.0f }, { 0.5f, -0.5f, -0.5f, 0.0f, 1.0f }, { -0.5f, -0.5f, 0.5f, 1.0f, 0.0f }, }; int vBufferSize = sizeof(vList); // create default heap // default heap is memory on the GPU. Only the GPU has access to this memory // To get data into this heap, we will have to upload the data using // an upload heap hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), // a default heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(vBufferSize), // resource description for a buffer D3D12_RESOURCE_STATE_COPY_DEST, // we will start this heap in the copy destination state since we will copy data // from the upload heap to this heap nullptr, // optimized clear value must be null for this type of resource. used for render targets and depth/stencil buffers IID_PPV_ARGS(&vertexBuffer)); if (FAILED(hr)) { Running = false; return false; } // we can give resource heaps a name so when we debug with the graphics debugger we know what resource we are looking at vertexBuffer->SetName(L"Vertex Buffer Resource Heap"); // create upload heap // upload heaps are used to upload data to the GPU. CPU can write to it, GPU can read from it // We will upload the vertex buffer using this heap to the default heap ID3D12Resource* vBufferUploadHeap; hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // upload heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(vBufferSize), // resource description for a buffer D3D12_RESOURCE_STATE_GENERIC_READ, // GPU will read from this buffer and copy its contents to the default heap nullptr, IID_PPV_ARGS(&vBufferUploadHeap)); if (FAILED(hr)) { Running = false; return false; } vBufferUploadHeap->SetName(L"Vertex Buffer Upload Resource Heap"); // store vertex buffer in upload heap D3D12_SUBRESOURCE_DATA vertexData = {}; vertexData.pData = reinterpret_cast<BYTE*>(vList); // pointer to our vertex array vertexData.RowPitch = vBufferSize; // size of all our triangle vertex data vertexData.SlicePitch = vBufferSize; // also the size of our triangle vertex data // we are now creating a command with the command list to copy the data from // the upload heap to the default heap UpdateSubresources(commandList, vertexBuffer, vBufferUploadHeap, 0, 0, 1, &vertexData); // transition the vertex buffer data from copy destination state to vertex buffer state commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(vertexBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER)); // Create index buffer // a quad (2 triangles) DWORD iList[] = { // ffront face 0, 1, 2, // first triangle 0, 3, 1, // second triangle // left face 4, 5, 6, // first triangle 4, 7, 5, // second triangle // right face 8, 9, 10, // first triangle 8, 11, 9, // second triangle // back face 12, 13, 14, // first triangle 12, 15, 13, // second triangle // top face 16, 17, 18, // first triangle 16, 19, 17, // second triangle // bottom face 20, 21, 22, // first triangle 20, 23, 21, // second triangle }; int iBufferSize = sizeof(iList); numCubeIndices = sizeof(iList) / sizeof(DWORD); // create default heap to hold index buffer hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), // a default heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(iBufferSize), // resource description for a buffer D3D12_RESOURCE_STATE_COPY_DEST, // start in the copy destination state nullptr, // optimized clear value must be null for this type of resource IID_PPV_ARGS(&indexBuffer)); if (FAILED(hr)) { Running = false; return false; } // we can give resource heaps a name so when we debug with the graphics debugger we know what resource we are looking at vertexBuffer->SetName(L"Index Buffer Resource Heap"); // create upload heap to upload index buffer ID3D12Resource* iBufferUploadHeap; hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // upload heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(vBufferSize), // resource description for a buffer D3D12_RESOURCE_STATE_GENERIC_READ, // GPU will read from this buffer and copy its contents to the default heap nullptr, IID_PPV_ARGS(&iBufferUploadHeap)); if (FAILED(hr)) { Running = false; return false; } vBufferUploadHeap->SetName(L"Index Buffer Upload Resource Heap"); // store vertex buffer in upload heap D3D12_SUBRESOURCE_DATA indexData = {}; indexData.pData = reinterpret_cast<BYTE*>(iList); // pointer to our index array indexData.RowPitch = iBufferSize; // size of all our index buffer indexData.SlicePitch = iBufferSize; // also the size of our index buffer // we are now creating a command with the command list to copy the data from // the upload heap to the default heap UpdateSubresources(commandList, indexBuffer, iBufferUploadHeap, 0, 0, 1, &indexData); // transition the vertex buffer data from copy destination state to vertex buffer state commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(indexBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER)); // Create the depth/stencil buffer // create a depth stencil descriptor heap so we can get a pointer to the depth stencil buffer D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {}; dsvHeapDesc.NumDescriptors = 1; dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; hr = device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&dsDescriptorHeap)); if (FAILED(hr)) { Running = false; return false; } D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {}; depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT; depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE; D3D12_CLEAR_VALUE depthOptimizedClearValue = {}; depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT; depthOptimizedClearValue.DepthStencil.Depth = 1.0f; depthOptimizedClearValue.DepthStencil.Stencil = 0; hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, Width, Height, 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL), D3D12_RESOURCE_STATE_DEPTH_WRITE, &depthOptimizedClearValue, IID_PPV_ARGS(&depthStencilBuffer) ); if (FAILED(hr)) { Running = false; return false; } dsDescriptorHeap->SetName(L"Depth/Stencil Resource Heap"); device->CreateDepthStencilView(depthStencilBuffer, &depthStencilDesc, dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); // create the constant buffer resource heap // We will update the constant buffer one or more times per frame, so we will use only an upload heap // unlike previously we used an upload heap to upload the vertex and index data, and then copied over // to a default heap. If you plan to use a resource for more than a couple frames, it is usually more // efficient to copy to a default heap where it stays on the gpu. In this case, our constant buffer // will be modified and uploaded at least once per frame, so we only use an upload heap // first we will create a resource heap (upload heap) for each frame for the cubes constant buffers // As you can see, we are allocating 64KB for each resource we create. Buffer resource heaps must be // an alignment of 64KB. We are creating 3 resources, one for each frame. Each constant buffer is // only a 4x4 matrix of floats in this tutorial. So with a float being 4 bytes, we have // 16 floats in one constant buffer, and we will store 2 constant buffers in each // heap, one for each cube, thats only 64x2 bits, or 128 bits we are using for each // resource, and each resource must be at least 64KB (65536 bits) for (int i = 0; i < frameBufferCount; ++i) { // create resource for cube 1 hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // this heap will be used to upload the constant buffer data D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), // size of the resource heap. Must be a multiple of 64KB for single-textures and constant buffers D3D12_RESOURCE_STATE_GENERIC_READ, // will be data that is read from so we keep it in the generic read state nullptr, // we do not have use an optimized clear value for constant buffers IID_PPV_ARGS(&constantBufferUploadHeaps[i])); if (FAILED(hr)) { Running = false; return false; } constantBufferUploadHeaps[i]->SetName(L"Constant Buffer Upload Resource Heap"); ZeroMemory(&cbPerObject, sizeof(cbPerObject)); CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU. (so end is less than or equal to begin) // map the resource heap to get a gpu virtual address to the beginning of the heap hr = constantBufferUploadHeaps[i]->Map(0, &readRange, reinterpret_cast<void**>(&cbvGPUAddress[i])); // Because of the constant read alignment requirements, constant buffer views must be 256 bit aligned. Our buffers are smaller than 256 bits, // so we need to add spacing between the two buffers, so that the second buffer starts at 256 bits from the beginning of the resource heap. memcpy(cbvGPUAddress[i], &cbPerObject, sizeof(cbPerObject)); // cube1's constant buffer data memcpy(cbvGPUAddress[i] + ConstantBufferPerObjectAlignedSize, &cbPerObject, sizeof(cbPerObject)); // cube2's constant buffer data } // load the image, create a texture resource and descriptor heap // Load the image from file D3D12_RESOURCE_DESC textureDesc; int imageBytesPerRow; BYTE* imageData; int imageSize = LoadImageDataFromFile(&imageData, textureDesc, L"braynzar.jpg", imageBytesPerRow); // make sure we have data if(imageSize <= 0) { Running = false; return false; } // create a default heap where the upload heap will copy its contents into (contents being the texture) hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), // a default heap D3D12_HEAP_FLAG_NONE, // no flags &textureDesc, // the description of our texture D3D12_RESOURCE_STATE_COPY_DEST, // We will copy the texture from the upload heap to here, so we start it out in a copy dest state nullptr, // used for render targets and depth/stencil buffers IID_PPV_ARGS(&textureBuffer)); if (FAILED(hr)) { Running = false; return false; } textureBuffer->SetName(L"Texture Buffer Resource Heap"); ID3D12Resource* textureBufferUploadHeap; UINT64 textureUploadBufferSize; // this function gets the size an upload buffer needs to be to upload a texture to the gpu. // each row must be 256 byte aligned except for the last row, which can just be the size in bytes of the row // eg. textureUploadBufferSize = ((((width * numBytesPerPixel) + 255) & ~255) * (height - 1)) + (width * numBytesPerPixel); //textureUploadBufferSize = (((imageBytesPerRow + 255) & ~255) * (textureDesc.Height - 1)) + imageBytesPerRow; device->GetCopyableFootprints(&textureDesc, 0, 1, 0, nullptr, nullptr, nullptr, &textureUploadBufferSize); // now we create an upload heap to upload our texture to the GPU hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // upload heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(textureUploadBufferSize), // resource description for a buffer (storing the image data in this heap just to copy to the default heap) D3D12_RESOURCE_STATE_GENERIC_READ, // We will copy the contents from this heap to the default heap above nullptr, IID_PPV_ARGS(&textureBufferUploadHeap)); if (FAILED(hr)) { Running = false; return false; } textureBufferUploadHeap->SetName(L"Texture Buffer Upload Resource Heap"); // store vertex buffer in upload heap D3D12_SUBRESOURCE_DATA textureData = {}; textureData.pData = &imageData[0]; // pointer to our image data textureData.RowPitch = imageBytesPerRow; // size of all our triangle vertex data textureData.SlicePitch = imageBytesPerRow * textureDesc.Height; // also the size of our triangle vertex data // Now we copy the upload buffer contents to the default heap UpdateSubresources(commandList, textureBuffer, textureBufferUploadHeap, 0, 0, 1, &textureData); // transition the texture default heap to a pixel shader resource (we will be sampling from this heap in the pixel shader to get the color of pixels) commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(textureBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)); // create the descriptor heap that will store our srv D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; heapDesc.NumDescriptors = 2; // we now have an srv for the font as well as the cube's srv heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; hr = device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&mainDescriptorHeap)); if (FAILED(hr)) { Running = false; } // now we create a shader resource view (descriptor that points to the texture and describes it) D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {}; srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; srvDesc.Format = textureDesc.Format; srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = 1; device->CreateShaderResourceView(textureBuffer, &srvDesc, mainDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); // Load Font arialFont = LoadFont(L"Arial.fnt", Width, Height); // Load the image from file D3D12_RESOURCE_DESC fontTextureDesc; int fontImageBytesPerRow; BYTE* fontImageData; int fontImageSize = LoadImageDataFromFile(&fontImageData, fontTextureDesc, arialFont.fontImage.c_str(), fontImageBytesPerRow); // make sure we have data if (fontImageData <= 0) { Running = false; return false; } // create the font texture resource hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &fontTextureDesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&arialFont.textureBuffer)); if (FAILED(hr)) { Running = false; return false; } arialFont.textureBuffer->SetName(L"Font Texture Buffer Resource Heap"); ID3D12Resource* fontTextureBufferUploadHeap; UINT64 fontTextureUploadBufferSize; device->GetCopyableFootprints(&fontTextureDesc, 0, 1, 0, nullptr, nullptr, nullptr, &fontTextureUploadBufferSize); // create an upload heap to copy the texture to the gpu hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(fontTextureUploadBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&fontTextureBufferUploadHeap)); if (FAILED(hr)) { Running = false; return false; } fontTextureBufferUploadHeap->SetName(L"Font Texture Buffer Upload Resource Heap"); // store font image in upload heap D3D12_SUBRESOURCE_DATA fontTextureData = {}; fontTextureData.pData = &fontImageData[0]; // pointer to our image data fontTextureData.RowPitch = fontImageBytesPerRow; // size of all our triangle vertex data fontTextureData.SlicePitch = fontImageBytesPerRow * fontTextureDesc.Height; // also the size of our triangle vertex data // Now we copy the upload buffer contents to the default heap UpdateSubresources(commandList, arialFont.textureBuffer, fontTextureBufferUploadHeap, 0, 0, 1, &fontTextureData); // transition the texture default heap to a pixel shader resource (we will be sampling from this heap in the pixel shader to get the color of pixels) commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(arialFont.textureBuffer, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)); // create an srv for the font D3D12_SHADER_RESOURCE_VIEW_DESC fontsrvDesc = {}; fontsrvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; fontsrvDesc.Format = fontTextureDesc.Format; fontsrvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; fontsrvDesc.Texture2D.MipLevels = 1; // we need to get the next descriptor location in the descriptor heap to store this srv srvHandleSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); arialFont.srvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mainDescriptorHeap->GetGPUDescriptorHandleForHeapStart(), 1, srvHandleSize); CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(mainDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), 1, srvHandleSize); device->CreateShaderResourceView(arialFont.textureBuffer, &fontsrvDesc, srvHandle); // create text vertex buffer committed resources for (int i = 0; i < frameBufferCount; ++i) { // create upload heap. We will fill this with data for our text ID3D12Resource* vBufferUploadHeap; hr = device->CreateCommittedResource( &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), // upload heap D3D12_HEAP_FLAG_NONE, // no flags &CD3DX12_RESOURCE_DESC::Buffer(maxNumTextCharacters * sizeof(TextVertex)), // resource description for a buffer D3D12_RESOURCE_STATE_GENERIC_READ, // GPU will read from this buffer and copy its contents to the default heap nullptr, IID_PPV_ARGS(&textVertexBuffer[i])); if (FAILED(hr)) { Running = false; return false; } textVertexBuffer[i]->SetName(L"Text Vertex Buffer Upload Resource Heap"); CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU. (so end is less than or equal to begin) // map the resource heap to get a gpu virtual address to the beginning of the heap hr = textVertexBuffer[i]->Map(0, &readRange, reinterpret_cast<void**>(&textVBGPUAddress[i])); } // create the text pso // Now we execute the command list to upload the initial assets (triangle data) commandList->Close(); ID3D12CommandList* ppCommandLists[] = { commandList }; commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); // increment the fence value now, otherwise the buffer might not be uploaded by the time we start drawing fenceValue[frameIndex]++; hr = commandQueue->Signal(fence[frameIndex], fenceValue[frameIndex]); if (FAILED(hr)) { Running = false; return false; } // we are done with image data now that we've uploaded it to the gpu, so free it up delete fontImageData; delete imageData; // create a vertex buffer view. We get the GPU memory address to the vertex pointer using the GetGPUVirtualAddress() method vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress(); vertexBufferView.StrideInBytes = sizeof(Vertex); vertexBufferView.SizeInBytes = vBufferSize; // set the text vertex buffer view for each frame for (int i = 0; i < frameBufferCount; ++i) { textVertexBufferView[i].BufferLocation = textVertexBuffer[i]->GetGPUVirtualAddress(); textVertexBufferView[i].StrideInBytes = sizeof(TextVertex); textVertexBufferView[i].SizeInBytes = maxNumTextCharacters * sizeof(TextVertex); } // create a vertex buffer view for the triangle. We get the GPU memory address to the vertex pointer using the GetGPUVirtualAddress() method indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress(); indexBufferView.Format = DXGI_FORMAT_R32_UINT; // 32-bit unsigned integer (this is what a dword is, double word, a word is 2 bytes) indexBufferView.SizeInBytes = iBufferSize; // Fill out the Viewport viewport.TopLeftX = 0; viewport.TopLeftY = 0; viewport.Width = Width; viewport.Height = Height; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; // Fill out a scissor rect scissorRect.left = 0; scissorRect.top = 0; scissorRect.right = Width; scissorRect.bottom = Height; // build projection and view matrix XMMATRIX tmpMat = XMMatrixPerspectiveFovLH(45.0f*(3.14f/180.0f), (float)Width / (float)Height, 0.1f, 1000.0f); XMStoreFloat4x4(&cameraProjMat, tmpMat); // set starting camera state cameraPosition = XMFLOAT4(0.0f, 2.0f, -4.0f, 0.0f); cameraTarget = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.0f); cameraUp = XMFLOAT4(0.0f, 1.0f, 0.0f, 0.0f); // build view matrix XMVECTOR cPos = XMLoadFloat4(&cameraPosition); XMVECTOR cTarg = XMLoadFloat4(&cameraTarget); XMVECTOR cUp = XMLoadFloat4(&cameraUp); tmpMat = XMMatrixLookAtLH(cPos, cTarg, cUp); XMStoreFloat4x4(&cameraViewMat, tmpMat); // set starting cubes position // first cube cube1Position = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.0f); // set cube 1's position XMVECTOR posVec = XMLoadFloat4(&cube1Position); // create xmvector for cube1's position tmpMat = XMMatrixTranslationFromVector(posVec); // create translation matrix from cube1's position vector XMStoreFloat4x4(&cube1RotMat, XMMatrixIdentity()); // initialize cube1's rotation matrix to identity matrix XMStoreFloat4x4(&cube1WorldMat, tmpMat); // store cube1's world matrix // second cube cube2PositionOffset = XMFLOAT4(1.5f, 0.0f, 0.0f, 0.0f); posVec = XMLoadFloat4(&cube2PositionOffset) + XMLoadFloat4(&cube1Position); // create xmvector for cube2's position // we are rotating around cube1 here, so add cube2's position to cube1 tmpMat = XMMatrixTranslationFromVector(posVec); // create translation matrix from cube2's position offset vector XMStoreFloat4x4(&cube2RotMat, XMMatrixIdentity()); // initialize cube2's rotation matrix to identity matrix XMStoreFloat4x4(&cube2WorldMat, tmpMat); // store cube2's world matrix return true; } void Update(double delta) { // update app logic, such as moving the camera or figuring out what objects are in view // create rotation matrices XMMATRIX rotXMat = XMMatrixRotationX(0.001f * delta); XMMATRIX rotYMat = XMMatrixRotationY(0.002f * delta); XMMATRIX rotZMat = XMMatrixRotationZ(0.003f * delta); // add rotation to cube1's rotation matrix and store it XMMATRIX rotMat = XMLoadFloat4x4(&cube1RotMat) * rotXMat * rotYMat * rotZMat; XMStoreFloat4x4(&cube1RotMat, rotMat); // create translation matrix for cube 1 from cube 1's position vector XMMATRIX translationMat = XMMatrixTranslationFromVector(XMLoadFloat4(&cube1Position)); // create cube1's world matrix by first rotating the cube, then positioning the rotated cube XMMATRIX worldMat = rotMat * translationMat; // store cube1's world matrix XMStoreFloat4x4(&cube1WorldMat, worldMat); // update constant buffer for cube1 // create the wvp matrix and store in constant buffer XMMATRIX viewMat = XMLoadFloat4x4(&cameraViewMat); // load view matrix XMMATRIX projMat = XMLoadFloat4x4(&cameraProjMat); // load projection matrix XMMATRIX wvpMat = XMLoadFloat4x4(&cube1WorldMat) * viewMat * projMat; // create wvp matrix XMMATRIX transposed = XMMatrixTranspose(wvpMat); // must transpose wvp matrix for the gpu XMStoreFloat4x4(&cbPerObject.wvpMat, transposed); // store transposed wvp matrix in constant buffer // copy our ConstantBuffer instance to the mapped constant buffer resource memcpy(cbvGPUAddress[frameIndex], &cbPerObject, sizeof(cbPerObject)); // now do cube2's world matrix // create rotation matrices for cube2 rotXMat = XMMatrixRotationX(0.003f * delta); rotYMat = XMMatrixRotationY(0.002f * delta); rotZMat = XMMatrixRotationZ(0.001f * delta); // add rotation to cube2's rotation matrix and store it rotMat = rotZMat * (XMLoadFloat4x4(&cube2RotMat) * (rotXMat * rotYMat)); XMStoreFloat4x4(&cube2RotMat, rotMat); // create translation matrix for cube 2 to offset it from cube 1 (its position relative to cube1 XMMATRIX translationOffsetMat = XMMatrixTranslationFromVector(XMLoadFloat4(&cube2PositionOffset)); // we want cube 2 to be half the size of cube 1, so we scale it by .5 in all dimensions XMMATRIX scaleMat = XMMatrixScaling(0.5f, 0.5f, 0.5f); // reuse worldMat. // first we scale cube2. scaling happens relative to point 0,0,0, so you will almost always want to scale first // then we translate it. // then we rotate it. rotation always rotates around point 0,0,0 // finally we move it to cube 1's position, which will cause it to rotate around cube 1 worldMat = scaleMat * translationOffsetMat * rotMat * translationMat; wvpMat = XMLoadFloat4x4(&cube2WorldMat) * viewMat * projMat; // create wvp matrix transposed = XMMatrixTranspose(wvpMat); // must transpose wvp matrix for the gpu XMStoreFloat4x4(&cbPerObject.wvpMat, transposed); // store transposed wvp matrix in constant buffer // copy our ConstantBuffer instance to the mapped constant buffer resource memcpy(cbvGPUAddress[frameIndex] + ConstantBufferPerObjectAlignedSize, &cbPerObject, sizeof(cbPerObject)); // store cube2's world matrix XMStoreFloat4x4(&cube2WorldMat, worldMat); } int fpscounter = 0; void UpdatePipeline() { HRESULT hr; // We have to wait for the gpu to finish with the command allocator before we reset it WaitForPreviousFrame(); // we can only reset an allocator once the gpu is done with it // resetting an allocator frees the memory that the command list was stored in hr = commandAllocator[frameIndex]->Reset(); if (FAILED(hr)) { Running = false; } // reset the command list. by resetting the command list we are putting it into // a recording state so we can start recording commands into the command allocator. // the command allocator that we reference here may have multiple command lists // associated with it, but only one can be recording at any time. Make sure // that any other command lists associated to this command allocator are in // the closed state (not recording). // Here you will pass an initial pipeline state object as the second parameter, // but in this tutorial we are only clearing the rtv, and do not actually need // anything but an initial default pipeline, which is what we get by setting // the second parameter to NULL hr = commandList->Reset(commandAllocator[frameIndex], pipelineStateObject); if (FAILED(hr)) { Running = false; } // here we start recording commands into the commandList (which all the commands will be stored in the commandAllocator) // transition the "frameIndex" render target from the present state to the render target state so the command list draws to it starting from here commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex], D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET)); // here we again get the handle to our current render target view so we can set it as the render target in the output merger stage of the pipeline CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), frameIndex, rtvDescriptorSize); // get a handle to the depth/stencil buffer CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); // set the render target for the output merger stage (the output of the pipeline) commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle); // Clear the render target by using the ClearRenderTargetView command const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f }; commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); // clear the depth/stencil buffer commandList->ClearDepthStencilView(dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); // set root signature commandList->SetGraphicsRootSignature(rootSignature); // set the root signature // set the descriptor heap ID3D12DescriptorHeap* descriptorHeaps[] = { mainDescriptorHeap }; commandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps); // set the descriptor table to the descriptor heap (parameter 1, as constant buffer root descriptor is parameter index 0) commandList->SetGraphicsRootDescriptorTable(1, mainDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); commandList->RSSetViewports(1, &viewport); // set the viewports commandList->RSSetScissorRects(1, &scissorRect); // set the scissor rects commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // set the primitive topology commandList->IASetVertexBuffers(0, 1, &vertexBufferView); // set the vertex buffer (using the vertex buffer view) commandList->IASetIndexBuffer(&indexBufferView); // first cube // set cube1's constant buffer commandList->SetGraphicsRootConstantBufferView(0, constantBufferUploadHeaps[frameIndex]->GetGPUVirtualAddress()); // draw first cube commandList->DrawIndexedInstanced(numCubeIndices, 1, 0, 0, 0); // second cube // set cube2's constant buffer. You can see we are adding the size of ConstantBufferPerObject to the constant buffer // resource heaps address. This is because cube1's constant buffer is stored at the beginning of the resource heap, while // cube2's constant buffer data is stored after (256 bits from the start of the heap). commandList->SetGraphicsRootConstantBufferView(0, constantBufferUploadHeaps[frameIndex]->GetGPUVirtualAddress() + ConstantBufferPerObjectAlignedSize); // draw second cube commandList->DrawIndexedInstanced(numCubeIndices, 1, 0, 0, 0); // draw the text RenderText(arialFont, std::wstring(L"FPS: ") + std::to_wstring(timer.fps), XMFLOAT2(0.02f, 0.01f), XMFLOAT2(2.0f, 2.0f)); // transition the "frameIndex" render target from the render target state to the present state. If the debug layer is enabled, you will receive a // warning if present is called on the render target when it's not in the present state commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(renderTargets[frameIndex], D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT)); hr = commandList->Close(); if (FAILED(hr)) { Running = false; } } void Render() { HRESULT hr; UpdatePipeline(); // update the pipeline by sending commands to the commandqueue // create an array of command lists (only one command list here) ID3D12CommandList* ppCommandLists[] = { commandList }; // execute the array of command lists commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); // this command goes in at the end of our command queue. we will know when our command queue // has finished because the fence value will be set to "fenceValue" from the GPU since the command // queue is being executed on the GPU hr = commandQueue->Signal(fence[frameIndex], fenceValue[frameIndex]); if (FAILED(hr)) { Running = false; } // present the current backbuffer hr = swapChain->Present(0, 0); if (FAILED(hr)) { Running = false; } } void Cleanup() { // wait for the gpu to finish all frames for (int i = 0; i < frameBufferCount; ++i) { frameIndex = i; WaitForPreviousFrame(); } // get swapchain out of full screen before exiting BOOL fs = false; if (swapChain->GetFullscreenState(&fs, NULL)) swapChain->SetFullscreenState(false, NULL); SAFE_RELEASE(device); SAFE_RELEASE(swapChain); SAFE_RELEASE(commandQueue); SAFE_RELEASE(rtvDescriptorHeap); SAFE_RELEASE(commandList); for (int i = 0; i < frameBufferCount; ++i) { SAFE_RELEASE(renderTargets[i]); SAFE_RELEASE(commandAllocator[i]); SAFE_RELEASE(fence[i]); }; SAFE_RELEASE(pipelineStateObject); SAFE_RELEASE(rootSignature); SAFE_RELEASE(vertexBuffer); SAFE_RELEASE(indexBuffer); SAFE_RELEASE(depthStencilBuffer); SAFE_RELEASE(dsDescriptorHeap); for (int i = 0; i < frameBufferCount; ++i) { SAFE_RELEASE(constantBufferUploadHeaps[i]); }; } void WaitForPreviousFrame() { HRESULT hr; // swap the current rtv buffer index so we draw on the correct buffer frameIndex = swapChain->GetCurrentBackBufferIndex(); // if the current fence value is still less than "fenceValue", then we know the GPU has not finished executing // the command queue since it has not reached the "commandQueue->Signal(fence, fenceValue)" command if (fence[frameIndex]->GetCompletedValue() < fenceValue[frameIndex]) { // we have the fence create an event which is signaled once the fence's current value is "fenceValue" hr = fence[frameIndex]->SetEventOnCompletion(fenceValue[frameIndex], fenceEvent); if (FAILED(hr)) { Running = false; } // We will wait until the fence has triggered the event that it's current value has reached "fenceValue". once it's value // has reached "fenceValue", we know the command queue has finished executing WaitForSingleObject(fenceEvent, INFINITE); } // increment fenceValue for next frame fenceValue[frameIndex]++; } // get the dxgi format equivilent of a wic format DXGI_FORMAT GetDXGIFormatFromWICFormat(WICPixelFormatGUID& wicFormatGUID) { if (wicFormatGUID == GUID_WICPixelFormat128bppRGBAFloat) return DXGI_FORMAT_R32G32B32A32_FLOAT; else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBAHalf) return DXGI_FORMAT_R16G16B16A16_FLOAT; else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBA) return DXGI_FORMAT_R16G16B16A16_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA) return DXGI_FORMAT_R8G8B8A8_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat32bppBGRA) return DXGI_FORMAT_B8G8R8A8_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat32bppBGR) return DXGI_FORMAT_B8G8R8X8_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA1010102XR) return DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBA1010102) return DXGI_FORMAT_R10G10B10A2_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat16bppBGRA5551) return DXGI_FORMAT_B5G5R5A1_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat16bppBGR565) return DXGI_FORMAT_B5G6R5_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat32bppGrayFloat) return DXGI_FORMAT_R32_FLOAT; else if (wicFormatGUID == GUID_WICPixelFormat16bppGrayHalf) return DXGI_FORMAT_R16_FLOAT; else if (wicFormatGUID == GUID_WICPixelFormat16bppGray) return DXGI_FORMAT_R16_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat8bppGray) return DXGI_FORMAT_R8_UNORM; else if (wicFormatGUID == GUID_WICPixelFormat8bppAlpha) return DXGI_FORMAT_A8_UNORM; else return DXGI_FORMAT_UNKNOWN; } // get a dxgi compatible wic format from another wic format WICPixelFormatGUID GetConvertToWICFormat(WICPixelFormatGUID& wicFormatGUID) { if (wicFormatGUID == GUID_WICPixelFormatBlackWhite) return GUID_WICPixelFormat8bppGray; else if (wicFormatGUID == GUID_WICPixelFormat1bppIndexed) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat2bppIndexed) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat4bppIndexed) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat8bppIndexed) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat2bppGray) return GUID_WICPixelFormat8bppGray; else if (wicFormatGUID == GUID_WICPixelFormat4bppGray) return GUID_WICPixelFormat8bppGray; else if (wicFormatGUID == GUID_WICPixelFormat16bppGrayFixedPoint) return GUID_WICPixelFormat16bppGrayHalf; else if (wicFormatGUID == GUID_WICPixelFormat32bppGrayFixedPoint) return GUID_WICPixelFormat32bppGrayFloat; else if (wicFormatGUID == GUID_WICPixelFormat16bppBGR555) return GUID_WICPixelFormat16bppBGRA5551; else if (wicFormatGUID == GUID_WICPixelFormat32bppBGR101010) return GUID_WICPixelFormat32bppRGBA1010102; else if (wicFormatGUID == GUID_WICPixelFormat24bppBGR) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat24bppRGB) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat32bppPBGRA) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat32bppPRGBA) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat48bppRGB) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat48bppBGR) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat64bppBGRA) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat64bppPRGBA) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat64bppPBGRA) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat48bppRGBFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat48bppBGRFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBAFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat64bppBGRAFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBFixedPoint) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat64bppRGBHalf) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat48bppRGBHalf) return GUID_WICPixelFormat64bppRGBAHalf; else if (wicFormatGUID == GUID_WICPixelFormat128bppPRGBAFloat) return GUID_WICPixelFormat128bppRGBAFloat; else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBFloat) return GUID_WICPixelFormat128bppRGBAFloat; else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBAFixedPoint) return GUID_WICPixelFormat128bppRGBAFloat; else if (wicFormatGUID == GUID_WICPixelFormat128bppRGBFixedPoint) return GUID_WICPixelFormat128bppRGBAFloat; else if (wicFormatGUID == GUID_WICPixelFormat32bppRGBE) return GUID_WICPixelFormat128bppRGBAFloat; else if (wicFormatGUID == GUID_WICPixelFormat32bppCMYK) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat64bppCMYK) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat40bppCMYKAlpha) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat80bppCMYKAlpha) return GUID_WICPixelFormat64bppRGBA; #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) || defined(_WIN7_PLATFORM_UPDATE) else if (wicFormatGUID == GUID_WICPixelFormat32bppRGB) return GUID_WICPixelFormat32bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat64bppRGB) return GUID_WICPixelFormat64bppRGBA; else if (wicFormatGUID == GUID_WICPixelFormat64bppPRGBAHalf) return GUID_WICPixelFormat64bppRGBAHalf; #endif else return GUID_WICPixelFormatDontCare; } // get the number of bits per pixel for a dxgi format int GetDXGIFormatBitsPerPixel(DXGI_FORMAT& dxgiFormat) { if (dxgiFormat == DXGI_FORMAT_R32G32B32A32_FLOAT) return 128; else if (dxgiFormat == DXGI_FORMAT_R16G16B16A16_FLOAT) return 64; else if (dxgiFormat == DXGI_FORMAT_R16G16B16A16_UNORM) return 64; else if (dxgiFormat == DXGI_FORMAT_R8G8B8A8_UNORM) return 32; else if (dxgiFormat == DXGI_FORMAT_B8G8R8A8_UNORM) return 32; else if (dxgiFormat == DXGI_FORMAT_B8G8R8X8_UNORM) return 32; else if (dxgiFormat == DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM) return 32; else if (dxgiFormat == DXGI_FORMAT_R10G10B10A2_UNORM) return 32; else if (dxgiFormat == DXGI_FORMAT_B5G5R5A1_UNORM) return 16; else if (dxgiFormat == DXGI_FORMAT_B5G6R5_UNORM) return 16; else if (dxgiFormat == DXGI_FORMAT_R32_FLOAT) return 32; else if (dxgiFormat == DXGI_FORMAT_R16_FLOAT) return 16; else if (dxgiFormat == DXGI_FORMAT_R16_UNORM) return 16; else if (dxgiFormat == DXGI_FORMAT_R8_UNORM) return 8; else if (dxgiFormat == DXGI_FORMAT_A8_UNORM) return 8; } // load and decode image from file int LoadImageDataFromFile(BYTE** imageData, D3D12_RESOURCE_DESC& resourceDescription, LPCWSTR filename, int &bytesPerRow) { HRESULT hr; // we only need one instance of the imaging factory to create decoders and frames static IWICImagingFactory *wicFactory; // reset decoder, frame and converter since these will be different for each image we load IWICBitmapDecoder *wicDecoder = NULL; IWICBitmapFrameDecode *wicFrame = NULL; IWICFormatConverter *wicConverter = NULL; bool imageConverted = false; if (wicFactory == NULL) { // Initialize the COM library CoInitialize(NULL); // create the WIC factory hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wicFactory) ); if (FAILED(hr)) return 0; } // load a decoder for the image hr = wicFactory->CreateDecoderFromFilename( filename, // Image we want to load in NULL, // This is a vendor ID, we do not prefer a specific one so set to null GENERIC_READ, // We want to read from this file WICDecodeMetadataCacheOnLoad, // We will cache the metadata right away, rather than when needed, which might be unknown &wicDecoder // the wic decoder to be created ); if (FAILED(hr)) return 0; // get image from decoder (this will decode the "frame") hr = wicDecoder->GetFrame(0, &wicFrame); if (FAILED(hr)) return 0; // get wic pixel format of image WICPixelFormatGUID pixelFormat; hr = wicFrame->GetPixelFormat(&pixelFormat); if (FAILED(hr)) return 0; // get size of image UINT textureWidth, textureHeight; hr = wicFrame->GetSize(&textureWidth, &textureHeight); if (FAILED(hr)) return 0; // we are not handling sRGB types in this tutorial, so if you need that support, you'll have to figure // out how to implement the support yourself // convert wic pixel format to dxgi pixel format DXGI_FORMAT dxgiFormat = GetDXGIFormatFromWICFormat(pixelFormat); // if the format of the image is not a supported dxgi format, try to convert it if (dxgiFormat == DXGI_FORMAT_UNKNOWN) { // get a dxgi compatible wic format from the current image format WICPixelFormatGUID convertToPixelFormat = GetConvertToWICFormat(pixelFormat); // return if no dxgi compatible format was found if (convertToPixelFormat == GUID_WICPixelFormatDontCare) return 0; // set the dxgi format dxgiFormat = GetDXGIFormatFromWICFormat(convertToPixelFormat); // create the format converter hr = wicFactory->CreateFormatConverter(&wicConverter); if (FAILED(hr)) return 0; // make sure we can convert to the dxgi compatible format BOOL canConvert = FALSE; hr = wicConverter->CanConvert(pixelFormat, convertToPixelFormat, &canConvert); if (FAILED(hr) || !canConvert) return 0; // do the conversion (wicConverter will contain the converted image) hr = wicConverter->Initialize(wicFrame, convertToPixelFormat, WICBitmapDitherTypeErrorDiffusion, 0, 0, WICBitmapPaletteTypeCustom); if (FAILED(hr)) return 0; // this is so we know to get the image data from the wicConverter (otherwise we will get from wicFrame) imageConverted = true; } int bitsPerPixel = GetDXGIFormatBitsPerPixel(dxgiFormat); // number of bits per pixel bytesPerRow = (textureWidth * bitsPerPixel) / 8; // number of bytes in each row of the image data int imageSize = bytesPerRow * textureHeight; // total image size in bytes // allocate enough memory for the raw image data, and set imageData to point to that memory *imageData = (BYTE*)malloc(imageSize); // copy (decoded) raw image data into the newly allocated memory (imageData) if (imageConverted) { // if image format needed to be converted, the wic converter will contain the converted image hr = wicConverter->CopyPixels(0, bytesPerRow, imageSize, *imageData); if (FAILED(hr)) return 0; } else { // no need to convert, just copy data from the wic frame hr = wicFrame->CopyPixels(0, bytesPerRow, imageSize, *imageData); if (FAILED(hr)) return 0; } // now describe the texture with the information we have obtained from the image resourceDescription = {}; resourceDescription.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; resourceDescription.Alignment = 0; // may be 0, 4KB, 64KB, or 4MB. 0 will let runtime decide between 64KB and 4MB (4MB for multi-sampled textures) resourceDescription.Width = textureWidth; // width of the texture resourceDescription.Height = textureHeight; // height of the texture resourceDescription.DepthOrArraySize = 1; // if 3d image, depth of 3d image. Otherwise an array of 1D or 2D textures (we only have one image, so we set 1) resourceDescription.MipLevels = 1; // Number of mipmaps. We are not generating mipmaps for this texture, so we have only one level resourceDescription.Format = dxgiFormat; // This is the dxgi format of the image (format of the pixels) resourceDescription.SampleDesc.Count = 1; // This is the number of samples per pixel, we just want 1 sample resourceDescription.SampleDesc.Quality = 0; // The quality level of the samples. Higher is better quality, but worse performance resourceDescription.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; // The arrangement of the pixels. Setting to unknown lets the driver choose the most efficient one resourceDescription.Flags = D3D12_RESOURCE_FLAG_NONE; // no flags // return the size of the image. remember to delete the image once your done with it (in this tutorial once its uploaded to the gpu) return imageSize; } Font LoadFont(LPCWSTR filename, int windowWidth, int windowHeight) { std::wifstream fs; fs.open(filename); Font font; std::wstring tmp; int startpos; // extract font name fs >> tmp >> tmp; // info face="Arial" startpos = tmp.find(L""") + 1; font.name = tmp.substr(startpos, tmp.size() - startpos - 1); // get font size fs >> tmp; // size=73 startpos = tmp.find(L"=") + 1; font.size = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // bold, italic, charset, unicode, stretchH, smooth, aa, padding, spacing fs >> tmp >> tmp >> tmp >> tmp >> tmp >> tmp >> tmp; // bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 // get padding fs >> tmp; // padding=5,5,5,5 startpos = tmp.find(L"=") + 1; tmp = tmp.substr(startpos, tmp.size() - startpos); // 5,5,5,5 // get up padding startpos = tmp.find(L",") + 1; font.toppadding = std::stoi(tmp.substr(0, startpos)) / (float)windowWidth; // get right padding tmp = tmp.substr(startpos, tmp.size() - startpos); startpos = tmp.find(L",") + 1; font.rightpadding = std::stoi(tmp.substr(0, startpos)) / (float)windowWidth; // get down padding tmp = tmp.substr(startpos, tmp.size() - startpos); startpos = tmp.find(L",") + 1; font.bottompadding = std::stoi(tmp.substr(0, startpos)) / (float)windowWidth; // get left padding tmp = tmp.substr(startpos, tmp.size() - startpos); font.leftpadding = std::stoi(tmp) / (float)windowWidth; fs >> tmp; // spacing=0,0 // get lineheight (how much to move down for each line), and normalize (between 0.0 and 1.0 based on size of font) fs >> tmp >> tmp; // common lineHeight=95 startpos = tmp.find(L"=") + 1; font.lineHeight = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowHeight; // get base height (height of all characters), and normalize (between 0.0 and 1.0 based on size of font) fs >> tmp; // base=68 startpos = tmp.find(L"=") + 1; font.baseHeight = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowHeight; // get texture width fs >> tmp; // scaleW=512 startpos = tmp.find(L"=") + 1; font.textureWidth = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get texture height fs >> tmp; // scaleH=512 startpos = tmp.find(L"=") + 1; font.textureHeight = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get pages, packed, page id fs >> tmp >> tmp; // pages=1 packed=0 fs >> tmp >> tmp; // page id=0 // get texture filename std::wstring wtmp; fs >> wtmp; // file="Arial.png" startpos = wtmp.find(L""") + 1; font.fontImage = wtmp.substr(startpos, wtmp.size() - startpos - 1); // get number of characters fs >> tmp >> tmp; // chars count=97 startpos = tmp.find(L"=") + 1; font.numCharacters = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // initialize the character list font.CharList = new FontChar[font.numCharacters]; for (int c = 0; c < font.numCharacters; ++c) { // get unicode id fs >> tmp >> tmp; // char id=0 startpos = tmp.find(L"=") + 1; font.CharList[c].id = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get x fs >> tmp; // x=392 startpos = tmp.find(L"=") + 1; font.CharList[c].u = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)font.textureWidth; // get y fs >> tmp; // y=340 startpos = tmp.find(L"=") + 1; font.CharList[c].v = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)font.textureHeight; // get width fs >> tmp; // width=47 startpos = tmp.find(L"=") + 1; tmp = tmp.substr(startpos, tmp.size() - startpos); font.CharList[c].width = (float)std::stoi(tmp) / (float)windowWidth; font.CharList[c].twidth = (float)std::stoi(tmp) / (float)font.textureWidth; // get height fs >> tmp; // height=57 startpos = tmp.find(L"=") + 1; tmp = tmp.substr(startpos, tmp.size() - startpos); font.CharList[c].height = (float)std::stoi(tmp) / (float)windowHeight; font.CharList[c].theight = (float)std::stoi(tmp) / (float)font.textureHeight; // get xoffset fs >> tmp; // xoffset=-6 startpos = tmp.find(L"=") + 1; font.CharList[c].xoffset = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowWidth; // get yoffset fs >> tmp; // yoffset=16 startpos = tmp.find(L"=") + 1; font.CharList[c].yoffset = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowHeight; // get xadvance fs >> tmp; // xadvance=65 startpos = tmp.find(L"=") + 1; font.CharList[c].xadvance = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)) / (float)windowWidth; // get page // get channel fs >> tmp >> tmp; // page=0 chnl=0 } // get number of kernings fs >> tmp >> tmp; // kernings count=96 startpos = tmp.find(L"=") + 1; font.numKernings = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // initialize the kernings list font.KerningsList = new FontKerning[font.numKernings]; for (int k = 0; k < font.numKernings; ++k) { // get first character fs >> tmp >> tmp; // kerning first=87 startpos = tmp.find(L"=") + 1; font.KerningsList[k].firstid = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get second character fs >> tmp; // second=45 startpos = tmp.find(L"=") + 1; font.KerningsList[k].secondid = std::stoi(tmp.substr(startpos, tmp.size() - startpos)); // get amount fs >> tmp; // amount=-1 startpos = tmp.find(L"=") + 1; int t = (float)std::stoi(tmp.substr(startpos, tmp.size() - startpos)); font.KerningsList[k].amount = (float)t / (float)windowWidth; } return font; } void RenderText(Font font, std::wstring text, XMFLOAT2 pos, XMFLOAT2 scale, XMFLOAT2 padding, XMFLOAT4 color) { // clear the depth buffer so we can draw over everything commandList->ClearDepthStencilView(dsDescriptorHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); // set the text pipeline state object commandList->SetPipelineState(textPSO); // this way we only need 4 vertices per quad rather than 6 if we were to use a triangle list topology commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); // set the text vertex buffer commandList->IASetVertexBuffers(0, 1, &textVertexBufferView[frameIndex]); // bind the text srv. We will assume the correct descriptor heap and table are currently bound and set commandList->SetGraphicsRootDescriptorTable(1, font.srvHandle); int numCharacters = 0; float topLeftScreenX = (pos.x * 2.0f) - 1.0f; float topLeftScreenY = ((1.0f - pos.y) * 2.0f) - 1.0f; float x = topLeftScreenX; float y = topLeftScreenY; float horrizontalPadding = (font.leftpadding + font.rightpadding) * padding.x; float verticalPadding = (font.toppadding + font.bottompadding) * padding.y; // cast the gpu virtual address to a textvertex, so we can directly store our vertices there TextVertex* vert = (TextVertex*)textVBGPUAddress[frameIndex]; wchar_t lastChar = -1; // no last character to start with for (int i = 0; i < text.size(); ++i) { wchar_t c = text[i]; FontChar* fc = font.GetChar(c); // character not in font char set if (fc == nullptr) continue; // end of string if (c == L'') break; // new line if (c == L'n') { x = topLeftScreenX; y -= (font.lineHeight + verticalPadding) * scale.y; continue; } // don't overflow the buffer. In your app if this is true, you can implement a resize of your text vertex buffer if (numCharacters >= maxNumTextCharacters) break; float kerning = 0.0f; if (i > 0) kerning = font.GetKerning(lastChar, c); vert[numCharacters] = TextVertex(color.x, color.y, color.z, color.w, fc->u, fc->v, fc->twidth, fc->theight, x + ((fc->xoffset + kerning) * scale.x), y - (fc->yoffset * scale.y), fc->width * scale.x, fc->height * scale.y); numCharacters++; // remove horrizontal padding and advance to next char position x += (fc->xadvance - horrizontalPadding) * scale.x; lastChar = c; } // we are going to have 4 vertices per character (trianglestrip to make quad), and each instance is one character commandList->DrawInstanced(4, numCharacters, 0, 0); }
Comments
This is the best directx12 tutorial series I have seen. I have learned a lot from it. Thank you very much!
on Oct 29 `19
57308618@qq.com
Thanks a lot for making the effort n taking out the time to write such descriptive n easy to follow tutorials! I've benefitted immensely from this DirectX 12 tutorial series. The best part is that you didn't hide the most important details inside C++ classes by trying to be object-oriented. This enabled me to concentrate only on the important detais n understand the flow at the same time. Thanks again! All the best!
on Jul 29 `22
cat7skill@gmail.com