Skip to content

Bugs — Hall of Fame

Every bug that cost more than 30 minutes, taught something, or was just absurd enough to remember.


01 30 seconds to draw a single triangle
What happened The window loaded but took literally 30 seconds to draw one triangle, nearly crashing every frame.
Cause Two problems. First, SDL_UpdateTexture was being called inside the per-pixel loop — uploading the full texture to the GPU on every single pixel write. Second, the framebuffer was being passed by value, so the entire array was copied every frame.
Fix Move SDL_UpdateTexture outside the pixel loop. Add a single & to pass the framebuffer by reference. One character fix for the second one.
02 Z-buffer working but completely wrong — barycentric assigned backwards
What happened Two intersecting triangles with depth — the z-buffer was active but behaved unpredictably, ignoring actual depth values.
Cause When interpolating, the edge function for edge AB was being multiplied by the depth of A — but the edge function for AB represents the area of the sub-triangle opposite to C. It should be multiplied by C's depth.
Fix Each edge function value maps to the vertex it's opposite to, not the one it's named after.
03 Moving the cube in Z sends it flying in the rotation direction
What happened Translating the cube along Z would send it in a completely wrong direction depending on its current rotation.
Cause The MVP matrix was assembled in the wrong order. The projection matrix was being applied first instead of last.
Fix Reverse the multiplication order: Projection × View × Model.
04 Only one face of the cube renders — a missing zero
What happened After fixing the MVP, only a single face of the cube appeared. The pipeline seemed correct.
Cause The rotation X matrix in math.hpp had only 15 elements — a single missing 0.
Fix Add the zero. 30 minutes hunting for a typo.
05 Back faces draw on top of front faces — z-buffer inverted
What happened Rotating the cube revealed that back faces were always drawn on top of front faces.
Cause The z-buffer was reading -w instead of +w, inverting all depth comparisons.
Fix Negate the sign: store +w so closer objects have smaller depth values.
06 Abstract art — renderer not cleared between frames
What happened Rotating and moving the cube produced a layered painting — every previous frame stayed on screen.
Cause The framebuffer and z-buffer were cleared each frame, but SDL_RenderClear was never called. SDL kept the previous frame underneath.
Fix Add SDL_RenderClear(renderer) at the start of every frame.
07 Black window — working directory, missing push_backs, wrong filenames
What happened After implementing the OBJ parser, nothing appeared on screen.
Cause Several problems stacked: wrong working directory, the MTL path being reset on every line read, filenames using \\ instead of / and .jpg instead of .bmp, and faces never being appended to the mesh list.
Fix Print statements everywhere to locate each error one by one.
08 OBJ indices start at 1, C++ vectors start at 0
What happened The mesh loaded but the geometry was completely destroyed — vertices in the wrong positions, faces twisted.
Cause OBJ files use 1-based indices. Accessing vertices[index] without subtracting 1 reads the wrong vertex every time.
Fix vertices[index - 1].
09 Back faces on top again — the w sign flip that worked, then didn't
What happened Same symptom as bug #05 — back faces drawing on top — but this time with a real OBJ file.
Cause The -w fix from bug #05 had only worked for the manually-constructed cube. Real OBJ files have different conventions, so negating w broke it again.
Fix Revert from -1 to +1. The previous fix was masking the real problem.
10 UV texture distortion — linear interpolation in screen space
What happened The cube faces showed correct textures, but they warped and stretched as the cube rotated.
Cause UV coordinates were being interpolated linearly in screen space, which ignores depth. A vertex at depth 10 and one at depth 1 are treated the same.
Fix Perspective-correct interpolation: interpolate UV/w and 1/w separately, then divide. Since 1/w is linear in screen space, this corrects for depth.
11 Crash when loading a detailed model — texture copied every frame
What happened Loading a low-poly Jeep instead of the Minecraft cube caused an immediate crash — stack overflow or memory access violation.
Cause The entire texture — over a million pixels — was being copied into the rasterizer every frame. The tiny Minecraft texture hid this; the Jeep's larger texture exposed it immediately.
Fix Pass the material as a const* pointer instead of by value. 8 bytes instead of megabytes per frame.
12 Multiple objects — one always renders on top regardless of depth
What happened With two objects in the scene, changing the render order always made one appear in front of the other, ignoring actual depth.
Cause Each object had its own z-buffer. Each one only compared depth against itself, not against the other objects in the scene.
Fix A single global z-buffer shared across all objects and passed as a parameter to the render function.
13 Phong shading frozen — normals not rotating with the object
What happened The lighting looked correct at first, but as the object rotated, the shadows stayed fixed — as if painted on once and never updated.
Cause The original OBJ normals and vertex positions were being used for lighting, not the transformed ones. The rotation wasn't being applied to the normals.
Fix Apply the rotation matrix to normals and the full model matrix to vertex positions before passing them to the rasterizer.
14 Phong shading looks flat — barycentric order wrong again
What happened The object rotated correctly and lighting responded, but even on detailed models the triangles were clearly visible — no smooth gradient.
Cause Normals and 3D positions were being interpolated, but assigned to the wrong barycentric coordinates — same mistake as the first color interpolation bug.
Fix Match the barycentric order to the one used for UV interpolation. 3 characters changed.
15 Drunk camera — lookAt not inverted
What happened Moving the camera made everything spin erratically. Looking left felt like going right. Nothing was consistent.
Cause The lookAt matrix was the camera-to-world transform, not the world-to-camera (view) transform. It was moving the camera into the world instead of moving the world into camera space. Also mixing OpenGL and DirectX conventions for the forward vector.
Fix Use the inverse (transposed rotation part) and fix the sign conventions consistently.
16 Spotlight not visible — gimbal lock in lookAt
What happened The directional light worked. The spotlight produced no light and no shadow.
Cause The spotlight was pointing straight down — perfectly parallel to the world up vector used to compute the right vector in lookAt. A cross product of two parallel vectors is zero, breaking the entire matrix.
Fix Short-term: tilt the light slightly off vertical. Long-term: handle the parallel case in lookAt by switching to a different up reference vector when forward ≈ up.