Latte+Vulkan: Several smaller optimizations

This commit is contained in:
Exzap 2026-06-15 04:07:57 +02:00
parent 321fa4d74d
commit 377307515c
9 changed files with 65 additions and 64 deletions

View File

@ -1,9 +1,6 @@
#include "Cafe/HW/Latte/Core/LatteConst.h"
#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "Cafe/OS/libs/gx2/GX2.h"
#include "Cafe/HW/Latte/Core/Latte.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h"
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h"
#include "Cafe/HW/Latte/Core/FetchShader.h"
@ -503,7 +500,7 @@ LatteFetchShader::CacheHash LatteFetchShader::CalculateCacheHash(void* programCo
LatteFetchShader* LatteFetchShader::FindInCacheByHash(LatteFetchShader::CacheHash fsHash)
{
// does not hold s_fetchShaderCache for better performance. Be careful not to call this while another thread invokes RegisterInCache()
// does not hold s_spinlockFetchShaderCache for better performance. Be careful not to call this while another thread invokes RegisterInCache()
auto itr = s_fetchShaderByHash.find(fsHash);
if (itr == s_fetchShaderByHash.end())
return nullptr;
@ -539,6 +536,11 @@ LatteFetchShader* LatteFetchShader::FindByGPUState()
}
// update lookup info
CacheHash fsHash = CalculateCacheHash(_getFSProgramPtr(), _getFSProgramSize());
if (lookupInfo->fetchShader->m_cacheHash == fsHash && lookupInfo->programSize == fsSize) // check if its still the same hash
{
lookupInfo->lastFrameAccessed = LatteGPUState.frameCounter;
return lookupInfo->fetchShader;
}
LatteFetchShader* fetchShader = FindInCacheByHash(fsHash);
if (!fetchShader)
{

View File

@ -171,15 +171,16 @@ bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance
LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader();
if (!parsedFetchShader)
return false;
uint32* __restrict bufferRegStartPtr = LatteGPUState.contextRegister + mmSQ_VTX_ATTRIBUTE_BLOCK_START;
for (auto& bufferGroup : parsedFetchShader->bufferGroups)
{
uint32 bufferIndex = bufferGroup.attributeBufferIndex;
uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7;
MPTR bufferAddress = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 0];
uint32 bufferSize = LatteGPUState.contextRegister[bufferBaseRegisterIndex + 1] + 1;
uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF;
uint32* __restrict bufferRegs = bufferRegStartPtr + bufferIndex * 7;
MPTR bufferAddress = bufferRegs[0];
/* uint32 bufferSize = bufferRegs[1] + 1; */
uint32 bufferStride = (bufferRegs[2] >> 11) & 0xFFFF;
if (bufferAddress == MPTR_NULL)
if (bufferAddress == MPTR_NULL) [[unlikely]]
{
g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0);
continue;

View File

@ -124,7 +124,7 @@ private:
bool m_isFirstDraw{false};
bool m_vertexBufferChanged{ false };
bool m_uniformBufferChanged{ false };
boost::container::small_vector<CmdQueuePos, 4> m_queuePosStack;
boost::container::static_vector<CmdQueuePos, 4> m_queuePosStack;
};
void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx);
@ -299,11 +299,11 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords)
#ifdef CEMU_DEBUG_ASSERT
cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER);
#endif
uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex);
uint32* __restrict outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex);
if (LatteGPUState.contextControl0 == 0x80000077)
{
// state shadowing enabled
uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex;
uint32* __restrict shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex;
sint32 indexCounter = 0;
while (--nWords)
{
@ -318,7 +318,6 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords)
else
{
// state shadowing disabled
sint32 indexCounter = 0;
while (--nWords)
{
*outputReg = LatteReadCMD();

View File

@ -325,22 +325,17 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN
// get mask of all used color buffers
uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShader, const LatteContextRegister& lcr)
{
if (!pixelShader) [[unlikely]]
return 0;
const uint32* regView = lcr.GetRawView();
uint8 colorBufferMask = 0;
for (uint32 i = 0; i < 8; i++)
{
if (regView[mmCB_COLOR0_BASE + i] != MPTR_NULL)
colorBufferMask |= (1 << i);
}
// check if color buffer output is active
const Latte::LATTE_CB_COLOR_CONTROL& colorControlReg = lcr.CB_COLOR_CONTROL;
uint32 colorBufferDisable = colorControlReg.get_SPECIAL_OP() == Latte::LATTE_CB_COLOR_CONTROL::E_SPECIALOP::DISABLE;
if (colorBufferDisable)
return 0;
cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported
// combine color buffer mask with pixel output mask from pixel shader
colorBufferMask &= (pixelShader ? pixelShader->pixelColorOutputMask : 0);
// start with color buffer mask from pixel shader output
uint8 colorBufferMask = pixelShader->pixelColorOutputMask;
// combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked)
uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK();
for (uint32 i = 0; i < 8; i++)
@ -348,9 +343,9 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade
if (((channelTargetMask >> (i * 4)) & 0xF) == 0)
colorBufferMask &= ~(1 << i);
}
// render targets smaller than the scissor size are not allowed
// this fixes a few render issues in Cemu but we dont know if this matches HW behavior
// also check for color buffers without a valid pointer
cemu_assert_debug(lcr.PA_SC_GENERIC_SCISSOR_TL.get_WINDOW_OFFSET_DISABLE() == true); // todo (not exposed by GX2 API)
uint32 scissorAccessWidth = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_X();
uint32 scissorAccessHeight = lcr.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y();
@ -358,6 +353,8 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade
{
if( (colorBufferMask&(1<<i)) == 0 )
continue;
if (regView[mmCB_COLOR0_BASE + i] == MPTR_NULL) [[unlikely]]
colorBufferMask &= ~(1 << i);
// get width/height
uint32 regColorSize = regView[mmCB_COLOR0_SIZE + i];
uint32 regColorInfo = regView[mmCB_COLOR0_INFO + i];
@ -369,15 +366,12 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade
uint32 colorBufferHeight = pitchHeight / colorBufferPitch;
uint32 colorBufferWidth = colorBufferPitch;
if ((colorBufferWidth < (sint32)scissorAccessWidth) ||
(colorBufferHeight < (sint32)scissorAccessHeight))
if ((colorBufferWidth < (sint32)scissorAccessWidth) || (colorBufferHeight < (sint32)scissorAccessHeight))
{
// log this?
colorBufferMask &= ~(1<<i);
}
}
return colorBufferMask;
}

View File

@ -217,7 +217,7 @@ void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint3
uint32 spi0_positionEnable = (psControl0 >> 8) & 1;
uint32 spi0_positionCentroid = (psControl0 >> 9) & 1;
cemu_assert_debug(spi0_positionCentroid == 0); // controls gl_FragCoord
uint32 spi0_positionAddr = (psControl0 >> 10) & 0x1F; // controls gl_FragCoord
uint32 spi0_positionAddr = spi0_positionEnable ? ((psControl0 >> 10) & 0x1F) : 0xFFFFFFFF; // controls gl_FragCoord
uint32 spi0_paramGen = (psControl0 >> 15) & 0xF; // used for gl_PointCoords
uint32 spi0_paramGenAddr = (psControl0 >> 19) & 0x7F;
sint32 importIndex = 0;
@ -278,7 +278,7 @@ void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint3
key += (uint64)psInputControl;
key = std::rotl<uint64>(key, 7);
if (spi0_positionEnable && f == spi0_positionAddr)
if (f == spi0_positionAddr)
{
psInputTable->import[f].semanticId = LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION;
psInputTable->import[f].isFlat = false;
@ -1053,7 +1053,7 @@ void LatteSHRC_UpdateActiveShaders()
vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8);
vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_ES + 1] << 3;
copyProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8);
if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0)
if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) [[unlikely]]
{
copyProgramCode = NULL;
debug_printf("copyProgram is NULL but used. Might be because of unsupported vertex/geometry mode?");
@ -1062,7 +1062,7 @@ void LatteSHRC_UpdateActiveShaders()
}
else
{
if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0)
if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0) [[unlikely]]
{
debug_printf("No vertex shader program set\n");
LatteGPUState.activeShaderHasError = true;
@ -1089,7 +1089,6 @@ void LatteSHRC_UpdateActiveShaders()
// returns the sampler base index for the given shader type
sint32 LatteDecompiler_getTextureSamplerBaseIndex(LatteConst::ShaderType shaderType)
{
uint32 samplerId = LATTE_DECOMPILER_SAMPLER_NONE;
if (shaderType == LatteConst::ShaderType::Vertex)
return Latte::SAMPLER_BASE_INDEX_VERTEX;
else if (shaderType == LatteConst::ShaderType::Pixel)

View File

@ -1365,10 +1365,8 @@ void LatteTexture_MarkConnectedTexturesForReloadFromDynamicTextures(LatteTexture
{
for (auto& it : texture->list_compatibleRelations)
{
if (texture == it->baseTexture)
it->subTexture->reloadFromDynamicTextures = true;
else
it->baseTexture->reloadFromDynamicTextures = true;
LatteTexture* connectedTexture = (texture == it->baseTexture) ? it->subTexture : it->baseTexture;
connectedTexture->reloadFromDynamicTextures = true;
}
}

View File

@ -334,6 +334,8 @@ void VulkanRenderer::GetDeviceFeatures()
m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4);
m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4);
cemuLog_log(LogType::Force, fmt::format("VulkanLimits: UBAlignment {0} nonCoherentAtomSize {1}", prop2.properties.limits.minUniformBufferOffsetAlignment, prop2.properties.limits.nonCoherentAtomSize));
// calculate used limits
m_featureControl.limits.calcUniformBufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
}
#if BOOST_OS_LINUX

