Forum

Notifications
Clear all

C++, Nvidia CUDA, DirectX12 3D Video Game Engine

1 Posts
1 Users
0 Reactions
8 Views
 josh
(@josh)
Member Admin
Joined: 2 months ago
Posts: 510
Topic starter  
# 3D Video Game Engine with CUDA and DirectX 12

Here's a complete folder structure and source code for a 3D game engine that integrates NVIDIA CUDA with DirectX 12:

## Folder Structure
```
GameEngine/
├── src/
│   ├── core/
│   │   ├── Engine.cpp
│   │   ├── Engine.h
│   │   ├── Window.cpp
│   │   ├── Window.h
│   │   └── Input.cpp
│   │   └── Input.h
│   ├── graphics/
│   │   ├── DirectX12Renderer.cpp
│   │   ├── DirectX12Renderer.h
│   │   ├── ShaderManager.cpp
│   │   ├── ShaderManager.h
│   │   ├── TextureManager.cpp
│   │   └── TextureManager.h
│   ├── cuda/
│   │   ├── CudaManager.cpp
│   │   ├── CudaManager.h
│   │   ├── CudaParticleSimulation.cu
│   │   └── CudaParticleSimulation.cuh
│   ├── scene/
│   │   ├── Scene.cpp
│   │   ├── Scene.h
│   │   ├── GameObject.cpp
│   │   ├── GameObject.h
│   │   └── Camera.cpp
│   │   └── Camera.h
│   ├── utils/
│   │   ├── Timer.cpp
│   │   └── Timer.h
│   └── main.cpp
├── include/
│   └── third_party/
│       ├── dxgi.h
│       ├── d3d12.h
│       └── cuda_runtime.h
├── assets/
│   ├── shaders/
│   │   ├── vertex.hlsl
│   │   └── pixel.hlsl
│   ├── models/
│   │   └── cube.obj
│   └── textures/
│       └── default.png
└── CMakeLists.txt
```

## Source Code Files

### src/main.cpp
```cpp
#include "core/Engine.h"
#include <iostream>

int main() {
    try {
        Engine engine;
        engine.run();
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return -1;
    }
    
    return 0;
}
```

### src/core/Engine.h
```cpp
#pragma once
#include <memory>
#include "Window.h"
#include "Input.h"
#include "../graphics/DirectX12Renderer.h"
#include "../cuda/CudaManager.h"
#include "../scene/Scene.h"

class Engine {
public:
    Engine();
    ~Engine();

    void run();
    void update(float deltaTime);
    void render();

private:
    std::unique_ptr<Window> window;
    std::unique_ptr<Input> input;
    std::unique_ptr<DirectX12Renderer> renderer;
    std::unique_ptr<CudaManager> cudaManager;
    std::unique_ptr<Scene> scene;

    bool isRunning;
};
```

### src/core/Engine.cpp
```cpp
#include "Engine.h"
#include "../utils/Timer.h"

Engine::Engine() : isRunning(false) {
    window = std::make_unique<Window>(1280, 720, L"3D Game Engine");
    input = std::make_unique<Input>();
    renderer = std::make_unique<DirectX12Renderer>(window->getHandle());
    cudaManager = std::make_unique<CudaManager>();
    scene = std::make_unique<Scene>();
    
    // Initialize CUDA
    cudaManager->initialize();
    
    // Initialize renderer
    renderer->initialize();
}

Engine::~Engine() {
    renderer->cleanup();
    cudaManager->cleanup();
}

void Engine::run() {
    isRunning = true;
    Timer timer;
    
    while (isRunning) {
        float deltaTime = timer.getElapsedTime();
        timer.reset();
        
        // Process input
        input->update();
        
        // Update game logic
        update(deltaTime);
        
        // Render frame
        render();
    }
}

void Engine::update(float deltaTime) {
    if (input->isKeyPressed(VK_ESCAPE)) {
        isRunning = false;
    }
    
    scene->update(deltaTime);
}

void Engine::render() {
    renderer->beginFrame();
    
    // Render scene with CUDA particle simulation
    cudaManager->simulateParticles();
    renderer->renderScene(*scene);
    
    renderer->endFrame();
}
```

