This is the write-up for Assignment 03 of GAMES 6320 (Game Engineering II). Assignment 02 introduced cMesh and cEffect but still had platform-specific Graphics.d3d.cpp and Graphics.gl.cpp files. Assignment 03 removes those two files completely by moving the remaining platform-specific frame work behind a small sContext abstraction. It also adds uint16_t index buffers to cMesh, makes both cMesh and cEffect initialize from caller-provided data, and renders two objects with two effects.


Download

Release build (Direct3D x64): MyGame_Assignment03_Release_x64.zip

The ZIP contains a Direct3D Release build (x64). Unzip it and run MyGame.exe.
Controls: none for this assignment (press Esc to close the window).


What changed

Assignment 03 removes the last two duplicated graphics frame files (Graphics.d3d.cpp and Graphics.gl.cpp) and replaces them with a single Graphics.cpp that contains no platform-specific macros or API calls.

The project now has:

  • one platform-independent Graphics.cpp;
  • indexed meshes using uint16_t indices;
  • effect initialization driven by caller-provided data;
  • mesh initialization driven by caller-provided vertex/index data;
  • two meshes and two effects, drawn with two distinct colors;
  • a configurable clear color instead of hard-coded black.

Platform-independent Graphics.cpp

New interfaces

I added the following platform-independent interfaces:

Interface Declared in Platform implementation
Graphics::sColor and Graphics::SubmitClearColor() Engine/Graphics/Graphics.h / Graphics.cpp shared
sContext::InitializeViews() Engine/Graphics/sContext.h Direct3D/sContext.d3d.cpp, OpenGL/sContext.gl.cpp
sContext::ClearBackBuffer() Engine/Graphics/sContext.h Direct3D/sContext.d3d.cpp, OpenGL/sContext.gl.cpp
sContext::Present() Engine/Graphics/sContext.h Direct3D/sContext.d3d.cpp, OpenGL/sContext.gl.cpp
sContext::UsesLeftHandedWinding() Engine/Graphics/sContext.h Direct3D/sContext.d3d.cpp, OpenGL/sContext.gl.cpp

The Direct3D render-target/depth views also moved out of Graphics.d3d.cpp and into Direct3D/sContext.d3d.cpp. OpenGL simply implements the same interface with glClearColor(), glClear(), and SwapBuffers().

Clear color

Graphics.cpp specifies the background color and submits it through the new platform-independent interface:

constexpr eae6320::Graphics::sColor s_clearColor = { 0.0f, 0.0f, 0.25f, 1.0f };

At render time, the code in Graphics.cpp uses only the context interface:

sContext::g_context.ClearBackBuffer( dataRequiredToRenderFrame->clearColor );

Changing s_clearColor to { 1, 0, 0, 1 }, { 0, 1, 0, 1 }, or { 0, 0, 1, 1 } changes the background to red, green, or blue respectively.

Effect initialization with data

The effect interface no longer hard-codes shader paths:

struct sEffectData
{
const char* vertexShaderPath = nullptr;
const char* fragmentShaderPath = nullptr;
uint8_t renderStateBits = 0;
};

cResult Initialize( const sEffectData& i_effectData );

The caller must provide:

  • a path to a vertex shader;
  • a path to a fragment shader;
  • the render-state bits for this effect.

These values are used only during initialization. cEffect stores the loaded shader objects and render-state object, not the paths or the raw input arrays.

This is the relevant code in Graphics.cpp:

const sEffectData effectData_animated =
{
s_vertexShaderPath,
s_fragmentShaderPath_animated,
s_renderStateBits,
};
if ( !( result = s_effect_animated.Initialize( effectData_animated ) ) )
{
EAE6320_ASSERTF( false, "Can't initialize Graphics without the animated shading data" );
return result;
}

Effect memory

I measured these values with a small sizeof() program compiled against the same headers and platform macros.

Object Direct3D x64 OpenGL x86
cEffect 48 bytes 16 bytes
cRenderState 32 bytes 1 byte