View File

@ -470,6 +470,8 @@ private:
{
uint32 minUniformBufferOffsetAlignment = 256;
uint32 nonCoherentAtomSize = 256;
// calculated
uint32 calcUniformBufferAlignmentM1{};
}limits;
bool usingDebugMarkerTool{ false }; // validation layer or other tool capable of handling debug markers is used

View File

@ -315,7 +315,7 @@ float s_vkUniformData[512 * 4];
uint32 VulkanRenderer::uniformData_uploadUniformDataBufferGetOffset(std::span<uint8> data)
{
const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1;
const uint32 bufferAlignmentM1 = m_featureControl.limits.calcUniformBufferAlignmentM1;
const uint32 uniformSize = ((uint32)data.size() + bufferAlignmentM1) & ~bufferAlignmentM1;
auto waitWhileCondition = [&](std::function<bool()> condition) {
@ -481,40 +481,44 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader)
uint64 hash = 0;
const sint32 textureCount = shader->resourceMapping.getTextureCount();
for (int i = 0; i < textureCount; ++i)
{
const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i);
auto hostTextureUnit = relative_textureUnit;
auto textureDim = shader->textureUnitDim[relative_textureUnit];
auto texUnitRegIndex = hostTextureUnit * 7;
LatteTextureViewVk** texViewBase = m_state.boundTexture;
uint32* __restrict texRegBase = LatteGPUState.contextRegister;
uint32 samplerBaseIndex = 0;
switch (shader->shaderType)
{
case LatteConst::ShaderType::Vertex:
hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE;
texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS;
texViewBase += LATTE_CEMU_VS_TEX_UNIT_BASE;
texRegBase += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS;
samplerBaseIndex = Latte::SAMPLER_BASE_INDEX_VERTEX;
break;
case LatteConst::ShaderType::Pixel:
hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE;
texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS;
texViewBase += LATTE_CEMU_PS_TEX_UNIT_BASE;
texRegBase += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS;
samplerBaseIndex = Latte::SAMPLER_BASE_INDEX_PIXEL;
break;
case LatteConst::ShaderType::Geometry:
hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE;
texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS;
texViewBase += LATTE_CEMU_GS_TEX_UNIT_BASE;
texRegBase += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS;
samplerBaseIndex = Latte::SAMPLER_BASE_INDEX_GEOMETRY;
break;
default:
UNREACHABLE;
}
auto texture = m_state.boundTexture[hostTextureUnit];
for (int i = 0; i < textureCount; ++i)
{
const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i);
uint32* __restrict texRegs = texRegBase + relative_textureUnit * 7;
auto texture = texViewBase[relative_textureUnit];
if (!texture)
continue;
const uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4];
const uint32 word4 = texRegs[4];
uint32 samplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit];
if (samplerIndex != LATTE_DECOMPILER_SAMPLER_NONE)
if (samplerIndex != LATTE_DECOMPILER_SAMPLER_NONE) [[likely]]
{
samplerIndex += LatteDecompiler_getTextureSamplerBaseIndex(shader->shaderType);
samplerIndex += samplerBaseIndex;
hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 0];
hash = std::rotl<uint64>(hash, 7);
hash += LatteGPUState.contextRegister[Latte::REGADDR::SQ_TEX_SAMPLER_WORD0_0 + samplerIndex * 3 + 1];