### src/core/Window.h
```cpp
#pragma once
#include <windows.h>

class Window {
public:
    Window(int width, int height, const wchar_t* title);
    ~Window();

    HWND getHandle() const { return hwnd; }
    void update();
    bool isRunning() const;

private:
    HWND hwnd;
    int windowWidth;
    int windowHeight;
    
    static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
};
```

### src/core/Window.cpp
```cpp
#include "Window.h"
#include <iostream>

Window::Window(int width, int height, const wchar_t* title) : 
    windowWidth(width), windowHeight(height) {
    
    WNDCLASSEX wc = {0};
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = L"GameWindowClass";
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    
    if (!RegisterClassEx(&wc)) {
        throw std::runtime_error("Failed to register window class");
    }
    
    hwnd = CreateWindowEx(
        0, 
        L"GameWindowClass",
        title,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        width, height,
        NULL, NULL,
        GetModuleHandle(NULL),
        this
    );
    
    if (!hwnd) {
        throw std::runtime_error("Failed to create window");
    }
    
    ShowWindow(hwnd, SW_SHOW);
}

Window::~Window() {
    DestroyWindow(hwnd);
    UnregisterClass(L"GameWindowClass", GetModuleHandle(NULL));
}

void Window::update() {
    MSG msg;
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

bool Window::isRunning() const {
    return true; // Simplified - in a real engine you'd check for WM_DESTROY
}

LRESULT CALLBACK Window::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
}
```

### src/core/Input.h
```cpp
#pragma once
#include <windows.h>

class Input {
public:
    void update();
    bool isKeyPressed(int key) const;
    
private:
    static const int MAX_KEYS = 256;
    bool keys[MAX_KEYS];
};
```

### src/core/Input.cpp
```cpp
#include "Input.h"
#include <windows.h>

void Input::update() {
    for (int i = 0; i < MAX_KEYS; ++i) {
        keys[i] = GetAsyncKeyState(i) & 0x8000;
    }
}

bool Input::isKeyPressed(int key) const {
    return keys[key];
}
```

### src/graphics/DirectX12Renderer.h
```cpp
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <wrl/client.h>
#include <vector>
#include <memory>

class Scene;
class ShaderManager;
class TextureManager;

class DirectX12Renderer {
public:
    DirectX12Renderer(HWND hwnd);
    ~DirectX12Renderer();

    void initialize();
    void cleanup();
    
    void beginFrame();
    void endFrame();
    void renderScene(const Scene& scene);

private:
    Microsoft::WRL::ComPtr<ID3D12Device> device;
    Microsoft::WRL::ComPtr<IDXGISwapChain4> swapChain;
    Microsoft::WRL::ComPtr<ID3D12CommandQueue> commandQueue;
    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> rtvHeap;
    Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> dsvHeap;
    Microsoft::WRL::ComPtr<ID3D12Resource> renderTargets[2];
    
    UINT frameIndex;
    static const UINT NUM_BACK_BUFFERS = 2;
    
    std::unique_ptr<ShaderManager> shaderManager;
    std::unique_ptr<TextureManager> textureManager;
    
    void createDevice();
    void createSwapChain(HWND hwnd);
    void createCommandQueue();
    void createDescriptorHeaps();
    void createRenderTargets();
    void createCommandObjects();
};
```

