Compare commits
1 Commits
fix_dead_lock
...
restart
| Author | SHA1 | Date | |
|---|---|---|---|
| d99da004d0 |
+59
-12
@@ -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
@@ -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 ()
|
||||
|
||||
@@ -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()
|
||||
@@ -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,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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include "Render_3D.h"
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user