LearnOpenGL 及 ShaderToy 的 CMake 构建框架

文章目录

    • 构建目标
    • 具体框架
      • 根目录
      • src 目录
      • app 目录
      • import.cmake
      • 其他 CMake 函数
    • 使用框架
    • 实际效果
      • 摄像机坐标变换
      • 使用 assimp 库加载模型
      • shadertoy 测试 framebuffer 离屏渲染
    • 其他

为了复习 OpenGL(主要是看到 shadertoy 上有好玩的着色器),所以打算重新写一下构建框架来适配 shadertoy 到本地 GLSL 代码的转换,这样 shadertoy 上有什么好玩的着色器都可以直接复制下来在本地运行(当然片段着色器里面有一些坐标还是需要转换一下的)。

构建目标

  • 构建源码目录前,扫描导入库目录下的所有动态库和静态库并引入为当前 CMake 的 target,这样其他二进制可执行文件目标在构建的时候可以随时链接全局 target 名称
  • 适配 Linux 和 Windows 系统平台的动态库和静态库链接编译,Windows 平台基于 MSVC 编译工具链的情况比较复杂一些,有一些坑
  • 允许灵活的添加测试目录或者指定要构建的单元测试源代码
  • 混合编译 C/C++
  • 指定每一个 shader 主程序编译所需要链接的动态库或者静态库

具体框架

主目录结构如下:

F:\FREDOM\WORKSPACE\PLAY-CC\SHADERTOY
├─.vscode
├─app
│  ├─learnopengl
│  └─shadertoy
├─assets
│  ├─audio
│  ├─model
│  ├─shader
│  │  ├─learnopengl
│  │  │  ├─frag
│  │  │  ├─geom
│  │  │  └─vert
│  │  └─shadertoy
│  └─texture
├─build
├─cmake
├─include
│  ├─assimp
│  ├─dummy
│  ├─glad
│  ├─GLFW
│  ├─glm
│  ├─KHR
│  ├─learnopengl
│  ├─stb_image
│  └─utils
├─lib
│  ├─assimp
│  ├─glfw3
│  └─opengl32
├─src
│  ├─dummy
│  ├─glad
│  ├─stb_image
│  └─utils
└─test
    ├─log
    └─misc
  • app:存放每个 main 函数入口源文件,一个文件就是一个应用程序的入口点,可以编写多个入口 main ,这当然是为了方便测试不同的 shadertoy 代码了,如果只能编译一个 main 的话太不方便了(所以用 VScode 比 VisualStudio 灵活)
  • assets:资源文件目录,比如纹理贴图、模型材质、着色器、音频
  • build:构建目录,存放构建的动态库静态库以及构建完之后安装(install 的概念大概就是复制构建产物到一个单独的目录下方便取用)
  • cmake:存放一些 cmake 脚本,比如自动搜索头文件目录或者扫描库目录下的库文件
  • include:存放所有头文件
  • lib:外部引入的第三方库(静态或动态),比如 glfw3、assimp 等等
  • src:头文件对应的实现文件,这一般是项目内部的构件库的实现文件目录,因为外部导入的头文件一般使用的当然是对应的构建好的库文件,实现都已经在库文件中了而不是实际的源代码,不然每次编译几百个源代码那真是有的好受的
  • test:存放各种单元测试文件

根目录

首先是项目根目录的 CMakeLists.txt ,既然是根目录的配置文件,当然是负责一些全局的设置和引入其他子目录了,不可能根目录下的一个配置文件完成所有构建目标的配置,每个目录下的源文件构建由子目录对应的构建配置 CMakeLists.txt 决定如何完成,根目录应该设置好当前编译系统名称以及全局的宏开关。

具体内容如下:

CMAKE_MINIMUM_REQUIRED(VERSION 3.21)
PROJECT(temp)

MESSAGE("build system: ${CMAKE_SYSTEM_NAME}")
MESSAGE("${CMAKE_CURRENT_BINARY_DIR}")
SET(CMAKE_CXX_STANDARD 17)
SET(CAMKE_C_STANDARD 17)

SET(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/install)
# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # binary executable
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # shared library
# set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) # static library

# help vscode find compiler_command.json
ADD_DEFINITIONS(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON)

# other define in source code
ADD_COMPILE_DEFINITIONS(CXX_COMPILER_ID_${CMAKE_CXX_COMPILER_ID})
ADD_COMPILE_DEFINITIONS(C_COMPILER_ID_${CMAKE_C_COMPILER_ID})
STRING(TOUPPER ${CMAKE_BUILD_TYPE} build_type_str)
ADD_COMPILE_DEFINITIONS(${build_type_str})