### src/graphics/DirectX12Renderer.cpp
```cpp
#include "DirectX12Renderer.h"
#include <dxgi1_6.h>
#include "../scene/Scene.h"
#include "../scene/Camera.h"
#include "ShaderManager.h"
#include "TextureManager.h"

DirectX12Renderer::DirectX12Renderer(HWND hwnd) : frameIndex(0) {
    createDevice();
    createSwapChain(hwnd);
    createCommandQueue();
    createDescriptorHeaps();
    createRenderTargets();
    
    shaderManager = std::make_unique<ShaderManager>(device.Get());
    textureManager = std::make_unique<TextureManager>(device.Get());
}

DirectX12Renderer::~DirectX12Renderer() {
    cleanup();
}

void DirectX12Renderer::initialize() {
    // Initialize resources
}

void DirectX12Renderer::cleanup() {
    // Cleanup resources
}

void DirectX12Renderer::beginFrame() {
    // Prepare for rendering
}

void DirectX12Renderer::endFrame() {
    frameIndex = (frameIndex + 1) % NUM_BACK_BUFFERS;
}

void DirectX12Renderer::renderScene(const Scene& scene) {
    // Render scene objects with DirectX 12
    // This is a simplified version - actual implementation would be more complex
    for (const auto& object : scene.getGameObjects()) {
        // Draw object using DirectX 12 commands
    }
}

void DirectX12Renderer::createDevice() {
    HRESULT hr = D3D12CreateDevice(
        nullptr,                    // default adapter
        D3D_FEATURE_LEVEL_11_0,     // minimum feature level
        IID_PPV_ARGS(&device)
    );
    
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to create DirectX 12 device");
    }
}

void DirectX12Renderer::createSwapChain(HWND hwnd) {
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
    swapChainDesc.Width = 0;
    swapChainDesc.Height = 0;
    swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDesc.Stereo = false;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = NUM_BACK_BUFFERS;
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapChainDesc.Flags = 0;
    
    Microsoft::WRL::ComPtr<IDXGIFactory4> dxgiFactory;
    HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to create DXGI factory");
    }
    
    hr = dxgiFactory->CreateSwapChainForHwnd(
        commandQueue.Get(),
        hwnd,
        &swapChainDesc,
        nullptr,
        nullptr,
        &swapChain
    );
    
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to create swap chain");
    }
}

void DirectX12Renderer::createCommandQueue() {
    D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    
    HRESULT hr = device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue));
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to create command queue");
    }
}

void DirectX12Renderer::createDescriptorHeaps() {
    D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
    rtvHeapDesc.NumDescriptors = NUM_BACK_BUFFERS;
    rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    
    HRESULT hr = device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap));
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to create RTV heap");
    }
}

void DirectX12Renderer::createRenderTargets() {
    UINT rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    
    for (UINT i = 0; i < NUM_BACK_BUFFERS; ++i) {
        HRESULT hr = swapChain->GetBuffer(i, IID_PPV_ARGS(&renderTargets[i]));
        if (FAILED(hr)) {
            throw std::runtime_error("Failed to get swap chain buffer");
        }
        
        D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
        rtvHandle.ptr += i * rtvDescriptorSize;
        device->CreateRenderTargetView(renderTargets[i].Get(), nullptr, rtvHandle);
    }
}
```

### src/graphics/ShaderManager.h
```cpp
#pragma once
#include <d3d12.h>
#include <wrl/client.h>
#include <string>
#include <vector>

class ShaderManager {
public:
    ShaderManager(ID3D12Device* device);
    ~ShaderManager();

    ID3DBlob* compileShader(const std::string& filename, const char* entryPoint, const char* shaderModel);
    void createRootSignature();
    
private:
    Microsoft::WRL::ComPtr<ID3D12Device> device;
    Microsoft::WRL::ComPtr<ID3D12RootSignature> rootSignature;
};
```

### src/graphics/ShaderManager.cpp
```cpp
#include "ShaderManager.h"
#include <d3dcompiler.h>
#include <iostream>

ShaderManager::ShaderManager(ID3D12Device* device) : device(device) {
    createRootSignature();
}

ShaderManager::~ShaderManager() {}

ID3DBlob* ShaderManager::compileShader(const std::string& filename, const char* entryPoint, const char* shaderModel) {
    Microsoft::WRL::ComPtr<ID3DBlob> shaderBlob;
    Microsoft::WRL::ComPtr<ID3DBlob> errorBlob;
    
    HRESULT hr = D3DCompileFromFile(
        filename.c_str(),
        nullptr,
        nullptr,
        entryPoint,
        shaderModel,
        D3D10_SHADER_ENABLE_STRICTNESS,
        0,
        &shaderBlob,
        &errorBlob
    );
    
    if (FAILED(hr)) {
        std::string error = "Shader compilation failed for: ";
        error += filename;
        if (errorBlob) {
            error += "\n";
            error += static_cast<char*>(errorBlob->GetBufferPointer());
        }
        throw std::runtime_error(error);
    }
    
    return shaderBlob.Get();
}

void ShaderManager::createRootSignature() {
    // Simplified root signature - in a real engine this would be more complex
    D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
    rootSignatureDesc.NumParameters = 0;
    rootSignatureDesc.pParameters = nullptr;
    rootSignatureDesc.NumStaticSamplers = 0;
    rootSignatureDesc.pStaticSamplers = nullptr;
    rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
    
    Microsoft::WRL::ComPtr<ID3DBlob> signature;
    Microsoft::WRL::ComPtr<ID3DBlob> error;
    
    HRESULT hr = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to serialize root signature");
    }
    
    hr = device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature));
    if (FAILED(hr)) {
        throw std::runtime_error("Failed to create root signature");
    }
}
```

