Skip to content

Tutorial Premake example with GLFW and OpenGL

starkos edited this page Apr 10, 2021 · 3 revisions

⚠️ We have a new website! Visit now for the most update to date documentation.
       This wiki is no longer being maintained.


Tutorial: Premake example with GLFW and OpenGL

This tutorial will show you how to create a simple Premake5 workspace, with a static library project, a windowed app (created with GLFW) and a unit test project (written with Catch).

Requirements

  • Premake5
  • A text editor
  • A C++11 compiler. The example code uses C++11 to shorten the code
  • A OpenGL 1.1 compatible Graphics Card - any graphics card created since 1991

Organizing the workspace

The first thing we need to decide on is how we will structure our folders. We will use this folder structure for our simple example:

Hello Premake/
├─ premake5.lua (this file will be used by Premake to build our app)
├─ Build/ (this folder will contain our binaries and objects)
├─ Libraries/ (we will store GLFW and Catch in here)
├─ Generated/ (Premake will put generated files in here, such as makefiles and .sln)
├─ Projects/ (we will keep our projects' sources in subfolders)
│   ├── ExampleLib/ (this is our static library)
│   ├── App/ (our actual application)
|   └── UnitTests/ (here we will keep unit tests with Catch)

You can structure your projects however you want, and this structure is fairly scalable for bigger projects.

Getting the required files

First and foremost, you should follow this tutorial on how to get premake5.

Secondly, you will need to download GLFW and Catch.

  • You can get precompiled binaries for GLFW or build instructions here.

    For GLFW, we are only interested in the Include/ and Lib/ folders. This tutorial was written for 64 bit systems, so you should get the 64 bit binaries, but you can also get the 32 bit ones if you are on a 32 bit system. Remember to change the architecture setting a bit further in this tutorial.

    • For Linux: If you don't see a Lib/ folder, then you will have to build GLFW (follow the link above for instructions).
    • For Windows: If you see something like lib-vc*/, then you just have to choose the right file for your version of Visual Studio / MinGW (for example, rename lib-vc2015 to lib if you have Visual Studio 2015).

    Put the Include/ and Lib/ folder in Libraries/GLFW/.

  • Catch is a header only library, you can get the latest file here.

    For Catch, just save the Catch.hpp file to Libraries/Catch/Include/Catch.hpp.

Adding some code

You can add code to your projects before or after setting up your Premake files. We will add code beforehand so we can get an idea of what we want premake to do. This isn't a GLFW / OpenGL tutorial, so you can just copy and paste the C++ files if you're only interested in Premake.

The library code

The library is just a simple wrapper around GLFWwindow. It hides the implementation of the window, so our other projects needn't know how the window is implemented.

These files should be in Projects/ExampleLib/

ExampleLib.hpp

#ifndef EXAMPLE_LIB_HPP
#define EXAMPLE_LIB_HPP 1

#include <string>
#include <memory>

// Forward declare GLFWwindow to avoid including glfw3.h
struct GLFWwindow;

namespace ExLib
{
	class Window
	{
	public:
		Window(int width, int  height, const std::string& title);
		~Window();

		bool shouldClose() const noexcept;

		void pollEvents() const noexcept;

		void swapBuffers() const noexcept;

		std::pair<int, int> getWindowSize() const noexcept;

	private:
		GLFWwindow* wnd;
	};
}

#endif

ExampleLib.cpp

#include "ExampleLib.hpp"

#include <GLFW/glfw3.h>

namespace ExLib
{
	Window::Window(int width, int height, const std::string& title)
	{
		glfwInit();
		wnd = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
		glfwMakeContextCurrent(wnd);
	}

	Window::~Window()
	{
		glfwDestroyWindow(wnd);
		glfwTerminate();
	}

	bool Window::shouldClose() const noexcept
	{
		return glfwWindowShouldClose(wnd) != 0;
	}

	void Window::pollEvents() const noexcept
	{
		glfwPollEvents();
	}

	void Window::swapBuffers() const noexcept
	{
		glfwSwapBuffers(wnd);
	}

	std::pair<int, int> Window::getWindowSize() const noexcept
	{
		std::pair<int, int> sz{};
		glfwGetWindowSize(wnd, &sz.first, &sz.second);
		return sz;
	}
}

The application

The application includes the lib to be able to open a window and just draws a white triangle using fixed-pipeline functions. Again, this isn't an OpenGL tutorial, if you want to learn OpenGL, you should use the modern graphics pipeline.

#include <ExampleLib.hpp>

#if defined _WIN32
        // OpenGL on Windows needs Windows.h
	#include <Windows.h>
#endif

#include <gl/GL.h>

int main()
{
	using namespace ExLib;
	Window window{ 800, 600, "Hello World!" };

	while (!window.shouldClose())
	{
		window.pollEvents();

		// Please note: this is old, OpenGL 1.1 code. It's here for simplicity.
		glBegin(GL_TRIANGLES);
			glVertex2f(-0.5f, -0.5f);
			glVertex2f(0.5f, -0.5f);
			glVertex2f(0, 0.5f);
		glEnd();

		window.swapBuffers();
	}

	return 0;
}

The tests

This test just checks if the created window has the same size as the requested size.

#define CATCH_CONFIG_MAIN
#include <Catch.hpp>

#include <ExampleLib.hpp>

TEST_CASE("Window tests", "[ExampleLib]")
{
	using namespace ExLib;
	Window w{ 600, 400, "Test Window" };

	auto size = w.getWindowSize();

	REQUIRE(size.first == 600);
	REQUIRE(size.second == 400);
}

The premake5.lua file

Premake will look for a premake5.lua file in the folder when you call it. If you want to (re)generate the project files, then you have to call premake5 <action>, where action is something like vs2015 or gmake.

Workspace settings

We first set some global settings that all our projects will use. We do this after our call to the workspace() function.

-- The name of your workspace will be used, for example, to name the Visual Studio .sln file generated by Premake.
workspace "Hello Premake"
	-- We set the location of the files Premake will generate
	location "Generated"

	-- We indicate that all the projects are C++ only
	language "C++"

	-- We will compile for x86_64. You can change this to x86 for 32 bit builds.
	architecture "x86_64"

	-- Configurations are often used to store some compiler / linker settings together.
    -- The Debug configuration will be used by us while debugging.
    -- The optimized Release configuration will be used when shipping the app.
	configurations { "Debug", "Release" }

	-- We use filters to set options, a new feature of Premake5.

	-- We now only set settings for the Debug configuration
	filter { "configurations:Debug" }
		-- We want debug symbols in our debug config
		symbols "On"

	-- We now only set settings for Release
	filter { "configurations:Release" }
		-- Release should be optimized
		optimize "On"

	-- Reset the filter for other settings
	filter { }

	-- Here we use some "tokens" (the things between %{ ... }). They will be replaced by Premake
	-- automatically when configuring the projects.
	-- * %{prj.name} will be replaced by "ExampleLib" / "App" / "UnitTests"
	--  * %{cfg.longname} will be replaced by "Debug" or "Release" depending on the configuration
	-- The path is relative to *this* folder
	targetdir ("Build/Bin/%{prj.name}/%{cfg.longname}")
	objdir ("Build/Obj/%{prj.name}/%{cfg.longname}")

GLFW functions

We will add some functions for GLFW. If we ever move GLFW to another folder, we only have to change these functions, and nothing else.

-- This function includes GLFW's header files
function includeGLFW()
	includedirs "Libraries/GLFW/Include"
end

-- This function links statically against GLFW
function linkGLFW()
	libdirs "Libraries/GLFW/Lib"

	-- Our static lib should not link against GLFW
	filter "kind:not StaticLib"
		links "glfw3"
	filter {}
end

The static library

-- Our first project, the static library
project "ExampleLib"
	-- kind is used to indicate the type of this project.
	kind "StaticLib"

	-- We specify where the source files are.
	-- It would be better to separate header files in a folder and sources
	-- in another, but for our simple project we will put everything in the same place.
	-- Note: ** means recurse in subdirectories, so it will get all the files in ExampleLib/
	files "Projects/ExampleLib/**"

	-- We need GLFW, so we include it
	includeGLFW()

We also add an utility function, to make it easier to use this library

function useExampleLib()
	-- The library's public headers
	includedirs "Projects/ExampleLib"

	-- We link against a library that's in the same workspace, so we can just
	-- use the project name - premake is really smart and will handle everything for us.
	links "ExampleLib"

	-- Users of ExampleLib need to link GLFW
	linkGLFW()
end

The application

-- The windowed app
project "App"
	kind "WindowedApp"
	files "Projects/App/**"

	-- We also need the headers
	includedirs "Projects/ExampleLib"

	useExampleLib()

	-- Now we need to add the OpenGL system libraries

	filter { "system:windows" }
		links { "OpenGL32" }

	filter { "system:not windows" }
		links { "GL" }

The tests

-- We will use this function to include Catch
function includeCatch()
	-- Catch is header-only, we need just the Catch.hpp header
	includedirs "Libraries/Catch/Include"

	-- We can also configure Catch through defines
	defines "CATCH_CPP11_OR_GREATER"
end

project "UnitTests"
	-- Catch prints information to the console
	kind "ConsoleApp"

	files "Projects/UnitTests/**"

	includeCatch()
	useExampleLib()

Generating the files and building

You can read this tutorial on how to generate the project files. They will be placed in the Generated/ folder. You should then follow your platform's instructions for building (on Windows: open the .sln files and use Build Solution, on other platforms, just use make or equivalent).

Other things you can do

Here are some other suggestions you can add to the example project. You could also consider them "exercises":

  • We currently store all our libraries in Libraries/. If we want to use different libraries for different Operating Systems, we should have subfolders in libraries: Libraries/Windows, Libraries/Linux etc.
  • Separate the source files into Include/ and Source/, so that all headers are in Include and all .cpp files are in Source/.
  • Add a custom post build command to automatically run tests after building ExampleLib.
Clone this wiki locally