Latte+Vulkan: Optimize by using incremental state update checking

This commit is contained in:
Exzap 2026-06-16 22:59:11 +02:00
parent c7ee701edd
commit 6d050e68ea
9 changed files with 475 additions and 156 deletions

View File

@ -240,6 +240,7 @@ void _fetchShaderDecompiler_parseInstruction_VTX_SEMANTIC(LatteFetchShader* pars
else
attribGroup = &parsedFetchShader->bufferGroupsInvalid.emplace_back();
parsedFetchShader->attributeBufferMask |= (1 << bufferIndex);
attribGroup->attributeBufferIndex = bufferIndex;
attribGroup->minOffset = offset;
attribGroup->maxOffset = offset;

View File

@ -42,6 +42,7 @@ struct LatteFetchShader
std::vector<LatteParsedFetchShaderBufferGroup_t> bufferGroupsInvalid; // groups with buffer index not being a valid buffer (dst components of these can affect shader code, but no actual vertex imports are done)
uint64 key{};
uint32 attributeBufferMask{}; // mask of buffers sourced by this fetch shader
// Vulkan
uint64 vkPipelineHashFragment{}; // hash of all fetch shader state that influences the Vulkan graphics pipeline

View File

@ -76,7 +76,12 @@ struct LatteDrawcallContext
{
bool isFirst{}; // first _execute() in current sequence
// these are only valid if isFirst is false:
// todo
mutable uint32 vertexBufferDirtyMask{}; // mask of vertex buffer indices which have been modified since last draw execute. It's the responsibility of the backend to unset bits
mutable uint32 vsUniformBufferDirtyMask{}; // mask of uniform buffer indices which have been modified (changed address or size) since last draw execute. It's the responsibility of the backend to unset bits
mutable uint32 psUniformBufferDirtyMask{};
mutable uint32 gsUniformBufferDirtyMask{};
mutable bool aluConstVSDirty{};
mutable bool aluConstPSDirty{};
};
// texture
@ -164,7 +169,9 @@ void LatteCP_ProcessRingbuffer();
// buffer cache
bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount);
void LatteBufferCache_SyncIncremental(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask);
void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData);
bool LatteBufferCache_LoadRemappedUniformsIncremental(LatteDecompilerShader* shader, float* uniformData, const LatteDrawcallContext& drawcallContext);
void LatteRenderTarget_updateViewport();

View File

