LearnOpenGL 及 ShaderToy 的 CMake 构建框架

news2024/11/27 7:27:54

文章目录

    • 构建目标
    • 具体框架
      • 根目录
      • 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

真的有点坑人……

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python类的优势及应用场景深度分析(代码封装与组织、继承与代码复用、多态与接口、状态管理与行为封装)(python class)

文章目录 Python 类的优势及应用场景深度分析1. 代码封装与组织1.1 封装性示例代码:用户账户管理 1.2 组织性 2. 继承与代码复用2.1 继承性示例代码:员工管理系统 3. 多态与接口3.1 多态性示例代码:图形渲染 4. 状态管理与行为的封装4.1 状态…

记录一下 Chrome浏览器打印时崩溃问题

问题描述: 为了查看页面内存占用情况,按F2,打开Memory chrome浏览器点击“打印”按钮,或Ctrl P 时出现如下页面 一直以为是页面问题,每次打印的时候遇到这种 崩溃现象 就是重新刷新页面 但今天刚开一个页面,内存 …

微信小程序 - 出于性能原因,对长行跳过令牌化。长行的长度可通过 “editor.maxTokenizationLineLength” 进行配置

问题描述 出于性能原因,对长行跳过令牌化。长行的长度可通过 “editor.maxTokenizationLineLength” 进行配置。 解决方案 设置 - 编辑器设置 - 更多编辑器设置... 搜索:maxtoken,原来是 20000,我改成了 200000 即可~

电脑已删除的文件在回收站找不到怎么办?数据恢复办法分享!

电脑中的数据已经成为了我们生活和工作的重要部分。无论是珍贵的照片、重要的文档,还是日常的工作文件,我们都希望能够妥善保存很久。 然而,误删除文件的情况时有发生,而当我们急切地打开回收站试图找回这些文件时,却…

Bev感知:sparse query

文章目录 1. 显示Bev方法介绍1.1 2D to 3D: LSS-based1.1.1 优点1.1.2 缺点1.2. 3D to 2D: BevFormer1.2.1 缺点1.2.2优点1.3 常见的Bev感知的问题2. Sparse query2.1 PETRv1创新点3D 位置编码实验对比2.2 PETRv22.2.1 时序对齐2.2.2 Feature guided 3D PE2.2.3 多任务2.2.3 性…

功能测试的内容与目的是什么?

在软件开发与测试过程中,功能测试是不可或缺的关键步骤,它主要关注软件产品是否能够按照设计规格和用户需求实现预定的功能。功能测试的内容与目的,简单来讲,就是验证软件的各种特性和功能是否正确、完整且符合预期,确…

【C#】汽车租赁系统设计与实现

目的: 设计一个简单的汽车租赁系统,包含以下功能: 添加车辆:用户可以添加新的车辆到系统中,包括车辆的品牌、型号、车牌号、日租金等信息。查找车辆:用户可以通过车牌号或者品牌来查找车辆,并…

SFNC —— 采集控制(四)

