0

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.

  • 1
    Good job on being detailed, but I wonder if you're being too detailed. `ctrl+f` of your current question body, there's only one question mark, and that paragraph seems like it could be asked as a generalized quesiton without most of the context you have provided. Do you think so? Or have I misunderstood (very likely since I was lazy and tl;dr) – starball Feb 01 '23 at 20:03
  • 1
    Thank you for your suggestion, first of all. Yes, that question would cover the general problem, but I don't know if what I am asking is even possible to do with CMake. That is why I provided the context with the project structure and this MWE code base, in case someone can attack this from another angle. That is also the reason why I am open to project restructuring suggestions. – Huldrekall97 Feb 01 '23 at 20:20
  • 1
    Hm. I might suggest creating a post where you try making it as abstracted away from your specific context as possible and just ask how to implement what include path logic and macro definitions or whatnot would solve your problem. You don't need to delete this post to do that. If you do, just [edit] this one to also link to the more abstracted one. – starball Feb 01 '23 at 20:23
  • Thank you for the suggestion. I managed to find a solution that would work for my case. – Huldrekall97 Feb 03 '23 at 05:11
  • Well done, and thanks for writing an answer. – starball Feb 03 '23 at 05:12

1 Answers1

0

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.