DEV Community

Cover image for From Python to OpenGL: A Modern, Cross-Platform Survival Guide for OSU CS-450
Juan Guerrero
Juan Guerrero

Posted on

From Python to OpenGL: A Modern, Cross-Platform Survival Guide for OSU CS-450

By Juan Guerrero

Disclaimer: This setup worked for me personally and is shared here to benefit others who might choose to follow it. This guide is based on my own experience in the class and is not an enforced standard or the "official" way to do things in CS-450. Always refer to the official class resources for the specific requirements of your assignment.

The Shock of the New (Old)

If you are an OSU student stepping into CS-450 (Introduction to Computer Graphics), you might be coming from a background of Python, Java, or web development. You're used to pip install, useful error messages, and languages that manage memory for you.

Suddenly, you are dropped into a world of pointers, segmentation faults, linker errors, and manual memory management. You visit the class resources page and see three different zip files: one for Windows (Visual Studio), one for Mac, and one for Linux. Each has its own build system.

There is a better way.

This guide is designed to set up a unified, modern, cross-platform environment that works comfortably on Windows, Linux, and Mac. We will replace manual configuration with tools that automate the hard stuff, allowing you to focus on the graphics.


Why Are We Doing This?

1. The Language: Why C++?

You might ask, "Why can't I just use Python?"
Graphics require performance. We need to talk directly to the GPU, manage memory down to the byte, and push millions of triangles per second. C++ gives us that control.

  • Memory: In Python, variables are just references. In C++, you decide if a variable lives on the stack (temporary, fast) or the heap (permanent, manual).
  • Pointers: You will pass addresses of data (&data) to OpenGL, not the data itself. This avoids copying massive 3D models around memory, keeping things fast.

2. The Problem: "It Runs on My Machine"

The default class setup relies on "project files" (like .sln for Visual Studio) or Makefiles. These are brittle. If you move a file, the project breaks. If you send your code to a partner on a Mac, it breaks.
The Solution: CMake.
CMake is a meta-build system. You write a "recipe" (CMakeLists.txt) describing your project, and CMake generates the correct project files for whatever computer you are on. It creates the Visual Studio solution on Windows and the Makefile on Linux.

3. The Nightmare: Dependency Management

In Python, you type pip install numpy.
In C++, traditionally, you download a .zip from a random website, extract it, guess where to put the .h files, guess where to put the .lib files, and pray the linker finds them.

The Solution:

  • On Windows: We will use Microsoft's vcpkg, a command-line package manager that integrates with Visual Studio.
  • On Linux/Mac: We will use system packages, but for tricky libraries (like outdated versions of GLUT), we can use CMake's FetchContent to download and compile them on the fly.

Part 1: The Environment Setup

1. Windows: Taming the Beast

On Windows, we will use vcpkg to install our libraries and Visual Studio 2022.

  1. Install Git: Download Git for Windows. We need this to clone the vcpkg repository.
  2. Install vcpkg: Open PowerShell and run:
    git clone https://github.com/microsoft/vcpkg.git C:\vcpkg
    cd C:\vcpkg
    .\bootstrap-vcpkg.bat
    .\vcpkg integrate install
Enter fullscreen mode Exit fullscreen mode

Why? bootstrap compiles the package manager itself. integrate install tells Visual Studio "Hey, look in C:\vcpkg for libraries". This means you never have to manually add "Include Directories" again.

  1. Install Graphics Libraries:
    .\vcpkg install glew freeglut opengl
Enter fullscreen mode Exit fullscreen mode

2. Linux & macOS: The Native Way

On Unix systems, package managers are built-in. However, CMake assumes certain low-level libraries are present.

  • Linux (Fedora):
    sudo dnf install cmake gcc-c++ glew-devel \
    libXi-devel libXmu-devel libXext-devel libXrandr-devel libXxf86vm-devel
Enter fullscreen mode Exit fullscreen mode

Why? We install glew-devel and the X11 libraries, but we skip the system freeglut. As we'll see in Part 3.5, we will tell CMake to download and build a specific version of FreeGLUT to ensure it works perfectly with Wayland.

  • Linux (Ubuntu):
    sudo apt install cmake g++ libglew-dev \
    libxi-dev libxmu-dev libxext-dev libxrandr-dev
