Skip to content
16 changes: 15 additions & 1 deletion Source/Core/Engine/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "Window/Window.h"
#include "frameData.h"
#include "Util/jsonUtil.h"
#include "Graphics/DX11.h"
#include "Graphics/DX12.h"

#include <nlohmann/json.hpp>

Expand All @@ -21,11 +23,23 @@ RF::Engine::Engine(const RF::EngineCreationParams& params) {

mWindow = std::make_unique<RF::Window>();
mWindow->Init(windowParams);

switch (mGraphicsAPI) {
case GraphicsAPI::DirectX11:
mRenderer = std::make_unique<RF::DX11>();
break;
case GraphicsAPI::DirectX12:
mRenderer = std::make_unique<RF::DX12>();
break;
}
Comment on lines +27 to +43
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det här borde vara en preprocessor check i och med att vi aldrig kommer byta api i runtime

Suggested change
switch (mGraphicsAPI) {
case GraphicsAPI::DirectX11:
mRenderer = std::make_unique<RF::DX11>(mWindow->GetHWND(), windowParams.width, windowParams.height);
break;
case GraphicsAPI::DirectX12:
mRenderer = std::make_unique<RF::DX12>(mWindow->GetHWND(), windowParams.width, windowParams.height);
break;
}
#ifdef DX11
mRenderer = std::make_unique<RF::DX11>(mWindow->GetHWND(), windowParams.width, windowParams.height);
#elif DX12
mRenderer = std::make_unique<RF::DX12>(mWindow->GetHWND(), windowParams.width, windowParams.height);
#endif

mRenderer->Init(mWindow->GetHWND(), windowParams.width, windowParams.height);
}

void RF::Engine::Update(const FrameData& frameData) { frameData; }

void RF::Engine::Render(const FrameData& frameData) { frameData; }
void RF::Engine::Render(const FrameData& frameData) {
mRenderer->Render(frameData);
}

void RF::Engine::Shutdown() {}

Expand Down
11 changes: 10 additions & 1 deletion Source/Core/Engine/Engine.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#pragma once
#include "Graphics/IRenderer.h"

namespace RF {
struct FrameData;
struct WindowCreationParams;
Expand All @@ -10,6 +12,11 @@ namespace RF {
HINSTANCE hInstance = nullptr;
};

enum class GraphicsAPI {
DirectX11,
DirectX12,
};

class Engine {
public:
Engine() = delete;
Expand All @@ -29,7 +36,9 @@ namespace RF {
void LoadConfigFile(RF::WindowCreationParams& windowParams);

std::unique_ptr<Window> mWindow;
std::unique_ptr<IRenderer> mRenderer;
GraphicsAPI mGraphicsAPI = GraphicsAPI::DirectX11;

std::wstring mAssetsPath;
};
}
}
91 changes: 91 additions & 0 deletions Source/Core/Graphics/DX11.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include "stdafx.h"
#include "DX11.h"

#include <stdexcept>

using namespace Microsoft::WRL;

RF::DX11::~DX11() {
}

void RF::DX11::Init(const HWND hwnd, const uint32_t width, const uint32_t height) {
mWidth = width;
mHeight = height;

CreateDeviceAndSwapChain(hwnd, width, height);
CreateRenderTargetView();
CreateViewport(width, height);

mInitialized = true;
}

