其他分享
首页 > 其他分享> > 纹理

纹理

作者:互联网

本教程将教您如何从文件创建纹理。 我们将学习如何使用Windows Imaging Component(WIC)API从文件中加载图像。 加载完图像后,我们将使用上传堆将其上传到默认资源堆,创建一个SRV,然后在像素着色器中使用该SRV中的样本为立方体着色。

介绍

在本教程中,我们将学习如何使用从文件加载的图像对立方体进行纹理处理。
我们需要执行3个步骤来获得SRV,我们可以使用它来对立方体进行纹理处理
  1.从图像文件加载数据,并将其解码为与DXGI格式(rgba)兼容的位图格式
  2.创建一个上传(中间)堆,默认堆和资源以存储位图数据
  3.创建用于描述并指向位图图像数据的着色器资源视图(SRV)
一旦有了SRV(位于描述符堆中),便将作为描述符表的根参数设置为SRV在描述符堆中所在的区域。 然后,我们的像素着色器可以从该纹理中采样以获取立方体中三角形的像素颜色。

Windows Imaging Component (WIC)(Windows Imaging Component

我们将首先介绍如何加载图像文件并将数据解码为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)。

Texture UV

根据您配置的采样,可能会设置它进行纹理包装。 这意味着如果大于1或小于0,纹理将重复自身,如下所示:

Texture UV over 1

另一种采样配置可能是具有某种颜色的边框。 如果大于1或小于0,则可能会看到“超出范围”的空间量为纯色。
在OpenGL和许多3D建模程序中,V被翻转,以便纹理的纹理坐标从左下角(0,0)开始并移到右上角(1,1)。

Samplers

采样器告诉着色器在给定纹理坐标以获取像素颜色时应如何读取纹理。 采样器是另一种资源,您可以通过根签名将其绑定到管道。
您可以创建“静态采样器”,这是“烘焙”到根签名中的采样器。 一旦创建了根签名,便无法更改静态采样器,但是由于它们是根签名的一部分,因此它们通常性能更高,因为您无需上传采样器资源并将其绑定为根参数,正如它们名称一样,它们是静态的,因此一旦您在根签名上创建了静态采样器,就无法更改它。
采样器不会像其他根参数那样添加到根签名使用的总内存中(例如,根常量为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文件。
最后,我们引用了图像中每行的字节数。这将由函数填充。
该函数返回图像的大小(以字节为单位)。

// Load the image from file
D3D12_RESOURCE_DESC textureDesc;
int imageBytesPerRow;
BYTE* imageData;
int imageSize = LoadImageDataFromFile(&imageData, textureDesc, L"braynzar.jpg", imageBytesPerRow);
LoadImageDataFromFile()

这是一个自定义函数,它使用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。

// get image from decoder (this will decode the "frame")
hr = wicDecoder->GetFrame(0, &wicFrame);
if (FAILED(hr)) return 0;
从帧获取WIC像素格式

现在我们得到了解码图像的像素格式。 我们可以使用IWICBitmapSource接口(IWICBitmapFrameDecode继承自IWICBitmapSource)的GetPixelFormat()方法来完成此操作。

// get wic pixel format of image
WICPixelFormatGUID pixelFormat;
hr = wicFrame->GetPixelFormat(&pixelFormat);
if (FAILED(hr)) return 0;
获取图像大小

我们需要获取图像的宽度和高度,这可以使用IWICBitmap Source接口的GetSize()方法来完成。

// get size of image
UINT textureWidth, textureHeight;
hr = wicFrame->GetSize(&textureWidth, &textureHeight);
if (FAILED(hr)) return 0;
获取兼容的DXGI格式

现在,我们调用另一个自定义函数,它只是一堆if语句。 此函数将返回与图像的WIC格式兼容的DXGI格式。 如果没有兼容的DXGI格式,它将返回DXGI_FORMAT_UNKNOWN,然后我们知道必须将其转换为DXGI兼容格式。

// convert wic pixel format to dxgi pixel format
DXGI_FORMAT dxgiFormat = GetDXGIFormatFromWICFormat(pixelFormat);
如有必要,将图像转换为DXGI格式

基本上,我们在这里所做的就是找到一种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-我们将要复制的实际数据。

将纹理数据复制到默认堆之后,必须将纹理资源的状态从复制目标转换为像素着色器资源。 我们这样做会遇到资源障碍。

// 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描述符堆

现在,我们创建一个描述符堆来存储描述纹理资源(SRV)的描述符。 我们已经在上一教程中创建了描述符堆,因此,如果您不知道这是怎么回事,则可以阅读Constant Buffer教程。

// 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,以便像素着色器可以使用纹理。
要创建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堆,其中可能有很多资源。您无法逐出堆或使其成为堆的一部分,因此您将希望根据堆的使用模式将资源分组,或者仅使用已提交的资源。

// 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的描述符堆设置为根参数

我们之前已经做到了。 我们创建一个描述符堆数组(仅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);
}

就是这样! 您应该以如下形式结束:

D3D12 Texture Coordinate Tutorial Finale

完整代码

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;
}

 

 

参考链接:

  1. https://docs.microsoft.com/en-us/windows/win32/direct3d12/directx-12-programming-guide
  2. http://www.d3dcoder.net/
  3. https://www.braynzarsoft.net/viewtutorial/q16390-04-directx-12-braynzar-soft-tutorials
  4. https://developer.nvidia.com/dx12-dos-and-donts
  5. https://www.3dgep.com/learning-directx-12-1/
  6. https://gpuopen.com/learn/lets-learn-directx12/
  7. https://alain.xyz/blog/raw-directx12
  8. https://www.rastertek.com/tutdx12.html
  9. https://digitalerr0r.net/2015/08/19/quickstart-directx-12-programming/
  10. https://walbourn.github.io/getting-started-with-direct3d-12/
  11. https://docs.aws.amazon.com/lumberyard/latest/userguide/graphics-rendering-directx.html
  12. http://diligentgraphics.com/diligent-engine/samples/
  13. https://www.programmersought.com/article/2904113865/
  14. https://www.tutorialspoint.com/directx/directx_first_hlsl.htm
  15. http://rbwhitaker.wikidot.com/hlsl-tutorials
  16. https://digitalerr0r.net/2015/08/19/quickstart-directx-12-programming/
  17. https://www.ronja-tutorials.com/post/002-hlsl/

标签:return,hr,0.5,else,纹理,D3D12,GUID
来源: https://blog.51cto.com/u_15273495/2914373