diff --git a/src/viewer/CMakeLists.txt b/src/viewer/CMakeLists.txt index 215a2cd..0153d0c 100644 --- a/src/viewer/CMakeLists.txt +++ b/src/viewer/CMakeLists.txt @@ -84,18 +84,20 @@ foreach( SHADER_PATH ${SHADERS} ) file( RELATIVE_PATH SHADER_RELATIVE_PATH ${SOURCE_SHADER_DIR} ${SHADER_PATH} ) get_filename_component( SHADER_NAME ${SHADER_RELATIVE_PATH} NAME ) + get_filename_component( SHADER_EXTENSION ${SHADER_RELATIVE_PATH} EXT ) if( NOT EXISTS ${GENERATED_SHADER_DIR} ) file( MAKE_DIRECTORY ${GENERATED_SHADER_DIR} ) endif() - - add_custom_command( - OUTPUT "${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" - DEPENDS ${SHADER_PATH} - COMMAND ${GLSLC_EXECUTABLE} "${SHADER_PATH}" -mfmt=num -o "${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" - COMMENT "Compiling shader: ${SHADER_PATH} to ${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" - ) - list( APPEND COMPILED_SHADERS "${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" ) + if( "${SHADER_EXTENSION}" STREQUAL ".comp" OR "${SHADER_EXTENSION}" STREQUAL ".vert" OR "${SHADER_EXTENSION}" STREQUAL ".frag" ) + add_custom_command( + OUTPUT "${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" + DEPENDS ${SHADER_PATH} + COMMAND ${GLSLC_EXECUTABLE} "${SHADER_PATH}" -mfmt=num -o "${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" + COMMENT "Compiling shader: ${SHADER_PATH} to ${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" + ) + list( APPEND COMPILED_SHADERS "${GENERATED_SHADER_DIR}/${SHADER_NAME}.spv.h" ) + endif() endforeach() add_custom_command( diff --git a/src/viewer/appState.cpp b/src/viewer/appState.cpp index 6dfbb78..69a4c53 100644 --- a/src/viewer/appState.cpp +++ b/src/viewer/appState.cpp @@ -22,27 +22,7 @@ void AppState::CallStateCallbacks() cmfPath.CallCallbacks( *this ); exitRequested.CallCallbacks( *this ); - modelState.selectedLod.CallCallbacks( *this ); - modelState.activeLod.CallCallbacks( *this ); - modelState.meshScreenSize.CallCallbacks( *this ); - modelState.visualizationShader.CallCallbacks( *this ); - modelState.availableShaders.CallCallbacks( *this ); - modelState.polygonMode.CallCallbacks( *this ); - modelState.meshVisibilityStates.CallCallbacks( *this ); - modelState.morphTargetWeight.CallCallbacks( *this ); - modelState.morphTargetEnabled.CallCallbacks( *this ); - modelState.meshWireframeOverlay.CallCallbacks( *this ); - modelState.audioOcclusionMesh.CallCallbacks( *this ); - modelState.meshBoundingBox.CallCallbacks( *this ); - modelState.modelBoundingBox.CallCallbacks( *this ); - modelState.currentAnimation.CallCallbacks( *this ); - modelState.currentAnimationTime.CallCallbacks( *this ); - modelState.availableShaders.CallCallbacks( *this ); - modelState.boneDebug.CallCallbacks( *this ); - modelState.jointDebug.CallCallbacks( *this ); - modelState.jointAxisDebug.CallCallbacks( *this ); - modelState.activeAnimationOwner.CallCallbacks( *this ); - modelState.selectedBones.CallCallbacks( *this ); + modelState.CallCallbacks( *this ); } void AppState::ResetModelState() diff --git a/src/viewer/appState.h b/src/viewer/appState.h index dbe2980..5a87922 100644 --- a/src/viewer/appState.h +++ b/src/viewer/appState.h @@ -5,6 +5,7 @@ #include #include "data/cmfcontent.h" +#include "rendering/vulkan/graphicseffecttypes.h" //forwards declaration struct AppState; @@ -31,6 +32,7 @@ class State public: State( T initialValue ); const T GetValue() const; + T& GetValue(); void SetValue( T newValue ); void ForceSetValue( T newValue ); void SetValueNoCallback( T newValue ); @@ -58,6 +60,12 @@ class StateCollection size_t AddState(); size_t AddState( T initialValue ); + template + size_t AddState( Callable configurator ); + + template + size_t AddState( T initialValue, Callable configurator ); + void Clear(); void CallCallbacks( AppState& appState ); @@ -66,6 +74,7 @@ class StateCollection void RemoveAt( size_t index ); size_t size() const; + bool empty() const; // Non-const iterators iterator begin(); @@ -100,28 +109,49 @@ enum class CameraTrigger CAMERA_TRIGGER_LOOK_BACK, }; +struct MeshState +{ + State display{ true }; + /// the pair is + StateCollection> morphs{ { 0.0f, true } }; + State wireframeOverlay{ false }; + State audioOcclusionMesh{ false }; + State renderBoundingBox{ false }; + StateCollection> showVertexNormals{ { 0, false } }; + StateCollection> showVertexTangents{ { 0, false } }; + StateCollection> showVertexBinormals{ { 0, false } }; + State activeLod{ 0 }; + State meshScreenSize{ 0.0f }; + + void CallCallbacks( AppState& appState ) + { + display.CallCallbacks( appState ); + morphs.CallCallbacks( appState ); + wireframeOverlay.CallCallbacks( appState ); + audioOcclusionMesh.CallCallbacks( appState ); + renderBoundingBox.CallCallbacks( appState ); + activeLod.CallCallbacks( appState ); + meshScreenSize.CallCallbacks( appState ); + showVertexNormals.CallCallbacks( appState ); + showVertexTangents.CallCallbacks( appState ); + showVertexBinormals.CallCallbacks( appState ); + } +}; + struct ModelState { /// The lod selected by the user (-1 for auto) State selectedLod{ -1 }; - /// The lod of the mesh that is currently active - StateCollection activeLod{ 0 }; - StateCollection meshScreenSize{ 0.0f }; + StateCollection meshes{ {} }; - State visualizationShader{ "" }; - State> availableShaders{ {} }; + State> activeShader{ {} }; + State>> availableShaders{ {} }; State polygonMode{ VK_POLYGON_MODE_FILL }; State currentAnimation{ "" }; State currentAnimationTime{ 0.0f }; - StateCollection meshVisibilityStates{ true }; - StateCollection morphTargetWeight{ 0.0 }; - StateCollection morphTargetEnabled{ true }; - StateCollection meshWireframeOverlay{ false }; - StateCollection audioOcclusionMesh{ false }; - StateCollection meshBoundingBox{ false }; State boneDebug{ false }; State jointDebug{ false }; State jointAxisDebug{ false }; @@ -129,6 +159,24 @@ struct ModelState State> activeAnimationOwner{ nullptr }; StateCollection> animationOverrides{ nullptr }; StateCollection selectedBones{ 0xFF }; + + void CallCallbacks( AppState& appState ) + { + selectedLod.CallCallbacks( appState ); + meshes.CallCallbacks( appState ); + activeShader.CallCallbacks( appState ); + availableShaders.CallCallbacks( appState ); + polygonMode.CallCallbacks( appState ); + currentAnimation.CallCallbacks( appState ); + currentAnimationTime.CallCallbacks( appState ); + boneDebug.CallCallbacks( appState ); + jointDebug.CallCallbacks( appState ); + jointAxisDebug.CallCallbacks( appState ); + modelBoundingBox.CallCallbacks( appState ); + activeAnimationOwner.CallCallbacks( appState ); + animationOverrides.CallCallbacks( appState ); + selectedBones.CallCallbacks( appState ); + } }; struct AppState diff --git a/src/viewer/appState_template_impl.h b/src/viewer/appState_template_impl.h index ecd7cdb..cfc8ba1 100644 --- a/src/viewer/appState_template_impl.h +++ b/src/viewer/appState_template_impl.h @@ -45,9 +45,33 @@ void State::SetValueNoCallback( T newValue ) m_value = newValue; } +template +T& State::GetValue() +{ + return m_value; +} + +namespace detail +{ +template +struct has_call_callbacks : std::false_type +{ +}; + +template +struct has_call_callbacks().CallCallbacks( std::declval() ) )>> : std::true_type +{ +}; +} + template void State::CallCallbacks( AppState& appState ) { + if constexpr( detail::has_call_callbacks::value ) + { + m_value.CallCallbacks( appState ); + } + if( m_fireCallbacks ) { for( auto& callback : m_callbacks ) @@ -91,6 +115,28 @@ size_t StateCollection::AddState( T initialValue ) return m_states.size() - 1; } +template +template +size_t StateCollection::AddState( Callable configurator ) +{ + State state( m_initialValue ); + m_states.push_back( state ); + configurator( m_states.back().GetValue() ); + m_fireCallbacks = true; + return m_states.size() - 1; +} + +template +template +size_t StateCollection::AddState( T initialValue, Callable configurator ) +{ + State state( initialValue ); + m_states.push_back( state ); + configurator( m_states.back().GetValue() ); + m_fireCallbacks = true; + return m_states.size() - 1; +} + template void StateCollection::RemoveAt( size_t index ) { @@ -111,12 +157,16 @@ void StateCollection::RegisterCallback( std::function, A template void StateCollection::CallCallbacks( AppState& appState ) { + for( auto& state : m_states ) + { + state.CallCallbacks( appState ); + } + std::vector values; values.reserve( m_states.size() ); for( auto& state : m_states ) { values.push_back( state.GetValue() ); - state.CallCallbacks( appState ); } if( m_fireCallbacks ) @@ -146,6 +196,12 @@ size_t StateCollection::size() const return m_states.size(); } +template +bool StateCollection::empty() const +{ + return m_states.empty(); +} + // Non-const iterators template typename StateCollection::iterator StateCollection::begin() diff --git a/src/viewer/assets/shaders/binormalaxis.frag b/src/viewer/assets/shaders/binormalaxis.frag new file mode 100644 index 0000000..9bcc4dc --- /dev/null +++ b/src/viewer/assets/shaders/binormalaxis.frag @@ -0,0 +1,14 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 + +// inputs +layout( location = 0 ) in vec3 inFragColor; + +// Outputs +layout( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4( inFragColor, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/binormalaxis.vert b/src/viewer/assets/shaders/binormalaxis.vert new file mode 100644 index 0000000..80c9f3d --- /dev/null +++ b/src/viewer/assets/shaders/binormalaxis.vert @@ -0,0 +1,40 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 +#pragma multi_usage inBinormal + +// Constants +layout( binding = 0 ) uniform PerFrame +{ + mat4 projectionMatrix; + mat4 viewMatrix; +} perframe; + + +layout( binding = 1 ) uniform AxisConfig +{ + vec3 color; + float scale; + int axisSelector; +} axisConfig; + +// Inputs +// inputs are per instance, not per vertex +layout( location = 0 ) in vec3 inPosition; +layout( location = 1 ) in vec3 inBinormal; + +// Output +layout ( location = 0 ) out vec3 outColor; + +void main() +{ + vec4 position = vec4( inPosition, 1.0 ); + + if( gl_VertexIndex == 1 ) + { + position.xyz += axisConfig.scale * inBinormal; + } + + gl_Position = perframe.projectionMatrix * perframe.viewMatrix * position; + outColor = axisConfig.color; +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/model_color.frag b/src/viewer/assets/shaders/model_color.frag new file mode 100644 index 0000000..99e42b7 --- /dev/null +++ b/src/viewer/assets/shaders/model_color.frag @@ -0,0 +1,14 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 + +// Inputs +layout( location = 0 ) in vec3 color; + +// Outputs +layout( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4( color, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/model_color.vert b/src/viewer/assets/shaders/model_color.vert new file mode 100644 index 0000000..c8496e2 --- /dev/null +++ b/src/viewer/assets/shaders/model_color.vert @@ -0,0 +1,26 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 +#pragma multi_usage inColor + +// Constants +layout( binding = 0 ) uniform PerFrame +{ + mat4 projectionMatrix; + mat4 viewMatrix; +} perframe; + + +// Inputs +layout( location = 0 ) in vec3 inPosition; +layout( location = 1 ) in vec3 inColor; + +// Outputs; +layout( location = 0 ) out vec3 color; + + +void main() +{ + color = inColor; + gl_Position = perframe.projectionMatrix * perframe.viewMatrix * vec4( inPosition, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/model_normal.vert b/src/viewer/assets/shaders/model_normal.vert index a569fce..cc10522 100644 --- a/src/viewer/assets/shaders/model_normal.vert +++ b/src/viewer/assets/shaders/model_normal.vert @@ -11,8 +11,9 @@ layout( binding = 0 ) uniform PerFrame // Inputs +// inputs are per instance, not per vertex layout( location = 0 ) in vec3 inPosition; -layout( location = 1 ) in vec3 inNormal; +layout( location = 1 ) in vec3 inNormal; // shadercachecreator_multi_usage_index // Outputs layout( location = 0 ) out vec3 viewPosition; diff --git a/src/viewer/assets/shaders/model_packed_normal.vert b/src/viewer/assets/shaders/model_packed_normal.vert index 86ceaaf..84c8493 100644 --- a/src/viewer/assets/shaders/model_packed_normal.vert +++ b/src/viewer/assets/shaders/model_packed_normal.vert @@ -1,6 +1,9 @@ // Copyright (c) 2025 CCP ehf. #version 450 +#pragma multi_usage inPackedTangents + +#include "packed_tangent.inc" // Constants layout( binding = 0 ) uniform PerFrame @@ -18,53 +21,11 @@ layout( location = 1 ) in vec4 inPackedTangents; layout( location = 0 ) out vec3 viewPosition; layout( location = 1 ) out vec3 normal; - -struct TangentSpace { - vec3 tangent; - vec3 bitangent; - vec3 normal; -}; - -TangentSpace unpackTangentSpace( vec4 t ) { - // Heavily optimized shader code that constructs the TBN matrix. - - // Extract the xyz components and square them - float x = t.x; - float y = t.y; - float z = t.z; - float x2 = x * x; - float y2 = y * y; - float z2 = z * z; - - // Optimized fma() chain to reconstruct W = sqrt(1 - x2 - y2 - z2) - // Don't use the above x2, y2 and z2 values, to reduce pipeline dependencies. - float w2 = clamp( fma( z, -z, fma( y, -y, fma( x, -x, 1.0f ) ) ), 0.0f, 1.0f ); - float w = sqrt( w2 ); - - // Calculate shared values. - // These multiplications by 2.0f are free on some GPUs. - float xy = x * y * 2.0f; - float xz = x * z * 2.0f; - float yz = y * z * 2.0f; - float xw = x * w * 2.0f; - float yw = y * w * 2.0f; - float zw = z * w * 2.0f; - - // Compute the three vectors. - TangentSpace space; - - space.tangent = vec3( fma( -2.0f, y2, fma( -2.0f, z2, 1.0f ) ), + xy + zw, + xz - yw ); - space.bitangent = vec3( - zw + xy, fma( -2.0f, x2, fma( -2.0f, z2, 1.0f ) ), + yz + xw ); - space.normal = vec3( + yw + xz, + yz - xw, fma( -2.0f, x2, fma( -2.0f, y2, 1.0f ) ) ) * t.w; // packed normal sign multiplication - - return space; -} - void main() { viewPosition = ( perframe.viewMatrix * vec4( inPosition, 1.0 ) ).xyz; gl_Position = perframe.projectionMatrix * vec4( viewPosition, 1.0 ); - TangentSpace space = unpackTangentSpace( inPackedTangents ); + TangentSpace space = UnpackTangentSpace( inPackedTangents ); normal = ( perframe.viewMatrix * vec4( space.normal, 0.0 ) ).xyz; } \ No newline at end of file diff --git a/src/viewer/assets/shaders/model_packed_normal_legacy.vert b/src/viewer/assets/shaders/model_packed_normal_legacy.vert index e576ae1..33fc43e 100644 --- a/src/viewer/assets/shaders/model_packed_normal_legacy.vert +++ b/src/viewer/assets/shaders/model_packed_normal_legacy.vert @@ -1,6 +1,9 @@ // Copyright (c) 2025 CCP ehf. #version 450 +#pragma multi_usage inPackedTangentsLegacy + +#include "packed_tangent.inc" // Constants layout( binding = 0 ) uniform PerFrame @@ -18,43 +21,11 @@ layout( location = 1 ) in vec4 inPackedTangentsLegacy; layout( location = 0 ) out vec3 viewPosition; layout( location = 1 ) out vec3 normal; - -struct TangentSpace { - vec3 tangent; - vec3 bitangent; - vec3 normal; -}; - -vec2 SinCosFloat( float angle ) -{ - return vec2( sin( angle ), cos( angle ) ); -} - -TangentSpace unpackTangentSpace( vec4 tangents ) -{ - vec4 angles = tangents * 6.28318530718 - 3.14159265359; - vec4 sc0, sc1; - sc0.xy = SinCosFloat( angles.x ); - sc0.zw = SinCosFloat( angles.y ); - sc1.xy = SinCosFloat( angles.z ); - sc1.zw = SinCosFloat( angles.w ); - - TangentSpace space; - - vec3 tangent = vec3( sc0.y * abs( sc0.z ), sc0.x * abs( sc0.z ), sc0.w ); - vec3 bitangent = vec3( sc1.y * abs( sc1.z ), sc1.x * abs( sc1.z ), sc1.w ); - vec3 normal = -cross( tangent, bitangent ); - //normal = all( angles.yw > 0.0 ) ? -normal : normal; - if( angles.y > 0.0 && angles.w > 0.0 ) normal = -normal; - - return TangentSpace( tangent, bitangent, normal ); -} - void main() { viewPosition = ( perframe.viewMatrix * vec4( inPosition, 1.0 ) ).xyz; gl_Position = perframe.projectionMatrix * vec4( viewPosition, 1.0 ); - TangentSpace space = unpackTangentSpace( inPackedTangentsLegacy ); + TangentSpace space = UnpackTangentSpaceLegacy( inPackedTangentsLegacy ); normal = ( perframe.viewMatrix * vec4( space.normal, 0.0 ) ).xyz; } \ No newline at end of file diff --git a/src/viewer/assets/shaders/normalaxis.frag b/src/viewer/assets/shaders/normalaxis.frag new file mode 100644 index 0000000..9bcc4dc --- /dev/null +++ b/src/viewer/assets/shaders/normalaxis.frag @@ -0,0 +1,14 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 + +// inputs +layout( location = 0 ) in vec3 inFragColor; + +// Outputs +layout( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4( inFragColor, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/normalaxis.vert b/src/viewer/assets/shaders/normalaxis.vert new file mode 100644 index 0000000..3868bef --- /dev/null +++ b/src/viewer/assets/shaders/normalaxis.vert @@ -0,0 +1,40 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 +#pragma multi_usage inNormal + +// Constants +layout( binding = 0 ) uniform PerFrame +{ + mat4 projectionMatrix; + mat4 viewMatrix; +} perframe; + + +layout( binding = 1 ) uniform AxisConfig +{ + vec3 color; + float scale; + int axisSelector; +} axisConfig; + +// Inputs +// inputs are per instance, not per vertex +layout( location = 0 ) in vec3 inPosition; +layout( location = 1 ) in vec3 inNormal; + +// Output +layout ( location = 0 ) out vec3 outColor; + +void main() +{ + vec4 position = vec4( inPosition, 1.0 ); + + if( gl_VertexIndex == 1 ) + { + position.xyz += axisConfig.scale * inNormal; + } + + gl_Position = perframe.projectionMatrix * perframe.viewMatrix * position; + outColor = axisConfig.color; +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/packed_tangent.inc b/src/viewer/assets/shaders/packed_tangent.inc new file mode 100644 index 0000000..d35ee96 --- /dev/null +++ b/src/viewer/assets/shaders/packed_tangent.inc @@ -0,0 +1,66 @@ +// Copyright (c) 2026 CCP ehf. + +struct TangentSpace { + vec3 tangent; + vec3 binormal; + vec3 normal; +}; + +vec2 SinCosFloat( float angle ) +{ + return vec2( sin( angle ), cos( angle ) ); +} + +TangentSpace UnpackTangentSpaceLegacy( vec4 tangents ) +{ + vec4 angles = tangents * 6.28318530718 - 3.14159265359; + vec4 sc0, sc1; + sc0.xy = SinCosFloat( angles.x ); + sc0.zw = SinCosFloat( angles.y ); + sc1.xy = SinCosFloat( angles.z ); + sc1.zw = SinCosFloat( angles.w ); + + TangentSpace space; + + vec3 tangent = vec3( sc0.y * abs( sc0.z ), sc0.x * abs( sc0.z ), sc0.w ); + vec3 binormal = vec3( sc1.y * abs( sc1.z ), sc1.x * abs( sc1.z ), sc1.w ); + vec3 normal = -cross( tangent, binormal ); + if( angles.y > 0.0 && angles.w > 0.0 ) normal = -normal; + + return TangentSpace( tangent, binormal, normal ); +} + +TangentSpace UnpackTangentSpace( vec4 t ) { + // Heavily optimized shader code that constructs the TBN matrix. + + // Extract the xyz components and square them + float x = t.x; + float y = t.y; + float z = t.z; + float x2 = x * x; + float y2 = y * y; + float z2 = z * z; + + // Optimized fma() chain to reconstruct W = sqrt(1 - x2 - y2 - z2) + // Don't use the above x2, y2 and z2 values, to reduce pipeline dependencies. + float w2 = clamp( fma( z, -z, fma( y, -y, fma( x, -x, 1.0f ) ) ), 0.0f, 1.0f ); + float w = sqrt( w2 ); + + // Calculate shared values. + // These multiplications by 2.0f are free on some GPUs. + float xy = x * y * 2.0f; + float xz = x * z * 2.0f; + float yz = y * z * 2.0f; + float xw = x * w * 2.0f; + float yw = y * w * 2.0f; + float zw = z * w * 2.0f; + + // Compute the three vectors. + TangentSpace space; + + space.tangent = vec3( fma( -2.0f, y2, fma( -2.0f, z2, 1.0f ) ), + xy + zw, + xz - yw ); + space.binormal = vec3( - zw + xy, fma( -2.0f, x2, fma( -2.0f, z2, 1.0f ) ), + yz + xw ); + space.normal = vec3( + yw + xz, + yz - xw, fma( -2.0f, x2, fma( -2.0f, y2, 1.0f ) ) ) * t.w; // packed normal sign multiplication + + return space; +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/packedtangentaxis.frag b/src/viewer/assets/shaders/packedtangentaxis.frag new file mode 100644 index 0000000..9bcc4dc --- /dev/null +++ b/src/viewer/assets/shaders/packedtangentaxis.frag @@ -0,0 +1,14 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 + +// inputs +layout( location = 0 ) in vec3 inFragColor; + +// Outputs +layout( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4( inFragColor, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/packedtangentaxis.vert b/src/viewer/assets/shaders/packedtangentaxis.vert new file mode 100644 index 0000000..3c4d269 --- /dev/null +++ b/src/viewer/assets/shaders/packedtangentaxis.vert @@ -0,0 +1,56 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 +#pragma multi_usage inPackedTangents + +#include "packed_tangent.inc" + +// Constants +layout( binding = 0 ) uniform PerFrame +{ + mat4 projectionMatrix; + mat4 viewMatrix; +} perframe; + +layout( binding = 1 ) uniform AxisConfig +{ + vec3 color; + float scale; + int axisSelector; +} axisConfig; + +// Inputs +// inputs are per instance, not per vertex +layout( location = 0 ) in vec3 inPosition; +layout( location = 1 ) in vec4 inPackedTangents; + +// output +layout ( location = 0 ) out vec3 outColor; + + +void main() +{ + vec4 position = vec4( inPosition, 1.0 ); + if( gl_VertexIndex == 1 ) + { + TangentSpace space = UnpackTangentSpace( inPackedTangents ); + vec3 axis = vec3(0.0); + if( axisConfig.axisSelector == 0 ) + { + axis = space.normal; + } + else if( axisConfig.axisSelector == 1 ) + { + axis = space.tangent; + } + else if( axisConfig.axisSelector == 2 ) + { + axis = space.binormal; + } + position.xyz += axisConfig.scale * axis; + } + + gl_Position = perframe.projectionMatrix * perframe.viewMatrix * position; + + outColor = axisConfig.color; +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/packedtangentlegacyaxis.frag b/src/viewer/assets/shaders/packedtangentlegacyaxis.frag new file mode 100644 index 0000000..9bcc4dc --- /dev/null +++ b/src/viewer/assets/shaders/packedtangentlegacyaxis.frag @@ -0,0 +1,14 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 + +// inputs +layout( location = 0 ) in vec3 inFragColor; + +// Outputs +layout( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4( inFragColor, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/packedtangentlegacyaxis.vert b/src/viewer/assets/shaders/packedtangentlegacyaxis.vert new file mode 100644 index 0000000..f5080f5 --- /dev/null +++ b/src/viewer/assets/shaders/packedtangentlegacyaxis.vert @@ -0,0 +1,56 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 +#pragma multi_usage inPackedTangentsLegacy + +#include "packed_tangent.inc" + +// Constants +layout( binding = 0 ) uniform PerFrame +{ + mat4 projectionMatrix; + mat4 viewMatrix; +} perframe; + +layout( binding = 1 ) uniform AxisConfig +{ + vec3 color; + float scale; + int axisSelector; +} axisConfig; + +// Inputs +// inputs are per instance, not per vertex +layout( location = 0 ) in vec3 inPosition; +layout( location = 1 ) in vec4 inPackedTangentsLegacy; + +// output +layout ( location = 0 ) out vec3 outColor; + + +void main() +{ + vec4 position = vec4( inPosition, 1.0 ); + if( gl_VertexIndex == 1 ) + { + TangentSpace space = UnpackTangentSpaceLegacy( inPackedTangentsLegacy ); + vec3 axis = vec3(0.0); + if( axisConfig.axisSelector == 0 ) + { + axis = space.normal; + } + else if( axisConfig.axisSelector == 1 ) + { + axis = space.tangent; + } + else if( axisConfig.axisSelector == 2 ) + { + axis = space.binormal; + } + position.xyz += axisConfig.scale * axis; + } + + gl_Position = perframe.projectionMatrix * perframe.viewMatrix * position; + + outColor = axisConfig.color; +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/tangentaxis.frag b/src/viewer/assets/shaders/tangentaxis.frag new file mode 100644 index 0000000..9bcc4dc --- /dev/null +++ b/src/viewer/assets/shaders/tangentaxis.frag @@ -0,0 +1,14 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 + +// inputs +layout( location = 0 ) in vec3 inFragColor; + +// Outputs +layout( location = 0 ) out vec4 outFragColor; + +void main() +{ + outFragColor = vec4( inFragColor, 1.0 ); +} \ No newline at end of file diff --git a/src/viewer/assets/shaders/tangentaxis.vert b/src/viewer/assets/shaders/tangentaxis.vert new file mode 100644 index 0000000..7bef8ed --- /dev/null +++ b/src/viewer/assets/shaders/tangentaxis.vert @@ -0,0 +1,40 @@ +// Copyright (c) 2026 CCP ehf. + +#version 450 +#pragma multi_usage inTangent + +// Constants +layout( binding = 0 ) uniform PerFrame +{ + mat4 projectionMatrix; + mat4 viewMatrix; +} perframe; + + +layout( binding = 1 ) uniform AxisConfig +{ + vec3 color; + float scale; + int axisSelector; +} axisConfig; + +// Inputs +// inputs are per instance, not per vertex +layout( location = 0 ) in vec3 inPosition; +layout( location = 1 ) in vec3 inTangent; + +// Output +layout ( location = 0 ) out vec3 outColor; + +void main() +{ + vec4 position = vec4( inPosition, 1.0 ); + + if( gl_VertexIndex == 1 ) + { + position.xyz += axisConfig.scale * inTangent; + } + + gl_Position = perframe.projectionMatrix * perframe.viewMatrix * position; + outColor = axisConfig.color; +} \ No newline at end of file diff --git a/src/viewer/rendering/camera.cpp b/src/viewer/rendering/camera.cpp index 11d7467..76c65ec 100644 --- a/src/viewer/rendering/camera.cpp +++ b/src/viewer/rendering/camera.cpp @@ -101,12 +101,17 @@ void Camera::HandleMouseStateChanged( MouseState& mouseState ) Matrix Camera::GetProjection() const { - float distToModel = Length( m_at - m_boundingSphere.center ); + const float ABSOLUTE_MIN_NEAR_PLANE = 0.01f; + const float boundingDepth = m_boundingSphere.radius * 2.0f; + const float centerDepth = -TransformCoord( m_boundingSphere.center, GetView() ).z; + const float nearPlane = std::max( ABSOLUTE_MIN_NEAR_PLANE, centerDepth - boundingDepth ); + const float farPlane = std::max( nearPlane + ABSOLUTE_MIN_NEAR_PLANE, centerDepth + boundingDepth ); + return PerspectiveFovMatrix( m_fov, m_screenSize.x / m_screenSize.y, - std::max( 0.01f, std::max( m_boundingSphere.radius / 10000.0f, m_zoom - distToModel - m_boundingSphere.radius ) ), - distToModel + m_zoom + m_boundingSphere.radius ); + nearPlane, + farPlane ); } Matrix Camera::GetRotation() const diff --git a/src/viewer/rendering/models/axis.cpp b/src/viewer/rendering/models/axis.cpp index 2500042..5baabc3 100644 --- a/src/viewer/rendering/models/axis.cpp +++ b/src/viewer/rendering/models/axis.cpp @@ -24,14 +24,66 @@ const std::array AXIS_MESH = { AxisVertex{ { 0.0f, 0.0f, 1.0f }, BLUE }, }; -PrimitiveRenderable Create( std::shared_ptr renderer ) +PrimitiveRenderable CreateOrientationPrimitive( std::shared_ptr renderer ) { - auto effect = PrimitiveEffects::CreateAxisEffect( renderer ); - - auto model = PrimitiveRenderable( renderer, std::move( effect ) ); + auto model = PrimitiveRenderable( renderer, PrimitiveEffects::CreateOrientationEffect( renderer ) ); model.SetBufferData( reinterpret_cast( AXIS_MESH.data() ), (uint32_t)AXIS_MESH.size() * sizeof( AxisVertex ), sizeof( AxisVertex ) ); return model; } +PrimitiveRenderable CreateNormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreateUnpackedAxisEffect( renderer, cmf::Usage::Normal, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreateTangent( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreateUnpackedAxisEffect( renderer, cmf::Usage::Tangent, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreateBinormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreateUnpackedAxisEffect( renderer, cmf::Usage::Binormal, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreatePackedNormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreatePackedAxisEffect( renderer, cmf::Usage::Normal, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreatePackedTangent( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreatePackedAxisEffect( renderer, cmf::Usage::Tangent, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreatePackedBinormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreatePackedAxisEffect( renderer, cmf::Usage::Binormal, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreatePackedLegacyNormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreatePackedLegacyAxisEffect( renderer, cmf::Usage::Normal, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreatePackedLegacyTangent( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreatePackedLegacyAxisEffect( renderer, cmf::Usage::Tangent, usageIndex, mesh ) ); +} + +PrimitiveRenderable CreatePackedLegacyBinormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ) +{ + // the bufferdata is the output of the geoprepass and will be handled later + return PrimitiveRenderable( renderer, PrimitiveEffects::CreatePackedLegacyAxisEffect( renderer, cmf::Usage::Binormal, usageIndex, mesh ) ); +} + } diff --git a/src/viewer/rendering/models/axis.h b/src/viewer/rendering/models/axis.h index 7ab99d2..287bb56 100644 --- a/src/viewer/rendering/models/axis.h +++ b/src/viewer/rendering/models/axis.h @@ -9,5 +9,16 @@ namespace Axis { -PrimitiveRenderable Create( std::shared_ptr renderer ); + +PrimitiveRenderable CreateOrientationPrimitive( std::shared_ptr renderer ); + +PrimitiveRenderable CreateNormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreateTangent( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreateBinormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreatePackedNormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreatePackedTangent( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreatePackedBinormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreatePackedLegacyNormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreatePackedLegacyTangent( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); +PrimitiveRenderable CreatePackedLegacyBinormal( std::shared_ptr renderer, const cmf::Mesh& mesh, const uint32_t usageIndex ); }; \ No newline at end of file diff --git a/src/viewer/rendering/models/bones.cpp b/src/viewer/rendering/models/bones.cpp index 6699263..25da490 100644 --- a/src/viewer/rendering/models/bones.cpp +++ b/src/viewer/rendering/models/bones.cpp @@ -8,9 +8,9 @@ namespace Bones PrimitiveRenderable CreateJoint( std::shared_ptr renderer, const cmf::Skeleton& skeleton, Vector3 color, Vector3 selectedColor ) { - GraphicsEffect::Config config{}; + GraphicsEffectTypes::Config config{}; config.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - config.stride = sizeof( Vector4 ); + auto colorInfo = PrimitiveEffects::ColorInfo{ Vector4( color, 1.0f ), @@ -32,17 +32,9 @@ PrimitiveRenderable CreateJoint( std::shared_ptr renderer, const PrimitiveRenderable CreateBone( std::shared_ptr renderer, const cmf::Skeleton& skeleton, Vector3 color, Vector3 selectedColor ) { - GraphicsEffect::Config config{}; - config.availableVertexElements = { - { cmf::Usage::Position, - 0, - cmf::ElementType::Float32, - 4, - 0 } - }; + GraphicsEffectTypes::Config config{}; config.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; - config.stride = sizeof( Vector4 ); config.lineWidth = 1.5f; auto colorInfo = PrimitiveEffects::ColorInfo{ diff --git a/src/viewer/rendering/models/boundingBox.cpp b/src/viewer/rendering/models/boundingBox.cpp index a1ca164..60b21ad 100644 --- a/src/viewer/rendering/models/boundingBox.cpp +++ b/src/viewer/rendering/models/boundingBox.cpp @@ -21,7 +21,7 @@ const std::array BOX_INDICES = { 0, 1, 0, 2, 0, 4, 3, 1, 3, 2, 3, PrimitiveRenderable Create( std::shared_ptr renderer, Vector3 color ) { - GraphicsEffect::Config config{}; + GraphicsEffectTypes::Config config{}; config.lineWidth = 2.0f; config.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; diff --git a/src/viewer/rendering/models/primitiveEffects.cpp b/src/viewer/rendering/models/primitiveEffects.cpp index f138203..146e900 100644 --- a/src/viewer/rendering/models/primitiveEffects.cpp +++ b/src/viewer/rendering/models/primitiveEffects.cpp @@ -1,17 +1,92 @@ // Copyright © 2026 CCP ehf. #include "primitiveEffects.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -GraphicsEffect PrimitiveEffects::CreateFlatColorEffect( std::shared_ptr renderer, ColorInfo colorInfo, GraphicsEffect::Config config, std::vector vertexToBoneMapping ) +namespace { - config.availableVertexElements = { - { cmf::Usage::Position, - 0, - cmf::ElementType::Float32, - 4, - 0 } +constexpr float AXIS_LENGTH_SCALE = 0.005f; +constexpr float AXIS_LENGTH_MIN_SIZE = 0.001f; + +GraphicsEffect CreateAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ) +{ + auto effectConfig = GraphicsEffectTypes::Config(); + PrimitiveEffects::AxisConfig axisConfig{}; + auto inputDeclaration = GraphicsEffectTypes::ShaderInputDeclaration(); + inputDeclaration.vertexInputRate = VK_VERTEX_INPUT_RATE_INSTANCE; + + for( const auto& element : mesh.decl ) + { + inputDeclaration.stride += cmf::GetVertexElementSize( element ); + if( element.usage == usage || element.usage == cmf::Usage::PackedTangent || element.usage == cmf::Usage::PackedTangentLegacy ) + { + if( element.usageIndex == usageIndex ) + { + inputDeclaration.vertexDeclarations.push_back( element ); + } + } + else + { + inputDeclaration.vertexDeclarations.push_back( element ); + } + } + effectConfig.inputDeclaration = inputDeclaration; + effectConfig.lineWidth = 2.0f; + effectConfig.topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + + assert( usage == cmf::Usage::Normal || usage == cmf::Usage::Tangent || usage == cmf::Usage::Binormal ); + switch( usage ) + { + case cmf::Usage::Normal: + axisConfig.color = Vector3( 0, 1, 0 ); + axisConfig.axisIndex = 0; + break; + case cmf::Usage::Tangent: + axisConfig.color = Vector3( 1, 0, 0 ); + axisConfig.axisIndex = 1; + break; + case cmf::Usage::Binormal: + axisConfig.color = Vector3( 0, 0, 1 ); + axisConfig.axisIndex = 2; + break; + default: + throw std::runtime_error( "Unsupported usage for CreateUnpackedAxisEffect" ); + } + + axisConfig.scale = std::max( AXIS_LENGTH_MIN_SIZE, CcpMath::Sphere( mesh.bounds ).radius * AXIS_LENGTH_SCALE ); + + auto effect = GraphicsEffect( renderer ); + effect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); + // this is what axis to draw, the shader will decode the packed tangent and use the sign to determine if it's normal, tangent or bitangent + effect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 1, axisConfig ); + effect.SetConfig( effectConfig ); + return effect; +} +} + +GraphicsEffect PrimitiveEffects::CreateFlatColorEffect( std::shared_ptr renderer, ColorInfo colorInfo, GraphicsEffectTypes::Config config, std::vector vertexToBoneMapping ) +{ + config.inputDeclaration.stride = sizeof( Vector4 ); + config.inputDeclaration.vertexDeclarations = { + cmf::VertexElement{ cmf::Usage::Position, 0, cmf::ElementType::Float32, 4, 0 } }; - config.stride = sizeof( Vector4 ); + auto effect = GraphicsEffect( renderer ); effect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); effect.RegisterStorageBuffer( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 1, 0xFF ); // bone transforms @@ -23,10 +98,10 @@ GraphicsEffect PrimitiveEffects::CreateFlatColorEffect( std::shared_ptr renderer ) +GraphicsEffect PrimitiveEffects::CreateOrientationEffect( std::shared_ptr renderer ) { - auto config = GraphicsEffect::Config(); - config.availableVertexElements = { + auto config = GraphicsEffectTypes::Config(); + config.inputDeclaration.vertexDeclarations = { { cmf::Usage::Position, 0, cmf::ElementType::Float32, @@ -38,13 +113,50 @@ GraphicsEffect PrimitiveEffects::CreateAxisEffect( std::shared_ptr( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); effect.RegisterStorageBuffer( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 1, 0xFF ); effect.SetConfig( config ); effect.SetShaderName( "orientationgizmo" ); return effect; +} + +GraphicsEffect PrimitiveEffects::CreateUnpackedAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ) +{ + std::string shaderName; + switch( usage ) + { + case cmf::Usage::Normal: + shaderName = "normalaxis"; + break; + case cmf::Usage::Tangent: + shaderName = "tangentaxis"; + break; + case cmf::Usage::Binormal: + shaderName = "binormalaxis"; + break; + default: + throw std::runtime_error( "Unsupported usage for CreateUnpackedAxisEffect" ); + } + auto effect = CreateAxisEffect( renderer, usage, usageIndex, mesh ); + effect.SetShaderName( shaderName ); + return effect; +} + +GraphicsEffect PrimitiveEffects::CreatePackedAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ) +{ + auto effect = CreateAxisEffect( renderer, usage, usageIndex, mesh ); + effect.SetShaderName( "packedtangentaxis" ); + return effect; +} + +GraphicsEffect PrimitiveEffects::CreatePackedLegacyAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ) +{ + auto effect = CreateAxisEffect( renderer, usage, usageIndex, mesh ); + effect.SetShaderName( "packedtangentlegacyaxis" ); + return effect; } \ No newline at end of file diff --git a/src/viewer/rendering/models/primitiveEffects.h b/src/viewer/rendering/models/primitiveEffects.h index 4ad6ac1..5510311 100644 --- a/src/viewer/rendering/models/primitiveEffects.h +++ b/src/viewer/rendering/models/primitiveEffects.h @@ -3,6 +3,7 @@ #pragma once #include "../vulkan/graphicseffect.h" +#include "cmf/cmf.h" namespace PrimitiveEffects { @@ -15,12 +16,23 @@ struct VertexUBO Vector4 boneInfo; // x = boneCount, y = how many bones transforms are used per instance }; +struct AxisConfig +{ + Vector3 color; + float scale; + uint32_t axisIndex; // 0 for normal, 1 for tangent, 2 for bitangent +}; + struct ColorInfo { Vector4 unselected; Vector4 selected; }; -GraphicsEffect CreateFlatColorEffect( std::shared_ptr renderer, ColorInfo colorInfo, GraphicsEffect::Config config, std::vector vertexToBoneMapping ); -GraphicsEffect CreateAxisEffect( std::shared_ptr renderer ); +GraphicsEffect CreateFlatColorEffect( std::shared_ptr renderer, ColorInfo colorInfo, GraphicsEffectTypes::Config config, std::vector vertexToBoneMapping ); +GraphicsEffect CreateOrientationEffect( std::shared_ptr renderer ); +GraphicsEffect CreateUnpackedAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ); +GraphicsEffect CreatePackedAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ); +GraphicsEffect CreatePackedLegacyAxisEffect( std::shared_ptr renderer, const cmf::Usage usage, uint32_t usageIndex, const cmf::Mesh& mesh ); + }; \ No newline at end of file diff --git a/src/viewer/rendering/orientationGizmoRenderer.cpp b/src/viewer/rendering/orientationGizmoRenderer.cpp index 0a89d77..d5ddda0 100644 --- a/src/viewer/rendering/orientationGizmoRenderer.cpp +++ b/src/viewer/rendering/orientationGizmoRenderer.cpp @@ -8,7 +8,7 @@ OrientationGizmoRenderer::OrientationGizmoRenderer( std::shared_ptr renderer ) : m_renderer( renderer ), m_graphicsCommandBuffer( renderer.get() ), - m_axis( Axis::Create( renderer ) ) + m_axis( Axis::CreateOrientationPrimitive( renderer ) ) { m_graphicsCommandBuffer.SetClearDepth( 1.0f ); } diff --git a/src/viewer/rendering/renderable/animationState.cpp b/src/viewer/rendering/renderable/animationState.cpp index 597833b..70bc538 100644 --- a/src/viewer/rendering/renderable/animationState.cpp +++ b/src/viewer/rendering/renderable/animationState.cpp @@ -13,7 +13,7 @@ AnimationState::AnimationState( uint8_t m_skeletonIndex, const cmf::Skeleton& sk m_skeletonIndex( m_skeletonIndex ), m_boneRenderable( std::make_unique( Bones::CreateBone( renderer, skeleton ) ) ), m_jointRenderable( std::make_unique( Bones::CreateJoint( renderer, skeleton ) ) ), - m_axisRenderable( std::make_unique( Axis::Create( renderer ) ) ), + m_axisRenderable( std::make_unique( Axis::CreateOrientationPrimitive( renderer ) ) ), m_skeleton( skeleton ) { ClearBoneMapping(); diff --git a/src/viewer/rendering/renderable/geometryprepass.cpp b/src/viewer/rendering/renderable/geometryprepass.cpp index 043b1c9..6bf41cb 100644 --- a/src/viewer/rendering/renderable/geometryprepass.cpp +++ b/src/viewer/rendering/renderable/geometryprepass.cpp @@ -30,23 +30,6 @@ GeometryPrePass::~GeometryPrePass() void GeometryPrePass::Initialize( AppState& appState ) { - auto firstMorphState = appState.modelState.morphTargetEnabled.size(); - - // go through the morph targets so we can register to the morph weight and display flags - for( size_t i = 0; i < m_cmfMesh.morphTargets.targets.size(); i++ ) - { - auto index = appState.modelState.morphTargetEnabled.AddState(); - appState.modelState.morphTargetWeight.AddState(); - - appState.modelState.morphTargetWeight[index].RegisterCallback( [this, index, firstMorphState]( float weight, AppState& ) { - SetMorphWeight( index - firstMorphState, weight ); - } ); - appState.modelState.morphTargetEnabled[index].RegisterCallback( [this, index, firstMorphState]( bool enabled, AppState& appState ) { - float weight = enabled ? appState.modelState.morphTargetWeight[index].GetValue() : 0.0f; - SetMorphWeight( index - firstMorphState, weight ); - } ); - } - m_weights.resize( m_cmfMesh.morphTargets.targets.size(), 0.0f ); const auto boneElement = std::find_if( m_cmfMesh.decl.begin(), m_cmfMesh.decl.end(), []( const cmf::VertexElement& elem ) { return elem.usage == cmf::Usage::BoneIndices; diff --git a/src/viewer/rendering/renderable/mesh.cpp b/src/viewer/rendering/renderable/mesh.cpp index 2ee5d53..005b448 100644 --- a/src/viewer/rendering/renderable/mesh.cpp +++ b/src/viewer/rendering/renderable/mesh.cpp @@ -3,6 +3,7 @@ #include "mesh.h" #include "../models/boundingBox.h" +#include "../models/axis.h" #include "../renderer.h" #include "../vulkan/vulkanerrors.h" #include "../models/primitiveEffects.h" @@ -17,11 +18,8 @@ MeshRenderable::MeshRenderable( std::shared_ptr data, const cmf::Mes m_audioOcclusionRenderable( renderer, GetAudioOcclusionEffect( renderer, cmfMesh ) ), m_boundingBox( BoundingBox::Create( renderer, Vector3( 0.5, 0.5, 0.0 ) ) ) { - for( const auto& vertexElement : m_cmfMesh.decl ) - { - m_availableVertexElements.push_back( vertexElement ); - } m_boundingSphere = CcpMath::Sphere( m_cmfMesh.bounds ); + m_boundingBoxTransform = ScalingMatrix( m_cmfMesh.bounds.Size() ) * TranslationMatrix( m_cmfMesh.bounds.Center() ); m_stride = m_cmfMesh.lods[0].vb.stride; @@ -44,53 +42,91 @@ MeshRenderable::MeshRenderable( std::shared_ptr data, const cmf::Mes void MeshRenderable::Initialize( AppState& appState ) { - appState.modelState.polygonMode.RegisterCallback( [this]( VkPolygonMode mode, AppState& appState ) { - SetRenderingMode( appState.modelState.visualizationShader.GetValue(), mode ); - } ); - - appState.modelState.visualizationShader.RegisterCallback( [this]( std::string shaderName, AppState& appState ) { - SetRenderingMode( shaderName, appState.modelState.polygonMode.GetValue() ); - } ); + m_prepass.Initialize( appState ); - // Register mesh visibility state - size_t stateIndex = appState.modelState.meshVisibilityStates.AddState(); - appState.modelState.meshVisibilityStates[stateIndex].RegisterCallback( [this]( bool visible, AppState& appState ) { - m_display = visible; + appState.modelState.polygonMode.RegisterCallback( [this]( VkPolygonMode mode, AppState& appState ) { + auto [name, declaration] = appState.modelState.activeShader.GetValue(); + SetRenderingMode( name, declaration, mode ); } ); - stateIndex = appState.modelState.meshWireframeOverlay.AddState(); - appState.modelState.meshWireframeOverlay[stateIndex].RegisterCallback( [this]( bool enabled, AppState& appState ) { - m_wireframe = enabled; - } ); + appState.modelState.activeShader.RegisterCallback( [this]( std::pair shaderInputDeclaration, AppState& appState ) { + auto [name, declaration] = shaderInputDeclaration; - stateIndex = appState.modelState.audioOcclusionMesh.AddState(); - appState.modelState.audioOcclusionMesh[stateIndex].RegisterCallback( [this]( bool enabled, AppState& appState ) { - m_audioOcclusion = enabled; + SetRenderingMode( name, declaration, appState.modelState.polygonMode.GetValue() ); } ); - stateIndex = appState.modelState.meshBoundingBox.AddState(); - appState.modelState.meshBoundingBox[stateIndex].RegisterCallback( [this]( bool enabled, AppState& appState ) { - m_showBoundingBox = enabled; - } ); + m_meshIndex = appState.modelState.meshes.AddState( [this]( MeshState& meshState ) { + meshState.display.RegisterCallback( [this]( bool visible, AppState& ) { + m_display = visible; + } ); + meshState.wireframeOverlay.RegisterCallback( [this]( bool enabled, AppState& ) { + m_wireframe = enabled; + } ); + meshState.audioOcclusionMesh.RegisterCallback( [this]( bool enabled, AppState& ) { + m_audioOcclusion = enabled; + } ); + meshState.renderBoundingBox.RegisterCallback( [this]( bool enabled, AppState& ) { + m_showBoundingBox = enabled; + } ); + meshState.activeLod.RegisterCallback( [this]( uint32_t lodIndex, AppState& appState ) { + SetLod( lodIndex ); + } ); + SetLod( meshState.activeLod.GetValue() ); + for( size_t i = 0; i < m_cmfMesh.morphTargets.targets.size(); ++i ) + { + meshState.morphs.AddState(); + meshState.morphs[i].RegisterCallback( [this, i]( std::pair morph, AppState& ) { + m_prepass.SetMorphWeight( static_cast( i ), morph.second ? morph.first : 0.0f ); + } ); + } + for( const auto& vertexElement : m_cmfMesh.decl ) + { + m_availableVertexElements.push_back( vertexElement ); + if( vertexElement.usage == cmf::Usage::Normal ) + { + meshState.showVertexNormals.AddState( { vertexElement.usageIndex, false } ); + m_normalAxisRenderables.push_back( Axis::CreateNormal( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + } + else if( vertexElement.usage == cmf::Usage::Tangent ) + { + meshState.showVertexTangents.AddState( { vertexElement.usageIndex, false } ); + m_tangentAxisRenderables.push_back( Axis::CreateTangent( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + } + else if( vertexElement.usage == cmf::Usage::Binormal ) + { + meshState.showVertexBinormals.AddState( { vertexElement.usageIndex, false } ); + m_binormalAxisRenderables.push_back( Axis::CreateBinormal( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + } + else if( vertexElement.usage == cmf::Usage::PackedTangent ) + { + meshState.showVertexNormals.AddState( { vertexElement.usageIndex, false } ); + meshState.showVertexTangents.AddState( { vertexElement.usageIndex, false } ); + meshState.showVertexBinormals.AddState( { vertexElement.usageIndex, false } ); - m_meshScreenSizeStateIndex = appState.modelState.meshScreenSize.AddState(); - m_activeLodStateIndex = appState.modelState.activeLod.AddState(); + m_normalAxisRenderables.push_back( Axis::CreatePackedNormal( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + m_tangentAxisRenderables.push_back( Axis::CreatePackedTangent( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + m_binormalAxisRenderables.push_back( Axis::CreatePackedBinormal( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + } + else if( vertexElement.usage == cmf::Usage::PackedTangentLegacy ) + { + meshState.showVertexNormals.AddState( { vertexElement.usageIndex, false } ); + meshState.showVertexTangents.AddState( { vertexElement.usageIndex, false } ); + meshState.showVertexBinormals.AddState( { vertexElement.usageIndex, false } ); - appState.modelState.activeLod[m_activeLodStateIndex].RegisterCallback( [this]( uint32_t lodIndex, AppState& appState ) { - SetLod( lodIndex ); + m_normalAxisRenderables.push_back( Axis::CreatePackedLegacyNormal( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + m_tangentAxisRenderables.push_back( Axis::CreatePackedLegacyTangent( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + m_binormalAxisRenderables.push_back( Axis::CreatePackedLegacyBinormal( m_renderer, m_cmfMesh, vertexElement.usageIndex ) ); + } + } } ); appState.modelState.selectedLod.RegisterCallback( [this]( int32_t lodIndex, AppState& appState ) { if( lodIndex != -1 ) { - appState.modelState.activeLod[m_activeLodStateIndex].SetValue( lodIndex ); + appState.modelState.meshes[m_meshIndex].GetValue().activeLod.SetValue( lodIndex ); } } ); - m_prepass.Initialize( appState ); - - SetLod( appState.modelState.activeLod[m_activeLodStateIndex].GetValue() ); - m_boundingBox.Initialize(); if( !m_cmfMesh.audioOcclusionMesh.vertices.empty() && !m_cmfMesh.audioOcclusionMesh.indices.empty() ) @@ -105,6 +141,19 @@ void MeshRenderable::Initialize( AppState& appState ) sizeof( uint16_t ) ); m_audioOcclusionRenderable.Initialize(); } + + for( auto& normalAxisRenderable : m_normalAxisRenderables ) + { + normalAxisRenderable.Initialize(); + } + for( auto& tangentAxisRenderable : m_tangentAxisRenderables ) + { + tangentAxisRenderable.Initialize(); + } + for( auto& binormalAxisRenderable : m_binormalAxisRenderables ) + { + binormalAxisRenderable.Initialize(); + } m_initialized = true; } @@ -120,7 +169,8 @@ void MeshRenderable::UpdateMeshCurves( float animationTime, const cmf::Animation // the line above is the one that updates the morph target weight in the prepass, // but we also need to update the app state so that the UI reflects the current weight // if we would do it the other way, then prepass would get the update with one frame delay - appState.modelState.morphTargetWeight[morphIndex].SetValueNoCallback( weight ); + auto& morphState = appState.modelState.meshes[m_meshIndex].GetValue().morphs[morphIndex]; + morphState.SetValueNoCallback( { weight, morphState.GetValue().second } ); } } } @@ -180,7 +230,8 @@ void MeshRenderable::Update( AppState& appState, const Camera& camera ) { // update the lod based on the camera and bounding sphere of the mesh auto sizeOnScreen = camera.GetSizeOnScreen( m_boundingSphere ); - appState.modelState.meshScreenSize[m_meshScreenSizeStateIndex].SetValueNoCallback( sizeOnScreen ); + auto& meshState = appState.modelState.meshes[m_meshIndex].GetValue(); + meshState.meshScreenSize.SetValueNoCallback( sizeOnScreen ); // find the closest lod that has the size on screen greater than the threshold uint32_t lodLevel = 0; @@ -194,17 +245,14 @@ void MeshRenderable::Update( AppState& appState, const Camera& camera ) if( m_currentLod != lodLevel ) { SetLod( lodLevel ); - if( m_activeLodStateIndex < appState.modelState.activeLod.size() ) - { - appState.modelState.activeLod[m_activeLodStateIndex].SetValueNoCallback( lodLevel ); - } + meshState.activeLod.SetValueNoCallback( lodLevel ); } } } void MeshRenderable::Render( GraphicsCommandBuffer& commandBuffer, const AppState& appState, const Camera& camera ) { - auto viewProj = VertexUboData{ camera.GetProjection(), camera.GetView() }; + auto viewProj = GraphicsEffect::VertexUboData{ camera.GetProjection(), camera.GetView() }; auto vertexBuffer = m_prepass.GetVertexBuffer(); const Buffer indexBuffer = m_prepass.GetIndexBuffer(); @@ -248,6 +296,7 @@ void MeshRenderable::Render( GraphicsCommandBuffer& commandBuffer, const AppStat void MeshRenderable::RenderDebug( GraphicsCommandBuffer& commandBuffer, const AppState& appState, const Camera& camera ) { + auto viewProj = GraphicsEffect::VertexUboData{ camera.GetProjection(), camera.GetView() }; if( m_showBoundingBox ) { auto vertexData = PrimitiveEffects::VertexUBO{ camera.GetProjection(), camera.GetView(), m_boundingBoxTransform, Vector4() }; @@ -257,10 +306,46 @@ void MeshRenderable::RenderDebug( GraphicsCommandBuffer& commandBuffer, const Ap if( m_audioOcclusion && !m_cmfMesh.audioOcclusionMesh.vertices.empty() && !m_cmfMesh.audioOcclusionMesh.indices.empty() ) { - auto viewProj = VertexUboData{ camera.GetProjection(), camera.GetView() }; m_audioOcclusionRenderable.SetUniformData( 0, viewProj ); m_audioOcclusionRenderable.Render( commandBuffer ); } + const auto& meshState = appState.modelState.meshes[m_meshIndex].GetValue(); + + auto streamElementCount = cmf::GetStreamElementCount( m_cmfMesh.lods[m_currentLod].vb ); + const auto* buffer = &( m_prepass.GetVertexBuffer() ); + uint32_t index = 0; + for( const auto& normalState : meshState.showVertexNormals ) + { + if( normalState.GetValue().second ) + { + // since the state and renderables are created in the same order based on the vertex elements, + // we can use the index to get the corresponding renderable for the normal/tangent/binormal we want to render + m_normalAxisRenderables[index].SetUniformData( 0, viewProj ); + m_normalAxisRenderables[index].Render( commandBuffer, buffer, nullptr, 2, streamElementCount ); + } + ++index; + } + index = 0; + for( const auto& tangentState : meshState.showVertexTangents ) + { + if( tangentState.GetValue().second ) + { + m_tangentAxisRenderables[index].SetUniformData( 0, viewProj ); + m_tangentAxisRenderables[index].Render( commandBuffer, buffer, nullptr, 2, streamElementCount ); + } + ++index; + } + + index = 0; + for( const auto& binormalState : meshState.showVertexBinormals ) + { + if( binormalState.GetValue().second ) + { + m_binormalAxisRenderables[index].SetUniformData( 0, viewProj ); + m_binormalAxisRenderables[index].Render( commandBuffer, buffer, nullptr, 2, streamElementCount ); + } + ++index; + } } void MeshRenderable::PrepareMesh( ComputeCommandBuffer& commandBuffer ) @@ -291,7 +376,7 @@ void MeshRenderable::DrawIndexed( GraphicsCommandBuffer& commandBuffer ) } } -VkResult MeshRenderable::SetRenderingMode( std::string shaderName, VkPolygonMode polygonMode ) +VkResult MeshRenderable::SetRenderingMode( std::string shaderName, GraphicsEffectTypes::ShaderInputDeclaration shaderInputDeclaration, VkPolygonMode polygonMode ) { auto logicalDevice = m_renderer->GetDevice()->GetLogicalDevice(); @@ -300,24 +385,24 @@ VkResult MeshRenderable::SetRenderingMode( std::string shaderName, VkPolygonMode CR_RETURN( vkDeviceWaitIdle( logicalDevice ) ); - auto config = GraphicsEffect::Config(); + auto config = GraphicsEffectTypes::Config(); config.topology = m_topology; config.polygonMode = polygonMode; config.cullMode = ( polygonMode == VK_POLYGON_MODE_FILL ) ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE; - config.availableVertexElements = m_availableVertexElements; - config.stride = m_stride; + config.inputDeclaration.vertexDeclarations = shaderInputDeclaration.vertexDeclarations; + config.inputDeclaration.stride = m_stride; m_modelEffect.SetShaderName( m_shaderName ); m_modelEffect.SetConfig( config ); if( !m_modelEffect.IsInitialized() ) { - m_modelEffect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); + m_modelEffect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); m_modelEffect.Initialize(); } if( !m_wireframeEffect.IsInitialized() ) { - auto wireframeConfig = GraphicsEffect::Config(); + auto wireframeConfig = GraphicsEffectTypes::Config(); wireframeConfig.topology = m_topology; // use fill mode even though we are rendering wireframe // The reason is when we rasterize the lines we will get issues with the depth buffer where some lines @@ -327,12 +412,16 @@ VkResult MeshRenderable::SetRenderingMode( std::string shaderName, VkPolygonMode wireframeConfig.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; wireframeConfig.cullMode = VK_CULL_MODE_NONE; wireframeConfig.blend = true; - wireframeConfig.availableVertexElements = m_availableVertexElements; - wireframeConfig.stride = m_stride; + wireframeConfig.inputDeclaration.stride = m_stride; + wireframeConfig.inputDeclaration.vertexDeclarations = { { cmf::Usage::Position, + 0, + cmf::ElementType::Float32, + 3, + 0 } }; m_wireframeEffect.SetShaderName( "wireframeoverlay" ); m_wireframeEffect.SetConfig( wireframeConfig ); - m_wireframeEffect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); + m_wireframeEffect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); m_wireframeEffect.Initialize(); } return VK_SUCCESS; @@ -340,13 +429,13 @@ VkResult MeshRenderable::SetRenderingMode( std::string shaderName, VkPolygonMode GraphicsEffect MeshRenderable::GetAudioOcclusionEffect( std::shared_ptr renderer, const cmf::Mesh& cmfMesh ) { - auto config = GraphicsEffect::Config(); + auto config = GraphicsEffectTypes::Config(); config.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; config.polygonMode = VK_POLYGON_MODE_FILL; config.cullMode = VK_CULL_MODE_NONE; config.blend = false; - config.stride = sizeof( Vector3 ); - config.availableVertexElements = { + config.inputDeclaration.stride = sizeof( Vector3 ); + config.inputDeclaration.vertexDeclarations = { cmf::VertexElement{ cmf::Usage::Position, 0, @@ -358,7 +447,7 @@ GraphicsEffect MeshRenderable::GetAudioOcclusionEffect( std::shared_ptr( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); + effect.RegisterUniformData( VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT, 0 ); return effect; } diff --git a/src/viewer/rendering/renderable/mesh.h b/src/viewer/rendering/renderable/mesh.h index 5e3049e..4425111 100644 --- a/src/viewer/rendering/renderable/mesh.h +++ b/src/viewer/rendering/renderable/mesh.h @@ -22,7 +22,7 @@ class MeshRenderable void Render( GraphicsCommandBuffer& commandBuffer, const AppState& appState, const Camera& camera ); void RenderDebug( GraphicsCommandBuffer& commandBuffer, const AppState& appState, const Camera& camera ); void PrepareMesh( ComputeCommandBuffer& computeCommandBuffer ); - VkResult SetRenderingMode( std::string shaderName, VkPolygonMode polygonMode ); + VkResult SetRenderingMode( std::string shaderName, GraphicsEffectTypes::ShaderInputDeclaration shaderInputDeclaration, VkPolygonMode polygonMode ); void UpdateMeshCurves( float animationTime, const cmf::Animation* animation, AppState& appState ); void SetSkeletonPose( const std::array& boneTransforms ); @@ -40,19 +40,12 @@ class MeshRenderable }; GraphicsEffect GetAudioOcclusionEffect( std::shared_ptr renderer, const cmf::Mesh& cmfMesh ); - struct VertexUboData - { - Matrix proj; - Matrix view; - }; - std::vector m_availableVertexElements; std::shared_ptr m_renderer; uint32_t m_stride{ 0 }; uint32_t m_currentLod{ std::numeric_limits::max() }; - size_t m_activeLodStateIndex{ 0 }; - size_t m_meshScreenSizeStateIndex{ 0 }; + size_t m_meshIndex{ 0 }; CcpMath::Sphere m_boundingSphere{}; VkPolygonMode m_polygonMode{ VK_POLYGON_MODE_FILL }; VkPrimitiveTopology m_topology{ VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST }; @@ -60,6 +53,8 @@ class MeshRenderable bool m_display{ true }; bool m_wireframe{ false }; + bool m_audioOcclusion{ false }; + bool m_showBoundingBox{ false }; bool m_initialized{ false }; // effects @@ -77,11 +72,13 @@ class MeshRenderable // debug renderables // bounding box - bool m_showBoundingBox{ false }; PrimitiveRenderable m_boundingBox; Matrix m_boundingBoxTransform{}; + std::vector m_normalAxisRenderables; + std::vector m_tangentAxisRenderables; + std::vector m_binormalAxisRenderables; + // audio occlusion - bool m_audioOcclusion{ false }; PrimitiveRenderable m_audioOcclusionRenderable; }; \ No newline at end of file diff --git a/src/viewer/rendering/renderable/model.cpp b/src/viewer/rendering/renderable/model.cpp index f950f1e..01ef9c7 100644 --- a/src/viewer/rendering/renderable/model.cpp +++ b/src/viewer/rendering/renderable/model.cpp @@ -91,6 +91,8 @@ VkResult ModelRenderable::Initialize( AppState& appState ) UpdateAnimation( animationTime, appState ); } ); + appState.modelState.activeAnimationOwner.SetValue( m_cmfContent ); + for( auto& animationState : m_animationStates ) { diff --git a/src/viewer/rendering/renderable/primitive.cpp b/src/viewer/rendering/renderable/primitive.cpp index 431ce74..83235e6 100644 --- a/src/viewer/rendering/renderable/primitive.cpp +++ b/src/viewer/rendering/renderable/primitive.cpp @@ -52,40 +52,50 @@ VkResult PrimitiveRenderable::Initialize() { VkCommandBuffer copyCmd; - RETURN_ERROR( m_renderer->CreateCopyCommandBuffer( ©Cmd ) ); - - m_vertexBuffer = BufferBuilder::Build( m_renderer.get(), m_data, m_vertexBufferSize, BufferType::Vertex, m_vertexStride ); - m_vertexBuffer->CopyFromStaging( copyCmd ); - m_elements = m_vertexBufferSize / m_vertexStride; - if( m_indexData ) + if( m_data != nullptr ) { - m_indexBuffer = BufferBuilder::Build( m_renderer.get(), m_indexData, m_indexBufferSize, BufferType::Index, m_indexStride ); - m_indexBuffer->CopyFromStaging( copyCmd ); - m_elements = m_indexBufferSize / m_indexStride; - } + RETURN_ERROR( m_renderer->CreateCopyCommandBuffer( ©Cmd ) ); - RETURN_ERROR( m_renderer->EndCopyCommandBuffer( copyCmd ) ); - m_vertexBuffer->ReleaseStaging( m_renderer.get() ); - if( m_indexData ) - { - m_indexBuffer->ReleaseStaging( m_renderer.get() ); + m_vertexBuffer = BufferBuilder::Build( m_renderer.get(), m_data, m_vertexBufferSize, BufferType::Vertex, m_vertexStride ); + m_vertexBuffer->CopyFromStaging( copyCmd ); + m_elements = m_vertexBufferSize / m_vertexStride; + if( m_indexData ) + { + m_indexBuffer = BufferBuilder::Build( m_renderer.get(), m_indexData, m_indexBufferSize, BufferType::Index, m_indexStride ); + m_indexBuffer->CopyFromStaging( copyCmd ); + m_elements = m_indexBufferSize / m_indexStride; + } + + RETURN_ERROR( m_renderer->EndCopyCommandBuffer( copyCmd ) ); + m_vertexBuffer->ReleaseStaging( m_renderer.get() ); + if( m_indexData ) + { + m_indexBuffer->ReleaseStaging( m_renderer.get() ); + } } RETURN_ERROR( m_effect.Initialize() ); return VK_SUCCESS; } -void PrimitiveRenderable::Render( GraphicsCommandBuffer& commandBuffer, uint32_t instanceCount ) +void PrimitiveRenderable::Render( GraphicsCommandBuffer& commandBuffer, const Buffer* vertexBuffer, const Buffer* indexBuffer, uint32_t elements, uint32_t instanceCount ) { + assert( vertexBuffer != nullptr ); + commandBuffer.BindEffect( m_effect ); - commandBuffer.BindVertexBuffer( m_vertexBuffer->GetGpuBuffer() ); - if( m_indexBuffer ) + commandBuffer.BindVertexBuffer( vertexBuffer->GetGpuBuffer() ); + if( indexBuffer ) { - commandBuffer.BindIndexBuffer( *m_indexBuffer ); - commandBuffer.DrawIndexed( 0, m_elements, instanceCount ); + commandBuffer.BindIndexBuffer( *indexBuffer ); + commandBuffer.DrawIndexed( 0, elements, instanceCount ); } else { - commandBuffer.Draw( 0, m_elements, instanceCount ); + commandBuffer.Draw( 0, elements, instanceCount ); } } + +void PrimitiveRenderable::Render( GraphicsCommandBuffer& commandBuffer, uint32_t instanceCount ) +{ + Render( commandBuffer, m_vertexBuffer, m_indexBuffer, m_elements, instanceCount ); +} diff --git a/src/viewer/rendering/renderable/primitive.h b/src/viewer/rendering/renderable/primitive.h index 0365e5f..8bc3abf 100644 --- a/src/viewer/rendering/renderable/primitive.h +++ b/src/viewer/rendering/renderable/primitive.h @@ -25,6 +25,7 @@ class PrimitiveRenderable VkResult Initialize(); void Render( GraphicsCommandBuffer& commandBuffer, uint32_t instanceCount = 1 ); + void Render( GraphicsCommandBuffer& commandBuffer, const Buffer* vertexBuffer, const Buffer* indexBuffer, uint32_t elements, uint32_t instanceCount ); private: std::shared_ptr m_renderer{ nullptr }; diff --git a/src/viewer/rendering/sceneRenderer.cpp b/src/viewer/rendering/sceneRenderer.cpp index 563687e..43f0ad0 100644 --- a/src/viewer/rendering/sceneRenderer.cpp +++ b/src/viewer/rendering/sceneRenderer.cpp @@ -5,6 +5,17 @@ #include "vulkan/shadercache.h" #include "vulkan/vulkanerrors.h" #include "vulkan/vulkanenums.h" +#include "vulkan/graphicseffecttypes.h" + +namespace +{ +const std::vector DEFAULT_SHADER_ORDER{ + "Normal", + "Packed Normal", + "Packed Normal Legacy", + "Face Normal" +}; +} SceneRenderer::SceneRenderer( std::shared_ptr renderer ) : m_renderer( renderer ), @@ -106,7 +117,7 @@ void SceneRenderer::SetData( std::shared_ptr data, AppState& appStat } // reset the visualization shader if it is not set or not applicable for the current model - std::string currentShaderName = appState.modelState.visualizationShader.GetValue(); + auto [currentShaderName, currentShaderDeclaration] = appState.modelState.activeShader.GetValue(); std::vector availableVertexElements; for( const auto& mesh : data->m_cmfData->meshes ) @@ -115,16 +126,29 @@ void SceneRenderer::SetData( std::shared_ptr data, AppState& appStat availableVertexElements.insert( availableVertexElements.end(), mesh.decl.begin(), mesh.decl.end() ); } - auto shaderNames = ShaderCache::GetAvailableShaderNames( availableVertexElements ); - auto foundItem = std::find_if( shaderNames.begin(), shaderNames.end(), [&]( auto name ) { - return name == currentShaderName; + std::vector> shaders = ShaderCache::GetAvailableShaders( availableVertexElements ); + auto foundItem = std::find_if( shaders.begin(), shaders.end(), [name = currentShaderName]( const auto& shaderNameAndDeclarations ) { + return shaderNameAndDeclarations.first == name; } ); - - if( foundItem == shaderNames.end() && shaderNames.size() > 0 ) + if( foundItem == shaders.end() && shaders.size() > 0 ) { - appState.modelState.visualizationShader.SetValue( shaderNames[0] ); + // pick the shader that best matches our default shader order + auto defaultShaderIt = std::find_if( shaders.begin(), shaders.end(), [&]( auto shaderNameAndDeclarations ) { + return std::find_if( DEFAULT_SHADER_ORDER.begin(), DEFAULT_SHADER_ORDER.end(), [&]( auto defaultShaderName ) { + return shaderNameAndDeclarations.first == defaultShaderName; + } ) != DEFAULT_SHADER_ORDER.end(); + } ); + + if( defaultShaderIt != shaders.end() ) + { + appState.modelState.activeShader.SetValue( *defaultShaderIt ); + } + else + { + appState.modelState.activeShader.SetValue( shaders[0] ); + } } - appState.modelState.availableShaders.SetValue( shaderNames ); + appState.modelState.availableShaders.SetValue( shaders ); m_model.reset( new ModelRenderable( data, m_renderer ) ); m_model->Initialize( appState ); diff --git a/src/viewer/rendering/uiRenderer.cpp b/src/viewer/rendering/uiRenderer.cpp index 4d08b9b..a7e35d3 100644 --- a/src/viewer/rendering/uiRenderer.cpp +++ b/src/viewer/rendering/uiRenderer.cpp @@ -2,11 +2,13 @@ #include "uiRenderer.h" +#include #include #include #include #include #include +#include #include #include "appState.h" @@ -16,6 +18,38 @@ // ImGui is using a lot of variadic functions for text formatting, so we disable the cppcoreguidelines-pro-type-vararg lint for this file // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) +// +// taken from https://github.com/ocornut/imgui/issues/2644 +namespace ImGui +{ + +// threeway checkbox +bool CheckBoxTristate( const char* label, CheckBoxTriStateValue* v_tristate ) +{ + bool ret; + if( *v_tristate == CheckBoxTriStateValue::MIXED ) + { + ImGui::PushItemFlag( ImGuiItemFlags_MixedValue, true ); + bool b = false; + ret = ImGui::Checkbox( label, &b ); + if( ret ) + { + *v_tristate = CheckBoxTriStateValue::CHECKED; + } + ImGui::PopItemFlag(); + } + else + { + bool b = ( *v_tristate != CheckBoxTriStateValue::UNCHECKED ); + ret = ImGui::Checkbox( label, &b ); + if( ret ) + { + *v_tristate = (CheckBoxTriStateValue)(int)b; + } + } + return ret; +} +}; const float MENU_BAR_HEIGHT = 18.0f; const float ANIMATION_PLAYER_HEIGHT = 36.0f; @@ -263,7 +297,6 @@ void UIRenderer::SetupGeneralView( AppState& appState ) ImGui::Text( "%u", m_uiState.modelStates.indexCount ); ImGui::TableNextRow(); - ImGui::TableNextColumn(); ImGui::Text( "Meshes" ); ImGui::TableNextColumn(); @@ -281,7 +314,21 @@ void UIRenderer::SetupGeneralView( AppState& appState ) ImGui::Text( "Visualization" ); ImGui::TableNextColumn(); ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x ); - SetupCombo( "##visualiationMode", m_uiState.visualizationShaderComboBox, appState.modelState.visualizationShader ); + SetupCombo( "##visualiationMode", m_uiState.visualizationShaderComboBox, appState.modelState.activeShader ); + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text( "Display" ); + ImGui::TableNextColumn(); + size_t checkedCount = std::count_if( appState.modelState.meshes.begin(), appState.modelState.meshes.end(), []( const State& state ) { + return state.GetValue().display.GetValue(); + } ); + ImGui::CheckBoxTriStateValue checked = ( checkedCount == 0 ) ? ImGui::CheckBoxTriStateValue::UNCHECKED : ( checkedCount == appState.modelState.meshes.size() ? ImGui::CheckBoxTriStateValue::CHECKED : ImGui::CheckBoxTriStateValue::MIXED ); + OnChange( ImGui::CheckBoxTristate( "##displaycheckbox", &checked ), [&appState, &checked]() { + std::for_each( appState.modelState.meshes.begin(), appState.modelState.meshes.end(), [checked]( State& state ) { + state.GetValue().display.SetValue( checked == ImGui::CheckBoxTriStateValue::CHECKED ); + } ); + } ); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -296,13 +343,13 @@ void UIRenderer::SetupGeneralView( AppState& appState ) ImGui::TableNextColumn(); ImGui::Text( "Wireframe Overlay" ); ImGui::TableNextColumn(); - bool wireframeOverlay = std::all_of( appState.modelState.meshWireframeOverlay.begin(), appState.modelState.meshWireframeOverlay.end(), []( const State& state ) { - return state.GetValue(); - } ) && - appState.modelState.meshWireframeOverlay.size() > 0; - OnChange( ImGui::Checkbox( "##wireframecheckbox", &wireframeOverlay ), [&appState, &wireframeOverlay]() { - std::for_each( appState.modelState.meshWireframeOverlay.begin(), appState.modelState.meshWireframeOverlay.end(), [wireframeOverlay]( State& state ) { - state.SetValue( wireframeOverlay ); + checkedCount = std::count_if( appState.modelState.meshes.begin(), appState.modelState.meshes.end(), []( const State& state ) { + return state.GetValue().wireframeOverlay.GetValue(); + } ); + checked = ( checkedCount == 0 ) ? ImGui::CheckBoxTriStateValue::UNCHECKED : ( checkedCount == appState.modelState.meshes.size() ? ImGui::CheckBoxTriStateValue::CHECKED : ImGui::CheckBoxTriStateValue::MIXED ); + OnChange( ImGui::CheckBoxTristate( "##wireframecheckbox", &checked ), [&appState, &checked]() { + std::for_each( appState.modelState.meshes.begin(), appState.modelState.meshes.end(), [checked]( State& state ) { + state.GetValue().wireframeOverlay.SetValue( checked == ImGui::CheckBoxTriStateValue::CHECKED ); } ); } ); ImGui::TableNextRow(); @@ -314,18 +361,20 @@ void UIRenderer::SetupGeneralView( AppState& appState ) ImGui::TableNextColumn(); ImGui::Text( "Audio Occlusion Mesh" ); ImGui::TableNextColumn(); - bool audioOcclusion = std::all_of( appState.modelState.audioOcclusionMesh.begin(), appState.modelState.audioOcclusionMesh.end(), []( const State& state ) { - return state.GetValue(); - } ) && - appState.modelState.audioOcclusionMesh.size() > 0; - OnChange( ImGui::Checkbox( "##audioocclusioncheckbox", &audioOcclusion ), [&appState, &audioOcclusion]() { - std::for_each( appState.modelState.audioOcclusionMesh.begin(), appState.modelState.audioOcclusionMesh.end(), [audioOcclusion]( State& state ) { - state.SetValue( audioOcclusion ); + checkedCount = std::count_if( appState.modelState.meshes.begin(), appState.modelState.meshes.end(), []( const State& state ) { + return state.GetValue().audioOcclusionMesh.GetValue(); + } ); + checked = ( checkedCount == 0 ) ? ImGui::CheckBoxTriStateValue::UNCHECKED : ( checkedCount == appState.modelState.meshes.size() ? ImGui::CheckBoxTriStateValue::CHECKED : ImGui::CheckBoxTriStateValue::MIXED ); + OnChange( ImGui::CheckBoxTristate( "##audioocclusioncheckbox", &checked ), [&appState, &checked]() { + std::for_each( appState.modelState.meshes.begin(), appState.modelState.meshes.end(), [checked]( State& state ) { + state.GetValue().audioOcclusionMesh.SetValue( checked == ImGui::CheckBoxTriStateValue::CHECKED ); } ); } ); ImGui::TableNextRow(); ImGui::EndDisabled(); + SetupModelAxisRows( appState ); + ImGui::EndTable(); } } @@ -347,6 +396,167 @@ void UIRenderer::SetupMeshListView( const ModelUiState& modelState, AppState& ap } } +void UIRenderer::SetupModelAxisRows( AppState& appState ) +{ + std::vector>> allNormalStates; + std::vector>> allTangentStates; + std::vector>> allBinormalStates; + + for( const auto& meshState : appState.modelState.meshes ) + { + allNormalStates.push_back( meshState.GetValue().showVertexNormals ); + allTangentStates.push_back( meshState.GetValue().showVertexTangents ); + allBinormalStates.push_back( meshState.GetValue().showVertexBinormals ); + } + + auto normalCheckboxes = GetAxisTriCheckboxStates( allNormalStates ); + auto tangentCheckboxes = GetAxisTriCheckboxStates( allTangentStates ); + auto binormalCheckboxes = GetAxisTriCheckboxStates( allBinormalStates ); + + SetupModelAxisRow( normalCheckboxes, std::string( "Normals" ), [&appState]( bool checked, uint32_t index ) { + for( auto& meshState : appState.modelState.meshes ) + { + auto& vertexNormalStates = meshState.GetValue().showVertexNormals; + auto foundState = std::find_if( vertexNormalStates.begin(), vertexNormalStates.end(), [index]( const State>& state ) { + return state.GetValue().first == index; + } ); + if( foundState != vertexNormalStates.end() ) + { + foundState->SetValue( { index, checked } ); + } + } + } ); + + SetupModelAxisRow( tangentCheckboxes, std::string( "Tangents" ), [&appState]( bool checked, uint32_t index ) { + for( auto& meshState : appState.modelState.meshes ) + { + auto& vertexTangentStates = meshState.GetValue().showVertexTangents; + auto foundState = std::find_if( vertexTangentStates.begin(), vertexTangentStates.end(), [index]( const State>& state ) { + return state.GetValue().first == index; + } ); + if( foundState != vertexTangentStates.end() ) + { + foundState->SetValue( { index, checked } ); + } + } + } ); + + SetupModelAxisRow( binormalCheckboxes, std::string( "Bitangents" ), [&appState]( bool checked, uint32_t index ) { + for( auto& meshState : appState.modelState.meshes ) + { + auto& vertexBinormalStates = meshState.GetValue().showVertexBinormals; + auto foundState = std::find_if( vertexBinormalStates.begin(), vertexBinormalStates.end(), [index]( const State>& state ) { + return state.GetValue().first == index; + } ); + if( foundState != vertexBinormalStates.end() ) + { + foundState->SetValue( { index, checked } ); + } + } + } ); +} + +std::vector> UIRenderer::GetAxisTriCheckboxStates( const std::vector>>& axisStates ) +{ + std::vector> combinedStates; + + for( const auto& meshState : axisStates ) + { + for( const auto& axis : meshState ) + { + auto [index, checked] = axis.GetValue(); + + auto foundState = std::find_if( combinedStates.begin(), combinedStates.end(), [idx = index]( const std::pair& pair ) { + return pair.first == idx; + } ); + + if( foundState == combinedStates.end() ) + { + combinedStates.push_back( { index, checked ? ImGui::CheckBoxTriStateValue::CHECKED : ImGui::CheckBoxTriStateValue::UNCHECKED } ); + } + else + { + if( foundState->second != ImGui::CheckBoxTriStateValue::MIXED ) + { + if( ( foundState->second == ImGui::CheckBoxTriStateValue::CHECKED && !checked ) || ( foundState->second == ImGui::CheckBoxTriStateValue::UNCHECKED && checked ) ) + { + foundState->second = ImGui::CheckBoxTriStateValue::MIXED; + } + } + } + } + } + + return combinedStates; +} + +void UIRenderer::SetupVertexAxisRows( MeshState& meshAppState ) +{ + auto usageIndexChecker = []( const State>& pair1, const State>& pair2 ) { + return pair1.GetValue().first < pair2.GetValue().first; + }; + + auto maxNormalIndexState = std::max_element( meshAppState.showVertexNormals.begin(), meshAppState.showVertexNormals.end(), usageIndexChecker ); + auto maxTangentIndexState = std::max_element( meshAppState.showVertexTangents.begin(), meshAppState.showVertexTangents.end(), usageIndexChecker ); + auto maxBitangentIndexState = std::max_element( meshAppState.showVertexBinormals.begin(), meshAppState.showVertexBinormals.end(), usageIndexChecker ); + + int32_t maxNormalIndex = maxNormalIndexState != meshAppState.showVertexNormals.end() ? maxNormalIndexState->GetValue().first : -1; + int32_t maxTangentIndex = maxTangentIndexState != meshAppState.showVertexTangents.end() ? maxTangentIndexState->GetValue().first : -1; + int32_t maxBitangentIndex = maxBitangentIndexState != meshAppState.showVertexBinormals.end() ? maxBitangentIndexState->GetValue().first : -1; + + SetupVertexAxisRow( meshAppState.showVertexNormals, "Normals", maxNormalIndex ); + SetupVertexAxisRow( meshAppState.showVertexTangents, "Tangents", maxTangentIndex ); + SetupVertexAxisRow( meshAppState.showVertexBinormals, "Bitangents", maxBitangentIndex ); +} + +void UIRenderer::SetupVertexAxisRow( StateCollection>& vertexAxisStates, const char* label, int maxUsageIndex ) +{ + if( vertexAxisStates.empty() ) + { + ImGui::BeginDisabled(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted( label ); + ImGui::TableNextColumn(); + + if( maxUsageIndex >= 0 ) + { + for( int32_t i = 0; i <= maxUsageIndex; ++i ) + { + // find the states that have the index of the column (f.ex there is nothing stopping a mesh having one color attribute that is using usageindex at 16) + auto foundElement = std::find_if( vertexAxisStates.begin(), vertexAxisStates.end(), [i]( const State>& state ) { + return state.GetValue().first == i; + } ); + + if( foundElement != vertexAxisStates.end() ) + { + bool changed = ImGui::Checkbox( ( std::string( "##" ) + label + std::to_string( i ) ).c_str(), &foundElement->GetValue().second ); + + OnChange( changed, [foundElement]() { + foundElement->SetValue( { foundElement->GetValue().first, foundElement->GetValue().second } ); + } ); + + ImGui::SetItemTooltip( "Toggles debug visualization for %s with usage index %d", label, i ); + ImGui::SameLine(); + } + else + { + ImGui::BeginDisabled( true ); + bool value = false; + ImGui::Checkbox( ( std::string( "##" ) + label + std::to_string( i ) ).c_str(), &value ); + ImGui::SetItemTooltip( "Mesh doesn't have %s with usage index %d.", label, i ); + ImGui::SameLine(); + ImGui::EndDisabled(); + } + } + ImGui::NewLine(); + } + if( vertexAxisStates.empty() ) + { + ImGui::EndDisabled(); + } +} void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) { @@ -354,6 +564,7 @@ void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) { if( ImGui::BeginTable( "##table", 2 ) ) { + auto& meshAppState = appState.modelState.meshes[mesh.meshIndex].GetValue(); ImGui::TableSetupColumn( "", ImGuiTableColumnFlags_WidthFixed ); ImGui::TableSetupColumn( "", ImGuiTableColumnFlags_WidthStretch ); ImGui::TableNextRow(); @@ -395,8 +606,8 @@ void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) ImGui::TableNextColumn(); bool display = mesh.display; - OnChange( ImGui::Checkbox( "##displaycheckbox", &display ), [&appState, &mesh, &display]() { - appState.modelState.meshVisibilityStates[mesh.meshIndex].SetValue( display ); + OnChange( ImGui::Checkbox( "##displaycheckbox", &display ), [&meshAppState, &display]() { + meshAppState.display.SetValue( display ); } ); ImGui::TableNextRow(); @@ -406,8 +617,8 @@ void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) ImGui::TableNextColumn(); bool boundingBox = mesh.boundingBox; - OnChange( ImGui::Checkbox( "##boundingboxcheckbox", &boundingBox ), [&appState, &mesh, &boundingBox]() { - appState.modelState.meshBoundingBox[mesh.meshIndex].SetValue( boundingBox ); + OnChange( ImGui::Checkbox( "##boundingboxcheckbox", &boundingBox ), [&meshAppState, &boundingBox]() { + meshAppState.renderBoundingBox.SetValue( boundingBox ); } ); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -416,8 +627,8 @@ void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) ImGui::TableNextColumn(); bool wireframeOverlay = mesh.wireframeOverlay; - OnChange( ImGui::Checkbox( "##wireframeoverlaycheckbox", &wireframeOverlay ), [&appState, &mesh, &wireframeOverlay]() { - appState.modelState.meshWireframeOverlay[mesh.meshIndex].SetValue( wireframeOverlay ); + OnChange( ImGui::Checkbox( "##wireframeoverlaycheckbox", &wireframeOverlay ), [&meshAppState, &wireframeOverlay]() { + meshAppState.wireframeOverlay.SetValue( wireframeOverlay ); } ); ImGui::TableNextRow(); @@ -427,23 +638,21 @@ void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) ImGui::SetItemTooltip( "Toggles the audio occlusion mesh rendering for the \"%s\" mesh", mesh.name.c_str() ); ImGui::TableNextColumn(); bool audioOcclusion = mesh.audioOcclusionMesh; - OnChange( ImGui::Checkbox( "##audioocclusionmeshcheckbox", &audioOcclusion ), [&appState, &mesh, &audioOcclusion]() { - appState.modelState.audioOcclusionMesh[mesh.meshIndex].SetValue( audioOcclusion ); + OnChange( ImGui::Checkbox( "##audioocclusionmeshcheckbox", &audioOcclusion ), [&meshAppState, &audioOcclusion]() { + meshAppState.audioOcclusionMesh.SetValue( audioOcclusion ); } ); ImGui::EndDisabled(); - ImGui::TableNextRow(); + SetupVertexAxisRows( meshAppState ); ImGui::EndTable(); - if( !mesh.morphTargets.empty() ) + + std::string header = "Morph Targets (" + std::to_string( mesh.morphTargets.size() ) + ")"; + if( ImGui::CollapsingHeader( header.c_str() ) ) { - if( ImGui::CollapsingHeader( "Morph Targets" ) ) + for( const auto& morphTarget : mesh.morphTargets ) { - uint32_t index = 0; - for( const auto& morphTarget : mesh.morphTargets ) - { - SetupMorphTarget( morphTarget, appState ); - } + SetupMorphTarget( morphTarget, mesh.meshIndex, appState ); } } } @@ -451,7 +660,7 @@ void UIRenderer::SetupMeshView( const MeshUiState& mesh, AppState& appState ) } } -void UIRenderer::SetupMorphTarget( const MorphTargetUiState& morphTarget, AppState& appState ) +void UIRenderer::SetupMorphTarget( const MorphTargetUiState& morphTarget, size_t meshIndex, AppState& appState ) { ImGui::SeparatorText( morphTarget.name.c_str() ); @@ -467,14 +676,16 @@ void UIRenderer::SetupMorphTarget( const MorphTargetUiState& morphTarget, AppSta ImGui::TableNextColumn(); ImGui::SetNextItemWidth( ImGui::GetContentRegionAvail().x ); float weight = morphTarget.weight; - OnChange( ImGui::SliderFloat( "##slider", &weight, 0.0f, 1.0f, "%.6f" ), [&appState, &morphTarget, &weight]() { - appState.modelState.morphTargetWeight[morphTarget.index].SetValue( weight ); + OnChange( ImGui::SliderFloat( "##slider", &weight, 0.0f, 1.0f, "%.6f" ), [&appState, &morphTarget, &weight, meshIndex]() { + auto& morphState = appState.modelState.meshes[meshIndex].GetValue().morphs[morphTarget.index]; + morphState.SetValue( { weight, morphState.GetValue().second } ); } ); ImGui::TableNextColumn(); bool enabled = morphTarget.enabled; - OnChange( ImGui::Checkbox( "##checkbox", &enabled ), [&appState, &morphTarget, &enabled]() { - appState.modelState.morphTargetEnabled[morphTarget.index].SetValue( enabled ); + OnChange( ImGui::Checkbox( "##checkbox", &enabled ), [&appState, &morphTarget, &enabled, meshIndex]() { + auto& morphState = appState.modelState.meshes[meshIndex].GetValue().morphs[morphTarget.index]; + morphState.SetValue( { morphState.GetValue().first, enabled } ); } ); ImGui::SetItemTooltip( "Toggles the \"%s\" morph target", morphTarget.name.c_str() ); ImGui::EndTable(); @@ -962,22 +1173,23 @@ void UIRenderer::UpdateUiState( AppState& appState ) for( const auto& mesh : cmfContent->m_cmfData->meshes ) { - if( meshIndex >= appState.modelState.meshVisibilityStates.size() ) + if( meshIndex >= appState.modelState.meshes.size() ) { break; } + const auto& meshAppState = appState.modelState.meshes[meshIndex].GetValue(); MeshUiState meshState{}; meshState.meshIndex = meshIndex; meshState.name = cmf::ToStdString( mesh.name ); - meshState.lod = appState.modelState.activeLod[meshIndex].GetValue(); + meshState.lod = meshAppState.activeLod.GetValue(); meshState.maxLodIndex = static_cast( mesh.lods.size() ) - 1; meshState.boundingSphereRadius = CcpMath::Sphere( mesh.bounds ).radius; - meshState.screenSize = appState.modelState.meshScreenSize[meshIndex].GetValue(); - meshState.display = appState.modelState.meshVisibilityStates[meshIndex].GetValue(); - meshState.wireframeOverlay = appState.modelState.meshWireframeOverlay[meshIndex].GetValue(); - meshState.audioOcclusionMesh = appState.modelState.audioOcclusionMesh[meshIndex].GetValue(); + meshState.screenSize = meshAppState.meshScreenSize.GetValue(); + meshState.display = meshAppState.display.GetValue(); + meshState.wireframeOverlay = meshAppState.wireframeOverlay.GetValue(); + meshState.audioOcclusionMesh = meshAppState.audioOcclusionMesh.GetValue(); meshState.hasAudioOcclusionMesh = !mesh.audioOcclusionMesh.vertices.empty() && !mesh.audioOcclusionMesh.indices.empty(); - meshState.boundingBox = appState.modelState.meshBoundingBox[meshIndex].GetValue(); + meshState.boundingBox = meshAppState.renderBoundingBox.GetValue(); maxLod = std::max( maxLod, mesh.lods.size() ); meshState.vertexCount = 0; @@ -998,17 +1210,19 @@ void UIRenderer::UpdateUiState( AppState& appState ) m_uiState.modelStates.vertexCount += meshState.vertexCount; m_uiState.modelStates.indexCount += meshState.indexCount; - for( const auto& morphTarget : mesh.morphTargets.targets ) + for( size_t i = 0; i < mesh.morphTargets.targets.size(); ++i ) { - if( morphIndex >= appState.modelState.morphTargetWeight.size() ) + if( i >= meshAppState.morphs.size() ) { break; } + const auto& morphTarget = mesh.morphTargets.targets[i]; + const auto& morphPair = meshAppState.morphs[i].GetValue(); MorphTargetUiState morphTargetState{}; morphTargetState.name = cmf::ToStdString( morphTarget.name ); - morphTargetState.weight = appState.modelState.morphTargetWeight[morphIndex].GetValue(); - morphTargetState.enabled = appState.modelState.morphTargetEnabled[morphIndex].GetValue(); - morphTargetState.index = morphIndex++; + morphTargetState.weight = morphPair.first; + morphTargetState.enabled = morphPair.second; + morphTargetState.index = i; meshState.morphTargets.push_back( morphTargetState ); } @@ -1017,11 +1231,17 @@ void UIRenderer::UpdateUiState( AppState& appState ) } // visualization shader selection - for( const auto& shaderName : appState.modelState.availableShaders.GetValue() ) + for( const auto& shaderSetupInfo : appState.modelState.availableShaders.GetValue() ) { - m_uiState.visualizationShaderComboBox.items.push_back( { shaderName, shaderName } ); + std::string shaderName = shaderSetupInfo.first; + if( !shaderSetupInfo.second.shaderNameAddition.empty() ) + { + shaderName += " " + shaderSetupInfo.second.shaderNameAddition; + } + + m_uiState.visualizationShaderComboBox.items.push_back( { shaderName, shaderSetupInfo } ); } - m_uiState.visualizationShaderComboBox.SetSelectedItemByValue( appState.modelState.visualizationShader.GetValue() ); + m_uiState.visualizationShaderComboBox.SetSelectedItemByValue( appState.modelState.activeShader.GetValue() ); m_uiState.modelStates.lod.items.push_back( std::make_pair( "Auto", -1 ) ); @@ -1073,7 +1293,7 @@ void UIRenderer::UpdateUiState( AppState& appState ) m_uiState.filePath = "No file loaded"; // visualization shader selection - m_uiState.visualizationShaderComboBox.items.push_back( { "", "" } ); + m_uiState.visualizationShaderComboBox.items.push_back( { "", {} } ); } } diff --git a/src/viewer/rendering/uiRenderer.h b/src/viewer/rendering/uiRenderer.h index 973d1b8..9676ddc 100644 --- a/src/viewer/rendering/uiRenderer.h +++ b/src/viewer/rendering/uiRenderer.h @@ -11,6 +11,17 @@ #include "uiDetailWindow.h" #include "vulkan/commandbuffer.h" +namespace ImGui +{ +enum class CheckBoxTriStateValue +{ + UNCHECKED = 0, + CHECKED = 1, + MIXED = -1 +}; +bool CheckBoxTristate( const char* label, CheckBoxTriStateValue* v_tristate ); +} + // Handles rendering the UI class UIRenderer { @@ -73,6 +84,9 @@ class UIRenderer bool wireframeOverlay{ false }; bool audioOcclusionMesh{ false }; bool hasAudioOcclusionMesh{ false }; + std::vector> showVertexNormals{}; + std::vector> showVertexTangents{}; + std::vector> showVertexBinormals{}; }; struct ModelUiState @@ -104,7 +118,7 @@ class UIRenderer ModelUiState modelStates{}; std::vector skeletonOwners{}; CmfUiComboBox polygonModeComboBox; - CmfUiComboBox visualizationShaderComboBox; + CmfUiComboBox> visualizationShaderComboBox; bool boneDebug{ false }; bool jointDebug{ false }; bool jointAxisDebug{ false }; @@ -124,7 +138,15 @@ class UIRenderer void SetupGeneralView( AppState& appState ); void SetupMeshListView( const ModelUiState& modelState, AppState& appState ); void SetupMeshView( const MeshUiState& mesh, AppState& appState ); - void SetupMorphTarget( const MorphTargetUiState& morphTarget, AppState& appState ); + void SetupModelAxisRows( AppState& appState ); + + template + void SetupModelAxisRow( std::vector>& checkboxStates, const std::string& name, const Callable& changeCallback ); + + std::vector> GetAxisTriCheckboxStates( const std::vector>>& axisStates ); + void SetupVertexAxisRows( MeshState& meshAppState ); + void SetupVertexAxisRow( StateCollection>& vertexAxisStates, const char* label, int columnCount ); + void SetupMorphTarget( const MorphTargetUiState& morphTarget, size_t meshIndex, AppState& appState ); void SetupSkeletonOwners( const std::vector& skeletonOwners, AppState& appState ); void SetupSkeletons( const std::vector& skeletonStates, AppState& appState ); void SetupPlaybackControls( AppState& appState ); diff --git a/src/viewer/rendering/uiRenderer_template_impl.h b/src/viewer/rendering/uiRenderer_template_impl.h index 0de3a65..4a6b865 100644 --- a/src/viewer/rendering/uiRenderer_template_impl.h +++ b/src/viewer/rendering/uiRenderer_template_impl.h @@ -41,4 +41,50 @@ void UIRenderer::CmfUiComboBox::SetSelectedItemByValue( T value ) selectedItemName = items.front().first; selectedItemValue = items.front().second; } +} + +template +void UIRenderer::SetupModelAxisRow( std::vector>& checkboxStates, const std::string& name, const Callable& changeCallback ) +{ + int32_t maxIndex = checkboxStates.empty() ? -1 : std::max_element( checkboxStates.begin(), checkboxStates.end(), []( const std::pair& a, const std::pair& b ) { + return a.first < b.first; + } )->first; + + ImGui::BeginDisabled( maxIndex < 0 ); + ImGui::TableNextColumn(); + ImGui::Text( "%s", name.c_str() ); + ImGui::SetItemTooltip( "Toggles the %s visualization for all meshes", name.c_str() ); + ImGui::TableNextColumn(); + + if( maxIndex >= 0 ) + { + for( int32_t i = 0; i <= maxIndex; ++i ) + { + auto foundState = std::find_if( checkboxStates.begin(), checkboxStates.end(), [i]( const std::pair& pair ) { + return pair.first == (uint32_t)i; + } ); + + if( foundState == checkboxStates.end() ) + { + // add a disabled checkbox + ImGui::BeginDisabled( true ); + auto disabledValue = ImGui::CheckBoxTriStateValue::UNCHECKED; + ImGui::CheckBoxTristate( ( std::string( "##tricheckbox" ) + name + std::to_string( i ) ).c_str(), &disabledValue ); + + ImGui::SetItemTooltip( "No mesh has %s information for usage %d", name.c_str(), i ); + ImGui::SameLine(); + ImGui::EndDisabled(); + } + else + { + auto value = foundState->second; + OnChange( ImGui::CheckBoxTristate( ( std::string( "##tricheckbox" ) + name + std::to_string( i ) ).c_str(), &value ), [changeCallback, i, value]() { + changeCallback( value != ImGui::CheckBoxTriStateValue::CHECKED, i ); + } ); + ImGui::SetItemTooltip( "Toggles debug visualization for %s with usage index %d for all meshes (if available)", name.c_str(), i ); + ImGui::SameLine(); + } + } + } + ImGui::EndDisabled(); } \ No newline at end of file diff --git a/src/viewer/rendering/vulkan/graphicseffect.cpp b/src/viewer/rendering/vulkan/graphicseffect.cpp index f90bb11..eacdd00 100644 --- a/src/viewer/rendering/vulkan/graphicseffect.cpp +++ b/src/viewer/rendering/vulkan/graphicseffect.cpp @@ -11,7 +11,7 @@ GraphicsEffect::GraphicsEffect( std::shared_ptr renderer ) : { } -void GraphicsEffect::SetConfig( GraphicsEffect::Config config ) +void GraphicsEffect::SetConfig( GraphicsEffectTypes::Config config ) { m_config = config; @@ -39,5 +39,5 @@ void GraphicsEffect::Bind( VkCommandBuffer commandBuffer, uint32_t currentFrameI size_t GraphicsEffect::GetStride() const { - return m_config.stride; + return m_config.inputDeclaration.stride; } diff --git a/src/viewer/rendering/vulkan/graphicseffect.h b/src/viewer/rendering/vulkan/graphicseffect.h index 4743c4f..4e49616 100644 --- a/src/viewer/rendering/vulkan/graphicseffect.h +++ b/src/viewer/rendering/vulkan/graphicseffect.h @@ -3,25 +3,20 @@ #pragma once #include "../renderer.h" #include "effect.h" +#include "graphicseffecttypes.h" class GraphicsEffect : public Effect { public: GraphicsEffect( std::shared_ptr renderer ); - struct Config + struct VertexUboData { - VkPrimitiveTopology topology{ VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST }; - VkPolygonMode polygonMode{ VK_POLYGON_MODE_FILL }; - float lineWidth{ 1.0f }; - VkCompareOp depthCompareOp{ VK_COMPARE_OP_LESS }; - VkCullModeFlags cullMode{ VK_CULL_MODE_BACK_BIT }; - bool blend{ false }; - size_t stride{ 0 }; - std::vector availableVertexElements{}; + Matrix proj; + Matrix view; }; - void SetConfig( GraphicsEffect::Config config ); + void SetConfig( GraphicsEffectTypes::Config config ); void Bind( VkCommandBuffer commandBuffer, uint32_t currentFrameIndex ) override; size_t GetStride() const; @@ -29,5 +24,5 @@ class GraphicsEffect : public Effect VkResult CreatePipeline() override; private: - Config m_config{}; + GraphicsEffectTypes::Config m_config{}; }; diff --git a/src/viewer/rendering/vulkan/graphicseffecttypes.h b/src/viewer/rendering/vulkan/graphicseffecttypes.h new file mode 100644 index 0000000..d7aa25a --- /dev/null +++ b/src/viewer/rendering/vulkan/graphicseffecttypes.h @@ -0,0 +1,50 @@ +#pragma once + +namespace GraphicsEffectTypes +{ + +struct ShaderInputDeclaration +{ + VkVertexInputRate vertexInputRate{ VK_VERTEX_INPUT_RATE_VERTEX }; + std::vector vertexDeclarations; + size_t stride{ 0 }; + // used for displaying additional info in the UI, f.ex Color 1, UV 2, etc. + std::string shaderNameAddition{ "" }; + + bool operator==( const ShaderInputDeclaration& other ) const + { + if( vertexInputRate != other.vertexInputRate || stride != other.stride || shaderNameAddition != other.shaderNameAddition ) + { + return false; + } + if( vertexDeclarations.size() != other.vertexDeclarations.size() ) + { + return false; + } + for( size_t i = 0; i < vertexDeclarations.size(); i++ ) + { + if( vertexDeclarations[i].usage != other.vertexDeclarations[i].usage || + vertexDeclarations[i].usageIndex != other.vertexDeclarations[i].usageIndex || + vertexDeclarations[i].type != other.vertexDeclarations[i].type || + vertexDeclarations[i].elementCount != other.vertexDeclarations[i].elementCount || + vertexDeclarations[i].offset != other.vertexDeclarations[i].offset ) + { + return false; + } + } + return true; + } +}; + +struct Config +{ + VkPrimitiveTopology topology{ VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST }; + VkPolygonMode polygonMode{ VK_POLYGON_MODE_FILL }; + float lineWidth{ 1.0f }; + VkCompareOp depthCompareOp{ VK_COMPARE_OP_LESS }; + VkCullModeFlags cullMode{ VK_CULL_MODE_BACK_BIT }; + bool blend{ false }; + ShaderInputDeclaration inputDeclaration; +}; + +} \ No newline at end of file diff --git a/src/viewer/rendering/vulkan/shadercache.cpp b/src/viewer/rendering/vulkan/shadercache.cpp index 2b7ccbd..36a17f3 100644 --- a/src/viewer/rendering/vulkan/shadercache.cpp +++ b/src/viewer/rendering/vulkan/shadercache.cpp @@ -89,7 +89,7 @@ void ShaderCache::ReleaseShaders( const Renderer* renderer ) s_initialized = false; } -VkResult ShaderCache::CreateGraphicsPipeline( const Renderer* renderer, std::string shaderName, GraphicsEffect::Config config, VkPipelineLayout pipelineLayout, VkPipeline* outPipeline ) +VkResult ShaderCache::CreateGraphicsPipeline( const Renderer* renderer, std::string shaderName, GraphicsEffectTypes::Config config, VkPipelineLayout pipelineLayout, VkPipeline* outPipeline ) { assert( s_initialized ); @@ -166,12 +166,12 @@ VkResult ShaderCache::CreateGraphicsPipeline( const Renderer* renderer, std::str dynamicState.flags = 0; std::vector vertexDescriptions; - ShaderCache::GenerateVertexDescriptions( shaderName, config.availableVertexElements, &vertexDescriptions ); + ShaderCache::GenerateVertexDescriptions( shaderName, config.inputDeclaration.vertexDeclarations, &vertexDescriptions ); VkVertexInputBindingDescription bindingDecl{}; bindingDecl.binding = 0; // expected to be 0 since we only support one vertex buffer - bindingDecl.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - bindingDecl.stride = (uint32_t)config.stride; + bindingDecl.inputRate = config.inputDeclaration.vertexInputRate; + bindingDecl.stride = (uint32_t)config.inputDeclaration.stride; VkPipelineVertexInputStateCreateInfo vertexInputState{}; vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; @@ -251,61 +251,73 @@ VkResult ShaderCache::CreateComputePipeline( const Renderer* renderer, std::stri return VK_SUCCESS; } -std::vector ShaderCache::GetAvailableShaderNames( const std::vector& availableVertexElements ) +std::vector> ShaderCache::GetAvailableShaders( const std::vector& availableVertexElements ) { - std::vector result; + std::vector> result; result.reserve( s_cache.size() ); - std::vector elements; - elements.reserve( availableVertexElements.size() ); - std::transform( - availableVertexElements.begin(), - availableVertexElements.end(), - std::back_inserter( elements ), - []( const cmf::VertexElement& element ) { - return element.usage; - } ); - - std::for_each( s_cache.begin(), s_cache.end(), [&result, &elements]( const auto& keyValue ) { - ShaderContainer shaderContainer = keyValue.second; + std::for_each( s_cache.begin(), s_cache.end(), [&result, &availableVertexElements]( const auto& keyValue ) { + auto& [shaderName, shaderContainer] = keyValue; // only model shaders available if( !shaderContainer.isModelShader ) { return; } - // check if there are any inputs that the shader expects that is not in the cmf vertex declaration + // find all the elements that are used + std::vector elements; + std::vector multiUsageElements; + for( const auto& inputLayout : shaderContainer.inputLayout ) { - if( std::find( elements.begin(), elements.end(), inputLayout.usage ) == elements.end() ) + std::vector foundElements; + std::copy_if( availableVertexElements.begin(), availableVertexElements.end(), std::back_inserter( foundElements ), [inputLayout]( const cmf::VertexElement& element ) { + return element.usage == inputLayout.usage; + } ); + + if( foundElements.empty() ) { + // couldn´t find any element for this usage, so this shader config is not compatible with the available vertex elements return; } + + if( inputLayout.multiUsageIndex ) + { + // this will gather all f.x Color usages, UV usages, etc. and create a shader config for each of them + multiUsageElements.insert( multiUsageElements.end(), foundElements.begin(), foundElements.end() ); + } + else + { + // just take the first one + elements.push_back( foundElements.front() ); + } } - result.push_back( keyValue.first ); + GraphicsEffectTypes::ShaderInputDeclaration shaderinputDeclaration{}; + shaderinputDeclaration.vertexDeclarations = elements; + + if( multiUsageElements.empty() ) + { + result.push_back( { shaderName, shaderinputDeclaration } ); + } + else + { + for( const auto& element : multiUsageElements ) + { + auto inputDecl = shaderinputDeclaration; + inputDecl.vertexDeclarations.push_back( element ); + + if( multiUsageElements.size() > 1 ) + { + inputDecl.shaderNameAddition = std::to_string( element.usageIndex ); + } + result.push_back( { shaderName, inputDecl } ); + } + } } ); return result; } -std::vector ShaderCache::GetShaderUsage( std::string shaderName ) -{ - std::vector usage{}; - auto entry = s_cache.find( shaderName ); - - if( entry == s_cache.end() ) - { - return usage; - } - auto shaderContainer = ( *entry ).second; - for( const auto& layout : shaderContainer.inputLayout ) - { - usage.push_back( layout.usage ); - } - return usage; -} - - void ShaderCache::GenerateVertexDescriptions( std::string shaderName, const std::vector& availableVertexElements, std::vector* outAttributeDescriptions ) { outAttributeDescriptions->clear(); diff --git a/src/viewer/rendering/vulkan/shadercache.h b/src/viewer/rendering/vulkan/shadercache.h index dfda0b8..358d9bb 100644 --- a/src/viewer/rendering/vulkan/shadercache.h +++ b/src/viewer/rendering/vulkan/shadercache.h @@ -33,6 +33,9 @@ struct ShaderInputLayout { uint8_t location; cmf::Usage usage; + // Used if we can support mapping of multiple vertex elements that have the same usage. + // e.g multiple UV sets, multiple color sets, etc. + bool multiUsageIndex; }; struct ShaderContainer @@ -51,11 +54,9 @@ class ShaderCache static VkResult InitializeShaders( const Renderer* renderer ); static void ReleaseShaders( const Renderer* renderer ); - static VkResult CreateGraphicsPipeline( const Renderer* renderer, std::string shaderName, GraphicsEffect::Config config, VkPipelineLayout pipelineLayout, VkPipeline* outPipeline ); + static VkResult CreateGraphicsPipeline( const Renderer* renderer, std::string shaderName, GraphicsEffectTypes::Config config, VkPipelineLayout pipelineLayout, VkPipeline* outPipeline ); static VkResult CreateComputePipeline( const Renderer* renderer, std::string shaderName, VkPipelineLayout pipelineLayout, VkPipeline* outPipeline ); - static std::vector GetAvailableShaderNames( const std::vector& availableVertexElements ); - - static std::vector GetShaderUsage( std::string shaderName ); + static std::vector> GetAvailableShaders( const std::vector& availableVertexElements ); private: static void GenerateVertexDescriptions( std::string shaderName, const std::vector& availableVertexElements, std::vector* outAttributeDescriptions );