Notifications
Clear all
Topic starter 30/08/2025 5:38 pm
# 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. ```