Enter fullscreen mode Exit fullscreen mode
  • macOS:
    1. Install Homebrew: brew.sh
    2. brew install cmake glew freeglut

Part 2: The Editor - Neovim

You can use Visual Studio 2022, but for a consistent experience across all OSs, I recommend Neovim. It's a terminal-based editor that, when configured, is faster and more powerful than VS Code.

Why Neovim for C++?

  1. LSP (Language Server Protocol): We will hook Neovim up to specialized "Language Servers" that act like a brain for your editor. We need three specific ones for this class:
    • clangd: The brain for C/C++. It provides autocomplete and real-time error checking.
    • cmake-language-server: Helps you write CMakeLists.txt without typos.
    • glsl_analyzer: This is a life-saver for Projects 6 & 7. It provides syntax checking and autocomplete for the GLSL shader language.
  2. Regex Block Editing: You will often need to edit blocks of coordinate data.
    • Example: You have 50 lines that look like v 1.0 2.0 3.0. You want to change them to vertex(1.0, 2.0, 3.0);.
    • In Neovim, you press Ctrl-v to select the column, shift-I to insert text on all lines at once, and escape to apply. It saves hours of typing.
  3. Speed: It opens instantly. No loading splash screens.

Quick Setup (LazyVim)

Configuring Vim from scratch is hard. Use LazyVim, a pre-made configuration.

  1. Install Neovim: (Check your OS package manager).
  2. Install LazyVim:
    git clone https://github.com/LazyVim/starter ~/.config/nvim
    rm -rf ~/.config/nvim/.git
    nvim
Enter fullscreen mode Exit fullscreen mode
  1. Install C++ & Shader Tools: Inside Neovim, type the following command to install the brains: :MasonInstall clangd cmake-language-server glsl_analyzer

Part 3: Modernizing the Code

The class provides a file named sample.cpp inside Sample2022.zip. It works, but it's full of legacy #ifdef code to handle platform differences manually. We are going to clean this up using CMake.

Step 1: The CMakeLists.txt

Create a file named CMakeLists.txt next to your sample.cpp. This file tells CMake how to build your program.

cmake_minimum_required(VERSION 3.15)
project(cs450_project)

set(CMAKE_CXX_STANDARD 17)

# 1. Find the libraries automatically
# CMake looks for standard locations (and vcpkg locations)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
find_package(GLUT REQUIRED)

# 2. Add all your C++ files here.
# NOTE: If you uncomment 'bmptotexture.cpp' in the code, you MUST add it here too!
add_executable(my_project
    sample.cpp
    # bmptotexture.cpp
    # loadobjmtlfiles.cpp
)

# 3. Tell the compiler where the .h files are
target_include_directories(my_project PRIVATE
    ${OPENGL_INCLUDE_DIR}
    ${GLEW_INCLUDE_DIRS}
    ${GLUT_INCLUDE_DIRS}
)

# 4. Link the libraries to the executable
# This connects the actual compiled code (.lib or .a files) to your program
target_link_libraries(my_project PRIVATE
    ${OPENGL_LIBRARIES}
    ${GLEW_LIBRARIES}
    ${GLUT_LIBRARIES}
)
Enter fullscreen mode Exit fullscreen mode

Step 2: Fixing sample.cpp Includes

In C++, #include literally pastes the file contents into your code. The old sample.cpp uses local copies of headers ("glew.h"). We want to use the system installed ones (<GL/glew.h>).

Find this block in sample.cpp:

#ifdef WIN32
#include <windows.h>
#pragma warning(disable:4996)
#endif

#include "glew.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include "glut.h"
Enter fullscreen mode Exit fullscreen mode

Replace it with this:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#define _USE_MATH_DEFINES
#include <math.h>

// Universal include block
#include <GL/glew.h>
#include <GL/freeglut.h> 
// Note: freeglut.h includes GL/gl.h and GL/glu.h automatically
Enter fullscreen mode Exit fullscreen mode

Why?
We use < > to tell the compiler "look in the system folders" (where vcpkg/brew put the files). We removed the #ifdef WIN32 because CMake handles platform differences for us.

Step 3: Fixing Initialization

The main() function in sample.cpp has a specific check for Windows to initialize GLEW. This is a trap. Linux/Mac users using modern OpenGL profiles also need to initialize GLEW.

