当前位置:网站首页>D3D11_ Chili_ Tutorial (2): draw a triangle
D3D11_ Chili_ Tutorial (2): draw a triangle
2022-07-04 16:15:00 【Kiss your wife】
List of articles
- 11:D3D Initialization —— COM(Component Object Model)
- 12:D3D framework / Exchange chain
- 13: Initialization equipment
- 14: Debugging layer
- 15: Intelligent pointer
- 16: Draw a triangle ( Part I )
- 17: Draw a triangle ( last volume of a two- or three-volume book )
- 18: Experimentalize
- 19: Constant cache
- Assembly line
- Row main order matrix and column main order matrix
11:D3D Initialization —— COM(Component Object Model)
Highly recommended article :
https://zhuanlan.zhihu.com/p/121800182
https://zhuanlan.zhihu.com/p/122482719
https://zhuanlan.zhihu.com/p/457348124
COM:Component Object Model
C++ Reuse is through source code rather than compiled binaries , It doesn't care much about the format of binary objects .
So first of all , Why does Microsoft want to do such a thing .
Take the virtual table for example ,MSVC The compiler places a pointer to the virtual table at the beginning of the class ( That is, first virtual table pointer , Subclass members ), However, for GNU It is placed at the end of the class . This will lead to an incompatibility problem : For example, I am in GNU I want to use MSVC compiled dll To do ? namely C++ It is difficult to interact between different compilers .
And even the same compiler , It is also difficult to interact between different versions .
For example, the leaders under this problem say : How to explain it in a popular way COM Components ? - You know (zhihu.com)、
When I write a dll When you upgrade , Internal members may be added , Cause the allocated space to change , Thus making times dll And the old dll Incompatible . This is notorious dll hell, To this end, Microsoft first thought of a very frustrating method , That is in dll Add your version number after it , Such as :myDll_1.dll, myDll_2.dll……
author :Froser
link :https://www.zhihu.com/question/49433640/answer/116028598
And as master Lingjian said , If the library is upgraded , A class has new members , The size changes , Then I made it up before dll Under the old definition new Out of the space , It cannot be used in the new library .
therefore COM Our goal is to solve such contradictions , Provide binary interface for software . We can directly let binary files be used by other software , Without getting the source code . This is a very stable interface , Even if the binary file is recompiled , Clients that depend on this will not crash .
COM Some of the benefits of are as follows ( But as master Lingjian said , It is a leftover problem , But it's still Windows Upper C++ The best choice for dynamic linking of binary native code ):
- Don't distinguish between languages
You can compile in different versions 、 Different compilers and even different languages provide interfaces , As long as this language supports function pointers . If this language supports operating memory , Then you can also create COM object - Resource allocation
Not dependent on any particular language , There is an independent resource allocation system . - UUID
Constructed an operating system level Factory, For all Interface All for unified use UUID To mark - Powerful language independent encapsulation
- Thread safety
- Support distribution
- …
about C++, We can make it like this interface: For example, two interfaces , One openable, One punchable, Make them into two pure virtual classes , Then those who have these two methods use C++ To inherit the two of them .
Remember what we said in the learning notes of dragon book before ,I Start object :COM Interfaces are in uppercase letters “I” As the beginning . For example, a that represents a list of commands COM Interface for ID3D12GraphicsCommandList
about COM Interface , Tell me first COM Factories let it create COM object , After creating, give some COM Object's interface , The prefix for I. We just need to focus on interacting with the interface .
When creating a COM When the object , You need to call factory related functions , Give Way COM To do the processing . So you can imagine for COM The object is not new and delete 了 :
After this transformation , And then there's the deconstruction process ~MyClass() Or say delete myClass, Because the same object may return many interfaces , Some interfaces are still in use , If one of them is delete 了 , All other interfaces will go wrong , So reference counting is introduced , So that many people can share the same object .
author : Spirit sword
link :https://www.zhihu.com/question/49433640/answer/115952604
COM Object counts its number of references , Therefore, when we finish using an interface, we should call its Release Method, not delete —— When COM Object has a reference count of 0 It will free up its own memory . At first it was creates The initialization reference count is 1, Use it elsewhere AddRef Reference count ++, If you don't use it elsewhere Release Then reference count –.
Next, we will introduce several important functions :
IUnknown::QueryInterface(REFIID,void) - Win32 apps | Microsoft Docs
Queries a COM object for a pointer to one of its interface; identifying the interface by a reference to its interface identifier (IID). If the COM object implements the interface, then it returns a pointer to that interface after calling IUnknown::AddRef on it.
So for UUID, One is in the header file <Wininet.h> Through class IActiveDesktop , This class name contains __uuidof Methods .
For example, we will use :
__uuidof(ID3D12CommandAllocator)
Its presence d3d12.h It is encapsulated in uuid:
MIDL_INTERFACE("6102dee4-af59-4b09-b999-b44d73f09b24")
ID3D12CommandAllocator : public ID3D12Pageable
{
public:
virtual HRESULT STDMETHODCALLTYPE Reset( void) = 0;
};
Sample code ( Get the name and path of the desktop wallpaper ):
#include <Windows.h>
#include <WinInet.h>
#include <ShlObj.h>
#include <iostream>
int main()
{
CoInitialize(nullptr); // Use COM Initialize the subsystem first ; But in the D3D Is used in “ Light weight COM”, There is no need to initialize the subsystem
IActiveDesktop* pDesktop = nullptr;
WCHAR wszWallpaper[MAX_PATH]; // Cache for storing the name of the wallpaper
// Create real COM object
CoCreateInstance(
CLSID_ActiveDesktop, // Active desktop
nullptr,
CLSCTX_INPROC_SERVER, // Create the context of the object . Like the process 、 Local machine 、 Remote context, etc
__uuidof(IActiveDesktop), // Interface UUID, I hope the object queried by the function has this interface
reinterpret_cast<void**>(&pDesktop)
);
pDesktop->GetWallpaper(wszWallpaper, MAX_PATH, 0);
pDesktop->Release();
std::wcout << wszWallpaper;
CoUninitialize();
std::cin.get();
return 0;
}
Output results :
Let's take a look at COM Some details of the object :
Pictured above , For example, a COM Object has two interfaces , Then it will have two virtual tables , A virtual table corresponds to a set of interface functions .
12:D3D framework / Exchange chain
D3D Is an object-oriented architecture , Based on the COM On the object .
Pictured above , These superclasses are Device . The yellow one above seems to be wrong , Should be IDXGIDEVICE
DXGI It is the bottom layer that can be undertaken from D3D Tasks separated from , And iteration in version will not be like D3D So fast .DXGI The job now is to traverse all available hardware on the device . Display rendered frames , control Gamma value . These things are in various versions D3D There is little need to change , So let's put these things separately DXGI in .
We created D3D11 The application does not mean that we need support D3D11 The graphics card .
When you write D3D11 Application time , As shown in the figure below :
As the caption above , The method is to create the device , Feature level selection 9 That's it .
Don't mix this with SDKVersion Confused. :
The goal is SDK edition 11, This means that users only need to update their applications D3D edition , It has nothing to do with the graphics card .
Devices are used to create objects , Context (CONTEXT Used to draw , That is, issue the rendering command and configure the rendering pipeline ):
DX11 The context in is divided into immediate and delayed :
So the delay context is good in multithreading . But the only delay context can't do is to query the graphics driver , Because it will only create the following command list , At some future time . Instant context can query information .(D3D12 It seems that the immediate context has been cancelled )
13: Initialization equipment
First create Graphics.h:
#pragma once
#include "ChiliWin.h"
#include <d3d11.h>
class Graphics
{
public:
Graphics(HWND hWnd);
};
Because the window must be used handle , So we pass a constructor HWND
Later on Windows Add members of this class to the class ( Due to initialization dependency HWND , So use smart pointer management ):
And add the obtained function :
Graphics& Gfx(); // Throw an exception when you can't get the graph , So there's no need to noexcept
The completed header file is like this :
#pragma once
#include "ChiliWin.h"
#include <d3d11.h>
class Graphics
{
public:
Graphics(HWND hWnd);
Graphics(const Graphics&) = delete;
Graphics& operator=(const Graphics&) = delete;
~Graphics();
void EndFrame();
void ClearBuffer(float red, float green, float blue) noexcept;
private:
ID3D11Device* pDevice = nullptr;
IDXGISwapChain* pSwap = nullptr;
ID3D11DeviceContext* pContext = nullptr;
ID3D11RenderTargetView* pTarget = nullptr;
};
Corresponding EndFrame function :
void Graphics::EndFrame()
{
pSwap->Present(1u, 0u);
}
Write here 1u We think we can reach 60 frame . If the target frame rate is only 30 frame , Just write 2u (60 / 2), And so on . The latter parameter 0u Don't use any labels .
And then it can be in our App In the framework :
void App::DoFrame()
{
wnd.Gfx().EndFrame();
}
We want to use this function :
RenderTargetView Usually from texture objects (Texture Object) Created , But we don't . But we can do this :
Because the exchange chain can be regarded as a collection of multiple textures , A collection of multiple frame caches . We can use functions on the exchange chain to access the post cache , This is actually a texture . Then call the function on the texture to create the rendering target view (RenderTargetView) And render .
So here we use pSwap->GetBuffer obtain pBackBuffer , The first parameter 0 Is the index of post cache . And then we use pTarget To save our render target view (RenderTargetView).
// gain access to texture subresource in swap chain (back buffer)
ID3D11Resource* pBackBuffer = nullptr;
pSwap->GetBuffer(0, __uuidof(ID3D11Resource), reinterpret_cast<void**>(&pBackBuffer));
pDevice->CreateRenderTargetView(
pBackBuffer,
nullptr,
&pTarget
);
pBackBuffer->Release();
And then ClearBuffer It's easy :
void Graphics::ClearBuffer(float red, float green, float blue) noexcept
{
const float color[] = {
red,green,blue,1.0f };
pContext->ClearRenderTargetView(pTarget, color);
}
With the above results , We can use the timer to make the red and green channels of our colors change all the time :
void App::DoFrame()
{
const float c = sin(timer.Peek()) / 2.0f + 0.5f;
wnd.Gfx().ClearBuffer(c, c, 1.0f);
wnd.Gfx().EndFrame();
}
14: Debugging layer
This section will be D3D Subsystem set up error checking and rich diagnostic procedures .
In the last section, we wrote similar code :
But no return value is given ,direct3d Functions usually return HRESULT , If an error occurs, it will throw some diagnostic information to help solve the exception .
The natural idea is the same as that of a window class , We wrote these two macros when dealing with window classes :
// error exception helper macro
#define CHWND_EXCEPT( hr ) Window::Exception( __LINE__,__FILE__,hr )
#define CHWND_LAST_EXCEPT() Window::Exception( __LINE__,__FILE__,GetLastError() )
Then when processing, we throw an exception directly like this :
final WinMain Then deal with the exception :
try
{
return App{
}.Go();
}
catch (const ChiliException& e)
{
MessageBox(nullptr, e.what(), e.GetType(), MB_OK | MB_ICONEXCLAMATION);
}
catch (const std::exception& e)
{
MessageBox(nullptr, e.what(), "Standard Exception", MB_OK | MB_ICONEXCLAMATION);
}
catch (...)
{
MessageBox(nullptr, "No details available", "Unknown Exception", MB_OK | MB_ICONEXCLAMATION);
}
But there's a problem , Long ago DirectX SDK and Windows SDK It's separate. , and DirectX SDK And Windows Of HRESULT Are not compatible . You need to pass a call DXerror Independent Library of HRESULT And convert it into a human readable string . later DirectX API Has been incorporated into the Windows SDK in , When this happens, they change the format of the message so that they also support reporting DX Error of , So you don't need it anymore DXerror(DXERR.LIB) 了 . But only if you use Windows 8 Or later . use WIN7 No way ,WIN7 You have to use DXERR.LIB . But the problem is when DirectX API Merge into Windows SDK When DXERR.LIB It's abandoned . And if your target platform is WIN7 Then you still need to download DXERR.LIB . And there is also a problem dxerr Support only Unicode and nstring(narrow string).
Last Chili Solved all this , The required documents are as follows :
In addition, some classes have been added to the graphics class :
And in the DXERR We are interested in these two methods :
const CHAR* WINAPI DXGetErrorStringA( _In_ HRESULT hr );
void WINAPI DXGetErrorDescriptionA( _In_ HRESULT hr, _Out_cap_(count) CHAR* desc, _In_ size_t count );
The former provides the name of the macro representing the error , The latter is a false description .
Then added Device deletion exception class :
And then in Graphics.cpp In the same way, we define several macros to make the code of exception throwing more concise :
// graphics exception checking/throwing macros (some with dxgi infos)
#define GFX_EXCEPT_NOINFO(hr) Graphics::HrException( __LINE__,__FILE__,(hr) )
#define GFX_THROW_NOINFO(hrcall) if( FAILED( hr = (hrcall) ) ) throw Graphics::HrException( __LINE__,__FILE__,hr )
#ifndef NDEBUG
#define GFX_EXCEPT(hr) Graphics::HrException( __LINE__,__FILE__,(hr),infoManager.GetMessages() )
#define GFX_THROW_INFO(hrcall) infoManager.Set(); if( FAILED( hr = (hrcall) ) ) throw GFX_EXCEPT(hr)
#define GFX_DEVICE_REMOVED_EXCEPT(hr) Graphics::DeviceRemovedException( __LINE__,__FILE__,(hr),infoManager.GetMessages() )
#else
#define GFX_EXCEPT(hr) Graphics::HrException( __LINE__,__FILE__,(hr) )
#define GFX_THROW_INFO(hrcall) GFX_THROW_NOINFO(hrcall)
#define GFX_DEVICE_REMOVED_EXCEPT(hr) Graphics::DeviceRemovedException( __LINE__,__FILE__,(hr) )
#endif
So we can directly wrap the code we wrote before ( Check their return HRESULT ):
// create device and front/back buffers, and swap chain and rendering context
GFX_THROW_INFO(D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
swapCreateFlags,
nullptr,
0,
D3D11_SDK_VERSION,
&sd,
&pSwap,
&pDevice,
nullptr,
&pContext
));
// gain access to texture subresource in swap chain (back buffer)
ID3D11Resource* pBackBuffer = nullptr;
GFX_THROW_INFO(pSwap->GetBuffer(0, __uuidof(ID3D11Resource), reinterpret_cast<void**>(&pBackBuffer)));
GFX_THROW_INFO(pDevice->CreateRenderTargetView(pBackBuffer, nullptr, &pTarget));
pBackBuffer->Release();
It says macro like this , In order to in Debug and Release There is a difference , In debug mode, let InfoManager Add information .
But in our EndFrame There is something special in the method :
void Graphics::EndFrame()
{
HRESULT hr;
#ifndef NDEBUG
infoManager.Set();
#endif
if (FAILED(hr = pSwap->Present(1u, 0u)))
{
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
throw GFX_DEVICE_REMOVED_EXCEPT(pDevice->GetDeviceRemovedReason());
}
else
{
throw GFX_EXCEPT(hr);
}
}
}
here pSwap->Present The error code of the removed device may be returned , This is a special error code , Because it also includes other information , So here we add another one pDevice->GetDeviceRemovedReason()
Use this function to get . The cause here is usually due to the driver crash , Or overclocking your GPU And screwed up . So if there is an error, throw an exception and get the reason .
Other handling exceptions and before Windows The processing method under is similar .
stay Window.h We also added an additional class ( No graphic exception ):
When we try to get graphic classes , But when there is no exception, it will be thrown .
When we deliberately make mistakes and work at the debugging layer :
In addition to the exception handling we just did , It's down there output There will be additional relevant information in :
But we hope that the error pop-up window will have enough information , So we created a new class ( Through one IDXGIInfoQueue):
DxgiInfoManager.h:
#pragma once
#include "ChiliWin.h"
#include <vector>
class DxgiInfoManager
{
public:
DxgiInfoManager();
~DxgiInfoManager();
DxgiInfoManager(const DxgiInfoManager&) = delete;
DxgiInfoManager& operator=(const DxgiInfoManager&) = delete;
void Set() noexcept;
std::vector<std::string> GetMessages() const;
private:
unsigned long long next = 0u;
struct IDXGIInfoQueue* pDxgiInfoQueue = nullptr;
};
In the implementation, we will load such a dll, And then in DLL Find the name of the interface in , Then call the function to handle the interface :
When we get the news , It is essentially traversing the message queue :
By calling GetMessage , Pass on nullptr, This will use the index to assign the length of the message to messageLength
We also use this function to facilitate intermediate changes :
Here we introduce DXGI_DEBUG_ALL , But you can also debug only from DXGI or D3D The news of , See MSDN:
For example, at this time, I deliberately made a mistake :
sd.OutputWindow = (HWND)216487;
There will be an error reporting window when running :
15: Intelligent pointer
Smart pointer management was not used before , such as :
// gain access to texture subresource in swap chain (back buffer)
ID3D11Resource* pBackBuffer = nullptr;
GFX_THROW_INFO(pSwap->GetBuffer(0, __uuidof(ID3D11Resource), reinterpret_cast<void**>(&pBackBuffer)));
GFX_THROW_INFO(pDevice->CreateRenderTargetView(pBackBuffer, nullptr, &pTarget));
pBackBuffer->Release();
If the middle two sentences are thrown abnormally , that Release Not to be carried out , Memory leak .
So here we go directly ComPtr( Need to include header file #include <wrl.h>
), Application RAII:
namespace wrl = Microsoft::WRL;
// gain access to texture subresource in swap chain (back buffer)
wrl::ComPtr<ID3D11Resource> pBackBuffer;
GFX_THROW_INFO(pSwap->GetBuffer(0, __uuidof(ID3D11Resource), &pBackBuffer));
GFX_THROW_INFO(pDevice->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &pTarget));
And the part we want to pass on before uses Get Method :
And because the smart pointer overloads the method , Previous reinterpret_cast You can also get the address directly .
The reason why we don't use, for example unique_ptr And so on. , Because unique_ptr The default delegator does not call COM Of release Method . So we use ComPtr
also , When you want to get the interface , We need to pass on a pp (pointer to pointer, The pointer to the pointer ), Then the function will help you fill in the pointer of this interface . While using Unique Pointer actually encapsulates the pointer , You can't get this pp value . also ,ComPtr There are reference counting and so on . To sum up, we need to use ComPtr .
ComPtr There are also some pits , For example, the function we wrote before :
GFX_THROW_INFO(D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
swapCreateFlags,
nullptr,
0,
D3D11_SDK_VERSION,
&sd,
&pSwap,
&pDevice,
nullptr,
&pContext
));
The exchange chain here pSwap And so on ComPtr Wrap it up. :
Graphics.h:
private:
#ifndef NDEBUG
DxgiInfoManager infoManager;
#endif
Microsoft::WRL::ComPtr<ID3D11Device> pDevice;
Microsoft::WRL::ComPtr<IDXGISwapChain> pSwap;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext;
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> pTarget;
And here to pass pp When , If pSwap Point to an actual COM object , Pass in what we wrote &pSwap , It will first call the release function (release) Then return to the address . That's reasonable , If you want to fill it ( Pass on pp Isn't it just to fill this pointer ), You must first release the previous things . This will not leak memory resources .
But sometimes you just want to get the address of the pointer to get the pointer , But I don't want to fill the pointer :
Then you can't use this operator & ( Otherwise, the resources will be released , It's not what we want ), have access to GetAddressOf Method :
16: Draw a triangle ( Part I )
You can see the assembly line from the official :
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/display/pipelines-for-direct3d-version-11
About buffering :
https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-buffers-intro
https://docs.microsoft.com/zh-cn/windows/win32/direct3d11/overviews-direct3d-11-resources-buffers-intro
here IA Is the rendering pipeline Input Assembler ( Enter the assembly phase ) It means , You can see that the third parameter is a pp Indicates that multiple buffers can be specified ( It's equivalent to a pointer array ):
IASetVertexBuffers file :
https://docs.microsoft.com/zh-cn/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetvertexbuffers
We must also have one vertex shader, Otherwise, an error will be reported . The output and input of shaders must be semantically marked .
We can directly Build ,VS Built in compilation , Will compile it into cso form , There will also be errors :
1>compilation object save succeeded; see E:\MyD3D11Learn\D3D11_Chili_Tutorial\D3D11_Chili_Tutorial\bin\Win32\Debug\VertexShader.cso
Our link #pragma comment(lib,"D3DCompiler.lib")
, You can use it to compile shaders at run time . But now we only need to use its shader loading function . You also need to include the header file #include <d3dcompiler.h>
.
wrl::ComPtr<ID3DBlob> pBlob;
GFX_THROW_INFO(D3DReadFileToBlob(L"VertexShader.cso", &pBlob));
But we write like this , You also need to configure each compilation cso File output location (hlsl Right click file ->Properties).
Changed to :$(ProjectDir)%(Filename).cso
You can also choose a shader type :
17: Draw a triangle ( last volume of a two- or three-volume book )
D3D Allow us to render to offline targets Off-Screen Target On .
There is a pit here, which is said to be ComPtr Taking the address will release the pit , So we're going to use GetAddressOf But can't use & :
// bind render target
pContext->OMSetRenderTargets(1u, pTarget.GetAddressOf(), nullptr);
Of course, we should write this section first pixel shader:
float4 main() : SV_TARGET
{
return float4(1.0f, 1.0f, 1.0f, 1.0f);
}
Here we must also specify D3D11_VIEWPORT . from NDC To Screen space , Here's the picture :
D3D11_VIEWPORT You can be dissatisfied with the screen space , For example, the Yellow frame in the above figure only accounts for a quarter of the screen , Can also be used as D3D11_VIEWPORT .
We also need to set up rendering primitives :
Reference link :
https://docs.microsoft.com/zh-cn/windows/win32/direct3d11/d3d10-graphics-programming-guide-primitive-topologies
// Set primitive topology to triangle list (groups of 3 vertices)
pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
To make shaders parse correctly buffer data , It also needs to be input layout, Our vertex shader input is like this :float2 pos : Position
The default here is Position0 了 , This 0 It corresponds to
// input (vertex) layout (2d position only)
wrl::ComPtr<ID3D11InputLayout> pInputLayout;
const D3D11_INPUT_ELEMENT_DESC ied[] =
{
{
"Position",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0 },
};
GFX_THROW_INFO(pDevice->CreateInputLayout(
ied, (UINT)std::size(ied),
pBlob->GetBufferPointer(),
pBlob->GetBufferSize(),
&pInputLayout
));
D3D11_INPUT_ELEMENT_DESC Second parameter of ( Corresponding to the above code "Position" Behind the 0).
such as :
Vertex shader code :
float4 main( float2 pos : Hbh2 ) : SV_Position
{
return float4(pos.x,pos.y,0.0f,1.0f);
}
input layout:
const D3D11_INPUT_ELEMENT_DESC ied[] =
{
{
"Hbh",2,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0 },
};
It is still right to write like this .
Look up MSDN We know :
Can see , We have BytecodeLength Is written pBlob->GetBufferSize(),
, In fact, the BytecodeLength Just to check that the data descriptor and shader do match .
Draw all the code of a triangle function :
void Graphics::DrawTestTriangle()
{
namespace wrl = Microsoft::WRL;
HRESULT hr;
struct Vertex
{
float x;
float y;
};
// create vertex buffer (1 2d triangle at center of screen)
const Vertex vertices[] =
{
{
0.0f,0.5f },
{
0.5f,-0.5f },
{
-0.5f,-0.5f },
};
wrl::ComPtr<ID3D11Buffer> pVertexBuffer;
D3D11_BUFFER_DESC bd = {
};
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.CPUAccessFlags = 0u;
bd.MiscFlags = 0u;
bd.ByteWidth = sizeof(vertices);
bd.StructureByteStride = sizeof(Vertex);
D3D11_SUBRESOURCE_DATA sd = {
};
sd.pSysMem = vertices;
GFX_THROW_INFO(pDevice->CreateBuffer(&bd, &sd, &pVertexBuffer));
// Bind vertex buffer to pipeline
const UINT stride = sizeof(Vertex);
const UINT offset = 0u;
pContext->IASetVertexBuffers(0u, 1u, pVertexBuffer.GetAddressOf(), &stride, &offset);
// create pixel shader
wrl::ComPtr<ID3D11PixelShader> pPixelShader;
wrl::ComPtr<ID3DBlob> pBlob;
GFX_THROW_INFO(D3DReadFileToBlob(L"PixelShader.cso", &pBlob));
GFX_THROW_INFO(pDevice->CreatePixelShader(pBlob->GetBufferPointer(), pBlob->GetBufferSize(), nullptr, &pPixelShader));
// bind pixel shader
pContext->PSSetShader(pPixelShader.Get(), nullptr, 0u);
// create vertex shader
wrl::ComPtr<ID3D11VertexShader> pVertexShader;
GFX_THROW_INFO(D3DReadFileToBlob(L"VertexShader.cso", &pBlob));
GFX_THROW_INFO(pDevice->CreateVertexShader(pBlob->GetBufferPointer(), pBlob->GetBufferSize(), nullptr, &pVertexShader));
// bind vertex shader
pContext->VSSetShader(pVertexShader.Get(), nullptr, 0u);
// input (vertex) layout (2d position only)
wrl::ComPtr<ID3D11InputLayout> pInputLayout;
const D3D11_INPUT_ELEMENT_DESC ied[] =
{
{
"Position",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0 },
};
GFX_THROW_INFO(pDevice->CreateInputLayout(
ied, (UINT)std::size(ied),
pBlob->GetBufferPointer(),
pBlob->GetBufferSize(),
&pInputLayout
));
// bind vertex layout
pContext->IASetInputLayout(pInputLayout.Get());
// bind render target
pContext->OMSetRenderTargets(1u, pTarget.GetAddressOf(), nullptr);
// Set primitive topology to triangle list (groups of 3 vertices)
pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// configure viewport
D3D11_VIEWPORT vp;
vp.Width = 800;
vp.Height = 600;
vp.MinDepth = 0;
vp.MaxDepth = 1;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
pContext->RSSetViewports(1u, &vp);
GFX_THROW_INFO_ONLY(pContext->Draw((UINT)std::size(vertices), 0u));
}
18: Experimentalize
By default , Render pipelines will be backculled (back-face culling), The order of triangular points counterclockwise is considered to be the back , It will be eliminated .
You can see our previous vertex :
const Vertex vertices[] =
{
{
0.0f,0.5f },
{
0.5f,-0.5f },
{
-0.5f,-0.5f },
};
It's clockwise , So it won't be eliminated .
Here we want to get colorful triangles , We add color attributes to vertices :
struct Vertex
{
struct
{
float x;
float y;
} pos;
struct
{
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
} color;
};
Vertex vertices[] =
{
{
0.0f,0.5f,255,0,0,0 },
{
0.5f,-0.5f,0,255,0,0 },
{
-0.5f,-0.5f,0,0,255,0 },
{
-0.3f,0.3f,0,255,0,0 },
{
0.3f,0.3f,0,0,255,0 },
{
0.0f,-0.8f,255,0,0,0 },
};
Shaders we will write like this :
VS:
struct VSOut
{
float3 color : Color;
float4 pos : SV_Position;
};
VSOut main( float2 pos : Position,float3 color : Color )
{
VSOut vso;
vso.pos = float4(pos.x,pos.y,0.0f,1.0f);
vso.color = color;
return vso;
}
PS:
float4 main( float3 color : Color ) : SV_Target
{
return float4( color,1.0f );
}
because PS We only want to pass one Color, Unwanted Position, therefore VS Of us VSOut The internal order is first color Again pos, It can't be replaced , otherwise PS The first parsing is not color Something will go wrong .
At the same time we are VS Specified in float3 color : Color
,d3d It will convert your input to the specified type . But we can also specify some rules , By the type we specify :
UINT Will be converted to the exact integer value , but UNORM The input type will be normalized . For example, enter 255 It will be converted to 1.0. This is exactly what we want ( Color in 0 To 1 Floating point range ).
So we are specifying input layout When I was, I specified DXGI_FORMAT_R8G8B8A8_UNORM:
const D3D11_INPUT_ELEMENT_DESC ied[] =
{
{
"Position",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0 },
{
"Color",0,DXGI_FORMAT_R8G8B8A8_UNORM,0,8u,D3D11_INPUT_PER_VERTEX_DATA,0 },
};
To avoid vertex repetition , In this section, we introduce index buffer:
// create index buffer
const unsigned short indices[] =
{
0,1,2,
0,2,3,
0,4,1,
2,1,5,
};
wrl::ComPtr<ID3D11Buffer> pIndexBuffer;
D3D11_BUFFER_DESC ibd = {
};
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
ibd.Usage = D3D11_USAGE_DEFAULT;
ibd.CPUAccessFlags = 0u;
ibd.MiscFlags = 0u;
ibd.ByteWidth = sizeof( indices );
ibd.StructureByteStride = sizeof( unsigned short );
D3D11_SUBRESOURCE_DATA isd = {
};
isd.pSysMem = indices;
GFX_THROW_INFO( pDevice->CreateBuffer( &ibd,&isd,&pIndexBuffer ) );
// bind index buffer
pContext->IASetIndexBuffer( pIndexBuffer.Get(),DXGI_FORMAT_R16_UINT,0u );
drawcall That is, from the original pContext->Draw((UINT)std::size(vertices), 0u)
Change it to :pContext->DrawIndexed( (UINT)std::size( indices ),0u,0u )
At this point, we can output a beautiful hexagon :
Test viewport :
Our viewport code is like this
// configure viewport
D3D11_VIEWPORT vp;
vp.Width = 800;
vp.Height = 600;
vp.MinDepth = 0;
vp.MaxDepth = 1;
vp.TopLeftX = 0;
vp.TopLeftY = 0;
pContext->RSSetViewports(1u, &vp);
Let's change it :
vp.Width = 400;
vp.Height = 300;
Then you will find that it is only rendered in the upper left corner ( Full screen 800 * 600, Viewport size 400 * 300, The upper left corner of the viewport is specified as 0, 0 ):
The size of the full screen is larger than ours App Frame :
App::App()
:
wnd(800, 600, "The Donkey Fart Box")
{
}
19: Constant cache
For every move , Of course we can be in CPU Calculate it and send it to GPU, however , For thousands of points , Doing so requires thousands of data to be transmitted each time , Take up a lot of bandwidth . So we usually use dynamic constant data , Move every time by changing . That is, changing thousands of points is better than changing only 16 Number of transformation matrix .
In the test code, we transmit every frame , But real engines don't do this , That's just test code .
The practice is to use a method called Shader constant cache (shader constant buffer) Things that are . This will allow us to bind some constant values to the shader stage , Available for every call to this shader .
The code is as follows :
// create constant buffer for transformation matrix
struct ConstantBuffer
{
struct
{
float element[4][4];
} transformation;
};
const ConstantBuffer cb =
{
{
(3.0f / 4.0f) * std::cos(angle), std::sin(angle), 0.0f, 0.0f,
(3.0f / 4.0f) * -std::sin(angle), std::cos(angle), 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
}
};
wrl::ComPtr<ID3D11Buffer> pConstantBuffer;
D3D11_BUFFER_DESC cbd;
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
cbd.MiscFlags = 0u;
cbd.ByteWidth = sizeof(cb);
cbd.StructureByteStride = 0u;
D3D11_SUBRESOURCE_DATA csd = {
};
csd.pSysMem = &cb;
GFX_THROW_INFO(pDevice->CreateBuffer(&cbd, &csd, &pConstantBuffer));
It's basically about Z Matrix of axis rotation .
(angle It's the parameter we passed in void Graphics::DrawTestTriangle(float angle)
)
Here we put USAGE Set as dynamic , This will usually be the way you use constant caching :
( Local documents :file:///E:/%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%96%99/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6%E5%AD%A6%E4%B9%A0%E8%B5%84%E6%96%99/D3D11/%E6%96%B0%E5%BB%BA%E6%96%87%E4%BB%B6%E5%A4%B9/DX11+%E4%B8%AD%E8%AF%91.pdf)
notes : Reference documents https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-subresources, One buffer It's a simple one subresource ,Textures It's a little complicated .
Create a buffer Usually three steps :
And then bind it :
// bind constant buffer to vertex shader
pContext->VSSetConstantBuffers(0u, 1u, pConstantBuffer.GetAddressOf());
The last is shader Use it inside :
VS First define ,cbuffer yes hlsl keyword :
cbuffer CBuf
{
matrix transform;
};
Then pay attention to D3D Matrix multiplication in is right multiplication , The vector is on the left and the matrix is on the right :
VSOut main( float2 pos : Position,float3 color : Color )
{
VSOut vso;
vso.pos = mul(float4(pos.x,pos.y,0.0f,1.0f), transform);
vso.color = color;
return vso;
}
There's another little detail :CPU The two-dimensional array in is stored in rows (row-major ordering), but GPU(HLSL in ) Is stored by column (column-major ordering). So we should do a wave of transpose when it comes in , But we can also use another method , Just tell HLSL , This matrix is arranged in rows :
But the disadvantage is that GPU Multiplying by the row main matrix is slightly slower than the column main matrix . But it's easier for us . In the future, we will actually be GPU Transpose the matrix before getting it . But now we do it .
Assembly line
Reference resources :https://zhuanlan.zhihu.com/p/259535556
Row main order matrix and column main order matrix
Reference resources :https://www.cnblogs.com/X-Jun/p/9808727.html
In order to maximize efficiency , For the matrix stored in the column main order, we want “ Take the right ”( That is, the matrix is on the right , In this way, you can just take out a continuous piece of space ), For the matrix stored in the row main order, we want “ Left multiplication ”.
边栏推荐
- Actual combat | use composite material 3 in application
- Game theory
- .Net 应用考虑x64生成
- Interpretation of the champion scheme of CVPR 2020 night target detection challenge
- Variable cannot have type 'void'
- 中国主要城市人均存款出炉,你达标了吗?
- 深入JS中几种数据类型的解构赋值细节
- Unity animation day05
- [book club issue 13] ffmpeg common methods for viewing media information and processing audio and video files
- Unity脚本API—Transform 变换
猜你喜欢
这几年爆火的智能物联网(AIoT),到底前景如何?
PR FAQ: how to set PR vertical screen sequence?
干货 | fMRI标准报告指南新鲜出炉啦,快来涨知识吧
A trap used by combinelatest and a debouncetime based solution
压力、焦虑还是抑郁? 正确诊断再治疗
[tutorial] yolov5_ DeepSort_ The whole process of pytoch target tracking and detection
Common API day03 of unity script
Hidden communication tunnel technology: intranet penetration tool NPS
error: ‘connect‘ was not declared in this scope connect(timer, SIGNAL(timeout()), this, SLOT(up
.Net 应用考虑x64生成
随机推荐
AI system content recommendation issue 24
Selenium element interaction
时钟轮在 RPC 中的应用
[book club issue 13] packaging format and coding format of audio files
QT graphical view frame: element movement
[Previous line repeated 995 more times]RecursionError: maximum recursion depth exceeded
The four most common errors when using pytorch
Preliminary practice of niuke.com (10)
在芯片高度集成的今天,绝大多数都是CMOS器件
Unity脚本介绍 Day01
MySQL~MySQL给已有的数据表添加自增ID
C language: implementation of daffodil number function
Interpretation of the champion scheme of CVPR 2020 night target detection challenge
PR FAQ: how to set PR vertical screen sequence?
The content of the source code crawled by the crawler is inconsistent with that in the developer mode
Understand the context in go language in an article
Filtered off site request to
An article learns variables in go language
Rearrange array
Understand Alibaba cloud's secret weapon "dragon architecture" in the article "science popularization talent"