mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-15 10:40:01 +01:00
Metal: Don't let command buffers retain resources
Resources that the GPU may still use are now released through the render manager's per-frame list, freed once the frame's work completes, instead of command buffers retaining everything that's bound. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
042e320559
commit
330be6c29a
5 files changed
+61
-11
No files matched your search
@@ -162,6 +162,15 @@ public:
|
||||
// Waits for all submitted work to complete.
|
||||
void WaitIdle();
|
||||
|
||||
// Command buffers don't retain what they use (that costs a retain and release per binding), so
|
||||
// anything that may still be in use by the GPU has to be released through this. Keeps the object
|
||||
// alive until the GPU is done with the current frame.
|
||||
void DeferRelease(id object) {
|
||||
if (object) {
|
||||
frames_[curFrame_].deleteList.push_back(object);
|
||||
}
|
||||
}
|
||||
|
||||
// Backbuffer
|
||||
void Resize(int width, int height);
|
||||
int BackbufferWidth() const { return bbWidth_; }
|
||||
@@ -249,6 +258,7 @@ private:
|
||||
|
||||
struct FrameData {
|
||||
MetalPushBuffer push;
|
||||
std::vector<id> deleteList;
|
||||
bool hasPendingWork = false;
|
||||
};
|
||||
FrameData frames_[METAL_MAX_INFLIGHT_FRAMES];
|
||||
|
||||
@@ -267,6 +267,7 @@ void MetalRenderManager::CreateOffscreenBackbuffer(int width, int height) {
|
||||
MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:width height:height mipmapped:NO];
|
||||
desc.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
desc.storageMode = MTLStorageModePrivate;
|
||||
DeferRelease(offscreenBackbuffer_);
|
||||
offscreenBackbuffer_ = [device_ newTextureWithDescriptor:desc];
|
||||
offscreenBackbuffer_.label = @"offscreen_backbuffer";
|
||||
bbWidth_ = width;
|
||||
@@ -274,6 +275,7 @@ void MetalRenderManager::CreateOffscreenBackbuffer(int width, int height) {
|
||||
}
|
||||
|
||||
void MetalRenderManager::CreateBackbufferDepth() {
|
||||
DeferRelease(bbDepth_);
|
||||
if (bbWidth_ <= 0 || bbHeight_ <= 0) {
|
||||
bbDepth_ = nil;
|
||||
return;
|
||||
@@ -338,10 +340,10 @@ void MetalRenderManager::NewCommandBuffers() {
|
||||
return;
|
||||
}
|
||||
// The upload buffer is enqueued first, so it runs first even though it's committed at the same time.
|
||||
uploadCmd_ = [queue_ commandBuffer];
|
||||
uploadCmd_ = [queue_ commandBufferWithUnretainedReferences];
|
||||
uploadCmd_.label = @"upload";
|
||||
[uploadCmd_ enqueue];
|
||||
cmd_ = [queue_ commandBuffer];
|
||||
cmd_ = [queue_ commandBufferWithUnretainedReferences];
|
||||
cmd_.label = @"main";
|
||||
[cmd_ enqueue];
|
||||
}
|
||||
@@ -370,6 +372,7 @@ void MetalRenderManager::Present(bool vsync) {
|
||||
|
||||
if (drawable_) {
|
||||
[cmd_ presentDrawable:drawable_];
|
||||
DeferRelease(drawable_);
|
||||
drawable_ = nil;
|
||||
}
|
||||
|
||||
@@ -401,6 +404,7 @@ void MetalRenderManager::Present(bool vsync) {
|
||||
curFrame_ = (int)(frameNumber_ % METAL_MAX_INFLIGHT_FRAMES);
|
||||
dispatch_semaphore_wait(frameSemaphore_, DISPATCH_TIME_FOREVER);
|
||||
frames_[curFrame_].push.Reset();
|
||||
frames_[curFrame_].deleteList.clear();
|
||||
}
|
||||
|
||||
void MetalRenderManager::Flush(bool wait) {
|
||||
|
||||
@@ -195,7 +195,14 @@ public:
|
||||
|
||||
class MetalSamplerState : public SamplerState {
|
||||
public:
|
||||
MetalSamplerState(MetalRenderManager *rm) : rm_(rm) {}
|
||||
~MetalSamplerState() {
|
||||
rm_->DeferRelease(sampler);
|
||||
}
|
||||
id<MTLSamplerState> sampler;
|
||||
|
||||
private:
|
||||
MetalRenderManager *rm_;
|
||||
};
|
||||
|
||||
class MetalInputLayout : public InputLayout {
|
||||
@@ -217,10 +224,14 @@ public:
|
||||
|
||||
class MetalPipeline : public Pipeline {
|
||||
public:
|
||||
MetalPipeline(MetalRenderManager *rm) : rm_(rm) {}
|
||||
~MetalPipeline() {
|
||||
if (vs) vs->Release();
|
||||
if (fs) fs->Release();
|
||||
delete[] ubo;
|
||||
for (auto &iter : psos) {
|
||||
rm_->DeferRelease(iter.second);
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLRenderPipelineState> GetPSO(id<MTLDevice> device, const MetalRenderTargetFormat &format);
|
||||
@@ -236,6 +247,9 @@ public:
|
||||
size_t uboSize = 0;
|
||||
std::string tag;
|
||||
std::unordered_map<uint32_t, id<MTLRenderPipelineState>> psos;
|
||||
|
||||
private:
|
||||
MetalRenderManager *rm_;
|
||||
};
|
||||
|
||||
id<MTLRenderPipelineState> MetalPipeline::GetPSO(id<MTLDevice> device, const MetalRenderTargetFormat &format) {
|
||||
@@ -312,7 +326,7 @@ public:
|
||||
|
||||
class MetalTexture : public Texture {
|
||||
public:
|
||||
MetalTexture(const TextureDesc &desc) {
|
||||
MetalTexture(MetalRenderManager *rm, const TextureDesc &desc) : rm_(rm) {
|
||||
width_ = desc.width;
|
||||
height_ = desc.height;
|
||||
depth_ = desc.depth;
|
||||
@@ -320,14 +334,21 @@ public:
|
||||
mipLevels_ = desc.mipLevels;
|
||||
}
|
||||
|
||||
~MetalTexture() {
|
||||
rm_->DeferRelease(texture);
|
||||
}
|
||||
|
||||
id<MTLTexture> texture;
|
||||
int mipLevels_ = 1;
|
||||
uint64_t lastUsedFrame = (uint64_t)-1;
|
||||
|
||||
private:
|
||||
MetalRenderManager *rm_;
|
||||
};
|
||||
|
||||
class MetalFramebuffer : public Framebuffer {
|
||||
public:
|
||||
MetalFramebuffer(const FramebufferDesc &desc) : tag_(desc.tag ? desc.tag : "") {
|
||||
MetalFramebuffer(MetalRenderManager *rm, const FramebufferDesc &desc) : rm_(rm), tag_(desc.tag ? desc.tag : "") {
|
||||
width_ = desc.width;
|
||||
height_ = desc.height;
|
||||
layers_ = 1;
|
||||
@@ -350,9 +371,18 @@ public:
|
||||
return stencilView;
|
||||
}
|
||||
|
||||
~MetalFramebuffer() {
|
||||
rm_->DeferRelease(color);
|
||||
rm_->DeferRelease(depth);
|
||||
rm_->DeferRelease(stencilView);
|
||||
}
|
||||
|
||||
id<MTLTexture> color;
|
||||
id<MTLTexture> depth;
|
||||
id<MTLTexture> stencilView;
|
||||
|
||||
private:
|
||||
MetalRenderManager *rm_;
|
||||
std::string tag_;
|
||||
};
|
||||
|
||||
@@ -692,7 +722,7 @@ SamplerState *MetalDrawContext::CreateSamplerState(const SamplerStateDesc &desc)
|
||||
}
|
||||
sd.lodMinClamp = 0.0f;
|
||||
sd.lodMaxClamp = FLT_MAX;
|
||||
MetalSamplerState *ss = new MetalSamplerState();
|
||||
MetalSamplerState *ss = new MetalSamplerState(rm_);
|
||||
ss->sampler = [device_ newSamplerStateWithDescriptor:sd];
|
||||
return ss;
|
||||
}
|
||||
@@ -735,7 +765,7 @@ ShaderModule *MetalDrawContext::CreateShaderModule(ShaderStage stage, ShaderLang
|
||||
}
|
||||
|
||||
Pipeline *MetalDrawContext::CreateGraphicsPipeline(const PipelineDesc &desc, const char *tag) {
|
||||
MetalPipeline *pipeline = new MetalPipeline();
|
||||
MetalPipeline *pipeline = new MetalPipeline(rm_);
|
||||
pipeline->tag = tag;
|
||||
for (ShaderModule *module : desc.shaders) {
|
||||
MetalShaderModule *mm = (MetalShaderModule *)module;
|
||||
@@ -825,7 +855,7 @@ Texture *MetalDrawContext::CreateTexture(const TextureDesc &desc) {
|
||||
ERROR_LOG(Log::G3D, "Metal: Unsupported texture type %d", (int)desc.type);
|
||||
return nullptr;
|
||||
}
|
||||
MetalTexture *tex = new MetalTexture(desc);
|
||||
MetalTexture *tex = new MetalTexture(rm_, desc);
|
||||
bool generateMips = desc.generateMips && (GetDataFormatSupport(desc.format) & FMT_AUTOGEN_MIPS);
|
||||
int mipLevels = std::max(1, desc.mipLevels);
|
||||
if (desc.generateMips && mipLevels == 1) {
|
||||
@@ -904,7 +934,7 @@ void MetalDrawContext::UpdateTextureRegions(Texture *texture, int level, const T
|
||||
|
||||
Framebuffer *MetalDrawContext::CreateFramebuffer(const FramebufferDesc &desc) {
|
||||
_dbg_assert_(desc.numLayers == 1);
|
||||
MetalFramebuffer *fb = new MetalFramebuffer(desc);
|
||||
MetalFramebuffer *fb = new MetalFramebuffer(rm_, desc);
|
||||
MTLTextureDescriptor *td = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:desc.width height:desc.height mipmapped:NO];
|
||||
td.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
td.storageMode = MTLStorageModePrivate;
|
||||
@@ -1023,6 +1053,7 @@ bool MetalDrawContext::CopyFramebufferToMemory(Framebuffer *srcfb, Aspect aspect
|
||||
int srcStride = w * srcPixelSize;
|
||||
size_t size = (size_t)srcStride * h;
|
||||
if (!readbackBuffer_ || readbackBuffer_.length < size) {
|
||||
rm_->DeferRelease(readbackBuffer_);
|
||||
readbackBuffer_ = [device_ newBufferWithLength:std::max(size, (size_t)(1024 * 1024)) options:MTLResourceStorageModeShared];
|
||||
readbackBuffer_.label = @"readback";
|
||||
}
|
||||
|
||||
@@ -188,7 +188,10 @@ void DrawEngineMetal::DeviceRestore(Draw::DrawContext *draw) {
|
||||
}
|
||||
|
||||
void DrawEngineMetal::ClearPipelines() {
|
||||
pipelines_.Iterate([](const MetalPipelineKey &, Pipeline *pipeline) {
|
||||
pipelines_.Iterate([&](const MetalPipelineKey &, Pipeline *pipeline) {
|
||||
if (rm_) {
|
||||
rm_->DeferRelease(pipeline->pso);
|
||||
}
|
||||
delete pipeline;
|
||||
});
|
||||
pipelines_.Clear();
|
||||
|
||||
@@ -75,9 +75,11 @@ void TextureCacheMetal::DeviceRestore(Draw::DrawContext *draw) {
|
||||
|
||||
void TextureCacheMetal::ReleaseTexture(TexCacheEntry *entry, bool delete_them) {
|
||||
if (entry->texturePtr) {
|
||||
// Balances the __bridge_retained in BuildTexture. Command buffers that use it keep it alive as needed.
|
||||
// Balances the __bridge_retained in BuildTexture. The GPU may still be using it.
|
||||
id<MTLTexture> tex = (__bridge_transfer id<MTLTexture>)entry->texturePtr;
|
||||
tex = nil;
|
||||
if (rm_) {
|
||||
rm_->DeferRelease(tex);
|
||||
}
|
||||
entry->texturePtr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user