Skip to content

Commit 1ef4167

Browse files
committed
Extend first abstractons
1 parent 002f9ba commit 1ef4167

File tree

1 file changed

+381
-4
lines changed

1 file changed

+381
-4
lines changed

docs/1-introduction/1-1-getting-started/1-1-4-abstractions.md

Lines changed: 381 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,392 @@ struct VertexPositionColor
9797
};
9898
```
9999

100+
This file will contain all future vertex types as well.
101+
100102
## Pipeline & DeviceContext
101103

102-
!!! error "Explain Pipeline"
104+
### Pipeline
105+
106+
What is a pipeline? It's just a immutable container of various things. It describes all state for the current thing you want to render or compute. All Vertex and Pixel or Compute shaders we might require for that render task to complete, the type of primitives we want to draw and how they are setup. Since we are coming from the Hello Triangle chapter our pipeline will not contain much, but it will grow in complexity further down the chapter road.
107+
108+
### PipelineFactory
109+
110+
`PipelineFactory` will handle the creation of `Pipeline` for us, which includes loading and compiling shaders, figuring out the right input layout by the given vertex type, for now.
111+
112+
### DeviceContext
113+
114+
`DeviceContext` is an abstraction over DX's native ID3D11DeviceContext, which has plenty of methods you need to call in order to get your triangles on screen. `DeviceContext` will know what to call specifically and handles that for you to keep the actual business logic "clean".
115+
116+
### Migration to Pipeline/PipelineFactory/DeviceContext
117+
118+
Let's create a `Pipeline.hpp` and add the following
119+
120+
```cpp
121+
#pragma once
122+
#include "Definitions.hpp"
123+
124+
#include <d3d11_2.h>
125+
126+
#include <cstdint>
127+
128+
class Pipeline
129+
{
130+
public:
131+
friend class PipelineFactory;
132+
friend class DeviceContext;
133+
134+
void SetViewport(
135+
float left,
136+
float top,
137+
float width,
138+
float height);
139+
140+
private:
141+
WRL::ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
142+
WRL::ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
143+
WRL::ComPtr<ID3D11InputLayout> _inputLayout = nullptr;
144+
D3D11_PRIMITIVE_TOPOLOGY _primitiveTopology = {};
145+
uint32_t _vertexSize = 0;
146+
D3D11_VIEWPORT _viewport = {};
147+
};
148+
```
149+
150+
`Pipeline` is supposed to be an immutable object, there fore all relevant fields are `private`, so that you cant accidentally set them from outside. Only `PipelineFactory` will be able to access those fields, as its creating them. Also `DeviceContext` can access them too, as it needs these to set the actual state/values.
151+
152+
We also create `Pipeline.cpp` with the following content:
153+
154+
```cpp
155+
#include "Pipeline.hpp"
156+
157+
void Pipeline::SetViewport(
158+
const float left,
159+
const float top,
160+
const float width,
161+
const float height)
162+
{
163+
_viewport.TopLeftX = left;
164+
_viewport.TopLeftY = top;
165+
_viewport.Width = width;
166+
_viewport.Height = height;
167+
_viewport.MinDepth = 0.0f;
168+
_viewport.MaxDepth = 1.0f;
169+
}
170+
```
171+
172+
Its quite empty for now.
173+
174+
Lets move on to `PipelineFactory`. Create a new file `PipelineFactory.hpp` and add the following content:
175+
176+
```cpp
177+
#pragma once
178+
179+
#include "Definitions.hpp"
180+
#include "VertexType.hpp"
181+
#include "Pipeline.hpp"
182+
183+
#include <d3d11_2.h>
184+
185+
#include <unordered_map>
186+
#include <string>
187+
#include <memory>
188+
189+
struct PipelineDescriptor
190+
{
191+
std::wstring VertexFilePath;
192+
std::wstring PixelFilePath;
193+
VertexType VertexType;
194+
};
195+
196+
class PipelineFactory
197+
{
198+
public:
199+
PipelineFactory(const WRL::ComPtr<ID3D11Device>& device);
200+
201+
bool CreatePipeline(
202+
const PipelineDescriptor& settings,
203+
std::unique_ptr<Pipeline>& pipeline);
204+
205+
private:
206+
static size_t GetLayoutByteSize(VertexType vertexType);
207+
208+
[[nodiscard]] WRL::ComPtr<ID3D11VertexShader> CreateVertexShader(
209+
const std::wstring& filePath,
210+
WRL::ComPtr<ID3DBlob>& vertexShaderBlob) const;
211+
[[nodiscard]] WRL::ComPtr<ID3D11PixelShader> CreatePixelShader(const std::wstring& filePath) const;
212+
213+
bool CreateInputLayout(
214+
VertexType layoutInfo,
215+
const WRL::ComPtr<ID3DBlob>& vertexBlob,
216+
WRL::ComPtr<ID3D11InputLayout>& inputLayout);
103217
104-
!!! error "Explain PipelineFactory"
218+
bool CompileShader(
219+
const std::wstring& filePath,
220+
const std::string& entryPoint,
221+
const std::string& profile,
222+
WRL::ComPtr<ID3DBlob>& shaderBlob) const;
105223
106-
!!! error "Explain DeviceContext"
224+
WRL::ComPtr<ID3D11Device> _device = nullptr;
225+
std::unordered_map<VertexType, std::vector<D3D11_INPUT_ELEMENT_DESC>> _layoutMap;
226+
};
227+
```
228+
229+
You can remove the methods from HelloTriangleApplication.hpp and HelloTriangleApplication.cpp since we are now using a separate class.
230+
231+
We also need `PipelineFactory.cpp` with the following content.
232+
233+
```cpp
234+
size_t PipelineFactory::GetLayoutByteSize(const VertexType vertexType)
235+
{
236+
switch (vertexType)
237+
{
238+
case VertexType::PositionColor: return sizeof(VertexPositionColor);
239+
}
240+
return 0;
241+
}
242+
243+
PipelineFactory::PipelineFactory(const WRL::ComPtr<ID3D11Device>& device)
244+
{
245+
_device = device;
246+
247+
_layoutMap[VertexType::PositionColor] =
248+
{
249+
{
250+
{
251+
"POSITION",
252+
0,
253+
DXGI_FORMAT::DXGI_FORMAT_R32G32B32_FLOAT,
254+
0,
255+
offsetof(VertexPositionColor, position),
256+
D3D11_INPUT_CLASSIFICATION::D3D11_INPUT_PER_VERTEX_DATA,
257+
0
258+
},
259+
{
260+
"COLOR",
261+
0,
262+
DXGI_FORMAT::DXGI_FORMAT_R32G32B32_FLOAT,
263+
0,
264+
offsetof(VertexPositionColor, color),
265+
D3D11_INPUT_CLASSIFICATION::D3D11_INPUT_PER_VERTEX_DATA,
266+
0
267+
},
268+
}
269+
};
270+
}
271+
272+
bool PipelineFactory::CreatePipeline(
273+
const PipelineDescriptor& settings,
274+
std::unique_ptr<Pipeline>& pipeline)
275+
{
276+
WRL::ComPtr<ID3DBlob> vertexShaderBlob;
277+
pipeline = std::make_unique<Pipeline>();
278+
pipeline->_vertexShader = CreateVertexShader(settings.VertexFilePath, vertexShaderBlob);
279+
pipeline->_pixelShader = CreatePixelShader(settings.PixelFilePath);
280+
if (!CreateInputLayout(settings.VertexType, vertexShaderBlob, pipeline->_inputLayout))
281+
{
282+
return false;
283+
}
284+
pipeline->_primitiveTopology = D3D11_PRIMITIVE_TOPOLOGY::D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
285+
pipeline->_vertexSize = static_cast<uint32_t>(GetLayoutByteSize(settings.VertexType));
286+
return true;
287+
}
288+
289+
bool PipelineFactory::CompileShader(
290+
const std::wstring& filePath,
291+
const std::string& entryPoint,
292+
const std::string& profile,
293+
WRL::ComPtr<ID3DBlob>& shaderBlob) const
294+
{
295+
constexpr uint32_t compileFlags = D3DCOMPILE_ENABLE_STRICTNESS;
296+
297+
WRL::ComPtr<ID3DBlob> tempShaderBlob = nullptr;
298+
WRL::ComPtr<ID3DBlob> errorBlob = nullptr;
299+
if (FAILED(D3DCompileFromFile(
300+
filePath.data(),
301+
nullptr,
302+
D3D_COMPILE_STANDARD_FILE_INCLUDE,
303+
entryPoint.data(),
304+
profile.data(),
305+
compileFlags,
306+
0,
307+
&tempShaderBlob,
308+
&errorBlob)))
309+
{
310+
std::cout << "D3D11: Failed to read shader from file\n";
311+
if (errorBlob != nullptr)
312+
{
313+
std::cout << "D3D11: With message: " << static_cast<const char*>(errorBlob->GetBufferPointer()) << "\n";
314+
}
315+
316+
return false;
317+
}
318+
319+
shaderBlob = std::move(tempShaderBlob);
320+
return true;
321+
}
322+
323+
WRL::ComPtr<ID3D11VertexShader> PipelineFactory::CreateVertexShader(
324+
const std::wstring& filePath,
325+
WRL::ComPtr<ID3DBlob>& vertexShaderBlob) const
326+
{
327+
if (!CompileShader(filePath, "Main", "vs_5_0", vertexShaderBlob))
328+
{
329+
return nullptr;
330+
}
331+
332+
WRL::ComPtr<ID3D11VertexShader> vertexShader;
333+
if (FAILED(_device->CreateVertexShader(
334+
vertexShaderBlob->GetBufferPointer(),
335+
vertexShaderBlob->GetBufferSize(),
336+
nullptr,
337+
&vertexShader)))
338+
{
339+
std::cout << "D3D11: Failed to compile vertex shader\n";
340+
return nullptr;
341+
}
342+
343+
return vertexShader;
344+
}
345+
346+
WRL::ComPtr<ID3D11PixelShader> PipelineFactory::CreatePixelShader(const std::wstring& filePath) const
347+
{
348+
WRL::ComPtr<ID3DBlob> pixelShaderBlob = nullptr;
349+
if (!CompileShader(filePath, "Main", "ps_5_0", pixelShaderBlob))
350+
{
351+
return nullptr;
352+
}
353+
354+
WRL::ComPtr<ID3D11PixelShader> pixelShader;
355+
if (FAILED(_device->CreatePixelShader(
356+
pixelShaderBlob->GetBufferPointer(),
357+
pixelShaderBlob->GetBufferSize(),
358+
nullptr,
359+
&pixelShader)))
360+
{
361+
std::cout << "D3D11: Failed to compile pixel shader\n";
362+
return nullptr;
363+
}
364+
365+
return pixelShader;
366+
}
367+
368+
bool PipelineFactory::CreateInputLayout(
369+
const VertexType layoutInfo,
370+
const WRL::ComPtr<ID3DBlob>& vertexBlob,
371+
WRL::ComPtr<ID3D11InputLayout>& inputLayout)
372+
{
373+
const std::vector<D3D11_INPUT_ELEMENT_DESC> inputLayoutDesc = _layoutMap[layoutInfo];
374+
if (FAILED(_device->CreateInputLayout(
375+
inputLayoutDesc.data(),
376+
static_cast<uint32_t>(inputLayoutDesc.size()),
377+
vertexBlob->GetBufferPointer(),
378+
vertexBlob->GetBufferSize(),
379+
&inputLayout)))
380+
{
381+
std::cout << "D3D11: Failed to create the input layout";
382+
return false;
383+
}
384+
return true;
385+
}
386+
```
387+
388+
Most of the methods are now private and cannot be called from outside `PipelineFactory` and that is good. There is no need for somebody or something else to
389+
create arbitrary shaders or other pipeline relevant things.
390+
391+
On to `DeviceContext`. Create a new `DeviceContext.hpp` with the following content
392+
393+
```cpp
394+
#pragma once
395+
396+
#include "Definitions.hpp"
397+
398+
#include <d3d11_2.h>
399+
400+
#include <cstdint>
401+
402+
class Pipeline;
403+
404+
class DeviceContext
405+
{
406+
public:
407+
DeviceContext(WRL::ComPtr<ID3D11DeviceContext>&& deviceContext);
408+
409+
void Clear(
410+
ID3D11RenderTargetView* renderTarget,
411+
float clearColor[4]) const;
412+
void SetPipeline(const Pipeline* pipeline);
413+
void SetVertexBuffer(
414+
ID3D11Buffer* triangleVertices,
415+
uint32_t vertexOffset);
416+
void Draw() const;
417+
void Flush() const;
418+
419+
private:
420+
uint32_t _drawVertices;
421+
const Pipeline* _activePipeline;
422+
WRL::ComPtr<ID3D11DeviceContext> _deviceContext;
423+
};
424+
```
425+
426+
And its implementation file `DeviceContext.cpp` with the following content
427+
428+
```cpp
429+
#include "DeviceContext.hpp"
430+
#include "Pipeline.hpp"
431+
432+
#include <utility>
433+
434+
DeviceContext::DeviceContext(WRL::ComPtr<ID3D11DeviceContext>&& deviceContext)
435+
{
436+
_deviceContext = std::move(deviceContext);
437+
_activePipeline = nullptr;
438+
_drawVertices = 0;
439+
}
440+
441+
void DeviceContext::Clear(
442+
ID3D11RenderTargetView* renderTarget,
443+
float clearColor[4]) const
444+
{
445+
_deviceContext->ClearRenderTargetView(renderTarget, clearColor);
446+
_deviceContext->OMSetRenderTargets(1, &renderTarget, nullptr);
447+
}
448+
449+
void DeviceContext::SetPipeline(const Pipeline* pipeline)
450+
{
451+
_activePipeline = pipeline;
452+
_deviceContext->IASetInputLayout(pipeline->_inputLayout.Get());
453+
_deviceContext->IASetPrimitiveTopology(pipeline->_primitiveTopology);
454+
_deviceContext->VSSetShader(pipeline->_vertexShader.Get(), nullptr, 0);
455+
_deviceContext->PSSetShader(pipeline->_pixelShader.Get(), nullptr, 0);
456+
_deviceContext->RSSetViewports(1, &pipeline->_viewport);
457+
}
458+
459+
void DeviceContext::SetVertexBuffer(
460+
ID3D11Buffer* triangleVertices,
461+
uint32_t vertexOffset)
462+
{
463+
D3D11_BUFFER_DESC description = {};
464+
triangleVertices->GetDesc(&description);
465+
_deviceContext->IASetVertexBuffers(
466+
0,
467+
1,
468+
&triangleVertices,
469+
&_activePipeline->_vertexSize,
470+
&vertexOffset);
471+
_drawVertices = description.ByteWidth / _activePipeline->_vertexSize;
472+
}
473+
474+
void DeviceContext::Draw() const
475+
{
476+
_deviceContext->Draw(_drawVertices, 0);
477+
}
478+
479+
void DeviceContext::Flush() const
480+
{
481+
_deviceContext->Flush();
482+
}
483+
```
107484
108-
!!! error "Migrate from current hello-triangle to hello-triangle-refactored"
485+
As you can see, `DeviceContext` will make sure all the state is set properly.
109486
110487
[Project on GitHub](https://github.com/GraphicsProgramming/learnd3d11/tree/main/src/Cpp/1-getting-started/1-1-3-HelloTriangle-Refactored)
111488

0 commit comments

Comments
 (0)