This is where I’ll keep track of my progress throughout Games 6320 as I build a game engine from scratch using C++. I’ll write about each assignment, what I worked on, the tools I used - such as Visual Studio, GitLab, Direct3D, and OpenGL, Maya, Lua - any problems I ran into, and what I learned along the way.
Later in the class, I’ll choose an engine feature, such as graphics or input, and combine it with another student’s work to create a small game. By the end, this page should show how both my engine and my understanding of C++, game engine systems, graphics programming, and development tools grew throughout the course.
This download contains the x64 Direct3D Release build for Windows.
Controls: Press Escape to close the game.
This assignment was mostly about getting familiar with the EAE6320 engine and figuring out how all the Visual Studio projects work together. I created a Graphics static library, configured the platform-specific graphics code, added the necessary project references and dependencies, and created my own game project from the provided example.
The biggest thing I learned is that getting a game to run involves a lot more than writing game code. The compiler settings, project references, build dependencies, working directories, and asset-building system all have to be set up correctly. If even one of those pieces is wrong, the game might compile but still fail to build or run.
I named my project "Angelina's Game" and changed the window icon to the provided alien icon. The game displays a triangle that continuously changes between shades of pink and purple.
I created a new fragment shader for the animation. It uses the elapsed simulation time and a sine function to create a value that smoothly moves back and forth. That value changes the red, green, and blue channels of the triangle over time.
The shader was implemented for both Direct3D and OpenGL. Direct3D uses float4 for the final color, while OpenGL uses vec4, but both versions use the same color calculation and behave the same way!
I added two custom messages to the generated log file:
Angelina's Game was successfully initialized!
Angelina's Game was successfuly cleaned up!
The first message is written after the game successfully initializes. The second message is writen when the game closes and finishes cleaning up. Seeing both messages in the log helped me confirm that the application completed both parts of its lifecycle correctly.
The engine is divided into several smaller Visual Studio projects, with each one handling a different job. The new Graphics project contains the code responsible for drawing things on the screen.
Only the Application project needed to add a reference to Graphics. Application directly uses drawing functions implemented in the Graphics project, so Visual Studio needs the reference to connect their compiled code when building the game.
ShaderBuilder also mentions something from Graphics called Graphics::eShaderType, which identifies the type of shader being built. However, ShaderBuilder didn’t need a project reference because that information is fully defined in a shared header file. In simpler terms, ShaderBuilder only needs to read a definition, while Application needs to use working code from the compiled Graphics library.
This taught me something important: mentioning a name from another project doesn’t always mean that a reference is needed. A reference is only necessary when one project needs to connect to compiled code from another project.
The engine is split into several smaller Visual Studio projects, including Application, Graphics, Logging, UserInput, and Assets. Each project has its own job, which makes it easier to figure out where different parts of the engine belong.
At first, the solution felt a little overwhelming. It contains a lot of projects, settings, references, and dependencies, along with separate build options such as Debug, Release, x86, and x64. After working through the assignment, though, the organization started to make more sense. Dividing the engine into smaller libraries keeps unrelated systems from getting mixed together, and each project only needs to connect to the systems it actually uses.
I also like how the engine handles different graphics platforms. The Direct3D and OpenGL implementations are stored in separate files, while the code they share stays in common files. This keeps the platform-specific code organized without requiring the entire Graphics system to be written twice.
The engine uses consistent naming rules, detailed comments, and clear dividers between sections of code. Names such as cMyGame, iApplication, and sContext give clues about the kind of code being used-for example, whether something is a class, interface, or structure. The comments were especially helpful when I was exploring parts of the engine I hadn’t worked with before.
At first, the style felt more detailed and wordy than what I was used to. There were a lot of prefixes, macros, and sections that only compile for certain platforms or settings. Once I became more familiar with the style, its consistency made the code easier to follow. Whenever I wasn’t sure how to do something, I could usually find a similar example in another part of the engine.
One concept that confused me at first was why including a Graphics header wasn’t always enough to use the Graphics system. The header allowed the compiler to see the relevant function declarations, but the linker still needed the compiled function definitions from the Graphics library.
This is why the Application project needed a reference to the Graphics project. Without that reference, the source code could compile but fail during linking because the linker could not find the Graphics function definitions. ShaderBuilder was different because it only used an enum that was fully defined in a header, so it did not need to link to a separately compiled function.
Working through this helped me better understand the difference between compile-time and link-time dependencies. This understanding will help me troubleshoot build problems more efficiently. If the compiler does not recognize a type or function, I will know to check the included headers and declarations. If the code compiles but produces an unresolved external symbol, I will know to check the library references and compiled function definitions.
This will also help me organize larger projects by avoiding unnecessary dependencies. Limiting references to what each project actually needs can reduce build times, prevent complicated dependency chains, and make the code easier to reuse or modify later.
Based on the first lecture and this assignment, I expect this class to teach me how the different systems inside a game engine work together. Instead of treating the engine like a black box, I’ll get to work directly with rendering, assets, application code, platform-specific implementations, and the build process.
I’m especially interested in learning more about rendering and shaders. Even though this assignment only changed the color of a triangle, it was interesting to see how simulation time could be passed to a shader and used to animate something on the GPU.
I also want to become more comfortable diagnosing Visual Studio configuration and linker problems. Fixing the difference between my Debug and Release builds was frustrating, but it helped me understand how to read the errors and figure out what was actually wrong.
By the end of the class, I hope to understand the engine well enough to confidently modify it, focus on an engine feature that interests me, and combine my work with another student’s system to create a small game.
This download contains the x64 Direct3D Release build for Windows.
Controls: Press Escape to close the game.
The purpose of this assignment was to learn how to organize the graphics code so that the rest of the engine can work with Direct3D and OpenGL through the same platform independent interfaces. Instead of keeping all of the geometry, shader, and rendering code together, the goal was to separate it into reusable parts that could eventually support multiple meshes and effects.
To do this, I created a mesh class that handles initializing, drawing, and cleaning up geometry. I also created an effect class that handles loading and binding shaders, managing render state, and cleaning up its graphics resources. The platform-specific implementations remain separate for Direct3D and OpenGL, but both APIs now use the same simple interface in the main graphics code.
I then added a second triangle to create the required rectangle, making sure to use the correct winding order for each graphics API. After completing the required geometry, I tried the optional challenge and replaced the rectangle with a small house made from three triangles.
The required mesh consists of two triangles that form a rectangle in the upper-right corner. Like in the previous assignment, the shader animates the rectangle’s color between pink and purple.
This code is identical in Graphics.d3d.cpp and Graphics.gl.cpp. Both files use the same platform independent interfaces to bind the effect and draw the mesh. The Direct3D and OpenGL implementation details are hidden inside the effect and mesh classes, which keeps the main rendering code short and easy to use.
There are still some differences between Graphics.d3d.cpp and Graphics.gl.cpp. Direct3D works with things like a device, device context, render target, and vertex buffers, while OpenGL mostly uses functions and numeric IDs for its graphics resources. They also clear the screen differently: Direct3D uses ClearRenderTargetView(), while OpenGL uses glClear(). Their winding orders are different too, so I had to list the same vertex positions in a different order.
In the future, more of the shared code could probably be moved into one platform independent Graphics.cpp file. Both versions follow the same general steps for submitting frame data, updating constant buffers, rendering, initializing, and cleaning up. A shared file could handle those steps and only call separate Direct3D or OpenGL functions when something actually needs to be done differently.
The new mesh and effect interfaces are already a step in that direction. Both graphics files can simply call s_effect.Bind() and s_mesh.Draw() without needing to know how those functions work for each graphics API.
This capture shows the Direct3D render target immediately after ClearRenderTargetView() runs. The render target is completely black because the mesh has not been drawn yet.
This capture shows the render target after Draw() draws the rectangle. The Pipeline Stages view shows the mesh as a wireframe, including the diagonal edge that separates the rectangle into two triangles.
This capture shows the OpenGL render target immediately after glClear() runs. The frame is black because the mesh has not been drawn yet
This capture shows the OpenGL render target after glDrawArrays() draws the animated rectangle.
The Mesh Viewer shows the 6 vertices used by glDrawArrays(). The wireframe view makes the two triangles that form the rectangle visible.
After finishing the required rectangle, I tried the optional challenge by turning the mesh into a small house made from 3 triangles. 2 triangles form the rectangular body, and 1 creates the roof.
To make it, I changed the vertex count and added three more vertex positions for the roof. I used the same coordinates in Direct3D and OpenGL, but reversed the order of the vertices because the two APIs use different winding orders. I kept the animated shader, so the entire house continues changing between pink and purple.
This assignment helped me understand why it is useful to hide the differences between Direct3D and OpenGL behind the same interface. The two APIs handle graphics resources differently, but the main graphics code can now use the same simple calls to bind an effect and draw a mesh. Once I saw that working, the reason for separating the mesh and effect code made a lot more sense.
The vertex winding order was a little confusing at first. I could use the same coordinates for both platforms, but I had to list the vertices in a different order for Direct3D and OpenGL. When only part of my rectangle appeared, RenderDoc helped me see how the vertices were being turned into two triangles.
For the optional challenge, I made a house using three triangles. This was a fun way to practice creating something more interesting than a rectangle. In the future, I would like to learn how to load mesh data from a file instead of writing every vertex directly in the code because that would make it much easier to create more complicated shapes.
I also want to thank Dallin for helping me figure out the problem I was having with Visual Studio Graphics Analyzer. Visual Studio 2022 could create a Direct3D frame capture, but it would get stuck saying that Graphics Analyzer was still shutting down whenever I tried to open the frame.
Dallin let me know that the capture was working for him in Visual Studio 2019, which helped me realize that the Visual Studio version might be the issue. I installed Visual Studio 2019 and temporarily changed the projects from the v143 toolset to v142 so they could build in the older version. After that, I was finally able to open the capture and take the required Direct3D screenshots. His suggestion saved me from continuing to troubleshoot the wrong part of my graphics code!
This download contains the x64 Direct3D Release build for Windows.
Controls: Press Escape to close the game.
For this assignment, I moved the main graphics code into one shared Graphics.cpp file. Before this, I had separate files for Direct3D and OpenGL that did a lot of the same things. I also changed the mesh and effect classes so I can pass in the data I want to use, instead of having it hard coded inside them.
My meshes now use uint16_t indices, and the mesh code handles the winding order for each platform. I can also choose the shaders and render settings when creating an effect. For the scene, I kept my pink and purple house and added a white crescent moon. I chose a navy background because I liked how it looked with those colors!
The two old Graphics files had a lot of shared code, so I moved that code into Graphics.cpp and deleted Graphics.d3d.cpp and Graphics.gl.cpp. Graphics.cpp now handles things like updating frame data, binding effects, and drawing meshes without using any platform specific API calls, types, or macros.
For the parts that still need to work differently on each platform, I created a RenderSurface interface. I declared Initialize(), CleanUp(), Clear(), and Present() in RenderSurface.h, next to Graphics.h. I left Graphics.h and its original sInitializationParameters struct unchanged.
I put the Direct3D implementations in Direct3D/RenderSurface.d3d.cpp and the OpenGL implementations in OpenGL/RenderSurface.gl.cpp. The project settings choose which file gets compiled. This lets Graphics.cpp make the same calls on both platforms, while each implementation handles its own graphics API.
I set the background color in Graphics.cpp using four values for red, green, blue, and alpha. These give me the navy background in my screenshot. I pass that color to RenderSurface::Clear(), and the selected platform implementation handles clearing the buffers. This means I can change the background color in one place for both platforms.
To create an effect, I pass in the paths for the vertex shader and fragment shader. I can also provide render state bits to control things like transparency, depth testing, depth writing, and drawing both sides of a triangle. That argument defaults to zero if I leave it out.
Both of my effects use the standard vertex shader. The house uses my animated fragment shader, and the moon uses the standard white fragment shader. I left the render state bits at zero for this scene. The effect class handles loading the shaders and setting up the render state for whichever platform I'm using.
To create a mesh, I pass in the vertex positions, the number of vertices, an array of uint16_t indices, and the number of indices. Each vertex has an x, y, and z position, and every group of three indices makes one triangle. My house uses 7 vertices and 9 indices to make three triangles. The 2 triangles forming the body share vertices, so I don't have to repeat all of their positions.
I give the indices in counterclockwise order on both platforms. OpenGL uses that order directly, and my Direct3D mesh code swaps the last 2 indices of each triangle. This means I don't have to change the winding order myself in Graphics.cpp.
The mesh uses these arrays to create its GPU buffers. After initialization, the mesh object only keeps the resource references and index count it needs for drawing and cleanup.
Direct3D - Debug x64
OpenGL - Debug Win32
I measured sizeof(eae6320::Graphics::cEffect) using Visual Studio’s debugger. One effect occupies 48 bytes in Direct3D x64 and 8 bytes in OpenGL x86. These measurements include the object’s members and alignment padding, but exclude separately allocated shader resources and GPU memory.
The render state stores three pointers and one byte of settings in Direct3D. In OpenGL, it only stores the settings byte. The compiler also adds padding where it's needed for alignment.
My effect keeps different data depending on the platform. Direct3D keeps pointers to both shaders because it needs them when binding the effect. OpenGL keeps the linked program ID. Both versions also keep a render state object for settings like transparency, depth testing, and triangle culling.
In Direct3D x64, the two shader pointers take up 16 bytes. The render state object takes up another 32 bytes, including its 3 pointers, settings byte, and padding. That adds up to 48 bytes per effect.
In OpenGL Win32, the render state settings take up 1 byte and the program ID takes up 4 bytes. With 3 bytes of padding, the total is 8 bytes. This version no longer keeps the two shader pointers.
I was able to shrink the OpenGL effect from 16 bytes to 8 bytes by removing the two shader pointer members. It now uses temporary shader pointers during initialization. Once the program links successfully, I detach the shaders and release those temporary references. The linked program is still available for drawing.
Direct3D still needs its shader references for binding, so I kept them there. Neither version keeps the shader paths or an extra copy of the initialization settings. With the members I'm using now, changing their order wouldn't make the object smaller because of alignment.
Mesh Measurements
Platform: sizeof(cMesh)
Direct3D- Debug x64 32 bytes
OpenGL - Debug Win32 16 bytes
The debugger screenshots in the Effect Memory section also show my mesh measurements: 32 bytes in Direct3D Debug x64 and 16 bytes in OpenGL Debug Win32.
In Direct3D, my mesh keeps a vertex format pointer and pointers to the vertex and index buffers. It uses these for drawing and releases them during cleanup. In my x64 build, those 3 pointers take up 24 bytes. The index count takes up 2 bytes, and padding adds another 6 , giving me 32 bytes per mesh.
In OpenGL, my mesh keeps 3 IDs: one for the vertex array, one for the vertex buffer, and one for the index buffer. The vertex array keeps track of the vertex setup and index buffer binding. The buffer IDs also let me delete the buffers during cleanup. These IDs take up 12 bytes altogether. Adding the 2 byte index count and 2 bytes of padding gives me 16 bytes per mesh.
Both versions keep the index count so the draw call knows how many indices to use. Their sizes are different because they store different types of resource references. The Direct3D x64 pointers are 8 bytes each, while the OpenGL IDs are 4 bytes each.
My mesh already keeps just the resource references and index count it uses for drawing and cleanup. It doesn't keep copies of the input vertices or indices. The temporary array used to change Direct3D's winding order is also released after initialization.
With these members, rearranging them wouldn't save space because of alignment. Making the object smaller would mean changing how I manage the resources. The house and moon have different geometry, but their mesh objects take up the same amount of memory because the actual geometry is stored separately.
Here's my final scene! The house still changes between pink and purple, and the crescent moon uses the standard white fragment shader. I moved the house down and put the moon in the top left corner. The navy background helps both shapes stand out.
This assignment helped me understand why it's useful to have shared interfaces for Direct3D and OpenGL. I can now change the main rendering code in one Graphics.cpp file, while the platform implementations handle the details that need to be different.
Working with indices also made it clearer how triangles can share vertices. Moving the winding order changes into the mesh code means I don't have to think about which platform I'm using every time I create a shape.
Checking the memory sizes in the debugger was useful too. My OpenGL effect went from 16 bytes to 8 bytes after I removed the shader pointers it no longer needed. It also helped me understand that sizeof() includes the object's members and padding, but doesn't include the graphics resources stored elsewhere.
This download contains the x64 Direct3D Release build for Windows.
Controls:
-Press Escape to close the game.
- Hold C to draw the mesh with a different effect
- Hold H to stop drawing a mesh
(Release either key to restore the default state)
For this assignment, I worked on how the application and render threads share data. MyGame now chooses the background color and submits the meshes and effects for each frame. Graphics caches those submissions until the render thread is ready, using 2 frame structures so the application can prepare 1 frame while the renderer works on another. I also made my meshes and effects reference counted so they stay valid until they are no longer needed!
In my previous assignment, I had a house and a crescent moon. After seeing Tony’s MegaMan example in class, I wanted to get more creative and try making Kirby! I replaced the house with a pixel art Kirby mesh and added a yellow star!
This is the default scene. Both meshes are visible, & MyGame submits the background color. Kirby uses the normal RGB effect while the star uses its own effect.
Holding C changes the effect submitted with Kirby’s mesh. The geometry stays the same, but his colors become blue toned!
When I hold H, MyGame stops submitting the star. Kirby stays visible, but the star disappears until I release the key.
I made Kirby using a 26 x 26 grid of characters. Each character represents one colored square. For example, P is pink, R is red, K is black, and . means the space is empty. I also used other letters for Kirby’s shading and highlights.
My code goes through the grid one row and column at a time. If it finds a . , it skips that spot. If it finds one of my color characters, it creates a square at that position.
To figure out where each square should go, I use its row and column from the grid. The column controls the X position and the row controls the Y position.
I use half of the grid width & height to center Kirby. I also move Kirby down by -0.05 on the Y axis.
For each colored square, I calculate four positions, left, right, top, and bottom. These values give me the four corners I need to create the square.
For a more detailed breakdown of how I calculated the pixel positions & connected the math to my C++ code, click here to view my spreadsheet
Once I know the position of a square, I create 4 vertices, bottom left, bottom right, top right, and top left.
The graphics system draws triangles, so I use 6 indices to connect those 4 vertices into 2 triangles. Together, the 2 triangles make 1 square.
Instead of calculating every square manually, I put this process inside my row and column loops. This lets the code repeat the same calculations for every colored character in the grid.
After finding a colored character, I use a switch statement to decide which RGB values it should have.
For Kirby, I used P for pink, R for red, K for black, B for burgundy, W for white, and S for rose. I give the same RGB values to all four vertices of that square.
I made the star using the same process as Kirby!
The star uses a smaller 13 × 13 grid with a pixel size of 0.035. I also use centerX = 0.68 and centerY = 0.63 to move the star above and to the right.
The star uses K for black, Y for yellow, and W for the lighter highlight. Just like Kirby, every colored square is made using 4 vertices and 2 triangles.
The calculations for the star are also included in my calculation sheet linked above.
After the loops finish creating all of the squares, I use the vertices and indices to create the final meshes. Kirby is stored as 1 mesh and the star is stored as another mesh.
I create both meshes during Initialize(), so I only have to build the geometry once. When the game renders, I can use the finished meshes instead of rebuilding every square each frame.
The three background values are red, green, and blue. I can change them in MyGame without editing Graphics.
m_Mesh is my Kirby mesh. MyGame chooses either the normal or ice effect and passes it into SubmitMeshAndEffect() along with that mesh. The first argument says which mesh to draw, and the second says which effect to use.
The star gets its own submission. If m_hideStar is true, MyGame skips that call. Graphics does not need special code for hiding a star - it just draws the objects that were submitted!
These values follow whether the keys are currently held, so releasing a key automatically restores the normal state.
There are two steps here: input changes the game state, and then the submission function uses that state to decide what to draw. This keeps the input decisions in MyGame instead of putting them in Graphics.
The application loop and rendering run on separate threads. The application can prepare the next frame while the render thread works on the previous one.
Because of this, game code cannot render things immediately. The renderer needs a complete and stable set of data for a frame that the application thread is no longer modifying. Therefore, all of the data needed to render a frame is cached before the render thread uses it.
The engine uses two sDataRequiredToRenderAFrame structures so that one can collect data for the next frame while the other is being rendered. Synchronization events control when the frame data is finished and when the two structures can switch roles.
Direct3D - Debug x64
OpenGL - Debug Win32
After making my mesh reference counted, sizeof(cMesh) is 32 bytes in Direct3D x64 and 16 bytes in OpenGL Win32.
In Direct3D, my mesh stores a vertex format pointer, a vertex buffer pointer, an index buffer pointer, the index count, and the reference count. The three pointers require 24 bytes total. The 2 uint16_t values require another 4 bytes, and alignment adds 4 bytes of padding, resulting in 32 bytes.
In OpenGL, the mesh stores three GLuint IDs for the vertex array, vertex buffer, and index buffer. These require 12 bytes. The index count and reference count each require 2 bytes, resulting in exactly 16 bytes.
The members are already arranged efficiently. Reordering the small values between the Direct3D pointers could add extra padding and increase its size. A similar inefficient order could increase the OpenGL mesh from 16 to 20 bytes, so I kept the current order
<-- These are the private data members stored by my mesh. The platform specific section stores either Direct3D resource pointers or OpenGL object IDs. Every mesh also stores its index count and a reference count
After making my effect reference counted, sizeof(cEffect) is 56 bytes in Direct3D x64 and 8 bytes in OpenGL Win32.
In Direct3D, my effect stores pointers to the vertex and fragment shaders, an embedded render state object, and a reference count. The two shader pointers require 16 bytes. The remaining size comes from the embedded render state, the 2 byte reference count, and alignment padding, resulting in 56 bytes total.
In OpenGL, my effect stores a 4-byte program ID, its render state, and a 2 byte reference count. The members are ordered so they fit into 8 bytes. Placing the program ID after the smaller members could introduce padding and increase the effect to 12 bytes, so I kept the current order.
<-- These are the private data members stored by my effect. Direct3D stores pointers to the vertex and fragment shaders, while OpenGL stores a shader program ID. Both platforms also store the render state and reference-count data. The reference count storage is declared by the EAE6320_ASSETS_DECLAREREFERENCECOUNT() macro.
My Graphics project stores two sDataRequiredToRenderAFrame structures. One structure can be filled by the application thread while the render thread uses the other.
As shown in my debugger measurements above, one frame structure is 424 bytes in Direct3D x64. This gives a total frame data budget of:
2 × 424 = 848 bytes
One frame structure is 292 bytes in OpenGL Win32, giving a total of:
2 × 292 = 584 bytes
These totals include the mesh and effect pointers stored inside each frame structure. I did not include the separate memory occupied by the mesh and effect objects themselves because the frame structures do not own copies of those assets. They only store references to shared objects, and including the complete objects for both frames would count the same resources more than once.
This assignment was honestly the biggest pain so far, but I learned a lot from it. The hardest part was getting the application thread & render thread to share frame data correctly. I learned that MyGame cannot just draw something immediately. It has to submit everything for the frame first so the render thread gets a complete set of data that will not change while it is using it.
Reference counting was also confusing at first, but it makes more sense now. Since the frame data only stores pointers to the meshes and effects, those objects have to stay alive until the render thread is finished with them. The reference count keeps them from being deleted too early and allows them to be cleaned up once nothing is using them.
Making Kirby was probably the most fun part. I learned how to use a grid to turn each colored pixel into 4 vertices and 6 indices, creating 2 triangles for every square. This was much easier than manually calculating every vertex. I also learned the annoying way that the order of class members matters because padding and alignment can change how much memory a class uses.
This download contains the x64 Direct3D Release build for Windows.
Controls:
Arrow keys - move the player game object
WASD - move the camera
Space - change the game object mesh
C - apply the alternate effect
H - hide the star mesh when held
For this assignment, I added a camera and game objects that can be rendered. You can move the object with the arrow keys, move the camera with WASD, and press Space to switch the object’s mesh. Movement uses velocity and fixed step updates, and extrapolation helps everything look smooth when it renders.
I also added per draw call data so each object can have its own position and orientation in the world. The game sends each object’s mesh, effect, and predicted transform to the Graphics system. Finally, I cleaned up the shaders so Direct3D and OpenGL share the constant buffer definitions and most of the shader calculations.
My sGameObject holds its rigid body state, a mesh pointer, and an effect pointer. The rigid body state keeps track of things like position, orientation, velocity, and acceleration, so I can update the object and predict where it’ll be when it renders. The mesh controls its shape, and the effect controls how it looks. Keeping them together also lets me change the object’s appearance without affecting how it moves.
For each visible object, the game sends Graphics its mesh, effect, and predicted local toworld transform. That keeps the game code pretty simple: it just says what to draw and where to draw it. Graphics only stores what it needs for the draw call, so it doesn’t need the whole game object or things like velocity.
Visual Studio Watch window showing that the draw call constant data remains 64 bytes and the complete cached draw call entry increases to 80 bytes in the Debug x64 build because pointers are larger.
Visual Studio Watch window showing that the draw call constant data is 64 bytes and the complete cached draw call entry is 72 bytes in the Debug x86 build.
For each draw call, Graphics caches a mesh pointer, an effect pointer, and a 4x4 local to world transformation matrix. The matrix contains 16 floats and requires 64 bytes. In the Debug x86 build, the two pointers require 4 bytes each, resulting in 72 bytes per cached draw call. In the Debug x64 build, the pointers require 8 bytes each, resulting in 80 bytes per cached draw call. The GPU constant buffer data remains 64 bytes on both platforms.
If the game renders more often than the simulation updates, some frames would show the object in the same spot before it suddenly jumps forward. That would look choppy.
Extrapolation smooths that out by using the object’s velocity & the time since the last simulation update to predict where it should appear. It only changes where the object is drawn, not its actual simulation state.
The camera keeps a rigid body state for movement, along with the values needed for its perspective view. Each frame, I use its predicted position and orientation to build the world to camera matrix. Then I use the field of view, current window aspect ratio, and near and far clipping distances to build the projection matrix. I send both matrices to Graphics through Graphics::SubmitCamera().
I put the frame and draw-call constant buffers in shaders.inc so they’re only defined once. Platform specific macros handle the differences between HLSL and GLSL types and matrix operations. The inputs, outputs, and function signatures still differ by platform, but the actual shader calculations are shared!
This assignment helped me understand how movement, cameras, and rendering fit together in a game engine. Using velocity instead of changing positions directly makes movement more consistent, and it’ll make things like acceleration, gravity, and collisions easier to add later.
I also learned why prediction matters. The game doesn’t always render at the same rate that the simulation updates, so predicting where an object should appear helps its movement look smooth without changing its actual simulation state.
Working with camera and object transforms helped me understand how objects can move around a 3D scene without changing their mesh data. The camera can move independently, so I can show the same scene from different viewpoints.
Separating game objects from Graphics keeps things organized too. The game can track movement, health, and behavior, while Graphics only gets what it needs to draw each object. Sharing shader code between Direct3D and OpenGL should also make future changes easier since I won’t have to update the same calculations twice.