IF(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    MESSAGE("compiler tool chain MSVC, all symbols will export by default")
    SET(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
ENDIF()



INCLUDE(cmake/macro.cmake)

INCLUDE(import.cmake)

ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(app)
ADD_SUBDIRECTORY(test)

其中这一段代码:

IF(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    MESSAGE("compiler tool chain MSVC, all symbols will export by default")
    SET(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
ENDIF()

是为了在 Windows 下使用 MSVC 编译时,默认导出所有函数符号,C++ 背景下接口的声明和实现就是函数的头文件和动态库或静态库,Windows 下默认函数符号是不导出的,必须手动使用 __declspec(dllexport) 来标注某个函数的导出,这是对于库的构建者来说的,同时对于使用该库的用户,如果使用了分发的接口头文件中的某个接口(函数),也即头文件中给出的接口函数都是开发者编译的动态库中允许导出的符号,则使用者使用函数之前也需要添加属性 __declspec(dllimport) (不知道这么理解对不对);如果 MSVC 编译静态库的话就没有这么麻烦,编译完输出文件之后在链接阶段直接把静态库链接到二进制可执行文件,没有什么导出不导出的问题了。

但是不幸的是因为我自己还有一个子目录是写了一个 C/C++ 的打印日志功能的,其中 C 的打印日志使用了全局的缓冲数组,因此在头文件中进行了 extern 的全局变量声明;注意这是一个常见的错误:不要在头文件中直接声明全局变量,否则容易发生重定义错误,这不是 #ifndef#define 能解决的重复包含问题(防止一个编译单元内多次包含,但是现在的情况是多个编译单元),而是在链接阶段如果存在多个使用了该头文件对应的链接库的链接输出文件,后续再次参与链接,那么多个这样的文件中的导出符号表将使得链接器无法选择重名的符号。

src 目录

注意,先包含 src 目录再包含 app 目录,因为 src 下可能有构建的目标被 app 中的主应用程序依赖,比如我这里的 log 的一些功能就是 test 中所依赖的实现,那么必须先编译 src 下面的目标,这样库目标存在之后才能被其他子目录的编译构建链接使用。

SET(lib_type SHARED)
# SET(lib_type STATIC)

# build dummy
FILE(
    GLOB module_dummy_src_list
    dummy/*.cpp
    dummy/*.cc
    dummy/*.c
)
PRINT_LIST("${module_dummy_src_list}" "MODULE [dummy] SRC" "")
ADD_LIBRARY(dummy ${lib_type} ${module_dummy_src_list})
INSTALL(
    TARGETS dummy 
    RUNTIME DESTINATION bin
    ARCHIVE DESTINATION lib
)

# build utils
FILE(
    GLOB module_utils_src_list
    utils/*.cpp
    utils/*.cc
    utils/*.c
)
PRINT_LIST("${module_utils_src_list}" "MODULE [utils] SRC" "")
ADD_LIBRARY(utils ${lib_type} ${module_utils_src_list})
IF(lib_type STREQUAL "SHARED")
    # this is for MSVC usage to export global data symbols
    # CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS only exports method
    IF(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
        TARGET_COMPILE_DEFINITIONS(
            utils
            PRIVATE DLLCOMPILE # can not use PUBLIC attr? That's weird...
        )
    ENDIF()
ENDIF()
INSTALL(
    TARGETS utils 
    RUNTIME DESTINATION bin
    ARCHIVE DESTINATION lib
)

# build other
FILE(
    GLOB module_other_src_list
    ./*.cpp
    ./*.cc
    ./*.c
)
PRINT_LIST("${module_other_src_list}" "MODULE [other] SRC" "")
ADD_LIBRARY(other ${lib_type} ${module_other_src_list})
INSTALL(
    TARGETS other 
    RUNTIME DESTINATION bin
    ARCHIVE DESTINATION lib
)


# build glad
FILE(
    GLOB module_glad_src_list
    glad/*.cpp
    glad/*.cc
    glad/*.c
)
PRINT_LIST("${module_glad_src_list}" "MODULE [glad] SRC" "")
ADD_LIBRARY(glad ${lib_type} ${module_glad_src_list})
IF(lib_type STREQUAL "SHARED")
    TARGET_COMPILE_DEFINITIONS(
        glad
        PUBLIC GLAD_GLAPI_EXPORT
        PRIVATE GLAD_GLAPI_EXPORT_BUILD
    )
ENDIF()
INSTALL(
    TARGETS glad
    RUNTIME DESTINATION bin
    ARCHIVE DESTINATION lib
)

# build stb_image
FILE(
    GLOB module_stb_image_src_list
    stb_image/*.cpp
    stb_image/*.cc
    stb_image/*.c
)
PRINT_LIST("${module_stb_image_src_list}" "MODULE [stb_image] SRC" "")
ADD_LIBRARY(stb_image ${lib_type} ${module_stb_image_src_list})
INSTALL(
    TARGETS stb_image
    RUNTIME DESTINATION bin
    ARCHIVE DESTINATION lib
)

这里选择的是构建动态库,其实 CMake 自己默认有一个这个宏的 BUILD_SHARED_LIBS 其实用哪个都行,这里我是自定义了 lib_type 变量,方便构建的时候直接传入这个变量代表构建库类型。

INSTALL 这些构建好的目标是必须的,二进制可执行文件如果使用的是动态库链接,那么运行时会在当前的目录下寻找链接库或者去系统环境变量指定的目录下寻找,为了在 VScode 中能够 F5 一键 Debug ,构建的动态库要放到编译产物的输出安装目录了,静态库的话安不安装倒无所谓……

这里当然可以写一个 FOREACH 循环扫描当前目录的每个子目录然后执行构建,但是这样有时候想单独指定某个库是动态的还是静态的就不方便了,这里我还是拆开每个子目录单独指定构建流程。

后续如果有其他的自定义实现函数模块,可以在 src 目录下新建一个子目录存放模块相关的实现文件,然后在 src 目录下的 CMakeLists.txt 文件里面添加对应的模块构建流程,就是把其他模块构建流程复制一遍改一下路径。

app 目录

app 目录就是指定要构建哪些主程序的入口文件,然后链接所需的库,再把链接过的动态库放到和输出二进制文件同一个目录方便调试。

# user should be responsible for installing the dynamic
# library that they used to link with any exe bin

# ============================= main =============================
SET(lib_inn_static)
SET(lib_inn_shared)
SET(lib_ext_static)
SET(lib_ext_shared)
SET(
    lib_all
    ${lib_inn_static}
    ${lib_inn_shared}
    ${lib_ext_static}
    ${lib_ext_shared}
)
ADD_EXECUTABLE(main main.cpp)
IF(lib_all)
    TARGET_LINK_LIBRARIES(main ${lib_all})
    # move dll/so to bin dir
    # for inner lib and external static
    INSTALL(
        TARGETS ${lib_inn}
        RUNTIME DESTINATION bin
        ARCHIVE DESTINATION lib
    )
ENDIF()
INSTALL(TARGETS main RUNTIME DESTINATION bin)


# ============================= learnopengl =============================
SET(
    learnopengl_src_list
    learnopengl/triangle.cpp
    learnopengl/texture.cpp
    learnopengl/transform.cpp
    learnopengl/coordinate.cpp
    learnopengl/camera.cpp
    learnopengl/framebuffer.cpp
    learnopengl/model_load.cpp
)

SET(lib_inn_static)
SET(lib_inn_shared glad stb_image)
SET(lib_ext_static opengl32)
SET(lib_ext_shared glfw3 assimp-vc143-mtd)
SET(
    lib_all
    ${lib_inn_static}
    ${lib_inn_shared}
    ${lib_ext_static}
    ${lib_ext_shared}
)

FOREACH(_src_file ${learnopengl_src_list})
    GET_FILENAME_COMPONENT(_src_name ${_src_file} NAME_WE)
    ADD_EXECUTABLE(${_src_name} ${_src_file})
    IF(lib_all)
        TARGET_LINK_LIBRARIES(${_src_name} ${lib_all})
    ENDIF()
    INSTALL(TARGETS ${_src_name} RUNTIME DESTINATION bin)
ENDFOREACH()

IF(lib_all)
    # move dll/so to bin dir
    # for internal targets
    INSTALL(
        TARGETS ${lib_inn_shared} ${lib_inn_static}
        RUNTIME DESTINATION bin
        ARCHIVE DESTINATION lib
    )
    # for external targets
    INSTALL(
        IMPORTED_RUNTIME_ARTIFACTS ${lib_ext_shared}
    )
ENDIF()


# ============================= shadertoy =============================
SET(
    shadertoy_src_list
    shadertoy/isovalues.cpp
    shadertoy/simple.cpp
)

SET(lib_inn_static)
SET(lib_inn_shared glad stb_image)
SET(lib_ext_static opengl32)
SET(lib_ext_shared glfw3 assimp-vc143-mtd)
SET(
    lib_all
    ${lib_inn_static}
    ${lib_inn_shared}
    ${lib_ext_static}
    ${lib_ext_shared}
)

FOREACH(_src_file ${shadertoy_src_list})
    GET_FILENAME_COMPONENT(_src_name ${_src_file} NAME_WE)
    ADD_EXECUTABLE(${_src_name} ${_src_file})
    IF(lib_all)
        TARGET_LINK_LIBRARIES(${_src_name} ${lib_all})
    ENDIF()
    INSTALL(TARGETS ${_src_name} RUNTIME DESTINATION bin)
ENDFOREACH()

IF(lib_all)
    # move dll/so to bin dir
    # for internal targets
    INSTALL(
        TARGETS ${lib_inn_shared} ${lib_inn_static}
        RUNTIME DESTINATION bin
        ARCHIVE DESTINATION lib
    )
    # for external targets
    INSTALL(
        IMPORTED_RUNTIME_ARTIFACTS ${lib_ext_shared}
    )
ENDIF()

同样也是可以收集所有子目录然后统一执行构建流程的,但问题是不是每个源文件都依赖于相同的库,虽然说直接把所有库放到一个列表里面让链接器自己去按需取用也没问题,但是感觉不够优雅而且不够精准,我就是想指定每个每个主程序所依赖的库……

比如这里分了 内部静态、内部动态、外部静态、外部动态 四个类型的库列表,按照源文件所依赖的函数填写对应的库名称即可。

SET(lib_inn_static)
SET(lib_inn_shared glad stb_image)
SET(lib_ext_static opengl32)
SET(lib_ext_shared glfw3 assimp-vc143-mtd)
SET(
    lib_all
    ${lib_inn_static}
    ${lib_inn_shared}
    ${lib_ext_static}
    ${lib_ext_shared}
)

import.cmake

这个文件主要是为了扫描所有的头文件目录和外部库,然后告诉 CMake 。如果不告诉 CMake 头文件的目录路径而是使用默认的 VScode C++ Intellisense ,那么就只能使用双引号+头文件的文件名来引入头文件,可是很多时候头文件都是带相对路径的,所以必须告诉 VScode 的 C++ Intellisense 插件这些信息才不会弹出头文件包含错误提示(虽然编译的时候没事,但是看着总是不顺眼的嘛)。

# 包含头文件目录
FIND_HDRS(include return_hdr_dir_list)
PRINT_LIST("${return_hdr_dir_list}" "HEADER" "")
INCLUDE_DIRECTORIES(${return_hdr_dir_list})

SET(lib_import_shared_list)
SET(lib_import_static_list)

SET(lib_shared_suffix "dll") # linux: so
SET(lib_static_suffix "lib") # linux: a

LINK_DIRECTORIES(lib/glfw3)
FIND_LIBS(lib/glfw3 ${lib_shared_suffix} return_lib_name_list return_lib_path_list)
MAKE_LIBS_TARGET("${return_lib_name_list}" "${return_lib_path_list}" SHARED)
LIST(APPEND lib_import_shared_list ${return_lib_name_list})

LINK_DIRECTORIES(lib/assimp)
FIND_LIBS(lib/assimp ${lib_shared_suffix} return_lib_name_list return_lib_path_list)
MAKE_LIBS_TARGET("${return_lib_name_list}" "${return_lib_path_list}" SHARED)
LIST(APPEND lib_import_shared_list ${return_lib_name_list})


LINK_DIRECTORIES(lib/opengl32)
FIND_LIBS(lib/opengl32 ${lib_static_suffix} return_lib_name_list return_lib_path_list)
MAKE_LIBS_TARGET("${return_lib_name_list}" "${return_lib_path_list}" STATIC)
LIST(APPEND lib_import_static_list ${return_lib_name_list})

PRINT_LIST("${lib_import_shared_list}" "IMPORT LIB SHARED" "")
PRINT_LIST("${lib_import_static_list}" "IMPORT LIB STATIC" "")

同理每次导入了新的库和头文件,应该在这里添加新的扫描(头文件目录是递归扫描的,但是库目录我没有这样做)。

其他 CMake 函数

还有一些上面用到的 CMake 函数宏在 cmake 目录下,主要是搜索头文件,库文件以及打印列表的函数。

FUNCTION(FIND_LIBS lib_dir suffix return_lib_name_list return_lib_path_list)
    UNSET(return_lib_name_list CACHE)
    UNSET(return_lib_path_list CACHE)

    FILE(
        GLOB lib_path_list
        ${lib_dir}/*.${suffix}
    )

    FOREACH(_lib_path ${lib_path_list})
        GET_FILENAME_COMPONENT(_lib_name ${_lib_path} NAME_WE)
        # MESSAGE("_lib_name ${_lib_name}")
        LIST(APPEND lib_name_list ${_lib_name})
    ENDFOREACH(_lib_path ${lib_path_list})

    # MESSAGE("lib_name_list ${lib_name_list}")
    # MESSAGE("lib_path_list ${lib_path_list}")
    SET(return_lib_name_list ${lib_name_list} PARENT_SCOPE)
    SET(return_lib_path_list ${lib_path_list} PARENT_SCOPE)
ENDFUNCTION()

FUNCTION(MAKE_LIBS_TARGET lib_name_list lib_path_list lib_type)
    IF(NOT lib_type STREQUAL "SHARED" AND NOT lib_type STREQUAL "STATIC")
        MESSAGE(FATAL_ERROR "unknown lib type ${lib_type}")
    ENDIF()

    LIST(LENGTH lib_name_list num_lib_name)
    LIST(LENGTH lib_path_list num_lib_path)
    IF(NOT num_lib_name EQUAL num_lib_path)
        MESSAGE(FATAL_ERROR "number of name and path of libs not equal")
    ENDIF()

    FOREACH(i RANGE 0 ${num_lib_name})
        IF(${i} EQUAL ${num_lib_name})
            BREAK()
        ENDIF()

        LIST(GET lib_name_list ${i} _lib_name)
        LIST(GET lib_path_list ${i} _lib_path)
        ADD_LIBRARY(${_lib_name} ${lib_type} IMPORTED GLOBAL)
        SET_PROPERTY(
            TARGET ${_lib_name}
            PROPERTY IMPORTED_LOCATION ${_lib_path}
        )
        # only for windows dll, that's suck
        IF(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND lib_type STREQUAL "SHARED")
            GET_FILENAME_COMPONENT(_lib_dir ${_lib_path} DIRECTORY)
            SET_PROPERTY(
                TARGET ${_lib_name}
                PROPERTY IMPORTED_IMPLIB ${_lib_dir}/${_lib_name}.lib # may be this name rule?
            )
        ENDIF()
    ENDFOREACH()

ENDFUNCTION()

FUNCTION(FIND_HDRS hdr_dir return_hdr_dir_list)
    UNSET(hdr_dir_list CACHE)
    SET(hdr_dir_list)
    FILE(
        GLOB_RECURSE hdr_path_list
        ${hdr_dir}/*.h
        ${hdr_dir}/*.hpp
    )
    SET(hdr_dir_list ${hdr_dir})
    FOREACH(_hdr_path ${hdr_path_list})
        GET_FILENAME_COMPONENT(_hdr_dir ${_hdr_path} PATH)
        LIST(APPEND hdr_dir_list ${_hdr_dir})
    ENDFOREACH(_hdr_path ${hdr_path_list})
    LIST(REMOVE_DUPLICATES hdr_dir_list)
    SET(return_hdr_dir_list ${hdr_dir_list} PARENT_SCOPE)
ENDFUNCTION()


# print list item
FUNCTION(PRINT_LIST list_item title prefix)
    IF(NOT list_item OR (list_item STREQUAL ""))
        RETURN()
    ENDIF()
    MESSAGE("┌────────────────── ${title}")
    FOREACH(item ${list_item})
        MESSAGE("│ ${prefix} ${item}")
    ENDFOREACH()
    MESSAGE("└──────────────────]\n")
ENDFUNCTION()

使用框架

因为是在 Windows 平台下编译的,所以能用 MSVC 当然还是用 MSVC,虽然感觉有些难用就是了。VScode 的 CMake 插件可以选择构建的工具链,比如 MinGW 或者 MSVC ,MinGW 的 g++/gcc 构建比较简单,这里我主要是测试 MSVC 构建流程。所以如果使用 MSVC 的话,那么是无法直接通过 VScode 打开项目目录构建的:

  • 此时 C++ Intellisense 没有 MSVC 的扫描权限,无法找到标准库头文件诸如 iostream 等
  • 此时编译可以完成,但是编译出来的动态库不具有链接性(可能是我个人的原因?)

正确的操作方式应该是打开 VisualStudio 开发者命令行工具,然后 cd 到项目的目录下,再使用 code . 命令在此目录下打开 VScode ,这样才能检索到 MSVC 和 WindowsSDK 的头文件目录以及标准库。

CMake 配置时的输出如下:

[main] 正在配置项目: shadertoy 
[proc] 执行命令: E:\CMake\bin\cmake.EXE -DCMAKE_EXPORT_COMPILE_COMMANDS=ON --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -SF:/fredom/workspace/play-cc/shadertoy -Bf:/fredom/workspace/play-cc/shadertoy/build -G "Visual Studio 17 2022" -T host=x64 -A x64
[cmake] Not searching for unused variables given on the command line.
[cmake] -- Selecting Windows SDK version 10.0.22621.0 to target Windows 10.0.19045.
[cmake] -- The C compiler identification is MSVC 19.38.33134.0
[cmake] -- The CXX compiler identification is MSVC 19.38.33134.0
[cmake] -- Detecting C compiler ABI info
[cmake] -- Detecting C compiler ABI info - done
[cmake] -- Check for working C compiler: E:/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.38.33130/bin/Hostx64/x64/cl.exe - skipped
[cmake] -- Detecting C compile features
[cmake] -- Detecting C compile features - done
[cmake] -- Detecting CXX compiler ABI info
[cmake] -- Detecting CXX compiler ABI info - done
[cmake] -- Check for working CXX compiler: E:/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.38.33130/bin/Hostx64/x64/cl.exe - skipped
[cmake] -- Detecting CXX compile features
[cmake] -- Detecting CXX compile features - done
[cmake] build system: Windows
[cmake] F:/fredom/workspace/play-cc/shadertoy/build
[cmake] compiler tool chain MSVC, all symbols will export by default
[cmake] ┌────────────────── HEADER
[cmake] │  include
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/GLFW
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/KHR
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/assimp
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/assimp/Compiler
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/dummy
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/dummy/dummy2
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glad
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glm
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glm/detail
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glm/ext
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glm/gtc
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glm/gtx
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/glm/simd
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/learnopengl
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/stb_image
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/include/utils
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── IMPORT LIB SHARED
[cmake] │  glfw3
[cmake] │  assimp-vc143-mtd
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── IMPORT LIB STATIC
[cmake] │  opengl32
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── MODULE [dummy] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/dummy/dummy.cpp
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/dummy/dummy2.cpp
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── MODULE [utils] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/utils/clog.c
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/utils/cpplog.cpp
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── MODULE [other] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/./add.cpp
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/./mathnipet.cpp
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── MODULE [glad] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/glad/glad.c
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── MODULE [stb_image] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/src/stb_image/stb_image.cpp
[cmake] └──────────────────]
[cmake] 
[cmake] ┌────────────────── TEST [LOG] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/test/log/clog_test.c
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/test/log/cpplog_test.cpp
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/test/log/mixlog_test.cpp
[cmake] └──────────────────]
[cmake] 
[cmake] -- Configuring done (4.9s)
[cmake] ┌────────────────── TEST [misc] SRC
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/test/misc/enummap.cpp
[cmake] │  F:/fredom/workspace/play-cc/shadertoy/test/misc/include.cpp
[cmake] └──────────────────]
[cmake] 
[cmake] -- Generating done (0.1s)
[cmake] -- Build files have been written to: F:/fredom/workspace/play-cc/shadertoy/build
[visual-studio] 为 E:\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat 修补从 C:\Program Files (x86)\Windows Kits\10\bin\x64 到 C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64 的 Windows SDK 路径

编译完所有项目之后,可以直接在 install 目录里面的 bin 目录双击执行可调式文件,或者直接在 VScode 里面 F5 一键调试。目前验证的 learnopengl 教程只有几个,没有全部验证,不过也就是简单的把 LearnOpenGL 的代码拷贝到本地做一下资源路径的修改罢了,因为本意还是给 ShaderToy 准备的,LearnOpenGL 是用来验证 MSVC 生成的其他动态库可以链接使用,功能正常。

实际效果

摄像机坐标变换

典型的 FPS 移动摄像机

使用 assimp 库加载模型

背包模型导入 obj 以及材质文件和纹理贴图之后的效果

shadertoy 测试 framebuffer 离屏渲染

帧缓冲重新作为片段着色器输出的测试

其他

相关的代码和 MSVC 2022 编译的 GLAD、assimp 库之类的放在代码仓库了,有点大,200 多 MB,好像 GitHub 最多支持 5 GB 的仓库大小,其实主要是 assets 资源文件里面纹理贴图有点多,构建出来的动态库本身倒不是很大。下载代码下来之后记得把 build 目录删除清空,重新执行 CMake 配置缓存。注意代码仓库里面包含的 lib 下的动态库都是 MSVC 2022 编译构建的,如果使用 MinGW 的话,需要自己把对应的 GLAD 还有 assimp 库用 MinGW 生成动态库,复制到项目的 lib 目录下对应的子目录。Windows 下编译动态库虽然也会输出 lib 文件,但是这个文件是用于动态库链接的时候找导出符号的,和平常的 lib 静态库文件不是一个东西……所以如果在 Windows 下使用 MSVC 构建程序动态链接,记得把和动态库同名的 .lib 后缀的符号导出文件一起拷贝过去,同时在 CMake 里面指定该动态库的导入符号属性文件路径。

CMake 现在已经为 Windows 平台下的函数动态库自动导出所有函数符号实现了预定义宏开关 CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE ,只要该宏被设置了,那么所有函数都可以像 GNU g++/gcc 那样生成的动态库一样自动导出,但是对于这些函数所依赖的全局数据变量,仍然需要使用 __declspec(dllexport/dllimport) 来完成符号的导出和引入,具体可以参考 这篇文章 。

For global data symbols, __declspec(dllimport) must still be used when compiling against the code in the DLL

另外在编译 OpenGL 项目的时候,因为需要用到 glad 来加载 OpenGL 的函数指针,所以我想把 glad.c 以及 glad.h 还有相关的头文件编译为动态库,但是尽管我使用了上述的 CMake 预定义宏,编译能完成但是链接二进制文件的时候,还是报错无法链接到动态库的函数,这困扰了我几个小时,后来在 glad 官方仓库的 issues 里面看到有人回答了这个问题,原来 glad.h 里面对所有函数指针的导出都是以全局公共数据变量而不是函数变量定义的,上文也说到,CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE 预定义宏只能导出函数符号,全局变量符号还是需要用到 __declspec ,所以对于该动态库的编译,必须在编译的时候添加额外的宏定义参数:

target_compile_definitions(glad PUBLIC GLAD_GLAPI_EXPORT PRIVATE GLAD_GLAPI_EXPORT_BUILD)

因为 glad.h 这个头文件里面定义了这么一段:

#ifndef GLAPI
# if defined(GLAD_GLAPI_EXPORT)
#  if defined(_WIN32) || defined(__CYGWIN__)
#   if defined(GLAD_GLAPI_EXPORT_BUILD)
#    if defined(__GNUC__)
#     define GLAPI __attribute__ ((dllexport)) extern
#    else
#     define GLAPI __declspec(dllexport) extern
#    endif
#   else
#    if defined(__GNUC__)
#     define GLAPI __attribute__ ((dllimport)) extern
#    else
#     define GLAPI __declspec(dllimport) extern
#    endif
#   endif
#  elif defined(__GNUC__) && defined(GLAD_GLAPI_EXPORT_BUILD)
#   define GLAPI __attribute__ ((visibility ("default"))) extern
#  else
#   define GLAPI extern
#  endif
# else
#  define GLAPI extern
# endif
#endif

真的有点坑人……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/719874.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux入门攻坚——26、Web Service基础知识与httpd配置-2

http协议 URL:Uniform Resource Locator,统一资源定位符 URL方案:scheme,如http://,https:// 服务器地址:IP:port 资源路径: 示例:http://www.test.com:80/bbs/…

rabbitMQ的简单使用

rabbitMQ的介绍 RabbitMQ是一个开源的消息代理和队列服务器,主要用于在不同的应用程序之间传递消息。它基于AMQP(Advanced Message Queuing Protocol)协议,提供了一种可靠的方式来处理异步通信。RabbitMQ使用Erlang语言编写&…

IT入门知识第三部分《软件开发》(3/10)

目录 IT入门知识大纲第三部分《软件开发》 1. 软件开发生命周期(SDLC) 1.1 需求分析 1.2 软件设计 1.3 程序编码 1.4 软件测试 1.5 项目部署 1.6 运行维护 2. 软件开发方法论 2.1 瀑布模型 2.2 敏捷开发 2.2.1 Scrum 2.2.2 Kanban 2.3 Dev…

告别盲目投放,Xinstall全方位监测App广告效果

在移动互联网高速发展的今天,App广告已成为品牌与消费者沟通的重要桥梁。然而,如何确保广告投放的精准性和效果性,成为广告主们关注的焦点。在这个数据驱动的时代,Xinstall以其专业的App广告效果统计功能,为广告主们提…

将Typora中图片从指定路径移动到当前文件夹下(准确位置为:XX.md文件所在目录/XX.assets/)

1、背景介绍 最开始时候,将复制到Typora中的图片放在了指定位置。但是在进行迁移(在别的电脑上打开md文件),会发现发送过来的文件不包含图片,导致文件内容缺失。解决办法就是将图片放在XX.md文件所在目录/XX.assets/。 2、方法 2.1 做好文件备份 2.2 替换图片链接中的…

使用MyBatisPlus让数据库和实体类字段自动映射

文章目录 使用MyBatisPlus让数据库和实体类字段自动映射需求场景假如没有映射把映射放到sql语句中使用MyBatisPlus提供的注解简化映射 使用MyBatisPlus让数据库和实体类字段自动映射 需求场景 数据库表中的字段名字,与实体类中的属性名字不一致,我们想…

使用PaddleNLP 从0构建一个属于你自己的心理大模型

项目源于:EmoLLM心理大模型,一直就有用paddle实践心理大模型的想法,终于实现了哈~。接下来就手把手带大家一块做一个心理大模型吧!!! 简单画了个框架图: 环境配置 In [ ] # !git clone -b develop https…

新安装的gcc编译出现编译器内部错误

在原本的环境中已经安装gcc了,但是版本比较低,想用新的版本。 重新下载安装gcc源码编译安装 make install安装好之后想用新的gcc来编译 先改下头文件搜索路径,xxxxxx就是安装后的include/c/xxx/这个路径 CPLUS_INCLUDE_PATH xxxxxx:$CPL…

建筑幕墙甲级设计资质:申请条件与评分标准

建筑幕墙甲级设计资质的申请条件与评分标准可以清晰归纳如下: 申请条件 一、企业基本情况 独立企业法人资格:企业需具有独立企业法人资格。注册资本:注册资本不少于300万元人民币。 二、技术人员条件 主要技术负责人或总工程师&#xff…

Nuxt.js 深入浅出:目录结构与文件组织详解

title: Nuxt.js 深入浅出:目录结构与文件组织详解 date: 2024/6/18 updated: 2024/6/18 author: cmdragon excerpt: 摘要:本文详述了Nuxt.js框架中关键目录与配置文件的作用及使用方法,包括布局设定、页面结构管理、插件集成、静态资源处理…

1999-2020年各地级市农村居民人均纯收入数据

1999-2020年各地级市农村居民人均纯收入数据 1、时间:1999-2020年 2、指标:年份、城市、农村居民人均纯收入 3、来源:区域年鉴、各省市年鉴 4、范围:地级市,具体每年城市数量参看下文图片,具体城市名单…

macbook屏幕录制技巧,这2个方法请你收好

在当今数字化时代,屏幕录制成为了一项不可或缺的技能,无论是教学演示、游戏直播,还是软件操作教程,屏幕录制都能帮助我们更直观地传达信息。MacBook作为苹果公司的标志性产品,其屏幕录制功能也备受用户关注。本文将详细…

【小白专用24.6.18】C# SqlSugar:连接数据库实现简单的,增、删、改、查

【小白专用 已验证24.6.18】C# SqlSugar操作MySQL数据库实现增删改查-CSDN博客 通过NuGet包管理器搜索SqlSugar(MySql还要安装MySql.Data、Newtonsoft.Json)包并安装 SqlSugarClient db new SqlSugarClient(new ConnectionConfig(){ConnectionString …

计数类DP——AcWing 900. 整数划分

计数类DP 定义 计数类DP主要是通过动态规划的方法来计算满足特定条件的方案数、组合数等数量相关的问题。 运用情况 需要计算不同排列、组合或情况的数量。问题具有明显的阶段性,且每个阶段的选择会对后续阶段产生影响。可以通过逐步构建较小规模问题的解来推导…

mumu 模拟器如何模拟指纹识别?

最近在帮朋友解决一些任务时,有些比较复杂的任务需要批量使用模拟器,但是模拟器存在一个缺点,就是缺少很多物理功能,比如说陀螺仪、温度传感器和生物识别模块等等,但是有些任务是需要这些功能的。没有办法,…

vue3-openlayers 使用tianditu,wmts和xyz等source加载天地图切片服务

本篇介绍一下使用vue3-openlayers加载天地图切片,三种方法: 使用tianditu(ol-source-tianditu内部实现其实用的wmts)使用wmts(ol-source-wmts)使用xyz(ol-source-xyz) 1 需求 vue…

为什么动态代理接口中可以不加@Mapper注解

为什么动态代理接口中可以不加Mapper注解 如下图: 我们上面的UserMapper上面没有加Mapper注解,按道理来说UserMapper这个类应该是注入不到IOC容器里面的,但是为什么我们程序的运行效果仍然是正常的呢?这是因为你的启动类上加了m…

zabbix“专家坐诊”第242期问答

问题一 Q:snmp检查用的什么性能啊?设备多了就检测失败,实际是能通的。 A:把大批量请求取消,把异常获取不到的监控项都禁用 Q:是这个吧,显示不一样。 A:什么版本?用的是v3…

ggpicrust2包:简化和直观化微生物功能预测分析

简介 ggpicrust2是一个强大的R语言包,旨在简化和直观化PICRUSt2输出的分析。通过预定义的图表和函数,研究人员可以轻松生成关于微生物功能预测的统计图,并提供丰富的自定义选项。本文将演示如何使用ggpicrust2包进行分析和可视化。 安装ggp…

VBA学习(9):按指定名单一键删除工作表

今天继续给大家聊VBA编程中工作表对象的常用操作,主要内容是如何批量删除工作表;也就是删除单个工作表、删除全部工作表和删除指定名单内的工作表。 1.删除单个工作表 删除工作表需要使用到工作表对象的delete方法,语法格式如下&#xff1a…