// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) // SPDX-FileCopyrightText: 2021 Jorrit Rouwe // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #ifdef _DEBUG #include #endif static Renderer *sRenderer = nullptr; struct VertexShaderConstantBuffer { Mat44 mView; Mat44 mProjection; Mat44 mLightView; Mat44 mLightProjection; }; struct PixelShaderConstantBuffer { Vec4 mCameraPos; Vec4 mLightPos; }; //-------------------------------------------------------------------------------------- // Called every time the application receives a message //-------------------------------------------------------------------------------------- static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; switch (message) { case WM_PAINT: BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); break; case WM_SIZE: if (sRenderer != nullptr) sRenderer->OnWindowResize(); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } Renderer::~Renderer() { // Ensure that the GPU is no longer referencing resources that are about to be cleaned up by the destructor. WaitForGpu(); // Don't add more stuff to the delay reference list mIsExiting = true; CloseHandle(mFenceEvent); } void Renderer::WaitForGpu() { // Schedule a Signal command in the queue UINT64 current_fence_value = mFenceValues[mFrameIndex]; FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value)); // Wait until the fence has been processed FatalErrorIfFailed(mFence->SetEventOnCompletion(current_fence_value, mFenceEvent)); WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE); // Increment the fence value for all frames for (uint n = 0; n < cFrameCount; ++n) mFenceValues[n] = current_fence_value + 1; // Release all used resources for (Array> &list : mDelayReleased) list.clear(); // Anything that's not used yet can be removed, delayed objects are now available mResourceCache.clear(); mDelayCached[mFrameIndex].swap(mResourceCache); } void Renderer::CreateRenterTargets() { // Create render targets and views for (uint n = 0; n < cFrameCount; ++n) { mRenderTargetViews[n] = mRTVHeap.Allocate(); FatalErrorIfFailed(mSwapChain->GetBuffer(n, IID_PPV_ARGS(&mRenderTargets[n]))); mDevice->CreateRenderTargetView(mRenderTargets[n].Get(), nullptr, mRenderTargetViews[n]); } } void Renderer::CreateDepthBuffer() { // Free any previous depth stencil view if (mDepthStencilView.ptr != 0) mDSVHeap.Free(mDepthStencilView); // Free any previous depth stencil buffer mDepthStencilBuffer.Reset(); // Allocate depth stencil buffer D3D12_CLEAR_VALUE clear_value = {}; clear_value.Format = DXGI_FORMAT_D32_FLOAT; clear_value.DepthStencil.Depth = 1.0f; clear_value.DepthStencil.Stencil = 0; D3D12_HEAP_PROPERTIES heap_properties = {}; heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT; heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heap_properties.CreationNodeMask = 1; heap_properties.VisibleNodeMask = 1; D3D12_RESOURCE_DESC depth_stencil_desc = {}; depth_stencil_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; depth_stencil_desc.Alignment = 0; depth_stencil_desc.Width = mWindowWidth; depth_stencil_desc.Height = mWindowHeight; depth_stencil_desc.DepthOrArraySize = 1; depth_stencil_desc.MipLevels = 1; depth_stencil_desc.Format = DXGI_FORMAT_D32_FLOAT; depth_stencil_desc.SampleDesc.Count = 1; depth_stencil_desc.SampleDesc.Quality = 0; depth_stencil_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; depth_stencil_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &depth_stencil_desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, &clear_value, IID_PPV_ARGS(&mDepthStencilBuffer))); // Allocate depth stencil view D3D12_DEPTH_STENCIL_VIEW_DESC depth_stencil_view_desc = {}; depth_stencil_view_desc.Format = DXGI_FORMAT_D32_FLOAT; depth_stencil_view_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; depth_stencil_view_desc.Flags = D3D12_DSV_FLAG_NONE; mDepthStencilView = mDSVHeap.Allocate(); mDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &depth_stencil_view_desc, mDepthStencilView); } void Renderer::Initialize() { // Prevent this window from auto scaling SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); // Register class WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = GetModuleHandle(nullptr); wcex.hIcon = nullptr; wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = nullptr; wcex.lpszMenuName = nullptr; wcex.lpszClassName = TEXT("TestFrameworkClass"); wcex.hIconSm = nullptr; if (!RegisterClassEx(&wcex)) FatalError("Failed to register window class"); // Create window RECT rc = { 0, 0, mWindowWidth, mWindowHeight }; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); mhWnd = CreateWindow(TEXT("TestFrameworkClass"), TEXT("TestFramework"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, nullptr, nullptr, wcex.hInstance, nullptr); if (!mhWnd) FatalError("Failed to create window"); // Show window ShowWindow(mhWnd, SW_SHOW); #if defined(_DEBUG) // Enable the D3D12 debug layer ComPtr debug_controller; if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debug_controller)))) debug_controller->EnableDebugLayer(); #endif // Create DXGI factory FatalErrorIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDXGIFactory))); // Find adapter ComPtr adapter; HRESULT result = E_FAIL; // First check if we have the Windows 1803 IDXGIFactory6 interface ComPtr factory6; if (SUCCEEDED(mDXGIFactory->QueryInterface(IID_PPV_ARGS(&factory6)))) { for (UINT index = 0; DXGI_ERROR_NOT_FOUND != factory6->EnumAdapterByGpuPreference(index, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)); ++index) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); // We don't want software renderers if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) continue; // Check to see whether the adapter supports Direct3D 12 result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)); if (SUCCEEDED(result)) break; } } else { // Fall back to the older method that may not get the fastest GPU for (UINT index = 0; DXGI_ERROR_NOT_FOUND != mDXGIFactory->EnumAdapters1(index, &adapter); ++index) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); // We don't want software renderers if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) continue; // Check to see whether the adapter supports Direct3D 12 result = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)); if (SUCCEEDED(result)) break; } } // Check if we managed to obtain a device FatalErrorIfFailed(result); #ifdef _DEBUG // Enable breaking on errors ComPtr info_queue; if (SUCCEEDED(mDevice.As(&info_queue))) { info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE); info_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, TRUE); // Disable an error that triggers on Windows 11 with a hybrid graphic system // See: https://stackoverflow.com/questions/69805245/directx-12-application-is-crashing-in-windows-11 D3D12_MESSAGE_ID hide[] = { D3D12_MESSAGE_ID_RESOURCE_BARRIER_MISMATCHING_COMMAND_LIST_TYPE, }; D3D12_INFO_QUEUE_FILTER filter = { }; filter.DenyList.NumIDs = static_cast( std::size( hide ) ); filter.DenyList.pIDList = hide; info_queue->AddStorageFilterEntries( &filter ); } #endif // _DEBUG // Disable full screen transitions FatalErrorIfFailed(mDXGIFactory->MakeWindowAssociation(mhWnd, DXGI_MWA_NO_ALT_ENTER)); // Create heaps mRTVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 2); mDSVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 4); mSRVHeap.Init(mDevice.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, 128); // Create a command queue D3D12_COMMAND_QUEUE_DESC queue_desc = {}; queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; FatalErrorIfFailed(mDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue))); // Create a command allocator for each frame for (uint n = 0; n < cFrameCount; n++) FatalErrorIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocators[n]))); // Describe and create the swap chain DXGI_SWAP_CHAIN_DESC swap_chain_desc = {}; swap_chain_desc.BufferCount = cFrameCount; swap_chain_desc.BufferDesc.Width = mWindowWidth; swap_chain_desc.BufferDesc.Height = mWindowHeight; swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swap_chain_desc.OutputWindow = mhWnd; swap_chain_desc.SampleDesc.Count = 1; swap_chain_desc.Windowed = TRUE; ComPtr swap_chain; FatalErrorIfFailed(mDXGIFactory->CreateSwapChain(mCommandQueue.Get(), &swap_chain_desc, &swap_chain)); FatalErrorIfFailed(swap_chain.As(&mSwapChain)); mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); CreateRenterTargets(); CreateDepthBuffer(); // Create a root signature suitable for all our shaders D3D12_ROOT_PARAMETER params[3] = {}; // Mapping a constant buffer to slot 0 for the vertex shader params[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; params[0].Descriptor.ShaderRegister = 0; params[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; // Mapping a constant buffer to slot 1 in the pixel shader params[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; params[1].Descriptor.ShaderRegister = 1; params[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; // Mapping a texture to slot 2 in the pixel shader D3D12_DESCRIPTOR_RANGE range = {}; range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; range.BaseShaderRegister = 2; range.NumDescriptors = 1; params[2].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; params[2].DescriptorTable.NumDescriptorRanges = 1; params[2].DescriptorTable.pDescriptorRanges = ⦥ params[2].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; D3D12_STATIC_SAMPLER_DESC samplers[3] = {}; // Sampler 0: Non-wrapping linear filtering samplers[0].Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; samplers[0].AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; samplers[0].AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; samplers[0].AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; samplers[0].MipLODBias = 0.0f; samplers[0].MaxAnisotropy = 1; samplers[0].ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; samplers[0].BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; samplers[0].MinLOD = 0.0f; samplers[0].MaxLOD = D3D12_FLOAT32_MAX; samplers[0].ShaderRegister = 0; samplers[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; // Sampler 1: Wrapping and linear filtering samplers[1] = samplers[0]; samplers[1].AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; samplers[1].AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; samplers[1].AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; samplers[1].ShaderRegister = 1; // Sampler 2: Point filtering, using SampleCmp mode to compare if sampled value <= reference value (for shadows) samplers[2] = samplers[0]; samplers[2].Filter = D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; samplers[2].ComparisonFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL; samplers[2].ShaderRegister = 2; D3D12_ROOT_SIGNATURE_DESC root_signature_desc = {}; root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; root_signature_desc.NumParameters = ARRAYSIZE(params); root_signature_desc.pParameters = params; root_signature_desc.NumStaticSamplers = ARRAYSIZE(samplers); root_signature_desc.pStaticSamplers = samplers; ComPtr signature; ComPtr error; FatalErrorIfFailed(D3D12SerializeRootSignature(&root_signature_desc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error)); FatalErrorIfFailed(mDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&mRootSignature))); // Create the command list FatalErrorIfFailed(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocators[mFrameIndex].Get(), nullptr, IID_PPV_ARGS(&mCommandList))); // 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 FatalErrorIfFailed(mCommandList->Close()); // Create synchronization object FatalErrorIfFailed(mDevice->CreateFence(mFenceValues[mFrameIndex], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))); // Increment fence value so we don't skip waiting the first time a command list is executed mFenceValues[mFrameIndex]++; // Create an event handle to use for frame synchronization mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); if (mFenceEvent == nullptr) FatalErrorIfFailed(HRESULT_FROM_WIN32(GetLastError())); // Initialize the queue used to upload resources to the GPU mUploadQueue.Initialize(mDevice.Get()); // Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it. for (uint n = 0; n < cFrameCount; ++n) { mVertexShaderConstantBufferProjection[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); mVertexShaderConstantBufferOrtho[n] = CreateConstantBuffer(sizeof(VertexShaderConstantBuffer)); mPixelShaderConstantBuffer[n] = CreateConstantBuffer(sizeof(PixelShaderConstantBuffer)); } // Store global renderer now that we're done initializing sRenderer = this; } void Renderer::OnWindowResize() { // Wait for the previous frame to be rendered WaitForGpu(); // Get new window size RECT rc; GetClientRect(mhWnd, &rc); mWindowWidth = max(rc.right - rc.left, 8); mWindowHeight = max(rc.bottom - rc.top, 8); // Free the render targets and views to allow resizing the swap chain for (uint n = 0; n < cFrameCount; ++n) { mRTVHeap.Free(mRenderTargetViews[n]); mRenderTargets[n].Reset(); } // Resize the swap chain buffers FatalErrorIfFailed(mSwapChain->ResizeBuffers(cFrameCount, mWindowWidth, mWindowHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0)); // Back buffer index may have changed after the resize (it always seems to go to 0 again) mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); // Since we may have switched frame index and we know everything is done, we need to update the fence value for our other frame as completed for (uint n = 0; n < cFrameCount; ++n) if (mFrameIndex != n) mFenceValues[n] = mFence->GetCompletedValue(); // Recreate render targets CreateRenterTargets(); // Recreate depth buffer CreateDepthBuffer(); } /// Construct a perspective matrix static inline Mat44 sPerspective(float inFovY, float inAspect, float inNear, float inFar) { float height = 1.0f / Tan(0.5f * inFovY); float width = height / inAspect; float range = inFar / (inNear - inFar); return Mat44(Vec4(width, 0.0f, 0.0f, 0.0f), Vec4(0.0f, height, 0.0f, 0.0f), Vec4(0.0f, 0.0f, range, -1.0f), Vec4(0.0f, 0.0f, range * inNear, 0.0f)); } void Renderer::BeginFrame(const CameraState &inCamera, float inWorldScale) { JPH_PROFILE_FUNCTION(); // Store state mCameraState = inCamera; // Reset command allocator FatalErrorIfFailed(mCommandAllocators[mFrameIndex]->Reset()); // Reset command list FatalErrorIfFailed(mCommandList->Reset(mCommandAllocators[mFrameIndex].Get(), nullptr)); // Set root signature mCommandList->SetGraphicsRootSignature(mRootSignature.Get()); // Set SRV heap ID3D12DescriptorHeap *heaps[] = { mSRVHeap.Get() }; mCommandList->SetDescriptorHeaps(_countof(heaps), heaps); // Indicate that the back buffer will be used as a render target. D3D12_RESOURCE_BARRIER barrier; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get(); barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; mCommandList->ResourceBarrier(1, &barrier); // Set the main back buffer as render target SetRenderTarget(nullptr); // Clear the back buffer. const float blue[] = { 0.098f, 0.098f, 0.439f, 1.000f }; mCommandList->ClearRenderTargetView(mRenderTargetViews[mFrameIndex], blue, 0, nullptr); mCommandList->ClearDepthStencilView(mDepthStencilView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); // Light properties Vec3 light_pos = inWorldScale * Vec3(250, 250, 250); Vec3 light_tgt = Vec3::sZero(); Vec3 light_up = Vec3(0, 1, 0); Vec3 light_fwd = (light_tgt - light_pos).Normalized(); float light_fov = DegreesToRadians(20.0f); float light_near = 1.0f; float light_far = 1000.0f; // Camera properties float camera_fovy = inCamera.mFOVY; float camera_aspect = static_cast(GetWindowWidth()) / GetWindowHeight(); float camera_fovx = 2.0f * ATan(camera_aspect * Tan(0.5f * camera_fovy)); float camera_near = 0.01f * inWorldScale; float camera_far = inCamera.mFarPlane * inWorldScale; // Set constants for vertex shader in projection mode VertexShaderConstantBuffer *vs = mVertexShaderConstantBufferProjection[mFrameIndex]->Map(); // Camera projection and view vs->mProjection = sPerspective(camera_fovy, camera_aspect, camera_near, camera_far); Vec3 cam_pos = Vec3(inCamera.mPos - mBaseOffset); Vec3 tgt = cam_pos + inCamera.mForward; vs->mView = Mat44::sLookAt(cam_pos, tgt, inCamera.mUp); // Light projection and view vs->mLightProjection = sPerspective(light_fov, 1.0f, light_near, light_far); vs->mLightView = Mat44::sLookAt(light_pos, light_tgt, light_up); mVertexShaderConstantBufferProjection[mFrameIndex]->Unmap(); // Set constants for vertex shader in ortho mode vs = mVertexShaderConstantBufferOrtho[mFrameIndex]->Map(); // Camera ortho projection and view vs->mProjection = Mat44(Vec4(2.0f / mWindowWidth, 0.0f, 0.0f, 0.0f), Vec4(0.0f, -2.0f / mWindowHeight, 0.0f, 0.0f), Vec4(0.0f, 0.0f, -1.0f, 0.0f), Vec4(-1.0f, 1.0f, 0.0f, 1.0f)); vs->mView = Mat44::sIdentity(); // Light projection and view are unused in ortho mode vs->mLightView = Mat44::sIdentity(); vs->mLightProjection = Mat44::sIdentity(); mVertexShaderConstantBufferOrtho[mFrameIndex]->Unmap(); // Switch to 3d projection mode SetProjectionMode(); // Set constants for pixel shader PixelShaderConstantBuffer *ps = mPixelShaderConstantBuffer[mFrameIndex]->Map(); ps->mCameraPos = Vec4(cam_pos, 0); ps->mLightPos = Vec4(light_pos, 0); mPixelShaderConstantBuffer[mFrameIndex]->Unmap(); // Set the pixel shader constant buffer data. mPixelShaderConstantBuffer[mFrameIndex]->Bind(1); // Calculate camera frustum mCameraFrustum = Frustum(cam_pos, inCamera.mForward, inCamera.mUp, camera_fovx, camera_fovy, camera_near, camera_far); // Calculate light frustum mLightFrustum = Frustum(light_pos, light_fwd, light_up, light_fov, light_fov, light_near, light_far); } void Renderer::EndFrame() { JPH_PROFILE_FUNCTION(); // Indicate that the back buffer will now be used to present. D3D12_RESOURCE_BARRIER barrier; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = mRenderTargets[mFrameIndex].Get(); barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; mCommandList->ResourceBarrier(1, &barrier); // Close the command list FatalErrorIfFailed(mCommandList->Close()); // Execute the command list ID3D12CommandList* command_lists[] = { mCommandList.Get() }; mCommandQueue->ExecuteCommandLists(_countof(command_lists), command_lists); // Present the frame FatalErrorIfFailed(mSwapChain->Present(1, 0)); // Schedule a Signal command in the queue UINT64 current_fence_value = mFenceValues[mFrameIndex]; FatalErrorIfFailed(mCommandQueue->Signal(mFence.Get(), current_fence_value)); // Update the frame index mFrameIndex = mSwapChain->GetCurrentBackBufferIndex(); // If the next frame is not ready to be rendered yet, wait until it is ready UINT64 completed_value = mFence->GetCompletedValue(); if (completed_value < mFenceValues[mFrameIndex]) { FatalErrorIfFailed(mFence->SetEventOnCompletion(mFenceValues[mFrameIndex], mFenceEvent)); WaitForSingleObjectEx(mFenceEvent, INFINITE, FALSE); } // Release all used resources mDelayReleased[mFrameIndex].clear(); // Anything that's not used yet can be removed, delayed objects are now available mResourceCache.clear(); mDelayCached[mFrameIndex].swap(mResourceCache); // Set the fence value for the next frame. mFenceValues[mFrameIndex] = current_fence_value + 1; } void Renderer::SetProjectionMode() { mVertexShaderConstantBufferProjection[mFrameIndex]->Bind(0); } void Renderer::SetOrthoMode() { mVertexShaderConstantBufferOrtho[mFrameIndex]->Bind(0); } Ref Renderer::CreateTexture(const Surface *inSurface) { return new Texture(this, inSurface); } Ref Renderer::CreateRenderTarget(int inWidth, int inHeight) { return new Texture(this, inWidth, inHeight); } void Renderer::SetRenderTarget(Texture *inRenderTarget) { // Unset the previous render target if (mRenderTargetTexture != nullptr) mRenderTargetTexture->SetAsRenderTarget(false); mRenderTargetTexture = nullptr; if (inRenderTarget == nullptr) { // Set the main back buffer as render target mCommandList->OMSetRenderTargets(1, &mRenderTargetViews[mFrameIndex], FALSE, &mDepthStencilView); // Set viewport D3D12_VIEWPORT viewport = { 0.0f, 0.0f, static_cast(mWindowWidth), static_cast(mWindowHeight), 0.0f, 1.0f }; mCommandList->RSSetViewports(1, &viewport); // Set scissor rect D3D12_RECT scissor_rect = { 0, 0, static_cast(mWindowWidth), static_cast(mWindowHeight) }; mCommandList->RSSetScissorRects(1, &scissor_rect); } else { // Use the texture as render target inRenderTarget->SetAsRenderTarget(true); mRenderTargetTexture = inRenderTarget; } } ComPtr Renderer::CreateVertexShader(const char *inFileName) { UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; #ifdef _DEBUG flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #endif const D3D_SHADER_MACRO defines[] = { { nullptr, nullptr } }; // Read shader source file Array data = ReadData(inFileName); // Compile source ComPtr shader_blob, error_blob; HRESULT hr = D3DCompile(&data[0], (uint)data.size(), inFileName, defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", "vs_5_0", flags, 0, shader_blob.GetAddressOf(), error_blob.GetAddressOf()); if (FAILED(hr)) { // Throw error if compilation failed if (error_blob) OutputDebugStringA((const char *)error_blob->GetBufferPointer()); FatalError("Failed to compile vertex shader"); } return shader_blob; } ComPtr Renderer::CreatePixelShader(const char *inFileName) { UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; #ifdef _DEBUG flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #endif const D3D_SHADER_MACRO defines[] = { { nullptr, nullptr } }; // Read shader source file Array data = ReadData(inFileName); // Compile source ComPtr shader_blob, error_blob; HRESULT hr = D3DCompile(&data[0], (uint)data.size(), inFileName, defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, "main", "ps_5_0", flags, 0, shader_blob.GetAddressOf(), error_blob.GetAddressOf()); if (FAILED(hr)) { // Throw error if compilation failed if (error_blob) OutputDebugStringA((const char *)error_blob->GetBufferPointer()); FatalError("Failed to compile pixel shader"); } return shader_blob; } unique_ptr Renderer::CreateConstantBuffer(uint inBufferSize) { return make_unique(this, inBufferSize); } unique_ptr Renderer::CreatePipelineState(ID3DBlob *inVertexShader, const D3D12_INPUT_ELEMENT_DESC *inInputDescription, uint inInputDescriptionCount, ID3DBlob *inPixelShader, D3D12_FILL_MODE inFillMode, D3D12_PRIMITIVE_TOPOLOGY_TYPE inTopology, PipelineState::EDepthTest inDepthTest, PipelineState::EBlendMode inBlendMode, PipelineState::ECullMode inCullMode) { return make_unique(this, inVertexShader, inInputDescription, inInputDescriptionCount, inPixelShader, inFillMode, inTopology, inDepthTest, inBlendMode, inCullMode); } ComPtr Renderer::CreateD3DResource(D3D12_HEAP_TYPE inHeapType, D3D12_RESOURCE_STATES inResourceState, uint64 inSize) { // Create a new resource D3D12_RESOURCE_DESC desc; desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Alignment = 0; desc.Width = inSize; desc.Height = 1; desc.DepthOrArraySize = 1; desc.MipLevels = 1; desc.Format = DXGI_FORMAT_UNKNOWN; desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Flags = D3D12_RESOURCE_FLAG_NONE; D3D12_HEAP_PROPERTIES heap_properties = {}; heap_properties.Type = inHeapType; heap_properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; heap_properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; heap_properties.CreationNodeMask = 1; heap_properties.VisibleNodeMask = 1; ComPtr resource; FatalErrorIfFailed(mDevice->CreateCommittedResource(&heap_properties, D3D12_HEAP_FLAG_NONE, &desc, inResourceState, nullptr, IID_PPV_ARGS(&resource))); return resource; } void Renderer::CopyD3DResource(ID3D12Resource *inDest, const void *inSrc, uint64 inSize) { // Copy data to destination buffer void *data; D3D12_RANGE range = { 0, 0 }; // We're not going to read FatalErrorIfFailed(inDest->Map(0, &range, &data)); memcpy(data, inSrc, size_t(inSize)); inDest->Unmap(0, nullptr); } void Renderer::CopyD3DResource(ID3D12Resource *inDest, ID3D12Resource *inSrc, uint64 inSize) { // Start a commandlist for the upload ID3D12GraphicsCommandList *list = mUploadQueue.Start(); // Copy the data to the GPU list->CopyBufferRegion(inDest, 0, inSrc, 0, inSize); // Change the state of the resource to generic read D3D12_RESOURCE_BARRIER barrier; barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; barrier.Transition.pResource = inDest; barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_GENERIC_READ; barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; list->ResourceBarrier(1, &barrier); // Wait for copying to finish mUploadQueue.ExecuteAndWait(); } ComPtr Renderer::CreateD3DResourceOnDefaultHeap(const void *inData, uint64 inSize) { ComPtr upload = CreateD3DResourceOnUploadHeap(inSize); ComPtr resource = CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, inSize); CopyD3DResource(upload.Get(), inData, inSize); CopyD3DResource(resource.Get(), upload.Get(), inSize); RecycleD3DResourceOnUploadHeap(upload.Get(), inSize); return resource; } ComPtr Renderer::CreateD3DResourceOnUploadHeap(uint64 inSize) { // Try cache first ResourceCache::iterator i = mResourceCache.find(inSize); if (i != mResourceCache.end() && !i->second.empty()) { ComPtr resource = i->second.back(); i->second.pop_back(); return resource; } return CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, inSize); } void Renderer::RecycleD3DResourceOnUploadHeap(ID3D12Resource *inResource, uint64 inSize) { if (!mIsExiting) mDelayCached[mFrameIndex][inSize].push_back(inResource); } void Renderer::RecycleD3DObject(ID3D12Object *inResource) { if (!mIsExiting) mDelayReleased[mFrameIndex].push_back(inResource); }