On Direct3D, cEffect stores two shader pointers and a cRenderState. cRenderState stores three Direct3D state-object pointers plus one byte of render-state bits. On OpenGL, cRenderState needs only the one render-state byte; cEffect stores two shader pointers, that byte, and the OpenGL program ID.

The values cannot easily be smaller because the active graphics API requires either COM state objects (Direct3D) or an OpenGL program ID and state bits. Shader paths are deliberately not stored after initialization.

Mesh initialization with data and indexed rendering

The mesh interface now accepts vertex and index data:

struct sMeshData
{
const VertexFormats::sVertex_mesh* vertexData = nullptr;
unsigned int vertexCount = 0;
const uint16_t* indexData = nullptr;
unsigned int indexCount = 0;
};

cResult Initialize( const sMeshData& i_meshData );

The caller must provide:

  • vertex data (x, y, z);
  • the number of vertices;
  • uint16_t index data;
  • the number of indices.

The public mesh API uses right-handed winding order. cMesh.cpp asks the active platform whether it expects left-handed winding:

if ( sContext::g_context.UsesLeftHandedWinding() )
{
// Direct3D expects left-handed winding, so reverse each triangle.
std::swap( triangle[1], triangle[2] );
}

Direct3D/sContext.d3d.cpp returns true; OpenGL/sContext.gl.cpp returns false. Therefore the same vertex/index data can be used regardless of platform.

This is the relevant code in Graphics.cpp:

const sMeshData rectangleData =
{
s_rectangleVertices,
static_cast<unsigned int>( sizeof( s_rectangleVertices ) / sizeof( s_rectangleVertices[0] ) ),
s_rectangleIndices,
static_cast<unsigned int>( sizeof( s_rectangleIndices ) / sizeof( s_rectangleIndices[0] ) ),
};
if ( !( result = s_mesh_rectangle.Initialize( rectangleData ) ) )
{
EAE6320_ASSERTF( false, "Can't initialize Graphics without the rectangle geometry" );
return result;
}

Mesh memory

Object Direct3D x64 OpenGL x86
cMesh 32 bytes 20 bytes

On Direct3D, cMesh stores a vertex-format pointer, a vertex-buffer pointer, an index-buffer pointer, and two counts. On OpenGL, it stores a VAO ID, a VBO ID, an IBO ID, and two counts. The vertex/index arrays themselves are copied into GPU buffers and are not stored on the CPU after initialization.

Two objects and two effects

Graphics.cpp creates:

  • a rectangle using the animated-color fragment shader;
  • a triangle using the standard white fragment shader.

Per frame:

s_effect_animated.Bind();
s_mesh_rectangle.Draw();

s_effect_standard.Bind();
s_mesh_triangle.Draw();

The two objects occupy different areas of the screen, so both are clearly visible.

Screenshots

Direct3D Debug (x64)

Direct3D running game

OpenGL Debug (x86)

OpenGL running game

Both screenshots show two distinct objects on a non-black blue background.

Build / test

All four configurations were built cleanly with warnings treated as errors:

  • Debug|x64 (Direct3D)
  • Release|x64 (Direct3D)
  • Debug|x86 (OpenGL)
  • Release|x86 (OpenGL)

The solution targets PlatformToolset=v143. The Visual Studio 2026 installation now has the v143 MSVC component (Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64) installed, so the builds above were run without any command-line PlatformToolset override.

The downloadable ZIP is the Direct3D Release build.

Reflection

The important design choice was to stop treating the graphics context as raw API data and start treating it as an abstraction boundary. Graphics.cpp now sees only InitializeViews(), ClearBackBuffer(), Present(), and the winding-order query. That is enough to remove both platform-specific frame files completely.

Moving winding-order conversion into cMesh.cpp keeps the public mesh interface simple. Callers provide indices once using one documented convention; the engine performs the tiny per-triangle swap only for the platform that needs it. This is intentionally simple and is exactly the kind of runtime conversion that can later move to the asset-build step.