mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 10:19:04 +02:00
The render manager now records commands on the emu thread, keeping all of its decisions there (lazy passes, clear merging, redundant state), and they're encoded on a render thread, or inline when bRenderMultiThreading is off, as in the Vulkan backend. Before encoding, passes to the same framebuffer are merged across independent work, by the rules of VulkanQueueRunner's render pass merge. God of War alternates with block transfers to other framebuffers, and went from about 41 to 18 render passes a frame, which more than halved its GPU time. Passes where nothing tests or writes depth or stencil leave the attachment unloaded and unstored, like the Vulkan backend, which doesn't attach depth then. On the eight benchmark states, Metal now uses 4-13% less CPU than Vulkan, with wall times from 6% faster to 4% slower. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
115 lines
3.9 KiB
Plaintext
115 lines
3.9 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/GPU/Metal/MetalGraphicsContext.h"
|
|
#include "Common/GPU/Metal/MetalRenderManager.h"
|
|
#include "Common/GPU/Vulkan/VulkanLoader.h"
|
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
|
#include "Common/GPU/thin3d_create.h"
|
|
#include "Common/Log.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "Core/System.h"
|
|
|
|
// The drawable size the layer should have, in pixels.
|
|
static void LayerPixelSize(CAMetalLayer *layer, int *width, int *height) {
|
|
CGSize size = layer.bounds.size;
|
|
CGFloat scale = layer.contentsScale;
|
|
*width = (int)(size.width * scale);
|
|
*height = (int)(size.height * scale);
|
|
}
|
|
|
|
MetalGraphicsContext::~MetalGraphicsContext() {
|
|
_dbg_assert_(!draw_ && !renderManager_ && !device_);
|
|
}
|
|
|
|
bool MetalGraphicsContext::InitAPI(void *wnd, std::string *deviceName, std::string *errorMessage) {
|
|
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
|
if (!device) {
|
|
*errorMessage = "No Metal device";
|
|
return false;
|
|
}
|
|
device_ = (__bridge_retained void *)device;
|
|
// The shaders go through glslang.
|
|
init_glslang();
|
|
return true;
|
|
}
|
|
|
|
bool MetalGraphicsContext::InitSurface(WindowSystem winsys, void *data1, void *data2, std::string *errorMessage) {
|
|
id<MTLDevice> device = (__bridge id<MTLDevice>)device_;
|
|
CAMetalLayer *layer = nil;
|
|
if (offscreenWidth_ == 0) {
|
|
layer = (__bridge CAMetalLayer *)data1;
|
|
if (!layer) {
|
|
*errorMessage = "No CAMetalLayer";
|
|
return false;
|
|
}
|
|
layer_ = data1;
|
|
}
|
|
// Like Vulkan: encoding on a thread of its own, unless that's turned off or there's only one frame in flight.
|
|
const bool useThread = g_Config.bRenderMultiThreading && g_Config.iInflightFrames != 1;
|
|
renderManager_ = new MetalRenderManager(device, layer, offscreenWidth_, offscreenHeight_, useThread);
|
|
renderManager_->SetInflightFrames(g_Config.iInflightFrames);
|
|
draw_ = Draw::T3DCreateMetalContext(renderManager_);
|
|
SetGPUBackend(GPUBackend::METAL, [[device name] UTF8String]);
|
|
if (!draw_->CreatePresets()) {
|
|
*errorMessage = "Failed to compile preset shaders";
|
|
ShutdownSurface();
|
|
return false;
|
|
}
|
|
int width = renderManager_->BackbufferWidth();
|
|
int height = renderManager_->BackbufferHeight();
|
|
if (layer) {
|
|
LayerPixelSize(layer, &width, &height);
|
|
}
|
|
INFO_LOG(Log::G3D, "Metal: Using %s, backbuffer %dx%d%s, %s", [[device name] UTF8String], width, height, layer ? "" : " (offscreen)", useThread ? "render thread" : "no render thread");
|
|
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, width, height);
|
|
return true;
|
|
}
|
|
|
|
void MetalGraphicsContext::ShutdownSurface() {
|
|
if (draw_) {
|
|
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, 0, 0);
|
|
}
|
|
delete draw_;
|
|
draw_ = nullptr;
|
|
delete renderManager_;
|
|
renderManager_ = nullptr;
|
|
layer_ = nullptr;
|
|
}
|
|
|
|
void MetalGraphicsContext::ShutdownAPI() {
|
|
if (device_) {
|
|
id<MTLDevice> device = (__bridge_transfer id<MTLDevice>)device_;
|
|
device = nil;
|
|
device_ = nullptr;
|
|
}
|
|
finalize_glslang();
|
|
}
|
|
|
|
void MetalGraphicsContext::Resize() {
|
|
if (offscreenWidth_ > 0 || !layer_) {
|
|
return;
|
|
}
|
|
int width, height;
|
|
LayerPixelSize((__bridge CAMetalLayer *)layer_, &width, &height);
|
|
draw_->HandleEvent(Draw::Event::LOST_BACKBUFFER, renderManager_->BackbufferWidth(), renderManager_->BackbufferHeight());
|
|
draw_->HandleEvent(Draw::Event::GOT_BACKBUFFER, width, height);
|
|
}
|