@ -128,34 +128,125 @@ void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float*
}
}
void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType)
// like LatteBufferCache_LoadRemappedUniforms but only updates data that is flagged as dirty in drawcallContext, returns true if any uniform value was updated.
bool LatteBufferCache_LoadRemappedUniformsIncremental(LatteDecompilerShader* shader, float* uniformData, const LatteDrawcallContext& drawcallContext)
{
if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
bool hasChange = false;
uint32 shaderAluConst;
uint32 shaderUniformRegisterOffset;
bool hasDirtyAluConst = false;
uint32 uniformBufferDirtyMask = 0;
switch (shader->shaderType)
{
for(const auto& buf : shader->list_quickBufferList)
case LatteConst::ShaderType::Vertex:
shaderAluConst = 0x400;
shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START;
hasDirtyAluConst = drawcallContext.aluConstVSDirty;
uniformBufferDirtyMask = drawcallContext.vsUniformBufferDirtyMask;
drawcallContext.vsUniformBufferDirtyMask = 0;
break;
case LatteConst::ShaderType::Pixel:
shaderAluConst = 0;
shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START;
hasDirtyAluConst = drawcallContext.aluConstPSDirty;
uniformBufferDirtyMask = drawcallContext.psUniformBufferDirtyMask;
drawcallContext.psUniformBufferDirtyMask = 0;
break;
case LatteConst::ShaderType::Geometry:
shaderAluConst = 0; // geometry shader has no ALU const
shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START;
uniformBufferDirtyMask = drawcallContext.gsUniformBufferDirtyMask;
drawcallContext.gsUniformBufferDirtyMask = 0;
break;
default:
UNREACHABLE;
}
// sourced from uniform registers
if (hasDirtyAluConst)
{
uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst;
for (auto it : shader->list_remappedUniformEntries_register)
{
sint32 i = buf.index;
MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0];
uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1;
if (physicalAddr == MPTR_NULL) [[unlikely]]
{
g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0);
uint64* __restrict uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4);
uint64* __restrict regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset);
regDest[0] = uniformRegData[0];
regDest[1] = uniformRegData[1];
}
if (!shader->list_remappedUniformEntries_register.empty())
hasChange = true;
}
// sourced from uniform buffers
if (uniformBufferDirtyMask)
{
for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups)
{
if ((uniformBufferDirtyMask&(1<<bufferGroup.bufferId)) == 0)
continue;
MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4];
if (physicalAddr)
{
uint8* __restrict uniformBase = memory_base + physicalAddr;
for (auto& it : bufferGroup.entries)
{
uint64* __restrict regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset);
uint64* __restrict uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset);
memcpy(regDest, uniformEntrySrc, 16);
}
}
uniformSize = std::min<uint32>(uniformSize, buf.size);
uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize);
g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize);
else
{
for (auto& it : bufferGroup.entries)
{
uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset);
regDest[0] = 0;
regDest[1] = 0;
}
}
hasChange = true;
}
}
return hasChange;
}
bool LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType, uint32 bufferDirtyMask)
{
cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK);
bool hasChange = false;
for(const auto& buf : shader->list_quickBufferList)
{
sint32 i = buf.index;
if ((bufferDirtyMask&(1<<i)) == 0)
continue;
hasChange = true;
MPTR physicalAddr = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 0];
uint32 uniformSize = LatteGPUState.contextRegister[uniformBufferRegOffset + i * 7 + 1] + 1;
if (physicalAddr == MPTR_NULL) [[unlikely]]
{
g_renderer->buffer_bindUniformBuffer(shaderType, i, 0, 0);
continue;
}
uniformSize = std::min<uint32>(uniformSize, buf.size);
uint32 bindOffset = LatteBufferCache_retrieveDataInCache(physicalAddr, uniformSize);
g_renderer->buffer_bindUniformBuffer(shaderType, i, bindOffset, uniformSize);
}
return hasChange;
}
// for detecting when vertex buffer size needs to be extended during incremental rendering
static sint32 s_vtxStateMaxIndex{};
static sint32 s_vtxStateMaxInstance{};
// upload vertex and uniform buffers
bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount)
{
static uint32 s_syncBufferCounter = 0;
s_vtxStateMaxIndex = maxIndex;
s_vtxStateMaxInstance = baseInstance + instanceCount - 1;
s_syncBufferCounter++;
if (s_syncBufferCounter >= 30)
if (s_syncBufferCounter >= 20)
{
LatteBufferCache_incrementalCleanup();
s_syncBufferCounter = 0;
@ -217,13 +308,112 @@ bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance
}
// sync uniform buffers
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
if (vertexShader)
LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex);
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
if (geometryShader)
LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry);
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
if (pixelShader)
LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel);
if (vertexShader && vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex, 0xFFFFFFFF);
if (pixelShader && pixelShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel, 0xFFFFFFFF);
if (geometryShader && geometryShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry, 0xFFFFFFFF);
return true;
}
// similar to LatteBufferCache_SyncIncremental but updates only the bindings that are marked in the dirty masks
void LatteBufferCache_SyncIncremental(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask)
{
// uint32 attribBufferDirtyMask, uint32 uniformBufferVSDirtyMask, uint32 uniformBufferPSDirtyMask, uint32 uniformBufferGSDirtyMask
LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader();
cemu_assert_debug(parsedFetchShader);
uint32 attribBufferDirtyMask = drawcallContext.vertexBufferDirtyMask;
if ( maxIndex > s_vtxStateMaxIndex )
{
attribBufferDirtyMask = 0xFFFFFFFF;
// todo - if the fetch shader can change between incremental calls, then we should also mark all of drawcallContext.vertexBufferDirtyMask as dirty. Or we could track the count per-buffer instead of as max index and max instance
// but for simplicity sake we should disallow fetch shader changes that use a differing set of buffers (this is already the case)
s_vtxStateMaxIndex = maxIndex;
}
uint32 maxInstance = baseInstance + instanceCount - 1;
if ( maxInstance > s_vtxStateMaxInstance )
{
attribBufferDirtyMask = 0xFFFFFFFF;
s_vtxStateMaxInstance = maxInstance;
}
attribBufferDirtyMask &= parsedFetchShader->attributeBufferMask;
// dont process flush queue and dont process deallocations yet, we are in the middle of a sequence of drawcalls that (most likely) reuse previous bindings
// sync and bind dirty vertex buffers
if (attribBufferDirtyMask != 0)
{
uint32* __restrict bufferRegStartPtr = LatteGPUState.contextRegister + mmSQ_VTX_ATTRIBUTE_BLOCK_START;
for (auto& bufferGroup : parsedFetchShader->bufferGroups)
{
uint32 bufferIndex = bufferGroup.attributeBufferIndex;
if ((attribBufferDirtyMask&(1<<bufferIndex)) == 0)
continue;
drawcallContext.vertexBufferDirtyMask &= ~(1<<bufferIndex);
uint32* __restrict bufferRegs = bufferRegStartPtr + bufferIndex * 7;
MPTR bufferAddress = bufferRegs[0];
uint32 bufferStride = (bufferRegs[2] >> 11) & 0xFFFF;
if (bufferAddress == MPTR_NULL) [[unlikely]]
{
g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0);
continue;
}
// dont rely on buffer size given by game
uint32 fixedBufferSize = 0;
if (bufferGroup.hasVtxIndexAccess)
fixedBufferSize = bufferStride * (maxIndex + 1) + bufferGroup.maxOffset;
if (bufferGroup.hasInstanceIndexAccess)
{
uint32 fixedBufferSizeInstance = bufferStride * ((baseInstance + instanceCount) + 1) + bufferGroup.maxOffset;
fixedBufferSize = std::max(fixedBufferSize, fixedBufferSizeInstance);
}
if (fixedBufferSize == 0 || bufferStride == 0)
fixedBufferSize += 128;
#if BOOST_OS_MACOS && defined(ENABLE_VULKAN)
if(bufferStride % 4 != 0)
{
if (g_renderer->GetType() == RendererAPI::Vulkan)
{
if (VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance())
{
auto fixedBuffer = vkRenderer->buffer_genStrideWorkaroundVertexBuffer(bufferAddress, fixedBufferSize, bufferStride);
vkRenderer->buffer_bindVertexStrideWorkaroundBuffer(fixedBuffer.first, fixedBuffer.second, bufferIndex, fixedBufferSize);
continue;
}
}
}
#endif
uint32 bindOffset = LatteBufferCache_retrieveDataInCache(bufferAddress, fixedBufferSize);
g_renderer->buffer_bindVertexBuffer(bufferIndex, bindOffset, fixedBufferSize);
}
}
// sync uniform buffers
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
// todo - if we AND the shader uniform buffer mask and the dirty mask we can completely skip calling syncGPUUniformBuffers if no relevant buffer was updated
if (vertexShader && vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
{
if (LatteBufferCache_syncGPUUniformBuffers(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex, drawcallContext.vsUniformBufferDirtyMask))
stageUniformModifiedMask |= (1<<VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX);
}
if (pixelShader && pixelShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
{
if (LatteBufferCache_syncGPUUniformBuffers(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel, drawcallContext.psUniformBufferDirtyMask))
stageUniformModifiedMask |= (1<<VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT); // todo - move this enum to Latte?
}
if (geometryShader && geometryShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
{
if ( LatteBufferCache_syncGPUUniformBuffers(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry, drawcallContext.gsUniformBufferDirtyMask) )
stageUniformModifiedMask |= (1<<VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY);
}
}

View File

@ -52,8 +52,12 @@ public:
{
m_drawPassActive = true;
m_drawcallContext.isFirst = true;
m_vertexBufferChanged = true;
m_uniformBufferChanged = true;
m_drawcallContext.vertexBufferDirtyMask = 0;
m_drawcallContext.vsUniformBufferDirtyMask = 0;
m_drawcallContext.psUniformBufferDirtyMask = 0;
m_drawcallContext.gsUniformBufferDirtyMask = 0;
m_drawcallContext.aluConstVSDirty = false;
m_drawcallContext.aluConstPSDirty = false;
g_renderer->draw_beginSequence();
}
@ -79,8 +83,7 @@ public:
if (!m_drawcallContext.isFirst)
performanceMonitor.cycle[performanceMonitor.cycleIndex].fastDrawCallCounter++;
m_drawcallContext.isFirst = false;
m_vertexBufferChanged = false;
m_uniformBufferChanged = false;
m_drawcallContext.vertexBufferDirtyMask = 0;
}
void endDrawPass()
@ -89,14 +92,34 @@ public:
m_drawPassActive = false;
}
void notifyModifiedVertexBuffer()
void MarkVertexBufferDirty(uint32 index)
{
m_vertexBufferChanged = true;
m_drawcallContext.vertexBufferDirtyMask |= (1<<index);
}
void notifyModifiedUniformBuffer()
void MarkVSAluConstantsDirty()
{
m_uniformBufferChanged = true;
m_drawcallContext.aluConstVSDirty = true;
}
void MarkPSAluConstantsDirty()
{
m_drawcallContext.aluConstPSDirty = true;
}
void MarkVSUniformBufferDirty(uint32 index)
{
m_drawcallContext.vsUniformBufferDirtyMask |= (1 << index);
}
void MarkPSUniformBufferDirty(uint32 index)
{
m_drawcallContext.psUniformBufferDirtyMask |= (1 << index);
}
void MarkGSUniformBufferDirty(uint32 index)
{
m_drawcallContext.gsUniformBufferDirtyMask |= (1 << index);
}
// command buffer processing position
@ -120,8 +143,6 @@ public:
private:
bool m_drawPassActive{ false };
LatteDrawcallContext m_drawcallContext{};
bool m_vertexBufferChanged{ false };
bool m_uniformBufferChanged{ false };
boost::container::static_vector<CmdQueuePos, 4> m_queuePosStack;
};
@ -334,7 +355,7 @@ bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCal
const uint32 registerOffset = LatteReadCMD();
const uint32 registerIndex = TRegisterBase + registerOffset;
const uint32 registerStartIndex = registerIndex;
const uint32 registerEndIndex = registerStartIndex + nWords;
const uint32 registerEndIndex = registerStartIndex + nWords - 1;
cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER);
uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex);
@ -996,25 +1017,34 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt
{
LatteCP_itSetRegistersGeneric2<LATTE_REG_BASE_RESOURCE>(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd, bool regValuesChanged)
{
if (!regValuesChanged)
return;
if ((registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) ||
(registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)) ||
(registerStart >= Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS && registerStart < (Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS + Latte::GPU_LIMITS::NUM_TEXTURES_PER_STAGE * 7)))
{
if (regValuesChanged)
{
// cemuLog_log(LogType::Force, "[FastDrawMode] End due to texture change");
drawPassCtx.endDrawPass(); // texture updates end the current draw sequence
}
// cemuLog_log(LogType::Force, "[FastDrawMode] End due to texture change");
drawPassCtx.endDrawPass(); // texture updates end the current draw sequence
}
else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END)
{
if (regValuesChanged)
drawPassCtx.notifyModifiedVertexBuffer();
uint32 bufferIndex = (registerStart - mmSQ_VTX_ATTRIBUTE_BLOCK_START) / 7;
drawPassCtx.MarkVertexBufferDirty(bufferIndex);
}
else
else if (registerStart >= mmSQ_VTX_UNIFORM_BLOCK_START && registerEnd <= mmSQ_VTX_UNIFORM_BLOCK_END)
{
if (regValuesChanged)
drawPassCtx.notifyModifiedUniformBuffer();
uint32 bufferIndex = (registerStart - mmSQ_VTX_UNIFORM_BLOCK_START) / 7;
drawPassCtx.MarkVSUniformBufferDirty(bufferIndex);
}
else if (registerStart >= mmSQ_PS_UNIFORM_BLOCK_START && registerEnd <= mmSQ_PS_UNIFORM_BLOCK_END)
{
uint32 bufferIndex = (registerStart - mmSQ_PS_UNIFORM_BLOCK_START) / 7;
drawPassCtx.MarkPSUniformBufferDirty(bufferIndex);
}
else if (registerStart >= mmSQ_GS_UNIFORM_BLOCK_START && registerEnd <= mmSQ_GS_UNIFORM_BLOCK_END)
{
uint32 bufferIndex = (registerStart - mmSQ_GS_UNIFORM_BLOCK_START) / 7;
drawPassCtx.MarkGSUniformBufferDirty(bufferIndex);
}
});
if (!drawPassCtx.isWithinDrawPass())
@ -1026,7 +1056,15 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt
}
case IT_SET_ALU_CONST: // uniform register
{
LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_ALU_CONST>(cmdData, nWords);
LatteCP_itSetRegistersGeneric2<LATTE_REG_BASE_ALU_CONST>(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd, bool regValuesChanged) {
if (!regValuesChanged)
return;
if ( registerStart >= (mmSQ_ALU_CONSTANT0_0 + 0x400) )
drawPassCtx.MarkVSAluConstantsDirty();
else
drawPassCtx.MarkPSAluConstantsDirty();
// todo - we could further optimize by tracking the min/max range of modified ALU constants
});
break;
}
case IT_SET_CTL_CONST:

View File

@ -1038,7 +1038,7 @@ void _LatteDecompiler_GenerateDataForFastAccess(LatteDecompilerShader* shader)
else
{
LatteFastAccessRemappedUniformEntry_buffer_t entryBuf;
uint32 kcacheBankIdOffset = entry->kcacheBankId* (7 * 4);
uint32 kcacheBankIdOffset = entry->kcacheBankId * (7 * 4);
entryBuf.indexOffset = entry->index * 16;
entryBuf.mappedIndexOffset = entry->mappedIndex * 16;
// find or create buffer group
@ -1049,7 +1049,7 @@ void _LatteDecompiler_GenerateDataForFastAccess(LatteDecompilerShader* shader)
}
else
{
shader->list_remappedUniformEntries_bufferGroups.emplace_back(kcacheBankIdOffset).entries.emplace_back(entryBuf);
shader->list_remappedUniformEntries_bufferGroups.emplace_back(entry->kcacheBankId, kcacheBankIdOffset).entries.emplace_back(entryBuf);
}
}
}

View File

@ -209,9 +209,9 @@ struct LatteDecompilerShader
// fast access
struct _RemappedUniformBufferGroup
{
_RemappedUniformBufferGroup(uint32 _kcacheBankIdOffset) : kcacheBankIdOffset(_kcacheBankIdOffset) {};
uint32 kcacheBankIdOffset;
_RemappedUniformBufferGroup(uint16 bufferId, uint16 _kcacheBankIdOffset) : bufferId(bufferId), kcacheBankIdOffset(_kcacheBankIdOffset) {};
uint16 bufferId;
uint16 kcacheBankIdOffset;
std::vector<LatteFastAccessRemappedUniformEntry_buffer_t> entries;
};
std::vector<LatteFastAccessRemappedUniformEntry_register_t> list_remappedUniformEntries_register;

