
Crafting immersive experiences that bridge players and game worlds.
Gameplay and Engine programmer
Personal Game Engine
A robust game engine built in C++, featuring a complete DirectX11 rendering pipeline and support for both 2D and 3D game development. In addition to core systems such as input, rendering, audio, and system management, the engine includes a comprehensive math and physics library. It is further enhanced with advanced features like a developer console, event system, multithreaded job system, and more.
Games Developed:




Rendering:
The engine uses a forward rendering pipeline built on Direct3D 11. Each frame begins by clearing the screen and setting up the camera’s view and projection matrices. Scene objects are rendered using dynamic vertex and index buffers, with support for skinned and static meshes. Lighting and shading are handled through HLSL shaders with configurable blend, rasterizer, and depth states. Emissive surfaces are drawn to an offscreen texture and processed with a multi-pass Gaussian blur. The result is composited back onto the main render target using additive blending before presenting the final image to the screen.
Mesh Input:
Meshes and geometric shapes are decomposed into triangles, with each triangle’s vertices arranged in counter-clockwise order within the vertex array to define the front-facing surface. And during rendering vertex array is copied from CPU to GPU as vertex buffer for drawing.
Index Buffer:
Index buffers are used to eliminate redundant vertices, reducing memory usage during vertex buffer creation, CPU-to-GPU transfers, and drawing.
For example, if three triangles all share the same vertex, that vertex would normally need to be duplicated 2 times. With an index buffer, we can store the vertex once and reference it multiple times using simple integer indices—e.g., triangle vertices at positions 564, 789, and 1024 can all point to vertex 200—avoiding unnecessary data duplication.


Networking:
The engine uses TCP sockets on Windows (Winsock2). The system supports asynchronous, non-blocking I/O to ensure consistent frame rates even during heavy network traffic.
Client/Server Setup:
The system supports two distinct modes: Client and Server, set at initialization via config.
Rather than sharing behavior, each mode has its own branching logic. On the server side, it binds a socket to a given IP:port and begins listening for clients. On the client side, it creates a TCP socket and attempts to connect.
To avoid locking the main thread, sockets are explicitly marked non-blocking, and everything (connect, accept, send, recv) is polled each frame.
Winsock is manually started via WSAStartup and shut down via WSACleanup, giving me direct control over socket lifecycle.


Example server config
(overwritting game config)
Example game config
Network Connection:
Connection is treated as a state machine.
-
For clients: Disconnected → Connecting → Connected
-
For servers: Listening → Connected
The client uses connect() once, then falls back to select() to check when the connection completes or fails. This makes the system tolerant to slow or flaky connections without stalling the main thread.
The server accepts a connection using accept() in non-blocking mode. Once a client connects, it updates its internal state and begins processing data.
Error states are caught through WSAGetLastError() and gracefully handled. If a connection fails or disconnects mid-session, the system cleans up the socket and resets back to its initial state.
Data Transfer and Parsing:
Data is managed through two fixed-size buffers: m_sendBuffer and m_recvBuffer, and two queues: m_sendQueue and m_recvQueue.
Messages are pushed into m_sendQueue, and flushed out in chunks each frame depending on how much buffer space remains.
The protocol is deliberately minimal:
-
Each message is a null-terminated string ('\0')
-
Messages are parsed from the receive queue based on this delimiter
-
Parsed message are sent to Dev Console and executed as commands
Multithreading Job System:
A multithreaded job system that distributes tasks across worker threads to keep the main thread free. This system uses a lightweight, queue-based model to track job status through all stages — queued, executing, completed, and retrieved.
Job System Setup:
The system begins by detecting the hardware thread count and spawns that many worker threads — minus one — to avoid starving the main thread. This decision ensures that the game loop stays responsive even during heavy multithreaded computation.
Each worker thread is a JobWorker object that owns its own std::thread. These threads live for the duration of the engine and pull tasks from a shared job queue.
Multithreaded Job Execution:
The system runs in a three-stages pipeline:
-
QueueNewJob() adds jobs to a synchronized m_queuedJobs list.
-
ClaimFirstJob() lets workers pull from the queue and start executing.
-
FinishJob() moves completed jobs into a m_completedJobs queue, ready for the main thread to retrieve results.
This design uses fine-grained locking (one mutex per queue) to minimize contention. A worker thread polls for work in a loop, sleeps briefly when no jobs are available, and exits cleanly once the system is flagged for shutdown.
Data Flow and Job Lifecycle:
Jobs that are added to the job system move through multiple queues as their state evolves:
-
QUEUED: Waiting in m_queuedJobs
-
EXECUTING: Being worked on by a thread
-
COMPLETED: Results ready to be collected
-
RETRIEVED: Handled and removed by the main thread
This model makes it easy to reason about job states and track progress in debugging or profiling sessions. Also functions like HasUnfinishedJob() gives the game engine an easy way to check whether background processing is still underway — useful for controlling loading screens or waiting on asset preprocessing before initializing the game .