void RF::DX11::CreateDeviceAndSwapChain(const HWND hwnd, const uint32_t width, const uint32_t height) {
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferDesc.Width = width;
scd.BufferDesc.Height = height;
scd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
scd.BufferDesc.RefreshRate.Numerator = 0u;
scd.BufferDesc.RefreshRate.Denominator = 0u;
scd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
scd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
scd.SampleDesc.Count = 1u; // Anti-aliasing
scd.SampleDesc.Quality = 0u; // Anti-aliasing
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.BufferCount = 1u; // 1 back buffer and 1 front buffer
scd.OutputWindow = hwnd;
scd.Windowed = true;
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
scd.Flags = 0u;
Comment thread
OlleKReutercrona marked this conversation as resolved.

UINT swapCreateFlags = 0u;
#ifndef NDEBUG
swapCreateFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

D3D11CreateDeviceAndSwapChain(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
swapCreateFlags,
nullptr,
0,
D3D11_SDK_VERSION,
&scd,
&pSwap,
&pDevice,
nullptr,
&pContext
);
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value from D3D11CreateDeviceAndSwapChain is ignored. If device/swapchain creation fails, subsequent calls will dereference null ComPtrs (pSwap/pDevice/pContext) and crash, while mInitialized is still set to true. Capture the HRESULT and throw/return an error when it fails.

Copilot uses AI. Check for mistakes.
}

void RF::DX11::CreateRenderTargetView() {
// Gain access to texture subresource in swap chains (back buffer)
Microsoft::WRL::ComPtr<ID3D11Resource> pBackBuffer;
pSwap->GetBuffer(0u, __uuidof(ID3D11Resource), &pBackBuffer);
pDevice->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &pDefaultTarget);
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetBuffer/CreateRenderTargetView results are not checked. If the swap chain buffer retrieval or RTV creation fails, pDefaultTarget may remain null and later ClearRenderTargetView will crash. Please check HRESULTs here and surface an initialization failure instead of continuing.

Copilot uses AI. Check for mistakes.
}

void RF::DX11::CreateViewport(const uint32_t width, const uint32_t height) {
D3D11_VIEWPORT vp = {};
vp.Width = static_cast<FLOAT>(width);
vp.Height = static_cast<FLOAT>(height);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
pContext->RSSetViewports(1u, &vp);
}

void RF::DX11::Render(const FrameData& frameData) {
frameData;

if (!mInitialized) {
throw std::runtime_error("Renderer not initialized");
}

// Clear the back buffer to a color (RGBA)
const FLOAT clearColor[] = { 0.2f, 0.4f, 0.6f, 1.0f };
Comment thread
OlleKReutercrona marked this conversation as resolved.
pContext->ClearRenderTargetView(pDefaultTarget.Get(), clearColor);
// Present the back buffer to the screen
pSwap->Present(1u, 0u);
}
30 changes: 30 additions & 0 deletions Source/Core/Graphics/DX11.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once
#include <wrl.h>
#include <d3d11.h>
#include <dxgi1_6.h>
#include "IRenderer.h"

namespace RF {
class DX11 : public IRenderer {
public:
DX11() = default;
~DX11();

virtual void Init(const HWND hwnd, const uint32_t width, const uint32_t height);
virtual void Render(const FrameData& frameData);
private:
void CreateDeviceAndSwapChain(const HWND hwnd, const uint32_t width, const uint32_t height);
void CreateRenderTargetView();
void CreateViewport(const uint32_t width, const uint32_t height);

bool mInitialized = false;

uint32_t mWidth;
uint32_t mHeight;

Microsoft::WRL::ComPtr<ID3D11Device> pDevice;
Microsoft::WRL::ComPtr<IDXGISwapChain> pSwap;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> pContext;
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> pDefaultTarget;
};
}
200 changes: 200 additions & 0 deletions Source/Core/Graphics/DX12.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#include "stdafx.h"
#include "DX12.h"
#include "d3dx12.h" // helper classes for barriers, handles, etc.

#include <stdexcept>

using namespace Microsoft::WRL;