Change the initialization code to:

    // Initialize GLEW on ALL platforms
    GLenum err = glewInit( );
    if( err != GLEW_OK )
    {
        fprintf( stderr, "glewInit Error: %s\n", glewGetErrorString(err) );
    }
Enter fullscreen mode Exit fullscreen mode

Why? glewInit loads the function pointers for OpenGL extensions. Without it, calling modern functions will cause a crash.

Part 3.5: Advanced CMake - The Robust "Wayland-Ready" Recipe

The simple CMakeLists.txt above works for many, but on modern Linux (Fedora/Ubuntu) with Wayland, system libraries can break.

The Solution: Use this robust CMakeLists.txt instead. It automatically downloads and patches a compatible version of FreeGLUT if you are on Linux, while using standard libraries on Windows/Mac.

Copy-Paste this into your CMakeLists.txt:

cmake_minimum_required(VERSION 3.15)
project(cs450_project)

set(CMAKE_CXX_STANDARD 17)

find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)

# --- Advanced Dependency Management ---
if(UNIX AND (NOT APPLE))
  include(FetchContent)
  include(ExternalProject)

  # Build FreeGLUT from source to ensure compatibility with modern Wayland desktops
  # and fix common header conflicts (Patch step)
  ExternalProject_Add(
    freeglut
    GIT_REPOSITORY https://github.com/freeglut/freeglut
    GIT_TAG "v3.6.0"
    PATCH_COMMAND sh -c "sed -i 's/^\\(.*fgPlatformDestroy.*\\)/\\/\\/\\1/' <SOURCE_DIR>/src/egl/fg_init_egl.h"
    CMAKE_ARGS -DFREEGLUT_BUILD_DEMOS=OFF -DFREEGLUT_BUILD_SHARED_LIBS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> -DCMAKE_INSTALL_LIBDIR=lib
    BUILD_COMMAND cmake --build <BINARY_DIR>
    INSTALL_COMMAND cmake --install <BINARY_DIR>
  )

  # Link against the locally built version
  ExternalProject_Get_Property(freeglut INSTALL_DIR)
  set(PROGRAM_FREEGLUT_LINK_LIBRARY ${INSTALL_DIR}/lib/libglut.a)
  include_directories(${INSTALL_DIR}/include)
else()
  # On Windows/Mac, use the system or vcpkg installed GLUT
  find_package(GLUT REQUIRED)
  set(PROGRAM_FREEGLUT_LINK_LIBRARY GLUT::GLUT)
endif()

# --- Project Files ---
add_executable(my_project
    sample.cpp
    # bmptotexture.cpp
    # loadobjmtlfiles.cpp
)

# --- Include Directories ---
target_include_directories(my_project PRIVATE
    ${OPENGL_INCLUDE_DIR}
    ${GLEW_INCLUDE_DIRS}
    ${GLUT_INCLUDE_DIRS}
)

# --- Linking ---
target_link_libraries(my_project PRIVATE
    ${OPENGL_LIBRARIES}
    ${GLEW_LIBRARIES}
    ${PROGRAM_FREEGLUT_LINK_LIBRARY}
)

# Linux-specific: Link X11 libraries required by static FreeGLUT
if(UNIX AND (NOT APPLE))
    add_dependencies(my_project freeglut)
    target_link_libraries(my_project PRIVATE X11 Xi Xrandr Xxf86vm)
endif()

# Auto-copy assets to build folder (if they exist)
if(EXISTS "${CMAKE_SOURCE_DIR}/models")
    file(COPY "${CMAKE_SOURCE_DIR}/models" DESTINATION "${PROJECT_BINARY_DIR}")
endif()
Enter fullscreen mode Exit fullscreen mode

This script allows us to script complex compatibility layers, ensuring that "It works on my machine" translates to "It works on your Wayland machine too."

Now, the magic of CMake. Instead of memorizing different commands for every OS, we follow a standard process: Configure, Build, Run.

Option A: The Terminal (Linux, macOS, Windows PowerShell)

This is the universal way to build C++ projects.

  1. Create a Build Directory: We don't want to clutter our source code with temporary build files.
    mkdir build
    cd build