View File

@ -369,7 +369,6 @@ private:
VkDescriptorSetInfo* activeGeometryDS{ nullptr };
bool descriptorSetsChanged{ false };
bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget
// viewport and scissor box
VkViewport currentViewport{};
VkRect2D currentScissorRect{};
@ -551,7 +550,8 @@ private:
// uniform
uint32 uniformData_uploadUniformDataBufferGetOffset(std::span<uint8, std::dynamic_extent> data);
void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader);
void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader, float* __restrict uniformBuf);
void uniformData_updateUniformVarsIncremental(uint32 shaderStageIndex, LatteDecompilerShader* shader, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask, float* __restrict uniformBuf);
// misc
void CreatePipelineCache();

View File

@ -311,7 +311,9 @@ void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation)
memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal);
}
float s_vkUniformData[512 * 4];
float s_vkUniformDataVS[512 * 4];
float s_vkUniformDataPS[512 * 4];
float s_vkUniformDataGS[512 * 4];
uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span<uint8> data)
{
@ -375,84 +377,135 @@ uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span<ui
return uniformOffset;
}
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader)
void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader, float* __restrict uniformBuf)
{
auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); };
sint32 shaderAluConst;
switch (shader->shaderType)
auto GET_UNIFORM_DATA_PTR = [&uniformBuf](size_t index) { return uniformBuf + (index / 4); };
if (shader->resourceMapping.uniformVarsBufferBindingPoint < 0)
return;
if (shader->uniform.list_ufTexRescale.empty() == false)
{
case LatteConst::ShaderType::Vertex:
shaderAluConst = 0x400;
break;
case LatteConst::ShaderType::Pixel:
shaderAluConst = 0;
break;
case LatteConst::ShaderType::Geometry:
shaderAluConst = 0; // geometry shader has no ALU const
break;
default:
UNREACHABLE;
}
if (shader->resourceMapping.uniformVarsBufferBindingPoint >= 0)
{
if (shader->uniform.list_ufTexRescale.empty() == false)
for (auto& entry : shader->uniform.list_ufTexRescale)
{
for (auto& entry : shader->uniform.list_ufTexRescale)
float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit);
memcpy(entry.currentValue, xyScale, sizeof(float) * 2);
memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2);
}
}
if (shader->uniform.loc_alphaTestRef >= 0)
{
*GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF();
}
if (shader->uniform.loc_pointSize >= 0)
{
const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE;
float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f;
if (pointWidth == 0.0f)
pointWidth = 1.0f / 8.0f; // minimum size
*GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth;
}
if (shader->uniform.loc_remapped >= 0)
{
LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped));
}
if (shader->uniform.loc_uniformRegister >= 0)
{
sint32 shaderAluConst;
switch (shader->shaderType)
{
case LatteConst::ShaderType::Vertex:
shaderAluConst = 0x400;
break;
case LatteConst::ShaderType::Pixel:
shaderAluConst = 0;
break;
case LatteConst::ShaderType::Geometry:
shaderAluConst = 0; // geometry shader has no ALU const
break;
default:
UNREACHABLE;
}
uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst);
memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16);
}
if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0)
{
sint32 viewportWidth;
sint32 viewportHeight;
LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport()
float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform);
v[0] = 2.0f / (float)viewportWidth;
v[1] = 2.0f / (float)viewportHeight;
}
if (shader->uniform.loc_fragCoordScale >= 0)
{
LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale));
}
if (shader->uniform.loc_verticesPerInstance >= 0)
{
*(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance;
for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++)
{
if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
{
float* xyScale = LatteTexture_getEffectiveTextureScale(shader->shaderType, entry.texUnit);
memcpy(entry.currentValue, xyScale, sizeof(float) * 2);
memcpy(GET_UNIFORM_DATA_PTR(entry.uniformLocation), xyScale, sizeof(float) * 2);
*(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_streamoutState.buffer[b].ringBufferOffset;
}
}
if (shader->uniform.loc_alphaTestRef >= 0)
}
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)uniformBuf, shader->uniform.uniformRangeSize});
}
void VulkanRenderer::uniformData_updateUniformVarsIncremental(uint32 shaderStageIndex, LatteDecompilerShader* shader, const LatteDrawcallContext& drawcallContext, uint8& stageUniformModifiedMask, float* __restrict uniformBuf)
{
// similar to uniformData_updateUniformVars, but checks only the fields that can change during a fast draw sequence
auto GET_UNIFORM_DATA_PTR = [&uniformBuf](size_t index) { return uniformBuf + (index / 4); };
if (shader->resourceMapping.uniformVarsBufferBindingPoint < 0)
return;
// list_ufTexRescale -> Skipped because textures do not change
// loc_alphaTestRef -> Skipped because modifying SX_ALPHA_REF ends sequence
// loc_pointSize -> Skipped because modifying PA_SU_POINT_SIZE ends sequence
// loc_windowSpaceToClipSpaceTransform/loc_fragCoordScale/ -> Skipped because viewport doesn't change
// loc_verticesPerInstance -> Skipped because sequences with streamout enabled are not allowed
bool hasChange = false; // todo - For loc_remapped and loc_uniformRegister we want to pass a mask of dirty uniform var / buffers. If nothing is dirty we can also skip it
if (shader->uniform.loc_remapped >= 0)
{
cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_REMAPPED);
hasChange = LatteBufferCache_LoadRemappedUniformsIncremental(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), drawcallContext);
}
if (shader->uniform.loc_uniformRegister >= 0)
{
cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE);
bool isDirty = false;
sint32 shaderAluConst;
switch (shader->shaderType)
{
*GET_UNIFORM_DATA_PTR(shader->uniform.loc_alphaTestRef) = LatteGPUState.contextNew.SX_ALPHA_REF.get_ALPHA_TEST_REF();
case LatteConst::ShaderType::Vertex:
shaderAluConst = 0x400;
isDirty = drawcallContext.aluConstVSDirty;
break;
case LatteConst::ShaderType::Pixel:
shaderAluConst = 0;
isDirty = drawcallContext.aluConstPSDirty;
break;
case LatteConst::ShaderType::Geometry:
cemu_assert_suspicious();
shaderAluConst = 0;
UNREACHABLE; // geometry shader has no ALU const
break;
default:
UNREACHABLE;
}
if (shader->uniform.loc_pointSize >= 0)
{
const auto& pointSizeReg = LatteGPUState.contextNew.PA_SU_POINT_SIZE;
float pointWidth = (float)pointSizeReg.get_WIDTH() / 8.0f;
if (pointWidth == 0.0f)
pointWidth = 1.0f / 8.0f; // minimum size
*GET_UNIFORM_DATA_PTR(shader->uniform.loc_pointSize) = pointWidth;
}
if (shader->uniform.loc_remapped >= 0)
{
LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped));
}
if (shader->uniform.loc_uniformRegister >= 0)
if (isDirty)
{
hasChange = true;
uint32* uniformRegData = (uint32*)(LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst);
memcpy(GET_UNIFORM_DATA_PTR(shader->uniform.loc_uniformRegister), uniformRegData, shader->uniform.count_uniformRegister * 16);
}
if (shader->uniform.loc_windowSpaceToClipSpaceTransform >= 0)
{
sint32 viewportWidth;
sint32 viewportHeight;
LatteRenderTarget_GetCurrentVirtualViewportSize(&viewportWidth, &viewportHeight); // always call after _updateViewport()
float* v = GET_UNIFORM_DATA_PTR(shader->uniform.loc_windowSpaceToClipSpaceTransform);
v[0] = 2.0f / (float)viewportWidth;
v[1] = 2.0f / (float)viewportHeight;
}
if (shader->uniform.loc_fragCoordScale >= 0)
{
LatteMRT::GetCurrentFragCoordScale(GET_UNIFORM_DATA_PTR(shader->uniform.loc_fragCoordScale));
}
if (shader->uniform.loc_verticesPerInstance >= 0)
{
*(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance;
for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++)
{
if (shader->uniform.loc_streamoutBufferBase[b] >= 0)
{
*(uint32*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_streamoutBufferBase[b]) = m_streamoutState.buffer[b].ringBufferOffset;
}
}
}
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)s_vkUniformData, shader->uniform.uniformRangeSize});
}
if (hasChange)
{
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)uniformBuf, shader->uniform.uniformRangeSize});
stageUniformModifiedMask |= (1 << shaderStageIndex);
}
}
@ -1331,11 +1384,11 @@ void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance,
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
if (vertexShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, s_vkUniformDataVS);
if (pixelShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, s_vkUniformDataPS);
if (geometryShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, s_vkUniformDataGS);
// store where the read pointer should go after command buffer execution
m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex;
@ -1497,12 +1550,15 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
uint8 stageUniformModifiedMask = 0; // one bit for each stage (using SHADER_STAGE_INDEX_* as bit index). Set if any uniform data has been modified and needs to be reuploaded
if (vertexShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader);
uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataVS);
if (pixelShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader);
uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataPS);
if (geometryShader)
uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader);
uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, drawcallContext, stageUniformModifiedMask, s_vkUniformDataGS);
drawcallContext.aluConstVSDirty = false;
drawcallContext.aluConstPSDirty = false;
// store where the read pointer should go after command buffer execution
m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex;
@ -1554,20 +1610,25 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan
else
{
// synchronize vertex and uniform cache and update buffer bindings
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
LatteBufferCache_SyncIncremental(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount, drawcallContext, stageUniformModifiedMask);
}
PipelineInfo* pipeline_info;
m_state.descriptorSetsChanged = false;
PipelineInfo* pipeline_info = m_state.activePipelineInfo;
// recalculate the part of the pipeline hash that can change during fast draws
if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew))
if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew)) [[unlikely]]
{
// pipeline changed
pipeline_info = draw_getOrCreateGraphicsPipeline(count);
m_state.activePipelineInfo = pipeline_info;
// if the pipeline is changed then we also need to get the descriptor sets
draw_prepareDescriptorSets(pipeline_info, m_state.activeVertexDS, m_state.activePixelDS, m_state.activeGeometryDS);
m_state.descriptorSetsChanged = true;
stageUniformModifiedMask = 0x7;
}
else
{
pipeline_info = m_state.activePipelineInfo;
// sanity check that we are using the right pipeline
#ifdef CEMU_DEBUG_ASSERT
auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count);
if (pipeline_info != pipeline_info2)
@ -1581,7 +1642,7 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan
if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE)
{
// invalid/uninitialized pipeline
m_state.activeVertexDS = nullptr;
//m_state.activeVertexDS = nullptr;
return;
}
@ -1591,15 +1652,10 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan
vertexDS = m_state.activeVertexDS;
pixelDS = m_state.activePixelDS;
geometryDS = m_state.activeGeometryDS;
m_state.descriptorSetsChanged = false;
}
else
{
draw_prepareDescriptorSets(pipeline_info, vertexDS, pixelDS, geometryDS);
m_state.activeVertexDS = vertexDS;
m_state.activePixelDS = pixelDS;
m_state.activeGeometryDS = geometryDS;
m_state.descriptorSetsChanged = true;
__debugbreak(); // should never happen unless draw_prepareDescriptorSets can fail?
}
draw_setRenderPass();
@ -1625,32 +1681,58 @@ void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstan
// update descriptor sets
uint32_t dynamicOffsets[17 * 2];
if (vertexDS && pixelDS)
VkDescriptorSet dsArray[2];
sint32 dsArraySize = 0;
sint32 dsArrayBase = 0;
sint32 numDynOffsets = 0;
if (vertexDS && (stageUniformModifiedMask&(1<<VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX)))
{
// update vertex and pixel descriptor set in a single call to vkCmdBindDescriptorSets
sint32 numDynOffsetsVS;
sint32 numDynOffsetsPS;
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsetsVS, pipeline_info);
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsetsVS, numDynOffsetsPS, pipeline_info);
VkDescriptorSet dsArray[2];
sint32 tmpNumDynOffsets = 0;
dsArray[0] = vertexDS->m_vkObjDescriptorSet->descriptorSet;
dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet;
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, tmpNumDynOffsets, pipeline_info);
dsArraySize = 1;
numDynOffsets += tmpNumDynOffsets;
}
if (pixelDS && (stageUniformModifiedMask&(1<<VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT)))
{
sint32 tmpNumDynOffsets = 0;
dsArrayBase = 1 - dsArraySize;
dsArray[dsArraySize] = pixelDS->m_vkObjDescriptorSet->descriptorSet;
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsets, tmpNumDynOffsets, pipeline_info);
dsArraySize++;
numDynOffsets += tmpNumDynOffsets;
}
if (dsArraySize > 0)
{
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, dsArrayBase, dsArraySize, dsArray, numDynOffsets, dynamicOffsets);
}
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets);
}
else if (vertexDS)
{
sint32 numDynOffsets;
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info);
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets);
}
else if (pixelDS)
{
sint32 numDynOffsets;
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info);
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets);
}
// if (vertexDS && pixelDS)
// {
// // update vertex and pixel descriptor set in a single call to vkCmdBindDescriptorSets
// sint32 numDynOffsetsVS;
// sint32 numDynOffsetsPS;
// draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsetsVS, pipeline_info);
// draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets + numDynOffsetsVS, numDynOffsetsPS, pipeline_info);
//
// VkDescriptorSet dsArray[2];
// dsArray[0] = vertexDS->m_vkObjDescriptorSet->descriptorSet;
// dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet;
//
// vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets);
// }
// else if (vertexDS)
// {
// sint32 numDynOffsets;
// draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info);
// vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets);
// }
// else if (pixelDS)
// {
// sint32 numDynOffsets;
// draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info);
// vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets);
// }
if (geometryDS)
{
sint32 numDynOffsets;