mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 02:09:05 +02:00
Loading trusted whatever was in the file, apart from a few sanity
checks, and a damaged entry stayed in the cache for good, since loading
it put it back in the pipeline map and every save wrote it out again.
A Syphon Filter: Logan's Shadow cache had pipeline keys with garbage in
useHWTransform, which MoltenVK refused to create ("Vertex attribute
texcoord(1) is missing from the vertex descriptor").
Now the files end in an XXH3 checksum of everything before it, checked
before anything is read, and a cache that fails it is deleted and
rebuilt.
Bumps both cache versions, since the files have a new layout. Old files
fail the checksum anyway.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
262 lines
8.7 KiB
C++
262 lines
8.7 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "Common/Profiler/Profiler.h"
|
|
#include "Common/Data/Text/I18n.h"
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/Data/Text/StringWriter.h"
|
|
#include "Common/GPU/GraphicsContext.h"
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/VR/PPSSPPVR.h"
|
|
#include "Common/StringUtils.h"
|
|
|
|
#include "Core/Config.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/ELF/ParamSFO.h"
|
|
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/Common/FramebufferManagerCommon.h"
|
|
#include "GPU/GLES/ShaderManagerGLES.h"
|
|
#include "GPU/GLES/GPU_GLES.h"
|
|
#include "GPU/GLES/FramebufferManagerGLES.h"
|
|
#include "GPU/GLES/DrawEngineGLES.h"
|
|
#include "GPU/GLES/TextureCacheGLES.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "Windows/GPU/WindowsGLContext.h"
|
|
#endif
|
|
|
|
GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
|
|
: GPUCommonHW(gfxCtx, draw), drawEngine_(draw), fragmentTestCache_(draw) {
|
|
gstate_c.SetUseFlags(CheckGPUFeatures());
|
|
|
|
shaderManagerGL_ = new ShaderManagerGLES(draw);
|
|
framebufferManagerGL_ = new FramebufferManagerGLES(draw);
|
|
framebufferManager_ = framebufferManagerGL_;
|
|
textureCacheGL_ = new TextureCacheGLES(draw, framebufferManager_->GetDraw2D());
|
|
textureCache_ = textureCacheGL_;
|
|
drawEngineCommon_ = &drawEngine_;
|
|
shaderManager_ = shaderManagerGL_;
|
|
|
|
drawEngine_.SetGPUCommon(this);
|
|
drawEngine_.SetShaderManager(shaderManagerGL_);
|
|
drawEngine_.SetTextureCache(textureCacheGL_);
|
|
drawEngine_.SetFramebufferManager(framebufferManagerGL_);
|
|
drawEngine_.SetFragmentTestCache(&fragmentTestCache_);
|
|
drawEngine_.Init();
|
|
framebufferManagerGL_->SetTextureCache(textureCacheGL_);
|
|
framebufferManagerGL_->SetShaderManager(shaderManagerGL_);
|
|
framebufferManagerGL_->SetDrawEngine(&drawEngine_);
|
|
framebufferManagerGL_->Init(msaaLevel_);
|
|
textureCacheGL_->SetFramebufferManager(framebufferManagerGL_);
|
|
textureCacheGL_->SetShaderManager(shaderManagerGL_);
|
|
textureCacheGL_->SetDrawEngine(&drawEngine_);
|
|
fragmentTestCache_.SetTextureCache(textureCacheGL_);
|
|
|
|
// Sanity check gstate
|
|
if ((int *)&gstate.transferstart - (int *)&gstate != 0xEA) {
|
|
ERROR_LOG(Log::G3D, "gstate has drifted out of sync!");
|
|
}
|
|
|
|
// No need to flush before the tex scale/offset commands if we are baking
|
|
// the tex scale/offset into the vertices anyway.
|
|
|
|
UpdateCmdInfo();
|
|
|
|
BuildReportingInfo();
|
|
|
|
textureCache_->NotifyConfigChanged();
|
|
|
|
// Load shader cache.
|
|
std::string discID = g_paramSFO.GetDiscID();
|
|
if (discID.size()) {
|
|
if (g_Config.bShaderCache) {
|
|
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
|
|
shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) / (discID + ".glshadercache");
|
|
// Actually precompiled by IsReady() since we're single-threaded.
|
|
File::IOFile f(shaderCachePath_, "rb");
|
|
if (f.IsOpen() && !VerifyShaderCacheChecksum(shaderCachePath_)) {
|
|
WARN_LOG(Log::G3D, "Old or damaged GL shader cache (no matching checksum) - rebuilding.");
|
|
f.Close();
|
|
File::Delete(shaderCachePath_);
|
|
}
|
|
if (f.IsOpen()) {
|
|
if (shaderManagerGL_->LoadCacheFlags(f, &drawEngine_)) {
|
|
gstate_c.SetUseFlags(CheckGPUFeatures());
|
|
// We're compiling now, clear if they changed.
|
|
gstate_c.useFlagsChanged = false;
|
|
|
|
if (shaderManagerGL_->LoadCache(f))
|
|
NOTICE_LOG(Log::G3D, "Precompiling the shader cache from '%s'", shaderCachePath_.c_str());
|
|
}
|
|
}
|
|
} else {
|
|
INFO_LOG(Log::G3D, "Shader cache disabled. Not loading.");
|
|
}
|
|
}
|
|
}
|
|
|
|
GPU_GLES::~GPU_GLES() {
|
|
// If we're here during app shutdown (exiting the Windows app in-game, for example)
|
|
// everything should already be cleared since DeviceLost has been run.
|
|
|
|
if (shaderCachePath_.Valid() && draw_) {
|
|
if (g_Config.bShaderCache) {
|
|
shaderManagerGL_->SaveCache(shaderCachePath_, &drawEngine_);
|
|
} else {
|
|
INFO_LOG(Log::G3D, "Shader cache disabled. Not saving.");
|
|
}
|
|
}
|
|
fragmentTestCache_.Clear();
|
|
}
|
|
|
|
// Take the raw GL extension and versioning data and turn into feature flags.
|
|
// TODO: This should use DrawContext::GetDeviceCaps() more and more, and eventually
|
|
// this can be shared between all the backends.
|
|
u32 GPU_GLES::CheckGPUFeatures() const {
|
|
u32 features = GPUCommonHW::CheckGPUFeatures();
|
|
|
|
features |= GPU_USE_16BIT_FORMATS;
|
|
|
|
if (!draw_->GetShaderLanguageDesc().bitwiseOps) {
|
|
features |= GPU_USE_FRAGMENT_TEST_CACHE;
|
|
}
|
|
|
|
// Can't use switch-case in older glsl.
|
|
if ((gl_extensions.IsGLES && !gl_extensions.GLES3) || (!gl_extensions.IsGLES && !gl_extensions.VersionGEThan(1, 3)))
|
|
features &= ~GPU_USE_LIGHT_UBERSHADER;
|
|
|
|
if (IsVREnabled() || g_Config.bForceVR) {
|
|
features |= GPU_USE_VIRTUAL_REALITY;
|
|
features &= ~GPU_USE_VS_RANGE_CULLING;
|
|
}
|
|
|
|
features = CheckGPUFeaturesLate(features);
|
|
|
|
if (draw_->GetBugs().Has(Draw::Bugs::ADRENO_RESOURCE_DEADLOCK) && g_Config.bVendorBugChecksEnabled) {
|
|
if (PSP_CoreParameter().compat.flags().OldAdrenoPixelDepthRoundingGL) {
|
|
features |= GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT;
|
|
}
|
|
}
|
|
|
|
// This is a bit ugly, but lets us reuse most of the depth logic in GPUCommon.
|
|
if (features & GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT) {
|
|
if (gl_extensions.IsGLES && !gl_extensions.GLES3) {
|
|
// Unsupported, switch to GPU_ROUND_DEPTH_TO_16BIT instead.
|
|
features &= ~GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT;
|
|
features |= GPU_ROUND_DEPTH_TO_16BIT;
|
|
}
|
|
}
|
|
|
|
if (g_Config.bSkipBufferEffects) {
|
|
features |= GPU_USE_NONBUFFERED_FLIP;
|
|
}
|
|
|
|
return features;
|
|
}
|
|
|
|
void GPU_GLES::BuildReportingInfo() {
|
|
GLRenderManager *render = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
|
|
if (!render) {
|
|
return;
|
|
}
|
|
|
|
std::string glVendor = render->GetGLString(GL_VENDOR);
|
|
std::string glRenderer = render->GetGLString(GL_RENDERER);
|
|
std::string glVersion = render->GetGLString(GL_VERSION);
|
|
std::string glSlVersion = render->GetGLString(GL_SHADING_LANGUAGE_VERSION);
|
|
std::string glExtensions;
|
|
|
|
if (gl_extensions.VersionGEThan(3, 0)) {
|
|
glExtensions = g_all_gl_extensions;
|
|
} else {
|
|
glExtensions = render->GetGLString(GL_EXTENSIONS);
|
|
}
|
|
|
|
reportingPrimaryInfo_ = glVendor;
|
|
reportingFullInfo_ = StringFromFormat("%s (%s %s), %s (extensions: %s)", glVersion.c_str(), glVendor.c_str(), glRenderer.c_str(), glSlVersion.c_str(), glExtensions.c_str());
|
|
|
|
Reporting::UpdateConfig();
|
|
}
|
|
|
|
void GPU_GLES::DeviceLost() {
|
|
INFO_LOG(Log::G3D, "GPU_GLES: DeviceLost");
|
|
|
|
// Simply drop all caches and textures.
|
|
// FBOs appear to survive? Or no?
|
|
// TransformDraw has registered as a GfxResourceHolder.
|
|
fragmentTestCache_.DeviceLost();
|
|
|
|
GPUCommonHW::DeviceLost();
|
|
}
|
|
|
|
void GPU_GLES::DeviceRestore(Draw::DrawContext *draw) {
|
|
GPUCommonHW::DeviceRestore(draw);
|
|
|
|
fragmentTestCache_.DeviceRestore(draw_);
|
|
}
|
|
|
|
void GPU_GLES::BeginHostFrame(const DisplayLayoutConfig &config) {
|
|
GPUCommonHW::BeginHostFrame(config);
|
|
drawEngine_.BeginFrame();
|
|
|
|
textureCache_->StartFrame();
|
|
|
|
// Save the cache from time to time. TODO: How often? We save on exit, so shouldn't need to do this all that often.
|
|
|
|
constexpr int saveShaderCacheFrameInterval = 32767; // power of 2 - 1. About every 10 minutes at 60fps.
|
|
if (shaderCachePath_.Valid() && !(gpuStats.totals.numFlips & saveShaderCacheFrameInterval) && coreState == CORE_RUNNING_CPU) {
|
|
shaderManagerGL_->SaveCache(shaderCachePath_, &drawEngine_);
|
|
}
|
|
|
|
gstate_c.Dirty(DIRTY_ALL_UNIFORMS | DIRTY_FRAGMENTSHADER_STATE | DIRTY_VERTEXSHADER_STATE);
|
|
|
|
framebufferManager_->BeginFrame(config);
|
|
|
|
fragmentTestCache_.Decimate();
|
|
if (gstate_c.useFlagsChanged) {
|
|
// TODO: It'd be better to recompile them in the background, probably?
|
|
// This most likely means that saw equal depth changed.
|
|
WARN_LOG(Log::G3D, "Shader use flags changed, clearing all shaders and depth buffers");
|
|
shaderManager_->ClearShaders();
|
|
framebufferManager_->ClearAllDepthBuffers();
|
|
gstate_c.useFlagsChanged = false;
|
|
}
|
|
}
|
|
|
|
void GPU_GLES::EndHostFrame() {
|
|
drawEngine_.EndFrame();
|
|
}
|
|
|
|
void GPU_GLES::FinishDeferred() {
|
|
// This finishes reading any vertex data that is pending.
|
|
drawEngine_.FinishDeferred();
|
|
}
|
|
|
|
void GPU_GLES::GetStats(StringWriter &w) {
|
|
w.F("Vertex, Fragment, Programs loaded: %d, %d, %d\n",
|
|
shaderManagerGL_->GetNumVertexShaders(),
|
|
shaderManagerGL_->GetNumFragmentShaders(),
|
|
shaderManagerGL_->GetNumPrograms()
|
|
);
|
|
FormatGPUStatsCommon(w);
|
|
}
|