### src/graphics/TextureManager.h
```cpp
#pragma once
#include <d3d12.h>
#include <wrl/client.h>
#include <string>

class TextureManager {
public:
    TextureManager(ID3D12Device* device);
    
    ID3D12Resource* loadTexture(const std::string& filename);
    
private:
    Microsoft::WRL::ComPtr<ID3D12Device> device;
};
```

### src/graphics/TextureManager.cpp
```cpp
#include "TextureManager.h"
#include <d3dcompiler.h>
#include <dxgiformat.h>

TextureManager::TextureManager(ID3D12Device* device) : device(device) {}

ID3D12Resource* TextureManager::loadTexture(const std::string& filename) {
    // In a real implementation, this would load texture data and create a GPU resource
    // This is a placeholder for the actual implementation
    
    return nullptr;
}
```

### src/cuda/CudaManager.h
```cpp
#pragma once
#include <cuda_runtime.h>
#include <vector>

class CudaManager {
public:
    CudaManager();
    ~CudaManager();

    void initialize();
    void cleanup();
    void simulateParticles();
    
private:
    bool initialized;
    
    // CUDA memory pointers
    float* d_positions;
    float* d_velocities;
    int numParticles;
    
    void allocateCudaMemory();
    void freeCudaMemory();
};
```

### src/cuda/CudaManager.cpp
```cpp
#include "CudaManager.h"
#include <iostream>

CudaManager::CudaManager() : initialized(false), d_positions(nullptr), d_velocities(nullptr), numParticles(1000) {}

CudaManager::~CudaManager() {
    cleanup();
}

void CudaManager::initialize() {
    if (initialized) return;
    
    // Initialize CUDA context
    cudaError_t err = cudaSetDevice(0);
    if (err != cudaSuccess) {
        throw std::runtime_error("Failed to set CUDA device");
    }
    
    allocateCudaMemory();
    initialized = true;
}

void CudaManager::cleanup() {
    if (!initialized) return;
    
    freeCudaMemory();
    initialized = false;
}

void CudaManager::simulateParticles() {
    if (!initialized) return;
    
    // This would call a CUDA kernel to update particle positions
    // Example: updateParticlesKernel<<<blocks, threads>>>(d_positions, d_velocities, numParticles);
    
    // Synchronize to ensure completion
    cudaDeviceSynchronize();
}

void CudaManager::allocateCudaMemory() {
    size_t size = numParticles * 3 * sizeof(float); // Position data (x,y,z)
    
    cudaError_t err = cudaMalloc(&d_positions, size);
    if (err != cudaSuccess) {
        throw std::runtime_error("Failed to allocate CUDA memory for positions");
    }
    
    err = cudaMalloc(&d_velocities, size);
    if (err != cudaSuccess) {
        throw std::runtime_error("Failed to allocate CUDA memory for velocities");
    }
}

void CudaManager::freeCudaMemory() {
    if (d_positions) {
        cudaFree(d_positions);
        d_positions = nullptr;
    }
    
    if (d_velocities) {
        cudaFree(d_velocities);
        d_velocities = nullptr;
    }
}
```

### src/cuda/particle_kernel.cu
```cuda
// Example CUDA kernel for particle simulation
extern "C" void updateParticlesKernel(float* positions, float* velocities, int numParticles) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (idx < numParticles) {
        // Simple physics update
        positions[idx*3] += velocities[idx*3];
        positions[idx*3+1] += velocities[idx*3+1];
        positions[idx*3+2] += velocities[idx*3+2];
    }
}
```

