Merge branch 'main' into android

This commit is contained in:
SSimco
2025-05-28 09:30:18 +03:00
37 changed files with 1102 additions and 631 deletions
+1 -1
View File
@@ -202,7 +202,7 @@ jobs:
- name: "Install molten-vk"
run: |
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar
curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.3.0/MoltenVK-macos.tar
tar xf MoltenVK-macos.tar
sudo mkdir -p /usr/local/lib
sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib
+15 -13
View File
@@ -13,6 +13,8 @@
#define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code
static_assert(sizeof(NCrypto::AesIv) == 16); // make sure IV is actually 16 bytes
class FSTDataSource
{
public:
@@ -868,7 +870,7 @@ static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE);
struct FSTCachedRawBlock
{
FSTRawBlock blockData;
uint8 ivForNextBlock[16];
NCrypto::AesIv ivForNextBlock;
uint64 lastAccess;
};
@@ -919,13 +921,13 @@ void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCach
}
}
void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16])
void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut)
{
memset(ivOut, 0, sizeof(ivOut));
ivOut = {};
if(blockIndex == 0)
{
ivOut[0] = (uint8)(clusterIndex >> 8);
ivOut[1] = (uint8)(clusterIndex >> 0);
ivOut.iv[0] = (uint8)(clusterIndex >> 8);
ivOut.iv[1] = (uint8)(clusterIndex >> 0);
}
else
{
@@ -936,20 +938,20 @@ void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex,
auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId);
if (itr != m_cacheDecryptedRawBlocks.end())
{
memcpy(ivOut, itr->second->ivForNextBlock, 16);
ivOut = itr->second->ivForNextBlock;
}
else
{
cemu_assert(m_sectorSize >= 16);
cemu_assert(m_sectorSize >= NCrypto::AesIv::SIZE);
uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize;
uint8 prevIV[16];
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - 16, prevIV, 16) != 16)
NCrypto::AesIv prevIV{};
if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - NCrypto::AesIv::SIZE, prevIV.iv, NCrypto::AesIv::SIZE) != NCrypto::AesIv::SIZE)
{
cemuLog_log(LogType::Force, "Failed to read IV for raw FST block");
m_detectedCorruption = true;
return;
}
memcpy(ivOut, prevIV, 16);
ivOut = prevIV;
}
}
}
@@ -984,10 +986,10 @@ FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 b
return nullptr;
}
// decrypt hash data
uint8 iv[16]{};
NCrypto::AesIv iv{};
DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv);
memcpy(block->ivForNextBlock, block->blockData.rawData.data() + m_sectorSize - 16, 16);
AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv);
std::copy(block->blockData.rawData.data() + m_sectorSize - NCrypto::AesIv::SIZE, block->blockData.rawData.data() + m_sectorSize, block->ivForNextBlock.iv);
AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv.iv);
// if this is the next block, then hash it
if(cluster.hasContentHash)
{
+1 -2
View File
@@ -83,7 +83,6 @@ public:
}
private:
/* FST data (in memory) */
enum class ClusterHashMode : uint8
{
@@ -193,7 +192,7 @@ private:
std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks;
uint64 m_cacheAccessCounter{};
void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]);
void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut);
struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex);
struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex);
-4
View File
@@ -47,8 +47,6 @@ struct LatteGPUState_t
gx2GPUSharedArea_t* sharedArea; // quick reference to shared area
MPTR sharedAreaAddr;
// other
// todo: Currently we have the command buffer logic implemented as a FIFO ringbuffer. On real HW it's handled as a series of command buffers that are pushed individually.
std::atomic<uint64> lastSubmittedCommandBufferTimestamp;
uint32 gx2InitCalled; // incremented every time GX2Init() is called
// OpenGL control
uint32 glVendor; // GLVENDOR_*
@@ -75,8 +73,6 @@ struct LatteGPUState_t
extern LatteGPUState_t LatteGPUState;
extern uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list)
// texture
#include "Cafe/HW/Latte/Core/LatteTexture.h"
+74 -134
View File
@@ -13,6 +13,7 @@
#include "Cafe/HW/Latte/Core/LattePM4.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/TCL/TCL.h" // TCL currently handles the GPU command ringbuffer
#include "Cafe/CafeSystem.h"
@@ -28,11 +29,6 @@ typedef uint32be* LatteCMDPtr;
#define LatteReadCMD() ((uint32)*(cmd++))
#define LatteSkipCMD(_nWords) cmd += (_nWords)
uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list)
uint8* gx2CPParserDisplayListPtr;
uint8* gx2CPParserDisplayListStart; // used for debugging
uint8* gx2CPParserDisplayListEnd;
void LatteThread_HandleOSScreen();
void LatteThread_Exit();
@@ -155,16 +151,12 @@ void LatteCP_signalEnterWait()
*/
uint32 LatteCP_readU32Deprc()
{
uint32 v;
uint8* gxRingBufferWritePtr;
sint32 readDistance;
// no display list active
while (true)
{
gxRingBufferWritePtr = gx2WriteGatherPipe.writeGatherPtrGxBuffer[GX2::sGX2MainCoreIndex];
readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr);
if (readDistance != 0)
break;
uint32 cmdWord;
if ( TCL::TCLGPUReadRBWord(cmdWord) )
return cmdWord;
g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands
performanceMonitor.gpuTime_idleTime.beginMeasuring();
@@ -175,56 +167,8 @@ uint32 LatteCP_readU32Deprc()
}
LatteThread_HandleOSScreen(); // check if new frame was presented via OSScreen API
readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr);
if (readDistance != 0)
break;
if (Latte_GetStopSignal())
LatteThread_Exit();
// still no command data available, do some other tasks
LatteTiming_HandleTimedVsync();
LatteAsyncCommands_checkAndExecute();
std::this_thread::yield();
performanceMonitor.gpuTime_idleTime.endMeasuring();
}
v = *(uint32*)gxRingBufferReadPtr;
gxRingBufferReadPtr += 4;
#ifdef CEMU_DEBUG_ASSERT
if (v == 0xcdcdcdcd)
assert_dbg();
#endif
v = _swapEndianU32(v);
return v;
}
void LatteCP_waitForNWords(uint32 numWords)
{
uint8* gxRingBufferWritePtr;
sint32 readDistance;
bool isFlushed = false;
sint32 waitDistance = numWords * sizeof(uint32be);
// no display list active
while (true)
{
gxRingBufferWritePtr = gx2WriteGatherPipe.writeGatherPtrGxBuffer[GX2::sGX2MainCoreIndex];
readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr);
if (readDistance < 0)
return; // wrap around means there is at least one full command queued after this
if (readDistance >= waitDistance)
break;
g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands
performanceMonitor.gpuTime_idleTime.beginMeasuring();
// no command data available, spin in a busy loop for a while then check again
for (sint32 busy = 0; busy < 80; busy++)
{
_mm_pause();
}
readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr);
if (readDistance < 0)
return; // wrap around means there is at least one full command queued after this
if (readDistance >= waitDistance)
break;
if ( TCL::TCLGPUReadRBWord(cmdWord) )
return cmdWord;
if (Latte_GetStopSignal())
LatteThread_Exit();
@@ -234,6 +178,7 @@ void LatteCP_waitForNWords(uint32 numWords)
std::this_thread::yield();
performanceMonitor.gpuTime_idleTime.endMeasuring();
}
UNREACHABLE;
}
template<uint32 readU32()>
@@ -270,21 +215,23 @@ void LatteCP_itIndirectBufferDepr(LatteCMDPtr cmd, uint32 nWords)
cemu_assert_debug(nWords == 3);
uint32 physicalAddress = LatteReadCMD();
uint32 physicalAddressHigh = LatteReadCMD(); // unused
uint32 sizeInDWords = LatteReadCMD();
uint32 displayListSize = sizeInDWords * 4;
DrawPassContext drawPassCtx;
uint32 sizeInU32s = LatteReadCMD();
#ifdef LATTE_CP_LOGGING
if (GetAsyncKeyState('A'))
LatteCP_DebugPrintCmdBuffer(MEMPTR<uint32be>(physicalAddress), displayListSize);
#endif
uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr();
drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords);
if (sizeInU32s > 0)
{
DrawPassContext drawPassCtx;
uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr();
drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInU32s);
LatteCP_processCommandBuffer(drawPassCtx);
if (drawPassCtx.isWithinDrawPass())
drawPassCtx.endDrawPass();
LatteCP_processCommandBuffer(drawPassCtx);
if (drawPassCtx.isWithinDrawPass())
drawPassCtx.endDrawPass();
}
}
// pushes the command buffer to the stack
@@ -294,11 +241,12 @@ void LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& d
uint32 physicalAddress = LatteReadCMD();
uint32 physicalAddressHigh = LatteReadCMD(); // unused
uint32 sizeInDWords = LatteReadCMD();
uint32 displayListSize = sizeInDWords * 4;
cemu_assert_debug(displayListSize >= 4);
uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr();
drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords);
if (sizeInDWords > 0)
{
uint32 displayListSize = sizeInDWords * 4;
uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr();
drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords);
}
}
LatteCMDPtr LatteCP_itStreamoutBufferUpdate(LatteCMDPtr cmd, uint32 nWords)
@@ -565,26 +513,55 @@ LatteCMDPtr LatteCP_itMemWrite(LatteCMDPtr cmd, uint32 nWords)
if (word1 == 0x40000)
{
// write U32
*memPtr = word2;
stdx::atomic_ref<uint32be> atomicRef(*memPtr);
atomicRef.store(word2);
}
else if (word1 == 0x00000)
{
// write U64 (as two U32)
// note: The U32s are swapped
memPtr[0] = word2;
memPtr[1] = word3;
// write U64
// note: The U32s are swapped here, but needs verification. Also, it seems like the two U32 halves are written independently and the U64 as a whole is not atomic -> investiagte
stdx::atomic_ref<uint64be> atomicRef(*(uint64be*)memPtr);
atomicRef.store(((uint64le)word2 << 32) | word3);
}
else if (word1 == 0x20000)
{
// write U64 (little endian)
memPtr[0] = _swapEndianU32(word2);
memPtr[1] = _swapEndianU32(word3);
stdx::atomic_ref<uint64le> atomicRef(*(uint64le*)memPtr);
atomicRef.store(((uint64le)word3 << 32) | word2);
}
else
cemu_assert_unimplemented();
return cmd;
}
LatteCMDPtr LatteCP_itEventWriteEOP(LatteCMDPtr cmd, uint32 nWords)
{
cemu_assert_debug(nWords == 5);
uint32 word0 = LatteReadCMD();
uint32 word1 = LatteReadCMD();
uint32 word2 = LatteReadCMD();
uint32 word3 = LatteReadCMD(); // value low bits
uint32 word4 = LatteReadCMD(); // value high bits
cemu_assert_debug(word2 == 0x40000000 || word2 == 0x42000000);
if (word0 == 0x504 && (word2&0x40000000)) // todo - figure out the flags
{
stdx::atomic_ref<uint64be> atomicRef(*(uint64be*)memory_getPointerFromPhysicalOffset(word1));
uint64 val = ((uint64)word4 << 32) | word3;
atomicRef.store(val);
}
else
{ cemu_assert_unimplemented();
}
bool triggerInterrupt = (word2 & 0x2000000) != 0;
if (triggerInterrupt)
{
// todo - timestamp interrupt
}
TCL::TCLGPUNotifyNewRetirementTimestamp();
return cmd;
}
LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords)
{
@@ -783,16 +760,6 @@ LatteCMDPtr LatteCP_itDrawImmediate(LatteCMDPtr cmd, uint32 nWords, DrawPassCont
drawPassCtx.executeDraw(count, false, _tempIndexArrayMPTR);
return cmd;
}
LatteCMDPtr LatteCP_itHLEFifoWrapAround(LatteCMDPtr cmd, uint32 nWords)
{
cemu_assert_debug(nWords == 1);
uint32 unused = LatteReadCMD();
gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer;
cmd = (LatteCMDPtr)gxRingBufferReadPtr;
return cmd;
}
LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords)
@@ -819,16 +786,6 @@ LatteCMDPtr LatteCP_itHLESpecialState(LatteCMDPtr cmd, uint32 nWords)
return cmd;
}
LatteCMDPtr LatteCP_itHLESetRetirementTimestamp(LatteCMDPtr cmd, uint32 nWords)
{
cemu_assert_debug(nWords == 2);
uint32 timestampHigh = (uint32)LatteReadCMD();
uint32 timestampLow = (uint32)LatteReadCMD();
uint64 timestamp = ((uint64)timestampHigh << 32ULL) | (uint64)timestampLow;
GX2::__GX2NotifyNewRetirementTimestamp(timestamp);
return cmd;
}
LatteCMDPtr LatteCP_itHLEBeginOcclusionQuery(LatteCMDPtr cmd, uint32 nWords)
{
cemu_assert_debug(nWords == 1);
@@ -1145,9 +1102,10 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
LatteCMDPtr cmd, cmdStart, cmdEnd;
if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd))
break;
uint32 itHeader;
while (cmd < cmdEnd)
{
uint32 itHeader = LatteReadCMD();
itHeader = LatteReadCMD();
uint32 itHeaderType = (itHeader >> 30) & 3;
if (itHeaderType == 3)
{
@@ -1361,11 +1319,6 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
LatteCP_itHLEEndOcclusionQuery(cmdData, nWords);
break;
}
case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP:
{
LatteCP_itHLESetRetirementTimestamp(cmdData, nWords);
break;
}
case IT_HLE_BOTTOM_OF_PIPE_CB:
{
LatteCP_itHLEBottomOfPipeCB(cmdData, nWords);
@@ -1421,6 +1374,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx)
void LatteCP_ProcessRingbuffer()
{
sint32 timerRecheck = 0; // estimates how much CP processing time has elapsed based on the executed commands, if the value exceeds CP_TIMER_RECHECK then _handleTimers() is called
uint32be tmpBuffer[128];
while (true)
{
uint32 itHeader = LatteCP_readU32Deprc();
@@ -1429,10 +1383,13 @@ void LatteCP_ProcessRingbuffer()
{
uint32 itCode = (itHeader >> 8) & 0xFF;
uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1;
LatteCP_waitForNWords(nWords);
LatteCMDPtr cmd = (LatteCMDPtr)gxRingBufferReadPtr;
uint8* cmdEnd = gxRingBufferReadPtr + nWords * 4;
gxRingBufferReadPtr = cmdEnd;
cemu_assert(nWords < 128);
for (sint32 i=0; i<nWords; i++)
{
uint32 word = LatteCP_readU32Deprc();
tmpBuffer[i] = word;
}
LatteCMDPtr cmd = (LatteCMDPtr)tmpBuffer;
switch (itCode)
{
case IT_SURFACE_SYNC:
@@ -1599,6 +1556,11 @@ void LatteCP_ProcessRingbuffer()
timerRecheck += CP_TIMER_RECHECK / 512;
break;
}
case IT_EVENT_WRITE_EOP:
{
LatteCP_itEventWriteEOP(cmd, nWords);
break;
}
case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER:
{
LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords);
@@ -1637,12 +1599,6 @@ void LatteCP_ProcessRingbuffer()
timerRecheck += CP_TIMER_RECHECK / 128;
break;
}
case IT_HLE_FIFO_WRAP_AROUND:
{
LatteCP_itHLEFifoWrapAround(cmd, nWords);
timerRecheck += CP_TIMER_RECHECK / 512;
break;
}
case IT_HLE_SAMPLE_TIMER:
{
LatteCP_itHLESampleTimer(cmd, nWords);
@@ -1667,12 +1623,6 @@ void LatteCP_ProcessRingbuffer()
timerRecheck += CP_TIMER_RECHECK / 512;
break;
}
case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP:
{
LatteCP_itHLESetRetirementTimestamp(cmd, nWords);
timerRecheck += CP_TIMER_RECHECK / 512;
break;
}
case IT_HLE_BOTTOM_OF_PIPE_CB:
{
LatteCP_itHLEBottomOfPipeCB(cmd, nWords);
@@ -1933,11 +1883,6 @@ void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size)
cemuLog_log(LogType::Force, "{} IT_HLE_COPY_SURFACE_NEW", strPrefix);
break;
}
case IT_HLE_FIFO_WRAP_AROUND:
{
cemuLog_log(LogType::Force, "{} IT_HLE_FIFO_WRAP_AROUND", strPrefix);
break;
}
case IT_HLE_SAMPLE_TIMER:
{
cemuLog_log(LogType::Force, "{} IT_HLE_SAMPLE_TIMER", strPrefix);
@@ -1958,11 +1903,6 @@ void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size)
cemuLog_log(LogType::Force, "{} IT_HLE_END_OCCLUSION_QUERY", strPrefix);
break;
}
case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP:
{
cemuLog_log(LogType::Force, "{} IT_HLE_SET_CB_RETIREMENT_TIMESTAMP", strPrefix);
break;
}
case IT_HLE_BOTTOM_OF_PIPE_CB:
{
cemuLog_log(LogType::Force, "{} IT_HLE_BOTTOM_OF_PIPE_CB", strPrefix);
+1 -2
View File
@@ -14,6 +14,7 @@
#define IT_MEM_WRITE 0x3D
#define IT_SURFACE_SYNC 0x43
#define IT_EVENT_WRITE 0x46
#define IT_EVENT_WRITE_EOP 0x47 // end of pipe
#define IT_LOAD_CONFIG_REG 0x60
#define IT_LOAD_CONTEXT_REG 0x61
@@ -47,14 +48,12 @@
#define IT_HLE_WAIT_FOR_FLIP 0xF1
#define IT_HLE_BOTTOM_OF_PIPE_CB 0xF2
#define IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER 0xF3
#define IT_HLE_FIFO_WRAP_AROUND 0xF4
#define IT_HLE_CLEAR_COLOR_DEPTH_STENCIL 0xF5
#define IT_HLE_SAMPLE_TIMER 0xF7
#define IT_HLE_TRIGGER_SCANBUFFER_SWAP 0xF8
#define IT_HLE_SPECIAL_STATE 0xF9
#define IT_HLE_BEGIN_OCCLUSION_QUERY 0xFA
#define IT_HLE_END_OCCLUSION_QUERY 0xFB
#define IT_HLE_SET_CB_RETIREMENT_TIMESTAMP 0xFD
#define pm4HeaderType3(__itCode, __dataDWordCount) (0xC0000000|((uint32)(__itCode)<<8)|((uint32)((__dataDWordCount)-1)<<16))
#define pm4HeaderType2Filler() (0x80000000)
-1
View File
@@ -206,7 +206,6 @@ int Latte_ThreadEntry()
if (Latte_GetStopSignal())
LatteThread_Exit();
}
gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer;
LatteCP_ProcessRingbuffer();
cemu_assert_debug(false); // should never reach
return 0;
@@ -873,7 +873,7 @@ void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBle
dynamicState.pDynamicStates = dynamicStates.data();
}
bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj)
bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj, bool requireRobustBufferAccess)
{
VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance();
@@ -888,6 +888,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const
m_vkGeometryShader = pipelineInfo->geometryShaderVk;
m_vkrObjPipeline = pipelineInfo->m_vkrObjPipeline;
m_renderPassObj = renderPassObj;
m_requestRobustBufferAccess = requireRobustBufferAccess;
// if required generate RECT emulation geometry shader
if (!vkRenderer->m_featureControl.deviceExtensions.nv_fill_rectangle && isPrimitiveRect)
@@ -998,6 +999,8 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show
if (!forceCompile)
pipelineInfo.flags |= VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT;
void* prevStruct = nullptr;
VkPipelineCreationFeedbackCreateInfoEXT creationFeedbackInfo;
VkPipelineCreationFeedbackEXT creationFeedback;
std::vector<VkPipelineCreationFeedbackEXT> creationStageFeedback(0);
@@ -1015,9 +1018,25 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show
creationFeedbackInfo.pPipelineCreationFeedback = &creationFeedback;
creationFeedbackInfo.pPipelineStageCreationFeedbacks = creationStageFeedback.data();
creationFeedbackInfo.pipelineStageCreationFeedbackCount = pipelineInfo.stageCount;
pipelineInfo.pNext = &creationFeedbackInfo;
creationFeedbackInfo.pNext = prevStruct;
prevStruct = &creationFeedbackInfo;
}
VkPipelineRobustnessCreateInfoEXT pipelineRobustnessCreateInfo{};
if (vkRenderer->m_featureControl.deviceExtensions.pipeline_robustness && m_requestRobustBufferAccess)
{
// per-pipeline handling of robust buffer access, if the extension is not available then we fall back to device feature robustBufferAccess
pipelineRobustnessCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO_EXT;
pipelineRobustnessCreateInfo.pNext = prevStruct;
prevStruct = &pipelineRobustnessCreateInfo;
pipelineRobustnessCreateInfo.storageBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT;
pipelineRobustnessCreateInfo.uniformBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT;
pipelineRobustnessCreateInfo.vertexInputs = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT;
pipelineRobustnessCreateInfo.images = VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DEVICE_DEFAULT_EXT;
}
pipelineInfo.pNext = prevStruct;
VkPipeline pipeline = VK_NULL_HANDLE;
VkResult result;
uint8 retryCount = 0;
@@ -1075,3 +1094,31 @@ void PipelineCompiler::TrackAsCached(uint64 baseHash, uint64 pipelineStateHash)
return;
pipelineCache.AddCurrentStateToCache(baseHash, pipelineStateHash);
}
// calculate whether the pipeline requires robust buffer access
// if there is a potential risk for a shader to do out-of-bounds reads or writes we need to enable robust buffer access
// this can happen when:
// - Streamout is used with too small of a buffer (probably? Could also be some issue with how the streamout array index is calculated -> We can maybe fix this in the future)
// - The shader uses dynamic indices for uniform access. This will trigger the uniform mode to be FULL_CBANK
bool PipelineCompiler::CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader)
{
bool requiresRobustBufferAcces = false;
if (vertexShader)
{
cemu_assert_debug(vertexShader->shaderType == LatteConst::ShaderType::Vertex);
requiresRobustBufferAcces |= vertexShader->hasStreamoutBufferWrite;
requiresRobustBufferAcces |= vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK;
}
if (geometryShader)
{
cemu_assert_debug(geometryShader->shaderType == LatteConst::ShaderType::Geometry);
requiresRobustBufferAcces |= geometryShader->hasStreamoutBufferWrite;
requiresRobustBufferAcces |= geometryShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK;
}
if (pixelShader)
{
cemu_assert_debug(pixelShader->shaderType == LatteConst::ShaderType::Pixel);
requiresRobustBufferAcces |= pixelShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK;
}
return requiresRobustBufferAcces;
}
@@ -38,11 +38,14 @@ public:
RendererShaderVk* m_vkPixelShader{};
RendererShaderVk* m_vkGeometryShader{};
bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj);
bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj, bool requireRobustBufferAccess);
void TrackAsCached(uint64 baseHash, uint64 pipelineStateHash); // stores pipeline to permanent cache if not yet cached. Must be called synchronously from render thread due to dependency on GPU state
static bool CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader);
VkPipelineLayout m_pipelineLayout;
VKRObjectRenderPass* m_renderPassObj{};
bool m_requestRobustBufferAccess{false};
/* shader stages */
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
@@ -277,8 +277,9 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData)
m_pipelineIsCachedLock.unlock();
// compile
{
PipelineCompiler pp;
if (!pp.InitFromCurrentGPUState(pipelineInfo, *lcr, renderPass))
PipelineCompiler pipelineCompiler;
bool requiresRobustBufferAccess = PipelineCompiler::CalcRobustBufferAccessRequirement(vertexShader, pixelShader, geometryShader);
if (!pipelineCompiler.InitFromCurrentGPUState(pipelineInfo, *lcr, renderPass, requiresRobustBufferAccess))
{
s_spinlockSharedInternal.lock();
delete lcr;
@@ -286,8 +287,7 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData)
s_spinlockSharedInternal.unlock();
return;
}
pp.Compile(true, true, false);
// destroy pp early
pipelineCompiler.Compile(true, true, false);
}
// on success, calculate pipeline hash and flag as present in cache
uint64 pipelineBaseHash = vertexShader->baseHash;
@@ -47,7 +47,8 @@ const std::vector<const char*> kOptionalDeviceExtensions =
VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME,
VK_KHR_PRESENT_WAIT_EXTENSION_NAME,
VK_KHR_PRESENT_ID_EXTENSION_NAME,
VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME
VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME,
VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME
};
const std::vector<const char*> kRequiredDeviceExtensions =
@@ -266,6 +267,14 @@ void VulkanRenderer::GetDeviceFeatures()
pwf.pNext = prevStruct;
prevStruct = &pwf;
VkPhysicalDevicePipelineRobustnessFeaturesEXT pprf{};
if (m_featureControl.deviceExtensions.pipeline_robustness)
{
pprf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT;
pprf.pNext = prevStruct;
prevStruct = &pprf;
}
VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
physicalDeviceFeatures2.pNext = prevStruct;
@@ -321,6 +330,11 @@ void VulkanRenderer::GetDeviceFeatures()
{
cemuLog_log(LogType::Force, "VK_EXT_depth_clip_enable not supported");
}
if (m_featureControl.deviceExtensions.pipeline_robustness)
{
if ( pprf.pipelineRobustness != VK_TRUE )
m_featureControl.deviceExtensions.pipeline_robustness = false;
}
// 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);
@@ -477,11 +491,17 @@ VulkanRenderer::VulkanRenderer()
deviceFeatures.occlusionQueryPrecise = m_featureControl.deviceFeatures.occlusion_query_precise;
deviceFeatures.depthClamp = m_featureControl.deviceFeatures.depth_clamp;
deviceFeatures.depthBiasClamp = VK_TRUE;
if (m_vendor == GfxVendor::AMD)
if (m_featureControl.deviceExtensions.pipeline_robustness)
{
deviceFeatures.robustBufferAccess = VK_TRUE;
cemuLog_log(LogType::Force, "Enable robust buffer access");
deviceFeatures.robustBufferAccess = VK_FALSE;
}
else
{
cemuLog_log(LogType::Force, "VK_EXT_pipeline_robustness not supported. Falling back to robustBufferAccess");
deviceFeatures.robustBufferAccess = VK_TRUE;
}
if (m_featureControl.mode.useTFEmulationViaSSBO)
{
m_featureControl.mode.useTFEmulationViaSSBO = deviceFeatures.vertexPipelineStoresAndAtomics = m_featureControl.deviceFeatures.vertex_pipeline_stores_and_atomics;
@@ -526,6 +546,15 @@ VulkanRenderer::VulkanRenderer()
deviceExtensionFeatures = &presentWaitFeature;
presentWaitFeature.presentWait = VK_TRUE;
}
// enable VK_EXT_pipeline_robustness
VkPhysicalDevicePipelineRobustnessFeaturesEXT pipelineRobustnessFeature{};
if (m_featureControl.deviceExtensions.pipeline_robustness)
{
pipelineRobustnessFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT;
pipelineRobustnessFeature.pNext = deviceExtensionFeatures;
deviceExtensionFeatures = &pipelineRobustnessFeature;
pipelineRobustnessFeature.pipelineRobustness = VK_TRUE;
}
std::vector<const char*> used_extensions;
VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions);
@@ -1143,6 +1172,8 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe
used_extensions.emplace_back(VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME);
if (m_featureControl.deviceExtensions.depth_clip_enable)
used_extensions.emplace_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME);
if (m_featureControl.deviceExtensions.pipeline_robustness)
used_extensions.emplace_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME);
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
@@ -1241,6 +1272,7 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device,
info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
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);
// 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);
@@ -463,6 +463,7 @@ private:
bool shader_float_controls = false; // VK_KHR_shader_float_controls
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
}deviceExtensions;
struct
@@ -298,7 +298,8 @@ PipelineInfo* VulkanRenderer::draw_createGraphicsPipeline(uint32 indexCount)
// init pipeline compiler
PipelineCompiler* pipelineCompiler = new PipelineCompiler();
pipelineCompiler->InitFromCurrentGPUState(pipelineInfo, LatteGPUState.contextNew, vkFBO->GetRenderPassObj());
bool requiresRobustBufferAccess = PipelineCompiler::CalcRobustBufferAccessRequirement(vertexShader, pixelShader, geometryShader);
pipelineCompiler->InitFromCurrentGPUState(pipelineInfo, LatteGPUState.contextNew, vkFBO->GetRenderPassObj(), requiresRobustBufferAccess);
pipelineCompiler->TrackAsCached(vsBaseHash, pipelineHash);
// use heuristics based on parameter patterns to determine if the current drawcall is essential (non-skipable)
+141 -8
View File
@@ -1,28 +1,161 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/OS/libs/TCL/TCL.h"
#include "HW/Latte/Core/LattePM4.h"
namespace TCL
{
SysAllocator<coreinit::OSEvent> s_updateRetirementEvent;
uint64 s_currentRetireMarker = 0;
enum class TCL_SUBMISSION_FLAG : uint32
struct TCLStatePPC // mapped into PPC space
{
SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd
TRIGGER_INTERRUPT = 0x200000, // probably
UKN_20000000 = 0x20000000,
uint64be gpuRetireMarker; // written by GPU
};
int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, uint32be* controlFlags, uint64* submissionTimestamp)
SysAllocator<TCLStatePPC> s_tclStatePPC;
// called from GPU for timestamp EOP event
void TCLGPUNotifyNewRetirementTimestamp()
{
// todo - figure out all the bits of *controlFlags
// if submissionTimestamp != nullptr then set it to the timestamp of the submission. Note: We should make sure that uint64's are written atomically by the GPU command processor
// gpuRetireMarker is updated via event eop command
__OSLockScheduler();
coreinit::OSSignalEventAllInternal(s_updateRetirementEvent.GetPtr());
__OSUnlockScheduler();
}
cemu_assert_debug(false);
int TCLTimestamp(TCLTimestampId id, uint64be* timestampOut)
{
if (id == TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED)
{
MEMPTR<uint32> b;
// this is the timestamp of the last buffer that was retired by the GPU
stdx::atomic_ref<uint64be> retireTimestamp(s_tclStatePPC->gpuRetireMarker);
*timestampOut = retireTimestamp.load();
return 0;
}
else
{
cemuLog_log(LogType::Force, "TCLTimestamp(): Unsupported timestamp ID {}", (uint32)id);
*timestampOut = 0;
return 0;
}
}
int TCLWaitTimestamp(TCLTimestampId id, uint64 waitTs, uint64 timeout)
{
if (id == TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED)
{
while ( true )
{
stdx::atomic_ref<uint64be> retireTimestamp(s_tclStatePPC->gpuRetireMarker);
uint64 currentTimestamp = retireTimestamp.load();
if (currentTimestamp >= waitTs)
return 0;
coreinit::OSWaitEvent(s_updateRetirementEvent.GetPtr());
}
}
else
{
cemuLog_log(LogType::Force, "TCLWaitTimestamp(): Unsupported timestamp ID {}", (uint32)id);
}
return 0;
}
static constexpr uint32 TCL_RING_BUFFER_SIZE = 4096; // in U32s
std::atomic<uint32> tclRingBufferA[TCL_RING_BUFFER_SIZE];
std::atomic<uint32> tclRingBufferA_readIndex{0};
uint32 tclRingBufferA_writeIndex{0};
// GPU code calls this to grab the next command word
bool TCLGPUReadRBWord(uint32& cmdWord)
{
if (tclRingBufferA_readIndex == tclRingBufferA_writeIndex)
return false;
cmdWord = tclRingBufferA[tclRingBufferA_readIndex];
tclRingBufferA_readIndex = (tclRingBufferA_readIndex+1) % TCL_RING_BUFFER_SIZE;
return true;
}
void TCLWaitForRBSpace(uint32be numU32s)
{
while ( true )
{
uint32 distance = (tclRingBufferA_readIndex + TCL_RING_BUFFER_SIZE - tclRingBufferA_writeIndex) & (TCL_RING_BUFFER_SIZE - 1);
if (tclRingBufferA_writeIndex == tclRingBufferA_readIndex) // buffer completely empty
distance = TCL_RING_BUFFER_SIZE;
if (distance >= numU32s+1) // assume distance minus one, because we are never allowed to completely wrap around
break;
_mm_pause();
}
}
// this function assumes that TCLWaitForRBSpace was called and that there is enough space
void TCLWriteCmd(uint32be* cmd, uint32 cmdLen)
{
while (cmdLen > 0)
{
tclRingBufferA[tclRingBufferA_writeIndex] = *cmd;
tclRingBufferA_writeIndex++;
tclRingBufferA_writeIndex &= (TCL_RING_BUFFER_SIZE - 1);
cmd++;
cmdLen--;
}
}
#define EVENT_TYPE_TS 5
void TCLSubmitRetireMarker(bool triggerEventInterrupt)
{
s_currentRetireMarker++;
uint32be cmd[6];
cmd[0] = pm4HeaderType3(IT_EVENT_WRITE_EOP, 5);
cmd[1] = (4 | (EVENT_TYPE_TS << 8)); // event type (bits 8-15) and event index (bits 0-7).
cmd[2] = MEMPTR<void>(&s_tclStatePPC->gpuRetireMarker).GetMPTR(); // address lower 32bits + data sel bits
cmd[3] = 0x40000000; // select 64bit write, lower 16 bits are the upper bits of the address
if (triggerEventInterrupt)
cmd[3] |= 0x2000000; // trigger interrupt after value has been written
cmd[4] = (uint32)s_currentRetireMarker; // data lower 32 bits
cmd[5] = (uint32)(s_currentRetireMarker>>32); // data higher 32 bits
TCLWriteCmd(cmd, 6);
}
int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, betype<TCLSubmissionFlag>* controlFlags, uint64be* timestampValueOut)
{
TCLSubmissionFlag flags = *controlFlags;
cemu_assert_debug(timestampValueOut); // handle case where this is null
// make sure there is enough space to submit all commands at one
uint32 totalCommandLength = cmdLen;
totalCommandLength += 6; // space needed for TCLSubmitRetireMarker
TCLWaitForRBSpace(totalCommandLength);
// submit command buffer
TCLWriteCmd(cmd, cmdLen);
// create new marker timestamp and tell GPU to write it to our variable after its done processing the command
if ((HAS_FLAG(flags, TCLSubmissionFlag::USE_RETIRED_MARKER)))
{
TCLSubmitRetireMarker(!HAS_FLAG(flags, TCLSubmissionFlag::NO_MARKER_INTERRUPT));
*timestampValueOut = s_currentRetireMarker; // incremented before each submit
}
else
{
cemu_assert_unimplemented();
}
return 0;
}
void Initialize()
{
cafeExportRegister("TCL", TCLSubmitToRing, LogType::Placeholder);
cafeExportRegister("TCL", TCLTimestamp, LogType::Placeholder);
cafeExportRegister("TCL", TCLWaitTimestamp, LogType::Placeholder);
s_currentRetireMarker = 0;
s_tclStatePPC->gpuRetireMarker = 0;
coreinit::OSInitEvent(s_updateRetirementEvent.GetPtr(), coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO);
}
}
+22 -1
View File
@@ -1,4 +1,25 @@
namespace TCL
{
enum class TCLTimestampId
{
TIMESTAMP_LAST_BUFFER_RETIRED = 1,
};
enum class TCLSubmissionFlag : uint32
{
SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd
NO_MARKER_INTERRUPT = 0x200000,
USE_RETIRED_MARKER = 0x20000000, // Controls whether the timer is updated before or after (retired) the cmd. Also controls which timestamp is returned for the submission. Before and after using separate counters
};
int TCLTimestamp(TCLTimestampId id, uint64be* timestampOut);
int TCLWaitTimestamp(TCLTimestampId id, uint64 waitTs, uint64 timeout);
int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, betype<TCLSubmissionFlag>* controlFlags, uint64be* timestampValueOut);
// called from Latte code
bool TCLGPUReadRBWord(uint32& cmdWord);
void TCLGPUNotifyNewRetirementTimestamp();
void Initialize();
}
}
ENABLE_BITMASK_OPERATORS(TCL::TCLSubmissionFlag);
+2 -1
View File
@@ -742,7 +742,8 @@ namespace coreinit
}
__FSCmdSubmitResult(cmd, fsStatus);
__FSUpdateQueue(&cmd->fsClientBody->fsCmdQueue);
// dont read from cmd after this point, since the game could already have modified it
__FSUpdateQueue(&client->fsCmdQueue);
osLib_returnFromFunction(hCPU, 0);
}
+10
View File
@@ -36,6 +36,16 @@ void dmaeExport_DMAECopyMem(PPCInterpreter_t* hCPU)
dstBuffer[i] = _swapEndianU32(srcBuffer[i]);
}
}
else if( hCPU->gpr[6] == DMAE_ENDIAN_16 )
{
// swap per uint16
uint16* srcBuffer = (uint16*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]);
uint16* dstBuffer = (uint16*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
for(uint32 i=0; i<hCPU->gpr[5]*2; i++)
{
dstBuffer[i] = _swapEndianU16(srcBuffer[i]);
}
}
else
{
cemuLog_logDebug(LogType::Force, "DMAECopyMem(): Unsupported endian swap\n");
+2 -89
View File
@@ -59,7 +59,7 @@ void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU)
if (isPokken)
GX2::GX2DrawDone();
GX2ReserveCmdSpace(5+2);
GX2::GX2ReserveCmdSpace(5+2);
uint64 tick64 = PPCInterpreter_getMainCoreCycleCounter() / 20ULL;
lastSwapTime = tick64;
@@ -86,24 +86,16 @@ void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU)
GX2::GX2WaitForFlip();
}
GX2::GX2WriteGather_checkAndInsertWrapAroundMark();
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2CopyColorBufferToScanBuffer(PPCInterpreter_t* hCPU)
{
cemuLog_log(LogType::GX2, "GX2CopyColorBufferToScanBuffer(0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4]);
GX2ReserveCmdSpace(5);
GX2::GX2ReserveCmdSpace(10);
// todo: proper implementation
// hack: Avoid running to far ahead of GPU. Normally this would be guaranteed by the circular buffer model, which we currently dont fully emulate
if(GX2::GX2WriteGather_getReadWriteDistance() > 32*1024*1024 )
{
debug_printf("Waiting for GPU to catch up...\n");
PPCInterpreter_relinquishTimeslice(); // release current thread
return;
}
GX2ColorBuffer* colorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]);
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9));
@@ -309,81 +301,6 @@ void gx2Export_GX2SetSemaphore(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, 0);
}
void gx2Export_GX2Flush(PPCInterpreter_t* hCPU)
{
cemuLog_log(LogType::GX2, "GX2Flush()");
_GX2SubmitToTCL();
osLib_returnFromFunction(hCPU, 0);
}
uint8* _GX2LastFlushPtr[PPC_CORE_COUNT] = {NULL};
uint64 _prevReturnedGPUTime = 0;
uint64 Latte_GetTime()
{
uint64 gpuTime = coreinit::OSGetSystemTime();
gpuTime *= 20000ULL;
if (gpuTime <= _prevReturnedGPUTime)
gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps
_prevReturnedGPUTime = gpuTime;
return gpuTime;
}
void _GX2SubmitToTCL()
{
uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance());
// do nothing if called from non-main GX2 core
if (GX2::sGX2MainCoreIndex != coreIndex)
{
cemuLog_logDebug(LogType::Force, "_GX2SubmitToTCL() called on non-main GX2 core");
return;
}
if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL )
return; // quit if in display list
_GX2LastFlushPtr[coreIndex] = (gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]);
// update last submitted CB timestamp
uint64 commandBufferTimestamp = Latte_GetTime();
LatteGPUState.lastSubmittedCommandBufferTimestamp.store(commandBufferTimestamp);
cemuLog_log(LogType::GX2, "Submitting GX2 command buffer with timestamp {:016x}", commandBufferTimestamp);
// submit HLE packet to write retirement timestamp
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SET_CB_RETIREMENT_TIMESTAMP, 2));
gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp>>32ULL));
gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp&0xFFFFFFFFULL));
}
uint32 _GX2GetUnflushedBytes(uint32 coreIndex)
{
uint32 unflushedBytes = 0;
if (_GX2LastFlushPtr[coreIndex] != NULL)
{
if (_GX2LastFlushPtr[coreIndex] > gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex])
unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer + 4); // this isn't 100% correct since we ignore the bytes between the last flush address and the start of the wrap around
else
unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - _GX2LastFlushPtr[coreIndex]);
}
else
unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer);
return unflushedBytes;
}
/*
* Guarantees that the requested amount of space is available on the current command buffer
* If the space is not available, the current command buffer is pushed to the GPU and a new one is allocated
*/
void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32)
{
uint32 coreIndex = coreinit::OSGetCoreId();
// if we are in a display list then do nothing
if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL )
return;
uint32 unflushedBytes = _GX2GetUnflushedBytes(coreIndex);
if( unflushedBytes >= 0x1000 )
{
_GX2SubmitToTCL();
}
}
void gx2_load()
{
osLib_addFunction("gx2", "GX2GetContextStateDisplayList", gx2Export_GX2GetContextStateDisplayList);
@@ -445,10 +362,6 @@ void gx2_load()
// semaphore
osLib_addFunction("gx2", "GX2SetSemaphore", gx2Export_GX2SetSemaphore);
// command buffer
osLib_addFunction("gx2", "GX2Flush", gx2Export_GX2Flush);
GX2::GX2Init_writeGather();
GX2::GX2MemInit();
GX2::GX2ResourceInit();
GX2::GX2CommandInit();
+1 -7
View File
@@ -67,10 +67,4 @@ void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU);
void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU);
// command buffer
uint32 _GX2GetUnflushedBytes(uint32 coreIndex);
void _GX2SubmitToTCL();
void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32);
void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU);
+82 -92
View File
@@ -82,35 +82,88 @@ namespace GX2
}
}
void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a)
void SubmitHLEClear(GX2ColorBuffer* colorBuffer, float colorRGBA[4], GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, bool clearColor, bool clearDepth, bool clearStencil)
{
GX2ReserveCmdSpace(50);
uint32 hleClearFlags = 0;
if (clearColor)
hleClearFlags |= 1;
if (clearDepth)
hleClearFlags |= 2;
if (clearStencil)
hleClearFlags |= 4;
// color buffer
MPTR colorPhysAddr = MPTR_NULL;
uint32 colorFormat = 0;
uint32 colorTileMode = 0;
uint32 colorWidth = 0;
uint32 colorHeight = 0;
uint32 colorPitch = 0;
uint32 colorFirstSlice = 0;
uint32 colorNumSlices = 0;
if (colorBuffer != nullptr)
{
colorPhysAddr = memory_virtualToPhysical(colorBuffer->surface.imagePtr);
colorFormat = (uint32)colorBuffer->surface.format.value();
colorTileMode = (uint32)colorBuffer->surface.tileMode.value();
colorWidth = colorBuffer->surface.width;
colorHeight = colorBuffer->surface.height;
colorPitch = colorBuffer->surface.pitch;
colorFirstSlice = _swapEndianU32(colorBuffer->viewFirstSlice);
colorNumSlices = _swapEndianU32(colorBuffer->viewNumSlices);
}
// depth buffer
MPTR depthPhysAddr = MPTR_NULL;
uint32 depthFormat = 0;
uint32 depthTileMode = 0;
uint32 depthWidth = 0;
uint32 depthHeight = 0;
uint32 depthPitch = 0;
uint32 depthFirstSlice = 0;
uint32 depthNumSlices = 0;
if (depthBuffer != nullptr)
{
depthPhysAddr = memory_virtualToPhysical(depthBuffer->surface.imagePtr);
depthFormat = (uint32)depthBuffer->surface.format.value();
depthTileMode = (uint32)depthBuffer->surface.tileMode.value();
depthWidth = depthBuffer->surface.width;
depthHeight = depthBuffer->surface.height;
depthPitch = depthBuffer->surface.pitch;
depthFirstSlice = _swapEndianU32(depthBuffer->viewFirstSlice);
depthNumSlices = _swapEndianU32(depthBuffer->viewNumSlices);
}
gx2WriteGather_submit(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23),
hleClearFlags,
colorPhysAddr,
colorFormat,
colorTileMode,
colorWidth,
colorHeight,
colorPitch,
colorFirstSlice,
colorNumSlices,
depthPhysAddr,
depthFormat,
depthTileMode,
depthWidth,
depthHeight,
depthPitch,
depthFirstSlice,
depthNumSlices,
(uint32)(colorRGBA[0] * 255.0f),
(uint32)(colorRGBA[1] * 255.0f),
(uint32)(colorRGBA[2] * 255.0f),
(uint32)(colorRGBA[3] * 255.0f),
*(uint32*)&depthClearValue,
stencilClearValue&0xFF);
}
void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a)
{
if ((colorBuffer->surface.resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0)
{
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23));
gx2WriteGather_submitU32AsBE(1); // color (1)
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE(colorBuffer->surface.width);
gx2WriteGather_submitU32AsBE(colorBuffer->surface.height);
gx2WriteGather_submitU32AsBE(colorBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE(MPTR_NULL);
gx2WriteGather_submitU32AsBE(0); // depth buffer format
gx2WriteGather_submitU32AsBE(0); // tilemode for depth buffer
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f));
gx2WriteGather_submitU32AsBE(0); // clear depth
gx2WriteGather_submitU32AsBE(0); // clear stencil
float colorRGBA[4] = { r, g, b, a };
SubmitHLEClear(colorBuffer, colorRGBA, nullptr, 0.0f, 0, true, false, false);
}
else
{
@@ -120,7 +173,6 @@ namespace GX2
void GX2ClearBuffersEx(GX2ColorBuffer* colorBuffer, GX2DepthBuffer* depthBuffer, float r, float g, float b, float a, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags)
{
GX2ReserveCmdSpace(50);
_updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags);
uint32 hleClearFlags = 0;
@@ -130,42 +182,13 @@ namespace GX2
hleClearFlags |= 4;
hleClearFlags |= 1;
// send command to clear color, depth and stencil
if (_swapEndianU32(colorBuffer->viewFirstSlice) != 0)
debugBreakpoint();
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23));
gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4)
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f));
gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f));
gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth
gx2WriteGather_submitU32AsBE(stencilClearValue&0xFF); // clear stencil
float colorRGBA[4] = { r, g, b, a };
SubmitHLEClear(colorBuffer, colorRGBA, depthBuffer, depthClearValue, stencilClearValue, true, (clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0, (clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0);
}
// always uses passed depthClearValue/stencilClearValue for clearing, even if clear flags dont specify value updates
void GX2ClearDepthStencilEx(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags)
{
GX2ReserveCmdSpace(50);
if (!depthBuffer && (depthBuffer->surface.width == 0 || depthBuffer->surface.height == 0))
{
// Super Smash Bros tries to clear an uninitialized depth surface?
@@ -175,41 +198,8 @@ namespace GX2
_updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags);
uint32 hleClearFlags = 0;
if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0)
hleClearFlags |= 2;
if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0)
hleClearFlags |= 4;
// send command to clear color, depth and stencil
if (hleClearFlags != 0)
{
gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23));
gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4)
gx2WriteGather_submitU32AsBE(MPTR_NULL);
gx2WriteGather_submitU32AsBE(0); // format for color buffer
gx2WriteGather_submitU32AsBE(0); // tilemode for color buffer
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr));
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value());
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height);
gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch);
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice));
gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices));
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(0);
gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth
gx2WriteGather_submitU32AsBE(stencilClearValue & 0xFF); // clear stencil
}
float colorRGBA[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
SubmitHLEClear(nullptr, colorRGBA, depthBuffer, depthClearValue, stencilClearValue, false, (clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0, (clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0);
}
void GX2BlitInit()

Some files were not shown because too many files have changed in this diff Show More