388 lines
No EOL
15 KiB
C++
388 lines
No EOL
15 KiB
C++
#include "Win32DxWindowInterface.h"
|
|
|
|
#include "Win32DxInterface.h"
|
|
#include "Win32Window.h"
|
|
#include "Window.h"
|
|
#include "Widget.h"
|
|
|
|
#include <dxgi.h>
|
|
#include <dxgi1_6.h>
|
|
#include <d3dcompiler.h>
|
|
#include <d3d12sdklayers.h>
|
|
|
|
#include <d3d11.h>
|
|
#include <d3d11on12.h>
|
|
#include <d2d1_3.h>
|
|
#include <d2d1_1.h>
|
|
|
|
#include <dwrite.h>
|
|
|
|
Win32DxWindowInterface::Win32DxWindowInterface(Win32DxInterface* dxInterface)
|
|
: mDxInterface(dxInterface)
|
|
{
|
|
|
|
}
|
|
|
|
Win32DxWindowInterface::~Win32DxWindowInterface()
|
|
{
|
|
|
|
}
|
|
|
|
void Win32DxWindowInterface::destroyWindow()
|
|
{
|
|
waitForPreviousFrame();
|
|
|
|
::CloseHandle(mFenceEvent);
|
|
}
|
|
|
|
void Win32DxWindowInterface::onRender()
|
|
{
|
|
// Record all the commands we need to render the scene into the command list.
|
|
populateCommandList();
|
|
|
|
// Execute the command list.
|
|
ID3D12CommandList* ppCommandLists[] = { mCommandList.Get() };
|
|
mDxInterface->getCommandQueue()->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
|
|
|
|
renderD2d();
|
|
|
|
// Present the frame.
|
|
mSwapChain->Present(1, 0);
|
|
|
|
waitForPreviousFrame();
|
|
}
|
|
|
|
void Win32DxWindowInterface::populateCommandList()
|
|
{
|
|
// Command list allocators can only be reset when the associated
|
|
// command lists have finished execution on the GPU; apps should use
|
|
// fences to determine GPU execution progress.
|
|
mCommandAllocator->Reset();
|
|
|
|
// However, when ExecuteCommandList() is called on a particular command
|
|
// list, that command list can then be reset at any time and must be before
|
|
// re-recording.
|
|
mCommandList->Reset(mCommandAllocator.Get(), mPipelineState.Get());
|
|
|
|
// Set necessary state.
|
|
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
|
|
mCommandList->RSSetViewports(1, &mViewport);
|
|
mCommandList->RSSetScissorRects(1, &mScissorRect);
|
|
|
|
// Indicate that the back buffer will be used as a render target.
|
|
auto barrier = CD3DX12_RESOURCE_BARRIER::Transition(mRenderTargets[mFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
|
|
mCommandList->ResourceBarrier(1, &barrier);
|
|
|
|
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart(), static_cast<INT>(mFrameIndex), mRtvDescriptorSize);
|
|
mCommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
|
|
|
|
// Record commands.
|
|
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
|
|
mCommandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
|
|
mCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
mCommandList->IASetVertexBuffers(0, 1, &mVertexBufferView);
|
|
mCommandList->DrawInstanced(3, 1, 0, 0);
|
|
|
|
// Indicate that the back buffer will now be used to present.
|
|
//barrier = CD3DX12_RESOURCE_BARRIER::Transition(mRenderTargets[mFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
|
|
//mCommandList->ResourceBarrier(1, &barrier);
|
|
|
|
mCommandList->Close();
|
|
}
|
|
|
|
bool Win32DxWindowInterface::initialize(mt::Window* window)
|
|
{
|
|
if (mInitialized)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const auto width = window->getWidth();
|
|
const auto height = window->getHeight();
|
|
mViewport = CD3DX12_VIEWPORT(0.0f, 0.0f, width, height);
|
|
mScissorRect = CD3DX12_RECT(0, 0, width, height);
|
|
|
|
loadPipeline(window);
|
|
|
|
loadAssets();
|
|
|
|
return true;
|
|
}
|
|
|
|
void Win32DxWindowInterface::loadPipeline(mt::Window* window)
|
|
{
|
|
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
|
|
swapChainDesc.BufferCount = FrameCount;
|
|
|
|
const auto width = window->getWidth();
|
|
const auto height = window->getHeight();
|
|
swapChainDesc.Width = width;
|
|
swapChainDesc.Height = height;
|
|
|
|
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
|
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
|
|
swapChainDesc.SampleDesc.Count = 1;
|
|
|
|
const auto hwnd = dynamic_cast<Win32Window*>(window->getPlatformWindow())->getHandle();
|
|
|
|
Microsoft::WRL::ComPtr<IDXGISwapChain1> swapChain;
|
|
mDxInterface->getFactory()->CreateSwapChainForHwnd(
|
|
mDxInterface->getCommandQueue(),
|
|
hwnd,
|
|
&swapChainDesc,
|
|
nullptr,
|
|
nullptr,
|
|
&swapChain
|
|
);
|
|
mDxInterface->getFactory()->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
|
|
swapChain.As(&mSwapChain);
|
|
auto mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();
|
|
|
|
// Query the desktop's dpi settings, which will be used to create
|
|
// D2D's render targets.
|
|
float dpiX{ 0.0 };
|
|
float dpiY{ 0.0 };
|
|
mDxInterface->getD2dFactory()->GetDesktopDpi(&dpiX, &dpiY);
|
|
|
|
D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1(
|
|
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
|
|
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
|
|
dpiX,
|
|
dpiY
|
|
);
|
|
|
|
// Create descriptor heaps.
|
|
{
|
|
// Describe and create a render target view (RTV) descriptor heap.
|
|
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
|
|
rtvHeapDesc.NumDescriptors = FrameCount;
|
|
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
|
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
|
|
mDxInterface->getDevice()->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&mRtvHeap));
|
|
|
|
mRtvDescriptorSize = mDxInterface->getDevice()->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
|
}
|
|
|
|
// Create frame resources.
|
|
{
|
|
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
|
|
|
|
// Create a RTV for each frame.
|
|
for (UINT n = 0; n < FrameCount; n++)
|
|
{
|
|
mSwapChain->GetBuffer(n, IID_PPV_ARGS(&mRenderTargets[n]));
|
|
mDxInterface->getDevice()->CreateRenderTargetView(mRenderTargets[n].Get(), nullptr, rtvHandle);
|
|
|
|
D3D11_RESOURCE_FLAGS d3d11Flags = { D3D11_BIND_RENDER_TARGET };
|
|
mDxInterface->get11On12Device()->CreateWrappedResource(
|
|
mRenderTargets[n].Get(),
|
|
&d3d11Flags,
|
|
D3D12_RESOURCE_STATE_RENDER_TARGET,
|
|
D3D12_RESOURCE_STATE_PRESENT,
|
|
IID_PPV_ARGS(&mWrappedBackBuffers[n])
|
|
);
|
|
|
|
// Create a render target for D2D to draw directly to this back buffer.
|
|
Microsoft::WRL::ComPtr<IDXGISurface> surface;
|
|
mWrappedBackBuffers[n].As(&surface);
|
|
mDxInterface->getD2dContext()->CreateBitmapFromDxgiSurface(
|
|
surface.Get(),
|
|
&bitmapProperties,
|
|
&mD2dRenderTargets[n]
|
|
);
|
|
|
|
rtvHandle.Offset(1, mRtvDescriptorSize);
|
|
}
|
|
}
|
|
|
|
mDxInterface->getDevice()->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocator));
|
|
}
|
|
|
|
void Win32DxWindowInterface::loadAssets()
|
|
{
|
|
// Create an empty root signature.
|
|
{
|
|
CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc;
|
|
rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
|
|
|
|
Microsoft::WRL::ComPtr<ID3DBlob> signature;
|
|
Microsoft::WRL::ComPtr<ID3DBlob> error;
|
|
D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
|
|
mDxInterface->getDevice()->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&mRootSignature));
|
|
}
|
|
|
|
// Create the pipeline state, which includes compiling and loading shaders.
|
|
{
|
|
Microsoft::WRL::ComPtr<ID3DBlob> vertexShader;
|
|
Microsoft::WRL::ComPtr<ID3DBlob> pixelShader;
|
|
|
|
#if defined(_DEBUG)
|
|
// Enable better shader debugging with the graphics debugging tools.
|
|
// UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
|
|
UINT compileFlags = 0;
|
|
#else
|
|
UINT compileFlags = 0;
|
|
#endif
|
|
|
|
auto shader_path = std::filesystem::path(__FILE__).parent_path() / "shaders.hlsl";
|
|
|
|
D3DCompileFromFile(shader_path.c_str(), nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr);
|
|
D3DCompileFromFile(shader_path.c_str(), nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr);
|
|
|
|
// Define the vertex input layout.
|
|
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
|
|
{
|
|
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
|
|
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
|
|
};
|
|
|
|
// Describe and create the graphics pipeline state object (PSO).
|
|
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
|
|
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
|
|
psoDesc.pRootSignature = mRootSignature.Get();
|
|
psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get());
|
|
psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get());
|
|
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
|
|
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
|
|
psoDesc.DepthStencilState.DepthEnable = FALSE;
|
|
psoDesc.DepthStencilState.StencilEnable = FALSE;
|
|
psoDesc.SampleMask = UINT_MAX;
|
|
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
|
psoDesc.NumRenderTargets = 1;
|
|
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
|
|
psoDesc.SampleDesc.Count = 1;
|
|
mDxInterface->getDevice()->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPipelineState));
|
|
}
|
|
|
|
// Create the command list.
|
|
mDxInterface->getDevice()->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocator.Get(), mPipelineState.Get(), IID_PPV_ARGS(&mCommandList));
|
|
|
|
// Create D2D/DWrite objects for rendering text.
|
|
{
|
|
mDxInterface->getD2dContext()->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &mTextBrush);
|
|
mDxInterface->getDirectWriteFactory()->CreateTextFormat(
|
|
L"Verdana",
|
|
NULL,
|
|
DWRITE_FONT_WEIGHT_NORMAL,
|
|
DWRITE_FONT_STYLE_NORMAL,
|
|
DWRITE_FONT_STRETCH_NORMAL,
|
|
50,
|
|
L"en-us",
|
|
&mTextFormat
|
|
);
|
|
mTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
|
|
mTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
|
|
}
|
|
|
|
|
|
// Command lists are created in the recording state, but there is nothing
|
|
// to record yet. The main loop expects it to be closed, so close it now.
|
|
mCommandList->Close();
|
|
|
|
// Create the vertex buffer.
|
|
{
|
|
// Define the geometry for a triangle.
|
|
const float aspectRatio = 1.0;
|
|
Vertex triangleVertices[] =
|
|
{
|
|
{ { 0.0f, 0.25f * aspectRatio, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
|
|
{ { 0.25f, -0.25f * aspectRatio, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
|
|
{ { -0.25f, -0.25f * aspectRatio, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
|
|
};
|
|
|
|
const UINT vertexBufferSize = sizeof(triangleVertices);
|
|
|
|
// Note: using upload heaps to transfer static data like vert buffers is not
|
|
// recommended. Every time the GPU needs it, the upload heap will be marshalled
|
|
// over. Please read up on Default Heap usage. An upload heap is used here for
|
|
// code simplicity and because there are very few verts to actually transfer.
|
|
CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
|
|
auto desc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
|
|
mDxInterface->getDevice()->CreateCommittedResource(
|
|
&heapProps,
|
|
D3D12_HEAP_FLAG_NONE,
|
|
&desc,
|
|
D3D12_RESOURCE_STATE_GENERIC_READ,
|
|
nullptr,
|
|
IID_PPV_ARGS(&mVertexBuffer));
|
|
|
|
// Copy the triangle data to the vertex buffer.
|
|
UINT8* pVertexDataBegin;
|
|
CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU.
|
|
mVertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin));
|
|
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
|
|
mVertexBuffer->Unmap(0, nullptr);
|
|
|
|
// Initialize the vertex buffer view.
|
|
mVertexBufferView.BufferLocation = mVertexBuffer->GetGPUVirtualAddress();
|
|
mVertexBufferView.StrideInBytes = sizeof(Vertex);
|
|
mVertexBufferView.SizeInBytes = vertexBufferSize;
|
|
}
|
|
|
|
// Create synchronization objects and wait until assets have been uploaded to the GPU.
|
|
{
|
|
mDxInterface->getDevice()->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence));
|
|
mFenceValue = 1;
|
|
|
|
// Create an event handle to use for frame synchronization.
|
|
mFenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
|
if (mFenceEvent == nullptr)
|
|
{
|
|
//ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
|
|
}
|
|
|
|
// Wait for the command list to execute; we are reusing the same command
|
|
// list in our main loop but for now, we just want to wait for setup to
|
|
// complete before continuing.
|
|
waitForPreviousFrame();
|
|
}
|
|
}
|
|
|
|
void Win32DxWindowInterface::renderD2d()
|
|
{
|
|
D2D1_SIZE_F rtSize = mD2dRenderTargets[mFrameIndex]->GetSize();
|
|
D2D1_RECT_F textRect = D2D1::RectF(0, 0, rtSize.width, rtSize.height);
|
|
static const WCHAR text[] = L"11On12";
|
|
|
|
// Acquire our wrapped render target resource for the current back buffer.
|
|
mDxInterface->get11On12Device()->AcquireWrappedResources(mWrappedBackBuffers[mFrameIndex].GetAddressOf(), 1);
|
|
|
|
// Render text directly to the back buffer.
|
|
mDxInterface->getD2dContext()->SetTarget(mD2dRenderTargets[mFrameIndex].Get());
|
|
mDxInterface->getD2dContext()->BeginDraw();
|
|
mDxInterface->getD2dContext()->SetTransform(D2D1::Matrix3x2F::Identity());
|
|
mDxInterface->getD2dContext()->DrawText(
|
|
text,
|
|
_countof(text) - 1,
|
|
mTextFormat.Get(),
|
|
&textRect,
|
|
mTextBrush.Get()
|
|
);
|
|
mDxInterface->getD2dContext()->EndDraw();
|
|
|
|
// Release our wrapped render target resource. Releasing
|
|
// transitions the back buffer resource to the state specified
|
|
// as the OutState when the wrapped resource was created.
|
|
mDxInterface->get11On12Device()->ReleaseWrappedResources(mWrappedBackBuffers[mFrameIndex].GetAddressOf(), 1);
|
|
|
|
// Flush to submit the 11 command list to the shared command queue.
|
|
mDxInterface->getD3d11DeviceContext()->Flush();
|
|
}
|
|
|
|
void Win32DxWindowInterface::waitForPreviousFrame()
|
|
{
|
|
// Signal and increment the fence value.
|
|
const UINT64 fence = mFenceValue;
|
|
mDxInterface->getCommandQueue()->Signal(mFence.Get(), fence);
|
|
mFenceValue++;
|
|
|
|
// Wait until the previous frame is finished.
|
|
if (mFence->GetCompletedValue() < fence)
|
|
{
|
|
mFence->SetEventOnCompletion(fence, mFenceEvent);
|
|
::WaitForSingleObject(mFenceEvent, INFINITE);
|
|
}
|
|
|
|
mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();
|
|
} |