mirror of
https://github.com/cemu-project/Cemu.git
synced 2026-07-10 01:24:41 -06:00
Skips the overhead of having to lookup each shader stage individually. Additionally, having whole sets as a lookup key gives us a more trustworthy heuristic than if we only have independent information for each shader stage. We can use this to skip shader hashing most of the time when they are part of a known set.
1351 lines
50 KiB
C++
1351 lines
50 KiB
C++
#include "Cafe/HW/Latte/Core/LatteConst.h"
|
|
#include "Cafe/HW/Latte/Core/LatteShaderAssembly.h"
|
|
#include "Cafe/HW/Latte/ISA/RegDefines.h"
|
|
#include "Cafe/HW/Latte/ISA/LatteReg.h"
|
|
#include "Cafe/HW/Latte/Core/LatteShader.h"
|
|
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h"
|
|
#include "Cafe/HW/Latte/Core/FetchShader.h"
|
|
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
|
|
#ifdef ENABLE_VULKAN
|
|
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
|
#endif
|
|
#include "Cafe/GraphicPack/GraphicPack2.h"
|
|
#include "HW/Latte/Core/Latte.h"
|
|
#include "HW/Latte/Renderer/Renderer.h"
|
|
#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
|
|
|
|
// experimental new decompiler (WIP)
|
|
#include "util/Zir/EmitterGLSL/ZpIREmitGLSL.h"
|
|
#include "util/Zir/Core/ZpIRDebug.h"
|
|
#include "Cafe/HW/Latte/Transcompiler/LatteTC.h"
|
|
#include "Cafe/HW/Latte/ShaderInfo/ShaderInfo.h"
|
|
|
|
struct _ShaderHashCache
|
|
{
|
|
uint64 prevHash1;
|
|
uint64 prevHash2;
|
|
uint32* prevProgramCode;
|
|
uint32 prevProgramSize;
|
|
};
|
|
|
|
_ShaderHashCache hashCacheVS = { 0 };
|
|
_ShaderHashCache hashCacheGS = { 0 };
|
|
_ShaderHashCache hashCachePS = { 0 };
|
|
|
|
LatteFetchShader* _activeFetchShader = nullptr;
|
|
LatteDecompilerShader* _activeVertexShader = nullptr;
|
|
LatteDecompilerShader* _activeGeometryShader = nullptr;
|
|
LatteDecompilerShader* _activePixelShader = nullptr;
|
|
|
|
// runtime shader cache
|
|
using SHRC_CACHE_TYPE = ska::flat_hash_map<uint64, LatteDecompilerShader*>;
|
|
|
|
SHRC_CACHE_TYPE sVertexShaders(512);
|
|
SHRC_CACHE_TYPE sGeometryShaders(512);
|
|
SHRC_CACHE_TYPE sPixelShaders(512);
|
|
|
|
uint64 _shaderBaseHash_vs;
|
|
uint64 _shaderBaseHash_gs;
|
|
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()
|
|
{
|
|
return _activeFetchShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetActiveVertexShader()
|
|
{
|
|
return _activeVertexShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetActiveGeometryShader()
|
|
{
|
|
return _activeGeometryShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetActivePixelShader()
|
|
{
|
|
return _activePixelShader;
|
|
}
|
|
|
|
inline ska::flat_hash_map<uint64, LatteDecompilerShader*>& LatteSHRC_GetCacheByType(LatteConst::ShaderType shaderType)
|
|
{
|
|
if (shaderType == LatteConst::ShaderType::Vertex)
|
|
return sVertexShaders;
|
|
else if (shaderType == LatteConst::ShaderType::Geometry)
|
|
return sGeometryShaders;
|
|
cemu_assert_debug(shaderType == LatteConst::ShaderType::Pixel);
|
|
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
|
|
void _calcShaderHashGeneric(uint32* programCode, uint32 programSize, uint64& outputHash1, uint64& outputHash2)
|
|
{
|
|
outputHash1 = 0;
|
|
outputHash2 = 0;
|
|
for (uint32 i = 0; i < programSize / 4; i++)
|
|
{
|
|
uint32 temp = programCode[i];
|
|
outputHash1 += (uint64)temp;
|
|
outputHash2 ^= (uint64)temp;
|
|
outputHash1 = (outputHash1 << 3) | (outputHash1 >> 61);
|
|
outputHash2 = (outputHash2 >> 7) | (outputHash2 << 57);
|
|
}
|
|
}
|
|
|
|
void _calculateShaderProgramHash(uint32* programCode, uint32 programSize, _ShaderHashCache* hashCache, uint64* outputHash1, uint64* outputHash2)
|
|
{
|
|
uint64 progHash1 = 0;
|
|
uint64 progHash2 = 0;
|
|
if (!programCode)
|
|
{
|
|
hashCache->prevProgramCode = NULL;
|
|
hashCache->prevProgramSize = 0;
|
|
hashCache->prevHash1 = 0;
|
|
hashCache->prevHash2 = 0;
|
|
}
|
|
else if (hashCache->prevProgramCode != programCode || hashCache->prevProgramSize != programSize)
|
|
{
|
|
_calcShaderHashGeneric(programCode, programSize, progHash1, progHash2);
|
|
hashCache->prevProgramCode = programCode;
|
|
hashCache->prevProgramSize = programSize;
|
|
hashCache->prevHash1 = progHash1;
|
|
hashCache->prevHash2 = progHash2;
|
|
}
|
|
else
|
|
{
|
|
progHash1 = hashCache->prevHash1;
|
|
progHash2 = hashCache->prevHash2;
|
|
}
|
|
*outputHash1 = progHash1;
|
|
*outputHash2 = progHash2;
|
|
}
|
|
|
|
void LatteSHRC_ResetCachedShaderHash()
|
|
{
|
|
hashCacheVS.prevProgramCode = 0;
|
|
hashCacheVS.prevProgramSize = 0;
|
|
hashCacheGS.prevProgramCode = 0;
|
|
hashCacheGS.prevProgramSize = 0;
|
|
hashCachePS.prevProgramCode = 0;
|
|
hashCachePS.prevProgramSize = 0;
|
|
}
|
|
|
|
LatteShaderPSInputTable _activePSImportTable;
|
|
|
|
LatteShaderPSInputTable* LatteSHRC_GetPSInputTable()
|
|
{
|
|
return &_activePSImportTable;
|
|
}
|
|
|
|
void LatteSHRC_RemoveFromCaches(LatteDecompilerShader* shader)
|
|
{
|
|
bool removed = false;
|
|
auto& cache = LatteSHRC_GetCacheByType(shader->shaderType);
|
|
// remove from hashtable
|
|
auto baseIt = cache.find(shader->baseHash);
|
|
if (baseIt == cache.end())
|
|
{
|
|
cemu_assert_suspicious(); // deleting from runtime cache but shader is not present?
|
|
}
|
|
else if (baseIt->second == shader)
|
|
{
|
|
cemu_assert_debug(baseIt->second == shader);
|
|
cache.erase(baseIt);
|
|
if (shader->next)
|
|
{
|
|
cemu_assert_debug(shader->baseHash == shader->next->baseHash);
|
|
cache.emplace(shader->baseHash, shader->next);
|
|
}
|
|
shader->next = 0;
|
|
removed = true;
|
|
}
|
|
else
|
|
{
|
|
// remove from chain
|
|
LatteDecompilerShader* shaderChain = baseIt->second;
|
|
while (shaderChain->next)
|
|
{
|
|
if (shaderChain->next == shader)
|
|
{
|
|
shaderChain->next = shaderChain->next->next;
|
|
removed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
LatteDecompilerShader* shader = nullptr;
|
|
if (type == LatteConst::ShaderType::Vertex)
|
|
shader = LatteSHRC_FindVertexShader(shader_base_hash, shader_aux_hash);
|
|
else if (type == LatteConst::ShaderType::Geometry)
|
|
shader = LatteSHRC_FindGeometryShader(shader_base_hash, shader_aux_hash);
|
|
else if (type == LatteConst::ShaderType::Pixel)
|
|
shader = LatteSHRC_FindPixelShader(shader_base_hash, shader_aux_hash);
|
|
if (shader)
|
|
LatteSHRC_RemoveFromCaches(shader);
|
|
}
|
|
|
|
void LatteShader_free(LatteDecompilerShader* shader) // todo - make this ~LatteDecompilerShader()
|
|
{
|
|
LatteSHRC_RemoveFromCaches(shader);
|
|
if (shader->shader)
|
|
delete shader->shader;
|
|
shader->shader = nullptr;
|
|
delete shader;
|
|
}
|
|
|
|
void LatteShader_CreatePSInputTable(LatteShaderPSInputTable* psInputTable, uint32* contextRegisters)
|
|
{
|
|
// PS control
|
|
uint32 psControl0 = contextRegisters[mmSPI_PS_IN_CONTROL_0];
|
|
uint32 spi0_positionEnable = (psControl0 >> 8) & 1;
|
|
uint32 spi0_positionCentroid = (psControl0 >> 9) & 1;
|
|
cemu_assert_debug(spi0_positionCentroid == 0); // 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;
|
|
|
|
//cemu_assert_debug(((psControl0>>26)&3) == 1); // BARYC_SAMPLE_CNTL
|
|
//cemu_assert_debug((psControl0&(1 << 28)) == 0); // PERSP_GRADIENT_ENA
|
|
//cemu_assert_debug((psControl0&(1 << 29)) == 0); // LINEAR_GRADIENT_ENA
|
|
// if LINEAR_GRADIENT_ENA_bit is enabled, the pixel shader accesses gl_ClipSize?
|
|
|
|
// VS/GS parameters
|
|
uint32 numPSInputs = contextRegisters[mmSPI_PS_IN_CONTROL_0] & 0x3F;
|
|
uint64 key = 0;
|
|
|
|
if (spi0_positionEnable)
|
|
{
|
|
key += (uint64)spi0_positionAddr + 1;
|
|
}
|
|
|
|
// parameter gen
|
|
if (spi0_paramGen != 0)
|
|
{
|
|
key += std::rotr<uint64>(spi0_paramGen, 7);
|
|
key += std::rotr<uint64>(spi0_paramGenAddr, 3);
|
|
psInputTable->paramGen = spi0_paramGen;
|
|
psInputTable->paramGenGPR = spi0_paramGenAddr;
|
|
}
|
|
else
|
|
{
|
|
psInputTable->paramGen = 0;
|
|
}
|
|
|
|
// semantic imports from vertex shader
|
|
#ifdef CEMU_DEBUG_ASSERT
|
|
uint8 semanticMask[256 / 8] = { 0 };
|
|
#endif
|
|
cemu_assert_debug(numPSInputs <= GPU7_PS_MAX_INPUTS);
|
|
numPSInputs = std::min<uint32>(numPSInputs, GPU7_PS_MAX_INPUTS);
|
|
|
|
for (uint32 f = 0; f < numPSInputs; f++)
|
|
{
|
|
uint32 psInputControl = contextRegisters[mmSPI_PS_INPUT_CNTL_0 + f];
|
|
uint32 psSemanticId = (psInputControl & 0xFF);
|
|
|
|
uint8 defaultValue = (psInputControl>>8)&3;
|
|
// default:
|
|
// 0 -> 0.0 0.0 0.0 0.0
|
|
// 1 -> 0.0 0.0 0.0 1.0
|
|
// 2 -> 1.0 1.0 1.0 0.0
|
|
// 3 -> 1.0 1.0 1.0 1.0
|
|
cemu_assert_debug(defaultValue <= 1);
|
|
|
|
uint32 uknBits = psInputControl & ~((0xFF)|(0x3<<8) | (1 << 10) | (1 << 12));
|
|
uknBits &= ~0x800; // FLAT_SHADE
|
|
//cemu_assert_debug(uknBits == 0);
|
|
//cemu_assert_debug(((psInputControl >> 11) & 1) == 0); // centroid
|
|
//cemu_assert_debug(((psInputControl >> 17) & 1) == 0); // point sprite coord
|
|
cemu_assert_debug(psSemanticId != 0xFF);
|
|
|
|
key += (uint64)psInputControl;
|
|
key = std::rotl<uint64>(key, 7);
|
|
if (f == spi0_positionAddr)
|
|
{
|
|
psInputTable->import[f].semanticId = LATTE_ANALYZER_IMPORT_INDEX_SPIPOSITION;
|
|
psInputTable->import[f].isFlat = false;
|
|
psInputTable->import[f].isNoPerspective = false;
|
|
key += (uint64)0x33;
|
|
}
|
|
else
|
|
{
|
|
#ifdef CEMU_DEBUG_ASSERT
|
|
if (semanticMask[psSemanticId >> 3] & (1 << (psSemanticId & 7)))
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "SemanticId already used");
|
|
}
|
|
semanticMask[psSemanticId >> 3] |= (1 << (psSemanticId & 7));
|
|
#endif
|
|
|
|
psInputTable->import[f].semanticId = psSemanticId;
|
|
psInputTable->import[f].isFlat = (psInputControl&(1 << 10)) != 0;
|
|
psInputTable->import[f].isNoPerspective = (psInputControl&(1 << 12)) != 0;
|
|
}
|
|
}
|
|
psInputTable->key = key;
|
|
psInputTable->count = numPSInputs;
|
|
}
|
|
|
|
// both vertex and geometry/pixel shader depend on PS inputs
|
|
// we prepare the PS import info in advance
|
|
void LatteShader_UpdatePSInputs(uint32* contextRegisters)
|
|
{
|
|
LatteShader_CreatePSInputTable(&_activePSImportTable, contextRegisters);
|
|
}
|
|
|
|
void LatteShader_CreateRendererShader(LatteDecompilerShader* shader, bool compileAsync)
|
|
{
|
|
if (shader->hasError )
|
|
{
|
|
cemuLog_log(LogType::Force, "Unable to compile shader {:016x}", shader->baseHash);
|
|
return;
|
|
}
|
|
|
|
GraphicPack2::GP_SHADER_TYPE gpShaderType;
|
|
RendererShader::ShaderType shaderType;
|
|
if (shader->shaderType == LatteConst::ShaderType::Vertex)
|
|
{
|
|
shaderType = RendererShader::ShaderType::kVertex;
|
|
gpShaderType = GraphicPack2::GP_SHADER_TYPE::VERTEX;
|
|
}
|
|
else if (shader->shaderType == LatteConst::ShaderType::Geometry)
|
|
{
|
|
shaderType = RendererShader::ShaderType::kGeometry;
|
|
gpShaderType = GraphicPack2::GP_SHADER_TYPE::GEOMETRY;
|
|
}
|
|
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
|
{
|
|
shaderType = RendererShader::ShaderType::kFragment;
|
|
gpShaderType = GraphicPack2::GP_SHADER_TYPE::PIXEL;
|
|
}
|
|
|
|
// check if a custom shader is present
|
|
std::string shaderSrc;
|
|
|
|
const std::string* customShaderSrc = GraphicPack2::FindCustomShaderSource(shader->baseHash, shader->auxHash, gpShaderType, g_renderer->GetType() == RendererAPI::Vulkan, g_renderer->GetType() == RendererAPI::Metal);
|
|
if (customShaderSrc)
|
|
{
|
|
shaderSrc.assign(*customShaderSrc);
|
|
shader->isCustomShader = true;
|
|
}
|
|
else
|
|
shaderSrc.assign(shader->strBuf_shaderSource->c_str());
|
|
|
|
if (shaderType == RendererShader::ShaderType::kVertex &&
|
|
(shader->baseHash == 0x15bc7edf9de2ed30 || shader->baseHash == 0x83a697d61a3b9202 ||
|
|
shader->baseHash == 0x97bc44a5028381c6 || shader->baseHash == 0x24838b84d15a1da1))
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "Filtered shader to avoid AMD crash");
|
|
shader->shader = nullptr;
|
|
shader->hasError = true;
|
|
return;
|
|
}
|
|
|
|
// create shader
|
|
shader->shader = g_renderer->shader_create(shaderType, shader->baseHash, shader->auxHash, shaderSrc, true, shader->isCustomShader);
|
|
if (shader->shader == nullptr)
|
|
shader->hasError = true;
|
|
// after renderer shader creation we can throw away any intermediate info
|
|
LatteShader_CleanupAfterCompile(shader);
|
|
}
|
|
|
|
void LatteShader_FinishCompilation(LatteDecompilerShader* shader)
|
|
{
|
|
if (shader->hasError)
|
|
{
|
|
cemuLog_logDebug(LogType::Force, "LatteShader_finishCompilation(): Skipped because of error in shader {:x}", shader->baseHash);
|
|
return;
|
|
}
|
|
shader->shader->WaitForCompiled();
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
LatteShader_prepareSeparableUniforms(shader);
|
|
#endif
|
|
LatteShader_CleanupAfterCompile(shader);
|
|
}
|
|
|
|
void LatteSHRC_RegisterShader(LatteDecompilerShader* shader, uint64 baseHash, uint64 auxHash)
|
|
{
|
|
auto& cache = LatteSHRC_GetCacheByType(shader->shaderType);
|
|
shader->baseHash = baseHash;
|
|
shader->auxHash = auxHash;
|
|
|
|
auto it = cache.find(baseHash);
|
|
if (it == cache.end())
|
|
{
|
|
shader->next = nullptr;
|
|
cache.emplace(shader->baseHash, shader);
|
|
}
|
|
else
|
|
{
|
|
shader->next = it->second->next;
|
|
it->second->next = shader;
|
|
}
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetFromChain(LatteDecompilerShader* baseShader, uint64 baseHash, uint64 auxHash)
|
|
{
|
|
while (baseShader && baseShader->auxHash != auxHash)
|
|
baseShader = baseShader->next;
|
|
return baseShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_Get(SHRC_CACHE_TYPE& cache, uint64 baseHash, uint64 auxHash)
|
|
{
|
|
auto it = cache.find(baseHash);
|
|
if (it == cache.end())
|
|
return nullptr;
|
|
LatteDecompilerShader* baseShader = it->second;
|
|
if (!baseShader)
|
|
return nullptr;
|
|
while (baseShader && baseShader->auxHash != auxHash)
|
|
baseShader = baseShader->next;
|
|
return baseShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_FindVertexShader(uint64 baseHash, uint64 auxHash)
|
|
{
|
|
return LatteSHRC_Get(sVertexShaders, baseHash, auxHash);
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_FindGeometryShader(uint64 baseHash, uint64 auxHash)
|
|
{
|
|
return LatteSHRC_Get(sGeometryShaders, baseHash, auxHash);
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_FindPixelShader(uint64 baseHash, uint64 auxHash)
|
|
{
|
|
return LatteSHRC_Get(sPixelShaders, baseHash, auxHash);
|
|
}
|
|
|
|
LatteFetchShader* LatteSHRC_GetOrCreateFetchShader()
|
|
{
|
|
return LatteFetchShader::FindByGPUState();
|
|
}
|
|
|
|
void LatteShader_CleanupAfterCompile(LatteDecompilerShader* shader)
|
|
{
|
|
if (shader->strBuf_shaderSource)
|
|
{
|
|
delete shader->strBuf_shaderSource;
|
|
shader->strBuf_shaderSource = nullptr;
|
|
}
|
|
}
|
|
|
|
void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShader* shader)
|
|
{
|
|
if (!ActiveSettings::DumpShadersEnabled())
|
|
return;
|
|
|
|
const char* suffix = "";
|
|
if (shader->shaderType == LatteConst::ShaderType::Vertex)
|
|
suffix = "vs";
|
|
else if (shader->shaderType == LatteConst::ShaderType::Geometry)
|
|
suffix = "gs";
|
|
else if (shader->shaderType == LatteConst::ShaderType::Pixel)
|
|
suffix = "ps";
|
|
|
|
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix));
|
|
if (fs)
|
|
{
|
|
if (shader->strBuf_shaderSource)
|
|
fs->writeData(shader->strBuf_shaderSource->c_str(), shader->strBuf_shaderSource->getLen());
|
|
delete fs;
|
|
}
|
|
}
|
|
|
|
void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uint8* programCode, uint32 programLen)
|
|
{
|
|
if (!ActiveSettings::DumpShadersEnabled())
|
|
return;
|
|
const char* suffix = "";
|
|
if (type == SHADER_DUMP_TYPE_FETCH)
|
|
suffix = "fs";
|
|
else if (type == SHADER_DUMP_TYPE_VERTEX)
|
|
suffix = "vs";
|
|
else if (type == SHADER_DUMP_TYPE_GEOMETRY)
|
|
suffix = "gs";
|
|
else if (type == SHADER_DUMP_TYPE_PIXEL)
|
|
suffix = "ps";
|
|
else if (type == SHADER_DUMP_TYPE_COPY)
|
|
suffix = "copy";
|
|
else if (type == SHADER_DUMP_TYPE_COMPUTE)
|
|
suffix = "compute";
|
|
|
|
FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix));
|
|
if (fs)
|
|
{
|
|
fs->writeData(programCode, programLen);
|
|
delete fs;
|
|
}
|
|
}
|
|
|
|
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 + fetchShader->key + _activePSImportTable.key + (usesGeometryShader ? 0x1111ULL : 0ULL);
|
|
|
|
uint32 tmp = LatteGPUState.contextNew.PA_CL_VTE_CNTL.getRawValue() ^ 0x43F;
|
|
vsHash += tmp;
|
|
|
|
auto primitiveType = LatteGPUState.contextNew.VGT_PRIMITIVE_TYPE.get_PRIMITIVE_MODE();
|
|
// TODO: include always in the hash in case of geometry shader or rect shader on Metal
|
|
if (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS)
|
|
{
|
|
vsHash += 13ULL;
|
|
}
|
|
else if (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::POINTS)
|
|
{
|
|
// required for Vulkan since we have to write the pointsize in the shader
|
|
vsHash += 71ULL;
|
|
}
|
|
vsHash += (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] ? 21 : 0);
|
|
// halfZ
|
|
if (LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_CLIP_SPACE_DEF())
|
|
vsHash += 0x1537;
|
|
|
|
#ifdef ENABLE_METAL
|
|
if (g_renderer->GetType() == RendererAPI::Metal)
|
|
{
|
|
bool isRectVertexShader = (primitiveType == Latte::LATTE_VGT_PRIMITIVE_TYPE::E_PRIMITIVE_TYPE::RECTS);
|
|
|
|
if ((usesGeometryShader || isRectVertexShader) || fetchShader->mtlFetchVertexManually)
|
|
{
|
|
for (sint32 g = 0; g < fetchShader->bufferGroups.size(); 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;
|
|
|
|
vsHash += (uint64)bufferStride;
|
|
vsHash = std::rotl<uint64>(vsHash, 7);
|
|
}
|
|
}
|
|
|
|
if (!(usesGeometryShader || isRectVertexShader))
|
|
{
|
|
if (LatteGPUState.contextNew.IsRasterizationEnabled())
|
|
vsHash += 51ULL;
|
|
|
|
// Vertex fetch
|
|
if (fetchShader->mtlFetchVertexManually)
|
|
vsHash += 349ULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
_shaderBaseHash_vs = vsHash;
|
|
}
|
|
|
|
void LatteSHRC_UpdateGSBaseHash(uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize)
|
|
{
|
|
// update hash from geometry shader data
|
|
uint64 gsHash1 = 0;
|
|
uint64 gsHash2 = 0;
|
|
_calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheGS, &gsHash1, &gsHash2);
|
|
// get geometry shader
|
|
uint64 gsHash = gsHash1 + gsHash2;
|
|
gsHash += (uint64)_activeVertexShader->ringParameterCount;
|
|
gsHash += (LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] ? 21 : 0);
|
|
_shaderBaseHash_gs = gsHash;
|
|
}
|
|
|
|
void LatteSHRC_UpdatePSBaseHash(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader)
|
|
{
|
|
uint32* psProgramCode = (uint32*)pixelShaderPtr;
|
|
// update hash from pixel shader data
|
|
uint64 psHash1 = 0;
|
|
uint64 psHash2 = 0;
|
|
_calculateShaderProgramHash(psProgramCode, pixelShaderSize, &hashCachePS, &psHash1, &psHash2);
|
|
// get vertex shader
|
|
uint64 psHash = psHash1 + psHash2 + _activePSImportTable.key + (usesGeometryShader ? hashCacheGS.prevHash1 : 0ULL);
|
|
|
|
_shaderBaseHash_ps = psHash;
|
|
}
|
|
|
|
uint64 LatteSHRC_CalcVSAuxHash(LatteDecompilerShader* vertexShader, uint32* contextRegisters)
|
|
{
|
|
// todo - include texture types in aux hash similar to how it is already done in pixel shader
|
|
// or maybe there is a way to figure out the proper texture types?
|
|
uint64 auxHash = 0;
|
|
if(vertexShader->hasStreamoutBufferWrite)
|
|
{
|
|
// hash stride for streamout buffers
|
|
for (uint32 i = 0; i < LATTE_NUM_STREAMOUT_BUFFER; i++)
|
|
{
|
|
if(!vertexShader->streamoutBufferWriteMask[i])
|
|
continue;
|
|
uint32 bufferStride = contextRegisters[mmVGT_STRMOUT_VTX_STRIDE_0 + i * 4];
|
|
auxHash = std::rotl<uint64>(auxHash, 7);
|
|
auxHash += (uint64)bufferStride;
|
|
}
|
|
}
|
|
// textures can affect the shader. Either by their type (2D, 3D, cubemap) or by their format (float vs integer)
|
|
uint64 auxHashTex = 0;
|
|
for (uint8 i = 0; i < vertexShader->textureUnitListCount; i++)
|
|
{
|
|
uint8 t = vertexShader->textureUnitList[i];
|
|
uint32 word4 = contextRegisters[Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_VS + t * 7 + 4];
|
|
if ((word4 & 0x300) == 0x100)
|
|
{
|
|
// integer format
|
|
auxHashTex = std::rotl<uint64>(auxHashTex, 7);
|
|
auxHashTex += 0x333;
|
|
}
|
|
}
|
|
|
|
return auxHash + auxHashTex;
|
|
}
|
|
|
|
uint64 LatteSHRC_CalcGSAuxHash(LatteDecompilerShader* geometryShader)
|
|
{
|
|
// todo - include texture types in aux hash similar to how it is already done in pixel shader
|
|
return 0;
|
|
}
|
|
|
|
uint64 LatteSHRC_CalcPSAuxHash(LatteDecompilerShader* pixelShader, uint32* contextRegisters)
|
|
{
|
|
uint64 auxHash = 0;
|
|
// CB_SHADER_MASK can remap pixel shader outputs
|
|
auxHash = (auxHash >> 3) | (auxHash << 61);
|
|
auxHash += (uint64)contextRegisters[mmCB_SHADER_MASK];
|
|
// alpha test
|
|
uint8 alphaTestFunc = contextRegisters[Latte::REGADDR::SX_ALPHA_TEST_CONTROL] & 0x7;
|
|
uint8 alphaTestEnable = (contextRegisters[Latte::REGADDR::SX_ALPHA_TEST_CONTROL] >> 3) & 1;
|
|
if (alphaTestEnable)
|
|
{
|
|
auxHash += (uint64)alphaTestFunc;
|
|
auxHash = (auxHash >> 3) | (auxHash << 61);
|
|
auxHash += 1;
|
|
}
|
|
// texture types (2D, 3D, cubemap etc.) affect the shader too
|
|
for (uint8 i = 0; i < pixelShader->textureUnitListCount; i++)
|
|
{
|
|
uint8 t = pixelShader->textureUnitList[i];
|
|
uint32 word0 = contextRegisters[Latte::REGADDR::SQ_TEX_RESOURCE_WORD0_N_PS + t * 7 + 0];
|
|
uint32 dim = (word0 & 7);
|
|
auxHash = (auxHash << 3) | (auxHash >> 61);
|
|
auxHash += (uint64)dim;
|
|
}
|
|
|
|
#ifdef ENABLE_METAL
|
|
if (g_renderer->GetType() == RendererAPI::Metal)
|
|
{
|
|
// Textures as render targets
|
|
for (uint32 i = 0; i < pixelShader->textureUnitListCount; i++)
|
|
{
|
|
uint8 t = pixelShader->textureUnitList[i];
|
|
auxHash = std::rotl<uint64>(auxHash, 11);
|
|
auxHash += (uint64)pixelShader->textureRenderTargetIndex[t];
|
|
}
|
|
|
|
// Color buffers
|
|
for (uint8 i = 0; i < LATTE_NUM_COLOR_TARGET; i++)
|
|
{
|
|
auto format = LatteMRT::GetColorBufferFormat(i, LatteGPUState.contextNew);
|
|
uint8 dataType = (uint8)GetMtlPixelFormatInfo(format, false).dataType;
|
|
auxHash = std::rotl<uint64>(auxHash, 7);
|
|
auxHash += (uint64)dataType;
|
|
}
|
|
|
|
// Depth buffer
|
|
bool hasDepthBuffer = LatteMRT::GetActiveDepthBufferMask(LatteGPUState.contextNew);
|
|
if (hasDepthBuffer)
|
|
{
|
|
auxHash = std::rotl<uint64>(auxHash, 5);
|
|
auxHash += 13u;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return auxHash;
|
|
}
|
|
|
|
static void InitUniformLayoutFromDecompiler(
|
|
LatteDecompilerShader* shader,
|
|
const LatteDecompilerOutput_t& decompilerOutput
|
|
)
|
|
{
|
|
if (g_renderer->GetType() == RendererAPI::OpenGL)
|
|
{
|
|
// hack - for OpenGL these are retrieved in _prepareSeparableUniforms()
|
|
shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister;
|
|
return;
|
|
}
|
|
const auto& offsets = decompilerOutput.uniformOffsetsVK;
|
|
|
|
shader->uniform.loc_remapped = offsets.offset_remapped;
|
|
shader->uniform.loc_uniformRegister = offsets.offset_uniformRegister;
|
|
shader->uniform.count_uniformRegister = offsets.count_uniformRegister;
|
|
shader->uniform.loc_windowSpaceToClipSpaceTransform = offsets.offset_windowSpaceToClipSpaceTransform;
|
|
shader->uniform.loc_alphaTestRef = offsets.offset_alphaTestRef;
|
|
shader->uniform.loc_pointSize = offsets.offset_pointSize;
|
|
shader->uniform.loc_fragCoordScale = offsets.offset_fragCoordScale;
|
|
|
|
// Texture scale uniforms
|
|
shader->uniform.list_ufTexRescale.clear();
|
|
for (sint32 t = 0; t < LATTE_NUM_MAX_TEX_UNITS; t++)
|
|
{
|
|
if (offsets.offset_texScale[t] >= 0)
|
|
{
|
|
LatteUniformTextureScaleEntry_t entry{};
|
|
entry.texUnit = t;
|
|
entry.uniformLocation = offsets.offset_texScale[t];
|
|
shader->uniform.list_ufTexRescale.push_back(entry);
|
|
}
|
|
}
|
|
|
|
shader->uniform.loc_verticesPerInstance = offsets.offset_verticesPerInstance;
|
|
|
|
// Streamout buffers
|
|
for (sint32 t = 0; t < LATTE_NUM_STREAMOUT_BUFFER; t++)
|
|
{
|
|
shader->uniform.loc_streamoutBufferBase[t] = offsets.offset_streamoutBufferBase[t];
|
|
}
|
|
|
|
shader->uniform.uniformRangeSize = offsets.offset_endOfBlock;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompilerOutput_t& decompilerOutput, uint64 baseHash, bool calculateAuxHash, uint64 optionalAuxHash, uint32* contextRegister)
|
|
{
|
|
LatteDecompilerShader* shader = decompilerOutput.shader;
|
|
shader->baseHash = baseHash;
|
|
// copy resource mapping
|
|
switch (g_renderer->GetType())
|
|
{
|
|
case RendererAPI::OpenGL:
|
|
shader->resourceMapping = decompilerOutput.resourceMappingGL;
|
|
break;
|
|
case RendererAPI::Vulkan:
|
|
shader->resourceMapping = decompilerOutput.resourceMappingVK;
|
|
break;
|
|
case RendererAPI::Metal:
|
|
shader->resourceMapping = decompilerOutput.resourceMappingMTL;
|
|
break;
|
|
}
|
|
// copy texture info
|
|
shader->textureUnitMask2 = decompilerOutput.textureUnitMask;
|
|
// copy streamout info
|
|
shader->streamoutBufferWriteMask = decompilerOutput.streamoutBufferWriteMask;
|
|
shader->hasStreamoutBufferWrite = decompilerOutput.streamoutBufferWriteMask.any();
|
|
// copy uniform offsets
|
|
InitUniformLayoutFromDecompiler(shader, decompilerOutput);
|
|
// calculate aux hash
|
|
if (calculateAuxHash)
|
|
{
|
|
if (decompilerOutput.shaderType == LatteConst::ShaderType::Vertex)
|
|
{
|
|
uint64 vsAuxHash = LatteSHRC_CalcVSAuxHash(shader, contextRegister);
|
|
shader->auxHash = vsAuxHash;
|
|
}
|
|
else if (decompilerOutput.shaderType == LatteConst::ShaderType::Geometry)
|
|
{
|
|
uint64 gsAuxHash = LatteSHRC_CalcGSAuxHash(shader);
|
|
shader->auxHash = gsAuxHash;
|
|
}
|
|
else if (decompilerOutput.shaderType == LatteConst::ShaderType::Pixel)
|
|
{
|
|
uint64 psAuxHash = LatteSHRC_CalcPSAuxHash(shader, contextRegister);
|
|
shader->auxHash = psAuxHash;
|
|
}
|
|
else
|
|
cemu_assert_debug(false);
|
|
}
|
|
else
|
|
{
|
|
shader->auxHash = optionalAuxHash;
|
|
}
|
|
return shader;
|
|
}
|
|
|
|
void LatteShader_GetDecompilerOptions(LatteDecompilerOptions& options, LatteConst::ShaderType shaderType, bool geometryShaderEnabled)
|
|
{
|
|
options.usesGeometryShader = geometryShaderEnabled;
|
|
options.spirvInstrinsics.hasRoundingModeRTEFloat32 = false;
|
|
options.useTFViaSSBO = g_renderer->UseTFViaSSBO();
|
|
#ifdef ENABLE_VULKAN
|
|
if (g_renderer->GetType() == RendererAPI::Vulkan)
|
|
{
|
|
options.spirvInstrinsics.hasRoundingModeRTEFloat32 = VulkanRenderer::GetInstance()->HasSPRIVRoundingModeRTE32();
|
|
}
|
|
#endif
|
|
options.strictMul = g_current_game_profile->GetAccurateShaderMul() != AccurateShaderMulOption::False;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteShader_CompileSeparableVertexShader2(uint64 baseHash, uint64& vsAuxHash, uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader)
|
|
{
|
|
/* Analyze shader to gather general information about inputs/outputs */
|
|
Latte::ShaderDescription shaderDescription;
|
|
if (!shaderDescription.analyzeShaderCode(vertexShaderPtr, vertexShaderSize, LatteConst::ShaderType::Vertex))
|
|
{
|
|
assert_dbg();
|
|
return nullptr;
|
|
}
|
|
/* Create context dependent IO info for this shader */
|
|
//Latte::ShaderInstanceInfo
|
|
assert_dbg();
|
|
|
|
// todo - Use ShaderInstanceInfo when generating the GLSL (GLSL::Emit() should take a 'GLSLInfoSource' class which has a bunch of virtual methods for retrieving uniform names etc. We then override this class and plug in logic using ShaderInstanceInfo
|
|
|
|
/* Translate R600Plus to GLSL */
|
|
ZpIR::DebugPrinter irDebugPrinter;
|
|
LatteTCGenIR genIR;
|
|
genIR.setVertexShaderContext(fetchShader, LatteGPUState.contextRegister + mmSQ_VTX_SEMANTIC_0);
|
|
auto irObj = genIR.transcompileLatteToIR(vertexShaderPtr, vertexShaderSize, LatteTCGenIR::VERTEX);
|
|
// debug output (before register allocation)
|
|
irDebugPrinter.setShowPhysicalRegisters(false);
|
|
irDebugPrinter.debugPrint(irObj);
|
|
// register allocation
|
|
ZirPass::RegisterAllocatorForGLSL ra(irObj);
|
|
ra.applyPass();
|
|
// debug output (after register allocation)
|
|
irDebugPrinter.setShowPhysicalRegisters(true);
|
|
irDebugPrinter.setPhysicalRegisterNameSource(ZirPass::RegisterAllocatorForGLSL::DebugPrintHelper_getPhysRegisterName);
|
|
irDebugPrinter.debugPrint(irObj);
|
|
// gen GLSL
|
|
StringBuf glslSourceBuffer(64 * 1024);
|
|
// emit GLSL header
|
|
assert_dbg(); // todo
|
|
// emit main
|
|
ZirEmitter::GLSL emitter;
|
|
emitter.Emit(irObj, &glslSourceBuffer);
|
|
|
|
// debug copy to string
|
|
std::string dbg;
|
|
dbg.insert(0, glslSourceBuffer.c_str(), glslSourceBuffer.getLen());
|
|
assert_dbg();
|
|
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// compile new vertex shader (relies partially on current state)
|
|
LatteDecompilerShader* LatteShader_CompileSeparableVertexShader(uint64 baseHash, uint64& vsAuxHash, uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader)
|
|
{
|
|
// new decompiler test
|
|
//LatteShader_CompileSeparableVertexShader2(baseHash, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader);
|
|
|
|
// legacy decompiler
|
|
LatteDecompilerOptions options;
|
|
LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Vertex, usesGeometryShader);
|
|
|
|
LatteDecompilerOutput_t decompilerOutput{};
|
|
LatteDecompiler_DecompileVertexShader(_shaderBaseHash_vs, LatteGPUState.contextRegister, vertexShaderPtr, vertexShaderSize, fetchShader, options, &decompilerOutput);
|
|
LatteDecompilerShader* vertexShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister);
|
|
vsAuxHash = vertexShader->auxHash;
|
|
if (vertexShader->hasError == false)
|
|
{
|
|
uint8* fsProgramCode = (uint8*)memory_getPointerFromPhysicalOffset(LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 0] << 8);
|
|
uint32 fsProgramSize = LatteGPUState.contextRegister[mmSQ_PGM_START_FS + 1] << 3;
|
|
LatteShaderCache_writeSeparableVertexShader(vertexShader->baseHash, vertexShader->auxHash, fsProgramCode, fsProgramSize, vertexShaderPtr, vertexShaderSize, LatteGPUState.contextRegister, usesGeometryShader);
|
|
}
|
|
LatteShader_DumpShader(vertexShader->baseHash, vertexShader->auxHash, vertexShader);
|
|
LatteShader_DumpRawShader(vertexShader->baseHash, vertexShader->auxHash, SHADER_DUMP_TYPE_VERTEX, vertexShaderPtr, vertexShaderSize);
|
|
LatteShader_CreateRendererShader(vertexShader, false);
|
|
performanceMonitor.numCompiledVS++;
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
if (g_renderer->GetType() == RendererAPI::OpenGL)
|
|
{
|
|
if (vertexShader->shader)
|
|
vertexShader->shader->PreponeCompilation(true);
|
|
LatteShader_FinishCompilation(vertexShader);
|
|
}
|
|
#endif
|
|
|
|
LatteSHRC_RegisterShader(vertexShader, vertexShader->baseHash, vertexShader->auxHash);
|
|
return vertexShader;
|
|
}
|
|
|
|
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, 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(), vertexShader->ringParameterCount);
|
|
}
|
|
LatteShader_DumpShader(geometryShader->baseHash, geometryShader->auxHash, geometryShader);
|
|
LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_GEOMETRY, geometryShaderPtr, geometryShaderSize);
|
|
LatteShader_DumpRawShader(geometryShader->baseHash, geometryShader->auxHash, SHADER_DUMP_TYPE_COPY, geometryCopyShader, geometryCopyShaderSize);
|
|
LatteShader_CreateRendererShader(geometryShader, false);
|
|
performanceMonitor.numCompiledGS++;
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
if (g_renderer->GetType() == RendererAPI::OpenGL)
|
|
{
|
|
if (geometryShader->shader)
|
|
geometryShader->shader->PreponeCompilation(true);
|
|
LatteShader_FinishCompilation(geometryShader);
|
|
}
|
|
#endif
|
|
|
|
LatteSHRC_RegisterShader(geometryShader, geometryShader->baseHash, geometryShader->auxHash);
|
|
return geometryShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteShader_CompileSeparablePixelShader(uint64 baseHash, uint64& psAuxHash, uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader)
|
|
{
|
|
LatteDecompilerOptions options;
|
|
LatteShader_GetDecompilerOptions(options, LatteConst::ShaderType::Pixel, usesGeometryShader);
|
|
|
|
LatteDecompilerOutput_t decompilerOutput{};
|
|
LatteDecompiler_DecompilePixelShader(baseHash, LatteGPUState.contextRegister, pixelShaderPtr, pixelShaderSize, options, &decompilerOutput);
|
|
LatteDecompilerShader* pixelShader = LatteShader_CreateShaderFromDecompilerOutput(decompilerOutput, baseHash, true, 0, LatteGPUState.contextRegister);
|
|
psAuxHash = pixelShader->auxHash;
|
|
LatteShader_DumpShader(_shaderBaseHash_ps, psAuxHash, pixelShader);
|
|
LatteShader_DumpRawShader(_shaderBaseHash_ps, psAuxHash, SHADER_DUMP_TYPE_PIXEL, pixelShaderPtr, pixelShaderSize);
|
|
LatteShader_CreateRendererShader(pixelShader, false);
|
|
performanceMonitor.numCompiledPS++;
|
|
if (pixelShader->hasError == false)
|
|
{
|
|
LatteShaderCache_writeSeparablePixelShader(_shaderBaseHash_ps, psAuxHash, pixelShaderPtr, pixelShaderSize, LatteGPUState.contextRegister, usesGeometryShader);
|
|
}
|
|
|
|
#ifdef ENABLE_OPENGL
|
|
if (g_renderer->GetType() == RendererAPI::OpenGL)
|
|
{
|
|
if (pixelShader->shader)
|
|
pixelShader->shader->PreponeCompilation(true);
|
|
LatteShader_FinishCompilation(pixelShader);
|
|
}
|
|
#endif
|
|
|
|
LatteSHRC_RegisterShader(pixelShader, _shaderBaseHash_ps, psAuxHash);
|
|
return pixelShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetOrCreateVertexShader(uint8* vertexShaderPtr, uint32 vertexShaderSize, bool usesGeometryShader, LatteFetchShader* fetchShader)
|
|
{
|
|
// todo - should include VTX_SEMANTIC table in state
|
|
LatteSHRC_UpdateVSBaseHash(vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader);
|
|
uint64 vsAuxHash = 0;
|
|
auto itBaseShader = sVertexShaders.find(_shaderBaseHash_vs);
|
|
LatteDecompilerShader* vertexShader = nullptr;
|
|
if (itBaseShader != sVertexShaders.end())
|
|
{
|
|
vsAuxHash = LatteSHRC_CalcVSAuxHash(itBaseShader->second, LatteGPUState.contextRegister);
|
|
vertexShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_vs, vsAuxHash);
|
|
}
|
|
if (!vertexShader)
|
|
vertexShader = LatteShader_CompileSeparableVertexShader(_shaderBaseHash_vs, vsAuxHash, vertexShaderPtr, vertexShaderSize, usesGeometryShader, fetchShader);
|
|
if (vertexShader->hasError)
|
|
LatteGPUState.activeShaderHasError = true;
|
|
return vertexShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetOrCreateGeometryShader(bool usesGeometryShader, uint8* geometryShaderPtr, uint32 geometryShaderSize, uint8* geometryCopyShader, uint32 geometryCopyShaderSize, LatteDecompilerShader* vertexShader)
|
|
{
|
|
if (!usesGeometryShader || !_activeVertexShader)
|
|
{
|
|
_shaderBaseHash_gs = 0;
|
|
return nullptr;
|
|
}
|
|
LatteSHRC_UpdateGSBaseHash(geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize);
|
|
auto itBaseShader = sGeometryShaders.find(_shaderBaseHash_gs);
|
|
LatteDecompilerShader* geometryShader;
|
|
if (itBaseShader != sGeometryShaders.end())
|
|
{
|
|
// geometry shader already known
|
|
geometryShader = itBaseShader->second;
|
|
cemu_assert_debug(LatteSHRC_CalcGSAuxHash(geometryShader) == 0);
|
|
}
|
|
else
|
|
{
|
|
// decompile geometry shader
|
|
geometryShader = LatteShader_CompileSeparableGeometryShader(_shaderBaseHash_gs, geometryShaderPtr, geometryShaderSize, geometryCopyShader, geometryCopyShaderSize, vertexShader);
|
|
}
|
|
if (geometryShader->hasError)
|
|
LatteGPUState.activeShaderHasError = true;
|
|
return geometryShader;
|
|
}
|
|
|
|
LatteDecompilerShader* LatteSHRC_GetOrCreatePixelShader(uint8* pixelShaderPtr, uint32 pixelShaderSize, bool usesGeometryShader)
|
|
{
|
|
LatteSHRC_UpdatePSBaseHash(pixelShaderPtr, pixelShaderSize, usesGeometryShader);
|
|
uint64 psAuxHash = 0;
|
|
auto itBaseShader = sPixelShaders.find(_shaderBaseHash_ps);
|
|
LatteDecompilerShader* pixelShader = nullptr;
|
|
if (itBaseShader != sPixelShaders.end())
|
|
{
|
|
psAuxHash = LatteSHRC_CalcPSAuxHash(itBaseShader->second, LatteGPUState.contextRegister);
|
|
pixelShader = LatteSHRC_GetFromChain(itBaseShader->second, _shaderBaseHash_ps, psAuxHash);
|
|
}
|
|
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
|
|
|
|
FORCEINLINE 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;
|
|
}
|
|
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()
|
|
{
|
|
// check if geometry shader is used
|
|
auto gsMode = LatteGPUState.contextNew.VGT_GS_MODE.get_MODE();
|
|
|
|
cemu_assert_debug(LatteGPUState.contextNew.VGT_GS_MODE.get_ES_PASSTHRU() == false);
|
|
// todo: Support for ES passthrough and cut mode in mmVGT_GS_MODE
|
|
|
|
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;
|
|
|
|
// 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)
|
|
{
|
|
// 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;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
// 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;
|
|
|
|
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)
|
|
{
|
|
if (shaderType == LatteConst::ShaderType::Vertex)
|
|
return Latte::SAMPLER_BASE_INDEX_VERTEX;
|
|
else if (shaderType == LatteConst::ShaderType::Pixel)
|
|
return Latte::SAMPLER_BASE_INDEX_PIXEL;
|
|
else if (shaderType == LatteConst::ShaderType::Geometry)
|
|
return Latte::SAMPLER_BASE_INDEX_GEOMETRY;
|
|
else
|
|
cemu_assert_suspicious();
|
|
return 0;
|
|
}
|
|
|
|
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()
|
|
{
|
|
while(!sVertexShaders.empty())
|
|
LatteShader_free(sVertexShaders.begin()->second);
|
|
cemu_assert_debug(sVertexShaders.empty());
|
|
while(!sGeometryShaders.empty())
|
|
LatteShader_free(sGeometryShaders.begin()->second);
|
|
cemu_assert_debug(sGeometryShaders.empty());
|
|
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;
|
|
}
|