### src/scene/GameObject.h
```cpp
#pragma once
#include <string>
#include <vector>

class GameObject {
public:
    GameObject(const std::string& name);
    virtual ~GameObject() = default;
    
    const std::string& getName() const { return name; }
    
    // Transform data
    float position[3];
    float rotation[3];
    float scale[3];
    
protected:
    std::string name;
};
```

### src/scene/GameObject.cpp
```cpp
#include "GameObject.h"

GameObject::GameObject(const std::string& name) : name(name) {
    // Initialize default values
    position[0] = position[1] = position[2] = 0.0f;
    rotation[0] = rotation[1] = rotation[2] = 0.0f;
    scale[0] = scale[1] = scale[2] = 1.0f;
}
```

### src/scene/Scene.h
```cpp
#pragma once
#include <vector>
#include <memory>
#include "GameObject.h"

class Scene {
public:
    Scene();
    ~Scene() = default;
    
    void addObject(std::unique_ptr<GameObject> object);
    const std::vector<std::unique_ptr<GameObject>>& getGameObjects() const { return objects; }
    
private:
    std::vector<std::unique_ptr<GameObject>> objects;
};
```

### src/scene/Scene.cpp
```cpp
#include "Scene.h"

Scene::Scene() {}

void Scene::addObject(std::unique_ptr<GameObject> object) {
    objects.push_back(std::move(object));
}
```

### src/main.cpp
```cpp
#include <iostream>
#include "cuda/CudaManager.h"
#include "scene/Scene.h"
#include "scene/GameObject.h"

int main() {
    try {
        // Initialize CUDA
        CudaManager cudaManager;
        cudaManager.initialize();
        
        // Create scene
        Scene scene;
        
        // Add some game objects
        auto player = std::make_unique<GameObject>("Player");
        scene.addObject(std::move(player));
        
        auto enemy = std::make_unique<GameObject>("Enemy");
        scene.addObject(std::move(enemy));
        
        // Simulate particles
        cudaManager.simulateParticles();
        
        std::cout << "Game initialized successfully!" << std::endl;
        
        return 0;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return -1;
    }
}
```

### CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.8)
project(GameEngine)

set(CMAKE_CXX_STANDARD 17)

# Find required packages
find_package(CUDA REQUIRED)
find_package(DirectXSDK REQUIRED)

# Set include directories
include_directories(${CUDA_INCLUDE_DIRS})
include_directories(${DirectXSDK_INCLUDE_DIR})

# Add executable
add_executable(${PROJECT_NAME}
    src/main.cpp
    src/cuda/CudaManager.cpp
    src/scene/GameObject.cpp
    src/scene/Scene.cpp
    src/graphics/ShaderManager.cpp
    src/graphics/TextureManager.cpp
    src/graphics/Renderer.cpp
)

# Link libraries
target_link_libraries(${PROJECT_NAME} 
    ${CUDA_LIBRARIES}
    d3d12.lib
    dxgi.lib
    d3dcompiler.lib
)

# Set CUDA properties
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_SEPARABLE_COMPILATION ON)
```

This code provides a basic structure for a modern game engine that combines DirectX 12 and CUDA. The architecture includes:

1. **Core Engine Components**:
   - CudaManager for GPU compute with CUDA
   - DirectX 12 rendering system
   - Scene management
   - Object hierarchy

2. **Key Features**:
   - CUDA particle simulation
   - DirectX 12 rendering pipeline
   - Modular architecture
   - Error handling and resource management

3. **Build System**:
   - CMake configuration for cross-platform builds
   - Proper linking of DirectX and CUDA libraries

To build this project, you'll need:
- Visual Studio (for Windows)
- CUDA Toolkit
- DirectX SDK
- CMake

The implementation provides a foundation that can be extended with additional features like audio, networking, physics, etc. The code is modular and follows modern C++ practices with RAII for resource management.

Note: This is a simplified framework. A production game engine would include many more features and robust error handling.
```

   
Quote
Share: