mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-07-10 09:34:40 -06:00
Merge branch 'cemu-project:main' into main
This commit is contained in:
commit
340861561f
2
dependencies/Vulkan-Headers
vendored
2
dependencies/Vulkan-Headers
vendored
@ -1 +1 @@
|
||||
Subproject commit 9b9fd871b08110cd8f0b74e721b03213d9cc3081
|
||||
Subproject commit 01393c3df0e5285b54ee6527466513f9e614be94
|
||||
@ -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"
|
||||
@ -17,6 +14,8 @@
|
||||
#include <openssl/sha.h> /* SHA1_DIGEST_LENGTH */
|
||||
#include <openssl/evp.h> /* EVP_Digest */
|
||||
|
||||
void LatteSHRC_RemoveShaderStateCacheEntryByKey(uint64 key);
|
||||
|
||||
uint32 LatteShaderRecompiler_getAttributeSize(LatteParsedFetchShaderAttribute_t* attrib)
|
||||
{
|
||||
if (attrib->format == FMT_32_32_32_32 || attrib->format == FMT_32_32_32_32_FLOAT)
|
||||
@ -243,6 +242,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;
|
||||
@ -470,6 +470,9 @@ LatteFetchShader* LatteShaderRecompiler_createFetchShader(LatteFetchShader::Cach
|
||||
LatteFetchShader::~LatteFetchShader()
|
||||
{
|
||||
UnregisterInCache();
|
||||
// remove from shader state cache
|
||||
while (!m_shaderStateCacheKeys.empty())
|
||||
LatteSHRC_RemoveShaderStateCacheEntryByKey(m_shaderStateCacheKeys.back());
|
||||
}
|
||||
|
||||
struct FetchShaderLookupInfo
|
||||
@ -503,7 +506,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 +542,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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
@ -63,11 +64,12 @@ struct LatteFetchShader
|
||||
|
||||
static bool isValidBufferIndex(const uint32 index) { return index < 0x10; };
|
||||
|
||||
// cache
|
||||
LatteFetchShader* RegisterInCache(CacheHash fsHash); // Fails if another fetch shader object is already registered with the same fsHash. Returns the previously registered fetch shader or null
|
||||
void UnregisterInCache();
|
||||
// keys in shader state cache
|
||||
std::vector<uint64> m_shaderStateCacheKeys;
|
||||
|
||||
// fetch shader cache (move these to separate Cache class?)
|
||||
LatteFetchShader* RegisterInCache(CacheHash fsHash); // fails if another fetch shader object is already registered with the same fsHash. Returns the previously registered fetch shader or null
|
||||
void UnregisterInCache();
|
||||
static CacheHash CalculateCacheHash(void* programCode, uint32 programSize);
|
||||
static LatteFetchShader* FindInCacheByHash(CacheHash fsHash);
|
||||
static LatteFetchShader* FindByGPUState();
|
||||
|
||||
@ -70,6 +70,20 @@ struct LatteGPUState_t
|
||||
|
||||
extern LatteGPUState_t LatteGPUState;
|
||||
|
||||
// drawcall context
|
||||
|
||||
struct LatteDrawcallContext
|
||||
{
|
||||
bool isFirst{}; // first _execute() in current sequence
|
||||
// these are only valid if isFirst is false:
|
||||
mutable uint32 vertexBufferDirtyMask{}; // mask of vertex buffer indices which have been modified since last draw execute
|
||||
uint32 vsUniformBufferDirtyMask{}; // mask of uniform buffer indices which have been modified (changed address or size) since last draw execute
|
||||
uint32 psUniformBufferDirtyMask{};
|
||||
uint32 gsUniformBufferDirtyMask{};
|
||||
bool aluConstVSDirty{};
|
||||
bool aluConstPSDirty{};
|
||||
};
|
||||
|
||||
// texture
|
||||
|
||||
#include "Cafe/HW/Latte/Core/LatteTexture.h"
|
||||
@ -154,8 +168,8 @@ void LatteCP_ProcessRingbuffer();
|
||||
|
||||
// buffer cache
|
||||
|
||||
bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount);
|
||||
void LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData);
|
||||
void LatteBufferCache_Sync(uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental = false);
|
||||
bool LatteBufferCache_LoadRemappedUniforms(struct LatteDecompilerShader* shader, float* uniformData, bool aluConstDirty, uint32 uniformBufferDirtyMask);
|
||||
|
||||
void LatteRenderTarget_updateViewport();
|
||||
|
||||
|
||||
@ -70,10 +70,9 @@ void rectGenerate4thVertex(uint32be* output, uint32be* input0, uint32be* input1,
|
||||
output[f] = _swapEndianU32(output[f]);
|
||||
}
|
||||
|
||||
#define ATTRIBUTE_CACHE_RING_SIZE (128) // up to 128 entries can be cached
|
||||
|
||||
void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData)
|
||||
bool LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float* uniformData, bool aluConstDirty, uint32 uniformBufferDirtyMask)
|
||||
{
|
||||
bool hasChange = false;
|
||||
uint32 shaderAluConst;
|
||||
uint32 shaderUniformRegisterOffset;
|
||||
|
||||
@ -92,139 +91,205 @@ void LatteBufferCache_LoadRemappedUniforms(LatteDecompilerShader* shader, float*
|
||||
shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START;
|
||||
break;
|
||||
default:
|
||||
cemu_assert_debug(false);
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
// sourced from uniform registers
|
||||
uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst;
|
||||
for (auto it : shader->list_remappedUniformEntries_register)
|
||||
if (aluConstDirty)
|
||||
{
|
||||
uint64* uniformRegData = (uint64*)(aluConstBase + it.indexOffset / 4);
|
||||
uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset);
|
||||
regDest[0] = uniformRegData[0];
|
||||
regDest[1] = uniformRegData[1];
|
||||
uint32* aluConstBase = LatteGPUState.contextRegister + mmSQ_ALU_CONSTANT0_0 + shaderAluConst;
|
||||
for (auto it : shader->list_remappedUniformEntries_register)
|
||||
{
|
||||
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
|
||||
for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups)
|
||||
if (uniformBufferDirtyMask)
|
||||
{
|
||||
MPTR physicalAddr = LatteGPUState.contextRegister[shaderUniformRegisterOffset + bufferGroup.kcacheBankIdOffset / 4];
|
||||
if (physicalAddr)
|
||||
for (auto& bufferGroup : shader->list_remappedUniformEntries_bufferGroups)
|
||||
{
|
||||
uint8* uniformBase = memory_base + physicalAddr;
|
||||
for (auto& it : bufferGroup.entries)
|
||||
{
|
||||
uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset);
|
||||
uint64* uniformEntrySrc = (uint64*)(uniformBase + it.indexOffset);
|
||||
memcpy(regDest, uniformEntrySrc, 16);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& it : bufferGroup.entries)
|
||||
{
|
||||
uint64* regDest = (uint64*)((uint8*)uniformData + it.mappedIndexOffset);
|
||||
regDest[0] = 0;
|
||||
regDest[1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LatteBufferCache_syncGPUUniformBuffers(LatteDecompilerShader* shader, const uint32 uniformBufferRegOffset, LatteConst::ShaderType shaderType)
|
||||
{
|
||||
if (shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
|
||||
{
|
||||
for(const auto& buf : shader->list_quickBufferList)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
// upload vertex and uniform buffers
|
||||
bool LatteBufferCache_Sync(uint32 minIndex, uint32 maxIndex, uint32 baseInstance, uint32 instanceCount)
|
||||
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{};
|
||||
|
||||
void LatteBufferCache_ProcessQueues()
|
||||
{
|
||||
static uint32 s_syncBufferCounter = 0;
|
||||
|
||||
s_syncBufferCounter++;
|
||||
if (s_syncBufferCounter >= 30)
|
||||
if (s_syncBufferCounter >= 25)
|
||||
{
|
||||
LatteBufferCache_incrementalCleanup();
|
||||
s_syncBufferCounter = 0;
|
||||
}
|
||||
|
||||
LatteBufferCache_processDCFlushQueue();
|
||||
// process queued deallocations from previous drawcall
|
||||
LatteBufferCache_processDeallocations();
|
||||
}
|
||||
|
||||
// sync and bind vertex buffers
|
||||
// upload vertex and uniform buffers and update bindings
|
||||
void LatteBufferCache_Sync(uint32 maxIndex, uint32 baseInstance, uint32 instanceCount, uint32 attribBufferDirtyMask, uint32 vsUniformBufferDirtyMask, uint32 psUniformBufferDirtyMask, uint32 gsUniformBufferDirtyMask, uint8& stageUniformModifiedMask, bool isIncremental)
|
||||
{
|
||||
LatteFetchShader* parsedFetchShader = LatteSHRC_GetActiveFetchShader();
|
||||
if (!parsedFetchShader)
|
||||
return false;
|
||||
for (auto& bufferGroup : parsedFetchShader->bufferGroups)
|
||||
cemu_assert_debug(parsedFetchShader);
|
||||
|
||||
// todo - vertex attribute offsets may eventually be allowed to change between incremental draws, we should set the attrib dirty bits in that case
|
||||
if (isIncremental)
|
||||
{
|
||||
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;
|
||||
|
||||
if (bufferAddress == MPTR_NULL)
|
||||
// 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
|
||||
uint32 maxInstance = baseInstance + instanceCount - 1;
|
||||
bool hasBufferChange = attribBufferDirtyMask != 0;
|
||||
if ( maxIndex > s_vtxStateMaxIndex )
|
||||
{
|
||||
g_renderer->buffer_bindVertexBuffer(bufferIndex, 0, 0);
|
||||
continue;
|
||||
attribBufferDirtyMask = 0xFFFFFFFF;
|
||||
s_vtxStateMaxIndex = maxIndex;
|
||||
}
|
||||
|
||||
// dont rely on buffer size given by game
|
||||
uint32 fixedBufferSize = 0;
|
||||
if (bufferGroup.hasVtxIndexAccess)
|
||||
fixedBufferSize = bufferStride * (maxIndex + 1) + bufferGroup.maxOffset;
|
||||
if (bufferGroup.hasInstanceIndexAccess)
|
||||
if ( maxInstance > s_vtxStateMaxInstance )
|
||||
{
|
||||
uint32 fixedBufferSizeInstance = bufferStride * ((baseInstance + instanceCount) + 1) + bufferGroup.maxOffset;
|
||||
fixedBufferSize = std::max(fixedBufferSize, fixedBufferSizeInstance);
|
||||
attribBufferDirtyMask = 0xFFFFFFFF;
|
||||
s_vtxStateMaxInstance = maxInstance;
|
||||
}
|
||||
if (fixedBufferSize == 0 || bufferStride == 0)
|
||||
fixedBufferSize += 128;
|
||||
if (hasBufferChange)
|
||||
{
|
||||
s_vtxStateMaxIndex = maxIndex;
|
||||
s_vtxStateMaxInstance = maxInstance;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LatteBufferCache_ProcessQueues();
|
||||
s_vtxStateMaxIndex = maxIndex;
|
||||
uint32 maxInstance = baseInstance + instanceCount - 1;
|
||||
s_vtxStateMaxInstance = maxInstance;
|
||||
}
|
||||
attribBufferDirtyMask &= parsedFetchShader->attributeBufferMask;
|
||||
|
||||
// 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;
|
||||
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(bufferStride % 4 != 0)
|
||||
{
|
||||
if (VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance())
|
||||
{
|
||||
auto fixedBuffer = vkRenderer->buffer_genStrideWorkaroundVertexBuffer(bufferAddress, fixedBufferSize, bufferStride);
|
||||
vkRenderer->buffer_bindVertexStrideWorkaroundBuffer(fixedBuffer.first, fixedBuffer.second, bufferIndex, fixedBufferSize);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
uint32 bindOffset = LatteBufferCache_retrieveDataInCache(bufferAddress, fixedBufferSize);
|
||||
g_renderer->buffer_bindVertexBuffer(bufferIndex, bindOffset, fixedBufferSize);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
return true;
|
||||
// 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, 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, 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, gsUniformBufferDirtyMask) )
|
||||
stageUniformModifiedMask |= (1<<VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
||||
#include "Cafe/OS/libs/gx2/GX2.h" // for write gatherer and special state. Get rid of dependency
|
||||
#include "Cafe/OS/libs/gx2/GX2_Misc.h" // for GX2::sGX2MainCoreIndex. Legacy dependency
|
||||
#include "Cafe/OS/libs/gx2/GX2_Event.h" // for notification callbacks
|
||||
#include "Cafe/HW/Latte/Renderer/Renderer.h"
|
||||
#include "Cafe/HW/Latte/Core/Latte.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteDraw.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteShader.h"
|
||||
#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h"
|
||||
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
||||
@ -53,9 +51,13 @@ public:
|
||||
void beginDrawPass()
|
||||
{
|
||||
m_drawPassActive = true;
|
||||
m_isFirstDraw = true;
|
||||
m_vertexBufferChanged = true;
|
||||
m_uniformBufferChanged = true;
|
||||
m_drawcallContext.isFirst = 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();
|
||||
}
|
||||
|
||||
@ -71,18 +73,22 @@ public:
|
||||
if (physIndices == MPTR_NULL)
|
||||
return;
|
||||
auto indexType = LatteGPUState.contextNew.VGT_DMA_INDEX_TYPE.get_INDEX_TYPE();
|
||||
g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, physIndices, indexType, m_isFirstDraw);
|
||||
g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, physIndices, indexType, m_drawcallContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_isFirstDraw);
|
||||
g_renderer->draw_execute(baseVertex, baseInstance, numInstances, count, MPTR_NULL, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE::AUTO, m_drawcallContext);
|
||||
}
|
||||
performanceMonitor.cycle[performanceMonitor.cycleIndex].drawCallCounter++;
|
||||
if (!m_isFirstDraw)
|
||||
if (!m_drawcallContext.isFirst)
|
||||
performanceMonitor.cycle[performanceMonitor.cycleIndex].fastDrawCallCounter++;
|
||||
m_isFirstDraw = false;
|
||||
m_vertexBufferChanged = false;
|
||||
m_uniformBufferChanged = false;
|
||||
m_drawcallContext.isFirst = false;
|
||||
m_drawcallContext.vertexBufferDirtyMask = 0;
|
||||
m_drawcallContext.vsUniformBufferDirtyMask = 0;
|
||||
m_drawcallContext.psUniformBufferDirtyMask = 0;
|
||||
m_drawcallContext.gsUniformBufferDirtyMask = 0;
|
||||
m_drawcallContext.aluConstVSDirty = false;
|
||||
m_drawcallContext.aluConstPSDirty = false;
|
||||
}
|
||||
|
||||
void endDrawPass()
|
||||
@ -91,14 +97,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
|
||||
@ -121,10 +147,8 @@ public:
|
||||
|
||||
private:
|
||||
bool m_drawPassActive{ false };
|
||||
bool m_isFirstDraw{false};
|
||||
bool m_vertexBufferChanged{ false };
|
||||
bool m_uniformBufferChanged{ false };
|
||||
boost::container::small_vector<CmdQueuePos, 4> m_queuePosStack;
|
||||
LatteDrawcallContext m_drawcallContext{};
|
||||
boost::container::static_vector<CmdQueuePos, 4> m_queuePosStack;
|
||||
};
|
||||
|
||||
void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx);
|
||||
@ -292,6 +316,7 @@ void LatteCP_itSetRegistersGeneric_handleSpecialRanges(uint32 registerStartIndex
|
||||
template<uint32 TRegisterBase>
|
||||
LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords)
|
||||
{
|
||||
nWords--; // subtract the register offset field
|
||||
uint32 registerOffset = LatteReadCMD();
|
||||
uint32 registerIndex = TRegisterBase + registerOffset;
|
||||
uint32 registerStartIndex = registerIndex;
|
||||
@ -299,13 +324,13 @@ 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)
|
||||
while (nWords--)
|
||||
{
|
||||
uint32 dataWord = LatteReadCMD();
|
||||
MPTR regShadowAddr = shadowAddrs[indexCounter];
|
||||
@ -318,11 +343,19 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords)
|
||||
else
|
||||
{
|
||||
// state shadowing disabled
|
||||
sint32 indexCounter = 0;
|
||||
while (--nWords)
|
||||
if (nWords == 1) // common case
|
||||
{
|
||||
*outputReg = LatteReadCMD();
|
||||
outputReg++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sint32 i = 0;
|
||||
while (i < nWords)
|
||||
{
|
||||
outputReg[i] = cmd[i];
|
||||
i++;
|
||||
}
|
||||
cmd += nWords;
|
||||
}
|
||||
}
|
||||
// some register writes trigger special behavior
|
||||
@ -330,30 +363,31 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords)
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// similar to LatteCP_itSetRegistersGeneric, but calls a callback for every register range checked and returns true ONLY if any register value has actually changed (e.g. not updated to the same value as before)
|
||||
template<uint32 TRegisterBase, typename TRegRangeCallback>
|
||||
LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange)
|
||||
bool LatteCP_itSetRegistersGeneric2(LatteCMDPtr cmd, uint32 nWords, TRegRangeCallback cbRegRange)
|
||||
{
|
||||
uint32 registerOffset = LatteReadCMD();
|
||||
uint32 registerIndex = TRegisterBase + registerOffset;
|
||||
uint32 registerStartIndex = registerIndex;
|
||||
uint32 registerEndIndex = registerStartIndex + nWords;
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
nWords--;
|
||||
const uint32 registerOffset = LatteReadCMD();
|
||||
const uint32 registerIndex = TRegisterBase + registerOffset;
|
||||
const uint32 registerStartIndex = registerIndex;
|
||||
const uint32 registerEndIndex = registerStartIndex + nWords - 1;
|
||||
cemu_assert_debug((registerIndex + nWords) <= LATTE_MAX_REGISTER);
|
||||
#endif
|
||||
cbRegRange(registerStartIndex, registerEndIndex);
|
||||
|
||||
uint32* outputReg = (uint32*)(LatteGPUState.contextRegister + registerIndex);
|
||||
bool hasRegChange = false;
|
||||
if (LatteGPUState.contextControl0 == 0x80000077)
|
||||
{
|
||||
// state shadowing enabled
|
||||
uint32* shadowAddrs = LatteGPUState.contextRegisterShadowAddr + registerIndex;
|
||||
sint32 indexCounter = 0;
|
||||
while (--nWords)
|
||||
while (nWords--)
|
||||
{
|
||||
uint32 dataWord = LatteReadCMD();
|
||||
MPTR regShadowAddr = shadowAddrs[indexCounter];
|
||||
if (regShadowAddr)
|
||||
*(uint32*)(memory_base + regShadowAddr) = _swapEndianU32(dataWord);
|
||||
hasRegChange |= (outputReg[indexCounter] != dataWord);
|
||||
outputReg[indexCounter] = dataWord;
|
||||
indexCounter++;
|
||||
}
|
||||
@ -361,16 +395,30 @@ LatteCMDPtr LatteCP_itSetRegistersGeneric(LatteCMDPtr cmd, uint32 nWords, TRegRa
|
||||
else
|
||||
{
|
||||
// state shadowing disabled
|
||||
sint32 indexCounter = 0;
|
||||
while (--nWords)
|
||||
if (nWords == 1) // common case
|
||||
{
|
||||
*outputReg = LatteReadCMD();
|
||||
outputReg++;
|
||||
uint32 v = LatteReadCMD();
|
||||
hasRegChange |= (*outputReg != v);
|
||||
*outputReg = v;
|
||||
}
|
||||
else
|
||||
{
|
||||
sint32 i = 0;
|
||||
while (i < nWords)
|
||||
{
|
||||
uint32 v = cmd[i];
|
||||
hasRegChange |= (outputReg[i] != v);
|
||||
outputReg[i] = v;
|
||||
i++;
|
||||
}
|
||||
cmd += nWords;
|
||||
}
|
||||
}
|
||||
// some register writes trigger special behavior
|
||||
LatteCP_itSetRegistersGeneric_handleSpecialRanges<TRegisterBase>(registerStartIndex, registerEndIndex);
|
||||
return cmd;
|
||||
// callback
|
||||
cbRegRange(registerStartIndex, registerEndIndex, hasRegChange);
|
||||
return hasRegChange;
|
||||
}
|
||||
|
||||
LatteCMDPtr LatteCP_itIndexType(LatteCMDPtr cmd, uint32 nWords)
|
||||
@ -992,16 +1040,36 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt
|
||||
{
|
||||
case IT_SET_RESOURCE: // attribute buffers, uniform buffers or texture units
|
||||
{
|
||||
LatteCP_itSetRegistersGeneric<LATTE_REG_BASE_RESOURCE>(cmdData, nWords, [&drawPassCtx](uint32 registerStart, uint32 registerEnd)
|
||||
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)))
|
||||
{
|
||||
drawPassCtx.endDrawPass(); // texture updates end the current draw sequence
|
||||
}
|
||||
else if (registerStart >= mmSQ_VTX_ATTRIBUTE_BLOCK_START && registerEnd <= mmSQ_VTX_ATTRIBUTE_BLOCK_END)
|
||||
drawPassCtx.notifyModifiedVertexBuffer();
|
||||
else
|
||||
drawPassCtx.notifyModifiedUniformBuffer();
|
||||
{
|
||||
uint32 bufferIndex = (registerStart - mmSQ_VTX_ATTRIBUTE_BLOCK_START) / 7;
|
||||
drawPassCtx.MarkVertexBufferDirty(bufferIndex);
|
||||
}
|
||||
else if (registerStart >= mmSQ_VTX_UNIFORM_BLOCK_START && registerEnd <= mmSQ_VTX_UNIFORM_BLOCK_END)
|
||||
{
|
||||
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())
|
||||
{
|
||||
@ -1012,7 +1080,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 and only uploading the affected range. Possibly not worth it
|
||||
});
|
||||
break;
|
||||
}
|
||||
case IT_SET_CTL_CONST:
|
||||
@ -1042,9 +1118,14 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt
|
||||
}
|
||||
case IT_SET_CONTEXT_REG:
|
||||
{
|
||||
drawPassCtx.endDrawPass();
|
||||
drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd);
|
||||
return;
|
||||
bool hasChanged = LatteCP_itSetRegistersGeneric2<LATTE_REG_BASE_CONTEXT>(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){});
|
||||
if (hasChanged)
|
||||
{
|
||||
drawPassCtx.endDrawPass();
|
||||
drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IT_INDIRECT_BUFFER_PRIV:
|
||||
{
|
||||
@ -1052,13 +1133,21 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt
|
||||
LatteCP_itIndirectBuffer(cmdData, nWords, drawPassCtx);
|
||||
if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) // switch to sub buffer
|
||||
cemu_assert_debug(false);
|
||||
|
||||
//if (!drawPassCtx.isWithinDrawPass())
|
||||
// return cmdData;
|
||||
break;
|
||||
}
|
||||
case IT_SET_SAMPLER:
|
||||
{
|
||||
bool hasChanged = LatteCP_itSetRegistersGeneric2<LATTE_REG_BASE_SAMPLER>(cmdData, nWords, [](uint32 registerStart, uint32 registerEnd, bool regValuesChanged){});
|
||||
if (hasChanged)
|
||||
{
|
||||
drawPassCtx.endDrawPass();
|
||||
drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// unsupported command for fast draw
|
||||
// unallowed command for fast draw
|
||||
drawPassCtx.endDrawPass();
|
||||
drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd);
|
||||
return;
|
||||
@ -1070,7 +1159,7 @@ void LatteCP_processCommandBuffer_continuousDrawPass(DrawPassContext& drawPassCt
|
||||
}
|
||||
else
|
||||
{
|
||||
// unsupported command for fast draw
|
||||
// unallowed command for fast draw
|
||||
drawPassCtx.endDrawPass();
|
||||
drawPassCtx.PushCurrentCommandQueuePos(cmdBeforeCommand, cmdStart, cmdEnd);
|
||||
return;
|
||||
@ -1179,6 +1268,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
|
||||
case IT_DRAW_INDEX_AUTO:
|
||||
{
|
||||
drawPassCtx.beginDrawPass();
|
||||
//cemuLog_log(LogType::Force, "[CmdBuf] DrawIndexAuto");
|
||||
LatteCP_itDrawIndexAuto(cmdData, nWords, drawPassCtx);
|
||||
// enter fast draw mode
|
||||
drawPassCtx.PushCurrentCommandQueuePos(cmd, cmdStart, cmdEnd);
|
||||
@ -1192,6 +1282,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
|
||||
{
|
||||
DrawPassContext drawPassCtx;
|
||||
drawPassCtx.beginDrawPass();
|
||||
//cemuLog_log(LogType::Force, "[CmdBuf] DrawIndexImm");
|
||||
LatteCP_itDrawImmediate(cmdData, nWords, drawPassCtx);
|
||||
drawPassCtx.endDrawPass();
|
||||
break;
|
||||
@ -1448,6 +1539,7 @@ void LatteCP_ProcessRingbuffer()
|
||||
{
|
||||
DrawPassContext drawPassCtx;
|
||||
drawPassCtx.beginDrawPass();
|
||||
cemuLog_log(LogType::Force, "[TopLevel] DrawIndex2");
|
||||
LatteCP_itDrawIndex2(cmd, nWords, drawPassCtx);
|
||||
drawPassCtx.endDrawPass();
|
||||
timerRecheck += CP_TIMER_RECHECK / 64;
|
||||
@ -1457,6 +1549,7 @@ void LatteCP_ProcessRingbuffer()
|
||||
{
|
||||
DrawPassContext drawPassCtx;
|
||||
drawPassCtx.beginDrawPass();
|
||||
cemuLog_log(LogType::Force, "[TopLevel] DrawIndexAuto");
|
||||
LatteCP_itDrawIndexAuto(cmd, nWords, drawPassCtx);
|
||||
drawPassCtx.endDrawPass();
|
||||
timerRecheck += CP_TIMER_RECHECK / 512;
|
||||
@ -1466,6 +1559,7 @@ void LatteCP_ProcessRingbuffer()
|
||||
{
|
||||
DrawPassContext drawPassCtx;
|
||||
drawPassCtx.beginDrawPass();
|
||||
cemuLog_log(LogType::Force, "[TopLevel] DrawIndexImm");
|
||||
LatteCP_itDrawImmediate(cmd, nWords, drawPassCtx);
|
||||
drawPassCtx.endDrawPass();
|
||||
timerRecheck += CP_TIMER_RECHECK / 64;
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
#define LATTE_NUM_COLOR_TARGET 8
|
||||
|
||||
#define LATTE_NUM_MAX_TEX_UNITS 18 // number of available texture units per shader stage (this might be higher than 18? BotW is the only game which uses more than 16?)
|
||||
#define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations
|
||||
#define LATTE_NUM_MAX_UNIFORM_BUFFERS 16 // number of supported uniform buffer binding locations per shader stage
|
||||
|
||||
#define LATTE_VS_ATTRIBUTE_LIMIT 32 // todo: verify
|
||||
#define LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS 256 // should this be 128 since there are only 128 GPRs?
|
||||
@ -109,16 +109,13 @@ namespace LatteConst
|
||||
{
|
||||
enum class ShaderType : uint32
|
||||
{
|
||||
Reserved = 0,
|
||||
// shaders for drawing
|
||||
FirstRender = 1,
|
||||
Vertex = 1,
|
||||
Pixel = 2,
|
||||
Geometry = 3,
|
||||
LastRender = 3,
|
||||
Vertex = 0,
|
||||
Pixel = 1,
|
||||
Geometry = 2,
|
||||
// compute shader
|
||||
Compute = 4,
|
||||
TotalCount = 5
|
||||
Compute = 3,
|
||||
TotalCount = 4
|
||||
};
|
||||
|
||||
enum class VertexFetchNFA
|
||||
|
||||
@ -21,7 +21,6 @@ struct
|
||||
LatteIndexType lastIndexType;
|
||||
uint64 lastUsed;
|
||||
// output
|
||||
uint32 indexMin;
|
||||
uint32 indexMax;
|
||||
Renderer::INDEX_TYPE renderIndexType;
|
||||
uint32 outputCount;
|
||||
@ -140,7 +139,7 @@ uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, L
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
const betype<T>* src = (betype<T>*)indexDataInput;
|
||||
T* dst = (T*)indexDataOutput;
|
||||
@ -148,7 +147,6 @@ void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, u
|
||||
{
|
||||
T v = *src;
|
||||
*dst = v;
|
||||
indexMin = std::min(indexMin, (uint32)v);
|
||||
indexMax = std::max(indexMax, (uint32)v);
|
||||
dst++;
|
||||
src++;
|
||||
@ -156,7 +154,7 @@ void LatteIndices_convertBE(const void* indexDataInput, void* indexDataOutput, u
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
const T* src = (T*)indexDataInput;
|
||||
T* dst = (T*)indexDataOutput;
|
||||
@ -164,7 +162,6 @@ void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, u
|
||||
{
|
||||
T v = *src;
|
||||
*dst = v;
|
||||
indexMin = std::min(indexMin, (uint32)v);
|
||||
indexMax = std::max(indexMax, (uint32)v);
|
||||
dst++;
|
||||
src++;
|
||||
@ -172,7 +169,7 @@ void LatteIndices_convertLE(const void* indexDataInput, void* indexDataOutput, u
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
sint32 numQuads = count / 4;
|
||||
const betype<T>* src = (betype<T>*)indexDataInput;
|
||||
@ -183,13 +180,9 @@ void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexD
|
||||
T idx1 = src[1];
|
||||
T idx2 = src[2];
|
||||
T idx3 = src[3];
|
||||
indexMin = std::min(indexMin, (uint32)idx0);
|
||||
indexMax = std::max(indexMax, (uint32)idx0);
|
||||
indexMin = std::min(indexMin, (uint32)idx1);
|
||||
indexMax = std::max(indexMax, (uint32)idx1);
|
||||
indexMin = std::min(indexMin, (uint32)idx2);
|
||||
indexMax = std::max(indexMax, (uint32)idx2);
|
||||
indexMin = std::min(indexMin, (uint32)idx3);
|
||||
indexMax = std::max(indexMax, (uint32)idx3);
|
||||
dst[0] = idx0;
|
||||
dst[1] = idx1;
|
||||
@ -203,7 +196,7 @@ void LatteIndices_unpackQuadsAndConvert(const void* indexDataInput, void* indexD
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
sint32 numQuads = count / 4;
|
||||
const betype<T>* src = (betype<T>*)indexDataInput;
|
||||
@ -223,12 +216,11 @@ void LatteIndices_generateAutoQuadIndices(const void* indexDataInput, void* inde
|
||||
src += 4;
|
||||
dst += 6;
|
||||
}
|
||||
indexMin = 0;
|
||||
indexMax = std::max(count, 1u) - 1;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
if (count <= 3)
|
||||
return;
|
||||
@ -241,13 +233,9 @@ void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* in
|
||||
T idx1 = src[1];
|
||||
T idx2 = src[2];
|
||||
T idx3 = src[3];
|
||||
indexMin = std::min(indexMin, (uint32)idx0);
|
||||
indexMax = std::max(indexMax, (uint32)idx0);
|
||||
indexMin = std::min(indexMin, (uint32)idx1);
|
||||
indexMax = std::max(indexMax, (uint32)idx1);
|
||||
indexMin = std::min(indexMin, (uint32)idx2);
|
||||
indexMax = std::max(indexMax, (uint32)idx2);
|
||||
indexMin = std::min(indexMin, (uint32)idx3);
|
||||
indexMax = std::max(indexMax, (uint32)idx3);
|
||||
dst[0] = idx0;
|
||||
dst[1] = idx1;
|
||||
@ -261,7 +249,7 @@ void LatteIndices_unpackQuadStripAndConvert(const void* indexDataInput, void* in
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
if (count <= 0)
|
||||
return;
|
||||
@ -271,7 +259,6 @@ void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* ind
|
||||
for (sint32 i = 0; i < (sint32)count; i++)
|
||||
{
|
||||
T idx = *src;
|
||||
indexMin = std::min(indexMin, (uint32)idx);
|
||||
indexMax = std::max(indexMax, (uint32)idx);
|
||||
*dst = idx;
|
||||
src++;
|
||||
@ -281,7 +268,7 @@ void LatteIndices_unpackLineLoopAndConvert(const void* indexDataInput, void* ind
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
if (count <= 3)
|
||||
return;
|
||||
@ -301,13 +288,12 @@ void LatteIndices_generateAutoQuadStripIndices(void* indexDataOutput, uint32 cou
|
||||
dst[5] = idx3;
|
||||
dst += 6;
|
||||
}
|
||||
indexMin = 0;
|
||||
indexMax = std::max(count, 1u) - 1;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
if (count == 0)
|
||||
return;
|
||||
@ -319,12 +305,11 @@ void LatteIndices_generateAutoLineLoopIndices(void* indexDataOutput, uint32 coun
|
||||
}
|
||||
*dst = 0;
|
||||
dst++;
|
||||
indexMin = 0;
|
||||
indexMax = std::max(count, 1u) - 1;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
const betype<T>* src = (betype<T>*)indexDataInput;
|
||||
T* dst = (T*)indexDataOutput;
|
||||
@ -337,14 +322,13 @@ void LatteIndices_unpackTriangleFanAndConvert(const void* indexDataInput, void*
|
||||
else
|
||||
i0 = count - 1 - i / 2;
|
||||
T idx = src[i0];
|
||||
indexMin = std::min(indexMin, (uint32)idx);
|
||||
indexMax = std::max(indexMax, (uint32)idx);
|
||||
dst[i] = idx;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
const betype<T>* src = (betype<T>*)indexDataInput;
|
||||
T* dst = (T*)indexDataOutput;
|
||||
@ -357,13 +341,12 @@ void LatteIndices_generateAutoTriangleFanIndices(const void* indexDataInput, voi
|
||||
idx = count - 1 - idx / 2;
|
||||
dst[i] = idx;
|
||||
}
|
||||
indexMin = 0;
|
||||
indexMax = std::max(count, 1u) - 1;
|
||||
}
|
||||
|
||||
#if defined(ARCH_X86_64)
|
||||
ATTRIBUTE_AVX2
|
||||
void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
// using AVX + AVX2 we can process 16 indices at a time
|
||||
const uint16* indicesU16BE = (const uint16*)indexDataInput;
|
||||
@ -385,33 +368,23 @@ void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDat
|
||||
// endian swap
|
||||
mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle16Swap);
|
||||
_mm256_store_si256((__m256i*)indexOutput, mIndexData);
|
||||
mMin = _mm256_min_epu16(mIndexData, mMin);
|
||||
mMax = _mm256_max_epu16(mIndexData, mMax);
|
||||
indexOutput += 16;
|
||||
} while (--count16);
|
||||
|
||||
// fold 32 to 16 byte
|
||||
mMin = _mm256_min_epu16(mMin, _mm256_permute2x128_si256(mMin, mMin, 1));
|
||||
mMax = _mm256_max_epu16(mMax, _mm256_permute2x128_si256(mMax, mMax, 1));
|
||||
// fold 16 to 8 byte
|
||||
mMin = _mm256_min_epu16(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6)));
|
||||
mMax = _mm256_max_epu16(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6)));
|
||||
|
||||
uint16* mMinU16 = (uint16*)&mMin;
|
||||
uint16* mMaxU16 = (uint16*)&mMax;
|
||||
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[0]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[1]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[2]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[3]);
|
||||
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[0]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[1]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[2]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[3]);
|
||||
}
|
||||
// process remaining indices
|
||||
uint32 _minIndex = 0xFFFFFFFF;
|
||||
uint32 _maxIndex = 0;
|
||||
for (sint32 i = countRemaining; (--i) >= 0;)
|
||||
{
|
||||
@ -420,15 +393,13 @@ void LatteIndices_fastConvertU16_AVX2(const void* indexDataInput, void* indexDat
|
||||
indexOutput++;
|
||||
indicesU16BE++;
|
||||
_maxIndex = std::max(_maxIndex, (uint32)idx);
|
||||
_minIndex = std::min(_minIndex, (uint32)idx);
|
||||
}
|
||||
// update min/max
|
||||
// update max
|
||||
indexMax = std::max(indexMax, _maxIndex);
|
||||
indexMin = std::min(indexMin, _minIndex);
|
||||
}
|
||||
|
||||
ATTRIBUTE_SSE41
|
||||
void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
// SSSE3 & SSE4.1 optimized decoding
|
||||
const uint16* indicesU16BE = (const uint16*)indexDataInput;
|
||||
@ -437,7 +408,6 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa
|
||||
sint32 countRemaining = count & 7;
|
||||
if (count8)
|
||||
{
|
||||
__m128i mMin = _mm_set_epi16((short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF, (short)0xFFFF);
|
||||
__m128i mMax = _mm_set_epi16(0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000);
|
||||
__m128i mTemp;
|
||||
__m128i* mRawIndices = (__m128i*)indicesU16BE;
|
||||
@ -450,14 +420,12 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa
|
||||
mTemp = _mm_loadu_si128(mRawIndices);
|
||||
mRawIndices++;
|
||||
mTemp = _mm_shuffle_epi8(mTemp, shufmask);
|
||||
mMin = _mm_min_epu16(mMin, mTemp);
|
||||
mMax = _mm_max_epu16(mMax, mTemp);
|
||||
_mm_store_si128(mOutputIndices, mTemp);
|
||||
mOutputIndices++;
|
||||
}
|
||||
|
||||
uint16* mMaxU16 = (uint16*)&mMax;
|
||||
uint16* mMinU16 = (uint16*)&mMin;
|
||||
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[0]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[1]);
|
||||
@ -467,16 +435,7 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[5]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[6]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[7]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[0]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[1]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[2]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[3]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[4]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[5]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[6]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[7]);
|
||||
}
|
||||
uint32 _minIndex = 0xFFFFFFFF;
|
||||
uint32 _maxIndex = 0;
|
||||
for (sint32 i = countRemaining; (--i) >= 0;)
|
||||
{
|
||||
@ -485,14 +444,12 @@ void LatteIndices_fastConvertU16_SSE41(const void* indexDataInput, void* indexDa
|
||||
indexOutput++;
|
||||
indicesU16BE++;
|
||||
_maxIndex = std::max(_maxIndex, (uint32)idx);
|
||||
_minIndex = std::min(_minIndex, (uint32)idx);
|
||||
}
|
||||
indexMax = std::max(indexMax, _maxIndex);
|
||||
indexMin = std::min(indexMin, _minIndex);
|
||||
}
|
||||
|
||||
ATTRIBUTE_AVX2
|
||||
void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
// using AVX + AVX2 we can process 8 indices at a time
|
||||
const uint32* indicesU32BE = (const uint32*)indexDataInput;
|
||||
@ -501,7 +458,6 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat
|
||||
sint32 countRemaining = count & 7;
|
||||
if (count8)
|
||||
{
|
||||
__m256i mMin = _mm256_set_epi32((sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF, (sint32)0xFFFFFFFF);
|
||||
__m256i mMax = _mm256_set_epi32(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
__m256i mShuffle32Swap = _mm256_set_epi8(28,29,30,31,
|
||||
24,25,26,27,
|
||||
@ -520,29 +476,20 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat
|
||||
// endian swap
|
||||
mIndexData = _mm256_shuffle_epi8(mIndexData, mShuffle32Swap);
|
||||
_mm256_store_si256((__m256i*)indexOutput, mIndexData);
|
||||
mMin = _mm256_min_epu32(mIndexData, mMin);
|
||||
mMax = _mm256_max_epu32(mIndexData, mMax);
|
||||
indexOutput += 8;
|
||||
} while (--count8);
|
||||
|
||||
// fold 32 to 16 byte
|
||||
mMin = _mm256_min_epu32(mMin, _mm256_permute2x128_si256(mMin, mMin, 1));
|
||||
mMax = _mm256_max_epu32(mMax, _mm256_permute2x128_si256(mMax, mMax, 1));
|
||||
// fold 16 to 8 byte
|
||||
mMin = _mm256_min_epu32(mMin, _mm256_shuffle_epi32(mMin, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6)));
|
||||
mMax = _mm256_max_epu32(mMax, _mm256_shuffle_epi32(mMax, (2 << 0) | (3 << 2) | (2 << 4) | (3 << 6)));
|
||||
|
||||
uint32* mMinU32 = (uint32*)&mMin;
|
||||
uint32* mMaxU32 = (uint32*)&mMax;
|
||||
|
||||
indexMin = std::min(indexMin, (uint32)mMinU32[0]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU32[1]);
|
||||
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU32[0]);
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU32[1]);
|
||||
}
|
||||
// process remaining indices
|
||||
uint32 _minIndex = 0xFFFFFFFF;
|
||||
uint32 _maxIndex = 0;
|
||||
for (sint32 i = countRemaining; (--i) >= 0;)
|
||||
{
|
||||
@ -551,15 +498,13 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat
|
||||
indexOutput++;
|
||||
indicesU32BE++;
|
||||
_maxIndex = std::max(_maxIndex, (uint32)idx);
|
||||
_minIndex = std::min(_minIndex, (uint32)idx);
|
||||
}
|
||||
// update min/max
|
||||
indexMax = std::max(indexMax, _maxIndex);
|
||||
indexMin = std::min(indexMin, _minIndex);
|
||||
}
|
||||
#elif defined(__aarch64__)
|
||||
|
||||
void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
const uint16* indicesU16BE = (const uint16*)indexDataInput;
|
||||
uint16* indexOutput = (uint16*)indexDataOutput;
|
||||
@ -568,7 +513,6 @@ void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDat
|
||||
|
||||
if (count8)
|
||||
{
|
||||
uint16x8_t mMin = vdupq_n_u16(0xFFFF);
|
||||
uint16x8_t mMax = vdupq_n_u16(0x0000);
|
||||
uint16x8_t mTemp;
|
||||
uint16x8_t* mRawIndices = (uint16x8_t*) indicesU16BE;
|
||||
@ -581,22 +525,18 @@ void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDat
|
||||
mTemp = vld1q_u16((uint16*)mRawIndices);
|
||||
mRawIndices++;
|
||||
mTemp = vrev16q_u8(mTemp);
|
||||
mMin = vminq_u16(mMin, mTemp);
|
||||
mMax = vmaxq_u16(mMax, mTemp);
|
||||
vst1q_u16((uint16*)mOutputIndices, mTemp);
|
||||
mOutputIndices++;
|
||||
}
|
||||
|
||||
uint16* mMaxU16 = (uint16*)&mMax;
|
||||
uint16* mMinU16 = (uint16*)&mMin;
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
indexMax = std::max(indexMax, (uint32)mMaxU16[i]);
|
||||
indexMin = std::min(indexMin, (uint32)mMinU16[i]);
|
||||
}
|
||||
}
|
||||
// process remaining indices
|
||||
uint32 _minIndex = 0xFFFFFFFF;
|
||||
uint32 _maxIndex = 0;
|
||||
for (sint32 i = countRemaining; (--i) >= 0;)
|
||||
{
|
||||
@ -605,14 +545,12 @@ void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDat
|
||||
indexOutput++;
|
||||
indicesU16BE++;
|
||||
_maxIndex = std::max(_maxIndex, (uint32)idx);
|
||||
_minIndex = std::min(_minIndex, (uint32)idx);
|
||||
}
|
||||
// update min/max
|
||||
indexMax = std::max(indexMax, _maxIndex);
|
||||
indexMin = std::min(indexMin, _minIndex);
|
||||
}
|
||||
|
||||
void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMax)
|
||||
{
|
||||
const uint32* indicesU32BE = (const uint32*)indexDataInput;
|
||||
uint32* indexOutput = (uint32*)indexDataOutput;
|
||||
@ -621,7 +559,6 @@ void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDat
|
||||
|
||||
if (count8)
|
||||
{
|
||||
uint32x4_t mMin = vdupq_n_u32(0xFFFFFFFF);
|
||||
uint32x4_t mMax = vdupq_n_u32(0x00000000);
|
||||
uint32x4_t mTemp;
|
||||
uint32x4_t* mRawIndices = (uint32x4_t*) indicesU32BE;
|
||||
@ -634,22 +571,18 @@ void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDat
|
||||
mTemp = vld1q_u32((uint32*)mRawIndices);
|
||||
mRawIndices++;
|
||||
mTemp = vrev32q_u8(mTemp);
|
||||
mMin = vminq_u32(mMin, mTemp);
|
||||
mMax = vmaxq_u32(mMax, mTemp);
|
||||
vst1q_u32((uint32*)mOutputIndices, mTemp);
|
||||
mOutputIndices++;
|
||||
}
|
||||
|
||||
uint32* mMaxU32 = (uint32*)&mMax;
|
||||
uint32* mMinU32 = (uint32*)&mMin;
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
indexMax = std::max(indexMax, mMaxU32[i]);
|
||||
indexMin = std::min(indexMin, mMinU32[i]);
|
||||
}
|
||||
}
|
||||
// process remaining indices
|
||||
uint32 _minIndex = 0xFFFFFFFF;
|
||||
uint32 _maxIndex = 0;
|
||||
for (sint32 i = countRemaining; (--i) >= 0;)
|
||||
{
|
||||
@ -658,21 +591,18 @@ void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDat
|
||||
indexOutput++;
|
||||
indicesU32BE++;
|
||||
_maxIndex = std::max(_maxIndex, idx);
|
||||
_minIndex = std::min(_minIndex, idx);
|
||||
}
|
||||
// update min/max
|
||||
indexMax = std::max(indexMax, _maxIndex);
|
||||
indexMin = std::min(indexMin, _minIndex);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
void _LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, uint32 count, uint32 primitiveRestartIndex, uint32& indexMin, uint32& indexMax)
|
||||
void _LatteIndices_alternativeCalculateIndexMax(const void* indexData, uint32 count, uint32 primitiveRestartIndex, uint32& indexMax)
|
||||
{
|
||||
cemu_assert_debug(count != 0);
|
||||
const betype<T>* idxPtrT = (betype<T>*)indexData;
|
||||
T _indexMin = *idxPtrT;
|
||||
T _indexMax = *idxPtrT;
|
||||
cemu_assert_debug(primitiveRestartIndex <= std::numeric_limits<T>::max());
|
||||
T restartIndexT = (T)primitiveRestartIndex;
|
||||
@ -681,23 +611,20 @@ void _LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, uint32
|
||||
T idx = *idxPtrT;
|
||||
if (idx != restartIndexT)
|
||||
{
|
||||
_indexMin = std::min(_indexMin, idx);
|
||||
_indexMax = std::max(_indexMax, idx);
|
||||
}
|
||||
idxPtrT++;
|
||||
count--;
|
||||
}
|
||||
indexMin = _indexMin;
|
||||
indexMax = _indexMax;
|
||||
}
|
||||
|
||||
// calculate min and max index while taking primitive restart into account
|
||||
// fallback implementation in case the fast path gives us invalid results
|
||||
void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIndexType indexType, uint32 count, uint32& indexMin, uint32& indexMax)
|
||||
void LatteIndices_alternativeCalculateIndexMax(const void* indexData, LatteIndexType indexType, uint32 count, uint32& indexMax)
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
indexMin = 0;
|
||||
indexMax = 0;
|
||||
return;
|
||||
}
|
||||
@ -705,11 +632,11 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn
|
||||
|
||||
if (indexType == LatteIndexType::U16_BE)
|
||||
{
|
||||
_LatteIndices_alternativeCalculateIndexMinMax<uint16>(indexData, count, primitiveRestartIndex, indexMin, indexMax);
|
||||
_LatteIndices_alternativeCalculateIndexMax<uint16>(indexData, count, primitiveRestartIndex, indexMax);
|
||||
}
|
||||
else if (indexType == LatteIndexType::U32_BE)
|
||||
{
|
||||
_LatteIndices_alternativeCalculateIndexMinMax<uint32>(indexData, count, primitiveRestartIndex, indexMin, indexMax);
|
||||
_LatteIndices_alternativeCalculateIndexMax<uint32>(indexData, count, primitiveRestartIndex, indexMax);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -717,7 +644,7 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn
|
||||
}
|
||||
}
|
||||
|
||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation)
|
||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation)
|
||||
{
|
||||
// what this should do:
|
||||
// [x] use fast SIMD-based index decoding
|
||||
@ -733,7 +660,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
});
|
||||
if (cacheEntry != LatteIndexCache.entry.end())
|
||||
{
|
||||
indexMin = cacheEntry->indexMin;
|
||||
indexMax = cacheEntry->indexMax;
|
||||
renderIndexType = cacheEntry->renderIndexType;
|
||||
outputCount = cacheEntry->outputCount;
|
||||
@ -759,7 +685,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
if (indexOutputSize == 0)
|
||||
{
|
||||
outputCount = count;
|
||||
indexMin = 0;
|
||||
indexMax = std::max(count, 1u)-1;
|
||||
renderIndexType = Renderer::INDEX_TYPE::NONE;
|
||||
indexAllocation = {};
|
||||
@ -770,7 +695,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
void* indexOutputPtr = indexAllocation.mem;
|
||||
|
||||
// decode indices
|
||||
indexMin = std::numeric_limits<uint32>::max();
|
||||
indexMax = std::numeric_limits<uint32>::min();
|
||||
if (primitiveMode == LattePrimitiveMode::QUADS)
|
||||
{
|
||||
@ -779,19 +703,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
{
|
||||
if (count <= 0xFFFF)
|
||||
{
|
||||
LatteIndices_generateAutoQuadIndices<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoQuadIndices<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U16;
|
||||
}
|
||||
else
|
||||
{
|
||||
LatteIndices_generateAutoQuadIndices<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoQuadIndices<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U32;
|
||||
}
|
||||
}
|
||||
else if (indexType == LatteIndexType::U16_BE)
|
||||
LatteIndices_unpackQuadsAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackQuadsAndConvert<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
else if (indexType == LatteIndexType::U32_BE)
|
||||
LatteIndices_unpackQuadsAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackQuadsAndConvert<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
else
|
||||
cemu_assert_debug(false);
|
||||
outputCount = count / 4 * 6;
|
||||
@ -803,19 +727,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
{
|
||||
if (count <= 0xFFFF)
|
||||
{
|
||||
LatteIndices_generateAutoQuadStripIndices<uint16>(indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoQuadStripIndices<uint16>(indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U16;
|
||||
}
|
||||
else
|
||||
{
|
||||
LatteIndices_generateAutoQuadStripIndices<uint32>(indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoQuadStripIndices<uint32>(indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U32;
|
||||
}
|
||||
}
|
||||
else if (indexType == LatteIndexType::U16_BE)
|
||||
LatteIndices_unpackQuadStripAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackQuadStripAndConvert<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
else if (indexType == LatteIndexType::U32_BE)
|
||||
LatteIndices_unpackQuadStripAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackQuadStripAndConvert<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
else
|
||||
cemu_assert_debug(false);
|
||||
if (count >= 2)
|
||||
@ -830,19 +754,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
{
|
||||
if (count <= 0xFFFF)
|
||||
{
|
||||
LatteIndices_generateAutoLineLoopIndices<uint16>(indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoLineLoopIndices<uint16>(indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U16;
|
||||
}
|
||||
else
|
||||
{
|
||||
LatteIndices_generateAutoLineLoopIndices<uint32>(indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoLineLoopIndices<uint32>(indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U32;
|
||||
}
|
||||
}
|
||||
else if (indexType == LatteIndexType::U16_BE)
|
||||
LatteIndices_unpackLineLoopAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackLineLoopAndConvert<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
else if (indexType == LatteIndexType::U32_BE)
|
||||
LatteIndices_unpackLineLoopAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackLineLoopAndConvert<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
else
|
||||
cemu_assert_debug(false);
|
||||
outputCount = count + 1;
|
||||
@ -853,19 +777,19 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
{
|
||||
if (count <= 0xFFFF)
|
||||
{
|
||||
LatteIndices_generateAutoTriangleFanIndices<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoTriangleFanIndices<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U16;
|
||||
}
|
||||
else
|
||||
{
|
||||
LatteIndices_generateAutoTriangleFanIndices<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_generateAutoTriangleFanIndices<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
renderIndexType = Renderer::INDEX_TYPE::U32;
|
||||
}
|
||||
}
|
||||
else if (indexType == LatteIndexType::U16_BE)
|
||||
LatteIndices_unpackTriangleFanAndConvert<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackTriangleFanAndConvert<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
else if (indexType == LatteIndexType::U32_BE)
|
||||
LatteIndices_unpackTriangleFanAndConvert<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_unpackTriangleFanAndConvert<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
else
|
||||
cemu_assert_debug(false);
|
||||
outputCount = count;
|
||||
@ -876,48 +800,48 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
{
|
||||
#if defined(ARCH_X86_64)
|
||||
if (g_CPUFeatures.x86.avx2)
|
||||
LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMax);
|
||||
else if (g_CPUFeatures.x86.sse4_1 && g_CPUFeatures.x86.ssse3)
|
||||
LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMax);
|
||||
else
|
||||
LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
#elif defined(__aarch64__)
|
||||
LatteIndices_fastConvertU16_NEON(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_fastConvertU16_NEON(indexData, indexOutputPtr, count, indexMax);
|
||||
#else
|
||||
LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
#endif
|
||||
}
|
||||
else if (indexType == LatteIndexType::U32_BE)
|
||||
{
|
||||
#if defined(ARCH_X86_64)
|
||||
if (g_CPUFeatures.x86.avx2)
|
||||
LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMax);
|
||||
else
|
||||
LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
#elif defined(__aarch64__)
|
||||
LatteIndices_fastConvertU32_NEON(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_fastConvertU32_NEON(indexData, indexOutputPtr, count, indexMax);
|
||||
#else
|
||||
LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
#endif
|
||||
}
|
||||
else if (indexType == LatteIndexType::U16_LE)
|
||||
{
|
||||
LatteIndices_convertLE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_convertLE<uint16>(indexData, indexOutputPtr, count, indexMax);
|
||||
}
|
||||
else if (indexType == LatteIndexType::U32_LE)
|
||||
{
|
||||
LatteIndices_convertLE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax);
|
||||
LatteIndices_convertLE<uint32>(indexData, indexOutputPtr, count, indexMax);
|
||||
}
|
||||
else
|
||||
cemu_assert_debug(false);
|
||||
outputCount = count;
|
||||
}
|
||||
// the above algorithms use a simplistic approach to get indexMin/indexMax
|
||||
// here we make sure primitive restart indices dont influence the index range
|
||||
if (primitiveRestartIndex == indexMin || primitiveRestartIndex == indexMax)
|
||||
// the above algorithms use a fast approach to get indexMax which does not filter out indices matching primitiveRestartIndex
|
||||
// here we use a fallback in case the determined index equals the primitive restart index
|
||||
if (primitiveRestartIndex == indexMax)
|
||||
{
|
||||
// recalculate index range but filter out primitive restart index
|
||||
LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax);
|
||||
LatteIndices_alternativeCalculateIndexMax(indexData, indexType, count, indexMax);
|
||||
}
|
||||
g_renderer->indexData_uploadIndexMemory(indexAllocation);
|
||||
performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize;
|
||||
@ -934,7 +858,6 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32
|
||||
lruEntry->lastCount = count;
|
||||
lruEntry->lastPrimitiveMode = primitiveMode;
|
||||
lruEntry->lastIndexType = indexType;
|
||||
lruEntry->indexMin = indexMin;
|
||||
lruEntry->indexMax = indexMax;
|
||||
lruEntry->renderIndexType = renderIndexType;
|
||||
lruEntry->outputCount = outputCount;
|
||||
|
||||
@ -4,4 +4,4 @@
|
||||
|
||||
void LatteIndices_invalidate(const void* memPtr, uint32 size);
|
||||
void LatteIndices_invalidateAll();
|
||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation);
|
||||
void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation);
|
||||
@ -35,6 +35,8 @@ struct OverlayStats
|
||||
|
||||
extern std::atomic_int g_compiled_shaders_total;
|
||||
extern std::atomic_int g_compiled_shaders_async;
|
||||
extern std::atomic_int g_shaderStateCacheSetCount;
|
||||
extern std::atomic_int g_shaderStateCacheSetAuxCount;
|
||||
|
||||
std::atomic_int g_compiling_pipelines;
|
||||
std::atomic_int g_compiling_pipelines_async;
|
||||
@ -110,6 +112,7 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio
|
||||
// general debug info
|
||||
ImGui::Text("--- Debug info ---");
|
||||
ImGui::Text("IndexUploadPerFrame: %dKB", (performanceMonitor.stats.indexDataUploadPerFrame+1023)/1024);
|
||||
ImGui::Text("SHCSets: %d / %d", g_shaderStateCacheSetCount.load(), g_shaderStateCacheSetAuxCount.load());
|
||||
// backend specific info
|
||||
g_renderer->AppendOverlayDebugInfo();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -695,6 +689,7 @@ void LatteRenderTarget_itHLESwapScanBuffer()
|
||||
performanceMonitor.gpuTime_frameTime.beginMeasuring();
|
||||
|
||||
LatteTC_CleanupUnusedTextures();
|
||||
LatteSHRC_CleanupShaderStateCache();
|
||||
#ifdef ENABLE_OPENGL
|
||||
LatteDraw_cleanupAfterFrame();
|
||||
#endif
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cafe/GameProfile/GameProfile.h"
|
||||
#include "util/containers/flat_hash_map.hpp"
|
||||
#include "util/helpers/StateHasher.h"
|
||||
#ifdef ENABLE_METAL
|
||||
#include "Cafe/HW/Latte/Renderer/Metal/LatteToMtl.h"
|
||||
#endif
|
||||
#include <cinttypes>
|
||||
|
||||
// experimental new decompiler (WIP)
|
||||
#include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h"
|
||||
@ -56,6 +56,8 @@ uint64 _shaderBaseHash_ps;
|
||||
|
||||
std::atomic_int g_compiled_shaders_total = 0;
|
||||
std::atomic_int g_compiled_shaders_async = 0;
|
||||
std::atomic_int g_shaderStateCacheSetCount = 0;
|
||||
std::atomic_int g_shaderStateCacheSetAuxCount = 0;
|
||||
|
||||
LatteFetchShader* LatteSHRC_GetActiveFetchShader()
|
||||
{
|
||||
@ -87,6 +89,8 @@ inline ska::flat_hash_map<uint64, LatteDecompilerShader*>& LatteSHRC_GetCacheByT
|
||||
return sPixelShaders;
|
||||
}
|
||||
|
||||
void LatteSHRC_RemoveShaderStateCacheEntryByKey(uint64 key);
|
||||
|
||||
// calculate hash from shader binary
|
||||
// this algorithm could be more efficient since we could leverage the fact that the size is always aligned to 8 byte
|
||||
// but since this is baked into the shader names used for gfx packs and shader caches we can't really change this
|
||||
@ -149,7 +153,7 @@ LatteShaderPSInputTable* LatteSHRC_GetPSInputTable()
|
||||
return &_activePSImportTable;
|
||||
}
|
||||
|
||||
void LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader)
|
||||
void LatteSHRC_RemoveFromCaches(LatteDecompilerShader* shader)
|
||||
{
|
||||
bool removed = false;
|
||||
auto& cache = LatteSHRC_GetCacheByType(shader->shaderType);
|
||||
@ -186,6 +190,10 @@ void LatteSHRC_RemoveFromCache(LatteDecompilerShader* shader)
|
||||
}
|
||||
}
|
||||
cemu_assert(removed);
|
||||
// remove from shader state cache
|
||||
// deleting by key means we delete all the other aux variants associated with it too, but it keeps the code simple and cache entries are cheap to recreate anyway
|
||||
while (!shader->m_shaderStateCacheKeys.empty())
|
||||
LatteSHRC_RemoveShaderStateCacheEntryByKey(shader->m_shaderStateCacheKeys.back());
|
||||
}
|
||||
|
||||
void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_hash, LatteConst::ShaderType type)
|
||||
@ -198,12 +206,12 @@ void LatteSHRC_RemoveFromCacheByHash(uint64 shader_base_hash, uint64 shader_aux_
|
||||
else if (type == LatteConst::ShaderType::Pixel)
|
||||
shader = LatteSHRC_FindPixelShader(shader_base_hash, shader_aux_hash);
|
||||
if (shader)
|
||||
LatteSHRC_RemoveFromCache(shader);
|
||||
LatteSHRC_RemoveFromCaches(shader);
|
||||
}
|
||||
|
||||
void LatteShader_free(LatteDecompilerShader* shader)
|
||||
void LatteShader_free(LatteDecompilerShader* shader) // todo - make this ~LatteDecompilerShader()
|
||||
{
|
||||
LatteSHRC_RemoveFromCache(shader);
|
||||
LatteSHRC_RemoveFromCaches(shader);
|
||||
if (shader->shader)
|
||||
delete shader->shader;
|
||||
shader->shader = nullptr;
|
||||
@ -217,7 +225,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 +286,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;
|
||||
@ -436,10 +444,9 @@ LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash
|
||||
return LatteSHRC_Get(sPixelShaders, baseHash, auxHash);
|
||||
}
|
||||
|
||||
// update the currently active fetch shader
|
||||
void LatteShaderSHRC_UpdateFetchShader()
|
||||
LatteFetchShader* LatteSHRC_GetOrCreateFetchShader()
|
||||
{
|
||||
_activeFetchShader = LatteFetchShader::FindByGPUState();
|
||||
return LatteFetchShader::FindByGPUState();
|
||||
}
|
||||
|
||||
void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader)
|
||||
@ -499,14 +506,14 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin
|
||||
}
|
||||
}
|
||||
|
||||
void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader)
|
||||
void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader)
|
||||
{
|
||||
uint32* vsProgramCode = (uint32*)vertexShaderPtr;
|
||||
// update hash from vertex shader data
|
||||
uint64 vsHash1 = 0;
|
||||
uint64 vsHash2 = 0;
|
||||
_calculateShaderProgramHash(vsProgramCode, vertexShaderSize, &hashCacheVS, &vsHash1, &vsHash2);
|
||||
uint64 vsHash = vsHash1 + vsHash2 + _activeFetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL);
|
||||
uint64 vsHash = vsHash1 + vsHash2 + fetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL);
|
||||
|
||||
uint32 tmp = LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() ^ 0x43F;
|
||||
vsHash += tmp;
|
||||
@ -532,11 +539,11 @@ void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize,
|
||||
{
|
||||
bool isRectVertexShader = (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS);
|
||||
|
||||
if ((usesGeometryShader || isRectVertexShader) || _activeFetchShader->mtlFetchVertexManually)
|
||||
if ((usesGeometryShader || isRectVertexShader) || fetchShader->mtlFetchVertexManually)
|
||||
{
|
||||
for (sint32 g = 0; g < _activeFetchShader->bufferGroups.size(); g++)
|
||||
for (sint32 g = 0; g < fetchShader->bufferGroups.size(); g++)
|
||||
{
|
||||
LatteParsedFetchShaderBufferGroup_t& group = _activeFetchShader->bufferGroups[g];
|
||||
LatteParsedFetchShaderBufferGroup_t& group = fetchShader->bufferGroups[g];
|
||||
uint32 bufferIndex = group.attributeBufferIndex;
|
||||
uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferIndex * 7;
|
||||
uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF;
|
||||
@ -552,7 +559,7 @@ void LatteSHRC_UpdateVSBaseHash(uint8* vertexShaderPtr, uint32 vertexShaderSize,
|
||||
vsHash += 51ULL;
|
||||
|
||||
// Vertex fetch
|
||||
if (_activeFetchShader->mtlFetchVertexManually)
|
||||
if (fetchShader->mtlFetchVertexManually)
|
||||
vsHash += 349ULL;
|
||||
}
|
||||
}
|
||||
@ -881,17 +888,17 @@ LatteDecompilerShader* LatteShader_CompileSeparableVertexShader(uint64 baseHash,
|
||||
return vertexShader;
|
||||
}
|
||||
|
||||
LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHash, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize)
|
||||
LatteDecompilerShader* LatteShader_CompileSeparableGeometryShader(uint64 baseHash, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize, LatteDecompilerShader* vertexShader)
|
||||
{
|
||||
LatteDecompilerOptions options;
|
||||
LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Geometry, true);
|
||||
|
||||
LatteDecompilerOutput_t decompilerOutput{};
|
||||
LatteDecompiler_DecompileGeometryShader(_shaderBaseHash_gs, LatteGPUState.contextRegister, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, _activeVertexShader->ringParameterCount, options, &decompilerOutput);
|
||||
LatteDecompiler_DecompileGeometryShader(_shaderBaseHash_gs, LatteGPUState.contextRegister, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, vertexShader->ringParameterCount, options, &decompilerOutput);
|
||||
LatteDecompilerShader* geometryShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister);
|
||||
if (geometryShader->hasError == false)
|
||||
{
|
||||
LatteShaderCache_writeSeparableGeometryShader(geometryShader->baseHash, geometryShader->auxHash, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextRegister, LatteGPUState.contextNew.GetSpecialStateValues(), _activeVertexShader->ringParameterCount);
|
||||
LatteShaderCache_writeSeparableGeometryShader(geometryShader->baseHash, geometryShader->auxHash, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, LatteGPUState.contextRegister, LatteGPUState.contextNew.GetSpecialStateValues(), vertexShader->ringParameterCount);
|
||||
}
|
||||
LatteShader_DumpShader(geometryShader->baseHash, geometryShader->auxHash, geometryShader);
|
||||
LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderPtr, geometryShaderSize);
|
||||
@ -943,10 +950,10 @@ LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash,
|
||||
return pixelShader;
|
||||
}
|
||||
|
||||
void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader)
|
||||
LatteDecompilerShader* LatteSHRC_GetOrCreateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader)
|
||||
{
|
||||
// todo - should include VTX_SEMANTIC table in state
|
||||
LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader);
|
||||
LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader);
|
||||
uint64 vsAuxHash = 0;
|
||||
auto itBaseShader = sVertexShaders.find(_shaderBaseHash_vs);
|
||||
LatteDecompilerShader* vertexShader = nullptr;
|
||||
@ -956,22 +963,18 @@ void LatteSHRC_UpdateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSiz
|
||||
vertexShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_vs, vsAuxHash);
|
||||
}
|
||||
if (!vertexShader)
|
||||
vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, _activeFetchShader);
|
||||
vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader);
|
||||
if (vertexShader->hasError)
|
||||
{
|
||||
LatteGPUState.activeShaderHasError = true;
|
||||
return;
|
||||
}
|
||||
_activeVertexShader = vertexShader;
|
||||
return vertexShader;
|
||||
}
|
||||
|
||||
void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize)
|
||||
LatteDecompilerShader* LatteSHRC_GetOrCreateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize, LatteDecompilerShader* vertexShader)
|
||||
{
|
||||
if (!usesGeometryShader || !_activeVertexShader)
|
||||
{
|
||||
_shaderBaseHash_gs = 0;
|
||||
_activeGeometryShader = nullptr;
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
LatteSHRC_UpdateGSBaseHash(geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize);
|
||||
auto itBaseShader = sGeometryShaders.find(_shaderBaseHash_gs);
|
||||
@ -985,17 +988,14 @@ void LatteSHRC_UpdateGeometryShader(bool usesGeometryShader, uint8* geometryShad
|
||||
else
|
||||
{
|
||||
// decompile geometry shader
|
||||
geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize);
|
||||
geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, vertexShader);
|
||||
}
|
||||
if (geometryShader->hasError)
|
||||
{
|
||||
LatteGPUState.activeShaderHasError = true;
|
||||
return;
|
||||
}
|
||||
_activeGeometryShader = geometryShader;
|
||||
return geometryShader;
|
||||
}
|
||||
|
||||
void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader)
|
||||
LatteDecompilerShader* LatteSHRC_GetOrCreatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader)
|
||||
{
|
||||
LatteSHRC_UpdatePSBaseHash(pixelShaderPtr, pixelShaderSize, usesGeometryShader);
|
||||
uint64 psAuxHash = 0;
|
||||
@ -1009,11 +1009,143 @@ void LatteSHRC_UpdatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize,
|
||||
if (!pixelShader)
|
||||
pixelShader = LatteShader_CompileSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, usesGeometryShader);
|
||||
if (pixelShader->hasError)
|
||||
{
|
||||
LatteGPUState.activeShaderHasError = true;
|
||||
return pixelShader;
|
||||
}
|
||||
|
||||
static inline uint64_t mix64(uint64_t v)
|
||||
{
|
||||
v ^= v >> 30;
|
||||
v *= 0xbf58476d1ce4e5b9ULL;
|
||||
v ^= v >> 27;
|
||||
v *= 0x94d049bb133111ebULL;
|
||||
v ^= v >> 31;
|
||||
return v;
|
||||
}
|
||||
|
||||
struct ShaderStateInfoAuxVariant
|
||||
{
|
||||
uint64 combinedAuxHash{};
|
||||
LatteDecompilerShader* vertexShader{nullptr};
|
||||
LatteDecompilerShader* pixelShader{nullptr};
|
||||
LatteDecompilerShader* geometryShader{nullptr};
|
||||
bool hasError{false}; // in case of error just set all the shaders to nullptr?
|
||||
};
|
||||
|
||||
struct ShaderStateInfo
|
||||
{
|
||||
// we need any set of shaders from an aux chain to calculate the actual aux variant hash
|
||||
// so these just match the first one encountered
|
||||
LatteFetchShader* fetchShader{nullptr};
|
||||
LatteDecompilerShader* vertexShader{nullptr};
|
||||
LatteDecompilerShader* pixelShader{nullptr};
|
||||
LatteDecompilerShader* geometryShader{nullptr};
|
||||
uint64 combinedAuxHash{};
|
||||
uint32 lastAccessFrameCount{};
|
||||
bool shaderError{false};
|
||||
std::vector<ShaderStateInfoAuxVariant> auxVariants;
|
||||
};
|
||||
|
||||
struct ShaderStateDirectHash
|
||||
{
|
||||
size_t operator()(uint64 x) const noexcept
|
||||
{
|
||||
return x;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<uint64> s_shaderStateCacheKeys;
|
||||
size_t s_shaderStateCacheCleanupIndex = 0;
|
||||
robin_hood::unordered_flat_map<uint64, ShaderStateInfo, ShaderStateDirectHash> s_shaderStateCache;
|
||||
// also benchmarked here but didn't perform better or only marginally better and not worth pulling in an extra library:
|
||||
// jg::dense_hash_map<uint64, ShaderStateInfo, FPHDirectHash>
|
||||
// folly::F14FastMap<uint64, ShaderStateInfo, FPHDirectHash>
|
||||
// robin_hood::unordered_flat_map ended up performing close to best and was choosen because we already have it included in the project anyway
|
||||
|
||||
FORCE_INLINE uint64 CalcCombinedAuxHash(LatteFetchShader* fetchShader, LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader)
|
||||
{
|
||||
uint64 vsAuxHash = vertexShader ? LatteSHRC_CalcVSAuxHash(vertexShader, LatteGPUState.contextRegister) : 0;
|
||||
uint64 psAuxHash = pixelShader ? LatteSHRC_CalcPSAuxHash(pixelShader, LatteGPUState.contextRegister) : 0;
|
||||
uint64 combinedAuxHash = vsAuxHash + mix64(psAuxHash);
|
||||
#ifdef ENABLE_METAL
|
||||
if (g_renderer->GetType() == RendererAPI::Metal && fetchShader)
|
||||
{
|
||||
for (auto& bufferGroup : fetchShader->bufferGroups)
|
||||
{
|
||||
uint32 bufferBaseRegisterIndex = mmSQ_VTX_ATTRIBUTE_BLOCK_START + bufferGroup.attributeBufferIndex * 7;
|
||||
uint32 bufferStride = (LatteGPUState.contextRegister[bufferBaseRegisterIndex + 2] >> 11) & 0xFFFF;
|
||||
combinedAuxHash = std::rotl<uint64>(combinedAuxHash, 7);
|
||||
combinedAuxHash += bufferStride;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return combinedAuxHash;
|
||||
}
|
||||
|
||||
void LatteSHRC_RemoveShaderStateCacheEntryByKey(uint64 key)
|
||||
{
|
||||
auto it = s_shaderStateCache.find(key);
|
||||
if (it == s_shaderStateCache.end())
|
||||
{
|
||||
cemu_assert_suspicious(); // shader shouldn't have a key which was already removed
|
||||
return;
|
||||
}
|
||||
_activePixelShader = pixelShader;
|
||||
ShaderStateInfo& shaderStateInfo = it->second;
|
||||
if (shaderStateInfo.fetchShader)
|
||||
std::erase(shaderStateInfo.fetchShader->m_shaderStateCacheKeys, key);
|
||||
g_shaderStateCacheSetAuxCount -= shaderStateInfo.auxVariants.size();
|
||||
for (auto& auxVariant : shaderStateInfo.auxVariants)
|
||||
{
|
||||
if (auxVariant.vertexShader)
|
||||
std::erase(auxVariant.vertexShader->m_shaderStateCacheKeys, key);
|
||||
if (auxVariant.pixelShader)
|
||||
std::erase(auxVariant.pixelShader->m_shaderStateCacheKeys, key);
|
||||
if (auxVariant.geometryShader)
|
||||
std::erase(auxVariant.geometryShader->m_shaderStateCacheKeys, key);
|
||||
}
|
||||
s_shaderStateCache.erase(it);
|
||||
--g_shaderStateCacheSetCount;
|
||||
}
|
||||
|
||||
void LatteSHRC_CleanupShaderStateCache()
|
||||
{
|
||||
constexpr uint32 NUM_FRAMES_UNTIL_EXPIRE = 5 * 60; // entries expire after not being used for 5 seconds at 60 FPS
|
||||
constexpr sint32 MAX_CHECKS_PER_FRAME = 30;
|
||||
constexpr sint32 MAX_DELETES_PER_FRAME = 8;
|
||||
|
||||
if (s_shaderStateCache.empty())
|
||||
{
|
||||
s_shaderStateCacheKeys.clear();
|
||||
s_shaderStateCacheCleanupIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
sint32 deleteCount = 0;
|
||||
for (sint32 i = 0; i < MAX_CHECKS_PER_FRAME && !s_shaderStateCacheKeys.empty(); i++)
|
||||
{
|
||||
if (s_shaderStateCacheCleanupIndex >= s_shaderStateCacheKeys.size())
|
||||
s_shaderStateCacheCleanupIndex = 0;
|
||||
uint64 key = s_shaderStateCacheKeys[s_shaderStateCacheCleanupIndex];
|
||||
auto it = s_shaderStateCache.find(key);
|
||||
if (it == s_shaderStateCache.end())
|
||||
{
|
||||
s_shaderStateCacheKeys[s_shaderStateCacheCleanupIndex] = s_shaderStateCacheKeys.back();
|
||||
s_shaderStateCacheKeys.pop_back();
|
||||
continue;
|
||||
}
|
||||
uint32 framesSinceLastAccess = LatteGPUState.frameCounter - it->second.lastAccessFrameCount;
|
||||
if (framesSinceLastAccess >= NUM_FRAMES_UNTIL_EXPIRE)
|
||||
{
|
||||
if (deleteCount >= MAX_DELETES_PER_FRAME)
|
||||
break;
|
||||
LatteSHRC_RemoveShaderStateCacheEntryByKey(key);
|
||||
s_shaderStateCacheKeys[s_shaderStateCacheCleanupIndex] = s_shaderStateCacheKeys.back();
|
||||
s_shaderStateCacheKeys.pop_back();
|
||||
deleteCount++;
|
||||
continue;
|
||||
}
|
||||
s_shaderStateCacheCleanupIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void LatteSHRC_UpdateActiveShaders()
|
||||
@ -1024,72 +1156,163 @@ void LatteSHRC_UpdateActiveShaders()
|
||||
cemu_assert_debug(LatteGPUState.contextNew.VGT_GS_MODE.get_ES_PASSTHRU() == false);
|
||||
// todo: Support for ES passthrough and cut mode in mmVGT_GS_MODE
|
||||
|
||||
bool geometryShaderUsed = false;
|
||||
if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::OFF)
|
||||
{
|
||||
geometryShaderUsed = false;
|
||||
}
|
||||
else if (gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G)
|
||||
{
|
||||
// could also be compute shader?
|
||||
geometryShaderUsed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
// get shader programs
|
||||
uint8* psProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_PS] & 0xFFFFFF) << 8);
|
||||
uint32 psProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_PS + 1] << 3;
|
||||
uint8* gsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_GS] & 0xFFFFFF) << 8);
|
||||
uint32 gsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_GS + 1] << 3;
|
||||
cemu_assert_debug(gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::OFF || gsMode == Latte::LATTE_VGT_GS_MODE::E_MODE::SCENARIO_G); // other modes are not supported
|
||||
bool geometryShaderUsed = gsMode != Latte::LATTE_VGT_GS_MODE::E_MODE::OFF;
|
||||
|
||||
uint8* vsProgramCode;
|
||||
uint32 vsProgramSize;
|
||||
uint8* copyProgramCode = NULL;
|
||||
// get program pointers and sizes
|
||||
uint32 fsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_FS] << 8;
|
||||
uint32 fsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3;
|
||||
uint32 vsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_VS] << 8;
|
||||
uint32 vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3;
|
||||
uint32 psProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_PS] << 8;
|
||||
uint32 psProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_PS + 1] << 3;
|
||||
uint32 gsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_GS] << 8;
|
||||
uint32 gsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_GS + 1] << 3;
|
||||
uint32 copyProgramAddr = 0;
|
||||
uint32 copyProgramSize = 0;
|
||||
|
||||
if (geometryShaderUsed)
|
||||
{
|
||||
vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_ES] & 0xFFFFFF) << 8);
|
||||
// VS parameters come from ES instead
|
||||
copyProgramAddr = vsProgramAddr;
|
||||
copyProgramSize = vsProgramSize;
|
||||
vsProgramAddr = LatteGPUState.contextRegister[mmSQ_PGM_START_ES] << 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)
|
||||
{
|
||||
copyProgramCode = NULL;
|
||||
debug_printf("copyProgram is NULL but used. Might be because of unsupported vertex/geometry mode?");
|
||||
}
|
||||
copyProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LatteGPUState.contextRegister[mmSQ_PGM_START_VS] == 0)
|
||||
gsProgramAddr = 0;
|
||||
gsProgramSize = 0;
|
||||
if (vsProgramAddr == 0 || vsProgramSize == 0)
|
||||
{
|
||||
// todo - we dont really need to handle this since invalid shader states get baked into the lookup cache anyway
|
||||
debug_printf("No vertex shader program set\n");
|
||||
LatteGPUState.activeShaderHasError = true;
|
||||
return;
|
||||
}
|
||||
vsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset((LatteGPUState.contextRegister[mmSQ_PGM_START_VS] & 0xFFFFFF) << 8);
|
||||
vsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_VS + 1] << 3;
|
||||
}
|
||||
// set new shaders
|
||||
// build a hash from the combined shader state
|
||||
// we use this as a lookup into a "shader state" cache rather than looking up all the shader stages individually
|
||||
DualStateHasher hasher;
|
||||
|
||||
uint64_t f = ((uint64_t)fsProgramAddr << 32) | (uint64_t)fsProgramSize;
|
||||
uint64_t a = ((uint64_t)vsProgramAddr << 32) | (uint64_t)vsProgramSize;
|
||||
uint64_t b = ((uint64_t)psProgramAddr << 32) | (uint64_t)psProgramSize;
|
||||
uint64_t c = ((uint64_t)gsProgramAddr << 32) | (uint64_t)gsProgramSize;
|
||||
uint64_t d = ((uint64_t)copyProgramAddr << 32) | (uint64_t)copyProgramSize;
|
||||
|
||||
hasher.MixIn(f, a);
|
||||
hasher.MixIn(b, c);
|
||||
|
||||
constexpr uint32 PA_CL_VTE_CNTL_MASK = 0x3F; // viewport scale and offset enable bits
|
||||
constexpr uint32 PA_CL_CLIP_CNTL_MASK = 1 << 19; // DX_CLIP_SPACE_DEF (halfZ)
|
||||
constexpr uint32 VGT_PRIMITIVE_TYPE_MASK = 0x3F;
|
||||
constexpr uint32 SPI_PS_IN_CONTROL_0_MASK = 0x3F | (1 << 8) | (0x1F << 10) | (0xF << 15) | (0x7F << 19);
|
||||
constexpr uint32 SPI_PS_IN_CONTROL_1_MASK = 0x1FF << 8; // front-face settings (gl_FrontFacing)
|
||||
constexpr uint32 SPI_INTERP_CONTROL_0_MASK = 1 << 1; // point sprite coord enable
|
||||
|
||||
uint64 baseState0 = ((uint64)(LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() & PA_CL_VTE_CNTL_MASK) << 32) | (LatteGPUState.contextNew.PA_CL_CLIP_CNTL.getRawValue() & PA_CL_CLIP_CNTL_MASK);
|
||||
uint64 baseState1 = ((uint64)(LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.getRawValue() & VGT_PRIMITIVE_TYPE_MASK) << 32) | LatteGPUState.contextRegister[mmVGT_STRMOUT_EN];
|
||||
hasher.MixIn(baseState0, baseState1);
|
||||
|
||||
LatteShader_UpdatePSInputs(LatteGPUState.contextRegister); // updates _activePSImportTable
|
||||
|
||||
hasher.MixIn(d, _activePSImportTable.key);
|
||||
hasher.MixIn(LatteGPUState.contextRegister[mmSPI_PS_IN_CONTROL_0] & SPI_PS_IN_CONTROL_0_MASK, LatteGPUState.contextRegister[mmSPI_PS_IN_CONTROL_1] & SPI_PS_IN_CONTROL_1_MASK);
|
||||
hasher.MixIn(LatteGPUState.contextRegister[mmSPI_INTERP_CONTROL_0] & SPI_INTERP_CONTROL_0_MASK, 0);
|
||||
#ifdef ENABLE_METAL
|
||||
if (g_renderer->GetType() == RendererAPI::Metal)
|
||||
{
|
||||
uint64 mtlState = LatteGPUState.contextNew.IsRasterizationEnabled() ? 1 : 0;
|
||||
hasher.MixInSingle(mtlState);
|
||||
}
|
||||
#endif
|
||||
uint64 h = hasher.Finish();
|
||||
|
||||
LatteGPUState.activeShaderHasError = false;
|
||||
LatteShader_UpdatePSInputs(LatteGPUState.contextRegister);
|
||||
LatteShaderSHRC_UpdateFetchShader();
|
||||
LatteSHRC_UpdateVertexShader(vsProgramCode, vsProgramSize, geometryShaderUsed);
|
||||
if (LatteGPUState.activeShaderHasError)
|
||||
return;
|
||||
LatteSHRC_UpdateGeometryShader(geometryShaderUsed, gsProgramCode, gsProgramSize, copyProgramCode, copyProgramSize);
|
||||
if (LatteGPUState.activeShaderHasError)
|
||||
return;
|
||||
LatteSHRC_UpdatePixelShader(psProgramCode, psProgramSize, geometryShaderUsed);
|
||||
if (LatteGPUState.activeShaderHasError)
|
||||
return;
|
||||
|
||||
ShaderStateInfo* shaderStateInfo = nullptr;
|
||||
|
||||
auto it = s_shaderStateCache.find(h);
|
||||
if (it != s_shaderStateCache.end())
|
||||
{
|
||||
shaderStateInfo = &it->second;
|
||||
shaderStateInfo->lastAccessFrameCount = LatteGPUState.frameCounter;
|
||||
uint64 combinedAuxHash = CalcCombinedAuxHash(shaderStateInfo->fetchShader, shaderStateInfo->vertexShader, shaderStateInfo->pixelShader);
|
||||
if (shaderStateInfo->combinedAuxHash == combinedAuxHash) [[likely]]
|
||||
{
|
||||
_activeFetchShader = shaderStateInfo->fetchShader;
|
||||
_activeVertexShader = shaderStateInfo->vertexShader;
|
||||
_activePixelShader = shaderStateInfo->pixelShader;
|
||||
_activeGeometryShader = shaderStateInfo->geometryShader;
|
||||
return;
|
||||
}
|
||||
for (auto& auxVariant : shaderStateInfo->auxVariants)
|
||||
{
|
||||
if (auxVariant.combinedAuxHash == combinedAuxHash)
|
||||
{
|
||||
_activeFetchShader = shaderStateInfo->fetchShader;
|
||||
_activeVertexShader = auxVariant.vertexShader;
|
||||
_activePixelShader = auxVariant.pixelShader;
|
||||
_activeGeometryShader = auxVariant.geometryShader;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// no cache entry found, get/create shaders individually and add to cache
|
||||
LatteFetchShader* fetchShader = LatteSHRC_GetOrCreateFetchShader();
|
||||
_activeFetchShader = fetchShader;
|
||||
bool shaderError = LatteGPUState.activeShaderHasError;
|
||||
LatteDecompilerShader* vertexShader = LatteSHRC_GetOrCreateVertexShader((uint8*)memory_getPointerFromPhysicalOffset(vsProgramAddr), vsProgramSize, geometryShaderUsed, fetchShader);
|
||||
shaderError |= LatteGPUState.activeShaderHasError;
|
||||
LatteDecompilerShader* pixelShader = LatteSHRC_GetOrCreatePixelShader((uint8*)memory_getPointerFromPhysicalOffset(psProgramAddr), psProgramSize, geometryShaderUsed);
|
||||
shaderError |= LatteGPUState.activeShaderHasError;
|
||||
LatteDecompilerShader* geometryShader = LatteSHRC_GetOrCreateGeometryShader(geometryShaderUsed, (uint8*)memory_getPointerFromPhysicalOffset(gsProgramAddr), gsProgramSize, (uint8*)memory_getPointerFromPhysicalOffset(copyProgramAddr), copyProgramSize, vertexShader);
|
||||
shaderError |= LatteGPUState.activeShaderHasError;
|
||||
uint64 combinedAuxHash = CalcCombinedAuxHash(fetchShader, vertexShader, pixelShader);
|
||||
|
||||
if (!shaderStateInfo)
|
||||
{
|
||||
// create base entry
|
||||
shaderStateInfo = &s_shaderStateCache[h];
|
||||
s_shaderStateCacheKeys.emplace_back(h);
|
||||
shaderStateInfo->shaderError = shaderError;
|
||||
shaderStateInfo->fetchShader = fetchShader;
|
||||
shaderStateInfo->vertexShader = vertexShader;
|
||||
shaderStateInfo->pixelShader = pixelShader;
|
||||
shaderStateInfo->geometryShader = geometryShader;
|
||||
shaderStateInfo->lastAccessFrameCount = LatteGPUState.frameCounter;
|
||||
shaderStateInfo->combinedAuxHash = combinedAuxHash;
|
||||
if (shaderStateInfo->fetchShader)
|
||||
shaderStateInfo->fetchShader->m_shaderStateCacheKeys.emplace_back(h);
|
||||
++g_shaderStateCacheSetCount;
|
||||
}
|
||||
|
||||
ShaderStateInfoAuxVariant auxVariant;
|
||||
auxVariant.combinedAuxHash = combinedAuxHash;
|
||||
auxVariant.vertexShader = vertexShader;
|
||||
auxVariant.pixelShader = pixelShader;
|
||||
auxVariant.geometryShader = geometryShader;
|
||||
auxVariant.hasError = shaderError;
|
||||
if (auxVariant.vertexShader)
|
||||
vectorAppendUnique(auxVariant.vertexShader->m_shaderStateCacheKeys, h);
|
||||
if (auxVariant.pixelShader)
|
||||
vectorAppendUnique(auxVariant.pixelShader->m_shaderStateCacheKeys, h);
|
||||
if (auxVariant.geometryShader)
|
||||
vectorAppendUnique(auxVariant.geometryShader->m_shaderStateCacheKeys, h);
|
||||
shaderStateInfo->auxVariants.emplace_back(auxVariant);
|
||||
++g_shaderStateCacheSetAuxCount;
|
||||
|
||||
// set shaders as active
|
||||
_activeFetchShader = shaderStateInfo->fetchShader;
|
||||
_activeVertexShader = auxVariant.vertexShader;
|
||||
_activePixelShader = auxVariant.pixelShader;
|
||||
_activeGeometryShader = auxVariant.geometryShader;
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -1106,6 +1329,8 @@ void LatteSHRC_Init()
|
||||
cemu_assert_debug(sVertexShaders.empty());
|
||||
cemu_assert_debug(sGeometryShaders.empty());
|
||||
cemu_assert_debug(sPixelShaders.empty());
|
||||
cemu_assert_debug(s_shaderStateCache.empty());
|
||||
cemu_assert_debug(s_shaderStateCacheKeys.empty());
|
||||
}
|
||||
|
||||
void LatteSHRC_UnloadAll()
|
||||
@ -1119,4 +1344,7 @@ void LatteSHRC_UnloadAll()
|
||||
while(!sPixelShaders.empty())
|
||||
LatteShader_free(sPixelShaders.begin()->second);
|
||||
cemu_assert_debug(sPixelShaders.empty());
|
||||
cemu_assert_debug(s_shaderStateCache.empty());
|
||||
s_shaderStateCacheKeys.clear();
|
||||
s_shaderStateCacheCleanupIndex = 0;
|
||||
}
|
||||
|
||||
@ -6,9 +6,10 @@ void LatteSHRC_Init();
|
||||
void LatteSHRC_UnloadAll();
|
||||
|
||||
void LatteSHRC_ResetCachedShaderHash();
|
||||
void LatteShaderSHRC_UpdateFetchShader();
|
||||
LatteFetchShader* LatteSHRC_GetOrCreateFetchShader();
|
||||
|
||||
void LatteSHRC_UpdateActiveShaders();
|
||||
void LatteSHRC_CleanupShaderStateCache();
|
||||
|
||||
struct LatteFetchShader* LatteSHRC_GetActiveFetchShader();
|
||||
LatteDecompilerShader* LatteSHRC_GetActiveVertexShader();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,64 +45,49 @@ typedef struct
|
||||
|
||||
struct LatteDecompilerShaderResourceMapping
|
||||
{
|
||||
static constexpr sint8 UNUSED_BINDING = -1;
|
||||
|
||||
LatteDecompilerShaderResourceMapping()
|
||||
{
|
||||
std::fill(textureUnitToBindingPoint, textureUnitToBindingPoint + LATTE_NUM_MAX_TEX_UNITS, -1);
|
||||
std::fill(uniformBuffersBindingPoint, uniformBuffersBindingPoint + LATTE_NUM_MAX_UNIFORM_BUFFERS, -1);
|
||||
std::fill(attributeMapping, attributeMapping + LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS, -1);
|
||||
std::fill(textureUnitToBindingPoint, textureUnitToBindingPoint + LATTE_NUM_MAX_TEX_UNITS, UNUSED_BINDING);
|
||||
std::fill(relBindingPointToRelTextureUnit, relBindingPointToRelTextureUnit + LATTE_NUM_MAX_TEX_UNITS, UNUSED_BINDING);
|
||||
std::fill(uniformBuffersBindingPoint, uniformBuffersBindingPoint + LATTE_NUM_MAX_UNIFORM_BUFFERS, UNUSED_BINDING);
|
||||
std::fill(attributeMapping, attributeMapping + LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS, UNUSED_BINDING);
|
||||
}
|
||||
static const sint8 UNUSED_BINDING = -1;
|
||||
// most of this is for Vulkan
|
||||
sint8 setIndex{};
|
||||
// texture
|
||||
sint8 textureUnitToBindingPoint[LATTE_NUM_MAX_TEX_UNITS];
|
||||
sint8 textureUnitToBindingPoint[LATTE_NUM_MAX_TEX_UNITS]; // mostly for OpenGL backwards compatibility where texture units are not remapped and binding points are sparse
|
||||
sint8 relBindingPointToRelTextureUnit[LATTE_NUM_MAX_TEX_UNITS]; // (only used on VK and Metal) index is relative binding point (absoluteBindingPoint - textureUnitBaseBindingPoint)
|
||||
sint8 textureUnitBaseBindingPoint{UNUSED_BINDING};
|
||||
sint8 textureUnitCount{0}; // number of entries set in textureUnitToBindingPoint
|
||||
// uniform buffer
|
||||
sint8 uniformVarsBufferBindingPoint{-1}; // special block for uniform registers/remapped array/custom variables
|
||||
sint8 uniformVarsBufferBindingPoint{UNUSED_BINDING}; // special block for uniform registers/remapped array/custom variables
|
||||
sint8 uniformBuffersBindingPoint[LATTE_NUM_MAX_UNIFORM_BUFFERS];
|
||||
// shader storage buffer for transform feedback (if alternative mode is used)
|
||||
sint8 tfStorageBindingPoint{-1};
|
||||
sint8 tfStorageBindingPoint{UNUSED_BINDING};
|
||||
// attributes (vertex shader only)
|
||||
sint8 attributeMapping[LATTE_NUM_MAX_ATTRIBUTE_LOCATIONS];
|
||||
// Vulkan exclusive
|
||||
sint8 setIndex{};
|
||||
// Metal exclusive
|
||||
sint8 verticesPerInstanceBinding{-1};
|
||||
sint8 indexBufferBinding{-1};
|
||||
sint8 indexTypeBinding{-1};
|
||||
sint8 verticesPerInstanceBinding{UNUSED_BINDING};
|
||||
sint8 indexBufferBinding{UNUSED_BINDING};
|
||||
sint8 indexTypeBinding{UNUSED_BINDING};
|
||||
|
||||
sint32 getTextureCount()
|
||||
{
|
||||
sint32 count = 0;
|
||||
for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++)
|
||||
{
|
||||
if (textureUnitToBindingPoint[i] >= 0)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
return textureUnitCount;
|
||||
}
|
||||
|
||||
sint32 getTextureUnitFromBindingPoint(sint8 bindingPoint)
|
||||
sint32 getRelativeTextureUnitFromRelativeBindingPoint(sint8 relativeBindingPoint)
|
||||
{
|
||||
for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++)
|
||||
{
|
||||
if (textureUnitToBindingPoint[i] == bindingPoint)
|
||||
return i;
|
||||
}
|
||||
|
||||
cemu_assert_debug(false);
|
||||
return -1;
|
||||
cemu_assert_debug(relativeBindingPoint >= 0 && relativeBindingPoint < LATTE_NUM_MAX_TEX_UNITS);
|
||||
return relBindingPointToRelTextureUnit[relativeBindingPoint];
|
||||
}
|
||||
|
||||
// returns -1 if no there is no texture binding point
|
||||
sint32 getTextureBaseBindingPoint()
|
||||
{
|
||||
sint32 bindingPoint = 9999;
|
||||
for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++)
|
||||
{
|
||||
if (textureUnitToBindingPoint[i] >= 0)
|
||||
bindingPoint = std::min(bindingPoint, (sint32)textureUnitToBindingPoint[i]);
|
||||
}
|
||||
if (bindingPoint == 9999)
|
||||
return -1;
|
||||
return bindingPoint;
|
||||
return textureUnitBaseBindingPoint;
|
||||
}
|
||||
|
||||
bool getUniformBufferBindingRange(sint32& minBinding, sint32& maxBinding)
|
||||
@ -224,13 +209,15 @@ 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;
|
||||
std::vector<_RemappedUniformBufferGroup> list_remappedUniformEntries_bufferGroups;
|
||||
// keys in shader state cache
|
||||
std::vector<uint64> m_shaderStateCacheKeys;
|
||||
};
|
||||
|
||||
struct LatteDecompilerOutputUniformOffsets
|
||||
|
||||
@ -482,45 +482,58 @@ namespace LatteDecompiler
|
||||
void _initTextureBindingPointsGL(LatteDecompilerShaderContext* decompilerContext)
|
||||
{
|
||||
// for OpenGL we use the relative texture unit index
|
||||
sint8 bindingBase = 0;
|
||||
if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex)
|
||||
bindingBase = LATTE_CEMU_VS_TEX_UNIT_BASE;
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry)
|
||||
bindingBase = LATTE_CEMU_GS_TEX_UNIT_BASE;
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel)
|
||||
bindingBase = LATTE_CEMU_PS_TEX_UNIT_BASE;
|
||||
|
||||
for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++)
|
||||
{
|
||||
if (!decompilerContext->output->textureUnitMask[i])
|
||||
continue;
|
||||
sint32 textureBindingPoint;
|
||||
if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex)
|
||||
textureBindingPoint = i + LATTE_CEMU_VS_TEX_UNIT_BASE;
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Geometry)
|
||||
textureBindingPoint = i + LATTE_CEMU_GS_TEX_UNIT_BASE;
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel)
|
||||
textureBindingPoint = i + LATTE_CEMU_PS_TEX_UNIT_BASE;
|
||||
|
||||
decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = textureBindingPoint;
|
||||
decompilerContext->output->resourceMappingGL.textureUnitToBindingPoint[i] = bindingBase + i;
|
||||
}
|
||||
}
|
||||
|
||||
void _initTextureBindingPointsVK(LatteDecompilerShaderContext* decompilerContext)
|
||||
{
|
||||
// for Vulkan we use consecutive indices
|
||||
decompilerContext->output->resourceMappingVK.textureUnitBaseBindingPoint = decompilerContext->currentBindingPointVK;
|
||||
sint32 relBindingPointIndex = 0;
|
||||
for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++)
|
||||
{
|
||||
if (!decompilerContext->output->textureUnitMask[i])
|
||||
continue;
|
||||
decompilerContext->output->resourceMappingVK.textureUnitToBindingPoint[i] = decompilerContext->currentBindingPointVK;
|
||||
decompilerContext->output->resourceMappingVK.relBindingPointToRelTextureUnit[relBindingPointIndex] = i;
|
||||
relBindingPointIndex++;
|
||||
decompilerContext->output->resourceMappingVK.textureUnitCount++;
|
||||
decompilerContext->currentBindingPointVK++;
|
||||
}
|
||||
if (relBindingPointIndex==0)
|
||||
decompilerContext->output->resourceMappingVK.textureUnitBaseBindingPoint = -1;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_METAL
|
||||
void _initTextureBindingPointsMTL(LatteDecompilerShaderContext* decompilerContext)
|
||||
{
|
||||
// for Vulkan we use consecutive indices
|
||||
decompilerContext->output->resourceMappingMTL.textureUnitBaseBindingPoint = decompilerContext->currentTextureBindingPointMTL;
|
||||
sint32 relBindingPointIndex = 0;
|
||||
for (sint32 i = 0; i < LATTE_NUM_MAX_TEX_UNITS; i++)
|
||||
{
|
||||
if (!decompilerContext->output->textureUnitMask[i] || decompilerContext->shader->textureRenderTargetIndex[i] != 255)
|
||||
continue;
|
||||
decompilerContext->output->resourceMappingMTL.textureUnitToBindingPoint[i] = decompilerContext->currentTextureBindingPointMTL;
|
||||
decompilerContext->output->resourceMappingMTL.relBindingPointToRelTextureUnit[relBindingPointIndex] = i;
|
||||
relBindingPointIndex++;
|
||||
decompilerContext->output->resourceMappingMTL.textureUnitCount++;
|
||||
decompilerContext->currentTextureBindingPointMTL++;
|
||||
}
|
||||
if (relBindingPointIndex==0)
|
||||
decompilerContext->output->resourceMappingMTL.textureUnitBaseBindingPoint = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -549,7 +562,7 @@ namespace LatteDecompiler
|
||||
bool alphaTestEnable = decompilerContext->contextRegistersNew->SX_ALPHA_TEST_CONTROL.get_ALPHA_TEST_ENABLE();
|
||||
if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && alphaTestEnable != 0)
|
||||
decompilerContext->hasUniformVarBlock = true; // uf_alphaTestRef
|
||||
if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel)
|
||||
if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && decompilerContext->analyzer.hasFragCoordAccess)
|
||||
decompilerContext->hasUniformVarBlock = true; // uf_fragCoordScale
|
||||
if (decompilerContext->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->analyzer.outputPointSize && decompilerContext->analyzer.writesPointSize == false)
|
||||
decompilerContext->hasUniformVarBlock = true; // uf_pointSize
|
||||
@ -594,6 +607,11 @@ namespace LatteDecompiler
|
||||
decompilerContext->output->resourceMappingMTL.uniformVarsBufferBindingPoint = decompilerContext->currentBufferBindingPointMTL;
|
||||
decompilerContext->currentBufferBindingPointMTL++;
|
||||
}
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel) // compatibility workaround to keep binding indices the same with existing gfx pack shader replacements, we now only emit uf_fragCoord when needed and it can lead to hasUniformVarBlock being false
|
||||
{
|
||||
decompilerContext->currentBindingPointVK++;
|
||||
decompilerContext->currentBufferBindingPointMTL++;
|
||||
}
|
||||
// assign binding points to uniform buffers
|
||||
if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK)
|
||||
{
|
||||
@ -993,6 +1011,38 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD
|
||||
shaderContext->analyzer.gprUseMask[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
}
|
||||
// check if shader accesses frag coord
|
||||
if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
||||
{
|
||||
LatteShaderPSInputTable* psInputTable = LatteSHRC_GetPSInputTable();
|
||||
for (sint32 i = 0; i < psInputTable->count; i++)
|
||||
{
|
||||
sint32 gprIndex = i;
|
||||
if ((shaderContext->analyzer.gprUseMask[gprIndex / 8] & (1 << (gprIndex % 8))) == 0 && shaderContext->analyzer.usesRelativeGPRRead == false)
|
||||
continue;
|
||||
uint32 psInputSemanticId = psInputTable->import[i].semanticId;
|
||||
if (psInputSemanticId == LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION)
|
||||
{
|
||||
shaderContext->analyzer.hasFragCoordAccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// some existing graphic pack replacement shaders rely on uf_fragCoordScale despite the original shader not needing it. We handle these exceptions here
|
||||
switch (shaderContext->shaderBaseHash)
|
||||
{
|
||||
case 0x21e6bc9b0cdbe8d7: case 0x37040a485a29d54e: case 0x37a4ec1a7dbc7391:
|
||||
case 0x50e29e8929cea348: case 0x572a6cfa3943923d: case 0x59df1c7e1806366c:
|
||||
case 0x6ea8b1aa69c0b6f7: case 0x88133ee405eaae28: case 0x95a5a89d62998e0d:
|
||||
case 0x998a9f67e353657b: case 0x9f6adb9a651f84b9: case 0xa5e9d150276a805c:
|
||||
case 0xa7f4801a8d29e333: case 0xbe99d80628d31127: case 0xc14019840473ff86:
|
||||
case 0xc612390d4c70f430: case 0xcb0e6e8cbec4502a: case 0xe334517825fdd599:
|
||||
case 0xe39a2a718bc419fe: case 0xfdf33c607cd1d737: case 0xff71dcd2ad4defdc:
|
||||
shaderContext->analyzer.hasFragCoordAccess = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// analyze CF stack
|
||||
sint32 cfCurrentStackDepth = 0;
|
||||
sint32 cfCurrentMaxStackDepth = 0;
|
||||
|
||||
@ -83,9 +83,15 @@ namespace LatteDecompiler
|
||||
}
|
||||
}
|
||||
// define uf_fragCoordScale which holds the xy scale for render target resolution vs effective resolution
|
||||
bool compatNeedFragCoordScalePadding = false; // 2026-06-15 - uf_fragCoordScale is only emitted when accessed now. To keep compatible with old shader replacements we insert padding if its not the last element
|
||||
if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
||||
{
|
||||
if (rendererType == RendererAPI::OpenGL)
|
||||
if (!decompilerContext->analyzer.hasFragCoordAccess)
|
||||
{
|
||||
// omit uf_fragCoordScale
|
||||
compatNeedFragCoordScalePadding = true;
|
||||
}
|
||||
else if (rendererType == RendererAPI::OpenGL)
|
||||
{
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7)&~7;
|
||||
shaderSrc->add("uniform vec2 uf_fragCoordScale;" _CRLF);
|
||||
@ -106,6 +112,20 @@ namespace LatteDecompiler
|
||||
{
|
||||
if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false)
|
||||
continue;
|
||||
if (compatNeedFragCoordScalePadding)
|
||||
{
|
||||
if (rendererType == RendererAPI::OpenGL)
|
||||
{
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7)&~7;
|
||||
shaderSrc->add("uniform vec2 uf_fragCoordScaleCompatPadding;" _CRLF); uniformCurrentOffset += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 15)&~15;
|
||||
shaderSrc->add("uniform vec4 uf_fragCoordScaleCompatPadding;" _CRLF); uniformCurrentOffset += 16;
|
||||
}
|
||||
compatNeedFragCoordScalePadding = false;
|
||||
}
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7;
|
||||
shaderSrc->addFmt("uniform vec2 uf_tex{}Scale;" _CRLF, t);
|
||||
uniformOffsets.offset_texScale[t] = uniformCurrentOffset;
|
||||
@ -116,6 +136,7 @@ namespace LatteDecompiler
|
||||
(shader->shaderType == LatteConst::ShaderType::Vertex && decompilerContext->options->usesGeometryShader == false) ||
|
||||
(shader->shaderType == LatteConst::ShaderType::Geometry) )
|
||||
{
|
||||
// note - we dont need to handle compatNeedFragCoordScalePadding here because it's pixel shader only
|
||||
shaderSrc->add("uniform int uf_verticesPerInstance;" _CRLF);
|
||||
uniformOffsets.offset_verticesPerInstance = uniformCurrentOffset;
|
||||
uniformCurrentOffset += 4;
|
||||
@ -280,7 +301,7 @@ namespace LatteDecompiler
|
||||
src->add("#define V2G_LAYOUT layout(location = 0)" _CRLF);
|
||||
}
|
||||
}
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel)
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && decompilerContext->analyzer.hasFragCoordAccess)
|
||||
{
|
||||
src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale.xy,gl_FragCoord.z, 1.0/gl_FragCoord.w)" _CRLF);
|
||||
}
|
||||
@ -305,7 +326,7 @@ namespace LatteDecompiler
|
||||
if (decompilerContext->options->usesGeometryShader)
|
||||
src->add("#define V2G_LAYOUT" _CRLF);
|
||||
}
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel)
|
||||
else if (decompilerContext->shaderType == LatteConst::ShaderType::Pixel && decompilerContext->analyzer.hasFragCoordAccess)
|
||||
{
|
||||
src->add("#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale,gl_FragCoord.zw)" _CRLF);
|
||||
}
|
||||
|
||||
@ -68,18 +68,34 @@ namespace LatteDecompiler
|
||||
}
|
||||
}
|
||||
// define fragCoordScale which holds the xy scale for render target resolution vs effective resolution
|
||||
bool compatNeedFragCoordScalePadding = false; // 2026-06-15 - fragCoordScale is only emitted when accessed now. To keep compatible with old shader replacements we insert padding if its not the last element
|
||||
if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
||||
{
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7)&~7;
|
||||
src->add("float2 fragCoordScale;" _CRLF);
|
||||
uniformOffsets.offset_fragCoordScale = uniformCurrentOffset;
|
||||
uniformCurrentOffset += 8;
|
||||
if (!decompilerContext->analyzer.hasFragCoordAccess)
|
||||
{
|
||||
// omit fragCoordScale
|
||||
compatNeedFragCoordScalePadding = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7)&~7;
|
||||
src->add("float2 fragCoordScale;" _CRLF);
|
||||
uniformOffsets.offset_fragCoordScale = uniformCurrentOffset;
|
||||
uniformCurrentOffset += 8;
|
||||
}
|
||||
}
|
||||
// provide scale factor for every texture that is accessed via texel coordinates (texelFetch)
|
||||
for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++)
|
||||
{
|
||||
if (decompilerContext->analyzer.texUnitUsesTexelCoordinates.test(t) == false)
|
||||
continue;
|
||||
if (compatNeedFragCoordScalePadding)
|
||||
{
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7)&~7;
|
||||
src->add("float2 fragCoordScaleCompatPadding;" _CRLF);
|
||||
uniformCurrentOffset += 8;
|
||||
compatNeedFragCoordScalePadding = false;
|
||||
}
|
||||
uniformCurrentOffset = (uniformCurrentOffset + 7) & ~7;
|
||||
src->addFmt("float2 tex{}Scale;" _CRLF, t);
|
||||
uniformOffsets.offset_texScale[t] = uniformCurrentOffset;
|
||||
@ -248,7 +264,8 @@ namespace LatteDecompiler
|
||||
{
|
||||
auto* src = shaderContext->shaderSource;
|
||||
|
||||
src->add("#define GET_FRAGCOORD() float4(in.position.xy * supportBuffer.fragCoordScale.xy, in.position.z, 1.0 / in.position.w)" _CRLF);
|
||||
if (shaderContext->analyzer.hasFragCoordAccess)
|
||||
src->add("#define GET_FRAGCOORD() float4(in.position.xy * supportBuffer.fragCoordScale.xy, in.position.z, 1.0 / in.position.w)" _CRLF);
|
||||
|
||||
src->add("struct FragmentIn {" _CRLF);
|
||||
src->add("float4 position [[position]];" _CRLF);
|
||||
|
||||
@ -237,6 +237,7 @@ struct LatteDecompilerShaderContext
|
||||
uint8 gprUseMask[(LATTE_NUM_GPR + 7) / 8]; // 1 bit per GPR, set if GPR is read/written anywhere in the program (ignores GPR accesses with relative index)
|
||||
bool hasStreamoutWrite; // stream-out CF instructions are used
|
||||
bool hasRedcCUBE; // has cube reduction instruction
|
||||
bool hasFragCoordAccess{false}; // accesses gl_FragCoord
|
||||
bool modifiesPixelActiveState; // set if the active mask is changed anywhere in the shader (If false, we can skip active mask checks)
|
||||
bool usesIntegerValues; // set if the shader uses any kind of integer instruction or integer-based GPR/AR access
|
||||
sint32 activeStackMaxDepth; // maximum depth of pixel state stack
|
||||
|
||||
@ -50,7 +50,7 @@ std::vector<MetalRenderer::DeviceInfo> MetalRenderer::GetDevices()
|
||||
return result;
|
||||
}
|
||||
|
||||
MetalRenderer::MetalRenderer()
|
||||
MetalRenderer::MetalRenderer() : Renderer(RendererAPI::Metal)
|
||||
{
|
||||
// Options
|
||||
|
||||
@ -1067,7 +1067,7 @@ void MetalRenderer::draw_beginSequence()
|
||||
m_state.m_skipDrawSequence = true;
|
||||
}
|
||||
|
||||
void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst)
|
||||
void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext)
|
||||
{
|
||||
if (m_state.m_skipDrawSequence)
|
||||
{
|
||||
@ -1142,10 +1142,9 @@ void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
// Index buffer
|
||||
Renderer::INDEX_TYPE hostIndexType;
|
||||
uint32 hostIndexCount;
|
||||
uint32 indexMin = 0;
|
||||
uint32 indexMax = 0;
|
||||
Renderer::IndexAllocation indexAllocation;
|
||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||
auto indexAllocationMtl = static_cast<MetalSynchronizedHeapAllocator::AllocatorReservation*>(indexAllocation.rendererInternal);
|
||||
|
||||
// Buffer cache
|
||||
@ -1164,7 +1163,8 @@ void MetalRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
{
|
||||
// synchronize vertex and uniform cache and update buffer bindings
|
||||
// We need to call this before getting the render command encoder, since it can cause buffer copies
|
||||
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
|
||||
uint8 stageUniformModifiedMask = 0;
|
||||
LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask);
|
||||
}
|
||||
|
||||
// Render pass
|
||||
@ -1996,7 +1996,7 @@ void MetalRenderer::BindStageResources(MTL::RenderCommandEncoder* renderCommandE
|
||||
sint32 textureCount = shader->resourceMapping.getTextureCount();
|
||||
for (int i = 0; i < textureCount; ++i)
|
||||
{
|
||||
const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i);
|
||||
const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i);
|
||||
auto hostTextureUnit = relative_textureUnit;
|
||||
|
||||
// Don't bind textures that are accessed with a framebuffer fetch
|
||||
@ -2138,7 +2138,7 @@ void MetalRenderer::BindStageResources(MTL::RenderCommandEncoder* renderCommandE
|
||||
}
|
||||
if (shader->uniform.loc_remapped >= 0)
|
||||
{
|
||||
LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped));
|
||||
LatteBufferCache_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), true, (1<<LATTE_NUM_MAX_UNIFORM_BUFFERS)-1);
|
||||
}
|
||||
if (shader->uniform.loc_uniformRegister >= 0)
|
||||
{
|
||||
|
||||
@ -153,11 +153,6 @@ public:
|
||||
MetalRenderer();
|
||||
~MetalRenderer() override;
|
||||
|
||||
RendererAPI GetType() override
|
||||
{
|
||||
return RendererAPI::Metal;
|
||||
}
|
||||
|
||||
static MetalRenderer* GetInstance() {
|
||||
return static_cast<MetalRenderer*>(g_renderer.get());
|
||||
}
|
||||
@ -250,7 +245,7 @@ public:
|
||||
|
||||
// core drawing logic
|
||||
void draw_beginSequence() override;
|
||||
void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override;
|
||||
void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) override;
|
||||
void draw_endSequence() override;
|
||||
|
||||
void draw_updateVertexBuffersDirectAccess();
|
||||
|
||||
@ -111,7 +111,7 @@ static const GLenum glAlphaTestFunc[] =
|
||||
GL_ALWAYS
|
||||
};
|
||||
|
||||
OpenGLRenderer::OpenGLRenderer()
|
||||
OpenGLRenderer::OpenGLRenderer() : Renderer(RendererAPI::OpenGL)
|
||||
{
|
||||
glRendererState.useTextureUploadBuffer = false;
|
||||
if (glRendererState.useTextureUploadBuffer)
|
||||
|
||||
@ -35,8 +35,6 @@ public:
|
||||
OpenGLRenderer();
|
||||
~OpenGLRenderer();
|
||||
|
||||
RendererAPI GetType() override { return RendererAPI::OpenGL; }
|
||||
|
||||
static OpenGLRenderer* GetInstance();
|
||||
|
||||
// imgui
|
||||
@ -159,7 +157,7 @@ public:
|
||||
void draw_init();
|
||||
|
||||
void draw_beginSequence() override;
|
||||
void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override;
|
||||
void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) override;
|
||||
void draw_endSequence() override;
|
||||
|
||||
template<bool TIsMinimal, bool THasProfiling>
|
||||
|
||||
@ -975,7 +975,8 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta
|
||||
endPerfMonProfiling(performanceMonitor.gpuTime_dcStageIndexMgr);
|
||||
|
||||
// synchronize vertex and uniform buffers
|
||||
LatteBufferCache_Sync(indexState.minIndex + baseVertex, indexState.maxIndex + baseVertex, baseInstance, instanceCount);
|
||||
uint8 stageUniformModifiedMask = 0;
|
||||
LatteBufferCache_Sync(indexState.maxIndex + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask);
|
||||
|
||||
_setupVertexAttributes();
|
||||
|
||||
@ -1148,9 +1149,9 @@ void OpenGLRenderer::draw_beginSequence()
|
||||
// no-op
|
||||
}
|
||||
|
||||
void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst)
|
||||
void OpenGLRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext)
|
||||
{
|
||||
bool isMinimal = !isFirst;
|
||||
bool isMinimal = !drawcallContext.isFirst;
|
||||
if (isMinimal)
|
||||
draw_genericDrawHandler<true, false>(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType);
|
||||
else
|
||||
|
||||
@ -36,7 +36,7 @@ void OpenGLRenderer::uniformData_update()
|
||||
auto& list_uniformMapping = shader->list_remappedUniformEntries;
|
||||
cemu_assert_debug(list_uniformMapping.size() <= 256);
|
||||
sint32 remappedArraySize = (sint32)list_uniformMapping.size();
|
||||
LatteBufferCache_LoadRemappedUniforms(shader, (float*)(_gl_remappedUniformData));
|
||||
LatteBufferCache_LoadRemappedUniforms(shader, (float*)(_gl_remappedUniformData), true, (1<<LATTE_NUM_MAX_UNIFORM_BUFFERS)-1);
|
||||
// update values only when the hash changed
|
||||
if (remappedArraySize > 0)
|
||||
{
|
||||
|
||||
@ -50,9 +50,10 @@ public:
|
||||
U32
|
||||
};
|
||||
|
||||
Renderer(RendererAPI api) : m_rendererAPI(api) {};
|
||||
virtual ~Renderer() = default;
|
||||
|
||||
virtual RendererAPI GetType() = 0;
|
||||
RendererAPI GetType() const { return m_rendererAPI; }
|
||||
|
||||
virtual void Initialize();
|
||||
virtual void Shutdown();
|
||||
@ -140,7 +141,7 @@ public:
|
||||
|
||||
// core drawing logic
|
||||
virtual void draw_beginSequence() = 0;
|
||||
virtual void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) = 0;
|
||||
virtual void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) = 0;
|
||||
virtual void draw_endSequence() = 0;
|
||||
|
||||
// index
|
||||
@ -162,6 +163,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual void GetVendorInformation() { }
|
||||
RendererAPI m_rendererAPI;
|
||||
GfxVendor m_vendor = GfxVendor::Generic;
|
||||
|
||||
static uint8 SRGBComponentToRGB(uint8 ci);
|
||||
|
||||
@ -19,6 +19,8 @@ uint32 RendererShader::GeneratePrecompiledCacheId()
|
||||
// settings that can influence shaders
|
||||
v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133;
|
||||
|
||||
v += 0x820a5277; // change this value for manual invalidation
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@ -126,7 +126,10 @@ void CachedFBOVk::InitDynamicRenderingData()
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_vkColorAttachments[i].imageLayout = static_cast<LatteTextureVk*>(buffer.texture->baseTexture)->GetDefaultLayout();
|
||||
m_vkColorAttachments[i].imageView = cbView->m_textureImageView;
|
||||
}
|
||||
}
|
||||
|
||||
m_vkRenderingInfo.pColorAttachments = m_vkColorAttachments;
|
||||
@ -167,6 +170,9 @@ void CachedFBOVk::InitDynamicRenderingData()
|
||||
// setup depth and stencil attachment
|
||||
if (depthStencilView)
|
||||
{
|
||||
auto depthTexVk = static_cast<LatteTextureVk*>(depthBuffer.texture->baseTexture);
|
||||
m_vkDepthAttachment.imageLayout = depthTexVk->GetDefaultLayout();
|
||||
m_vkStencilAttachment.imageLayout = depthTexVk->GetDefaultLayout();
|
||||
m_vkDepthAttachment.imageView = depthStencilView->m_textureImageView;
|
||||
m_vkDepthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||
m_vkDepthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
@ -189,41 +195,46 @@ void CachedFBOVk::InitDynamicRenderingData()
|
||||
m_vkRenderingInfo.layerCount = 1;
|
||||
}
|
||||
|
||||
static uint32 s_selfDependencyCheckIndex = 1;
|
||||
|
||||
uint32 s_currentCollisionCheckIndex = 1;
|
||||
|
||||
bool CachedFBOVk::CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const
|
||||
CachedFBOVk::RendertargetSelfDependencyMask CachedFBOVk::CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const
|
||||
{
|
||||
s_currentCollisionCheckIndex++;
|
||||
const uint32 curColIndex = s_currentCollisionCheckIndex;
|
||||
for (auto& itr : m_referencedTextures)
|
||||
s_selfDependencyCheckIndex++;
|
||||
const uint32 curColIndex = s_selfDependencyCheckIndex;
|
||||
for (auto& colorAttachment : colorBuffer)
|
||||
{
|
||||
LatteTextureVk* vkTex = (LatteTextureVk*)itr;
|
||||
vkTex->m_collisionCheckIndex = curColIndex;
|
||||
}
|
||||
if (vsDS)
|
||||
{
|
||||
for (auto& itr : vsDS->list_fboCandidates)
|
||||
if (colorAttachment.texture)
|
||||
{
|
||||
if (itr->m_collisionCheckIndex == curColIndex)
|
||||
return true;
|
||||
LatteTextureVk* vkTex = static_cast<LatteTextureVk*>(colorAttachment.texture->baseTexture);
|
||||
vkTex->m_selfDependencyCheckIndex = curColIndex;
|
||||
vkTex->m_selfDependencyCheckAspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
}
|
||||
if (gsDS)
|
||||
if (depthBuffer.texture)
|
||||
{
|
||||
for (auto& itr : gsDS->list_fboCandidates)
|
||||
{
|
||||
if (itr->m_collisionCheckIndex == curColIndex)
|
||||
return true;
|
||||
}
|
||||
LatteTextureVk* vkTex = static_cast<LatteTextureVk*>(depthBuffer.texture->baseTexture);
|
||||
vkTex->m_selfDependencyCheckIndex = curColIndex;
|
||||
vkTex->m_selfDependencyCheckAspectMask = depthBuffer.hasStencil ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT): VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
}
|
||||
if (psDS)
|
||||
|
||||
auto getSelfDependencyMask = [curColIndex](VkDescriptorSetInfo* ds) -> VkImageAspectFlags
|
||||
{
|
||||
for (auto& itr : psDS->list_fboCandidates)
|
||||
VkImageAspectFlags aspectMask = 0;
|
||||
if (!ds)
|
||||
return aspectMask;
|
||||
for (auto& itr : ds->list_fboCandidates)
|
||||
{
|
||||
if (itr->m_collisionCheckIndex == curColIndex)
|
||||
return true;
|
||||
if (itr->m_selfDependencyCheckIndex == curColIndex)
|
||||
aspectMask |= itr->m_selfDependencyCheckAspectMask;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return aspectMask;
|
||||
};
|
||||
|
||||
RendertargetSelfDependencyMask selfDepInfo{};
|
||||
VkImageAspectFlags vertexAspectFlags = getSelfDependencyMask(vsDS);
|
||||
VkImageAspectFlags geometryAspectFlags = getSelfDependencyMask(gsDS);
|
||||
VkImageAspectFlags pixelAspectFlags = getSelfDependencyMask(psDS);
|
||||
selfDepInfo.aspectMaskFlags = vertexAspectFlags | geometryAspectFlags | pixelAspectFlags;
|
||||
selfDepInfo.hasNonPixelSelfDependency = (vertexAspectFlags | geometryAspectFlags) != 0;
|
||||
return selfDepInfo;
|
||||
}
|
||||
|
||||
@ -51,8 +51,29 @@ public:
|
||||
|
||||
[[nodiscard]] const VkExtent2D& GetExtend() const { return m_extend;}
|
||||
|
||||
struct RendertargetSelfDependencyMask
|
||||
{
|
||||
VkImageAspectFlags aspectMaskFlags{}; // aspect flags which are simultaneously sampled and written
|
||||
bool hasNonPixelSelfDependency{false};
|
||||
|
||||
VkImageAspectFlags GetAspectMask() const
|
||||
{
|
||||
return aspectMaskFlags;
|
||||
}
|
||||
|
||||
bool HasSelfDependency() const
|
||||
{
|
||||
return GetAspectMask() != 0;
|
||||
}
|
||||
|
||||
bool HasVertexOrGeometrySelfDependency() const
|
||||
{
|
||||
return hasNonPixelSelfDependency; // vertex or geometry shader samples texture which is written to
|
||||
}
|
||||
};
|
||||
|
||||
// checks if any of the sampled textures are output by the FBO
|
||||
bool CheckForCollision(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const;
|
||||
RendertargetSelfDependencyMask CheckForSelfDependency(VkDescriptorSetInfo* vsDS, VkDescriptorSetInfo* gsDS, VkDescriptorSetInfo* psDS) const;
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@ -67,6 +67,12 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
}
|
||||
|
||||
if (m_vkr->UseAttachmentFeedbackLoop() && (imageInfo.usage & (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) != 0)
|
||||
{
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_ATTACHMENT_FEEDBACK_LOOP_BIT_EXT;
|
||||
m_defaultLayout = VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT;
|
||||
}
|
||||
|
||||
if (dim == Latte::E_DIM::DIM_2D)
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
else if (dim == Latte::E_DIM::DIM_1D)
|
||||
|
||||
@ -20,6 +20,7 @@ public:
|
||||
|
||||
VkFormat GetFormat() const { return vkObjTex->m_format; }
|
||||
VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; }
|
||||
VkImageLayout GetDefaultLayout() const { return m_defaultLayout; }
|
||||
|
||||
VkImageLayout GetImageLayout(VkImageSubresource& subresource)
|
||||
{
|
||||
@ -83,12 +84,14 @@ public:
|
||||
uint64 m_vkFlushIndex_read{};
|
||||
uint64 m_vkFlushIndex_write{};
|
||||
|
||||
uint32 m_collisionCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall
|
||||
uint32 m_selfDependencyCheckIndex{}; // used to track if texture is being both sampled and output to during drawcall
|
||||
VkImageAspectFlags m_selfDependencyCheckAspectMask{};
|
||||
|
||||
private:
|
||||
class VulkanRenderer* m_vkr;
|
||||
|
||||
VKRObjectTexture* vkObjTex{};
|
||||
VkImageLayout m_defaultLayout{ VK_IMAGE_LAYOUT_GENERAL }; // the targetted long term layout of the texture. Can be either VK_IMAGE_LAYOUT_GENERAL or VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT for potential rendertargets if supported
|
||||
std::vector<VkImageLayout> m_layouts;
|
||||
uint32 m_layoutsMips;
|
||||
uint32 m_layoutsDepth;
|
||||
|
||||
@ -122,15 +122,15 @@ void LatteTextureReadbackInfoVk::StartTransfer()
|
||||
const auto renderer = VulkanRenderer::GetInstance();
|
||||
renderer->draw_endRenderPass();
|
||||
|
||||
renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL);
|
||||
renderer->barrier_image<VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE, VulkanRenderer::TRANSFER_READ>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
||||
|
||||
renderer->barrier_sequentializeTransfer();
|
||||
|
||||
vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_GENERAL, m_buffer, 1, ®ion);
|
||||
vkCmdCopyImageToBuffer(renderer->getCurrentCommandBuffer(), baseTexture->GetImageObj()->m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, m_buffer, 1, ®ion);
|
||||
|
||||
renderer->barrier_sequentializeTransfer();
|
||||
|
||||
renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); // make sure transfer is finished before image is modified
|
||||
renderer->barrier_image<VulkanRenderer::TRANSFER_READ, VulkanRenderer::ANY_TRANSFER | VulkanRenderer::IMAGE_WRITE>(baseTexture, region.imageSubresource, baseTexture->GetDefaultLayout()); // make sure transfer is finished before image is modified
|
||||
renderer->barrier_bufferRange<VulkanRenderer::TRANSFER_WRITE, VulkanRenderer::HOST_READ>(m_buffer, m_buffer_offset, m_image_size); // make sure transfer is finished before result is read
|
||||
|
||||
m_associatedCommandBufferId = renderer->GetCurrentCommandBufferId();
|
||||
|
||||
@ -58,20 +58,13 @@ PipelineInfo::~PipelineInfo()
|
||||
}
|
||||
|
||||
// delete descriptor sets
|
||||
while (!pixel_ds_cache.empty())
|
||||
for (auto& it : ds_cache)
|
||||
{
|
||||
VkDescriptorSetInfo* dsInfo = pixel_ds_cache.begin()->second;
|
||||
delete dsInfo;
|
||||
}
|
||||
while (!geometry_ds_cache.empty())
|
||||
{
|
||||
VkDescriptorSetInfo* dsInfo = geometry_ds_cache.begin()->second;
|
||||
delete dsInfo;
|
||||
}
|
||||
while (!vertex_ds_cache.empty())
|
||||
{
|
||||
VkDescriptorSetInfo* dsInfo = vertex_ds_cache.begin()->second;
|
||||
delete dsInfo;
|
||||
while (!it.empty())
|
||||
{
|
||||
VkDescriptorSetInfo* dsInfo = it.begin()->second;
|
||||
delete dsInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// disassociate from shaders
|
||||
|
||||
@ -58,6 +58,14 @@ void VulkanBenchmarkPrintResults()
|
||||
cemuLog_log(LogType::Force, "--- Vulkan API CPU benchmark ---");
|
||||
cemuLog_log(LogType::Force, "Elapsed cycles this frame: {:} | Current cycle {:} | NumFunc {:}", elapsedCycles, currentCycle, s_vulkanBenchmarkFuncs.size());
|
||||
|
||||
// sum up total time of Vulkan calls
|
||||
uint64 totalVkCycles = 0;
|
||||
for (auto& it : s_vulkanBenchmarkFuncs)
|
||||
{
|
||||
totalVkCycles += it->cycles;
|
||||
}
|
||||
cemuLog_log(LogType::Force, "Total Vulkan time: {:.4}%", ((double)totalVkCycles / elapsedCyclesDbl) * 100.0);
|
||||
|
||||
std::vector<sint32> sortedIndices(s_vulkanBenchmarkFuncs.size());
|
||||
std::iota(sortedIndices.begin(), sortedIndices.end(), 0);
|
||||
std::sort(sortedIndices.begin(), sortedIndices.end(),
|
||||
|
||||
@ -189,6 +189,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR);
|
||||
VKFUNC_DEVICE(vkCmdBeginRenderingKHR);
|
||||
VKFUNC_DEVICE(vkCmdEndRenderingKHR);
|
||||
|
||||
// ext_attachment_feedback_loop_dynamic_state
|
||||
VKFUNC_DEVICE(vkCmdSetAttachmentFeedbackLoopEnableEXT);
|
||||
|
||||
// khr_present_wait
|
||||
VKFUNC_DEVICE(vkWaitForPresentKHR);
|
||||
|
||||
|
||||
@ -815,6 +815,10 @@ void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBle
|
||||
dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
|
||||
pipelineInfo->usesDepthBias = true;
|
||||
}
|
||||
if (VulkanRenderer::GetInstance()->UseAttachmentFeedbackLoop())
|
||||
{
|
||||
dynamicStates.emplace_back(VK_DYNAMIC_STATE_ATTACHMENT_FEEDBACK_LOOP_ENABLE_EXT);
|
||||
}
|
||||
|
||||
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamicState.dynamicStateCount = dynamicStates.size();
|
||||
@ -1135,4 +1139,4 @@ void PipelineCompiler::CompileThreadPool_Stop()
|
||||
void PipelineCompiler::CompileThreadPool_QueueCompilation(PipelineCompiler* v)
|
||||
{
|
||||
s_pipelineCompileRequests.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,9 @@ const std::vector<const char*> kOptionalDeviceExtensions =
|
||||
VK_KHR_PRESENT_WAIT_EXTENSION_NAME,
|
||||
VK_KHR_PRESENT_ID_EXTENSION_NAME,
|
||||
VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
|
||||
VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME
|
||||
VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME,
|
||||
VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME,
|
||||
VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME
|
||||
};
|
||||
|
||||
const std::vector<const char*> kRequiredDeviceExtensions =
|
||||
@ -271,6 +273,22 @@ void VulkanRenderer::GetDeviceFeatures()
|
||||
prevStruct = &pprf;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{};
|
||||
if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state)
|
||||
{
|
||||
attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT;
|
||||
attachmentFeedbackLoopDynamicStateFeature.pNext = prevStruct;
|
||||
prevStruct = &attachmentFeedbackLoopDynamicStateFeature;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{};
|
||||
if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout)
|
||||
{
|
||||
attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT;
|
||||
attachmentFeedbackLoopLayoutFeature.pNext = prevStruct;
|
||||
prevStruct = &attachmentFeedbackLoopLayoutFeature;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
|
||||
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
physicalDeviceFeatures2.pNext = prevStruct;
|
||||
@ -330,10 +348,18 @@ void VulkanRenderer::GetDeviceFeatures()
|
||||
if ( pprf.pipelineRobustness != VK_TRUE )
|
||||
m_featureControl.deviceExtensions.pipeline_robustness = false;
|
||||
}
|
||||
if (m_featureControl.deviceExtensions.attachment_feedback_loop_layout)
|
||||
m_featureControl.deviceExtensions.attachment_feedback_loop_layout = attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout == VK_TRUE;
|
||||
if (m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state && m_featureControl.deviceExtensions.attachment_feedback_loop_layout)
|
||||
m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state = attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState == VK_TRUE;
|
||||
if (!UseAttachmentFeedbackLoop())
|
||||
cemuLog_log(LogType::Force, "VK_EXT_attachment_feedback_loop_layout(_dynamic_state) not supported");
|
||||
// get limits
|
||||
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
|
||||
@ -466,7 +492,7 @@ static void LinuxBreathOfTheWildWorkaround(VkInstance& instance, const VkInstanc
|
||||
|
||||
#endif
|
||||
|
||||
VulkanRenderer::VulkanRenderer()
|
||||
VulkanRenderer::VulkanRenderer() : Renderer(RendererAPI::Vulkan)
|
||||
{
|
||||
glslang::InitializeProcess();
|
||||
|
||||
@ -696,6 +722,21 @@ VulkanRenderer::VulkanRenderer()
|
||||
deviceExtensionFeatures = &pipelineRobustnessFeature;
|
||||
pipelineRobustnessFeature.pipelineRobustness = VK_TRUE;
|
||||
}
|
||||
// enable attachment feedback loop layout + dynamic state if both are supported
|
||||
VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachmentFeedbackLoopLayoutFeature{};
|
||||
VkPhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT attachmentFeedbackLoopDynamicStateFeature{};
|
||||
if (UseAttachmentFeedbackLoop())
|
||||
{
|
||||
attachmentFeedbackLoopLayoutFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT;
|
||||
attachmentFeedbackLoopLayoutFeature.pNext = deviceExtensionFeatures;
|
||||
deviceExtensionFeatures = &attachmentFeedbackLoopLayoutFeature;
|
||||
attachmentFeedbackLoopLayoutFeature.attachmentFeedbackLoopLayout = VK_TRUE;
|
||||
|
||||
attachmentFeedbackLoopDynamicStateFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_FEATURES_EXT;
|
||||
attachmentFeedbackLoopDynamicStateFeature.pNext = deviceExtensionFeatures;
|
||||
deviceExtensionFeatures = &attachmentFeedbackLoopDynamicStateFeature;
|
||||
attachmentFeedbackLoopDynamicStateFeature.attachmentFeedbackLoopDynamicState = VK_TRUE;
|
||||
}
|
||||
|
||||
std::vector<const char*> used_extensions;
|
||||
VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions);
|
||||
@ -867,7 +908,7 @@ VulkanRenderer::~VulkanRenderer()
|
||||
defaultShaders.copySurface_psDepth2Color = nullptr;
|
||||
|
||||
// destroy misc
|
||||
for (auto& it : m_cmd_buffer_fences)
|
||||
for (auto& it : m_cmdBufferFences)
|
||||
{
|
||||
vkDestroyFence(m_logicalDevice, it, nullptr);
|
||||
it = VK_NULL_HANDLE;
|
||||
@ -917,11 +958,8 @@ VulkanRenderer::~VulkanRenderer()
|
||||
|
||||
VulkanRenderer* VulkanRenderer::GetInstance()
|
||||
{
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
cemu_assert_debug(g_renderer && dynamic_cast<VulkanRenderer*>(g_renderer.get()));
|
||||
// Use #if here because dynamic_casts dont get optimized away even if the result is not stored as with cemu_assert_debug
|
||||
#endif
|
||||
return (VulkanRenderer*)g_renderer.get();
|
||||
cemu_assert_debug(g_renderer->GetType() == RendererAPI::Vulkan);
|
||||
return static_cast<VulkanRenderer*>(g_renderer.get());
|
||||
}
|
||||
|
||||
void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow)
|
||||
@ -1120,7 +1158,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
|
||||
range.mipLevel = 0;
|
||||
range.baseArrayLayer = texViewVk->firstSlice;
|
||||
range.layerCount = 1;
|
||||
barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, range, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, range, baseImageTex->GetDefaultLayout());
|
||||
}
|
||||
|
||||
format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
@ -1176,6 +1214,10 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad
|
||||
}
|
||||
|
||||
vkCmdCopyImageToBuffer(m_state.currentCommandBuffer, dumpImage, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, ®ion);
|
||||
if (dumpImage == baseImage)
|
||||
{
|
||||
barrier_image<TRANSFER_READ, TRANSFER_WRITE | IMAGE_WRITE>(baseImageTex, region.imageSubresource, baseImageTex->GetDefaultLayout());
|
||||
}
|
||||
|
||||
SubmitCommandBuffer();
|
||||
WaitCommandBufferFinished(GetCurrentCommandBufferId());
|
||||
@ -1280,6 +1322,11 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe
|
||||
}
|
||||
if (m_featureControl.deviceExtensions.pipeline_robustness)
|
||||
used_extensions.emplace_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME);
|
||||
if (UseAttachmentFeedbackLoop())
|
||||
{
|
||||
used_extensions.emplace_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME);
|
||||
used_extensions.emplace_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
VkDeviceCreateInfo createInfo{};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
@ -1377,6 +1424,8 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device,
|
||||
info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
|
||||
info.deviceExtensions.depth_clip_enable = isExtensionAvailable(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
|
||||
info.deviceExtensions.pipeline_robustness = isExtensionAvailable(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME);
|
||||
info.deviceExtensions.attachment_feedback_loop_layout = isExtensionAvailable(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME);
|
||||
info.deviceExtensions.attachment_feedback_loop_dynamic_state = isExtensionAvailable(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME);
|
||||
// dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022
|
||||
info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME);
|
||||
|
||||
@ -1612,14 +1661,14 @@ void VulkanRenderer::CreateCommandPool()
|
||||
|
||||
void VulkanRenderer::CreateCommandBuffers()
|
||||
{
|
||||
auto it = m_cmd_buffer_fences.begin();
|
||||
auto it = m_cmdBufferFences.begin();
|
||||
VkFenceCreateInfo fenceInfo{};
|
||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it);
|
||||
|
||||
++it;
|
||||
fenceInfo.flags = 0;
|
||||
for (; it != m_cmd_buffer_fences.end(); ++it)
|
||||
for (; it != m_cmdBufferFences.end(); ++it)
|
||||
{
|
||||
vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &*it);
|
||||
}
|
||||
@ -2079,7 +2128,7 @@ void VulkanRenderer::InitFirstCommandBuffer()
|
||||
m_commandBufferSyncIndex = 0;
|
||||
|
||||
m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex];
|
||||
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
|
||||
vkResetFences(m_logicalDevice, 1, &m_cmdBufferFences[m_commandBufferIndex]);
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
@ -2096,7 +2145,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers()
|
||||
bool finishedCmdBuffers = false;
|
||||
while (m_commandBufferSyncIndex != m_commandBufferIndex)
|
||||
{
|
||||
VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]);
|
||||
VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmdBufferFences[m_commandBufferSyncIndex]);
|
||||
if (fenceStatus == VK_SUCCESS)
|
||||
{
|
||||
ProcessDestructionQueue();
|
||||
@ -2125,7 +2174,7 @@ void VulkanRenderer::WaitForNextFinishedCommandBuffer()
|
||||
{
|
||||
cemu_assert_debug(m_commandBufferSyncIndex != m_commandBufferIndex);
|
||||
// wait on least recently submitted command buffer
|
||||
VkResult result = vkWaitForFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferSyncIndex], true, UINT64_MAX);
|
||||
VkResult result = vkWaitForFences(m_logicalDevice, 1, &m_cmdBufferFences[m_commandBufferSyncIndex], true, UINT64_MAX);
|
||||
if (result == VK_TIMEOUT)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "vkWaitForFences: Returned VK_TIMEOUT on infinite fence");
|
||||
@ -2178,7 +2227,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
|
||||
submitInfo.pWaitDstStageMask = semWaitStageMask;
|
||||
submitInfo.pWaitSemaphores = waitSemArray;
|
||||
|
||||
const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_cmd_buffer_fences[m_commandBufferIndex]);
|
||||
const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_cmdBufferFences[m_commandBufferIndex]);
|
||||
if (result != VK_SUCCESS)
|
||||
UnrecoverableError(fmt::format("failed to submit command buffer. Error {}", result).c_str());
|
||||
m_numSubmittedCmdBuffers++;
|
||||
@ -2199,7 +2248,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor
|
||||
|
||||
|
||||
m_state.currentCommandBuffer = m_commandBuffers[m_commandBufferIndex];
|
||||
vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]);
|
||||
vkResetFences(m_logicalDevice, 1, &m_cmdBufferFences[m_commandBufferIndex]);
|
||||
vkResetCommandBuffer(m_state.currentCommandBuffer, 0);
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo{};
|
||||
@ -3235,7 +3284,7 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS
|
||||
performanceMonitor.vk.numDescriptorSets.increment();
|
||||
|
||||
VkDescriptorImageInfo imageInfo = {};
|
||||
imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
imageInfo.imageLayout = static_cast<LatteTextureVk*>(texViewVk->baseTexture)->GetDefaultLayout();
|
||||
imageInfo.imageView = texViewVk->GetViewRGBA()->m_textureImageView;
|
||||
imageInfo.sampler = texViewVk->GetDefaultTextureSampler(useLinearTexFilter);
|
||||
|
||||
@ -3372,29 +3421,8 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo()
|
||||
for (auto& it : list_referencedViews)
|
||||
it->RemoveDescriptorSetReference(this);
|
||||
// unregister
|
||||
switch (shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
{
|
||||
auto r = pipeline_info->vertex_ds_cache.erase(stateHash);
|
||||
cemu_assert_debug(r == 1);
|
||||
break;
|
||||
}
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
{
|
||||
auto r = pipeline_info->pixel_ds_cache.erase(stateHash);
|
||||
cemu_assert_debug(r == 1);
|
||||
break;
|
||||
}
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
{
|
||||
auto r = pipeline_info->geometry_ds_cache.erase(stateHash);
|
||||
cemu_assert_debug(r == 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
auto r = pipeline_info->GetDescriptorSetCache(shaderType).erase(stateHash);
|
||||
cemu_assert_debug(r == 1);
|
||||
// update global stats
|
||||
performanceMonitor.vk.numDescriptorSamplerTextures.decrement(statsNumSamplerTextures);
|
||||
performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers);
|
||||
@ -3414,7 +3442,7 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI
|
||||
else
|
||||
{
|
||||
cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D);
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL);
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, vkTexture->GetDefaultLayout());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3425,7 +3453,7 @@ void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s
|
||||
{
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL);
|
||||
ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, vkTexture->GetDefaultLayout());
|
||||
}
|
||||
|
||||
void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue)
|
||||
@ -3466,7 +3494,7 @@ void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s
|
||||
|
||||
vkCmdClearDepthStencilImage(m_state.currentCommandBuffer, imageObj->m_image, VK_IMAGE_LAYOUT_GENERAL, &depthStencilValue, 1, &range);
|
||||
|
||||
barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, subresourceRange, vkTexture->GetDefaultLayout());
|
||||
}
|
||||
|
||||
void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize)
|
||||
@ -3569,7 +3597,7 @@ void VulkanRenderer::texture_loadSlice(LatteTexture* hostTexture, sint32 width,
|
||||
|
||||
vkCmdCopyBufferToImage(m_state.currentCommandBuffer, uploadResv.vkBuffer, vkImageObj->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion);
|
||||
|
||||
barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, barrierSubresourceRange, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier_image<ANY_TRANSFER, ANY_TRANSFER | IMAGE_READ | IMAGE_WRITE>(vkTexture, barrierSubresourceRange, vkTexture->GetDefaultLayout());
|
||||
}
|
||||
|
||||
LatteTexture* VulkanRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels,
|
||||
@ -3647,13 +3675,11 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip,
|
||||
sint32 mipWidth = std::max(dst->width >> dstMip, 1);
|
||||
sint32 mipHeight = std::max(dst->height >> dstMip, 1);
|
||||
|
||||
|
||||
if (mipWidth < 4 || mipHeight < 4)
|
||||
{
|
||||
cemuLog_logDebug(LogType::Force, "vkCmdCopyImage - blocked copy for unsupported uncompressed->compressed copy with dst smaller than 4x4");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// make sure all write operations to the src image have finished
|
||||
@ -3664,7 +3690,8 @@ void VulkanRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip,
|
||||
vkCmdCopyImage(m_state.currentCommandBuffer, srcVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, dstVkObj->m_image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion);
|
||||
|
||||
// make sure the transfer is finished before the image is read or written
|
||||
barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, VK_IMAGE_LAYOUT_GENERAL);
|
||||
barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcVk, region.srcSubresource, srcVk->GetDefaultLayout());
|
||||
barrier_image<SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstVk, region.dstSubresource, dstVk->GetDefaultLayout());
|
||||
}
|
||||
|
||||
LatteTextureReadbackInfo* VulkanRenderer::texture_createReadback(LatteTextureView* textureView)
|
||||
@ -3789,6 +3816,7 @@ void VulkanRenderer::bufferCache_init(const sint32 bufferSize)
|
||||
m_importedMemBaseAddress = 0x10000000;
|
||||
size_t hostAllocationSize = 0x40000000ull;
|
||||
// todo - get size of allocation
|
||||
/*
|
||||
bool configUseHostMemory = false; // todo - replace this with a config option
|
||||
m_useHostMemoryForCache = false;
|
||||
if (m_featureControl.deviceExtensions.external_memory_host && configUseHostMemory)
|
||||
@ -3799,6 +3827,7 @@ void VulkanRenderer::bufferCache_init(const sint32 bufferSize)
|
||||
cemuLog_log(LogType::Force, "Unable to import host memory to Vulkan buffer. Use default cache system instead");
|
||||
}
|
||||
}
|
||||
*/
|
||||
if(!m_useHostMemoryForCache)
|
||||
memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, m_bufferCache, m_bufferCacheMemory);
|
||||
}
|
||||
@ -4081,6 +4110,10 @@ void VKRObjectSampler::DestroyCache()
|
||||
|
||||
VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount)
|
||||
{
|
||||
VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance();
|
||||
bool useAttachmentFeedbackLoop = vkRenderer->UseAttachmentFeedbackLoop();
|
||||
VkImageLayout attachmentLayout = useAttachmentFeedbackLoop ? VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT : VK_IMAGE_LAYOUT_GENERAL;
|
||||
|
||||
// generate helper hash for pipeline state
|
||||
uint64 stateHash = 0;
|
||||
for (int i = 0; i < Latte::GPU_LIMITS::NUM_COLOR_ATTACHMENTS; ++i)
|
||||
@ -4114,7 +4147,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
|
||||
m_colorAttachmentFormat[i] = attachmentInfo.colorAttachment[i].format;
|
||||
|
||||
color_attachments_references[i].attachment = (uint32)attachments_descriptions.size();
|
||||
color_attachments_references[i].layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
color_attachments_references[i].layout = attachmentLayout;
|
||||
|
||||
VkAttachmentDescription entry{};
|
||||
entry.format = attachmentInfo.colorAttachment[i].format;
|
||||
@ -4123,8 +4156,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
|
||||
entry.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
entry.initialLayout = attachmentLayout;
|
||||
entry.finalLayout = attachmentLayout;
|
||||
attachments_descriptions.emplace_back(entry);
|
||||
|
||||
numColorAttachments = i + 1;
|
||||
@ -4141,7 +4174,7 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
|
||||
{
|
||||
hasDepthStencilAttachment = true;
|
||||
depth_stencil_attachments_references.attachment = (uint32)attachments_descriptions.size();
|
||||
depth_stencil_attachments_references.layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
depth_stencil_attachments_references.layout = attachmentLayout;
|
||||
m_depthAttachmentFormat = attachmentInfo.depthAttachment.format;
|
||||
|
||||
VkAttachmentDescription entry{};
|
||||
@ -4159,8 +4192,8 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
|
||||
entry.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
entry.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
}
|
||||
entry.initialLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
entry.finalLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
entry.initialLayout = attachmentLayout;
|
||||
entry.finalLayout = attachmentLayout;
|
||||
attachments_descriptions.emplace_back(entry);
|
||||
}
|
||||
|
||||
@ -4181,12 +4214,29 @@ VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint3
|
||||
renderPassInfo.subpassCount = 1;
|
||||
renderPassInfo.pSubpasses = &subpass;
|
||||
|
||||
renderPassInfo.pDependencies = nullptr;
|
||||
renderPassInfo.dependencyCount = 0;
|
||||
VkSubpassDependency feedbackLoopDependency{};
|
||||
if (useAttachmentFeedbackLoop)
|
||||
{
|
||||
feedbackLoopDependency.srcSubpass = 0;
|
||||
feedbackLoopDependency.dstSubpass = 0;
|
||||
feedbackLoopDependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
feedbackLoopDependency.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
feedbackLoopDependency.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
feedbackLoopDependency.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
feedbackLoopDependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT | VK_DEPENDENCY_FEEDBACK_LOOP_BIT_EXT;
|
||||
renderPassInfo.pDependencies = &feedbackLoopDependency;
|
||||
renderPassInfo.dependencyCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderPassInfo.pDependencies = nullptr;
|
||||
renderPassInfo.dependencyCount = 0;
|
||||
}
|
||||
// before Cemu 1.25.5 we used zero here, which means implicit synchronization. For 1.25.5 it was changed to 2 (using the subpass dependencies above)
|
||||
// Reverted this again to zero for Cemu 1.25.5b as the performance cost is just too high. Manual synchronization is preferred
|
||||
// as of Cemu 2.7 we are now using VK_EXT_attachment_feedback_loop_layout with a matching renderpass dependency if supported. Otherwise we are falling back to the above
|
||||
|
||||
if (vkCreateRenderPass(VulkanRenderer::GetInstance()->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS)
|
||||
if (vkCreateRenderPass(vkRenderer->GetLogicalDevice(), &renderPassInfo, nullptr, &m_renderPass) != VK_SUCCESS)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Vulkan-Error: Failed to create render pass");
|
||||
throw std::runtime_error("failed to create render pass!");
|
||||
|
||||
@ -55,12 +55,17 @@ private:
|
||||
|
||||
namespace VulkanRendererConst
|
||||
{
|
||||
static const inline int SHADER_STAGE_INDEX_VERTEX = 0;
|
||||
static const inline int SHADER_STAGE_INDEX_FRAGMENT = 1;
|
||||
static const inline int SHADER_STAGE_INDEX_GEOMETRY = 2;
|
||||
static const inline int SHADER_STAGE_INDEX_COUNT = 3;
|
||||
static const inline int SHADER_STAGE_INDEX_VERTEX = static_cast<int>(LatteConst::ShaderType::Vertex);
|
||||
static const inline int SHADER_STAGE_INDEX_FRAGMENT = static_cast<int>(LatteConst::ShaderType::Pixel);
|
||||
static const inline int SHADER_STAGE_INDEX_GEOMETRY = static_cast<int>(LatteConst::ShaderType::Geometry);
|
||||
static const inline int SHADER_STAGE_INDEX_COUNT = 4;
|
||||
};
|
||||
|
||||
// the order doesnt really matter but the types should cover range 0-2 since we use them as an array index
|
||||
static_assert(static_cast<int>(LatteConst::ShaderType::Vertex) == 0);
|
||||
static_assert(static_cast<int>(LatteConst::ShaderType::Pixel) == 1);
|
||||
static_assert(static_cast<int>(LatteConst::ShaderType::Geometry) == 2);
|
||||
|
||||
class PipelineInfo
|
||||
{
|
||||
public:
|
||||
@ -82,11 +87,18 @@ public:
|
||||
return k;
|
||||
}
|
||||
};
|
||||
using DescriptorSetCache = ska::flat_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>>;
|
||||
|
||||
FORCE_INLINE DescriptorSetCache& GetDescriptorSetCache(LatteConst::ShaderType shaderType)
|
||||
{
|
||||
cemu_assert_debug(shaderType == LatteConst::ShaderType::Vertex || shaderType == LatteConst::ShaderType::Pixel || shaderType == LatteConst::ShaderType::Geometry);
|
||||
return ds_cache[static_cast<size_t>(shaderType)];
|
||||
}
|
||||
|
||||
// std::unordered_map<uint64, VkDescriptorSetInfo*> 3.16% (total CPU time)
|
||||
// robin_hood::unordered_flat_map<uint64, VkDescriptorSetInfo*> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; ~1.80%
|
||||
// ska::bytell_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; -> 1.91%
|
||||
ska::flat_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> vertex_ds_cache, pixel_ds_cache, geometry_ds_cache; // 1.71%
|
||||
// robin_hood::unordered_flat_map<uint64, VkDescriptorSetInfo*> descriptor set cache; ~1.80%
|
||||
// ska::bytell_hash_map<uint64, VkDescriptorSetInfo*, direct_hash<uint64>> descriptor set cache; -> 1.91%
|
||||
DescriptorSetCache ds_cache[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; // 1.71%
|
||||
|
||||
VKRObjectPipeline* m_vkrObjPipeline;
|
||||
|
||||
@ -177,8 +189,6 @@ public:
|
||||
VulkanRenderer();
|
||||
virtual ~VulkanRenderer();
|
||||
|
||||
RendererAPI GetType() override { return RendererAPI::Vulkan; }
|
||||
|
||||
static VulkanRenderer* GetInstance();
|
||||
|
||||
void UnrecoverableError(const char* errMsg) const;
|
||||
@ -368,8 +378,7 @@ private:
|
||||
VkDescriptorSetInfo* activePixelDS{ nullptr };
|
||||
VkDescriptorSetInfo* activeGeometryDS{ nullptr };
|
||||
bool descriptorSetsChanged{ false };
|
||||
bool hasRenderSelfDependency{ false }; // set if current drawcall samples textures which are also output as a rendertarget
|
||||
|
||||
VkImageAspectFlags feedbackLoopImageAspect{0xFFFFFFFF};
|
||||
// viewport and scissor box
|
||||
VkViewport currentViewport{};
|
||||
VkRect2D currentScissorRect{};
|
||||
@ -403,6 +412,7 @@ private:
|
||||
activeIndexType = Renderer::INDEX_TYPE::NONE;
|
||||
activeIndexBufferIndex = std::numeric_limits<uint32>::max();
|
||||
activeIndexBufferOffset = std::numeric_limits<uint32>::max();
|
||||
feedbackLoopImageAspect = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
// invalidation / flushing
|
||||
@ -454,6 +464,8 @@ private:
|
||||
bool present_wait = false; // VK_KHR_present_wait
|
||||
bool depth_clip_enable = false; // VK_EXT_depth_clip_enable
|
||||
bool pipeline_robustness = false; // VK_EXT_pipeline_robustness
|
||||
bool attachment_feedback_loop_layout = false; // VK_EXT_attachment_feedback_loop_layout
|
||||
bool attachment_feedback_loop_dynamic_state = false; // VK_EXT_attachment_feedback_loop_dynamic_state (this is forced to false if VK_EXT_attachment_feedback_loop_layout is not supported)
|
||||
}deviceExtensions;
|
||||
|
||||
struct
|
||||
@ -470,6 +482,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
|
||||
@ -528,7 +542,7 @@ private:
|
||||
void draw_endRenderPass();
|
||||
|
||||
void draw_beginSequence() override;
|
||||
void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst) override;
|
||||
void draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext) override;
|
||||
void draw_endSequence() override;
|
||||
|
||||
void draw_updateVertexBuffersDirectAccess();
|
||||
@ -540,7 +554,7 @@ private:
|
||||
void draw_handleSpecialState5();
|
||||
|
||||
// draw synchronization helper
|
||||
void sync_inputTexturesChanged();
|
||||
void sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass = false);
|
||||
void sync_RenderPassLoadTextures(CachedFBOVk* fboVk);
|
||||
void sync_RenderPassStoreTextures(CachedFBOVk* fboVk);
|
||||
|
||||
@ -549,7 +563,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, uint8& stageUniformModifiedMask, float* __restrict uniformBuf, bool aluConstDirty, uint32 uniformBufferDirtyMask);
|
||||
|
||||
// misc
|
||||
void CreatePipelineCache();
|
||||
@ -633,7 +648,7 @@ private:
|
||||
// if VK_EXT_external_memory_host is supported we can (optionally) import all of the Wii U memory into a Vulkan memory object
|
||||
// this allows us to skip any vertex/uniform caching logic and let the GPU directly read the memory from main RAM
|
||||
// Wii U memory imported into a buffer
|
||||
bool m_useHostMemoryForCache{ false };
|
||||
static constexpr bool m_useHostMemoryForCache{ false }; // currently disabled and made constexpr so the compiler eliminates the branches that will never be taken
|
||||
VkBuffer m_importedMem = VK_NULL_HANDLE;
|
||||
VkDeviceMemory m_importedMemMemory = VK_NULL_HANDLE;
|
||||
MPTR m_importedMemBaseAddress = 0;
|
||||
@ -644,8 +659,8 @@ private:
|
||||
size_t m_commandBufferIndex = 0; // current buffer being filled
|
||||
size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit)
|
||||
size_t m_commandBufferIDOfPrevFrame = 0;
|
||||
std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer
|
||||
std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences;
|
||||
std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // read index in the uniform ringbuffer after the command buffer finishes
|
||||
std::array<VkFence, kCommandBufferPoolSize> m_cmdBufferFences;
|
||||
std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers;
|
||||
std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores;
|
||||
|
||||
@ -661,6 +676,10 @@ private:
|
||||
uint32 m_submitThreshold{}; // submit current buffer if recordedDrawcalls exceeds this number
|
||||
bool m_submitOnIdle{}; // submit current buffer if Latte command processor goes into idle state (no more commands or waiting for externally signaled condition)
|
||||
|
||||
// drawcall handling
|
||||
void draw_execute_first(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext);
|
||||
void draw_execute_continued(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext);
|
||||
|
||||
// tracking for dynamic offsets
|
||||
struct
|
||||
{
|
||||
@ -934,6 +953,7 @@ public:
|
||||
bool HasSPRIVRoundingModeRTE32() const { return m_featureControl.shaderFloatControls.shaderRoundingModeRTEFloat32; }
|
||||
bool IsDebugMarkersEnabled() const { return m_featureControl.usingDebugMarkerTool; }
|
||||
bool IsTracingToolEnabled() const { return m_featureControl.usingTracingTool; }
|
||||
bool UseAttachmentFeedbackLoop() const { return m_featureControl.deviceExtensions.attachment_feedback_loop_dynamic_state; }
|
||||
|
||||
private:
|
||||
|
||||
|
||||
@ -311,30 +311,27 @@ 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)
|
||||
{
|
||||
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) {
|
||||
auto waitWhileCondition = [&]<class TCondition>(TCondition&& condition) {
|
||||
while (condition())
|
||||
{
|
||||
if (m_commandBufferSyncIndex == m_commandBufferIndex)
|
||||
{
|
||||
if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex)
|
||||
{
|
||||
draw_endRenderPass();
|
||||
SubmitCommandBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// submitting work would not change readIndex, so there's no way for conditions based on it to change
|
||||
cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption");
|
||||
cemu_assert_suspicious();
|
||||
break;
|
||||
}
|
||||
// we caught up to the current command buffer and there is still not enough space in the uniform ringbuffer
|
||||
// in this case we just accept the corruption. In practice this is very unlikely to happen with recent reductions to uniform size
|
||||
// If this does continue to be an issue then the likely easiest solution is to start a new command buffer at the end of a draw sequence if the ringbuffer is filled beyond a certain threshold
|
||||
// submitting the command buffere here is complicated because we'd have to restore all of the dynamic state
|
||||
cemuLog_log(LogType::Force, "Vulkan: Uniform ring buffer overflowed. Expect visual corruption");
|
||||
cemu_assert_suspicious();
|
||||
break;
|
||||
}
|
||||
WaitForNextFinishedCommandBuffer();
|
||||
}
|
||||
@ -375,90 +372,133 @@ 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;
|
||||
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->resourceMapping.uniformVarsBufferBindingPoint >= 0)
|
||||
if (shader->uniform.loc_alphaTestRef >= 0)
|
||||
{
|
||||
if (shader->uniform.list_ufTexRescale.empty() == false)
|
||||
*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), true, (1<<LATTE_NUM_MAX_UNIFORM_BUFFERS)-1);
|
||||
}
|
||||
if (shader->uniform.loc_uniformRegister >= 0)
|
||||
{
|
||||
sint32 shaderAluConst;
|
||||
switch (shader->shaderType)
|
||||
{
|
||||
for (auto& entry : shader->uniform.list_ufTexRescale)
|
||||
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, uint8& stageUniformModifiedMask, float* __restrict uniformBuf, bool aluConstDirty, uint32 uniformBufferDirtyMask)
|
||||
{
|
||||
// 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_LoadRemappedUniforms(shader, GET_UNIFORM_DATA_PTR(shader->uniform.loc_remapped), aluConstDirty, uniformBufferDirtyMask);
|
||||
}
|
||||
if (shader->uniform.loc_uniformRegister >= 0 && aluConstDirty)
|
||||
{
|
||||
cemu_assert_debug(shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE);
|
||||
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;
|
||||
break;
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
shaderAluConst = 0;
|
||||
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)
|
||||
{
|
||||
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});
|
||||
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 (hasChange)
|
||||
{
|
||||
dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformData_uploadUniformDataBufferGetOffset({(uint8*)uniformBuf, shader->uniform.uniformRangeSize});
|
||||
stageUniformModifiedMask |= (1 << shaderStageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets,
|
||||
sint32& numDynOffsets,
|
||||
const PipelineInfo* pipeline_info)
|
||||
void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderStageIndex, uint32* dynamicOffsets, sint32& numDynOffsets, const PipelineInfo* pipeline_info)
|
||||
{
|
||||
numDynOffsets = 0;
|
||||
if (pipeline_info->dynamicOffsetInfo.hasUniformVar[shaderStageIndex])
|
||||
@ -481,40 +521,44 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader)
|
||||
uint64 hash = 0;
|
||||
|
||||
const sint32 textureCount = shader->resourceMapping.getTextureCount();
|
||||
|
||||
LatteTextureViewVk** texViewBase = m_state.boundTexture;
|
||||
uint32* __restrict texRegBase = LatteGPUState.contextRegister;
|
||||
uint32 samplerBaseIndex = 0;
|
||||
switch (shader->shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
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:
|
||||
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:
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < textureCount; ++i)
|
||||
{
|
||||
const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i);
|
||||
auto hostTextureUnit = relative_textureUnit;
|
||||
auto textureDim = shader->textureUnitDim[relative_textureUnit];
|
||||
auto texUnitRegIndex = hostTextureUnit * 7;
|
||||
switch (shader->shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
hostTextureUnit += LATTE_CEMU_VS_TEX_UNIT_BASE;
|
||||
texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS;
|
||||
break;
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
hostTextureUnit += LATTE_CEMU_PS_TEX_UNIT_BASE;
|
||||
texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS;
|
||||
break;
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
hostTextureUnit += LATTE_CEMU_GS_TEX_UNIT_BASE;
|
||||
texUnitRegIndex += Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_GS;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
auto texture = m_state.boundTexture[hostTextureUnit];
|
||||
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];
|
||||
@ -535,31 +579,27 @@ uint64 VulkanRenderer::GetDescriptorSetStateHash(LatteDecompilerShader* shader)
|
||||
VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* pipeline_info, LatteDecompilerShader* shader)
|
||||
{
|
||||
const uint64 stateHash = GetDescriptorSetStateHash(shader);
|
||||
cemu_assert_debug(shader->shaderType == LatteConst::ShaderType::Vertex || shader->shaderType == LatteConst::ShaderType::Pixel || shader->shaderType == LatteConst::ShaderType::Geometry);
|
||||
auto& ds_cache = pipeline_info->GetDescriptorSetCache(shader->shaderType);
|
||||
const auto it = ds_cache.find(stateHash);
|
||||
if (it != ds_cache.cend())
|
||||
return it->second;
|
||||
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
switch (shader->shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
{
|
||||
const auto it = pipeline_info->vertex_ds_cache.find(stateHash);
|
||||
if (it != pipeline_info->vertex_ds_cache.cend())
|
||||
return it->second;
|
||||
descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_vertexDSL;
|
||||
break;
|
||||
}
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
{
|
||||
const auto it = pipeline_info->pixel_ds_cache.find(stateHash);
|
||||
if (it != pipeline_info->pixel_ds_cache.cend())
|
||||
return it->second;
|
||||
descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_pixelDSL;
|
||||
break;
|
||||
}
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
{
|
||||
const auto it = pipeline_info->geometry_ds_cache.find(stateHash);
|
||||
if (it != pipeline_info->geometry_ds_cache.cend())
|
||||
return it->second;
|
||||
descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_geometryDSL;
|
||||
break;
|
||||
}
|
||||
@ -601,7 +641,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||
for (int i = 0; i < textureCount; ++i)
|
||||
{
|
||||
VkDescriptorImageInfo info{};
|
||||
const auto relative_textureUnit = shader->resourceMapping.getTextureUnitFromBindingPoint(i);
|
||||
const auto relative_textureUnit = shader->resourceMapping.getRelativeTextureUnitFromRelativeBindingPoint(i);
|
||||
auto hostTextureUnit = relative_textureUnit;
|
||||
auto textureDim = shader->textureUnitDim[relative_textureUnit];
|
||||
auto texUnitRegIndex = hostTextureUnit * 7;
|
||||
@ -666,14 +706,13 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||
continue;
|
||||
}
|
||||
|
||||
info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
|
||||
VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{};
|
||||
|
||||
VkSamplerCreateInfo samplerInfo{};
|
||||
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
|
||||
LatteTexture* baseTexture = textureView->baseTexture;
|
||||
LatteTextureVk* baseTexture = static_cast<LatteTextureVk*>(textureView->baseTexture);
|
||||
info.imageLayout = baseTexture->GetDefaultLayout();
|
||||
// get texture register word 0
|
||||
uint32 word4 = LatteGPUState.contextRegister[texUnitRegIndex + 4];
|
||||
|
||||
@ -686,7 +725,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||
textureView->AddDescriptorSetReference(dsInfo);
|
||||
|
||||
if (!baseTexture->IsCompressedFormat())
|
||||
vectorAppendUnique(dsInfo->list_fboCandidates, (LatteTextureVk*)baseTexture);
|
||||
vectorAppendUnique(dsInfo->list_fboCandidates, baseTexture);
|
||||
|
||||
uint32 stageSamplerIndex = shader->textureUnitSamplerAssignment[relative_textureUnit];
|
||||
if (stageSamplerIndex != LATTE_DECOMPILER_SAMPLER_NONE)
|
||||
@ -951,31 +990,12 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo*
|
||||
if (!descriptorWrites.empty())
|
||||
vkUpdateDescriptorSets(m_logicalDevice, (uint32)descriptorWrites.size(), descriptorWrites.data(), 0, nullptr);
|
||||
|
||||
switch (shader->shaderType)
|
||||
{
|
||||
case LatteConst::ShaderType::Vertex:
|
||||
{
|
||||
pipeline_info->vertex_ds_cache[stateHash] = dsInfo;
|
||||
break;
|
||||
}
|
||||
case LatteConst::ShaderType::Pixel:
|
||||
{
|
||||
pipeline_info->pixel_ds_cache[stateHash] = dsInfo;
|
||||
break;
|
||||
}
|
||||
case LatteConst::ShaderType::Geometry:
|
||||
{
|
||||
pipeline_info->geometry_ds_cache[stateHash] = dsInfo;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
ds_cache[stateHash] = dsInfo;
|
||||
|
||||
return dsInfo;
|
||||
}
|
||||
|
||||
void VulkanRenderer::sync_inputTexturesChanged()
|
||||
void VulkanRenderer::sync_inputTexturesChanged(bool withinFeedbackLoopRenderPass)
|
||||
{
|
||||
bool writeFlushRequired = false;
|
||||
|
||||
@ -1024,14 +1044,27 @@ void VulkanRenderer::sync_inputTexturesChanged()
|
||||
srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
|
||||
// dst
|
||||
dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
if (withinFeedbackLoopRenderPass)
|
||||
{
|
||||
// this renderpass has a pixel self dependency. Feedback loop extension allows barriers during renderpass, but they must be BY_REGION
|
||||
dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// dst
|
||||
dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
|
||||
dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT;
|
||||
}
|
||||
|
||||
VkDependencyFlags dependencyFlags = withinFeedbackLoopRenderPass ? VK_DEPENDENCY_BY_REGION_BIT : 0;
|
||||
vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, dependencyFlags, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
|
||||
|
||||
performanceMonitor.vk.numDrawBarriersPerFrame.increment();
|
||||
|
||||
@ -1154,22 +1187,32 @@ bool s_syncOnNextDraw = false;
|
||||
void VulkanRenderer::draw_setRenderPass()
|
||||
{
|
||||
CachedFBOVk* fboVk = m_state.activeFBO;
|
||||
// note - pixel self dependency can be handled via feedback_loop extension
|
||||
// vertex/geometry self dependency needs renderpass split
|
||||
CachedFBOVk::RendertargetSelfDependencyMask renderSelfDependencyInfo{};
|
||||
|
||||
// update self-dependency flag
|
||||
// update self-dependency state
|
||||
if (m_state.descriptorSetsChanged || m_state.activeRenderpassFBO != fboVk)
|
||||
{
|
||||
m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
|
||||
renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
|
||||
}
|
||||
|
||||
auto vkObjRenderPass = fboVk->GetRenderPassObj();
|
||||
auto vkObjFramebuffer = fboVk->GetFramebufferObj();
|
||||
|
||||
bool overridePassReuse = m_state.hasRenderSelfDependency && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier);
|
||||
bool feedbackLoopHandlesSelfDependency = UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.HasSelfDependency() && !renderSelfDependencyInfo.HasVertexOrGeometrySelfDependency();
|
||||
bool selfDependencyNeedsPassSplit = renderSelfDependencyInfo.HasSelfDependency() && !feedbackLoopHandlesSelfDependency;
|
||||
bool overridePassReuse = selfDependencyNeedsPassSplit && (GetConfig().vk_accurate_barriers || m_state.activePipelineInfo->neverSkipAccurateBarrier);
|
||||
|
||||
if (!overridePassReuse && m_state.activeRenderpassFBO == fboVk)
|
||||
{
|
||||
if (m_state.descriptorSetsChanged)
|
||||
sync_inputTexturesChanged();
|
||||
sync_inputTexturesChanged(feedbackLoopHandlesSelfDependency);
|
||||
if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect)
|
||||
{
|
||||
m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask();
|
||||
vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask());
|
||||
}
|
||||
return;
|
||||
}
|
||||
draw_endRenderPass();
|
||||
@ -1177,7 +1220,7 @@ void VulkanRenderer::draw_setRenderPass()
|
||||
sync_inputTexturesChanged();
|
||||
|
||||
// assume that FBO changed, update self-dependency state
|
||||
m_state.hasRenderSelfDependency = fboVk->CheckForCollision(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
|
||||
renderSelfDependencyInfo = fboVk->CheckForSelfDependency(m_state.activeVertexDS, m_state.activeGeometryDS, m_state.activePixelDS);
|
||||
|
||||
sync_RenderPassLoadTextures(fboVk);
|
||||
|
||||
@ -1202,6 +1245,11 @@ void VulkanRenderer::draw_setRenderPass()
|
||||
}
|
||||
|
||||
m_state.activeRenderpassFBO = fboVk;
|
||||
if (UseAttachmentFeedbackLoop() && renderSelfDependencyInfo.GetAspectMask() != m_state.feedbackLoopImageAspect)
|
||||
{
|
||||
m_state.feedbackLoopImageAspect = renderSelfDependencyInfo.GetAspectMask();
|
||||
vkCmdSetAttachmentFeedbackLoopEnableEXT(m_state.currentCommandBuffer, renderSelfDependencyInfo.GetAspectMask());
|
||||
}
|
||||
|
||||
vkObjRenderPass->flagForCurrentCommandBuffer();
|
||||
vkObjFramebuffer->flagForCurrentCommandBuffer();
|
||||
@ -1300,11 +1348,10 @@ void VulkanRenderer::draw_beginSequence()
|
||||
m_state.drawSequenceSkip = true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, bool isFirst)
|
||||
void VulkanRenderer::draw_execute_first(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext)
|
||||
{
|
||||
if (m_state.drawSequenceSkip)
|
||||
{
|
||||
LatteGPUState.drawCallCounter++;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1312,13 +1359,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0)
|
||||
{
|
||||
LatteDraw_handleSpecialState8_clearAsDepth();
|
||||
LatteGPUState.drawCallCounter++;
|
||||
return;
|
||||
}
|
||||
else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0)
|
||||
{
|
||||
draw_handleSpecialState5();
|
||||
LatteGPUState.drawCallCounter++;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1332,11 +1377,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
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;
|
||||
|
||||
@ -1345,13 +1390,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
|
||||
Renderer::INDEX_TYPE hostIndexType;
|
||||
uint32 hostIndexCount;
|
||||
uint32 indexMin = 0;
|
||||
uint32 indexMax = 0;
|
||||
Renderer::IndexAllocation indexAllocation;
|
||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||
VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal;
|
||||
// update index binding
|
||||
bool isPrevIndexData = false;
|
||||
if (hostIndexType != INDEX_TYPE::NONE)
|
||||
{
|
||||
uint32 indexBufferIndex = indexReservation->bufferIndex;
|
||||
@ -1370,8 +1413,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
cemu_assert(false);
|
||||
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType);
|
||||
}
|
||||
else
|
||||
isPrevIndexData = true;
|
||||
}
|
||||
|
||||
if (m_useHostMemoryForCache)
|
||||
@ -1391,36 +1432,12 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
else
|
||||
{
|
||||
// synchronize vertex and uniform cache and update buffer bindings
|
||||
LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount);
|
||||
uint8 stageUniformModifiedMask = 0;
|
||||
LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, stageUniformModifiedMask);
|
||||
}
|
||||
|
||||
PipelineInfo* pipeline_info;
|
||||
|
||||
if (!isFirst)
|
||||
{
|
||||
if (m_state.activePipelineInfo->minimalStateHash != draw_calculateMinimalGraphicsPipelineHash(vertexShader->compatibleFetchShader, LatteGPUState.contextNew))
|
||||
{
|
||||
// pipeline changed
|
||||
pipeline_info = draw_getOrCreateGraphicsPipeline(count);
|
||||
m_state.activePipelineInfo = pipeline_info;
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline_info = m_state.activePipelineInfo;
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count);
|
||||
if (pipeline_info != pipeline_info2)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline_info = draw_getOrCreateGraphicsPipeline(count);
|
||||
m_state.activePipelineInfo = pipeline_info;
|
||||
}
|
||||
PipelineInfo* pipeline_info = draw_getOrCreateGraphicsPipeline(count);
|
||||
m_state.activePipelineInfo = pipeline_info;
|
||||
|
||||
auto vkObjPipeline = pipeline_info->m_vkrObjPipeline;
|
||||
if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE)
|
||||
@ -1430,22 +1447,205 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
return;
|
||||
}
|
||||
|
||||
VkDescriptorSetInfo *vertexDS = nullptr, *pixelDS = nullptr, *geometryDS = nullptr;
|
||||
draw_prepareDescriptorSets(pipeline_info, vertexDS, pixelDS, geometryDS);
|
||||
m_state.activeVertexDS = vertexDS;
|
||||
m_state.activePixelDS = pixelDS;
|
||||
m_state.activeGeometryDS = geometryDS;
|
||||
m_state.descriptorSetsChanged = true;
|
||||
|
||||
draw_setRenderPass();
|
||||
|
||||
if (m_state.currentPipeline != vkObjPipeline->GetPipeline())
|
||||
{
|
||||
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->GetPipeline());
|
||||
vkObjPipeline->flagForCurrentCommandBuffer();
|
||||
m_state.currentPipeline = vkObjPipeline->GetPipeline();
|
||||
// depth bias
|
||||
if (pipeline_info->usesDepthBias)
|
||||
draw_updateDepthBias(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pipeline_info->usesDepthBias)
|
||||
draw_updateDepthBias(false);
|
||||
}
|
||||
// update blend constants
|
||||
if (pipeline_info->usesBlendConstants)
|
||||
draw_updateVkBlendConstants();
|
||||
// update descriptor sets
|
||||
uint32_t dynamicOffsets[17 * 2];
|
||||
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;
|
||||
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info);
|
||||
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets);
|
||||
}
|
||||
// draw
|
||||
if (hostIndexType != INDEX_TYPE::NONE)
|
||||
vkCmdDrawIndexed(m_state.currentCommandBuffer, hostIndexCount, instanceCount, 0, baseVertex, baseInstance);
|
||||
else
|
||||
vkCmdDraw(m_state.currentCommandBuffer, count, instanceCount, baseVertex, baseInstance);
|
||||
LatteStreamout_FinishDrawcall(m_useHostMemoryForCache);
|
||||
}
|
||||
|
||||
void VulkanRenderer::draw_execute_continued(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext)
|
||||
{
|
||||
if (m_state.drawSequenceSkip)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// fast clear color as depth
|
||||
if (LatteGPUState.contextNew.GetSpecialStateValues()[8] != 0)
|
||||
{
|
||||
LatteDraw_handleSpecialState8_clearAsDepth();
|
||||
return;
|
||||
}
|
||||
else if (LatteGPUState.contextNew.GetSpecialStateValues()[5] != 0)
|
||||
{
|
||||
draw_handleSpecialState5();
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare streamout
|
||||
m_streamoutState.verticesPerInstance = count;
|
||||
LatteStreamout_PrepareDrawcall(count, instanceCount);
|
||||
|
||||
// update uniform vars
|
||||
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
|
||||
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_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader, stageUniformModifiedMask, s_vkUniformDataVS, drawcallContext.aluConstVSDirty, drawcallContext.vsUniformBufferDirtyMask);
|
||||
if (pixelShader)
|
||||
uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader, stageUniformModifiedMask, s_vkUniformDataPS, drawcallContext.aluConstPSDirty, drawcallContext.psUniformBufferDirtyMask);
|
||||
if (geometryShader)
|
||||
uniformData_updateUniformVarsIncremental(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader, stageUniformModifiedMask, s_vkUniformDataGS, false, drawcallContext.gsUniformBufferDirtyMask);
|
||||
// store where the read pointer should go after command buffer execution
|
||||
m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex;
|
||||
|
||||
// process index data
|
||||
const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]);
|
||||
|
||||
Renderer::INDEX_TYPE hostIndexType;
|
||||
uint32 hostIndexCount;
|
||||
uint32 indexMax = 0;
|
||||
Renderer::IndexAllocation indexAllocation;
|
||||
LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMax, hostIndexType, hostIndexCount, indexAllocation);
|
||||
VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal;
|
||||
// update index binding
|
||||
if (hostIndexType != INDEX_TYPE::NONE)
|
||||
{
|
||||
uint32 indexBufferIndex = indexReservation->bufferIndex;
|
||||
uint32 indexBufferOffset = indexReservation->bufferOffset;
|
||||
if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType)
|
||||
{
|
||||
m_state.activeIndexType = hostIndexType;
|
||||
m_state.activeIndexBufferOffset = indexBufferOffset;
|
||||
m_state.activeIndexBufferIndex = indexBufferIndex;
|
||||
VkIndexType vkType;
|
||||
if (hostIndexType == INDEX_TYPE::U16)
|
||||
vkType = VK_INDEX_TYPE_UINT16;
|
||||
else if (hostIndexType == INDEX_TYPE::U32)
|
||||
vkType = VK_INDEX_TYPE_UINT32;
|
||||
else
|
||||
cemu_assert(false);
|
||||
vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_useHostMemoryForCache)
|
||||
{
|
||||
// direct memory access (Wii U memory space imported as a Vulkan buffer), update buffer bindings
|
||||
draw_updateVertexBuffersDirectAccess();
|
||||
LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader();
|
||||
if (vertexShader)
|
||||
draw_updateUniformBuffersDirectAccess(vertexShader, mmSQ_VTX_UNIFORM_BLOCK_START, LatteConst::ShaderType::Vertex);
|
||||
LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader();
|
||||
if (geometryShader)
|
||||
draw_updateUniformBuffersDirectAccess(geometryShader, mmSQ_GS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Geometry);
|
||||
LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader();
|
||||
if (pixelShader)
|
||||
draw_updateUniformBuffersDirectAccess(pixelShader, mmSQ_PS_UNIFORM_BLOCK_START, LatteConst::ShaderType::Pixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// synchronize vertex and uniform cache and update buffer bindings
|
||||
LatteBufferCache_Sync(indexMax + baseVertex, baseInstance, instanceCount, drawcallContext.vertexBufferDirtyMask, drawcallContext.vsUniformBufferDirtyMask, drawcallContext.psUniformBufferDirtyMask, drawcallContext.gsUniformBufferDirtyMask, stageUniformModifiedMask, true);
|
||||
}
|
||||
|
||||
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)) [[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
|
||||
{
|
||||
// sanity check that we are using the right pipeline
|
||||
#ifdef CEMU_DEBUG_ASSERT
|
||||
auto pipeline_info2 = draw_getOrCreateGraphicsPipeline(count);
|
||||
if (pipeline_info != pipeline_info2)
|
||||
{
|
||||
cemu_assert_debug(false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto vkObjPipeline = pipeline_info->m_vkrObjPipeline;
|
||||
if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE)
|
||||
{
|
||||
// invalid/uninitialized pipeline
|
||||
//m_state.activeVertexDS = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
VkDescriptorSetInfo *vertexDS = nullptr, *pixelDS = nullptr, *geometryDS = nullptr;
|
||||
if (!isFirst && m_state.activeVertexDS)
|
||||
if (m_state.activeVertexDS)
|
||||
{
|
||||
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;
|
||||
cemu_assert(false); // should never happen unless draw_prepareDescriptorSets can fail?
|
||||
}
|
||||
|
||||
draw_setRenderPass();
|
||||
@ -1471,50 +1671,63 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
|
||||
// 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;
|
||||
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets,
|
||||
pipeline_info);
|
||||
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets,
|
||||
dynamicOffsets);
|
||||
draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info);
|
||||
vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets);
|
||||
}
|
||||
|
||||
// draw
|
||||
@ -1524,7 +1737,14 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32
|
||||
vkCmdDraw(m_state.currentCommandBuffer, count, instanceCount, baseVertex, baseInstance);
|
||||
|
||||
LatteStreamout_FinishDrawcall(m_useHostMemoryForCache);
|
||||
}
|
||||
|
||||
void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 instanceCount, uint32 count, MPTR indexDataMPTR, Latte::LATTE_VGT_DMA_INDEX_TYPE::E_INDEX_TYPE indexType, const LatteDrawcallContext& drawcallContext)
|
||||
{
|
||||
if (drawcallContext.isFirst)
|
||||
draw_execute_first(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType, drawcallContext);
|
||||
else
|
||||
draw_execute_continued(baseVertex, baseInstance, instanceCount, count, indexDataMPTR, indexType, drawcallContext);
|
||||
LatteGPUState.drawCallCounter++;
|
||||
}
|
||||
|
||||
|
||||
@ -557,7 +557,7 @@ VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkC
|
||||
|
||||
descriptorImageInfo.sampler = vkObjImageView->m_textureDefaultSampler[0];
|
||||
descriptorImageInfo.imageView = vkObjImageView->m_textureImageView;
|
||||
descriptorImageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
descriptorImageInfo.imageLayout = state.sourceTexture->GetDefaultLayout();
|
||||
|
||||
VkWriteDescriptorSet write_descriptor{};
|
||||
write_descriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
@ -673,8 +673,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3
|
||||
|
||||
cemu_assert_debug(srcTextureVk->GetImageObj()->m_image != dstTextureVk->GetImageObj()->m_image);
|
||||
|
||||
barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any modifying operations on source image to complete
|
||||
barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_WRITE>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for any operations on destination image to complete
|
||||
barrier_image<SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_READ>(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for any modifying operations on source image to complete
|
||||
barrier_image<SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER, SYNC_OP::IMAGE_WRITE>(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for any operations on destination image to complete
|
||||
|
||||
|
||||
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
@ -692,8 +692,8 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3
|
||||
|
||||
vkCmdEndRenderPass(m_state.currentCommandBuffer);
|
||||
|
||||
barrier_image<SYNC_OP::IMAGE_READ, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcTextureVk, srcImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the source image
|
||||
barrier_image<SYNC_OP::IMAGE_WRITE, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstTextureVk, dstImageSubresource, VK_IMAGE_LAYOUT_GENERAL); // wait for drawcall to complete before any other operations on the destination image
|
||||
barrier_image<SYNC_OP::IMAGE_READ, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(srcTextureVk, srcImageSubresource, srcTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the source image
|
||||
barrier_image<SYNC_OP::IMAGE_WRITE, SYNC_OP::IMAGE_READ | SYNC_OP::IMAGE_WRITE | SYNC_OP::ANY_TRANSFER>(dstTextureVk, dstImageSubresource, dstTextureVk->GetDefaultLayout()); // wait for drawcall to complete before any other operations on the destination image
|
||||
|
||||
// restore viewport and scissor box
|
||||
vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport);
|
||||
|
||||
@ -199,10 +199,9 @@ void GX2CopySurfaceInternal(GX2Surface* srcSurface, uint32 srcMip, uint32 srcSli
|
||||
return;
|
||||
}
|
||||
// make sure formats are compatible
|
||||
if( srcHwFormat != dstHwFormat )
|
||||
if( surfOutSrc.bpp != surfOutDst.bpp )
|
||||
{
|
||||
// mismatching format
|
||||
cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format mismatch (src=0x{:04x} dst=0x{:04x})", (sint32)srcFormat, (sint32)dstFormat);
|
||||
cemuLog_logDebug(LogType::Force, "GX2CopySurface(): Format bpp mismatch (src=0x{:04x} dst=0x{:04x})", (sint32)srcFormat, (sint32)dstFormat);
|
||||
return;
|
||||
}
|
||||
// get input pointer
|
||||
|
||||
@ -39,7 +39,7 @@ namespace H264
|
||||
struct
|
||||
{
|
||||
MEMPTR<void> outputFunc{ nullptr };
|
||||
uint8be outputPerFrame{ 0 }; // whats the default?
|
||||
uint8be outputPerFrame{ 0 }; // default is 0
|
||||
MEMPTR<void> userMemoryParam{ nullptr };
|
||||
}Param;
|
||||
// misc
|
||||
@ -52,18 +52,18 @@ namespace H264
|
||||
}decoderState;
|
||||
};
|
||||
|
||||
uint32 H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut)
|
||||
H264DEC_STATUS H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut)
|
||||
{
|
||||
if (H264_IsBotW())
|
||||
{
|
||||
static_assert(sizeof(H264Context) < 256);
|
||||
*sizeRequirementOut = 256;
|
||||
return 0;
|
||||
return H264DEC_STATUS::SUCCESS;
|
||||
}
|
||||
|
||||
// note: On console this seems to check if maxWidth or maxHeight < 64 but Pikmin 3 passes 32x32 and crashes if this function fails ?
|
||||
if (width < 0x20 || height < 0x20 || width > 2800 || height > 1408 || sizeRequirementOut == MPTR_NULL || codecLevel >= 52 || (codecProfile != 0x42 && codecProfile != 0x4D && codecProfile != 0x64))
|
||||
return 0x1010000;
|
||||
if (width < 32 || height < 32 || width > 2800 || height > 1408 || !sizeRequirementOut || codecLevel >= 52 || (codecProfile != 66 && codecProfile != 77 && codecProfile != 100))
|
||||
return H264DEC_STATUS::INVALID_PARAM;
|
||||
|
||||
uint32 workbufferSize = 0;
|
||||
if (codecLevel < 0xB)
|
||||
@ -112,7 +112,7 @@ namespace H264
|
||||
}
|
||||
workbufferSize += 0x447;
|
||||
*sizeRequirementOut = workbufferSize;
|
||||
return 0;
|
||||
return H264DEC_STATUS::SUCCESS;
|
||||
}
|
||||
|
||||
uint32 H264DECCheckMemSegmentation(MPTR memory, uint32 size)
|
||||
@ -189,16 +189,15 @@ namespace H264
|
||||
return H264DEC_STATUS::BAD_STREAM;
|
||||
}
|
||||
|
||||
H264DEC_STATUS H264DECGetImageSize(uint8* stream, uint32 length, uint32 offset, uint32be* outputWidth, uint32be* outputHeight)
|
||||
H264DEC_STATUS H264DECGetImageSize(uint8* stream, sint32 streamSize, sint32 offset, uint32be* outputWidth, uint32be* outputHeight)
|
||||
{
|
||||
if(!stream || length < 4 || !outputWidth || !outputHeight)
|
||||
if (!stream || streamSize < 4 || offset < 0 || !outputWidth || !outputHeight || offset < streamSize)
|
||||
return H264DEC_STATUS::INVALID_PARAM;
|
||||
if( (offset+4) > length )
|
||||
if ( (offset+4) >= streamSize )
|
||||
return H264DEC_STATUS::INVALID_PARAM;
|
||||
uint8* cur = stream + offset;
|
||||
uint8* end = stream + length;
|
||||
cur += 2; // we access cur[-2] and cur[-1] so we need to start at offset 2
|
||||
while(cur < end-2)
|
||||
uint8* end = stream + streamSize;
|
||||
while (cur < end-2)
|
||||
{
|
||||
// check for start code
|
||||
if(*cur != 1)
|
||||
@ -207,7 +206,7 @@ namespace H264
|
||||
continue;
|
||||
}
|
||||
// check if this is a valid NAL header
|
||||
if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1)
|
||||
if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1) // if offset is < 2, this will read out of bounds. The console implementation has this behavior too so we have to replicate this bug
|
||||
{
|
||||
cur++;
|
||||
continue;
|
||||
@ -226,7 +225,14 @@ namespace H264
|
||||
return H264DEC_STATUS::BAD_STREAM;
|
||||
}
|
||||
*outputWidth = (psp.pic_width_in_mbs_minus1 + 1) * 16;
|
||||
*outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16; // affected by frame_mbs_only_flag?
|
||||
*outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16;
|
||||
if (!psp.frame_mbs_only_flag)
|
||||
*outputHeight *= 2;
|
||||
if (!*outputHeight || !*outputWidth)
|
||||
return H264DEC_STATUS::BAD_STREAM;
|
||||
// BotW 1080p video support
|
||||
if (H264_IsBotW() && *outputWidth == 1920 && *outputHeight == 1088)
|
||||
*outputHeight = 1080;
|
||||
return H264DEC_STATUS::SUCCESS;
|
||||
}
|
||||
return H264DEC_STATUS::BAD_STREAM;
|
||||
|
||||
@ -28,6 +28,7 @@ add_library(CemuUtil
|
||||
helpers/MapAdaptor.h
|
||||
helpers/MemoryPool.h
|
||||
helpers/ringbuffer.h
|
||||
helpers/StateHasher.h
|
||||
helpers/Semaphore.h
|
||||
helpers/Serializer.cpp
|
||||
helpers/Serializer.h
|
||||
|
||||
@ -45,13 +45,12 @@ public:
|
||||
// if no match is found a default-constructed object is returned
|
||||
T lookup(uint32 offset)
|
||||
{
|
||||
uint32 indexX = (offset >> (TBitsZ + TBitsY)) & ((1u << TBitsX) - 1);
|
||||
auto& a = m_tableXArr[indexX];
|
||||
uint32 indexY = (offset >> TBitsZ) & ((1u << TBitsY) - 1);
|
||||
auto& b = a->arr[indexY];
|
||||
uint32 indexZ = offset & ((1u << TBitsZ) - 1);
|
||||
offset >>= TBitsZ;
|
||||
uint32 indexY = offset & ((1u << TBitsY) - 1);
|
||||
offset >>= TBitsY;
|
||||
uint32 indexX = offset & ((1u << TBitsX) - 1);
|
||||
//offset >>= TBitsX;
|
||||
return m_tableXArr[indexX]->arr[indexY]->arr[indexZ];
|
||||
return b->arr[indexZ];
|
||||
}
|
||||
|
||||
void store(uint32 offset, T& t)
|
||||
@ -77,6 +76,7 @@ private:
|
||||
TableY* tableY = new TableY();
|
||||
for (auto& itr : tableY->arr)
|
||||
itr = m_placeholderTableZ;
|
||||
tYCount++;
|
||||
return tableY;
|
||||
}
|
||||
|
||||
@ -84,8 +84,11 @@ private:
|
||||
TableZ* GenerateNewTableZ()
|
||||
{
|
||||
TableZ* tableZ = new TableZ();
|
||||
tZCount++;
|
||||
return tableZ;
|
||||
}
|
||||
|
||||
TableY* m_tableXArr[1 << TBitsX]; // x lookup
|
||||
int tYCount = 0;
|
||||
int tZCount = 0;
|
||||
};
|
||||
|
||||
42
src/util/helpers/StateHasher.h
Normal file
42
src/util/helpers/StateHasher.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
// hashes two separate streams at once for better parallelism, combined into one final uint64 hash at the end
|
||||
// inspired by xxHash and MurmurHash3
|
||||
class DualStateHasher
|
||||
{
|
||||
public:
|
||||
FORCE_INLINE DualStateHasher()
|
||||
{
|
||||
m_h0 = 0x9E3779B97F4A7C15;
|
||||
m_h1 = 0xC2B2AE3D27D4EB4F;
|
||||
};
|
||||
|
||||
FORCE_INLINE void MixIn(uint64 a, uint64 b)
|
||||
{
|
||||
uint64 tmp = m_h1;
|
||||
m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL;
|
||||
m_h0 = (tmp ^ b) * 0x165667B19E3779F9ULL;
|
||||
}
|
||||
|
||||
FORCE_INLINE void MixInSingle(uint64 a)
|
||||
{
|
||||
uint64 tmp = m_h1;
|
||||
m_h1 = (m_h0 ^ a) * 0x85EBCA77C2B2AE63ULL;
|
||||
m_h0 = tmp;
|
||||
}
|
||||
|
||||
FORCE_INLINE uint64 Finish()
|
||||
{
|
||||
uint64 combined = m_h0 ^ std::rotl(m_h1, 31);
|
||||
combined ^= combined >> 33;
|
||||
combined *= 0xff51afd7ed558ccdULL;
|
||||
combined ^= combined >> 33;
|
||||
combined *= 0xc4ceb9fe1a85ec53ULL;
|
||||
combined ^= combined >> 33;
|
||||
return combined;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64 m_h0;
|
||||
uint64 m_h1;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user