RF::DX12::~DX12() {
// Ensure GPU is finished before destroying resources
mFenceValue++;
HRESULT hr = mCommandQueue->Signal(mFence.Get(), mFenceValue);

if (mFence->GetCompletedValue() < mFenceValue) {
hr = mFence->SetEventOnCompletion(mFenceValue, mFenceEvent);
WaitForSingleObject(mFenceEvent, INFINITE);
}
CloseHandle(mFenceEvent);
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DX12 destructor assumes initialization succeeded and unconditionally dereferences mCommandQueue/mFence (and closes mFenceEvent). If Init was never called or threw part-way through, this can null-deref and/or call CloseHandle on an invalid handle. Guard cleanup with mInitialized and/or nullptr checks, and only Signal/SetEvent/CloseHandle when the corresponding objects/handle are valid.

Suggested change
// Ensure GPU is finished before destroying resources
mFenceValue++;
HRESULT hr = mCommandQueue->Signal(mFence.Get(), mFenceValue);
if (mFence->GetCompletedValue() < mFenceValue) {
hr = mFence->SetEventOnCompletion(mFenceValue, mFenceEvent);
WaitForSingleObject(mFenceEvent, INFINITE);
}
CloseHandle(mFenceEvent);
// Only perform GPU synchronization/handle cleanup if initialization completed
// and the corresponding resources were actually created.
if (!mInitialized)
return;
if (mCommandQueue && mFence) {
// Ensure GPU is finished before destroying resources
mFenceValue++;
HRESULT hr = mCommandQueue->Signal(mFence.Get(), mFenceValue);
if (SUCCEEDED(hr) && mFence->GetCompletedValue() < mFenceValue && mFenceEvent) {
hr = mFence->SetEventOnCompletion(mFenceValue, mFenceEvent);
if (SUCCEEDED(hr))
WaitForSingleObject(mFenceEvent, INFINITE);
}
}
if (mFenceEvent)
CloseHandle(mFenceEvent);

Copilot uses AI. Check for mistakes.
}

void RF::DX12::Init(const HWND hwnd, const uint32_t width, const uint32_t height) {
UINT dxgiFactoryFlags = 0;

#if defined(_DEBUG)
// Enable debug layer in debug builds (gives better validation messages)
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) {
debugController->EnableDebugLayer();
dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif

// Create DXGI factory (manages adapters, swap chains)
ComPtr<IDXGIFactory4> factory;
if (FAILED(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory))))
throw std::runtime_error("Failed to create DXGI Factory");

// Create the D3D12 device (nullptr = default hardware adapter)
if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice))))
throw std::runtime_error("Failed to create D3D12 device");

// Setup command queue/allocator/list
CreateCommandObjects();

// Create swap chain tied to window
CreateSwapChain(hwnd, width, height);

// Create render target views for each back buffer
CreateRTV();

// Fence for GPU/CPU synchronization
if (FAILED(mDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))))
throw std::runtime_error("Failed to create fence");

mFenceValue = 0;
mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (!mFenceEvent)
throw std::runtime_error("Failed to create fence event");

// Get the current back buffer index
mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();

mInitialized = true;
}

void RF::DX12::Render(const FrameData& frameData) {
frameData;

if (!mInitialized) {
throw std::runtime_error("Renderer not initialized");
}

// Reset allocator and command list each frame
HRESULT hr = mCommandAllocator->Reset();
hr = mCommandList->Reset(mCommandAllocator.Get(), nullptr);

Comment on lines +69 to +72
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HRESULTs returned from Reset/Close/Present/Signal/SetEventOnCompletion are assigned to hr but never validated. When these fail (e.g., device removed, invalid state, allocator still in use), the renderer will continue issuing commands with undefined behavior. Check each HRESULT and fail fast (throw/log/return) so errors are surfaced and you don't proceed with an invalid command list or swap chain state.

Copilot uses AI. Check for mistakes.
// Transition back buffer from "present" to "render target"
CD3DX12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
mRenderTargets[mFrameIndex].Get(),
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET);
mCommandList->ResourceBarrier(1, &barrier);

// Get the RTV for the current back buffer
const CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(),
static_cast<int>(mFrameIndex),
mRtvDescriptorSize);

// Clear screen to a color (similar to ClearRenderTargetView in DX11)
constexpr FLOAT clearColor[] = { 0.6f, 0.4f, 0.2f, 1.0f };
mCommandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

