Files
ppsspp/GPU/Metal/GPU_Metal.mm
T

225 lines
8.0 KiB
Plaintext

// 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 "ppsspp_config.h"
#include "Common/Log.h"
#include "Common/Data/Text/StringWriter.h"
#include "Common/GPU/thin3d.h"
#include "Common/File/FileUtil.h"
#include "Common/TimeUtil.h"
#include "Core/Config.h"
#include "Core/Reporting.h"
#include "Core/System.h"
#include "Core/ELF/ParamSFO.h"
#include "GPU/GPUState.h"
#include "GPU/Common/ShaderCommon.h"
#include "GPU/Metal/GPU_Metal.h"
#include "GPU/Metal/DrawEngineMetal.h"
#include "GPU/Metal/FramebufferManagerMetal.h"
#include "GPU/Metal/ShaderManagerMetal.h"
#include "GPU/Metal/TextureCacheMetal.h"
GPU_Metal::GPU_Metal(GraphicsContext *gfxCtx, Draw::DrawContext *draw) : GPUCommonHW(gfxCtx, draw) {
gstate_c.SetUseFlags(CheckGPUFeatures());
drawEngine_ = new DrawEngineMetal(draw);
shaderManagerMetal_ = new ShaderManagerMetal(draw);
framebufferManagerMetal_ = new FramebufferManagerMetal(draw);
framebufferManager_ = framebufferManagerMetal_;
textureCacheMetal_ = new TextureCacheMetal(draw, framebufferManager_->GetDraw2D());
textureCache_ = textureCacheMetal_;
drawEngineCommon_ = drawEngine_;
shaderManager_ = shaderManagerMetal_;
drawEngine_->SetGPUCommon(this);
drawEngine_->SetTextureCache(textureCacheMetal_);
drawEngine_->SetFramebufferManager(framebufferManagerMetal_);
drawEngine_->SetShaderManager(shaderManagerMetal_);
drawEngine_->Init();
framebufferManagerMetal_->SetTextureCache(textureCacheMetal_);
framebufferManagerMetal_->SetDrawEngine(drawEngine_);
framebufferManagerMetal_->SetShaderManager(shaderManagerMetal_);
framebufferManagerMetal_->Init(msaaLevel_);
textureCacheMetal_->SetFramebufferManager(framebufferManagerMetal_);
textureCacheMetal_->SetShaderManager(shaderManagerMetal_);
textureCacheMetal_->SetDrawEngine(drawEngine_);
UpdateCmdInfo();
BuildReportingInfo();
textureCache_->NotifyConfigChanged();
std::string discID = g_paramSFO.GetDiscID();
if (!discID.empty()) {
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) / (discID + ".mtlshadercache");
LoadCache(shaderCachePath_);
}
}
namespace {
// Increase when the stored keys change meaning (MetalPipelineKey, the shader IDs, the formats).
constexpr uint32_t METAL_CACHE_MAGIC = 0x4C54534D; // "MSTL"
constexpr uint32_t METAL_CACHE_VERSION = 2;
struct MetalCacheHeader {
uint32_t magic;
uint32_t version;
uint32_t useFlags;
uint32_t pipelineRecordSize; // Catches layout changes that forgot the version.
int32_t numVertexShaders;
int32_t numFragmentShaders;
int32_t numPipelines;
};
} // namespace
// Runs on the loader thread, before the game starts. Waits for the pipelines, like the Vulkan backend, so
// they don't stutter in the first frames.
void GPU_Metal::LoadCache(const Path &filename) {
if (!g_Config.bShaderCache) {
return;
}
FILE *f = File::OpenCFile(filename, "rb");
if (!f) {
return;
}
if (!VerifyShaderCacheChecksum(filename)) {
WARN_LOG(Log::G3D, "Old or damaged Metal shader cache (no matching checksum) - rebuilding.");
fclose(f);
File::Delete(filename);
return;
}
MetalCacheHeader header{};
bool ok = fread(&header, sizeof(header), 1, f) == 1 && header.magic == METAL_CACHE_MAGIC && header.version == METAL_CACHE_VERSION &&
header.pipelineRecordSize == drawEngine_->PipelineRecordSize();
if (ok && header.useFlags != gstate_c.GetUseFlags()) {
// The IDs carry the flags they were made with, so this just means some of them won't be used.
WARN_LOG(Log::G3D, "Metal shader cache useFlags mismatch, %08x, expected %08x", header.useFlags, gstate_c.GetUseFlags());
}
double start = time_now_d();
ok = ok && shaderManagerMetal_->LoadCache(f, header.numVertexShaders, header.numFragmentShaders);
ok = ok && drawEngine_->LoadPipelines(f, header.numPipelines, true);
fclose(f);
if (ok) {
INFO_LOG(Log::G3D, "Loaded Metal shader cache: %d vertex and %d fragment shaders, %d pipelines in %0.1f ms",
header.numVertexShaders, header.numFragmentShaders, header.numPipelines, (time_now_d() - start) * 1000.0);
} else {
WARN_LOG(Log::G3D, "Incompatible or damaged Metal shader cache, deleting it");
File::Delete(filename);
}
}
void GPU_Metal::SaveCache(const Path &filename) {
// After DeviceLost (which saved it), the shaders are gone. Don't overwrite the cache with nothing.
if (!g_Config.bShaderCache || !filename.Valid() || !draw_) {
return;
}
FILE *f = File::OpenCFile(filename, "wb");
if (!f) {
return;
}
MetalCacheHeader header{};
header.magic = METAL_CACHE_MAGIC;
header.version = METAL_CACHE_VERSION;
header.useFlags = gstate_c.GetUseFlags();
header.pipelineRecordSize = drawEngine_->PipelineRecordSize();
header.numVertexShaders = shaderManagerMetal_->GetNumVertexShaders();
header.numFragmentShaders = shaderManagerMetal_->GetNumFragmentShaders();
header.numPipelines = drawEngine_->GetNumPipelines();
bool ok = fwrite(&header, sizeof(header), 1, f) == 1;
ok = ok && shaderManagerMetal_->SaveCache(f);
ok = ok && drawEngine_->SavePipelines(f);
fclose(f);
ok = ok && AppendShaderCacheChecksum(filename);
if (ok) {
INFO_LOG(Log::G3D, "Saved Metal shader cache: %d vertex and %d fragment shaders, %d pipelines", header.numVertexShaders, header.numFragmentShaders, header.numPipelines);
} else {
ERROR_LOG(Log::G3D, "Failed to write the Metal shader cache, disk full?");
File::Delete(filename);
}
}
GPU_Metal::~GPU_Metal() {
SaveCache(shaderCachePath_);
if (draw_) {
MetalRenderManager *rm = (MetalRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
rm->WaitIdle();
}
// The other managers are deleted in ~GPUCommonHW.
delete drawEngine_;
drawEngine_ = nullptr;
drawEngineCommon_ = nullptr;
}
u32 GPU_Metal::CheckGPUFeatures() const {
u32 features = GPUCommonHW::CheckGPUFeatures();
MetalRenderManager *rm = (MetalRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
// The packed 16-bit formats (B5G6R5Unorm and friends) only exist on Apple GPUs.
if (rm->IsAppleGPU()) {
features |= GPU_USE_16BIT_FORMATS;
}
return CheckGPUFeaturesLate(features);
}
void GPU_Metal::BeginHostFrame(const DisplayLayoutConfig &config) {
GPUCommonHW::BeginHostFrame(config);
drawEngine_->BeginFrame();
textureCache_->StartFrame();
framebufferManager_->BeginFrame(config);
gstate_c.Dirty(DIRTY_ALL);
if (gstate_c.useFlagsChanged) {
WARN_LOG(Log::G3D, "Shader use flags changed, clearing all shaders and depth buffers");
drawEngine_->ClearPipelines();
shaderManager_->ClearShaders();
framebufferManager_->ClearAllDepthBuffers();
gstate_c.useFlagsChanged = false;
}
}
void GPU_Metal::FinishDeferred() {
drawEngine_->FinishDeferred();
}
void GPU_Metal::DeviceLost() {
// The managers free their caches (pipelines, samplers, depth stencil states) right away, so nothing
// recorded can still be waiting to use them.
if (draw_) {
MetalRenderManager *rm = (MetalRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER);
rm->WaitIdle();
}
SaveCache(shaderCachePath_);
drawEngine_->ClearPipelines();
GPUCommonHW::DeviceLost();
}
void GPU_Metal::DeviceRestore(Draw::DrawContext *draw) {
GPUCommonHW::DeviceRestore(draw);
}
void GPU_Metal::GetStats(StringWriter &w) {
w.F("Vertex, Fragment shaders, Pipelines loaded: %d, %d, %d\n",
shaderManagerMetal_->GetNumVertexShaders(),
shaderManagerMetal_->GetNumFragmentShaders(),
drawEngine_->GetNumPipelines());
FormatGPUStatsCommon(w);
}