1 Commits

Author SHA1 Message Date
psc d99da004d0 3D 第一版 2026-08-13 19:47:37 +08:00
26 changed files with 2141 additions and 297 deletions
+59 -12
View File
@@ -1,14 +1,32 @@
include_guard(GLOBAL)
if (WIN32 AND (NOT DEFINED ENV{VULKAN_SDK} OR NOT EXISTS "$ENV{VULKAN_SDK}/Include/vulkan/vulkan.h"))
file(GLOB _renderive_vulkan_sdk_candidates LIST_DIRECTORIES TRUE "C:/VulkanSDK/*")
list(SORT _renderive_vulkan_sdk_candidates COMPARE NATURAL ORDER DESCENDING)
foreach (_renderive_vulkan_sdk_candidate IN LISTS _renderive_vulkan_sdk_candidates)
if (EXISTS "${_renderive_vulkan_sdk_candidate}/Include/vulkan/vulkan.h" AND
EXISTS "${_renderive_vulkan_sdk_candidate}/Bin/glslc.exe")
set(ENV{VULKAN_SDK} "${_renderive_vulkan_sdk_candidate}")
break()
endif ()
endforeach ()
unset(_renderive_vulkan_sdk_candidate)
unset(_renderive_vulkan_sdk_candidates)
endif ()
block()
set(datoviz_opts
-DBUILD_SHARED_LIBS=OFF
"-DCMAKE_C_FLAGS=-utf-8"
"-DCMAKE_CXX_FLAGS=-utf-8"
-DDVZ_BUILD_CORE=ON
-DDVZ_BUILD_CONTROLLER=ON
-DDVZ_BUILD_VK=ON
-DDVZ_BUILD_CANVAS=ON
-DDVZ_BUILD_CANVAS=OFF
-DDVZ_BUILD_DRP2=ON
-DDVZ_BUILD_WEBGPU=OFF
-DDVZ_BUILD_SCENE=ON
-DDVZ_BUILD_APP=ON
-DDVZ_BUILD_APP=OFF
-DDVZ_BUILD_GUI=OFF
-DDVZ_BUILD_TESTING=OFF
-DDVZ_BUILD_EXAMPLES=OFF
@@ -17,35 +35,64 @@ block()
-DDVZ_ENABLE_CUDA=OFF
-DDVZ_ENABLE_KVAZAAR=OFF
-DDVZ_ENABLE_QT_BRIDGE=OFF
-DDVZ_ENABLE_SHADERC=OFF
-DDVZ_ENABLE_SHADERC=ON
-DDVZ_WITH_FREETYPE=OFF
-DDVZ_WITH_MSDF_ATLAS=OFF
-DDVZ_WITH_MSDF_SVG=OFF
-DDVZ_WITH_ZLIB=OFF
-DDVZ_WITH_GLFW=ON
-DDVZ_WITH_GLFW=OFF
-DDVZ_USE_MIMALLOC_RELEASE_DEFAULT=OFF
)
_register_git_cmake_library(renderive::datoviz
if (WIN32)
set(DATOVIZ_VULKAN_SDK "$ENV{VULKAN_SDK}")
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DATOVIZ_SHADERC_RUNTIME "${DATOVIZ_VULKAN_SDK}/Bin/shaderc_sharedd.dll")
set(DATOVIZ_SHADERC_LIBRARY "${DATOVIZ_VULKAN_SDK}/Lib/shaderc_sharedd.lib")
else ()
set(DATOVIZ_SHADERC_RUNTIME "${DATOVIZ_VULKAN_SDK}/Bin/shaderc_shared.dll")
set(DATOVIZ_SHADERC_LIBRARY "${DATOVIZ_VULKAN_SDK}/Lib/shaderc_shared.lib")
endif ()
if (NOT EXISTS "${DATOVIZ_VULKAN_SDK}/Include/vulkan/vulkan.h" OR
NOT EXISTS "${DATOVIZ_VULKAN_SDK}/Bin/glslc.exe" OR
NOT EXISTS "${DATOVIZ_SHADERC_LIBRARY}" OR
NOT EXISTS "${DATOVIZ_SHADERC_RUNTIME}")
message(FATAL_ERROR "Render_3D requires a Vulkan SDK with glslc and shared shaderc")
endif ()
list(APPEND datoviz_opts
"-DGLSLC_PATH=${DATOVIZ_VULKAN_SDK}/Bin"
"-DDVZ_SHADERC_INCLUDE_DIR=${DATOVIZ_VULKAN_SDK}/Include"
"-DDVZ_SHADERC_LIBRARY=${DATOVIZ_SHADERC_LIBRARY}"
"-DDVZ_SHADERC_RUNTIME_LIBRARY=${DATOVIZ_SHADERC_RUNTIME}"
)
endif ()
_register_git_cmake_library(render_3D::datoviz
"https://github.com/datoviz/datoviz.git"
"v0.4.0rc2"
)
rcl_cmake_library_set_cmake_options(renderive::datoviz ${datoviz_opts})
if (WIN32 AND NOT MINGW)
rcl_cmake_library_set_deps(renderive::datoviz renderive::pthreads4w)
rcl_get_effective_install_dir(render_3D::PThreads4W PTHREADS4W_ROOT)
set(PTHREADS4W_CONFIG_DIR "${CMAKE_CURRENT_LIST_DIR}/../render_3D/cmake/package/PThreads4W")
rcl_cmake_library_set_deps(render_3D::datoviz render_3D::PThreads4W)
list(APPEND datoviz_opts
"-DPTHREADS4W_ROOT=${PTHREADS4W_ROOT}"
"-DPThreads4W_DIR=${PTHREADS4W_CONFIG_DIR}"
)
endif ()
rcl_get_effective_install_dir(renderive::datoviz DATOVIZ_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::datoviz ${datoviz_opts})
rcl_get_effective_install_dir(render_3D::datoviz DATOVIZ_ROOT)
set(DATOVIZ_CMAKE_DIR "${DATOVIZ_ROOT}/lib/cmake/datoviz")
rcl_cmake_library_set_other_use_opt(renderive::datoviz
rcl_cmake_library_set_other_use_opt(render_3D::datoviz
"-Ddatoviz_DIR=\"${DATOVIZ_CMAKE_DIR}\""
"-DDatoviz_DIR=\"${DATOVIZ_CMAKE_DIR}\""
)
rcl_cmake_library_set_init_script(renderive::datoviz
rcl_cmake_library_set_init_script(render_3D::datoviz
"set(DATOVIZ_ROOT \"${DATOVIZ_ROOT}\")"
"set(datoviz_DIR \"${DATOVIZ_CMAKE_DIR}\")"
"set(Datoviz_DIR \"${DATOVIZ_CMAKE_DIR}\")"
)
rcl_cmake_library_set_clear_script(renderive::datoviz
rcl_cmake_library_set_clear_script(render_3D::datoviz
"unset(DATOVIZ_ROOT)"
"unset(datoviz_DIR)"
"unset(Datoviz_DIR)"
)
endblock()
endblock()
+39 -19
View File
@@ -1,16 +1,17 @@
# 加载 datoviz的依赖库
include(${CMAKE_CURRENT_LIST_DIR}/cmake/export.cmake)
set(Renderive_render_3D_dependencies
render_3D::PThreads4W
render_3D::cglm
render_3D::Vulkan-Hpp
render_3D::VulkanMemoryAllocator
render_3D::msdf-atlas-gen
render_3D::tinyobjloader
)
if (POLICY CMP0144)
cmake_policy(SET CMP0144 NEW)
endif ()
include("${CMAKE_CURRENT_LIST_DIR}/cmake/export.cmake")
set(Renderive_render_3D_dependencies render_3D::datoviz)
if (WIN32 AND NOT MINGW)
list(APPEND Renderive_render_3D_dependencies render_3D::PThreads4W)
endif ()
if (RENDERIVE_BUILD_TESTS)
list(APPEND Renderive_render_3D_dependencies global::GTest)
endif ()
rcl_add_dependency_action_targets(Renderive_render_3D_env ${Renderive_render_3D_dependencies})
set_target_properties(Renderive_render_3D_env PROPERTIES FOLDER Renderive_render_3D)
library_is_installed_with_rely(Renderive_render_3D_dependencies_installed ${Renderive_render_3D_dependencies})
@@ -18,23 +19,26 @@ if (NOT Renderive_render_3D_dependencies_installed)
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_render_3D dependencies are not installed. Build Renderive_render_3D_env first")
return()
endif ()
rcl_load_dependency_environment(${Renderive_render_3D_dependencies})
# 官网下载安装 https://vulkan.lunarg.com/sdk/home
set(ENV{VULKAN_SDK} "C:/VulkanSDK/1.4.357.0")
find_package(Vulkan REQUIRED)
message(STATUS "${B}Vulkan_VERSION=${Vulkan_VERSION}${E}")
find_package(PThreads4W REQUIRED)
find_package(cglm CONFIG REQUIRED)
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
find_package(msdf-atlas-gen CONFIG REQUIRED)
find_package(tinyobjloader CONFIG REQUIRED)
if (WIN32 AND NOT MINGW)
rcl_get_effective_install_dir(render_3D::PThreads4W PTHREADS4W_ROOT)
find_package(PThreads4W CONFIG REQUIRED
PATHS "${CMAKE_CURRENT_LIST_DIR}/cmake/package/PThreads4W"
NO_DEFAULT_PATH
)
endif ()
find_package(datoviz CONFIG REQUIRED)
if (RENDERIVE_BUILD_TESTS)
find_package(GTest CONFIG REQUIRED)
endif ()
if (NOT TARGET Renderive_Kernel)
rcl_log_append(${CMAKE_CURRENT_LIST_LINE} "[FATAL_ERROR] Renderive_render_3D requires Renderive_Kernel")
return()
endif ()
set(Renderive_render_3D_source_dir "${CMAKE_CURRENT_LIST_DIR}")
append_glob_source(Renderive_render_3D_sources "${Renderive_render_3D_source_dir}")
list(FILTER Renderive_render_3D_sources EXCLUDE REGEX "/tests/")
@@ -43,9 +47,16 @@ target_include_directories(Renderive_render_3D PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
)
target_compile_features(Renderive_render_3D PUBLIC cxx_std_20)
target_link_libraries(Renderive_render_3D
PRIVATE Renderive_Kernel datoviz::datoviz Vulkan::Vulkan
)
if (WIN32 AND NOT MINGW)
target_link_libraries(Renderive_render_3D PRIVATE PThreads4W::PThreads4W)
endif ()
if (MSVC)
target_compile_options(Renderive_render_3D PRIVATE /utf-8)
endif ()
if (RENDERIVE_BUILD_TESTS)
set(Renderive_render_3D_test_dir "${CMAKE_CURRENT_LIST_DIR}/tests")
append_glob_source(Renderive_render_3D_test_sources "${Renderive_render_3D_test_dir}")
@@ -60,7 +71,16 @@ if (RENDERIVE_BUILD_TESTS)
string(MAKE_C_IDENTIFIER "${Renderive_render_3D_test_name}" Renderive_render_3D_test_name)
set(Renderive_render_3D_test_target "Renderive_render_3D_${Renderive_render_3D_test_name}_${Renderive_render_3D_test_hash}")
add_executable("${Renderive_render_3D_test_target}" "${Renderive_render_3D_test_source}")
target_link_libraries("${Renderive_render_3D_test_target}" PRIVATE Renderive_render_3D GTest::gtest_main)
target_link_libraries("${Renderive_render_3D_test_target}" PRIVATE
Renderive_render_3D Renderive_Kernel datoviz::datoviz GTest::gtest_main
)
add_test(NAME "${Renderive_render_3D_test_target}" COMMAND "${Renderive_render_3D_test_target}")
set_property(TEST "${Renderive_render_3D_test_target}" PROPERTY TIMEOUT 60)
if (WIN32)
set_property(TEST "${Renderive_render_3D_test_target}" PROPERTY ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:${DATOVIZ_ROOT}/bin"
"PATH=path_list_prepend:${PThreads4W_RUNTIME_DIR}"
)
endif ()
endforeach ()
endif ()
-238
View File
@@ -1,238 +0,0 @@
include_guard(GLOBAL)
block()
set(freetype_opts)
_register_git_cmake_library(render_3D::freetype
https://github.com/freetype/freetype.git
VER-2-13-3
)
rcl_get_effective_install_dir(render_3D::freetype FREETYPE_ROOT)
set(FREETYPE_INCLUDE_DIRS "${FREETYPE_ROOT}/include/freetype2")
set(FREETYPE_INCLUDE_DIR_ft2build "${FREETYPE_ROOT}/include/freetype2")
set(FREETYPE_INCLUDE_DIR_freetype2 "${FREETYPE_ROOT}/include/freetype2")
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(FREETYPE_LIBRARY "${FREETYPE_ROOT}/lib/freetyped.lib")
else ()
set(FREETYPE_LIBRARY "${FREETYPE_ROOT}/lib/freetype.lib")
endif ()
rcl_cmake_library_set_cmake_options(render_3D::freetype ${freetype_opts})
rcl_cmake_library_set_other_use_opt(render_3D::freetype
"-DFREETYPE_INCLUDE_DIRS=\"${FREETYPE_INCLUDE_DIRS}\""
"-DFREETYPE_INCLUDE_DIR_ft2build=\"${FREETYPE_INCLUDE_DIR_ft2build}\""
"-DFREETYPE_INCLUDE_DIR_freetype2=\"${FREETYPE_INCLUDE_DIR_freetype2}\""
"-DFREETYPE_LIBRARY=\"${FREETYPE_LIBRARY}\""
"-DFREETYPE_LIBRARIES=\"${FREETYPE_LIBRARY}\""
)
rcl_cmake_library_set_init_script(render_3D::freetype
"set(FREETYPE_ROOT \"${FREETYPE_ROOT}\")"
"set(FREETYPE_INCLUDE_DIRS \"${FREETYPE_INCLUDE_DIRS}\")"
"set(FREETYPE_INCLUDE_DIR_ft2build \"${FREETYPE_INCLUDE_DIR_ft2build}\")"
"set(FREETYPE_INCLUDE_DIR_freetype2 \"${FREETYPE_INCLUDE_DIR_freetype2}\")"
"set(FREETYPE_LIBRARY \"${FREETYPE_LIBRARY}\")"
"set(FREETYPE_LIBRARIES \"${FREETYPE_LIBRARY}\")"
)
rcl_cmake_library_set_clear_script(render_3D::freetype
"unset(FREETYPE_ROOT)"
"unset(FREETYPE_INCLUDE_DIRS)"
"unset(FREETYPE_INCLUDE_DIR_ft2build)"
"unset(FREETYPE_INCLUDE_DIR_freetype2)"
"unset(FREETYPE_LIBRARY)"
"unset(FREETYPE_LIBRARIES)"
)
set(tinyxml2_opts
-Dtinyxml2_BUILD_TESTING=OFF
-Dtinyxml2_INSTALL_PKGCONFIG=OFF
-Dtinyxml2_SHARED_LIBS=OFF
-Dtinyxml2_INSTALL_CMAKEDIR=lib/cmake/tinyxml2
)
_register_git_cmake_library(render_3D::tinyxml2
"https://github.com/leethomason/tinyxml2.git"
"11.0.0"
)
rcl_get_effective_install_dir(render_3D::tinyxml2 TINYXML2_ROOT)
set(TINYXML2_CONFIG_DIR "${TINYXML2_ROOT}/lib/cmake/tinyxml2")
rcl_cmake_library_set_cmake_options(render_3D::tinyxml2 ${tinyxml2_opts})
rcl_cmake_library_set_other_use_opt(render_3D::tinyxml2
"-Dtinyxml2_DIR=\"${TINYXML2_CONFIG_DIR}\""
)
rcl_cmake_library_set_init_script(render_3D::tinyxml2
"set(TINYXML2_ROOT \"${TINYXML2_ROOT}\")"
"set(tinyxml2_DIR \"${TINYXML2_CONFIG_DIR}\")"
)
rcl_cmake_library_set_clear_script(render_3D::tinyxml2
"unset(TINYXML2_ROOT)"
"unset(tinyxml2_DIR)"
)
set(msdfgen_cmake_option ${base_options})
list(APPEND msdfgen_cmake_option
-DMSDFGEN_USE_SKIA=OFF
-DMSDFGEN_USE_VCPKG=OFF
-DBUILD_SHARED_LIBS=ON
-DMSDFGEN_DYNAMIC_RUNTIME=ON
-DMSDFGEN_INSTALL=ON
)
_register_git_cmake_library(render_3D::msdfgen
https://github.com/Chlumsky/msdfgen.git
v1.12
)
rcl_get_effective_install_dir(render_3D::msdfgen MSDFGEN_ROOT)
set(MSDFGEN_CONFIG_DIR "${MSDFGEN_ROOT}/lib/cmake/msdfgen")
rcl_cmake_library_set_cmake_options(render_3D::msdfgen ${msdfgen_cmake_option})
rcl_cmake_library_set_deps(render_3D::msdfgen
global::zlib
global::libpng
render_3D::freetype
render_3D::tinyxml2
)
rcl_cmake_library_set_other_use_opt(render_3D::msdfgen
"-Dmsdfgen_DIR=\"${MSDFGEN_CONFIG_DIR}\""
)
rcl_cmake_library_set_init_script(render_3D::msdfgen
"set(MSDFGEN_ROOT \"${MSDFGEN_ROOT}\")"
"set(msdfgen_DIR \"${MSDFGEN_CONFIG_DIR}\")"
)
rcl_cmake_library_set_clear_script(render_3D::msdfgen
"unset(MSDFGEN_ROOT)"
"unset(msdfgen_DIR)"
)
set(msdfgl_cmake_option "")
if (WIN32)
set(PThreads4W_option ${base_options})
list(APPEND PThreads4W_option
"-DCMAKE_C_FLAGS=-D_WINDLL"
"-DCMAKE_CXX_FLAGS=-D_WINDLL"
)
_register_git_cmake_library(render_3D::PThreads4W
https://github.com/jwinarske/pthreads4w.git
"cmake"
)
rcl_get_effective_install_dir(render_3D::PThreads4W PTHREADS4W_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::PThreads4W ${PThreads4W_option})
endif ()
set(glfw3_option ${base_options})
_register_git_cmake_library(render_3D::glfw3
https://github.com/glfw/glfw.git
"3.4"
)
rcl_get_effective_install_dir(render_3D::glfw3 GLFW3_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::glfw3 ${glfw3_option})
set(msdf-atlas-gen_option ${base_options})
list(APPEND msdf-atlas-gen_option
-DMSDFGEN_DISABLE_SVG=OFF
-DMSDFGEN_USE_SKIA=OFF
-DMSDF_ATLAS_BUILD_STANDALONE=OFF
-DMSDF_ATLAS_USE_VCPKG=OFF
-DMSDF_ATLAS_USE_SKIA=OFF
-DMSDF_ATLAS_DYNAMIC_RUNTIME=ON
-DMSDF_ATLAS_MSDFGEN_EXTERNAL=OFF
-DMSDF_ATLAS_INSTALL=ON
-DMSDFGEN_INSTALL=ON
-DBUILD_SHARED_LIBS=OFF
)
_register_git_cmake_library(render_3D::msdf-atlas-gen
https://github.com/Chlumsky/msdf-atlas-gen.git
v1.4
)
rcl_get_effective_install_dir(render_3D::msdf-atlas-gen MSDF_ATLAS_GEN_ROOT)
set(MSDF_ATLAS_GEN_CONFIG_DIR "${MSDF_ATLAS_GEN_ROOT}/lib/cmake/msdf-atlas-gen")
set(MSDF_ATLAS_GEN_MSDFGEN_CONFIG_DIR "${MSDF_ATLAS_GEN_ROOT}/lib/cmake/msdfgen")
rcl_cmake_library_set_cmake_options(render_3D::msdf-atlas-gen ${msdf-atlas-gen_option})
rcl_cmake_library_set_deps(render_3D::msdf-atlas-gen
global::zlib
global::libpng
render_3D::freetype
render_3D::tinyxml2
)
rcl_cmake_library_set_other_use_opt(render_3D::msdf-atlas-gen
"-Dmsdf-atlas-gen_DIR=\"${MSDF_ATLAS_GEN_CONFIG_DIR}\""
"-Dmsdfgen_DIR=\"${MSDF_ATLAS_GEN_MSDFGEN_CONFIG_DIR}\""
)
rcl_cmake_library_set_init_script(render_3D::msdf-atlas-gen
"set(MSDF_ATLAS_GEN_ROOT \"${MSDF_ATLAS_GEN_ROOT}\")"
"set(msdf-atlas-gen_DIR \"${MSDF_ATLAS_GEN_CONFIG_DIR}\")"
"set(msdfgen_DIR \"${MSDF_ATLAS_GEN_MSDFGEN_CONFIG_DIR}\")"
)
rcl_cmake_library_set_clear_script(render_3D::msdf-atlas-gen
"unset(MSDF_ATLAS_GEN_ROOT)"
"unset(msdf-atlas-gen_DIR)"
"unset(msdfgen_DIR)"
)
set(cglm_option ${base_options})
_register_git_cmake_library(render_3D::cglm
"https://github.com/recp/cglm.git"
v0.9.6
)
rcl_get_effective_install_dir(render_3D::cglm CGLM_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::cglm ${cglm_option})
set(glm_option ${base_options})
_register_git_cmake_library(render_3D::glm
"https://github.com/g-truc/glm.git"
"1.0.3"
)
rcl_get_effective_install_dir(render_3D::glm GLM_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::glm ${glm_option})
set(tinyobjloader_option ${base_options})
list(APPEND tinyobjloader_option
-DCMAKE_POLICY_VERSION_MINIMUM=3.20
)
_register_git_cmake_library(render_3D::tinyobjloader
"https://github.com/tinyobjloader/tinyobjloader.git"
"v1.0.7"
)
rcl_get_effective_install_dir(render_3D::tinyobjloader TINYOBJLOADER_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::tinyobjloader ${tinyobjloader_option})
set(vk-bootstrap_option ${base_options})
_register_git_cmake_library(render_3D::vk-bootstrap
"https://github.com/charles-lunarg/vk-bootstrap.git"
"v1.4.357"
)
rcl_get_effective_install_dir(render_3D::vk-bootstrap VK_BOOTSTRAP_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::vk-bootstrap ${vk-bootstrap_option})
set(Vulkan-Hpp_option ${base_options})
_register_git_cmake_library(render_3D::Vulkan-Hpp
"https://github.com/KhronosGroup/Vulkan-Hpp.git"
"v1.4.357"
)
rcl_get_effective_install_dir(render_3D::Vulkan-Hpp VULKAN_HPP_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::Vulkan-Hpp ${Vulkan-Hpp_option})
set(volk_option ${base_options})
list(APPEND volk_option
-DVOLK_INSTALL=ON
-DVOLK_PULL_IN_VULKAN=ON
-DVOLK_HEADERS_ONLY=OFF
-DVOLK_NAMESPACE=ON
)
_register_git_cmake_library(render_3D::volk
"https://github.com/zeux/volk.git"
"vulkan-sdk-1.4.357.0"
)
rcl_get_effective_install_dir(render_3D::volk VOLK_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::volk ${volk_option})
set(VulkanMemoryAllocator_option ${base_options})
_register_git_cmake_library(render_3D::VulkanMemoryAllocator
"https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git"
"v3.3.0"
)
rcl_get_effective_install_dir(render_3D::VulkanMemoryAllocator VULKAN_MEMORY_ALLOCATOR_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::VulkanMemoryAllocator ${VulkanMemoryAllocator_option})
set(tinygltf_option ${base_options})
_register_git_cmake_library(render_3D::tinygltf
"https://github.com/syoyo/tinygltf.git"
"v3.0.0"
)
rcl_get_effective_install_dir(render_3D::tinygltf TINYGLTF_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::tinygltf ${tinygltf_option})
set(fastgltf_option ${base_options})
_register_git_cmake_library(render_3D::fastgltf
"https://github.com/spnda/fastgltf.git"
"v0.9.0"
)
rcl_get_effective_install_dir(render_3D::fastgltf FASTGLTF_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::fastgltf ${fastgltf_option})
set(VulkanSceneGraph_option ${base_options})
_register_git_cmake_library(render_3D::VulkanSceneGraph
"https://github.com/vsg-dev/VulkanSceneGraph.git"
"v1.1.15"
)
rcl_get_effective_install_dir(render_3D::VulkanSceneGraph VULKAN_SCENE_GRAPH_ROOT)
rcl_cmake_library_set_cmake_options(render_3D::VulkanSceneGraph ${VulkanSceneGraph_option})
endblock()
+26
View File
@@ -0,0 +1,26 @@
include_guard(GLOBAL)
block()
set(render_3D_dependency_options
-DBUILD_TESTING=OFF
-DBUILD_SHARED_LIBS=OFF
"-DCMAKE_DEBUG_POSTFIX=d"
"-DCMAKE_RELWITHDEBINFO_POSTFIX="
"-DCMAKE_MINSIZEREL_POSTFIX="
)
if (WIN32 AND NOT MINGW)
set(render_3D_pthreads_options ${render_3D_dependency_options})
list(APPEND render_3D_pthreads_options
"-DCMAKE_C_FLAGS=-D_WINDLL"
"-DCMAKE_CXX_FLAGS=-D_WINDLL"
)
_register_git_cmake_library(render_3D::PThreads4W
"https://github.com/jwinarske/pthreads4w.git"
"cmake"
)
rcl_cmake_library_set_cmake_options(render_3D::PThreads4W ${render_3D_pthreads_options})
endif ()
endblock()
include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/datoviz.cmake")
+1 -2
View File
@@ -1,3 +1,2 @@
rcl_init(render_3D)
include(${CMAKE_CURRENT_LIST_DIR}/computer_graphics.cmake)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/module")
include("${CMAKE_CURRENT_LIST_DIR}/dependencies.cmake")
@@ -1,26 +0,0 @@
include_guard(GLOBAL)
rcl_get_effective_install_dir(render_3D::PThreads4W PTHREADS4W_ROOT)
message("render_3D::PThreads4W PTHREADS4W_ROOT=${PTHREADS4W_ROOT}")
set(PThreads4W_INCLUDE_DIR "${PTHREADS4W_ROOT}/include")
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(PThreads4W_LIBRARY "${PTHREADS4W_ROOT}/lib/pthreadVC3d.lib")
else ()
set(PThreads4W_LIBRARY "${PTHREADS4W_ROOT}/lib/pthreadVC3.lib")
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(PThreads4W
REQUIRED_VARS
PThreads4W_INCLUDE_DIR
PThreads4W_LIBRARY
)
if (PThreads4W_FOUND AND NOT TARGET PThreads4W::PThreads4W)
add_library(PThreads4W::PThreads4W UNKNOWN IMPORTED)
set_target_properties(PThreads4W::PThreads4W PROPERTIES
IMPORTED_LOCATION "${PThreads4W_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${PThreads4W_INCLUDE_DIR}"
)
endif ()
mark_as_advanced(
PThreads4W_INCLUDE_DIR
PThreads4W_LIBRARY
)
@@ -0,0 +1,31 @@
if (NOT PTHREADS4W_ROOT)
message(FATAL_ERROR "PTHREADS4W_ROOT is required by PThreads4WConfig.cmake")
endif ()
set(_pthreads4w_prefix "${PTHREADS4W_ROOT}")
if (EXISTS "${PTHREADS4W_ROOT}/x86_64/${CMAKE_BUILD_TYPE}/include/pthread.h")
set(_pthreads4w_prefix "${PTHREADS4W_ROOT}/x86_64/${CMAKE_BUILD_TYPE}")
endif ()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(_pthreads4w_library "${_pthreads4w_prefix}/lib/pthreadVC3d.lib")
else ()
set(_pthreads4w_library "${_pthreads4w_prefix}/lib/pthreadVC3.lib")
endif ()
if (NOT EXISTS "${_pthreads4w_prefix}/include/pthread.h" OR NOT EXISTS "${_pthreads4w_library}")
message(FATAL_ERROR "PThreads4W installation is incomplete at ${PTHREADS4W_ROOT}")
endif ()
if (NOT TARGET PThreads4W::PThreads4W)
add_library(PThreads4W::PThreads4W UNKNOWN IMPORTED)
set_target_properties(PThreads4W::PThreads4W PROPERTIES
IMPORTED_LOCATION "${_pthreads4w_library}"
INTERFACE_INCLUDE_DIRECTORIES "${_pthreads4w_prefix}/include"
)
endif ()
set(PThreads4W_FOUND TRUE)
set(PThreads4W_RUNTIME_DIR "${_pthreads4w_prefix}/bin")
unset(_pthreads4w_library)
unset(_pthreads4w_prefix)
+300
View File
@@ -0,0 +1,300 @@
#include "Render_3D.h"
#include "detail/Datoviz_Backend.h"
#include "detail/Ordered_Backend_Shutdown.h"
#include "detail/Render_3D_Core.h"
#include "detail/Render_Domain.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <limits>
#include <stdexcept>
namespace renderive {
namespace {
bool finite(Vec_3 value) {
return std::isfinite(value.x) && std::isfinite(value.y) && std::isfinite(value.z);
}
void validate_camera(const Camera_3D& camera) {
if (!finite(camera.eye) || !finite(camera.target) || !finite(camera.up) ||
!std::isfinite(camera.vertical_field_of_view_radians) ||
!std::isfinite(camera.near_clip) || !std::isfinite(camera.far_clip) ||
!std::isfinite(camera.orthographic_height) || camera.near_clip <= 0.0F ||
camera.far_clip <= camera.near_clip || camera.vertical_field_of_view_radians <= 0.0F ||
camera.vertical_field_of_view_radians >= 3.141592654F ||
camera.orthographic_height <= 0.0F)
throw std::invalid_argument("invalid Render_3D camera");
}
void validate_clear_color(const Clear_Color& color) {
const auto valid = [](float value) { return std::isfinite(value) && value >= 0.0F && value <= 1.0F; };
if (!valid(color.red) || !valid(color.green) || !valid(color.blue) || !valid(color.alpha))
throw std::invalid_argument("Render_3D clear color components must be in [0, 1]");
}
void validate_scale(float device_x, float device_y, float render, float user) {
const auto valid = [](float value) { return std::isfinite(value) && value > 0.0F; };
if (!valid(device_x) || !valid(device_y) || !valid(render) || !valid(user))
throw std::invalid_argument("Render_3D scale values must be finite and positive");
}
void validate_sphere(const Sphere_3D& sphere) {
if (!finite(sphere.position) || !std::isfinite(sphere.radius) || sphere.radius <= 0.0F)
throw std::invalid_argument("invalid Render_3D sphere");
for (const float value : sphere.transform.values) {
if (!std::isfinite(value))
throw std::invalid_argument("Render_3D transform contains a non-finite value");
}
}
} // namespace
struct Render_3D::Configuration {
Frame_3D_Control_Mode frame_control_mode{Frame_3D_Control_Mode::Low_Latency};
double maximum_frames_per_second{60.0};
std::size_t frame_slot_count{2};
std::uint32_t gpu_index{};
bool validation_enabled{};
detail::Render_3D_State initial_state;
};
struct Render_3D::Impl {
explicit Impl(const Configuration& configuration)
: states(configuration.initial_state),
scheduler(configuration.frame_control_mode, configuration.maximum_frames_per_second) {
try {
render_domain.invoke([&] {
backend = std::make_unique<detail::Datoviz_Backend>(
configuration.gpu_index, configuration.validation_enabled,
configuration.initial_state);
targets = backend->create_target_pool(configuration.frame_slot_count);
});
} catch (...) {
render_domain.invoke([&] {
targets.reset();
backend.reset();
});
throw;
}
}
~Impl() {
accepting_frames.store(false, std::memory_order_release);
render_domain.invoke([&] {
if (backend == nullptr)
return;
if (targets != nullptr)
targets->wait_all();
detail::ordered_backend_shutdown(
[&] { backend->destroy_runtime(); },
[&] { targets.reset(); },
[&] { backend->destroy_scene(); },
[&] { backend->destroy_gpu_context(); });
backend.reset();
});
}
[[nodiscard]] bool prepare_frame() {
return accepting_frames.load(std::memory_order_acquire) && scheduler.prepare(states);
}
[[nodiscard]] bool render_prepared_frame() {
if (!accepting_frames.load(std::memory_order_acquire))
return false;
return render_domain.invoke([&] {
return scheduler.render([&](const detail::Render_3D_Frame_Data& frame) {
return backend->render(frame, *targets);
});
});
}
void wait_for_render() {
if (!accepting_frames.load(std::memory_order_acquire))
return;
render_domain.invoke([&] { targets->wait_all(); });
}
[[nodiscard]] Object_3D_Id allocate_object_id() {
auto value = next_object_id.load(std::memory_order_relaxed);
for (;;) {
if (value == 0 || value == std::numeric_limits<std::uint64_t>::max())
throw std::overflow_error("Render_3D object identifiers are exhausted");
if (next_object_id.compare_exchange_weak(
value, value + 1, std::memory_order_relaxed))
return {value};
}
}
detail::Render_Domain render_domain;
std::unique_ptr<detail::Datoviz_Backend> backend;
std::unique_ptr<detail::Frame_Target_Pool> targets;
detail::Logical_State_Buffer states;
detail::Frame_Scheduler scheduler;
std::atomic<std::uint64_t> next_object_id{1};
std::atomic<bool> accepting_frames{true};
};
Render_3D::Builder& Render_3D::Builder::frame_control(
Frame_3D_Control_Mode mode, double maximum_frames_per_second) {
frame_control_mode_ = mode;
maximum_frames_per_second_ = maximum_frames_per_second;
return *this;
}
Render_3D::Builder& Render_3D::Builder::viewport(Extent_2D extent) {
viewport_ = extent;
return *this;
}
Render_3D::Builder& Render_3D::Builder::frame_slots(std::size_t count) {
frame_slot_count_ = count;
return *this;
}
Render_3D::Builder& Render_3D::Builder::gpu(
std::uint32_t index, bool validation_enabled) {
gpu_index_ = index;
validation_enabled_ = validation_enabled;
return *this;
}
Render_3D::Builder& Render_3D::Builder::camera(Camera_3D value) {
camera_ = value;
return *this;
}
Render_3D::Builder& Render_3D::Builder::clear_color(Clear_Color value) {
clear_color_ = value;
return *this;
}
Render_3D::Builder& Render_3D::Builder::scale(
float device_x, float device_y, float render, float user) {
device_scale_x_ = device_x;
device_scale_y_ = device_y;
render_scale_ = render;
user_scale_ = user;
return *this;
}
std::unique_ptr<Render_3D> Render_3D::Builder::build() const {
if (!std::isfinite(maximum_frames_per_second_) || maximum_frames_per_second_ <= 0.0)
throw std::invalid_argument("Render_3D frame frequency must be finite and positive");
if (frame_slot_count_ < 2)
throw std::invalid_argument("Render_3D requires at least two frame slots");
validate_camera(camera_);
validate_clear_color(clear_color_);
validate_scale(device_scale_x_, device_scale_y_, render_scale_, user_scale_);
Configuration configuration;
configuration.frame_control_mode = frame_control_mode_;
configuration.maximum_frames_per_second = maximum_frames_per_second_;
configuration.frame_slot_count = frame_slot_count_;
configuration.gpu_index = gpu_index_;
configuration.validation_enabled = validation_enabled_;
configuration.initial_state.viewport = viewport_;
configuration.initial_state.camera = camera_;
configuration.initial_state.clear_color = clear_color_;
configuration.initial_state.device_scale_x = device_scale_x_;
configuration.initial_state.device_scale_y = device_scale_y_;
configuration.initial_state.render_scale = render_scale_;
configuration.initial_state.user_scale = user_scale_;
return std::unique_ptr<Render_3D>(new Render_3D(configuration));
}
Render_3D::Render_3D(const Configuration& configuration)
: impl_(std::make_unique<Impl>(configuration)) {}
Render_3D::~Render_3D() = default;
void Render_3D::resize(Extent_2D extent) {
impl_->states.update([extent](detail::Render_3D_State& state) { state.viewport = extent; });
}
void Render_3D::set_camera(Camera_3D camera) {
validate_camera(camera);
impl_->states.update([camera](detail::Render_3D_State& state) { state.camera = camera; });
}
void Render_3D::set_clear_color(Clear_Color color) {
validate_clear_color(color);
impl_->states.update([color](detail::Render_3D_State& state) { state.clear_color = color; });
}
void Render_3D::set_scale(float device_x, float device_y, float render, float user) {
validate_scale(device_x, device_y, render, user);
impl_->states.update([=](detail::Render_3D_State& state) {
state.device_scale_x = device_x;
state.device_scale_y = device_y;
state.render_scale = render;
state.user_scale = user;
});
}
Object_3D_Id Render_3D::add_sphere(Sphere_3D sphere) {
validate_sphere(sphere);
const Object_3D_Id id = impl_->allocate_object_id();
impl_->states.update([id, sphere](detail::Render_3D_State& state) {
state.objects.push_back({id, sphere, 1});
});
return id;
}
bool Render_3D::replace_sphere(Object_3D_Id id, Sphere_3D sphere) {
if (!id)
return false;
validate_sphere(sphere);
bool replaced{};
impl_->states.update([&](detail::Render_3D_State& state) {
const auto iterator = std::find_if(
state.objects.begin(), state.objects.end(),
[id](const detail::Object_3D_State& object) { return object.id == id; });
if (iterator == state.objects.end())
return;
iterator->sphere = sphere;
++iterator->revision;
replaced = true;
});
return replaced;
}
bool Render_3D::erase_object(Object_3D_Id id) {
if (!id)
return false;
bool erased{};
impl_->states.update([&](detail::Render_3D_State& state) {
const auto iterator = std::find_if(
state.objects.begin(), state.objects.end(),
[id](const detail::Object_3D_State& object) { return object.id == id; });
if (iterator == state.objects.end())
return;
state.objects.erase(iterator);
erased = true;
});
return erased;
}
bool Render_3D::prepare_frame() {
return impl_->prepare_frame();
}
bool Render_3D::refresh_manual_frame() {
return impl_->accepting_frames.load(std::memory_order_acquire) && impl_->scheduler.refresh();
}
bool Render_3D::discard_pending_frame() {
return impl_->accepting_frames.load(std::memory_order_acquire) && impl_->scheduler.discard_pending();
}
bool Render_3D::render_prepared_frame() {
return impl_->render_prepared_frame();
}
void Render_3D::wait_for_render() {
impl_->wait_for_render();
}
} // namespace renderive
+147
View File
@@ -0,0 +1,147 @@
#pragma once
#include <array>
#include <compare>
#include <cstddef>
#include <cstdint>
#include <memory>
namespace renderive {
enum class Frame_3D_Control_Mode : std::uint8_t {
Manual,
Low_Latency,
Playback
};
struct Extent_2D {
std::uint32_t width{};
std::uint32_t height{};
[[nodiscard]] constexpr bool empty() const noexcept { return width == 0 || height == 0; }
bool operator==(const Extent_2D&) const = default;
};
struct Vec_3 {
float x{};
float y{};
float z{};
bool operator==(const Vec_3&) const = default;
};
struct Rgba_8 {
std::uint8_t red{255};
std::uint8_t green{255};
std::uint8_t blue{255};
std::uint8_t alpha{255};
bool operator==(const Rgba_8&) const = default;
};
struct Clear_Color {
float red{};
float green{};
float blue{};
float alpha{1.0F};
bool operator==(const Clear_Color&) const = default;
};
struct Matrix_4x4 {
std::array<float, 16> values{
1.0F, 0.0F, 0.0F, 0.0F,
0.0F, 1.0F, 0.0F, 0.0F,
0.0F, 0.0F, 1.0F, 0.0F,
0.0F, 0.0F, 0.0F, 1.0F};
bool operator==(const Matrix_4x4&) const = default;
};
enum class Camera_Projection : std::uint8_t {
Perspective,
Orthographic
};
struct Camera_3D {
Vec_3 eye{0.0F, 0.0F, 4.0F};
Vec_3 target{};
Vec_3 up{0.0F, 1.0F, 0.0F};
Camera_Projection projection{Camera_Projection::Perspective};
float vertical_field_of_view_radians{0.785398163F};
float near_clip{0.01F};
float far_clip{1000.0F};
float orthographic_height{2.0F};
bool operator==(const Camera_3D&) const = default;
};
struct Sphere_3D {
Vec_3 position{};
float radius{1.0F};
Rgba_8 color{};
Matrix_4x4 transform{};
bool visible{true};
bool operator==(const Sphere_3D&) const = default;
};
struct Object_3D_Id {
std::uint64_t value{};
[[nodiscard]] explicit constexpr operator bool() const noexcept { return value != 0; }
auto operator<=>(const Object_3D_Id&) const = default;
};
class Render_3D final {
public:
class Builder final {
public:
Builder& frame_control(Frame_3D_Control_Mode mode, double maximum_frames_per_second = 60.0);
Builder& viewport(Extent_2D extent);
Builder& frame_slots(std::size_t count);
Builder& gpu(std::uint32_t index, bool validation_enabled = false);
Builder& camera(Camera_3D value);
Builder& clear_color(Clear_Color value);
Builder& scale(float device_x, float device_y, float render, float user);
[[nodiscard]] std::unique_ptr<Render_3D> build() const;
private:
Frame_3D_Control_Mode frame_control_mode_{Frame_3D_Control_Mode::Low_Latency};
double maximum_frames_per_second_{60.0};
Extent_2D viewport_{800, 600};
std::size_t frame_slot_count_{2};
std::uint32_t gpu_index_{};
bool validation_enabled_{};
Camera_3D camera_;
Clear_Color clear_color_;
float device_scale_x_{1.0F};
float device_scale_y_{1.0F};
float render_scale_{1.0F};
float user_scale_{1.0F};
};
~Render_3D();
Render_3D(const Render_3D&) = delete;
Render_3D& operator=(const Render_3D&) = delete;
Render_3D(Render_3D&&) = delete;
Render_3D& operator=(Render_3D&&) = delete;
void resize(Extent_2D extent);
void set_camera(Camera_3D camera);
void set_clear_color(Clear_Color color);
void set_scale(float device_x, float device_y, float render, float user);
[[nodiscard]] Object_3D_Id add_sphere(Sphere_3D sphere);
[[nodiscard]] bool replace_sphere(Object_3D_Id id, Sphere_3D sphere);
[[nodiscard]] bool erase_object(Object_3D_Id id);
[[nodiscard]] bool prepare_frame();
[[nodiscard]] bool refresh_manual_frame();
[[nodiscard]] bool discard_pending_frame();
[[nodiscard]] bool render_prepared_frame();
void wait_for_render();
private:
struct Configuration;
struct Impl;
explicit Render_3D(const Configuration& configuration);
std::unique_ptr<Impl> impl_;
};
} // namespace renderive
@@ -0,0 +1,155 @@
#include "Datoviz_Backend.h"
#include "Datoviz_Frame_Executor.h"
#include <datoviz/drp2/stream.h>
#include <cstdio>
#include <stdexcept>
namespace renderive::detail {
Datoviz_Backend::Datoviz_Backend(
std::uint32_t gpu_index, bool validation_enabled, const Render_3D_State& initial_state) {
try {
DvzGpuCtxConfig configuration = dvz_gpu_ctx_config();
dvz_gpu_ctx_config_validation(&configuration, validation_enabled);
dvz_gpu_ctx_config_gpu(&configuration, gpu_index);
dvz_gpu_ctx_config_enable_canvas_extensions(&configuration, false);
gpu_context_ = dvz_gpu_ctx(&configuration);
if (gpu_context_ == nullptr)
throw std::runtime_error("failed to create Datoviz GPU context");
if (dvz_gpu_ctx_device(gpu_context_) == nullptr ||
dvz_gpu_ctx_alloc(gpu_context_) == nullptr ||
dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN) == nullptr)
throw std::runtime_error("Datoviz GPU context does not expose required resources");
create_runtime();
create_scene(initial_state.viewport);
} catch (...) {
destroy_runtime();
destroy_scene();
destroy_gpu_context();
throw;
}
}
Datoviz_Backend::~Datoviz_Backend() {
destroy_runtime();
destroy_scene();
destroy_gpu_context();
}
std::unique_ptr<Frame_Target_Pool> Datoviz_Backend::create_target_pool(std::size_t slot_count) {
return std::make_unique<Frame_Target_Pool>(gpu_context_, slot_count);
}
bool Datoviz_Backend::render(
const Render_3D_Frame_Data& frame, Frame_Target_Pool& targets) {
if (frame.state.viewport.empty())
return false;
if (runtime_ == nullptr || scene_binding_ == nullptr) {
if (!rebuild(frame, targets))
return false;
}
if (!scene_binding_->apply(frame.state, frame.revision)) {
std::fputs("Render_3D: failed to apply logical state to the Datoviz scene\n", stderr);
(void)rebuild(frame, targets);
return false;
}
try {
Frame_Target& target = targets.acquire(frame.frame_sequence, frame.state.viewport);
if (Datoviz_Frame_Executor::execute(*this, targets, target, frame))
return true;
target.abort();
} catch (const std::exception& error) {
std::fprintf(stderr, "Render_3D: backend transaction threw: %s\n", error.what());
} catch (...) {
std::fputs("Render_3D: backend transaction failed with an unknown error\n", stderr);
}
(void)rebuild(frame, targets);
return false;
}
void Datoviz_Backend::destroy_runtime() noexcept {
if (runtime_ != nullptr) {
dvz_drp2_runtime_destroy(runtime_);
runtime_ = nullptr;
}
}
void Datoviz_Backend::destroy_scene() noexcept {
scene_binding_.reset();
if (scene_ != nullptr) {
dvz_scene_destroy(scene_);
scene_ = nullptr;
}
}
void Datoviz_Backend::destroy_gpu_context() noexcept {
if (gpu_context_ != nullptr) {
dvz_gpu_ctx_destroy(gpu_context_);
gpu_context_ = nullptr;
}
}
void Datoviz_Backend::create_runtime() {
DvzDrp2RuntimeConfig configuration = dvz_drp2_runtime_vklite_config(
dvz_gpu_ctx_device(gpu_context_), dvz_gpu_ctx_alloc(gpu_context_));
runtime_ = dvz_drp2_runtime_vklite(&configuration);
if (runtime_ == nullptr)
throw std::runtime_error("failed to create Datoviz DRP2 runtime");
}
void Datoviz_Backend::create_scene(Extent_2D extent) {
scene_ = dvz_scene();
if (scene_ == nullptr)
throw std::runtime_error("failed to create Datoviz scene");
const DvzCapabilitySnapshot capabilities = dvz_capability_snapshot();
if (dvz_scene_set_capabilities(scene_, &capabilities) != DVZ_OK)
throw std::runtime_error("failed to configure Datoviz scene capabilities");
scene_binding_ = std::make_unique<Datoviz_Scene_Binding>(scene_, extent);
}
bool Datoviz_Backend::rebuild(
const Render_3D_Frame_Data& frame, Frame_Target_Pool& targets) noexcept {
targets.wait_all();
destroy_runtime();
targets.runtime_released_targets();
destroy_scene();
try {
create_runtime();
create_scene(frame.state.viewport);
return scene_binding_->apply(frame.state, frame.revision);
} catch (...) {
destroy_runtime();
destroy_scene();
return false;
}
}
DvzSceneFrameArtifact* Datoviz_Backend::emit(
const DvzFramePlanEmitConfig& configuration) {
const DvzCapabilitySnapshot capabilities = dvz_capability_snapshot();
return scene_binding_->emit(capabilities, configuration);
}
bool Datoviz_Backend::attach(const DvzStreamFrame& frame) {
return dvz_drp2_runtime_attach_frame_target(runtime_, Datoviz_Color_Target_Id, &frame);
}
bool Datoviz_Backend::execute(const DvzDrp2CommandStream& stream) {
const DvzDrp2ValidationResult result = dvz_drp2_runtime_execute(runtime_, &stream);
if (!result.ok) {
const DvzDrp2Command* command = dvz_drp2_stream_get(&stream, result.command_index);
std::fprintf(
stderr, "Render_3D: DRP2 execution failed (code=%u command=%u type=%u count=%u)\n",
static_cast<unsigned>(result.code), result.command_index,
command == nullptr ? 0U : static_cast<unsigned>(dvz_drp2_command_type(command)),
dvz_drp2_stream_count(&stream));
}
return result.ok;
}
} // namespace renderive::detail
@@ -0,0 +1,53 @@
#pragma once
#include "Datoviz_Scene_Binding.h"
#include "Frame_Target_Pool.h"
#include "Render_3D_Core.h"
#include <datoviz/drp2/runtime.h>
#include <datoviz/vk/gpu_ctx.h>
#include <cstddef>
#include <cstdint>
#include <memory>
namespace renderive::detail {
class Datoviz_Frame_Executor;
struct Datoviz_Transaction_Api;
inline constexpr std::uint64_t Datoviz_Color_Target_Id = 4242;
class Datoviz_Backend final {
public:
Datoviz_Backend(
std::uint32_t gpu_index, bool validation_enabled, const Render_3D_State& initial_state);
~Datoviz_Backend();
Datoviz_Backend(const Datoviz_Backend&) = delete;
Datoviz_Backend& operator=(const Datoviz_Backend&) = delete;
[[nodiscard]] std::unique_ptr<Frame_Target_Pool> create_target_pool(std::size_t slot_count);
[[nodiscard]] bool render(const Render_3D_Frame_Data& frame, Frame_Target_Pool& targets);
void destroy_runtime() noexcept;
void destroy_scene() noexcept;
void destroy_gpu_context() noexcept;
private:
friend class Datoviz_Frame_Executor;
friend struct Datoviz_Transaction_Api;
void create_runtime();
void create_scene(Extent_2D extent);
[[nodiscard]] bool rebuild(const Render_3D_Frame_Data& frame, Frame_Target_Pool& targets) noexcept;
[[nodiscard]] DvzSceneFrameArtifact* emit(const DvzFramePlanEmitConfig& configuration);
[[nodiscard]] bool attach(const DvzStreamFrame& frame);
[[nodiscard]] bool execute(const DvzDrp2CommandStream& stream);
DvzGpuCtx* gpu_context_{};
DvzDrp2Runtime* runtime_{};
DvzScene* scene_{};
std::unique_ptr<Datoviz_Scene_Binding> scene_binding_;
};
} // namespace renderive::detail
@@ -0,0 +1,92 @@
#include "Datoviz_Frame_Executor.h"
#include "Datoviz_Backend.h"
#include "Frame_Target_Pool.h"
#include "Ordered_Artifact_Transaction.h"
#include <datoviz/scene/frame_plan.h>
#include <cstdio>
namespace renderive::detail {
namespace {
DvzFramePlanEmitConfig emit_configuration(const Render_3D_State& state) {
DvzFramePlanEmitConfig configuration = dvz_frame_plan_emit_config();
configuration.shader_format = DVZ_SCENE_SHADER_FORMAT_GLSL;
configuration.external_color_target = true;
configuration.color_target_id = Datoviz_Color_Target_Id;
configuration.color_target_format = DVZ_FORMAT_R8G8B8A8_UNORM;
configuration.target_width = state.viewport.width;
configuration.target_height = state.viewport.height;
configuration.device_scale_x = state.device_scale_x;
configuration.device_scale_y = state.device_scale_y;
configuration.render_scale = state.render_scale;
configuration.user_scale = state.user_scale;
configuration.clear_color[0] = state.clear_color.red;
configuration.clear_color[1] = state.clear_color.green;
configuration.clear_color[2] = state.clear_color.blue;
configuration.clear_color[3] = state.clear_color.alpha;
return configuration;
}
} // namespace
struct Datoviz_Transaction_Api {
Datoviz_Backend& backend;
Frame_Target_Pool& targets;
Frame_Target& target;
DvzFramePlanEmitConfig configuration;
[[nodiscard]] DvzSceneFrameArtifact* emit() { return backend.emit(configuration); }
[[nodiscard]] static const DvzDrp2CommandStream* stream(DvzSceneFrameArtifact& artifact) {
return dvz_scene_frame_artifact_stream(&artifact);
}
[[nodiscard]] bool attach() {
const DvzStreamFrame frame = target.stream_frame();
const bool attached = backend.attach(frame);
if (attached)
targets.external_target_replaced();
return attached;
}
[[nodiscard]] bool execute(const DvzDrp2CommandStream& stream) {
return backend.execute(stream);
}
static void destroy(DvzSceneFrameArtifact& artifact) {
dvz_scene_frame_artifact_destroy(&artifact);
}
};
bool Datoviz_Frame_Executor::execute(
Datoviz_Backend& backend, Frame_Target_Pool& targets, Frame_Target& target,
const Render_3D_Frame_Data& frame) {
if (!target.begin()) {
std::fputs("Render_3D: failed to begin the owned command buffer\n", stderr);
return false;
}
Datoviz_Transaction_Api api{
backend, targets, target, emit_configuration(frame.state)};
const auto transaction = execute_ordered_artifact_transaction(api);
if (!static_cast<bool>(transaction)) {
std::fprintf(
stderr,
"Render_3D: Datoviz transaction failed (emit=%d stream=%d attach=%d execute=%d execute_ok=%d)\n",
transaction.artifact_emitted, transaction.stream_available,
transaction.target_attached, transaction.stream_executed,
transaction.execution_succeeded);
target.abort();
return false;
}
if (!target.submit_and_wait()) {
std::fputs("Render_3D: failed to submit or complete the owned command buffer\n", stderr);
return false;
}
return true;
}
} // namespace renderive::detail
@@ -0,0 +1,18 @@
#pragma once
#include "Render_3D_Core.h"
namespace renderive::detail {
class Datoviz_Backend;
class Frame_Target;
class Frame_Target_Pool;
class Datoviz_Frame_Executor final {
public:
[[nodiscard]] static bool execute(
Datoviz_Backend& backend, Frame_Target_Pool& targets, Frame_Target& target,
const Render_3D_Frame_Data& frame);
};
} // namespace renderive::detail
@@ -0,0 +1,114 @@
#include "Datoviz_Scene_Binding.h"
#include <algorithm>
#include <array>
#include <stdexcept>
#include <unordered_set>
namespace renderive::detail {
Datoviz_Scene_Binding::Datoviz_Scene_Binding(DvzScene* scene, Extent_2D initial_extent)
: scene_(scene) {
if (scene_ == nullptr)
throw std::invalid_argument("Datoviz scene binding requires a scene");
const auto width = std::max(1U, initial_extent.width);
const auto height = std::max(1U, initial_extent.height);
figure_ = dvz_figure(scene_, width, height, 0);
if (figure_ == nullptr)
throw std::runtime_error("failed to create Datoviz figure");
panel_ = dvz_panel_full(figure_);
if (panel_ == nullptr)
throw std::runtime_error("failed to create Datoviz panel");
}
bool Datoviz_Scene_Binding::apply(const Render_3D_State& state, std::uint64_t revision) {
if (revision == applied_revision_)
return true;
if (!state.viewport.empty() &&
dvz_figure_resize(figure_, state.viewport.width, state.viewport.height) != DVZ_OK)
return false;
if (!apply_camera(state.camera))
return false;
std::unordered_set<std::uint64_t> live_ids;
live_ids.reserve(state.objects.size());
for (const auto& object : state.objects) {
live_ids.insert(object.id.value);
auto [iterator, inserted] = visuals_.try_emplace(object.id.value);
auto& binding = iterator->second;
if (inserted) {
binding.visual = dvz_sphere(scene_, 0);
if (binding.visual == nullptr ||
dvz_panel_add_visual(panel_, binding.visual, nullptr) != DVZ_OK ||
dvz_visual_set_depth_test(binding.visual, true) != DVZ_OK)
return false;
}
if (binding.applied_revision != object.revision && !apply_object(object, binding))
return false;
}
for (auto iterator = visuals_.begin(); iterator != visuals_.end();) {
if (!live_ids.contains(iterator->first)) {
if (dvz_visual_set_visible(iterator->second.visual, false) != DVZ_OK)
return false;
iterator = visuals_.erase(iterator);
} else
++iterator;
}
applied_revision_ = revision;
return true;
}
DvzSceneFrameArtifact* Datoviz_Scene_Binding::emit(
const DvzCapabilitySnapshot& capabilities, const DvzFramePlanEmitConfig& configuration) {
DvzDiagnosticReport report{};
dvz_diagnostic_report_init(&report);
return dvz_figure_emit_frame(figure_, &capabilities, &report, &configuration);
}
bool Datoviz_Scene_Binding::apply_camera(const Camera_3D& camera) {
DvzPanelView3DDesc description = dvz_panel_view3d_desc();
description.view.eye[0] = camera.eye.x;
description.view.eye[1] = camera.eye.y;
description.view.eye[2] = camera.eye.z;
description.view.target[0] = camera.target.x;
description.view.target[1] = camera.target.y;
description.view.target[2] = camera.target.z;
description.view.up[0] = camera.up.x;
description.view.up[1] = camera.up.y;
description.view.up[2] = camera.up.z;
description.projection.type = camera.projection == Camera_Projection::Perspective
? DVZ_CAMERA_PERSPECTIVE
: DVZ_CAMERA_ORTHOGRAPHIC;
description.projection.fov_y = camera.vertical_field_of_view_radians;
description.projection.near_clip = camera.near_clip;
description.projection.far_clip = camera.far_clip;
description.projection.ortho_height = camera.orthographic_height;
return dvz_panel_set_view3d_desc(panel_, &description) == DVZ_OK;
}
bool Datoviz_Scene_Binding::apply_object(
const Object_3D_State& object, Visual_Binding& binding) {
const std::array<float, 3> position{object.sphere.position.x, object.sphere.position.y, object.sphere.position.z};
const std::array<std::uint8_t, 4> color{
object.sphere.color.red, object.sphere.color.green,
object.sphere.color.blue, object.sphere.color.alpha};
const float radius = object.sphere.radius;
mat4 transform{};
for (std::size_t row = 0; row < 4; ++row) {
for (std::size_t column = 0; column < 4; ++column)
transform[row][column] = object.sphere.transform.values[row * 4 + column];
}
if (dvz_visual_set_data(binding.visual, "position", position.data(), 1) != DVZ_OK ||
dvz_visual_set_data(binding.visual, "color", color.data(), 1) != DVZ_OK ||
dvz_visual_set_data(binding.visual, "radius", &radius, 1) != DVZ_OK ||
dvz_visual_set_transform(binding.visual, transform) != DVZ_OK ||
dvz_visual_set_visible(binding.visual, object.sphere.visible) != DVZ_OK)
return false;
binding.applied_revision = object.revision;
return true;
}
} // namespace renderive::detail
@@ -0,0 +1,40 @@
#pragma once
#include "Render_3D_Core.h"
#include <datoviz/scene.h>
#include <cstdint>
#include <unordered_map>
namespace renderive::detail {
class Datoviz_Scene_Binding final {
public:
Datoviz_Scene_Binding(DvzScene* scene, Extent_2D initial_extent);
Datoviz_Scene_Binding(const Datoviz_Scene_Binding&) = delete;
Datoviz_Scene_Binding& operator=(const Datoviz_Scene_Binding&) = delete;
[[nodiscard]] bool apply(const Render_3D_State& state, std::uint64_t revision);
[[nodiscard]] DvzSceneFrameArtifact* emit(
const DvzCapabilitySnapshot& capabilities, const DvzFramePlanEmitConfig& configuration);
private:
struct Visual_Binding {
DvzVisual* visual{};
std::uint64_t applied_revision{};
};
[[nodiscard]] bool apply_camera(const Camera_3D& camera);
[[nodiscard]] static bool apply_object(
const Object_3D_State& object, Visual_Binding& binding);
DvzScene* scene_{};
DvzFigure* figure_{};
DvzPanel* panel_{};
std::unordered_map<std::uint64_t, Visual_Binding> visuals_;
std::uint64_t applied_revision_{};
};
} // namespace renderive::detail
@@ -0,0 +1,237 @@
#include "Frame_Target_Pool.h"
#include <stdexcept>
#include <utility>
namespace renderive::detail {
namespace {
template <class Resource, class Allocate>
Resource* allocate_wrapper(Allocate allocate, const char* name) {
Resource* resource = allocate();
if (resource == nullptr)
throw std::runtime_error(name);
return resource;
}
} // namespace
Frame_Target::Frame_Target(
DvzGpuCtx* gpu_context, Extent_2D extent, std::uint64_t generation)
: gpu_context_(gpu_context), extent_(extent), generation_(generation) {
if (gpu_context == nullptr || extent.empty())
throw std::invalid_argument("invalid Render_3D frame target configuration");
DvzDevice* device = dvz_gpu_ctx_device(gpu_context);
DvzVma* allocator = dvz_gpu_ctx_alloc(gpu_context);
DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context, DVZ_QUEUE_MAIN);
if (device == nullptr || allocator == nullptr || queue == nullptr)
throw std::invalid_argument("Render_3D frame target requires a complete GPU context");
try {
image_ = allocate_wrapper<DvzImages>(dvz_images_create_wrapper, "failed to allocate Datoviz image wrapper");
dvz_images(device, allocator, VK_IMAGE_TYPE_2D, 1, image_);
dvz_images_format(image_, VK_FORMAT_R8G8B8A8_UNORM);
dvz_images_size(image_, extent.width, extent.height, 1);
dvz_images_tiling(image_, VK_IMAGE_TILING_OPTIMAL);
dvz_images_usage(image_, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
dvz_images_alloc_flags(image_, DVZ_ALLOC_FLAGS_NONE);
if (dvz_images_create(image_) != 0)
throw std::runtime_error("failed to create Render_3D color image");
image_view_ = allocate_wrapper<DvzImageViews>(dvz_image_views_create_wrapper, "failed to allocate Datoviz image-view wrapper");
dvz_image_views(image_, image_view_);
dvz_image_views_type(image_view_, VK_IMAGE_VIEW_TYPE_2D);
dvz_image_views_aspect(image_view_, VK_IMAGE_ASPECT_COLOR_BIT);
dvz_image_views_mip(image_view_, 0, 1);
dvz_image_views_layers(image_view_, 0, 1);
if (dvz_image_views_create(image_view_) != 0)
throw std::runtime_error("failed to create Render_3D color image view");
commands_ = allocate_wrapper<DvzCommands>(dvz_commands_create_wrapper, "failed to allocate Datoviz command wrapper");
dvz_commands(device, queue, 1, commands_);
if (dvz_commands_handle(commands_) == VK_NULL_HANDLE)
throw std::runtime_error("failed to create Render_3D command buffer");
fence_ = allocate_wrapper<DvzFence>(dvz_fence_create_wrapper, "failed to allocate Datoviz fence wrapper");
dvz_fence(device, true, fence_);
if (dvz_fence_handle(fence_) == VK_NULL_HANDLE)
throw std::runtime_error("failed to create Render_3D fence");
submit_ = allocate_wrapper<DvzSubmit>(dvz_submit_create_wrapper, "failed to allocate Datoviz submit wrapper");
} catch (...) {
destroy();
throw;
}
}
Frame_Target::~Frame_Target() {
destroy();
}
bool Frame_Target::begin() {
wait();
dvz_cmd_reset(commands_);
if (dvz_cmd_begin_result(commands_) != 0)
return false;
recording_ = true;
if (completed_layout_ == VK_IMAGE_LAYOUT_UNDEFINED)
transition_to_color_attachment();
return true;
}
DvzStreamFrame Frame_Target::stream_frame() const {
DvzStreamFrame frame{};
frame.image = dvz_image_handle(image_, 0);
frame.command_buffer = dvz_commands_handle(commands_);
frame.image_view = dvz_image_views_handle(image_view_, 0);
frame.extent = {extent_.width, extent_.height};
frame.color_format = VK_FORMAT_R8G8B8A8_UNORM;
frame.image_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
frame.usage = DVZ_STREAM_FRAME_USAGE_RENDER_TARGET | DVZ_STREAM_FRAME_USAGE_COPY_SRC;
frame.command_buffer_recording = recording_;
frame.image_borrowed = true;
frame.image_view_borrowed = true;
frame.command_buffer_borrowed = true;
frame.handles_dirty = true;
frame.resource_generation = generation_;
frame.image_valid = frame.image != VK_NULL_HANDLE && frame.image_view != VK_NULL_HANDLE;
frame.memory_fd = -1;
frame.wait_semaphore_fd = -1;
return frame;
}
bool Frame_Target::submit_and_wait() {
if (!recording_ || dvz_cmd_end_result(commands_) != 0) {
abort();
return false;
}
recording_ = false;
dvz_fence_reset(fence_);
dvz_submit(submit_);
dvz_submit_command(submit_, dvz_commands_handle(commands_));
DvzQueue* queue = dvz_gpu_ctx_queue(gpu_context_, DVZ_QUEUE_MAIN);
if (dvz_submit_send(submit_, dvz_queue_handle(queue), dvz_fence_handle(fence_)) != VK_SUCCESS)
return false;
submitted_ = true;
if (!dvz_fence_wait(fence_)) {
dvz_device_wait(dvz_gpu_ctx_device(gpu_context_));
submitted_ = false;
return false;
}
submitted_ = false;
completed_layout_ = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
return true;
}
void Frame_Target::abort() noexcept {
if (submitted_)
wait();
if (recording_ && commands_ != nullptr)
dvz_cmd_reset(commands_);
recording_ = false;
}
void Frame_Target::wait() noexcept {
if (submitted_ && fence_ != nullptr) {
if (!dvz_fence_wait(fence_))
dvz_device_wait(dvz_gpu_ctx_device(gpu_context_));
submitted_ = false;
completed_layout_ = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
}
}
void Frame_Target::transition_to_color_attachment() {
DvzBarriers barriers{};
dvz_barriers(&barriers);
auto* barrier = dvz_barriers_image(&barriers, dvz_image_handle(image_, 0));
if (barrier == nullptr)
throw std::runtime_error("failed to allocate Render_3D image barrier");
dvz_barrier_image_stage(barrier, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT);
dvz_barrier_image_access(barrier, 0, VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
dvz_barrier_image_layout(barrier, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
dvz_barrier_image_aspect(barrier, VK_IMAGE_ASPECT_COLOR_BIT);
dvz_barrier_image_mip(barrier, 0, 1);
dvz_barrier_image_layers(barrier, 0, 1);
dvz_cmd_barriers(commands_, &barriers);
}
void Frame_Target::destroy() noexcept {
wait();
if (fence_ != nullptr) {
dvz_fence_destroy(fence_);
dvz_fence_free(fence_);
fence_ = nullptr;
}
if (submit_ != nullptr) {
dvz_submit_free(submit_);
submit_ = nullptr;
}
if (commands_ != nullptr) {
dvz_commands_destroy(commands_);
dvz_commands_free(commands_);
commands_ = nullptr;
}
if (image_view_ != nullptr) {
dvz_image_views_destroy(image_view_);
dvz_image_views_free(image_view_);
image_view_ = nullptr;
}
if (image_ != nullptr) {
dvz_images_destroy(image_);
dvz_images_free(image_);
image_ = nullptr;
}
}
Frame_Target_Pool::Frame_Target_Pool(DvzGpuCtx* gpu_context, std::size_t slot_count)
: gpu_context_(gpu_context), slot_count_(slot_count) {
if (gpu_context == nullptr || slot_count < 2)
throw std::invalid_argument("Render_3D requires at least two valid GPU frame slots");
}
Frame_Target_Pool::~Frame_Target_Pool() {
wait_all();
}
Frame_Target& Frame_Target_Pool::acquire(
std::uint64_t frame_sequence, Extent_2D extent) {
if (frame_sequence == 0 || extent.empty())
throw std::invalid_argument("cannot acquire an empty Render_3D target");
if (targets_.empty() || targets_.front()->extent_ != extent) {
auto replacement = make_generation(extent);
wait_all();
for (auto& target : targets_)
retired_targets_.push_back(std::move(target));
targets_ = std::move(replacement);
}
Frame_Target& target = *targets_[(frame_sequence - 1) % targets_.size()];
target.wait();
return target;
}
void Frame_Target_Pool::external_target_replaced() {
retired_targets_.clear();
}
void Frame_Target_Pool::runtime_released_targets() {
retired_targets_.clear();
}
void Frame_Target_Pool::wait_all() noexcept {
for (auto& target : targets_)
target->wait();
for (auto& target : retired_targets_)
target->wait();
}
std::vector<std::unique_ptr<Frame_Target>> Frame_Target_Pool::make_generation(Extent_2D extent) {
const std::uint64_t next_generation = generation_ + 1;
std::vector<std::unique_ptr<Frame_Target>> result;
result.reserve(slot_count_);
for (std::size_t index = 0; index < slot_count_; ++index)
result.push_back(std::make_unique<Frame_Target>(gpu_context_, extent, next_generation));
generation_ = next_generation;
return result;
}
} // namespace renderive::detail
@@ -0,0 +1,72 @@
#pragma once
#include "render_3D/Render_3D.h"
#include <datoviz/stream/frame_stream.h>
#include <datoviz/vk/gpu_ctx.h>
#include <datoviz/vklite.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
namespace renderive::detail {
class Frame_Target final {
public:
Frame_Target(DvzGpuCtx* gpu_context, Extent_2D extent, std::uint64_t generation);
~Frame_Target();
Frame_Target(const Frame_Target&) = delete;
Frame_Target& operator=(const Frame_Target&) = delete;
[[nodiscard]] bool begin();
[[nodiscard]] DvzStreamFrame stream_frame() const;
[[nodiscard]] bool submit_and_wait();
void abort() noexcept;
void wait() noexcept;
private:
friend class Frame_Target_Pool;
void transition_to_color_attachment();
void destroy() noexcept;
DvzGpuCtx* gpu_context_{};
Extent_2D extent_;
std::uint64_t generation_{};
DvzImages* image_{};
DvzImageViews* image_view_{};
DvzCommands* commands_{};
DvzFence* fence_{};
DvzSubmit* submit_{};
VkImageLayout completed_layout_{VK_IMAGE_LAYOUT_UNDEFINED};
bool recording_{};
bool submitted_{};
};
class Frame_Target_Pool final {
public:
Frame_Target_Pool(DvzGpuCtx* gpu_context, std::size_t slot_count);
~Frame_Target_Pool();
Frame_Target_Pool(const Frame_Target_Pool&) = delete;
Frame_Target_Pool& operator=(const Frame_Target_Pool&) = delete;
[[nodiscard]] Frame_Target& acquire(std::uint64_t frame_sequence, Extent_2D extent);
void external_target_replaced();
void runtime_released_targets();
void wait_all() noexcept;
private:
[[nodiscard]] std::vector<std::unique_ptr<Frame_Target>> make_generation(Extent_2D extent);
DvzGpuCtx* gpu_context_{};
std::size_t slot_count_{};
std::uint64_t generation_{};
std::vector<std::unique_ptr<Frame_Target>> targets_;
std::vector<std::unique_ptr<Frame_Target>> retired_targets_;
};
} // namespace renderive::detail
@@ -0,0 +1,45 @@
#pragma once
#include <utility>
namespace renderive::detail {
struct Artifact_Transaction_Result {
bool artifact_emitted{};
bool stream_available{};
bool target_attached{};
bool stream_executed{};
bool execution_succeeded{};
[[nodiscard]] explicit operator bool() const noexcept {
return artifact_emitted && stream_available && target_attached &&
stream_executed && execution_succeeded;
}
};
template <class Api>
[[nodiscard]] Artifact_Transaction_Result execute_ordered_artifact_transaction(Api& api) {
Artifact_Transaction_Result result;
auto* artifact = api.emit();
if (artifact == nullptr)
return result;
result.artifact_emitted = true;
struct Artifact_Guard {
Api& api;
decltype(artifact) artifact;
~Artifact_Guard() { api.destroy(*artifact); }
} guard{api, artifact};
auto* stream = api.stream(*artifact);
if (stream == nullptr)
return result;
result.stream_available = true;
result.target_attached = api.attach();
result.stream_executed = true;
result.execution_succeeded = api.execute(*stream);
return result;
}
} // namespace renderive::detail
@@ -0,0 +1,23 @@
#pragma once
#include <concepts>
#include <functional>
#include <utility>
namespace renderive::detail {
template <class Destroy_Runtime, class Destroy_Targets, class Destroy_Scene, class Destroy_Gpu_Context>
requires std::invocable<Destroy_Runtime> && std::invocable<Destroy_Targets> &&
std::invocable<Destroy_Scene> && std::invocable<Destroy_Gpu_Context>
void ordered_backend_shutdown(
Destroy_Runtime&& destroy_runtime,
Destroy_Targets&& destroy_targets,
Destroy_Scene&& destroy_scene,
Destroy_Gpu_Context&& destroy_gpu_context) {
std::invoke(std::forward<Destroy_Runtime>(destroy_runtime));
std::invoke(std::forward<Destroy_Targets>(destroy_targets));
std::invoke(std::forward<Destroy_Scene>(destroy_scene));
std::invoke(std::forward<Destroy_Gpu_Context>(destroy_gpu_context));
}
} // namespace renderive::detail
+139
View File
@@ -0,0 +1,139 @@
#pragma once
#include "render_3D/Render_3D.h"
#include <renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp>
#include <renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp>
#include <renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp>
#include <renderive/state/Double_State_Strategy.hpp>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <utility>
#include <variant>
#include <vector>
namespace renderive::detail {
struct Object_3D_State {
Object_3D_Id id;
Sphere_3D sphere;
std::uint64_t revision{1};
bool operator==(const Object_3D_State&) const = default;
};
struct Render_3D_State {
Extent_2D viewport{800, 600};
Camera_3D camera;
Clear_Color clear_color;
float device_scale_x{1.0F};
float device_scale_y{1.0F};
float render_scale{1.0F};
float user_scale{1.0F};
std::vector<Object_3D_State> objects;
bool operator==(const Render_3D_State&) const = default;
};
struct Render_3D_Frame_Data {
Render_3D_State state;
std::uint64_t revision{};
std::uint64_t frame_sequence{};
};
struct Logical_State_Base {};
class Logical_State_Buffer final
: public ::Double_State_Strategy<Logical_State_Base, Render_3D_State, std::mutex> {
using Base = ::Double_State_Strategy<Logical_State_Base, Render_3D_State, std::mutex>;
public:
struct Published_Frame {
Render_3D_State state;
std::uint64_t revision{};
};
explicit Logical_State_Buffer(const Render_3D_State& initial) : Base(initial) {}
[[nodiscard]] Published_Frame publish_frame() {
std::lock_guard publication_lock(publication_mutex_);
Base::publish();
return {Base::render_state_value(), Base::state_revision()};
}
private:
std::mutex publication_mutex_;
};
class Frame_Scheduler final {
using Manual = ::Manual_Refresh_Strategy<Render_3D_Frame_Data, std::mutex>;
using Low_Latency = ::Low_Latency_Strategy<Render_3D_Frame_Data, std::mutex>;
using Playback = ::Flow_Refresh_Strategy<Render_3D_Frame_Data, std::mutex>;
using Strategy = std::variant<std::unique_ptr<Manual>, std::unique_ptr<Low_Latency>, std::unique_ptr<Playback>>;
public:
Frame_Scheduler(Frame_3D_Control_Mode mode, double maximum_frames_per_second)
: strategy_(make_strategy(mode, maximum_frames_per_second)) {}
[[nodiscard]] bool prepare(Logical_State_Buffer& states) {
return std::visit(
[&](auto& strategy) {
auto lease = strategy->acquire_painter();
if (!lease)
return false;
auto published = states.publish_frame();
lease->state = std::move(published.state);
lease->revision = published.revision;
lease->frame_sequence = lease->statistics.sequence;
return true;
},
strategy_);
}
[[nodiscard]] bool refresh() {
auto* manual = std::get_if<std::unique_ptr<Manual>>(&strategy_);
return manual != nullptr && (*manual)->refresh();
}
[[nodiscard]] bool discard_pending() {
if (auto* manual = std::get_if<std::unique_ptr<Manual>>(&strategy_))
return (*manual)->discard_pending_frame();
if (auto* low_latency = std::get_if<std::unique_ptr<Low_Latency>>(&strategy_))
return (*low_latency)->discard_pending_frame();
return false;
}
template <class Render>
requires std::invocable<Render, const Render_3D_Frame_Data&>
[[nodiscard]] bool render(Render&& render) {
return std::visit(
[&](auto& strategy) {
auto lease = strategy->acquire_renderer();
if (!lease)
return false;
return static_cast<bool>(std::invoke(std::forward<Render>(render), static_cast<const Render_3D_Frame_Data&>(*lease)));
},
strategy_);
}
private:
static Strategy make_strategy(Frame_3D_Control_Mode mode, double maximum_frames_per_second) {
switch (mode) {
case Frame_3D_Control_Mode::Manual:
return Strategy(std::in_place_type<std::unique_ptr<Manual>>, std::make_unique<Manual>());
case Frame_3D_Control_Mode::Low_Latency:
return Strategy(
std::in_place_type<std::unique_ptr<Low_Latency>>,
std::make_unique<Low_Latency>(Observer_State<>{}, typename Low_Latency::Configuration{maximum_frames_per_second}));
case Frame_3D_Control_Mode::Playback:
return Strategy(std::in_place_type<std::unique_ptr<Playback>>, std::make_unique<Playback>());
}
throw std::invalid_argument("unknown Render_3D frame control mode");
}
Strategy strategy_;
};
} // namespace renderive::detail
@@ -0,0 +1,75 @@
#pragma once
#include <condition_variable>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <utility>
namespace renderive::detail {
class Render_Domain final {
public:
Render_Domain() : thread_([this] { run(); }) {}
~Render_Domain() {
{
std::lock_guard lock(mutex_);
stopping_ = true;
}
condition_.notify_one();
if (thread_.joinable())
thread_.join();
}
Render_Domain(const Render_Domain&) = delete;
Render_Domain& operator=(const Render_Domain&) = delete;
template <class Function>
auto invoke(Function&& function) -> std::invoke_result_t<Function> {
using Result = std::invoke_result_t<Function>;
auto task = std::make_shared<std::packaged_task<Result()>>(std::forward<Function>(function));
auto result = task->get_future();
{
std::lock_guard lock(mutex_);
if (stopping_)
throw std::runtime_error("Render_3D render domain is stopping");
tasks_.emplace([task] { (*task)(); });
}
condition_.notify_one();
if constexpr (std::is_void_v<Result>) {
result.get();
} else {
return result.get();
}
}
private:
void run() {
for (;;) {
std::function<void()> task;
{
std::unique_lock lock(mutex_);
condition_.wait(lock, [&] { return stopping_ || !tasks_.empty(); });
if (stopping_ && tasks_.empty())
return;
task = std::move(tasks_.front());
tasks_.pop();
}
task();
}
}
std::mutex mutex_;
std::condition_variable condition_;
std::queue<std::function<void()>> tasks_;
bool stopping_{};
std::thread thread_;
};
} // namespace renderive::detail
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include "Render_3D.h"
View File
@@ -0,0 +1,96 @@
#include <gtest/gtest.h>
#include "render_3D/detail/Ordered_Artifact_Transaction.h"
#include "render_3D/detail/Ordered_Backend_Shutdown.h"
#include <string>
#include <vector>
namespace renderive::detail {
namespace {
struct Fake_Artifact {
int stream = 7;
};
struct Fake_Transaction_Api {
std::vector<std::string> events;
Fake_Artifact artifact;
bool emit_succeeds = true;
bool attach_succeeds = true;
bool execute_succeeds = true;
std::size_t execute_count{};
Fake_Artifact* emit() {
events.emplace_back("emit");
return emit_succeeds ? &artifact : nullptr;
}
int* stream(Fake_Artifact& value) {
events.emplace_back("stream");
return &value.stream;
}
bool attach() {
events.emplace_back("attach");
return attach_succeeds;
}
bool execute(const int&) {
events.emplace_back("execute");
++execute_count;
return execute_succeeds;
}
void destroy(Fake_Artifact&) { events.emplace_back("destroy"); }
};
TEST(DatovizTransaction, SuccessfulEmitExecutesExactlyOnceInCausalOrder) {
Fake_Transaction_Api api;
EXPECT_TRUE(static_cast<bool>(execute_ordered_artifact_transaction(api)));
EXPECT_EQ(api.execute_count, 1U);
EXPECT_EQ(api.events, std::vector<std::string>({"emit", "stream", "attach", "execute", "destroy"}));
}
TEST(DatovizTransaction, AttachFailureStillConsumesEmittedArtifactExactlyOnce) {
Fake_Transaction_Api api;
api.attach_succeeds = false;
EXPECT_FALSE(static_cast<bool>(execute_ordered_artifact_transaction(api)));
EXPECT_EQ(api.execute_count, 1U);
EXPECT_EQ(api.events, std::vector<std::string>({"emit", "stream", "attach", "execute", "destroy"}));
}
TEST(DatovizTransaction, ExecuteFailureConsumesAndDestroysArtifactBeforeRecovery) {
Fake_Transaction_Api api;
api.execute_succeeds = false;
EXPECT_FALSE(static_cast<bool>(execute_ordered_artifact_transaction(api)));
EXPECT_EQ(api.execute_count, 1U);
EXPECT_EQ(api.events, std::vector<std::string>({"emit", "stream", "attach", "execute", "destroy"}));
}
TEST(DatovizTransaction, FailedEmitDoesNotStartAttachOrExecute) {
Fake_Transaction_Api api;
api.emit_succeeds = false;
EXPECT_FALSE(static_cast<bool>(execute_ordered_artifact_transaction(api)));
EXPECT_EQ(api.execute_count, 0U);
EXPECT_EQ(api.events, std::vector<std::string>({"emit"}));
}
TEST(DatovizOwnership, ShutdownOrderProtectsBorrowedDeviceAndTargets) {
std::vector<std::string> events;
ordered_backend_shutdown(
[&] { events.emplace_back("runtime"); },
[&] { events.emplace_back("targets"); },
[&] { events.emplace_back("scene"); },
[&] { events.emplace_back("gpu_ctx"); });
EXPECT_EQ(events, std::vector<std::string>({"runtime", "targets", "scene", "gpu_ctx"}));
}
} // namespace
} // namespace renderive::detail
+160
View File
@@ -0,0 +1,160 @@
#include <gtest/gtest.h>
#include "render_3D/detail/Render_3D_Core.h"
#include <atomic>
#include <cstdint>
#include <thread>
#include <vector>
namespace renderive::detail {
namespace {
Render_3D_State state_with_width(std::uint32_t width) {
Render_3D_State state;
state.viewport = {width, 600};
return state;
}
TEST(Render3DState, WriterDoesNotChangePublishedStateUntilPublish) {
Logical_State_Buffer states(state_with_width(800));
const auto front = states.publish_frame();
states.update([](Render_3D_State& state) { state.viewport.width = 1920; });
EXPECT_EQ(front.state.viewport.width, 800U);
const auto frame = states.publish_frame();
EXPECT_EQ(frame.state.viewport.width, 1920U);
EXPECT_EQ(frame.revision, 2U);
}
TEST(Render3DFrameStrategy, LowLatencyDropsBeforeRenderTransaction) {
Logical_State_Buffer states(state_with_width(1));
Frame_Scheduler scheduler(Frame_3D_Control_Mode::Low_Latency, 60.0);
for (const std::uint32_t width : {10U, 11U, 12U}) {
states.update([width](Render_3D_State& state) { state.viewport.width = width; });
ASSERT_TRUE(scheduler.prepare(states));
}
std::vector<std::uint32_t> emitted;
std::vector<std::uint64_t> sequences;
ASSERT_TRUE(scheduler.render([&](const Render_3D_Frame_Data& frame) {
emitted.push_back(frame.state.viewport.width);
sequences.push_back(frame.frame_sequence);
return true;
}));
EXPECT_EQ(emitted, std::vector<std::uint32_t>({12U}));
EXPECT_EQ(sequences, std::vector<std::uint64_t>({3U}));
}
TEST(Render3DFrameStrategy, ManualRefreshControlsVisibilityAndReplay) {
Logical_State_Buffer states(state_with_width(1));
Frame_Scheduler scheduler(Frame_3D_Control_Mode::Manual, 60.0);
states.update([](Render_3D_State& state) { state.viewport.width = 20; });
ASSERT_TRUE(scheduler.prepare(states));
std::vector<std::uint32_t> rendered;
std::vector<std::uint64_t> sequences;
const auto render = [&](const Render_3D_Frame_Data& frame) {
rendered.push_back(frame.state.viewport.width);
sequences.push_back(frame.frame_sequence);
return true;
};
EXPECT_FALSE(scheduler.render(render));
ASSERT_TRUE(scheduler.refresh());
EXPECT_TRUE(scheduler.render(render));
EXPECT_TRUE(scheduler.render(render));
EXPECT_EQ(rendered, std::vector<std::uint32_t>({20U, 20U}));
EXPECT_EQ(sequences, std::vector<std::uint64_t>({1U, 1U}));
}
TEST(Render3DFrameStrategy, PlaybackExecutesEveryPublishedStateInOrder) {
Logical_State_Buffer states(state_with_width(1));
Frame_Scheduler scheduler(Frame_3D_Control_Mode::Playback, 60.0);
for (const std::uint32_t width : {30U, 31U, 32U}) {
states.update([width](Render_3D_State& state) { state.viewport.width = width; });
ASSERT_TRUE(scheduler.prepare(states));
}
std::vector<std::uint32_t> rendered;
std::vector<std::uint64_t> sequences;
const auto render = [&](const Render_3D_Frame_Data& frame) {
rendered.push_back(frame.state.viewport.width);
sequences.push_back(frame.frame_sequence);
return true;
};
EXPECT_TRUE(scheduler.render(render));
EXPECT_TRUE(scheduler.render(render));
EXPECT_TRUE(scheduler.render(render));
EXPECT_FALSE(scheduler.render(render));
EXPECT_EQ(rendered, std::vector<std::uint32_t>({30U, 31U, 32U}));
EXPECT_EQ(sequences, std::vector<std::uint64_t>({1U, 2U, 3U}));
}
TEST(Render3DFrameStrategy, DiscardNeverStartsRenderTransaction) {
Logical_State_Buffer states(state_with_width(40));
Frame_Scheduler scheduler(Frame_3D_Control_Mode::Low_Latency, 60.0);
ASSERT_TRUE(scheduler.prepare(states));
ASSERT_TRUE(scheduler.discard_pending());
std::size_t render_transaction_count{};
EXPECT_FALSE(scheduler.render([&](const Render_3D_Frame_Data&) {
++render_transaction_count;
return true;
}));
EXPECT_EQ(render_transaction_count, 0U);
}
TEST(Render3DState, ConcurrentPublishNeverExposesPartialLogicalState) {
Logical_State_Buffer states(state_with_width(1));
Frame_Scheduler scheduler(Frame_3D_Control_Mode::Low_Latency, 60.0);
std::atomic<bool> producer_done{};
std::atomic<bool> invalid_snapshot{};
std::atomic<bool> non_monotonic_revision{};
std::atomic<std::uint32_t> maximum_width{};
std::thread producer([&] {
for (std::uint32_t width = 2; width <= 500; ++width) {
states.update([width](Render_3D_State& state) {
state.viewport.width = width;
state.viewport.height = width * 2;
});
(void)scheduler.prepare(states);
}
producer_done.store(true, std::memory_order_release);
});
std::thread renderer([&] {
std::uint64_t last_revision{};
while (!producer_done.load(std::memory_order_acquire) || maximum_width.load() < 500) {
(void)scheduler.render([&](const Render_3D_Frame_Data& frame) {
if (frame.state.viewport.height != frame.state.viewport.width * 2)
invalid_snapshot.store(true, std::memory_order_relaxed);
if (frame.revision < last_revision)
non_monotonic_revision.store(true, std::memory_order_relaxed);
last_revision = frame.revision;
auto observed = maximum_width.load(std::memory_order_relaxed);
while (observed < frame.state.viewport.width &&
!maximum_width.compare_exchange_weak(observed, frame.state.viewport.width)) {
}
return true;
});
std::this_thread::yield();
}
});
producer.join();
renderer.join();
EXPECT_FALSE(invalid_snapshot.load());
EXPECT_FALSE(non_monotonic_revision.load());
EXPECT_EQ(maximum_width.load(), 500U);
}
} // namespace
} // namespace renderive::detail
@@ -0,0 +1,216 @@
#include <gtest/gtest.h>
#include "render_3D/Render_3D.h"
#include "render_3D/detail/Datoviz_Backend.h"
#include "render_3D/detail/Ordered_Backend_Shutdown.h"
#include <atomic>
#include <cstdint>
#include <exception>
#include <memory>
#include <thread>
namespace renderive {
namespace {
std::unique_ptr<Render_3D> build_or_skip(Frame_3D_Control_Mode mode) {
try {
Render_3D::Builder builder;
return builder
.frame_control(mode)
.viewport({96, 64})
.frame_slots(2)
.gpu(0, false)
.clear_color({0.02F, 0.03F, 0.04F, 1.0F})
.build();
} catch (const std::exception&) {
return {};
}
return {};
}
bool publish_and_render(Render_3D& renderer, Frame_3D_Control_Mode mode) {
if (!renderer.prepare_frame())
return false;
if (mode == Frame_3D_Control_Mode::Manual && !renderer.refresh_manual_frame())
return false;
return renderer.render_prepared_frame();
}
struct Backend_Harness {
explicit Backend_Harness(const detail::Render_3D_State& state) {
backend = std::make_unique<detail::Datoviz_Backend>(0, false, state);
targets = backend->create_target_pool(2);
}
~Backend_Harness() {
if (backend == nullptr)
return;
if (targets != nullptr)
targets->wait_all();
detail::ordered_backend_shutdown(
[&] { backend->destroy_runtime(); },
[&] { targets.reset(); },
[&] { backend->destroy_scene(); },
[&] { backend->destroy_gpu_context(); });
}
std::unique_ptr<detail::Datoviz_Backend> backend;
std::unique_ptr<detail::Frame_Target_Pool> targets;
};
class Render3DStrategyIntegration
: public testing::TestWithParam<Frame_3D_Control_Mode> {};
TEST_P(Render3DStrategyIntegration, UsesExternalTargetsAcrossResizeGenerations) {
const auto mode = GetParam();
auto renderer = build_or_skip(mode);
if (renderer == nullptr)
GTEST_SKIP() << "Vulkan/Datoviz backend unavailable";
ASSERT_NE(renderer, nullptr);
Sphere_3D sphere;
sphere.radius = 0.35F;
sphere.color = {220, 80, 40, 255};
const Object_3D_Id id = renderer->add_sphere(sphere);
ASSERT_TRUE(publish_and_render(*renderer, mode));
for (int frame = 0; frame < 2; ++frame) {
sphere.position.x += 0.02F;
ASSERT_TRUE(renderer->replace_sphere(id, sphere));
ASSERT_TRUE(publish_and_render(*renderer, mode));
}
for (const Extent_2D extent : {Extent_2D{128, 72}, Extent_2D{80, 80}, Extent_2D{160, 90}}) {
renderer->resize(extent);
sphere.position.x += 0.05F;
ASSERT_TRUE(renderer->replace_sphere(id, sphere));
ASSERT_TRUE(publish_and_render(*renderer, mode));
}
renderer->wait_for_render();
}
INSTANTIATE_TEST_SUITE_P(
AllKernelStrategies, Render3DStrategyIntegration,
testing::Values(
Frame_3D_Control_Mode::Low_Latency,
Frame_3D_Control_Mode::Manual,
Frame_3D_Control_Mode::Playback));
TEST(Render3DIntegration, BuiltObjectOutlivesBuilderConfiguration) {
std::unique_ptr<Render_3D> renderer;
try {
{
Render_3D::Builder builder;
renderer = builder.viewport({64, 64}).frame_slots(2).build();
}
} catch (const std::exception& error) {
GTEST_SKIP() << "Vulkan/Datoviz backend unavailable: " << error.what();
}
ASSERT_NE(renderer, nullptr);
(void)renderer->add_sphere({});
ASSERT_TRUE(renderer->prepare_frame());
EXPECT_TRUE(renderer->render_prepared_frame());
}
TEST(Render3DIntegration, RepeatedCreateRenderResizeDestroyIsStable) {
for (int iteration = 0; iteration < 3; ++iteration) {
auto renderer = build_or_skip(Frame_3D_Control_Mode::Low_Latency);
if (renderer == nullptr)
GTEST_SKIP() << "Vulkan/Datoviz backend unavailable";
ASSERT_NE(renderer, nullptr);
(void)renderer->add_sphere({});
ASSERT_TRUE(publish_and_render(*renderer, Frame_3D_Control_Mode::Low_Latency));
renderer->resize({static_cast<std::uint32_t>(100 + iteration * 8), 72});
ASSERT_TRUE(publish_and_render(*renderer, Frame_3D_Control_Mode::Low_Latency));
}
}
TEST(Render3DIntegration, SlotTargetsRotateAndBackendRebuildsFromLogicalState) {
detail::Render_3D_State state;
state.viewport = {64, 64};
std::unique_ptr<Backend_Harness> harness;
try {
harness = std::make_unique<Backend_Harness>(state);
} catch (const std::exception& error) {
GTEST_SKIP() << "Vulkan/Datoviz backend unavailable: " << error.what();
}
detail::Frame_Target& slot_0 = harness->targets->acquire(1, state.viewport);
ASSERT_TRUE(slot_0.begin());
const DvzStreamFrame slot_0_frame = slot_0.stream_frame();
EXPECT_TRUE(slot_0_frame.command_buffer_recording);
EXPECT_TRUE(slot_0_frame.image_borrowed);
EXPECT_TRUE(slot_0_frame.image_view_borrowed);
EXPECT_TRUE(slot_0_frame.command_buffer_borrowed);
slot_0.abort();
detail::Frame_Target& slot_1 = harness->targets->acquire(2, state.viewport);
const DvzStreamFrame slot_1_frame = slot_1.stream_frame();
EXPECT_NE(slot_0_frame.image, slot_1_frame.image);
EXPECT_EQ(slot_0_frame.resource_generation, slot_1_frame.resource_generation);
detail::Frame_Target& reused_slot_0 = harness->targets->acquire(3, state.viewport);
const DvzStreamFrame reused_slot_0_frame = reused_slot_0.stream_frame();
EXPECT_EQ(slot_0_frame.image, reused_slot_0_frame.image);
EXPECT_EQ(slot_0_frame.command_buffer, reused_slot_0_frame.command_buffer);
detail::Render_3D_Frame_Data frame{state, 1, 4};
ASSERT_TRUE(harness->backend->render(frame, *harness->targets));
harness->backend->destroy_runtime();
frame.state.clear_color.red = 0.25F;
++frame.revision;
++frame.frame_sequence;
EXPECT_TRUE(harness->backend->render(frame, *harness->targets));
}
TEST(Render3DIntegration, ConcurrentStateUpdatesAndRenderingStopBeforeDestroy) {
auto renderer = build_or_skip(Frame_3D_Control_Mode::Low_Latency);
if (renderer == nullptr)
GTEST_SKIP() << "Vulkan/Datoviz backend unavailable";
Sphere_3D sphere;
const Object_3D_Id id = renderer->add_sphere(sphere);
std::atomic<bool> first_frame_prepared{};
std::atomic<bool> failed{};
std::thread producer([&] {
try {
for (int update = 0; update < 6; ++update) {
sphere.position.x = static_cast<float>(update) * 0.03F;
if (!renderer->replace_sphere(id, sphere) || !renderer->prepare_frame()) {
failed.store(true, std::memory_order_release);
return;
}
if (update == 0)
first_frame_prepared.store(true, std::memory_order_release);
std::this_thread::yield();
}
} catch (...) {
failed.store(true, std::memory_order_release);
}
});
std::thread consumer([&] {
while (!first_frame_prepared.load(std::memory_order_acquire) &&
!failed.load(std::memory_order_acquire))
std::this_thread::yield();
try {
for (int frame = 0; frame < 6; ++frame)
(void)renderer->render_prepared_frame();
} catch (...) {
failed.store(true, std::memory_order_release);
}
});
producer.join();
consumer.join();
ASSERT_FALSE(failed.load(std::memory_order_acquire));
ASSERT_TRUE(renderer->prepare_frame());
ASSERT_TRUE(renderer->render_prepared_frame());
renderer->wait_for_render();
}
} // namespace
} // namespace renderive