// Transition back buffer back to "present" state
barrier = CD3DX12_RESOURCE_BARRIER::Transition(
mRenderTargets[mFrameIndex].Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT);
mCommandList->ResourceBarrier(1, &barrier);

// Close and execute command list
hr = mCommandList->Close();
ID3D12CommandList* ppCommandLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

// Present back buffer to screen (vsync = 1)
hr = mSwapChain->Present(1, 0);
mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();

// Fence synchronization: ensure CPU doesn't get too far ahead of GPU
mFenceValue++;
hr = mCommandQueue->Signal(mFence.Get(), mFenceValue);

if (mFence->GetCompletedValue() < mFenceValue) {
hr = mFence->SetEventOnCompletion(mFenceValue, mFenceEvent);
WaitForSingleObject(mFenceEvent, INFINITE);
}
}

void RF::DX12::CreateCommandObjects() {
// Describe command queue (like immediate context in DX11)
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; // direct = can do all ops

// Create the GPU command queue
if (FAILED(mDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue))))
throw std::runtime_error("Failed to create command queue");

// Command allocator (reusable memory for command lists)
if (FAILED(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAllocator))))
throw std::runtime_error("Failed to create command allocator");

// Command list (records commands, submitted to GPU)
if (FAILED(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAllocator.Get(), nullptr, IID_PPV_ARGS(&mCommandList))))
throw std::runtime_error("Failed to create command list");

// Close immediately; we only open it per-frame
HRESULT hr = mCommandList->Close();
if (FAILED(hr))
throw std::runtime_error("Failed to close command list");
}

void RF::DX12::CreateSwapChain(const HWND hwnd, const uint32_t width, const uint32_t height) {
ComPtr<IDXGIFactory4> factory;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(hr))
throw std::runtime_error("Failed to create DXGI Factory");
Comment on lines +140 to +144
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateSwapChain creates a new DXGI factory via CreateDXGIFactory1 even though Init already created a factory with debug flags. This duplicates work and (in _DEBUG) can drop DXGI debug factory behavior. Consider passing the factory created in Init into CreateSwapChain or storing it as a member and reusing it.

Copilot uses AI. Check for mistakes.

// Swap chain description (like DXGI_SWAP_CHAIN_DESC in DX11 but v1.6+)
DXGI_SWAP_CHAIN_DESC1 swapDesc = {};
swapDesc.BufferCount = FRAME_COUNT;
swapDesc.Width = width;
swapDesc.Height = height;
swapDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 32-bit color
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // modern, efficient
swapDesc.SampleDesc.Count = 1; // no MSAA

// Create swap chain for HWND
ComPtr<IDXGISwapChain1> swapChain1;
if (FAILED(factory->CreateSwapChainForHwnd(mCommandQueue.Get(), hwnd, &swapDesc, nullptr, nullptr, &swapChain1)))
throw std::runtime_error("Failed to create swap chain");

// Upgrade to IDXGISwapChain3 for modern features
if (FAILED(swapChain1.As(&mSwapChain)))
throw std::runtime_error("Failed to cast swap chain");

mFrameIndex = mSwapChain->GetCurrentBackBufferIndex();
}

void RF::DX12::CreateRTV() {
// Create descriptor heap for render target views (like RTV array in DX11)
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FRAME_COUNT;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;

if (FAILED(mDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&mRtvHeap))))
throw std::runtime_error("Failed to create RTV heap");

// RTV handle size (increment for each back buffer)
mRtvDescriptorSize = mDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

// First RTV handle in heap
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());

// Create RTV for each swap chain buffer
for (UINT i = 0; i < FRAME_COUNT; i++) {
if (FAILED(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mRenderTargets[i]))))
throw std::runtime_error("Failed to get swap chain buffer");

mDevice->CreateRenderTargetView(mRenderTargets[i].Get(), nullptr, rtvHandle);

// Move handle to next descriptor slot
rtvHandle.Offset(1, mRtvDescriptorSize);
}
}
Loading