Enter fullscreen mode Exit fullscreen mode
  1. Configure (The "CMake" Step): Run CMake to look at your CMakeLists.txt (in the parent directory, hence ..) and generate the actual build files (Makefiles).
    cmake ..
Enter fullscreen mode Exit fullscreen mode

If this fails, it means CMake couldn't find a library. Check your CMakeLists.txt or install the missing package.

  1. Build: Now we tell CMake to compile the code.
    cmake --build .
Enter fullscreen mode Exit fullscreen mode

The . tells CMake to build the project in the current directory.

  1. Run: If the build succeeded, you will see an executable file. Linux/Mac: You must prefix the command with ./ to tell the shell "run the program in this folder", not a system command.
./my_project
Enter fullscreen mode Exit fullscreen mode

Windows (PowerShell):

.\Debug\my_project.exe
Enter fullscreen mode Exit fullscreen mode

Option B: Visual Studio 2022 (Windows)

Visual Studio now supports CMake natively.

  1. Open the Folder:
    Launch Visual Studio. Click "Open a Local Folder" and select the folder containing your CMakeLists.txt.

  2. Wait for Configuration:
    Look at the "Output" window. You should see CMake scanning your system. It will find the libraries you installed with vcpkg automatically.

  3. Run:
    In the top toolbar, select my_project.exe as the startup item and press F5.


Part 5: Managing Assets & Subdirectories

In Python, you might just open cat.jpg. In C++, where the program runs matters.

1. The "Current Working Directory" Trap

When you run a program from Visual Studio, the "working directory" (where it looks for files) might be different from where your source code is. This means fopen("models/cat.obj") might fail because the program is running inside build/Debug/.

The CMake Fix:
Add this to the end of your CMakeLists.txt to automatically copy your assets folder to the build directory every time you compile:

file(COPY models DESTINATION ${PROJECT_BINARY_DIR})
Enter fullscreen mode Exit fullscreen mode

2. Fixing loadobjmtlfiles.cpp for Subdirectories

The provided class code for loading OBJ files (loadobjmtlfiles.cpp) sometimes assumes textures are in the same folder as the executable. If your OBJ file refers to a texture skin.bmp, but that file is actually in models/skin.bmp, the loader will fail.

We can fix this by updating the loader to be "path aware".

In loadobjmtlfiles.cpp, find where map_kd (diffuse texture) is parsed:

Original Code:

if( strcmp( tok, "map_kd" ) == 0 ) {
    tok = strtok( NULL, DELIMS );
    // calls BmpToTexture(tok) directly
}
Enter fullscreen mode Exit fullscreen mode

Modernized C++ Fix:
Use std::filesystem to prepend the parent directory of the OBJ file to the texture filename.

#include <filesystem> // Add this at the top

// ... inside the loop ...
if (strcmp(tok, "map_kd") == 0) {
    tok = strtok(NULL, DELIMS);

    // Construct the full path
    std::filesystem::path texturePath(tok);
    if (!texturePath.has_parent_path() && !basePath.empty()) {
        texturePath = basePath / texturePath;
    }

    // Convert back to string for the old function
    std::string fullPath = texturePath.generic_string();
    BmpToTexture( (char*)fullPath.c_str(), ... );
}
Enter fullscreen mode Exit fullscreen mode

Part 6: Tips for the Python Developer

1. Compilation vs Interpretation

In Python, if you have a syntax error on line 50, the program runs until line 49.
In C++, the Compiler reads your whole file first. If there is one typo, nothing runs.
Then, the Linker combines your code with libraries. If you get "Undefined Symbol", it means you declared a function but didn't give the Linker the .cpp file that contains it. Check your add_executable list in CMake!

2. Segfaults (The Silent Killer)

If your program crashes instantly without an error message, you likely have a Segmentation Fault. This usually means you tried to access a list item that doesn't exist (like arr[10] in a list of size 5) or used a pointer that points to nothing (NULL).
Tip: Use a debugger. In Visual Studio, it will pause exactly where the crash happens and show you the bad variable.


Conclusion

Setting up C++ graphics environments used to be a rite of passage involving pain and suffering. With CMake and vcpkg, it becomes a repeatable, scriptable process. You have now graduated from "It runs on my machine" to "It runs anywhere."

Good luck with CS-450. May your framerates be high and your compile times low!


References

Top comments (0)