This is my first larger-scale project using CMake, so please bear with me because I might be going about this wrong.
I have the following code, to open a GLFWwindow
, and start setting up a VkInstance
with Vulkan. My problem is that this version works fine,
but I can use GLFW components from the EngineDemo.cpp file. This is due to GameEngine/VulkanEngine/Renderer/src/CMakeLists.txt
including the glfw3.h
file as a PUBLIC
header. I would like to know if there is a way to be able to use glfw3.h
inside VulkanRenderer.h
without exposing it to the
API consumer EngineDemo.cpp
. Also, CMake should be able to only give the right release version (Debug, RelWithDebugInfo, Release) of the glfw3.h
and glfw3.lib
to
VulkanRenderer.h
.
I have the Vulkan SDK with all the components (glfw3, glm) installed on this path:
C:\VulkanSDK\1.3.236.0
And I have the following structure in a CLion C++ project using CMake and Microsoft's MSVC compiler version 1929:
GameEngine
|-- Libs
| |-- Windows
| | |-- DebugBuild
| | | |-- glfw3
| | | | |-- lib
| | | | | |-- glfw3.lib
| | | | |-- include
| | | | | |-- glfw3.h
| | | | | |-- glfw3native.h
| | |-- RelWithDebInfoBuild
| | | |-- ...
| | |-- ReleaseBuild
| | | |-- ...
| |-- Linux (same structure as Windows folder)
|-- EngineDemo
| |-- CMakeLists.txt
| |-- include
| | |-- EngineDemo
| | | |-- empty
| |-- src
| | |-- EngineDemo.cpp
| | |-- CMakeLists.txt
|-- VulkanEngine
| |-- CMakeLists.txt
| |-- Renderer
| | |-- CMakeLists.txt
| | |-- include
| | | |-- Renderer
| | | | |-- VulkanRenderer.h
| | |-- src
| | | |-- VulkanRenderer.cpp
| | | |-- CMakeLists.txt
|-- CMakeLists.txt
GameEngine/CMakeLists.txt
content:
cmake_minimum_required(VERSION 3.24)
project(VulkanEngine)
set(CMAKE_CXX_STANDARD 17)
set(DEBUG_WIN32_LIB_PATH "${CMAKE_SOURCE_DIR}/Libs/Windows/DebugBuild")
set(RELEASE_WITH_DEB_INFO_WIN32_LIB_PATH "${CMAKE_SOURCE_DIR}/Libs/Windows/RelWithDebInfoBuild")
set(RELEASE_WIN32_LIB_PATH "${CMAKE_SOURCE_DIR}/Libs/Windows/ReleaseBuild")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(GLFW3_INCLUDE_PATH "${DEBUG_WIN32_LIB_PATH}/glfw3/include")
set(GLFW3_LIB_PATH "${DEBUG_WIN32_LIB_PATH}/glfw3/lib/glfw3.lib")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# using GCC
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# using Visual Studio C++
add_definitions(-D_ITERATOR_DEBUG_LEVEL=2)
endif()
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(GLFW3_INCLUDE_PATH "${RELEASE_WITH_DEB_INFO_WIN32_LIB_PATH}/glfw3/include")
set(GLFW3_LIB_PATH "${RELEASE_WITH_DEB_INFO_WIN32_LIB_PATH}/glfw3/lib/glfw3.lib")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# using GCC
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# using Visual Studio C++
add_definitions(-D_ITERATOR_DEBUG_LEVEL=0)
endif()
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
set(GLFW3_INCLUDE_PATH "${RELEASE_WIN32_LIB_PATH}/glfw3/include")
set(GLFW3_LIB_PATH "${RELEASE_WIN32_LIB_PATH}/glfw3/lib/glfw3.lib")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# using GCC
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
# using Visual Studio C++
add_definitions(-D_ITERATOR_DEBUG_LEVEL=0)
endif()
endif()
# Include sub-projects.
add_subdirectory ("VulkanEngine")
add_subdirectory ("EngineDemo")
GameEngine/EngineDemo/CMakeLists.txt
content:
add_subdirectory(src)
GameEngine/EngineDemo/src/CMakeLists.txt
content:
project(EngineDemo)
add_executable (${PROJECT_NAME} EngineDemo.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC ../include)
target_link_libraries(${PROJECT_NAME} PUBLIC VulkanEngineModule)
GameEngine/EngineDemo/src/EngineDemo.cpp
content:
#include <iostream>
#include <cstdlib>
#include <stdexcept>
#include "Renderer/VulkanRenderer.h"
int main()
{
VulkanRenderer app;
try {
app.run();
} catch (const std::exception &exception) {
std::cerr << exception.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
GameEngine/VulkanEngine/CMakeLists.txt
content:
add_subdirectory(Renderer)
GameEngine/VulkanEngine/Renderer/CMakeLists.txt
content:
add_subdirectory(src)
GameEngine/VulkanEngine/Renderer/include/Renderer/VulkanRenderer.h
content:
#pragma once
#define GLFW_INCLUDE_VULKAN
#include "glfw3.h"
class VulkanRenderer {
private:
VkInstance vkInstance;
GLFWwindow* window = nullptr;
public:
void run();
private:
void initWindow();
void initVulkan();
void mainLoop();
void cleanup();
void createVulkanInstance();
};
GameEngine/VulkanEngine/Renderer/src/CMakeLists.txt
content:
cmake_minimum_required (VERSION 3.24)
project(VulkanEngineModule)
add_library (${PROJECT_NAME} STATIC VulkanRenderer.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC ../include)
find_package(Vulkan REQUIRED)
if(Vulkan_FOUND)
target_include_directories(${PROJECT_NAME} PUBLIC ${Vulkan_INCLUDE_DIR})
target_link_libraries(${PROJECT_NAME} INTERFACE ${Vulkan_LIBRARIES})
endif(Vulkan_FOUND)
if(WIN32)
target_include_directories(${PROJECT_NAME} PUBLIC ${GLFW3_INCLUDE_PATH})
target_link_libraries(${PROJECT_NAME} INTERFACE ${GLFW3_LIB_PATH})
endif(WIN32)
GameEngine/VulkanEngine/Renderer/src/VulkanRenderer.cpp
content:
#ifdef _WIN32
#include <Windows.h>
#endif
#include "Renderer/VulkanRenderer.h"
void VulkanRenderer::run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
void VulkanRenderer::initWindow() {
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
const char* WINDOW_TITLE = "Vulkan Engine";
glfwInit();
VkInstance c;
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, nullptr, nullptr);
}
void VulkanRenderer::initVulkan() {
createVulkanInstance();
}
void VulkanRenderer::mainLoop() {
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
}
}
void VulkanRenderer::cleanup() {
glfwDestroyWindow(window);
glfwTerminate();
}
void VulkanRenderer::createVulkanInstance() {
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "Hello Triangle";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
}
If I move the VkInstance
and GLFWwindow
declarations and the glfw3.h
header include macro to the VulkanRenderer.cpp
file
with the #define GLFW_INCLUDE_VULKAN
alongside it and change the target_include_directories
to PRIVATE
for the GLFW_INCLUDE_PATH
in GameEngine/VulkanEngine/Renderer/src/CMakeLists.txt
an empty window opens when I run the EngineDemo.exe
; this does not expose lower-level functions to EngineDemo.cpp
either. But this is not the goal.
I would like to declare every member variable of the VulkanRenderer
class in the corresponding header file.
I tried to change GameEngine/VulkanEngine/Renderer/include/Renderer/VulkanRenderer.h
to this:
#pragma once
#define GLFW_INCLUDE_VULKAN
#include "Renderer/glfw_config.h"
class VulkanRenderer {
private:
VkInstance vkInstance;
GLFWwindow* window = nullptr;
public:
void run();
private:
void initWindow();
void initVulkan();
void mainLoop();
void cleanup();
void createVulkanInstance();
};
Added a glfw_config.h.in
file to the GameEngine/VulkanEngine/Renderer/src
folder that looked like this:
#pragma once
#include "@GLFW3_INCLUDE_PATH@/glfw3.h"
Appended these two lines to the end of GameEngine/VulkanEngine/Renderer/src/CMakeLists.txt
:
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/glfw_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/../include/Renderer/glfw_config.h" @ONLY)
target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/../include")
The resulting glfw_config.h
file in GameEngine/cmake-build-debug-visual-studio/VulkanEngine/Renderer/include/Renderer
has the following content:
#pragma once
#include "C:/Projects/GameEngine/Libs/Windows/DebugBuild/glfw3/include/glfw3.h"
The above path to the header file is the correct one, and this works just like the previous setup.
If this line target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/../include")
changes to INTERFACE
or PRIVATE
then Renderer/glfw_config.h
cannot be found.
My idea was that if I generate the proper glfw_config.h
header file with the appropriate absolute path when reloading the CMake project, I would be able to
finally, use glfw3.h
in all different run configurations from the VulkanRenderer.h
header file and not expose it to the outside world if I set the above-mentioned visibility to INTERFACE
or PRIVATE
.
Is there a way to automatically generate this #include
path with CMake depending on which release I am building my project on and to only keep the GLFW components found in glfw3.h
and by extension, all components in vulkan.h
inside the GameEngine/VulkanEngine
project only, and expose a limited API to the EngineDemo
project?
If this is not possible with just the use of CMake, I would also greatly appreciate any suggestions on how to restructure the project the achieve the desired effect.
I tried googling for a similar question already, but the automatic system of StackOverflow didn't detect any duplicates either.
After trying for a day to formulate a more generic question and researching for it, I found out about a new use case for header files here.
This seems to work perfectly well in my application when the VkInstance
and GLFWwindow *
are declared in the private header instead of the public one.
When the private header is the one that has these macros:
#define GLFW_INCLUDE_VULKAN
#include "glfw3.h"
I can still easily control the propagation of the 3rd party header files with CMake to higher levels on the dependency graph.