在本教程中,我们将学习如何在DirectX 12中绘制一些位图文本。我们还将学习如何使用高精度计时器来获取每秒的帧数,以及使游戏逻辑的时序保持一致,而不是像前面的教程中那样基于计算机的运行速度。
介绍在本教程中,我们将学习如何使用位图字体在DirectX 12中绘制文本。 为了显示文本,我们将使用QueryPerformanceTimer API每秒显示帧数。 我们将学习如何使用名为Hiero的工具创建位图字体,该工具还会输出一个fnt文件,我们可以阅读该文件来了解如何渲染字体中的每个字符,例如放置偏移量和纹理坐标。 您可以使用很多工具来创建位图字体,甚至可以自己创建位图字体,但是我选择Hiero的原因是它确实满足了我们的需要,并且还提供了距离场效果的选项,我们将在后面进行讨论。 关于本教程的内容,您可以使用它来使文本几乎以任何比例平滑呈现。
Hiero
让我们从创建位图字体开始。Hiero字体生成器是Java中的工具构建,它包含在libgxd nightly 构建中。 LibGDX是使用Java构建的跨平台游戏开发框架。 我们将只需要Hiero字体生成器。
首先下载最新的LibGDX nightly 构建版本,然后从此处解压缩到新文件夹中。 压缩包文件的名称应类似于libgdx-nightly-latest.zip。
解压缩文件后,进入刚刚将压缩包解压缩到的新文件夹。 创建一个新的文本文档,将其命名为hiero.bat。 这将是我们运行Hiero的批处理文件。 在文本编辑器中打开bat文件,并将以下内容粘贴到其中: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
您必须确保已安装Java运行时环境(jre)才能运行Hiero。 如果仍然需要安装Java运行时,则可以在此处下载安装程序。
生成位图字体
在安装了jre并创建了上面的bat文件之后,您应该能够通过双击执行bat文件。 如果一切顺利,Hiero将启动,并且将向您显示以下窗口:现在,我们已经启动并运行了Hiero,让我们生成一个位图字体。结果将是2个文件,一个文件包含有关字体和字符的信息,另一个文件包含字体图像。
首先,在右上角的组合框中选择所需的字体。在本教程中,我们将使用Arial。在组合框下方,为“渲染”选择“ Java”单选按钮。这将启用右上方的“效果”组合框。对于本教程,我们将不使用任何效果,但是您可能希望在渲染文本后实现距离字段。
接下来,选择底部字体预览框上方的“字形缓存”单选按钮。默认情况下,选择“示例文本”。这将为我们提供更多输出图像的选项。在选择了“字形缓存”的字体预览框的右侧,您将看到“页面宽度”和“页面高度”。我们将制作一个512x512的字体图像,因此将它们都设置为512。
没有备份到右上角,而是在将渲染设置为“ Java”的上方,您会看到一个尺寸输入。您要做的是将大小增加到“ Pages”为2之前(您将在上方看到“ Pages”,在其中设置输出图像的宽度和高度)。这将使字体的大小增加到尽可能适合一页的大小(所有字符都适合512x512图像)。使用Arial字体,在将某些字符移到下一页之前,我能够将字体大小增加到73,在该页面上您将拥有两个512x512字体图像,而不只是一个。
右下角是4个用于填充的输入框,分别用于左,上,右和下。我们希望在最终输出图像中为每个字符添加一些填充,以便在对字符的纹理进行采样时,在着色器中对周围的字符进行采样的机会较小。当我们在绘制字符的屏幕上的四边形太小时,从纹理采样的准确性将降低,并可能最终从下一个字符获取值。让我们将所有这些设置为5像素进行填充。
设置应该就是这样。这是一个屏幕,显示如果您继续执行以下操作将会看到的内容:现在让我们获取位图字体文件。
Angel Code字体格式
单击顶部菜单中的“文件”,然后单击“保存BMFont文件(文本)...”。 选择输出文件的目录和名称。 我将其命名为Arial.fnt。 单击“保存”时,它将保存一个.fnt文件,其中包含有关渲染字体的信息,而.png文件中则包含字体图像。
生成的位图如下所示:上面Hiero吐出的输出.fnt文件包含有关以Angel Code字体格式读取和呈现该字体的信息。 以下是从上述配置的Hiero输出的.fnt文件:
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
格式分为三部分: 最后将说明适用于整个字体的信息,适用于单个字符的信息以及适用于字距调整的信息。
字体信息行开始于:
- info-这是有关整个字体的信息
- common -这是每个字符之间共同的信息
- page -这是有关字体图像文件的信息。每个字体图像只有一个“页面”行。
信息专线
- face -字体名称
- size-字体大小(以像素为单位)。我们不会使用它,但是您可能会使用几种不同大小的位图字体。当我们以100%的比例绘制字体时,一个50像素宽的字符将占用屏幕上的50像素。
- bold -如果不是粗体则为0,如果为粗体则为1
- italic -如果不是斜体则为0,如果是斜体则为1
- charset -未使用
- unicode-不使用
- StretchH-字体的高度拉伸,100%
- smooth-不使用
- aa-未使用
- padding -顶部,右侧,底部,左侧填充(以像素为单位)
- spacing -不使用
公用线
- lineHeight-向下移动到下一行的距离(以像素为单位)
- base-每个字符的实际高度(以像素为单位)。我们实际上不会使用它,因为每个字符都有自己的高度。
- scaleW-纹理的宽度(字体图像)
- scaleH-纹理的高度
- pages-字体图像文件的数量
- packed -不使用
页线(用于每个字体图像)
- id-纹理的ID
- file-字体图像(纹理)的文件名
角色信息
每行字符以“ char”开头,并包含字体中一个字符的信息。
紧缩信息
- id-字符的ID。我们应该能够直接将“ char”转换为“ int”并找到ID与该int匹配的字符
- x-“ U”纹理坐标
- y-“ V”纹理坐标
- width-纹理上字符的宽度(以像素为单位)
- height-纹理上角色的高度
- xoffset-渲染时从当前x位置偏移多少个像素(以像素为单位)(而不是从纹理读取字符时)
- yoffset-渲染时字符从行顶部偏移多少(而不是从纹理读取时)
- xadvance-渲染时将当前x位置前进到下一个字符的像素数
- page -包含字符的页面ID(字体图像文件或纹理)
- chnl -您可以在任何rgba通道中编码字符。我们仅使用一个通道,因此不要使用它,但是您可以在r通道上编码常规字体,在g通道上编码粗体,在b通道上编码斜体,在a通道上编码粗体/斜体。
有些字符在某些其他字符旁边时需要水平偏移,例如“ W”和“ o”。 “ o”应位于“ W”的右侧Wo下方,这意味着,在与W相邻时,o必须向左偏移一点,否则W与o之间将存在不自然的空间。
每个字距调整行都以“字距调整”开头。
- first-前一个字符ID,或前一个(左)字符
- second -第二个字符ID,或当前(右)字符
- amount -以像素为单位的数量,以偏移当前值或x轴上的第二个字符
这是字体的重要因素的外观。 顶部的“ Wo”是角色在纹理中的放置方式。 底部的“ Wo”是应如何渲染字符。
绘制字体我们将利用实例化来绘制字体。这样,我们的顶点缓冲区可以为每个要绘制的字符提供一个顶点。顶点将包含x和y屏幕坐标,屏幕坐标的宽度和高度,uv纹理坐标以及u宽度和v宽度纹理坐标。
新结构
这样做的方式是在绘制时,我们告诉GPU绘制4个顶点,因为每个字符都需要渲染为一个四边形。我们告诉它绘制numCharacter实例,以便为每个字符运行4次顶点着色器。我们将使用系统值来获取顶点ID SV_VertexID。该ID将为0-3,具体取决于ID,我们将知道我们是四边形中的左上,右上,右下还是左下顶点。如果听起来令人困惑,请继续阅读直到获得代码为止,希望看到该代码可以为您清除代码。
还有一点要提到的是,字体文件中的某些值以像素为单位,必须将其转换为坐标空间(0到1),(-1到1)或(1到-1),但是我们将当我们开始加载文件时,对其进行详细说明。
现在,我们已经创建了位图字体,并且知道如何阅读它,接下来我们可以继续进行代码。
我想警告您,本教程的代码比平时更“草率”,但是您应该能够了解如何呈现字体,然后在自己的应用程序中创建字体模块。在本教程中,我们有几个新结构。 第一个是新的顶点结构。
TextVertex结构
此顶点结构具有3个float4,一个用于位置,一个用于纹理坐标,一个用于颜色。该顶点结构实际上仅包含实例数据。如上所述,一个顶点是一个字符,因此我们需要在纹理中字符四边形的左上角的x,y位置,正方形的宽度和高度,纹理中字符左上角的u,v,以及角色在纹理中的宽度和高度,以及角色的颜色。如果您不希望任何字符都具有不同的颜色,则可以将颜色放入常量缓冲区中,但是我并不是为了简单起见,在本教程中,我们不需要为文本提供常量缓冲区。
该位置在屏幕空间中,这意味着它们必须在x轴上转换为-1到1,在y轴上转换为1到-1,并包含左上角的x,y以及四边形的宽度和高度渲染为(x,y,width,height)。
纹理坐标位于纹理空间中,也必须进行转换,这意味着在u轴和v轴上都为0-1。然后,texCoord看起来像(u,v,uwidth,uheight),其中uwidth和uheight是纹理上字符的宽度和高度。
最后,我们有颜色,它是RGBA的形式。FontChar结构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结构的作用。 我们将拥有一个由这些对象组成的数组,每个对象对应一个字体中的字符。
我已经注释了代码,因此在这里我不做全部解释,但是字符的id与将char转换为int的char是相同的值,您应该能够直接将char转换为int以获取id 一个字符。FontKerning结构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结构包含有关字距调整的信息。 我已经在上面谈论过字距调整,所以在此不再赘述。
字型结构struct FontKerning { int firstid; // the first character int secondid; // the second character float amount; // the amount to add/subtract to second characters x };
最后,我们有了字体结构。该结构包含几个方法,一个字符和字距调整数组以及其他描述字体的成员。我们还存储了指向字体纹理资源(SRV)的指针和SRV的句柄,因此我们可以在需要时轻松设置字体的绑定SRV。
GetKerning()方法接受左字符,右字符(其中右字符是您正在使用的当前字符),并循环遍历kernings数组。如果找到匹配项,它将返回字距调整量或您应水平偏移字符的数量,否则返回0,这意味着不需要偏移正确的(当前)字符。
GetChar()是另一个简单的方法,它仅循环遍历所有字符并尝试查找匹配的字符。如果未找到任何字符,则它仅返回一个空指针,我们应该跳过该字符或使用替换字符。替换字符是更好的方法,但是在本教程中,我们只是跳过该字符。计时器结构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; } };
在本教程中,我们将实现一个计时器。我们将使用此计时器做两件事。
我们使用计时器的第一件事是每秒帧数或FPS计数,因此我们需要渲染一些有用的文本。第二件事是正确地对游戏逻辑进行更新。以前,多维数据集会尽可能快地旋转。如果计算机速度较快,它们的旋转速度将比计算机速度较慢时的旋转速度快。现在,我们正在实现一个计时器,我们可以确保多维数据集以完全相同的速度旋转,而不管您的计算机快慢如何。如果FPS较低,则每帧的多维数据集将比FPS较高的多维数据集移动得多。为此,我们将最后一帧时间作为增量传递给更新函数。然后,我们可以乘以度数,以使三角形旋转方差。因此,如果您的最后一帧花费了0.5秒钟,那么多维数据集将仅旋转到那一帧的一半,而不是您将最后一帧旋转到1秒。
我们将使用QueryPerformanceCounter API来实现计时器。我们本来可以使用std :: chrono,它是独立于平台的,但是在Windows上,chrono还是使用QueryPerformanceCounter,所以我们只是在这里跳过中间人。我对chrono和QueryPerformanceCounter之间的当前时间戳进行了多快测试,发现QueryPerformanceCounter相当快(尽管我们说的是微秒,所以可以忽略不计)。
在构造Timer类时,它获取当前时间戳,我们可以使用所需的任何度量,我选择毫秒是因为这是最常见的,但是您可以通过操作timerFrequency来更改它。
GetFrameDelta()是执行fps并返回帧花费时间的方法,因此我们只希望每帧调用一次此方法。新局部变量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; } };
我们有几个新的全局变量,其中包括两个新的函数原型。
第一个新的全局变量是用于呈现文本的新的管道状态对象。字体顶点和像素着色器以及混合状态与我们为渲染多维数据集而创建的其他PSO不同,因此我们需要为文本使用单独的PSO。
我们有一个Font对象,用于将要加载的arial字体。
maxNumTextCharacters是这样的,因此我们可以将顶点缓冲区资源的大小调整到足以容纳所有文本的大小。我选择了1024,如果您超过该数量,则可以删除当前的顶点缓冲区资源,并创建一个新的具有足够空间容纳所需文本的资源,或者也可以将其设置得很高。
实际上,我们将需要3个顶点缓冲区资源,每个帧一个,因此,当我们修改顶点缓冲区时,我们不会覆盖着色器可能正在使用的最后一帧顶点缓冲区。由于我们经常修改顶点缓冲区(我们每帧设置一次),因此我们可以使用上载堆。
除了3个顶点缓冲区资源外,我们还需要3个顶点缓冲区视图和3个顶点缓冲区GPU虚拟地址,以便我们可以写入顶点缓冲区。
我们将创建一个Timer对象,以便获得完成最后一帧的时间,我们可以根据时间更新游戏逻辑并获取FPS。
最后,我们有了新的函数原型。我们有一个函数加载位图字体,另一个函数使用提供的字体绘制文本。更新了Update()函数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));
现在,我们将向此函数传递一个增量参数,以便我们可以根据时间更新游戏逻辑。
更新了mainloop()函数void Update(double delta); // update the game logic
在主循环中,我们有一条额外的线和一条修改的线。 多余的行获取了最后一帧完成的时间。 我们将此增量变量传递给更新函数。
文字PSOvoid 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) } } }
InitD3D()函数中的第一个新功能是创建文本PSO。 这涉及到编译文本顶点和像素着色器,为文本PSO创建混合状态,最后为实际PSO创建混合状态。
除了混合状态外,我们在以前的教程中已经完成了所有这些操作,因此在这里我将解释混合状态。
D3D12_BLEND_DESC结构如下:typedef struct D3D12_BLEND_DESC { BOOL AlphaToCoverageEnable; BOOL IndependentBlendEnable; D3D12_RENDER_TARGET_BLEND_DESC RenderTarget[8]; } D3D12_BLEND_DESC;
- AlphaToCoverageEnable-我们将其设置为false。 这是一项额外的操作,可以以更高的分辨率对纹理进行采样,以找出屏幕上实际覆盖的像素数量,并能够将像素的值与后面的像素更正确地融合在一起。 它对树叶和草很有用,例如,草叶的边缘由alpha值定义。 它可以为您带来更好的结果,但会影响性能。 您可以在这里,这里和这里阅读更多有关它的信息。
- IndependentBlendEnable-确定是否应为每个渲染目标独立完成混合。 我们只有一个渲染目标,因此我们将其设置为FALSE。 如果将其设置为FALSE,则仅将rendertarget [0]成员用于混合,而忽略所有其他渲染目标成员。
- RenderTarget [8]-D3D12_RENDER_TARGET_BLEND_DESC的数组,每个渲染目标范围一个。 我们只有一个。
D3D12_RENDER_TARGET_BLEND_DESC结构是渲染目标的实际混合状态参数所在的位置(D3D12_BLEND_DESC中的其他两个混合参数仅表示是否执行操作)。
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-是否应该执行混合操作
- LogicOpEnable-是否对传入的RGBA和当前在渲染目标上的RGBA值执行逻辑运算。我们不需要这个。
- SrcBlend-D3D12_BLEND枚举。 RGB通道的源组件的值
- DestBlend-D3D12_BLEND枚举。 RGB通道的目标组件的值
- BlendOp-D3D12_BLEND_OP枚举。在RGB通道的源组件和目标组件上执行的操作
- SrcBlendAlpha-D3D12_BLEND枚举。 Alpha通道的源分量的值
- DestBlendAlpha-D3D12_BLEND枚举。 Alpha通道的目标组件的值
- BlendOpAlpha-D3D12_BLEND_OP枚举。在Alpha通道的源组件和目标组件上执行的操作
- LogicOp-D3D12_LOGIC_OP枚举。启用逻辑操作时要执行的操作
- RenderTargetWriteMask-D3D12_COLOR_WRITE_ENABLE枚举值ORed(|)的组合在一起,以指定写掩码。这指定了像素的哪些分量可写入。我们指定D3D12_COLOR_WRITE_ENABLE_ALL是因为我们要写入所有通道。
混合时DirectX执行两种算法。 第一个用于颜色通道RGB,第二个用于alpha通道A。
这是DirectX从混合操作中获取最终颜色值(RGB)的公式:
源是sourceRGB * blendFactor,目标是destinationRGB * blendFactor。(source * SrcBlend) BlendOp (destination * DestBlend)
这是DirectX从混合操作获取最终alpha值(A)所使用的论坛:
与RGB一样,源alpha通道的混合因子为sourceA * blendFactor,目标alpha为destinationA * blendFactor。(source * SrcBlendAlpha) BlendOpAlpha (destination * DestBlendAlpha)
可以使用命令OMSetBlendFactor()来设置混合因子。 如果未指定,DirectX将使用[1,1,1,1]。
对于我们的文本,我们希望目标上的颜色与渲染目标上已经存在的颜色混合。 当文本上的alpha为0时,我们要完全显示渲染目标上已经存在的颜色,当它为1时,我们要完全显示文本颜色,并且介于两者之间的任何内容都将混合在文本颜色和已经存在的颜色之间 在渲染目标上,因此我们的最终混合公式如下所示: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))
现在让我们看一下文本输入元素的描述。 我们有三个输入元素,一个用于位置和位置宽度/高度,一个用于纹理坐标和纹理坐标宽度/高度,一个用于颜色。
您将注意到我们如何将InputSlotClass设置为D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA。 这意味着该数据是基于实例的,而不是每个顶点的。 因此,前四个顶点将使用相同的输入数据,后四个顶点将使用下一个实例数据,依此类推。 最后一个参数InstanceDataStepRate指定在移至下一个实例数据(顶点缓冲区中的下一个顶点)之前必须绘制多少个实例。 由于每个实例在顶点缓冲区中都有一个顶点,因此我们希望将其设置为1,以便绘制的每个实例(字符)都将在顶点缓冲区中获得下一个顶点。 如果将其设置为0,则所有字符将在顶点缓冲区中使用相同的顶点。加载位图字体// 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; }
InitD3D()函数中的下一个新功能是加载arial字体。 加载.fnt文件后,我们将加载.fnt文件的字体纹理:
稍后我们将讨论LoadFont()函数。
在上一教程中,我们已经介绍了从文件中加载纹理,因此在此不再赘述。为每帧创建文本顶点缓冲区资源// 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);
在这里,我们仅为每个帧创建3个承诺的资源。 这些已提交的资源将保存我们的文本顶点数据。 由于我们将经常修改文本,因此我们只使用一个上传堆。
获取测试顶点缓冲区视图// 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])); }
在这里,我们仅获得每帧的顶点缓冲区视图,以便我们可以在需要时将正确的顶点缓冲区绑定到管道。
更新了Update()函数// 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); }
我们更新了游戏逻辑以使用delta变量。 基本上,增量是前一帧完成所花费的秒数。 我们可以将旋转量乘以这个delta变量,这样,如果最后一帧花了很长时间才能完成,则多维数据集将进一步旋转该帧;如果最后一帧完成得非常快,则多维数据集将仅对该帧旋转一点 。
更新了UpdatePipeline()函数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);
在这里,我们将调用该函数来绘制文本。
我们提供了要使用的字体,希望绘制的字符串,文本的位置以及文本的比例。 (1.0,1.0)的比例为100%,我们将其绘制为(2.0,2.0),因此它的绘制比例为200%,即两倍。 我们对该函数还有其他参数,只是使用我们在函数原型中定义的默认值。LoadFont()函数// draw the text RenderText(arialFont, std::wstring(L"FPS: ") + std::to_wstring(timer.fps), XMFLOAT2(0.02f, 0.01f), XMFLOAT2(2.0f, 2.0f));
让我们快速看一下LoadFont()函数,然后再看一下RenderText()函数。
此功能有4个部分。第一部分仅加载.fnt文件,而其他三部分则对其进行解析。
第二部分获取常规字体信息,例如名称,大小和行高。
此功能需要以像素为单位输入窗口的宽度和高度,以便正确地将字体信息从像素转换为屏幕空间。
看一下填充渲染文字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; }
在这里,我们实际上是在函数RenderText()中创建顶点缓冲区和用于渲染文本的命令。
此功能的第一部分设置管道以准备绘制文本。
我们需要绘制渲染目标上已经存在的所有内容,因此我们清除了深度缓冲区。
接下来要做的是设置文本PSO。
我们将三角形拓扑设置为trianglestrip。我们这样做是为了我们可以仅使用4个顶点轻松绘制一个四边形。前三个顶点将绘制第一个三角形,而最后一个顶点以及前一个三角形的前两个顶点将绘制第二个三角形。
我们需要为所在的帧设置正确的顶点缓冲区资源,因此在绘制文本时,我们正在从正确的顶点缓冲区进行读取。
管道设置的最后一部分是我们设置根描述符表。这只是指向字体纹理的SRV的指针。
接下来,我们设置几个变量。前两个是屏幕空间中文本顶部和左侧的位置。我们希望此函数起作用,以便当我们在该位置传递0,0时,它将在左上角开始,而1,1将在右下角。我们需要将该输入转换为屏幕空间,即从左到右在x轴上为-1到1,从上到下在y轴上为1到-1。
之后,我们设置初始的x和y位置。每个字符将增加x xadvance数量,每个换行符(n)将增加y lineheight数量,并将x重置回topLeftScreenX。
然后我们获得垂直和水平填充,以便在设置字符位置时可以从x和y中减去该量,否则我们的字符将间隔太大。
之后,将顶点数组设置为顶点缓冲区资源的开始,以便我们直接修改顶点缓冲区资源。对于每个字符,我们将vert增加TextVertex的大小,因此我们继续进行顶点缓冲区中的下一个顶点。我们不必担心清除顶点缓冲区,因为我们将只绘制与写入顶点缓冲区时一样多的文本字符。
我们需要跟踪最后一个字符id,以便在需要时可以找到字距调整量。
接下来,我们遍历字符串中的字符,找到字体中的字符,找到字体中的字距调整量(如果我们没有找到字体中的字符,我们可以跳过它,但这是一个更好的主意,使用我们在此arial字体中确实拥有的默认字符(例如块),我将由您自行决定)。
设置顶点x和y位置时,我们需要考虑字距调整和偏移量。设置字符的宽度和高度时,我们还需要考虑比例。
接下来,我们增加绘制的字符数,增加x,这是通过向当前字符添加xadvance,减去水平填充并乘以比例来完成的,最后设置lastChar的值。
一旦我们完成了顶点缓冲区的更新,就绘制文本。我们将其称为DrawInstanced,其中第一个参数是我们要绘制的每个实例的顶点数,我们绘制了一个四边形,因此我们说了4个顶点,即要绘制的实例数,即字符数,然后保留最后两个参数为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); }
我们的顶点着色器接受两个参数,一个是VS_INPUT结构,另一个是顶点id。顶点ID是我们可以使用SV_VertexID语义获得的系统值。
现在,着色器中的第二行代码是一个很酷的技巧。我们正在使用按位运算符来获取实际使用的顶点。我在行上方添加了一条注释,以便您可以直观地看到每个顶点的uv值。基本上,我们要知道的顶点是四边形的左上,右上,右下还是左下顶点。例如,第一个顶点(ID为0)将位于左上角。二进制中的整数0用0000表示。uv的x值为vertexID&1,这意味着我们将0000与0001进行比较。结果将是0000,或者只是0,因为一个是0,另一个是1。对于id 0,第二个参数也将为0。我们正在做(vertexID >> 1)&1,这意味着我们首先将顶点id右移1位,然后再次将其与1进行比较。因此向右移的0000仍然是0000,与1进行AND运算的结果也是0000,即0。
让我们跳到ID为2的第三个顶点。整数2以0010二进制表示。 uv的x值是将0010与0001进行“与”运算的结果,即0000或0。y值是您可以看到此处实际发生情况的地方。我们首先将vertexID移到右边一位,所以它从0010到0001,您可以看到1移到了一个位置。现在我们将结果与0001进行AND,得到0001和0001,结果现在为0001或1。
现在我们有了uv float2变量,我们知道了我们位于哪个顶点。左上角的顶点不会在其位置增加宽度和高度,因此我们将宽度和高度乘以uv.x和uv.y,这将使我们得到0的宽度和0的高度,并将其添加到顶点位置。
右下顶点需要将宽度和高度添加到字符x和y位置,因此uv(右下顶点为1,1)会将宽度和高度乘以uv.x(1)和uv.y( 1),然后将其添加到字符位置,这将为我们提供右下角顶点的位置。
我们对纹理坐标进行相同的操作,实例(字符)的4个顶点的结果是一个具有正确宽度,高度,位置和纹理坐标的四边形。整个字符在这里是纯色,但是您当然可以从像素着色器中的另一个纹理进行采样以使字体纹理化,而我会留给您进行练习的另一件事。我们从顶点着色器返回这些值,以输入到像素着色器。文字像素着色器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; }
对于文本的纹理和采样器,我们在顶部有texture2d和samplerstate。我们将当前像素片段的位置,颜色和纹理坐标作为文本像素着色器的输入。
我们在这里所做的就是将像素片段的颜色设置为字符的颜色,并将alpha设置为字符颜色的alpha乘以字体图像中采样的texel的alpha。基本上,我们只需要字体的alpha通道,除非您对字体进行了着色或纹理化,否则您肯定也可以从字体图像中获取字体的颜色。如果像素片段在实际字符上,则alpha将为1或接近1;如果像素片段不在实际字符上,则alpha将为0或接近0(我说接近是因为当您以不同的比例进行采样,有时您会获得图像上纹理像素周围区域的平均值)。如果采样的Alpha为0,那么我们将看到文本后面的内容,如果为1,那么我们将看到文本的颜色。Texture2D t1:寄存器(t0);
采样器状态s1:寄存器(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); }
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); }
参考链接:
- https://docs.microsoft.com/en-us/windows/win32/direct3d12/directx-12-programming-guide
- http://www.d3dcoder.net/
- https://www.braynzarsoft.net/viewtutorial/q16390-04-directx-12-braynzar-soft-tutorials
- https://developer.nvidia.com/dx12-dos-and-donts
- https://www.3dgep.com/learning-directx-12-1/
- https://gpuopen.com/learn/lets-learn-directx12/
- https://alain.xyz/blog/raw-directx12
- https://www.rastertek.com/tutdx12.html
- https://digitalerr0r.net/2015/08/19/quickstart-directx-12-programming/
- https://walbourn.github.io/getting-started-with-direct3d-12/
- https://docs.aws.amazon.com/lumberyard/latest/userguide/graphics-rendering-directx.html
- http://diligentgraphics.com/diligent-engine/samples/
- https://www.programmersought.com/article/2904113865/
- https://www.tutorialspoint.com/directx/directx_first_hlsl.htm
- http://rbwhitaker.wikidot.com/hlsl-tutorials
- https://digitalerr0r.net/2015/08/19/quickstart-directx-12-programming/
- https://www.ronja-tutorials.com/post/002-hlsl/
在DirectX 12中绘制文字
原创
©著作权归作者所有:来自51CTO博客作者嘿克不黑的原创作品,请联系作者获取转载授权,否则将追究法律责任
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
VUE中实现文字转拼音
在vue2中,实现将文字转为拼音的使用案例
ruby标签 文字转拼音 拼音 vue转拼音 -
TortoiseSVN复制
SVN是subversion的缩写,是一个开放源代码的版本控制系统,通过采用分支管理系统的高效管理,简而言之就是用于多个人共同开发同一个项目,实现共享资源,实现最终集中式的管理此处要备份的库名以test为例目的当svn服务器发生灾难级故障时,我们可以利用备份文件对公司资产:svn代码进行恢复面临的现状整个svn备份文件非常大,如果每次进行都全量备份;将消耗非常多的硬盘资源。解决方案使用全量备份+增
TortoiseSVN复制 .svn文件夹 svn查看版本号 svn还原到上一个版本 从svn导出来的项目包名不一致