4242namespace ax ::rhi::vk
4343{
4444
45- static constexpr uint32_t MAX_DESCRIPTOR_SETS_PER_FRAME = 1024 ;
46-
4745/*
4846 * Helper: map PrimitiveType to VkPrimitiveTopology
4947 *
@@ -96,6 +94,24 @@ static VkIndexType toVkIndexType(IndexFormat fmt)
9694 }
9795}
9896
97+ inline bool nearlyEqual (float a, float b, float eps = 1e-6f )
98+ {
99+ return std::fabs (a - b) < eps;
100+ }
101+
102+ inline bool operator ==(const VkViewport& a, const VkViewport& b)
103+ {
104+ return nearlyEqual (a.x , b.x ) && nearlyEqual (a.y , b.y ) && nearlyEqual (a.width , b.width ) &&
105+ nearlyEqual (a.height , b.height ) && nearlyEqual (a.minDepth , b.minDepth ) &&
106+ nearlyEqual (a.maxDepth , b.maxDepth );
107+ }
108+
109+ inline bool operator ==(const VkRect2D& a, const VkRect2D& b)
110+ {
111+ return a.offset .x == b.offset .x && a.offset .y == b.offset .y && a.extent .width == b.extent .width &&
112+ a.extent .height == b.extent .height ;
113+ }
114+
99115// NOTE: This implementation assumes the existence of a Vulkan driver context that owns device, queues,
100116// swapchain, render pass, and descriptor management. Adapt integration points to your driver as needed.
101117
@@ -323,6 +339,8 @@ void RenderContextImpl::createDescriptorPool()
323339 /* {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 32},*/ // SSBO, unused currently
324340 };
325341
342+ constexpr uint32_t MAX_DESCRIPTOR_SETS_PER_FRAME = 1024 ;
343+
326344 VkDescriptorPoolCreateInfo poolInfo{};
327345 poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
328346 poolInfo.poolSizeCount = static_cast <uint32_t >(std::size (poolSizes));
@@ -603,8 +621,9 @@ void RenderContextImpl::endRenderPass()
603621 rtImpl->endRenderPass (_currentCmdBuffer);
604622
605623 // Reset state cache
606- _programState = nullptr ;
607- _vertexLayout = nullptr ;
624+ _programState = nullptr ;
625+ _vertexLayout = nullptr ;
626+ _boundPipeline = nullptr ;
608627
609628 AX_SAFE_RELEASE_NULL (_indexBuffer);
610629 AX_SAFE_RELEASE_NULL (_vertexBuffer);
@@ -705,6 +724,17 @@ void RenderContextImpl::updatePipelineState(const RenderTarget* rt, const Pipeli
705724 AXASSERT (_renderPipeline, " RenderPipelineImpl not set" );
706725 _renderPipeline->prepareUpdate (_depthStencilState);
707726 _renderPipeline->update (rt, desc);
727+
728+ // Bind pipeline
729+ auto pipeline = _renderPipeline->getVkPipeline ();
730+ if (_boundPipeline != pipeline)
731+ {
732+ vkCmdBindPipeline (_currentCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, _renderPipeline->getVkPipeline ());
733+ _boundPipeline = pipeline;
734+
735+ // Prime dynamic states required by this pipeline
736+ bitmask::set (_inFlightDynamicDirtyBits[_currentFrame], PIPELINE_REQUIRED_DYNAMIC_BITS);
737+ }
708738}
709739
710740void RenderContextImpl::setViewport (int x, int y, unsigned int w, unsigned int h)
@@ -720,7 +750,11 @@ void RenderContextImpl::setViewport(int x, int y, unsigned int w, unsigned int h
720750 vp.minDepth = 0 .0f ;
721751 vp.maxDepth = 1 .0f ;
722752
723- _cachedViewport = vp;
753+ if (vp != _cachedViewport)
754+ {
755+ _cachedViewport = vp;
756+ markDynamicStateDirty (DynamicStateBits::Viewport);
757+ }
724758}
725759
726760void RenderContextImpl::setScissorRect (bool isEnabled, float x, float y, float width, float height)
@@ -748,37 +782,64 @@ void RenderContextImpl::setScissorRect(bool isEnabled, float x, float y, float w
748782 rect.extent = {_renderTargetWidth, _renderTargetHeight};
749783 }
750784
751- _scissorEnabled = isEnabled;
752- _cachedScissor = rect;
785+ if (_scissorEnabled != isEnabled || _cachedScissor != rect)
786+ {
787+ _scissorEnabled = isEnabled;
788+ _cachedScissor = rect;
789+ markDynamicStateDirty (DynamicStateBits::Scissor);
790+ }
753791}
754792
755793void RenderContextImpl::setCullMode (CullMode mode)
756794{
795+ VkCullModeFlags nativeMode{0 };
757796 switch (mode)
758797 {
759798 case CullMode::NONE:
760- _cachedCullMode = VK_CULL_MODE_NONE;
799+ nativeMode = VK_CULL_MODE_NONE;
761800 break ;
762801 case CullMode::BACK:
763- _cachedCullMode = VK_CULL_MODE_BACK_BIT;
802+ nativeMode = VK_CULL_MODE_BACK_BIT;
764803 break ;
765804 case CullMode::FRONT:
766- _cachedCullMode = VK_CULL_MODE_FRONT_BIT;
805+ nativeMode = VK_CULL_MODE_FRONT_BIT;
767806 break ;
768807 }
808+
809+ if (_cachedCullMode != nativeMode)
810+ {
811+ _cachedCullMode = nativeMode;
812+ markDynamicStateDirty (DynamicStateBits::CullMode);
813+ }
769814}
770815
771816void RenderContextImpl::setWinding (Winding winding)
772817{
818+ VkFrontFace frontFace = VkFrontFace::VK_FRONT_FACE_MAX_ENUM;
773819 switch (winding)
774820 {
775821 case Winding::CLOCK_WISE:
776- _cachedFrontFace = VK_FRONT_FACE_CLOCKWISE;
822+ frontFace = VK_FRONT_FACE_CLOCKWISE;
777823 break ;
778824 case Winding::COUNTER_CLOCK_WISE:
779- _cachedFrontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
825+ frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
780826 break ;
781827 }
828+
829+ if (frontFace != _cachedFrontFace)
830+ {
831+ _cachedFrontFace = frontFace;
832+ markDynamicStateDirty (DynamicStateBits::FrontFace);
833+ }
834+ }
835+
836+ void RenderContextImpl::setStencilReferenceValue (uint32_t value)
837+ {
838+ if (value != _stencilReferenceValue)
839+ {
840+ RenderContext::setStencilReferenceValue (value);
841+ markDynamicStateDirty (DynamicStateBits::StencilRef);
842+ }
782843}
783844
784845void RenderContextImpl::setVertexBuffer (Buffer* buffer)
@@ -812,14 +873,12 @@ void RenderContextImpl::prepareDrawing()
812873{
813874 AXASSERT (_programState, " ProgramState must be set before drawing" );
814875 AXASSERT (_renderPipeline, " RenderPipelineImpl must be set before drawing" );
876+ AXASSERT (_boundPipeline, " boundPipeline must be set before drawing" );
815877
816878 // Populate CPU-side uniforms via callbacks
817879 for (auto & cb : _programState->getCallbackUniforms ())
818880 cb.second (_programState, cb.first );
819881
820- // Bind pipeline
821- vkCmdBindPipeline (_currentCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, _renderPipeline->getVkPipeline ());
822-
823882 // Acquire descriptor sets for this frame, matching current pipeline layout
824883 VkPipelineLayout pipelineLayout = _renderPipeline->getVkPipelineLayout ();
825884 const auto dslState = _renderPipeline->getDescriptorSetLayoutState ();
@@ -900,48 +959,42 @@ void RenderContextImpl::prepareDrawing()
900959 imageInfos.clear ();
901960 imageInfos.reserve (dslState->samplerDescriptorCount );
902961
903- const bool hasSamplerSet = (dslState->descriptorSetLayoutCount > RenderPipelineImpl::SET_INDEX_SAMPLER) &&
904- (descriptorSets[RenderPipelineImpl::SET_INDEX_SAMPLER] != VK_NULL_HANDLE);
905-
906- if (hasSamplerSet)
962+ for (const auto & [bindingIndex, bindingSet] : _programState->getTextureBindingSets ())
907963 {
908- for (const auto & [bindingIndex, bindingSet] : _programState->getTextureBindingSets ())
909- {
910- const auto & texs = bindingSet.texs ;
911- if (texs.empty ())
912- continue ;
964+ const auto & texs = bindingSet.texs ;
965+ if (texs.empty ())
966+ continue ;
913967
914- // Remember current offset in imageInfos before appending
915- const size_t offset = imageInfos.size ();
968+ // Remember current offset in imageInfos before appending
969+ const size_t offset = imageInfos.size ();
916970
917- if (texs.size () == 1 )
971+ if (texs.size () == 1 )
972+ {
973+ auto * textureImpl = static_cast <TextureImpl*>(texs[0 ]);
974+ VkDescriptorImageInfo& imageInfo = imageInfos.emplace_back ();
975+ imageInfo.sampler = textureImpl->getSampler ();
976+ imageInfo.imageView = textureImpl->internalHandle ().view ;
977+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
978+ }
979+ else
980+ {
981+ for (auto * tex : texs)
918982 {
919- auto * textureImpl = static_cast <TextureImpl*>(texs[ 0 ] );
983+ auto * textureImpl = static_cast <TextureImpl*>(tex );
920984 VkDescriptorImageInfo& imageInfo = imageInfos.emplace_back ();
921985 imageInfo.sampler = textureImpl->getSampler ();
922986 imageInfo.imageView = textureImpl->internalHandle ().view ;
923987 imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
924988 }
925- else
926- {
927- for (auto * tex : texs)
928- {
929- auto * textureImpl = static_cast <TextureImpl*>(tex);
930- VkDescriptorImageInfo& imageInfo = imageInfos.emplace_back ();
931- imageInfo.sampler = textureImpl->getSampler ();
932- imageInfo.imageView = textureImpl->internalHandle ().view ;
933- imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
934- }
935- }
936-
937- VkWriteDescriptorSet& write = writes.emplace_back ();
938- write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
939- write.dstSet = descriptorSets[RenderPipelineImpl::SET_INDEX_SAMPLER];
940- write.dstBinding = bindingIndex;
941- write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
942- write.descriptorCount = static_cast <uint32_t >(texs.size ());
943- write.pImageInfo = imageInfos.data () + offset;
944989 }
990+
991+ VkWriteDescriptorSet& write = writes.emplace_back ();
992+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
993+ write.dstSet = descriptorSets[RenderPipelineImpl::SET_INDEX_SAMPLER];
994+ write.dstBinding = bindingIndex;
995+ write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
996+ write.descriptorCount = static_cast <uint32_t >(texs.size ());
997+ write.pImageInfo = imageInfos.data () + offset;
945998 }
946999
9471000 // Commit descriptor writes
@@ -951,16 +1004,8 @@ void RenderContextImpl::prepareDrawing()
9511004 }
9521005
9531006 // Bind descriptor sets: bind only the sets that exist
954- uint32_t setCountToBind = dslState->descriptorSetLayoutCount ;
955- if (!hasSamplerSet && setCountToBind > 1 )
956- setCountToBind = 1 ;
957-
958- AXASSERT (!writes.empty (), " No descriptor writes prepared before vkUpdateDescriptorSets" );
959- AXASSERT (setCountToBind == dslState->descriptorSetLayoutCount || setCountToBind == 1 ,
960- " Descriptor set count mismatch with pipeline layout" );
961-
962- vkCmdBindDescriptorSets (_currentCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0 , setCountToBind,
963- descriptorSets.data (), 0 , nullptr );
1007+ vkCmdBindDescriptorSets (_currentCmdBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0 ,
1008+ dslState->descriptorSetLayoutCount , descriptorSets.data (), 0 , nullptr );
9641009
9651010 // Bind vertex buffers
9661011 if (!_instanceBuffer)
@@ -976,20 +1021,43 @@ void RenderContextImpl::prepareDrawing()
9761021 vkCmdBindVertexBuffers (_currentCmdBuffer, 0 , 2 , buffers, offsets);
9771022 }
9781023
979- // Dynamic states
980- vkCmdSetViewport (_currentCmdBuffer, 0 , 1 , &_cachedViewport);
1024+ // Apply dynamic states based on dirty bits
1025+ auto & dynamicDirtyBits = _inFlightDynamicDirtyBits[_currentFrame];
1026+ if (bitmask::any (dynamicDirtyBits, DynamicStateBits::Viewport))
1027+ {
1028+ vkCmdSetViewport (_currentCmdBuffer, 0 , 1 , &_cachedViewport);
1029+ bitmask::clear (dynamicDirtyBits, DynamicStateBits::Viewport);
1030+ }
9811031
982- if (_scissorEnabled)
983- vkCmdSetScissor (_currentCmdBuffer, 0 , 1 , &_cachedScissor);
984- else
1032+ if (bitmask::any (dynamicDirtyBits, DynamicStateBits::Scissor))
1033+ {
1034+ if (_scissorEnabled)
1035+ vkCmdSetScissor (_currentCmdBuffer, 0 , 1 , &_cachedScissor);
1036+ else
1037+ {
1038+ VkRect2D fullRect{{0 , 0 }, {_renderTargetWidth, _renderTargetHeight}};
1039+ vkCmdSetScissor (_currentCmdBuffer, 0 , 1 , &fullRect);
1040+ }
1041+ bitmask::clear (dynamicDirtyBits, DynamicStateBits::Scissor);
1042+ }
1043+
1044+ if (bitmask::any (dynamicDirtyBits, DynamicStateBits::StencilRef))
9851045 {
986- VkRect2D fullRect{{ 0 , 0 }, {_renderTargetWidth, _renderTargetHeight}} ;
987- vkCmdSetScissor (_currentCmdBuffer, 0 , 1 , &fullRect );
1046+ vkCmdSetStencilReference (_currentCmdBuffer, VK_STENCIL_FACE_FRONT_AND_BACK, _stencilReferenceValue) ;
1047+ bitmask::clear (dynamicDirtyBits, DynamicStateBits::StencilRef );
9881048 }
9891049
990- vkCmdSetStencilReference (_currentCmdBuffer, VK_STENCIL_FACE_FRONT_AND_BACK, _stencilReferenceValue);
991- vkCmdSetCullModeEXT (_currentCmdBuffer, _cachedCullMode);
992- vkCmdSetFrontFaceEXT (_currentCmdBuffer, _cachedFrontFace);
1050+ if (bitmask::any (dynamicDirtyBits, DynamicStateBits::CullMode))
1051+ {
1052+ vkCmdSetCullModeEXT (_currentCmdBuffer, _cachedCullMode);
1053+ bitmask::clear (dynamicDirtyBits, DynamicStateBits::CullMode);
1054+ }
1055+
1056+ if (bitmask::any (dynamicDirtyBits, DynamicStateBits::FrontFace))
1057+ {
1058+ vkCmdSetFrontFaceEXT (_currentCmdBuffer, _cachedFrontFace);
1059+ bitmask::clear (dynamicDirtyBits, DynamicStateBits::FrontFace);
1060+ }
9931061}
9941062
9951063void RenderContextImpl::drawArrays (PrimitiveType primitiveType,
0 commit comments