纹理
作者:互联网
本教程将教您如何从文件创建纹理。 我们将学习如何使用Windows Imaging Component(WIC)API从文件中加载图像。 加载完图像后,我们将使用上传堆将其上传到默认资源堆,创建一个SRV,然后在像素着色器中使用该SRV中的样本为立方体着色。
介绍在本教程中,我们将学习如何使用从文件加载的图像对立方体进行纹理处理。
Windows Imaging Component (WIC)(Windows Imaging Component)
我们需要执行3个步骤来获得SRV,我们可以使用它来对立方体进行纹理处理
1.从图像文件加载数据,并将其解码为与DXGI格式(rgba)兼容的位图格式
2.创建一个上传(中间)堆,默认堆和资源以存储位图数据
3.创建用于描述并指向位图图像数据的着色器资源视图(SRV)
一旦有了SRV(位于描述符堆中),便将作为描述符表的根参数设置为SRV在描述符堆中所在的区域。 然后,我们的像素着色器可以从该纹理中采样以获取立方体中三角形的像素颜色。我们将首先介绍如何加载图像文件并将数据解码为DXGI兼容位图格式。
我们将利用Microsoft为我们提供的Windows Imaging Component API的优势,而在我们的项目中必须包含的所有文件都是wincodec.h。
WIC API是用于处理数字图像的低级API,其中包括诸如编码,解码,格式转换和元数据处理之类的内容。
WIC内置了许多默认编解码器。这些格式包括:
- BMP
- GIF
- ICO
- JPEG
- PNG
- TIFF
- DDS
该API的设计使其无论使用哪种编解码器或格式,所有内容都使用同一组API调用。它允许开发人员创建自己的编解码器,并且任何机器都可以使用与内置编解码器(例如PNG)相同的API调用集来解码格式。所需要做的就是将编解码器安装在客户端计算机上。
无论如何,我们只关心将图像解码为与DXGI格式兼容的位图格式。如果您愿意,可以使用WIC编写自己的编解码器,而只需要随应用程序一起分发编解码器,以便客户端计算机可以解码使用自定义编解码器编码的图像。
这是我们必须采取的流程,以将图像转换为可以使用的格式:
0.创建一个WIC工厂
1.创建一个WIC位图解码器(我们可以通过提供一个文件名来做到这一点)
2.从解码器获取“帧”,该接口是包含来自文件的解码图像数据的接口
3.获取图像信息,例如WIC像素格式和图像大小(宽度,高度)
4.获取兼容的DXGI格式(与WIC像素格式兼容的DXGI格式)
5.如果找不到兼容的DXGI格式,则必须将图像转换为与DXGI格式兼容的WIC像素格式。
6.最后,将像素从WIC帧复制到BYTE阵列。这是我们的位图数据,将用于构造几何图形。创建一个WIC工厂
要初始化WIC,我们必须首先使用名为CoInitialize()的函数在当前线程上初始化COM库,以便我们可以调用CoCreateInstance()函数来创建WIC工厂。
CoCreateInstance()函数将为我们提供该函数的类创建一个未初始化的对象,在本例中,我们需要一个WIC工厂,因此我们为其提供CLSID_WICImagingFactory。
我们可以使用WIC Factory创建WIC位图解码器的实例,该实例用于从文件中加载图像并从中获取“帧”。创建一个WIC位图解码器
WIC位图解码器(IWICBitmapDecoder)是一个接口,我们从文件中创建图像格式的实例。 该界面将允许您读取元数据以及从图像文件中获取“框架”。
从解码器中获取“帧”
WIC帧(IWICBitmapFrameDecode)接口包含来自图像的单个帧的信息。 诸如GIF的格式通常具有多个“帧”,而其他图像格式(如PNG和JPEG)仅具有一个。 我们可以使用它的GetFrame()方法从WIC位图解码器中获取第一帧。 第一个参数是帧索引(第一帧为0),第二个参数是将存储该帧的WIC帧。
获取图像信息
现在我们有了图像的框架,我们需要获取像素格式和图像的大小。我们可以使用WIC Frame接口的GetPixelFormat()方法获取WIC像素格式,该方法是GUID(全局唯一标识符)WICPixelFormatGUID。
我们可以使用GetSize()方法获取图像的大小。第一个参数是用于存储宽度的UINT,第二个参数是用于存储高度的UINT。获取兼容的DXGI格式
我们需要获取图像的DXGI格式,以便为SRV创建纹理描述。您可以创建一个函数,该函数根据输入的WICPixelFormatGUID返回DXGI格式。如果找不到格式,则可以返回DXGI_FORMAT_UNKNOWN。
如果还不是DXGI兼容格式,则转换图像
当我第一次编写本教程的代码时,我跳过了这一步。然后,我尝试了一些其他从互联网上拍摄的图像,以确保不需要此部分,但是我发现其中一些不是与DXGI兼容的格式,因此我决定最好包括此部分。
如果从上一节中提到的函数获得的DXGI格式是DXGI_FORMAT_UNKNOWN,则表示图像格式不是与DXGI兼容的格式,这意味着我们现在需要使用WIC将图像转换为DXGI格式。兼容的。
我们可以使用WIC的CreateFormatConverter()方法从WIC工厂创建WIC格式转换器接口实例(IWICFormatConverter)。
一旦知道必须将图像转换为DXGI兼容格式,就需要找出应将其转换为WIC像素格式。我们有一个函数,该函数接收WICPixelFormatGUID(它是当前像素格式),并返回与DXGI格式兼容的WICPixelFormatGUID。我们可能没有兼容的格式可以将其转换为这种格式,在这种情况下,我们将无法加载该图像。
要转换图像,我们首先需要找出是否有可能,我们可以使用WIC Converter接口的CanConvert()方法来完成。
如果我们可以转换图像,则可以通过调用转换器的“ Initialize”方法来完成。完成此功能后,将在此界面中转换的图像数据。将像素从WIC帧复制到BYTE数组
一旦获得WIC帧并根据需要进行转换,就需要将像素数据复制到BYTE数组。
我们可以使用CopyPixels()方法复制像素数据。如果不必转换图像,则可以调用WIC Frame接口的CopyPixels()方法,该方法包含文件中的图像数据。如果必须转换图像,则需要调用WIC Converter接口的CopyPixels()方法,该方法现在包含转换后的图像数据。
我们还需要从图像中获取每个像素的位数,我们有一个采用DXGI格式并返回每个像素位数的整数的函数。我们将在确定图像大小以及图像每行的字节数时使用它。
在加载图像的功能结束时,我们将为纹理填写资源描述。我们将使用此结构来创建SRV和资源堆。
以下是我们在代码中提供的功能以供参考:
GetDXGIFormatFromWICFormat()函数:// 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; }
GetConvertToWICFormat()函数:
// 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; }
GetDXGIFormatBitsPerPixel()函数
// 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; }
LoadImageDataFromFile()函数:
创建资源// 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; }
好了,让我们谈谈如何将纹理数据获取到GPU。我想首先弄清楚一些定义/想法。
堆
堆只是物理内存的连续块。此内存可以是显存(GPU),系统内存(CPU RAM)或页面内存(内存填满后,数据将放置在硬盘上)。堆是在显存中还是在系统内存中取决于创建标志,但是,如果显存已满,则本应放置在显存中的堆将被放置在内存RAM中,如果内存也满了就只能放置在页面内存中。
实际地址
物理地址是硬件上特定位置的地址。要直接访问系统内存,我们将直接从物理地址读取到要读取的内存中的位置。将系统内存(RAM)视为一条街道,这是一条很长的街道。这样,每个内存块就是一所房子。每个房子都有一个地址。该物理地址对于所有硬件和内存都是唯一的。
虚拟地址
虚拟地址类似于物理地址,因为它们都是地址。但是虚拟地址不是映射到内存块的直接位置,而是映射到物理内存。映射虚拟地址后,当您访问该虚拟地址时,API会将其定向到该虚拟地址所映射的物理地址。
映射
上面基本解释了,映射在虚拟地址和物理地址之间创建了链接。
资源
资源的基本思想是它是一个虚拟地址范围。资源只是指向该数据的数据所在的堆的窗口或部分的指针。
驻留(Residency)
创建堆后,它以驻留状态开始。这意味着与堆关联的物理内存可访问GPU。该堆可以在显存中,也可以在系统内存中。如果有足够的空间,则默认堆将驻留在显存中;否则,如果没有足够的系统内存,则将移动到系统内存或磁盘上的页面内存。当然,您希望默认堆驻留在显存中,以便GPU可以更快地访问数据,这就是D3D12允许我们“Evict”或“ MakeResident”堆的原因。
驱逐(Evict)
设备接口的Evict()方法允许您将一个或多个堆设置为非驻留式。这只会设置一个标志,因此CPU / GPU开销可能不会立即增加。它的作用是当新堆成为驻留堆时(通过创建或“ MakeResident”方法)并且需要非驻留堆驻留的空间时,非驻留堆将移出内存的较慢部分(如果被驱逐的堆位于显存中,它将移至系统内存,如果已经在系统内存中,则将移至页面内存),新的常驻堆将使用被驱逐的堆所在的空间。驻留(MakeResident)
驱逐(Evict)的反面。
您必须使用围栅来确保GPU尝试访问的所有内存都已驻留。
有效内存
在创建任何堆之前,应查询应用程序的可用内存。 D3D12提供了一种使用适配器接口的QueryVideoMemoryInfo()方法执行此操作的方法。结果可能会非常频繁地更改。即使您每秒间隔两次调用此函数,也可能不会获得完全相同的结果。
结果将是DXGI_QUERY_VIDEO_MEMORY_INFO结构,该结构将为您提供进程可用的显存总量。
如果没有足够的空间或即将使用所有可用的显存,则可能会决定不加载和渲染微不足道的资源。
描述符
描述符是D3D12表示View的词,尽管在诸如Shader Resource View或Constant Buffer View之类的资源类型的名称中仍使用View。
您必须为资源创建一个描述符,才能在图形管道中使用该资源。
D3D12中有三种类型的资源:Committed Resource
提交的资源既是隐式(implicit)堆又是资源。 堆足够大以容纳资源,并且资源被映射到整个堆。 这可能是将数据获取到GPU的最常见和最简单的方法。 他们将其称为隐式堆,因为除了已提交资源中的资源之外,您将无法直接访问该堆。 要对堆执行任何操作(例如逐出或使驻留),您将在资源上执行此操作。
因为创建提交的资源会创建一个堆(分配内存)并创建一个资源并将该资源映射到该堆,所以创建和销毁它的速度要比其他两个资源慢。 您在关卡或游戏中经常使用的资源将是不错的候选者或可靠的资源,例如HUD,用户界面或主要角色。
我们可以使用设备接口的CreateCommittedResource()方法创建提交的资源。 此方法将创建资源和堆。Placed Resource
放置的资源只是映射到显式堆的一部分的资源,因此创建和销毁它们的速度更快。显式堆是您创建的堆,除了任何资源之外。您可以使用设备接口的CreateHeap()方法显式创建一个堆。这将创建一个堆,根据标志和可用内存在显存或系统内存中分配所需的数据量。如果您用完所有内存,则此方法将失败,并显示内存不足错误。
创建放置的资源时,必须指定堆以及从堆开头到堆的偏移量以及资源的大小。
您可以使用放置的资源来更好地管理您的内存。可以销毁不再需要的已放置资源,可以创建新的已放置资源,并重新使用已销毁的已放置资源正在使用的堆部分。
放置的资源在“非活动(inactive)”状态下创建。您只能在GPU上使用“活动的”放置资源,因此必须使用别名屏障(barriers)更改放置资源的状态。
放置的资源可以重叠,但是只有一个重叠的放置资源可以处于“活动”状态。激活放置的资源时,共享相同物理内存(重叠资源)的所有其他放置的资源将自动变为非活动状态。
放置的资源有助于进行更精细的内存管理,并且应用于经常更改的资源。
要创建放置的资源,请使用设备接口的CreatePlacedResource()方法。Reserved Resource
最后,我们介绍预留资源。预留资源类似于D3D11中的tiled resources。如果您不知道tiled resources是什么,或者为什么它们有用,我建议您查看该URL。
纹理空间
保留资源实际上只是虚拟地址范围。您可以根据需要映射和取消映射该资源以进行堆操作,而且速度非常快。保留的资源尚不能在所有D3D12硬件上使用(仅当适配器支持切片的资源层1或更高版本时才可以创建保留的资源)。
流数据或非常大的地图地形是预留资源的良好候选者。您可以想象您的相机正在世界范围内移动。您无需一次使用整个世界的地形,因此您创建了一个预留资源,该资源将重新映射到摄像机周围地形窗口上的每一帧。资源保持相同大小,并且仅根据相机所在的位置在堆周围滑动。
预留资源在创建时不会像放置的资源和提交的资源一样映射到物理内存,因此您需要将预留资源显式映射到物理内存(堆)。您可以使用命令列表的CopyTileMappings()和UpdateTileMappings()方法将预留资源映射到物理内存。
您可以使用设备接口的CreateReservedResource()方法创建预留资源。与已提交和已放置资源不同,您可以根据需要将其映射和取消映射到不同的堆或堆的不同部分(已提交的资源在创建时严格映射到其隐式堆,而已放置的资源则映射到显式堆的一部分)。
为了提高性能,您将需要在游戏运行时进行后台线程映射,取消映射和创建资源。在图形或计算管道中使用资源之前,必须确保该资源可用于GPU。纹理空间是我们在像素着色器中用于基于纹理坐标从纹理获取像素颜色的空间。 纹理坐标通常由“ x”轴的U和“ y”轴的V表示。 但是有时,您可能会看到S代表轴x,T代表y轴。
对于3D纹理,第三个轴将由“ W”表示,表示纹理的深度。
在Direct3D中,纹理的纹理坐标始于纹理的左上角,点(0,0),然后转到纹理的右下角(1,1)。根据您配置的采样,可能会设置它进行纹理包装。 这意味着如果大于1或小于0,纹理将重复自身,如下所示:
另一种采样配置可能是具有某种颜色的边框。 如果大于1或小于0,则可能会看到“超出范围”的空间量为纯色。
Samplers
在OpenGL和许多3D建模程序中,V被翻转,以便纹理的纹理坐标从左下角(0,0)开始并移到右上角(1,1)。采样器告诉着色器在给定纹理坐标以获取像素颜色时应如何读取纹理。 采样器是另一种资源,您可以通过根签名将其绑定到管道。
新变量
您可以创建“静态采样器”,这是“烘焙”到根签名中的采样器。 一旦创建了根签名,便无法更改静态采样器,但是由于它们是根签名的一部分,因此它们通常性能更高,因为您无需上传采样器资源并将其绑定为根参数,正如它们名称一样,它们是静态的,因此一旦您在根签名上创建了静态采样器,就无法更改它。
采样器不会像其他根参数那样添加到根签名使用的总内存中(例如,根常量为1 DWORD到最大64 DWORDS)。
在本教程中,我们将使用静态采样器,因为它使代码更容易一些。
上代码了〜第一个新的全局变量是称为TextureBuffer的资源对象。 该资源将保存我们的纹理数据。
接下来的几个是函数原型。 这些功能将用于使用WIC API从文件中加载纹理。
我们有一个新的描述符堆,它将存储纹理的SRV,然后有一个上传堆,用于上传纹理并将其复制到textureBuffer资源(默认堆)。新的顶点结构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; ID3D12Resource* textureBufferUploadHeap;
我们删除了XMLFLOAT4颜色成员,并向顶点结构添加了XMFLOAT2成员,以表示每个顶点的纹理坐标数据。
描述符表struct Vertex { Vertex(float x, float y, float z, float u, float v) : pos(x, y, z), texCoord(u, v) {} XMFLOAT3 pos; XMFLOAT2 texCoord; };
我们在根签名中添加了两个新参数,一个描述符表和一个静态采样器。 我们在上一教程中已经讨论过描述符表,因此在此不再赘述。
第一个根参数[0]是我们的常量缓冲区,第二个[1]是我们的描述符表,其中将包含SRV的描述符。// 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
在这里,我们通过填写D3D12_STATIC_SAMPLER_DESC结构来创建静态采样器。
typedef struct D3D12_STATIC_SAMPLER_DESC { D3D12_FILTER Filter; D3D12_TEXTURE_ADDRESS_MODE AddressU; D3D12_TEXTURE_ADDRESS_MODE AddressV; D3D12_TEXTURE_ADDRESS_MODE AddressW; FLOAT MipLODBias; UINT MaxAnisotropy; D3D12_COMPARISON_FUNC ComparisonFunc; D3D12_STATIC_BORDER_COLOR BorderColor; FLOAT MinLOD; FLOAT MaxLOD; UINT ShaderRegister; UINT RegisterSpace; D3D12_SHADER_VISIBILITY ShaderVisibility; } D3D12_STATIC_SAMPLER_DESC;
- Filter-这是D3D12_FILTER枚举值。这说明了从纹理采样时我们要使用哪种类型的过滤器。
- AddressU-这是D3D12_TEXTURE_ADDRESS_MODE枚举值。这解释了当纹理坐标超出0-1范围(大于1或小于0)时应该发生的情况。
- AddressV-同上
- AddressW-与上述相同
- MipLODBias-对于mipapped纹理,这是D3D认为应保持的mip级别的偏移量。例如,如果将其设置为1,并且D3D决定应该采样的Mip级别为2级,那么我们将从3级采样。
- MaxAnisotropy-范围(clamping)值,用于将滤波器设置为D3D12_FILTER_ANISOTROPIC或D3D12_FILTER_COMPARISON_ANISOTROPIC。值必须介于1到16之间。
- comparisonFunc-D3D12_COMPARISON_FUNC枚举值。此功能用于将采样值与现有采样数据进行比较。实话实说,我不确定D3D12文档在谈论什么采样数据,因此我只是将其设置为D3D12_COMPARISON_FUNC_NEVER。
- BorderColor-如果为上述地址成员之一指定了border,则将为0-1纹理坐标范围以外的值采样该值。只能是透明的黑色,不透明的黑色或不透明的白色(D3D12_STATIC_BORDER_COLOR枚举值)。
- MinLOD-这是将Mipmap范围的下限范围到的Mipmap级别,其中0是最低级别和最详细的Mipmap。如果在创建采样器并决定切断更详细的mipmap时发现可用的显存太低,则可以将其设置为大于零。
- MaxLOD-与上述相反。这是将mipmap级别固定到的较高级别,其中mipmap的图像较小且细节较少。将此设置为D3D12_FLOAT32_MAX以不限制Mipmap
- ShaderRegister-这是您要将此采样器绑定到的s寄存器。例如,在本教程中,我们在s0有一个采样器。您将使用此采样器从绑定的SRV(t寄存器)中为像素采样一个值。
- RegisterSpace-这是寄存器空间。在先前的教程中讨论了寄存器空间。
- ShaderVisibility-这是此采样器对着色器的可见性。只有一个着色器可以看到采样器,或者所有的着色器都可见D3D12_SHADER_VISIBILITY_ALL。我们正在从像素着色器中采样纹理,因此我们将其设置为D3D12_SHADER_VISIBILITY_PIXEL
更新了根签名// 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;
现在,我们创建根签名描述。该根签名与上一教程的不同之处在于,我们现在有一个静态采样器,以及描述符表的第二个根参数。
根签名描述的Init函数的第一个参数(在d3dx12.h标头中)是我们要用来创建此根签名的参数数量。我们有2个参数,一个用于常量缓冲区的根常量,另一个用于存储纹理的SRV的描述符表。
第二个参数是D3D12_ROOT_PARAMETER结构的数组,它们定义每个参数。
第三个参数是我们用来创建根签名的静态采样器的数量。我们只有一个,因此我们将该参数设置为1。
第四个参数是对我们的采样器描述的引用。
最后一个参数是我们用来创建根签名的标志。我们希望对尽可能多的着色器拒绝根签名,让不需要访问根签名的着色器不知道它,从而不会失去性能。我们的顶点着色器和像素着色器都需要访问根签名,因此我们拒绝了除这两个以外的所有着色器访问。新的输入布局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);
我们删除了COLOR输入元素,并将其替换为DXCO_FORMAT_R32G32_FLOAT的TEXCOORD元素。
新立方体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 } };
我们已将每个顶点的4个颜色值替换为纹理坐标的2个值
从文件加载纹理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 }, };
我们创建了几个函数,这些函数会将图像数据从文件加载到内存中,并以DXGI兼容格式(例如32位RGBA)进行解码和布局。
该函数称为LoadImageDataFromFile(),并带有4个参数。
第一个参数是对BYTE数组的引用。一旦将图像解码为DXGI兼容格式,此BYTE数组(BYTE)将存储实际的图像数据。
第二个参数是对D3D12_RESOURCE_DESC结构的引用。当我们加载和解码图像时,该结构将在函数中填写。它将包含所有纹理信息,例如宽度,高度和像素格式。尽管一旦获得数据和初始纹理描述,您就可以计算和创建自己的mipmap并使用mipmap的数量更新纹理描述,此功能将不会创建mipmap。
第三个参数是我们要加载的文件名。在这种情况下,我们正在加载一个名为braynzar.jpg的jpeg文件。
最后,我们引用了图像中每行的字节数。这将由函数填充。
该函数返回图像的大小(以字节为单位)。LoadImageDataFromFile()// Load the image from file D3D12_RESOURCE_DESC textureDesc; int imageBytesPerRow; BYTE* imageData; int imageSize = LoadImageDataFromFile(&imageData, textureDesc, L"braynzar.jpg", imageBytesPerRow);
这是一个自定义函数,它使用WIC API将图像文件加载和解码为DXGI兼容格式。它填写一个D3D12_RESOURCE_DESC,并返回图像数据,图像大小(以字节为单位)以及图像每行的字节数。稍后您将看到为什么我们需要每行字节。我们必须以与D3D驱动程序和GPU兼容的方式在D3D12中正确对齐数据。
让我们从此功能的顶部开始。一些WIC API返回HRESULT,因此我们仅在函数顶部创建此对象。
对于我们的应用程序,我们只需要一个IWICImagingFactory,我们将使用它来创建解码器,帧和转换器,因此我们将此变量设为静态。
解码器,帧和转换器都是每个图像,因此我们必须为加载的每个图像重新创建每个图像。
并非所有WIC格式都与DXGI兼容,因此在必须将图像格式转换为兼容的DXGI格式的情况下,我们将imageConverted设置为true,我们将很快发现。
如果我们尚未创建WIC工厂,请立即进行操作。当我们加载第一个图像时,这只会发生一次。创建位图解码器// 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; }
我们可以使用WIC工厂的CreateDecoderFromFilename()方法从文件创建解码器。
抓取图像中的第一帧// 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;
某些图像格式(例如GIF)具有多个图像或帧。 其他图像格式(例如jpg)仅具有一个图像或一帧,因此我们只需使用WIC解码器的GetFrame()方法从解码器获取第一帧。 第一个参数是我们希望获取的帧号,第二个参数是我们希望将其存储在其中的IWICBitmapFrameDecode。
从帧获取WIC像素格式// get image from decoder (this will decode the "frame") hr = wicDecoder->GetFrame(0, &wicFrame); if (FAILED(hr)) return 0;
现在我们得到了解码图像的像素格式。 我们可以使用IWICBitmapSource接口(IWICBitmapFrameDecode继承自IWICBitmapSource)的GetPixelFormat()方法来完成此操作。
获取图像大小// get wic pixel format of image WICPixelFormatGUID pixelFormat; hr = wicFrame->GetPixelFormat(&pixelFormat); if (FAILED(hr)) return 0;
我们需要获取图像的宽度和高度,这可以使用IWICBitmap Source接口的GetSize()方法来完成。
获取兼容的DXGI格式// get size of image UINT textureWidth, textureHeight; hr = wicFrame->GetSize(&textureWidth, &textureHeight); if (FAILED(hr)) return 0;
现在,我们调用另一个自定义函数,它只是一堆if语句。 此函数将返回与图像的WIC格式兼容的DXGI格式。 如果没有兼容的DXGI格式,它将返回DXGI_FORMAT_UNKNOWN,然后我们知道必须将其转换为DXGI兼容格式。
如有必要,将图像转换为DXGI格式// convert wic pixel format to dxgi pixel format DXGI_FORMAT dxgiFormat = GetDXGIFormatFromWICFormat(pixelFormat);
基本上,我们在这里所做的就是找到一种WIC格式,该格式可以根据图像的当前WIC像素格式转换为WIC格式。 一旦有了我们知道与DXGI兼容的格式,就可以转换图像。
为此,我们首先使用WIC工厂创建一个IWICFormatConverter,检查当前格式是否可以转换为新格式,然后最后使用IWICFormatConverter对象进行转换。
新转换的图像实际上现在将驻留在IWICFormatConverter对象中,因此,当我们获取像素数据时,如果不需要转换图像,则必须从IWICBitmapFrameDecode获取它;如果需要图像,则必须从IWICFormatConverter中获取它。 被转换。 这是imageConverted布尔变量的来源。如果图像已转换,则将其设置为true。获取图像尺寸信息// 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; }
现在,我们通过调用另一个自定义函数(仅是一堆if语句)将基于dxgi格式获得每个像素的位数,这些语句将基于给定的DXGI格式返回每个像素的位数。
一旦有了每个像素的位数,就可以计算每行的字节数。 每行字节数是通过(每位宽度位像素数)/ 8来计算的。由于D3D驱动程序如何管理缓存资源,因此必须获得每行字节数,这给我们带来了对齐责任。
然后,我们获得实际的图像大小(以字节为单位)。获取像素数据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
我们要做的第一件事是分配足够的内存来存储图像。此时,图像已被解码,并且很有可能比其存储在其中的文件大得多(因为它已编码)。 我们使用malloc进行此操作,传入图像的大小(以字节为单位)。
一旦分配了足够的内存,就可以将像素数据从wic帧或wic格式转换器(取决于是否转换了图像)复制到分配的内存中。 我们使用接口的CopyPixels方法从这些接口复制数据。填写纹理描述// 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; }
现在我们已经将图像存储在内存中,并且有了有关图像的信息,我们必须填写D3D12_RESOURCE_DESC结构。 我决定通过一个参数传递对此结构的引用,以便我们可以让此函数返回多个数据。 参数名称为resourceDescription,这是我们将要填写的结构。
typedef struct D3D12_RESOURCE_DESC { D3D12_RESOURCE_DIMENSION Dimension; UINT64 Alignment; UINT64 Width; UINT Height; UINT16 DepthOrArraySize; UINT16 MipLevels; DXGI_FORMAT Format; DXGI_SAMPLE_DESC SampleDesc; D3D12_TEXTURE_LAYOUT Layout; D3D12_RESOURCE_FLAGS Flags; } D3D12_RESOURCE_DESC;
- Dimension-这是D3D12_RESOURCE_DIMENSION枚举值。基本上,这只是说这是什么类型的资源。我们正在创建2D纹理,因此将其设置为D3D12_RESOURCE_DIMENSION_TEXTURE2D
- Alignment -这是资源的对齐,值可以是0、4KB(4096或D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT),64KB(65536或D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT)或4MB(4194304或D3D12_DEFAULT_MENT_AAPL)。如果设置为0,则D3D驱动程序将根据图像的大小和mipmap的数量自动确定对齐方式。如果您正在使用放置或保留的资源,则需要自己明确设置,以便更好地将资源映射到堆。
- Width -纹理的宽度
- Height -纹理的高度
- DepthOrArraySize-对于1D或2D纹理,将其设置为1。否则,这是使用3D纹理时纹理的深度。
- MipLevels-此资源中的Mip级别数。我们不在此处创建任何mipmap,因此将其设置为1,因此只有一个mipmap级别。
- Format -这是纹理的DXGI格式。
- SampleDesc-这是DXGI_SAMPLE_DESC结构,我们在上一教程中已经讨论过。只有两个成员,Count和Quality。我们分别将其设置为1和0。
- Layout -D3D12_TEXTURE_LAYOUT枚举值。我们可以将其设置为D3D12_TEXTURE_LAYOUT_UNKNOWN,以使D3D驱动程序选择最有效的像素布局。这就是数据在内存中的布局方式。
- Flags -D3D12_RESOURCE_FLAGS枚举值。我们将在此处将其设置为D3D12_RESOURCE_FLAG_NONE。
从函数返回// 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; }
回到InitD3D()函数,一旦从文件中加载纹理,我们首先检查它的大小以确保我们有数据
创建纹理资源// 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");
现在,我们需要创建一个足以容纳数据的上传堆,然后将图像数据复制到纹理资源。 我们要做的第一件事是找出上传堆必须多大才能存储我们的纹理。 这是必须满足对齐要求的地方。
行间距是图像中每行像素的大小(以字节为单位)。 对于上传堆,除最后一行(不需要满足此对齐要求)外,每行的行间距必须为256字节对齐。
那么我们如何计算呢? 我们得到每行的字节数减去最后一行的字节数,添加填充以使其对齐256字节,然后将最后一行的字节数相加。 您可以使用以下代码进行操作:int textureHeapSize = ((((width * numBytesPerPixel) + 255) & ~255) * (height - 1)) + (width * numBytesPerPixel);
或者,我们可以使用D3D设备提供给我们的API GetCopyableFootprints()。 使用上面的公式,并将其与GetCopyableFootprints的结果进行比较,它们最终应具有相同的值。
一旦拥有了纹理所需的上传堆大小,便创建了上传堆(作为提交的资源)。上传纹理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");
现在,我们有一个可以读取像素着色器的默认堆,还有一个我们可以用来上传纹理数据并复制到默认堆的上传堆。 现在就开始做吧。
我们将使用d3dx12.h帮助程序头中的UpdateSubresources()函数通过上传堆将资源数据复制到默认堆。 此函数为我们做了很多工作,然后最终调用CopyTextureRegion()将资源从上传堆复制到默认堆。UINT64 inline UpdateSubresources( _In_ ID3D12GraphicsCommandList *pCmdList, _In_ ID3D12Resource *pDestinationResource, _In_ ID3D12Resource *pIntermediate, UINT64 IntermediateOffset, _In_ UINT FirstSubresource, _In_ UINT NumSubresources, _In_ D3D12_SUBRESOURCE_DATA *pSrcData );
- pCmdList-将执行此命令的命令列表
- pDestinationResource-我们希望更新的资源
- pIntermediate-我们希望通过其更新目标资源的资源
- IntermediateOffset-要复制的上传堆中资源数据的偏移量(以字节为单位)。 上传堆仅具有这一资源,因此我们不需要偏移。 设置为0
- FirstSubresource-我们要从中复制的中间资源中第一个子资源的索引。 我们只有一个资源,因此我们将其设置为0
- NumSubresources-我们要复制的子资源数。 我们只有一个资源,我们想复制整个内容,因此我们将其设置为1
- pSrcData-我们将要复制的实际数据。
将纹理数据复制到默认堆之后,必须将纹理资源的状态从复制目标转换为像素着色器资源。 我们这样做会遇到资源障碍。
创建SRV描述符堆// 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));
现在,我们创建一个描述符堆来存储描述纹理资源(SRV)的描述符。 我们已经在上一教程中创建了描述符堆,因此,如果您不知道这是怎么回事,则可以阅读Constant Buffer教程。
创建SRV描述符// create the descriptor heap that will store our srv D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {}; heapDesc.NumDescriptors = 1; 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; }
在这里,我们创建了着色器资源视图,该视图描述了我们的纹理以及在何处可以找到它。 我们将这个SRV存储在上面刚刚创建的描述符堆中,然后使用描述符表指向该SRV,以便像素着色器可以使用纹理。
要创建SRV,请填写D3D12_SHADER_RESOURCE_VIEW_DESC结构。typedef struct D3D12_SHADER_RESOURCE_VIEW_DESC { DXGI_FORMAT Format; D3D12_SRV_DIMENSION ViewDimension; UINT Shader4ComponentMapping; union { D3D12_BUFFER_SRV Buffer; D3D12_TEX1D_SRV Texture1D; D3D12_TEX1D_ARRAY_SRV Texture1DArray; D3D12_TEX2D_SRV Texture2D; D3D12_TEX2D_ARRAY_SRV Texture2DArray; D3D12_TEX2DMS_SRV Texture2DMS; D3D12_TEX2DMS_ARRAY_SRV Texture2DMSArray; D3D12_TEX3D_SRV Texture3D; D3D12_TEXCUBE_SRV TextureCube; D3D12_TEXCUBE_ARRAY_SRV TextureCubeArray; }; } D3D12_SHADER_RESOURCE_VIEW_DESC;
- Format -这是资源的DXGI格式,即DXGI_FORMAT枚举值。
- ViewDimension-这是D3D12_SRV_DIMENSION枚举值。 对于2D纹理,我们指定D3D12_SRV_DIMENSION_TEXTURE2D。
- Shader4ComponentMapping-这是D3D12_SHADER_COMPONENT_MAPPING枚举值。 指定D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING表示默认的1:1映射。 如果要指定其他任何内容,则所有4个组件都将返回我们在此处指定的内容,例如红色通道的D3D12_SHADER_COMPONENT_MAPPING_FROM_MEMORY_COMPONENT_0。
- Buffer - TextureCubeArray -这些是联合结构。 由于联合会确保结构有足够的空间容纳最大的联合结构,因此我们仅设置其中一个联合结构。 由于我们只使用单个texture2d,因此我们将Texture2D联合参数的MipLevels设置为1,因为我们没有进行mipmapping,而只有一个级别。 我们可以将Texture2D成员的其他成员保留为默认值。
上传纹理// 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());
现在,我们已经创建了一些命令,特别是上传纹理并将纹理缓冲区的状态从复制目标更改为像素着色器资源,我们需要在使用纹理之前执行这些命令。请记住,尝试在任何着色器中使用资源之前,必须确保您的资源已完全上传,处于活动状态并且GPU可见。
上传纹理数据后,我们可以删除纹理数据的本地副本,因为我们不再使用它了。
记住residency。我们无需在本教程中担心它,但是在您自己的应用程序中,如果您暂时完成了资源的使用,但是计划再次使用它,而不是释放资源,则可以“逐出”它,如果另一个资源需要该内存,它将使该内存可用,并将在需要时将其移动到较低的内存位置,例如系统内存或页面内存。当再次需要它时,可以“使居民”(MakeResident)资源,而不是从文件中加载,解码并重新上传。
现在我在上一段中说了资源,但事实是,您只能拥有Evice和MakeResident ENTIRE堆,其中可能有很多资源。您无法逐出堆或使其成为堆的一部分,因此您将希望根据堆的使用模式将资源分组,或者仅使用已提交的资源。将SRV的描述符堆设置为根参数// 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 imageData;
我们之前已经做到了。 我们创建一个描述符堆数组(仅SRV描述符堆),并使其成为当前描述符堆。
设置根描述符表// 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);
顶点着色器有一个新的输入参数,纹理坐标。 我们在VS_INPUT结构的TEXCOORD语义上添加了float2参数。
struct VS_INPUT { float4 pos : POSITION; float2 texCoord: TEXCOORD; };
我们还必须将纹理坐标传递给像素着色器,因此我们还要向VS_OUTPUT结构中添加一个float2。
struct VS_OUTPUT { float4 pos: SV_POSITION; float2 texCoord: TEXCOORD; };
在我们的顶点着色器中,我们要做的就是将输出纹理坐标设置为输入纹理坐标的值。
在像素着色器中采样纹理VS_OUTPUT main(VS_INPUT input) { VS_OUTPUT output; output.pos = mul(input.pos, wvpMat); output.texCoord = input.texCoord; return output; }
在这里,我们有两个统一变量,一个Texture2D和一个SamplerState,分别绑定在寄存器t0和s0处。 Texture2D将是我们在根描述符表中为寄存器t0设置的SRV,而SamplerState是我们在根签名中创建并绑定到寄存器s0的静态采样器。
要使用这两个新变量并从纹理中获取像素的颜色,我们调用Texture2D的Sample方法,将SamplerState作为第一个参数,将2D纹理坐标作为第二个参数。 结果将是float4,其中包含使用我们提供的采样器作为参数从该纹理坐标处的纹理采样的rgba值。Texture2D t1 : register(t0); SamplerState s1 : register(s0); struct VS_OUTPUT { float4 pos: SV_POSITION; float2 texCoord: TEXCOORD; }; float4 main(VS_OUTPUT input) : SV_TARGET { // return interpolated color return t1.Sample(s1, input.texCoord); }
就是这样! 您应该以如下形式结束:
完整代码VertexShader.hlsl
struct VS_INPUT { float4 pos : POSITION; float2 texCoord: TEXCOORD; }; struct VS_OUTPUT { float4 pos: SV_POSITION; float2 texCoord: TEXCOORD; }; cbuffer ConstantBuffer : register(b0) { float4x4 wvpMat; }; VS_OUTPUT main(VS_INPUT input) { VS_OUTPUT output; output.pos = mul(input.pos, wvpMat); output.texCoord = input.texCoord; return output; }
PixelShader.hlsl
Texture2D t1 : register(t0); SamplerState s1 : register(s0); struct VS_OUTPUT { float4 pos: SV_POSITION; float2 texCoord: TEXCOORD; }; float4 main(VS_OUTPUT input) : SV_TARGET { // return interpolated color return t1.Sample(s1, input.texCoord); }
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> // 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(); // 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; ID3D12Resource* textureBufferUploadHeap;
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; }; 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 Update(); // 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; } // 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"); 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 = 1; 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()); // 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 imageData; // create a vertex buffer view for the triangle. 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; // 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() { // update app logic, such as moving the camera or figuring out what objects are in view // create rotation matrices XMMATRIX rotXMat = XMMatrixRotationX(0.0001f); XMMATRIX rotYMat = XMMatrixRotationY(0.0002f); XMMATRIX rotZMat = XMMatrixRotationZ(0.0003f); // 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.0003f); rotYMat = XMMatrixRotationY(0.0002f); rotZMat = XMMatrixRotationZ(0.0001f); // 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); } 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); // 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; }
参考链接:
- 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/
标签:return,hr,0.5,else,纹理,D3D12,GUID 来源: https://blog.51cto.com/u_15273495/2914373