系列文章目录 SFNC —— 标准特征命名约定(一) SFNC —— 设备控制(二) SFNC —— 图像格式控制(三) SFNC —— 采集控制(四) 文章目录 系列文章目录5、采集控制(Acquisi…

第6章 设备驱动程序(2)

目录 6.3 和文件系统关联 6.3.1 inode的设备文件成员 6.3.2 标准文件操作 6.3.3 字符设备的标准操作 6.3.4 块设备的标准操作 6.4 字符设备操作 6.4.1 表示字符设备 6.4.2 打开设备文件 6.4.3 读写操作 本专栏文章将有70篇左右,欢迎关注,查看后…

Vue项目中实现骨架占位效果-demo

创建组件 Skeleton.vue <template><div class"skeleton"><div class"skeleton-item" v-for"n in count" :key"n"></div></div> </template><script> export default {props: {count: {ty…

物联网技术-第3章物联网感知技术-3.2定位技术

目录 1.1位置信息和位置服务 1.1.1位置信息 1.1.2位置服务 1.2主流定位系统 1.2.1卫星定位系统&#xff08;Satellite Positioning Systems&#xff09; 1.2.2移动通信蜂窝基站定位&#xff08;Cellular Triangulation or Advanced Forward Link Trilateration&#xff09…

Unity2D游戏制作入门 | 14( 之人物实装攻击判定 )

上期链接&#xff1a;Unity2D游戏制作入门 | 13 ( 之人物三段攻击 )-CSDN博客 上期我们聊到给人物添加三段攻击的动画&#xff0c;通过建立新的图层动画当我们按下攻击按键就会自动切换进攻击的动画&#xff0c;如果我们连续按下攻击键&#xff0c;我们还可以进行好几段的攻击…

Linux系统:信号概念 信号产生

Linux系统&#xff1a;信号概念 & 信号产生 信号概念信号产生软件信号killraiseabortalarm 硬件信号键盘产生信号硬件中断 信号概念 信号是进程之间事件异步通知的一种方式 在Linux命令行中&#xff0c;我们可以通过ctrl c来终止一个前台运行的进程&#xff0c;其实这就是…

利用K8S技术栈打造个人私有云

1.三个节点&#xff1a;master&#xff0c;slave&#xff0c;client 在Kubernetes集群中&#xff0c;三个节点的职责分别如下&#xff1a; Master节点&#xff1a; docker&#xff1a;用于运行Docker容器。 etcd&#xff1a;一个分布式键值存储系统&#xff0c;用于保存Kuberne…

微信投票源码系统+礼物+道具投票 无限多开 带完整的安装代码包以及搭建教程

系统概述 微信投票源码系统是一款基于先进技术开发的综合性投票平台&#xff0c;它不仅融合了传统投票的核心功能&#xff0c;还创新性地引入了礼物和道具投票机制&#xff0c;为用户带来了全新的投票体验。 该系统支持无限多开&#xff0c;这意味着用户可以根据实际需求&…

AI写真:ControlNet 之 InstantID

但是 IPAdapter-FaceId 目前只在 SD 1.5 模型上表现较好&#xff0c;SDXL 模型上的表现较差&#xff0c;不能用于实际生产。可是很多同学已经在使用SDXL了&#xff0c;而且SDXL确实整体上出图效果更好&#xff0c;怎么办&#xff1f; 这篇文章就来给大家介绍一个在SDXL中创作A…

多线程环境下,HashMap 为什么会出现死循环?

引言&#xff1a;HashMap作为一个常用的键值对存储结构&#xff0c;其内部实现在不同的JDK版本中有所演变&#xff0c;但其基本原理始终是通过哈希算法和数组来实现快速查找和存储。我们将探讨HashMap在多线程环境下出现死循环的根本原因&#xff0c;深入分析其中的数据结构特点…

网络安全(完整)

WAPI鉴别及密钥管理的方式有两种&#xff0c;既基于证书和基于预共享密钥PSK。若采用基于证书的方式&#xff0c;整个国产包括证书鉴别、单播密钥协商与组播密钥通告&#xff1b;若采用预共享密钥方式&#xff0c;整个国产则为单播密钥协商与组播密钥通告蠕虫利用信息系统缺陷&…

Tailwind CSS 响应式设计实战指南

title: Tailwind CSS 响应式设计实战指南 date: 2024/6/13 updated: 2024/6/13 author: cmdragon excerpt: 这篇文章介绍了如何运用Tailwind CSS框架创建响应式网页设计&#xff0c;涵盖博客、电商网站及企业官网的布局实例&#xff0c;包括头部导航、内容区域、侧边栏、页脚…

国产24位I2S输入+192kHz立体声DAC音频数模转换器CJC4344

CJC4344是一款立体声数模转换芯片&#xff0c;内含插值滤波器、multi bit数模转换器、输出模拟滤波器。CJC4344系列支持大部分的音频数据格式。CJC4344基于一个带线性模拟低通滤波器的四阶multi-bitΔ-Σ调制器&#xff0c;而且本芯片可以通过检测信号频率和主时钟频率&#xf…