1//
2// Copyright 2020 Pixar
3//
4// Licensed under the Apache License, Version 2.0 (the "Apache License")
5// with the following modification; you may not use this file except in
6// compliance with the Apache License and the following modification to it:
7// Section 6. Trademarks. is deleted and replaced with:
8//
9// 6. Trademarks. This License does not grant permission to use the trade
10//    names, trademarks, service marks, or product names of the Licensor
11//    and its affiliates, except as required to comply with Section 4(c) of
12//    the License and to reproduce the content of the NOTICE file.
13//
14// You may obtain a copy of the Apache License at
15//
16//     http://www.apache.org/licenses/LICENSE-2.0
17//
18// Unless required by applicable law or agreed to in writing, software
19// distributed under the Apache License with the above modification is
20// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21// KIND, either express or implied. See the Apache License for the specific
22// language governing permissions and limitations under the Apache License.
23//
24#include "pxr/base/arch/defines.h"
25
26#include "pxr/imaging/hgiMetal/hgi.h"
27#include "pxr/imaging/hgiMetal/buffer.h"
28#include "pxr/imaging/hgiMetal/blitCmds.h"
29#include "pxr/imaging/hgiMetal/computeCmds.h"
30#include "pxr/imaging/hgiMetal/computePipeline.h"
31#include "pxr/imaging/hgiMetal/capabilities.h"
32#include "pxr/imaging/hgiMetal/conversions.h"
33#include "pxr/imaging/hgiMetal/diagnostic.h"
34#include "pxr/imaging/hgiMetal/graphicsCmds.h"
35#include "pxr/imaging/hgiMetal/graphicsPipeline.h"
36#include "pxr/imaging/hgiMetal/resourceBindings.h"
37#include "pxr/imaging/hgiMetal/sampler.h"
38#include "pxr/imaging/hgiMetal/shaderFunction.h"
39#include "pxr/imaging/hgiMetal/shaderProgram.h"
40#include "pxr/imaging/hgiMetal/texture.h"
41
42#include "pxr/base/trace/trace.h"
43
44#include "pxr/base/tf/getenv.h"
45#include "pxr/base/tf/registryManager.h"
46#include "pxr/base/tf/type.h"
47
48PXR_NAMESPACE_OPEN_SCOPE
49
50TF_REGISTRY_FUNCTION(TfType)
51{
52    TfType t = TfType::Define<HgiMetal, TfType::Bases<Hgi> >();
53    t.SetFactory<HgiFactory<HgiMetal>>();
54}
55
56static int _GetAPIVersion()
57{
58    if (@available(macOS 10.15, ios 13.0, *)) {
59        return APIVersion_Metal3_0;
60    }
61    if (@available(macOS 10.13, ios 11.0, *)) {
62        return APIVersion_Metal2_0;
63    }
64
65    return APIVersion_Metal1_0;
66}
67
68HgiMetal::HgiMetal(id<MTLDevice> device)
69: _device(device)
70, _currentCmds(nullptr)
71, _frameDepth(0)
72, _apiVersion(_GetAPIVersion())
73, _workToFlush(false)
74{
75    if (!_device) {
76        if( TfGetenvBool("HGIMETAL_USE_INTEGRATED_GPU", false)) {
77            _device = MTLCopyAllDevices()[1];
78        }
79
80        if (!_device) {
81            _device = MTLCreateSystemDefaultDevice();
82        }
83    }
84
85    static int const commandBufferPoolSize = 256;
86    _commandQueue = [_device newCommandQueueWithMaxCommandBufferCount:
87                     commandBufferPoolSize];
88    _commandBuffer = [_commandQueue commandBuffer];
89    [_commandBuffer retain];
90
91    _capabilities.reset(new HgiMetalCapabilities(_device));
92
93    HgiMetalSetupMetalDebug();
94
95    _captureScopeFullFrame =
96        [[MTLCaptureManager sharedCaptureManager]
97            newCaptureScopeWithDevice:_device];
98    _captureScopeFullFrame.label =
99        [NSString stringWithFormat:@"Full Hydra Frame"];
100
101    [[MTLCaptureManager sharedCaptureManager]
102        setDefaultCaptureScope:_captureScopeFullFrame];
103}
104
105HgiMetal::~HgiMetal()
106{
107    [_commandBuffer commit];
108    [_commandBuffer release];
109    [_captureScopeFullFrame release];
110    [_commandQueue release];
111}
112
113id<MTLDevice>
114HgiMetal::GetPrimaryDevice() const
115{
116    return _device;
117}
118
119HgiGraphicsCmdsUniquePtr
120HgiMetal::CreateGraphicsCmds(
121    HgiGraphicsCmdsDesc const& desc)
122{
123    // XXX We should TF_CODING_ERROR here when there are no attachments, but
124    // during the Hgi transition we allow it to render to global gl framebuffer.
125    if (!desc.HasAttachments()) {
126        // TF_CODING_ERROR("Graphics encoder desc has no attachments");
127        return nullptr;
128    }
129
130    HgiMetalGraphicsCmds* encoder(
131        new HgiMetalGraphicsCmds(this, desc));
132
133    return HgiGraphicsCmdsUniquePtr(encoder);
134}
135
136HgiComputeCmdsUniquePtr
137HgiMetal::CreateComputeCmds()
138{
139    HgiComputeCmds* computeCmds = new HgiMetalComputeCmds(this);
140    if (!_currentCmds) {
141        _currentCmds = computeCmds;
142    }
143    return HgiComputeCmdsUniquePtr(computeCmds);
144}
145
146HgiBlitCmdsUniquePtr
147HgiMetal::CreateBlitCmds()
148{
149    HgiMetalBlitCmds* blitCmds = new HgiMetalBlitCmds(this);
150    if (!_currentCmds) {
151        _currentCmds = blitCmds;
152    }
153    return HgiBlitCmdsUniquePtr(blitCmds);
154}
155
156HgiTextureHandle
157HgiMetal::CreateTexture(HgiTextureDesc const & desc)
158{
159    return HgiTextureHandle(new HgiMetalTexture(this, desc), GetUniqueId());
160}
161
162void
163HgiMetal::DestroyTexture(HgiTextureHandle* texHandle)
164{
165    _TrashObject(texHandle);
166}
167
168HgiTextureViewHandle
169HgiMetal::CreateTextureView(HgiTextureViewDesc const & desc)
170{
171    if (!desc.sourceTexture) {
172        TF_CODING_ERROR("Source texture is null");
173    }
174
175    HgiTextureHandle src =
176        HgiTextureHandle(new HgiMetalTexture(this, desc), GetUniqueId());
177    HgiTextureView* view = new HgiTextureView(desc);
178    view->SetViewTexture(src);
179    return HgiTextureViewHandle(view, GetUniqueId());
180}
181
182void
183HgiMetal::DestroyTextureView(HgiTextureViewHandle* viewHandle)
184{
185    // Trash the texture inside the view and invalidate the view handle.
186    HgiTextureHandle texHandle = (*viewHandle)->GetViewTexture();
187    _TrashObject(&texHandle);
188    (*viewHandle)->SetViewTexture(HgiTextureHandle());
189    delete viewHandle->Get();
190    *viewHandle = HgiTextureViewHandle();
191}
192
193HgiSamplerHandle
194HgiMetal::CreateSampler(HgiSamplerDesc const & desc)
195{
196    return HgiSamplerHandle(new HgiMetalSampler(this, desc), GetUniqueId());
197}
198
199void
200HgiMetal::DestroySampler(HgiSamplerHandle* smpHandle)
201{
202    _TrashObject(smpHandle);
203}
204
205HgiBufferHandle
206HgiMetal::CreateBuffer(HgiBufferDesc const & desc)
207{
208    return HgiBufferHandle(new HgiMetalBuffer(this, desc), GetUniqueId());
209}
210
211void
212HgiMetal::DestroyBuffer(HgiBufferHandle* bufHandle)
213{
214    _TrashObject(bufHandle);
215}
216
217HgiShaderFunctionHandle
218HgiMetal::CreateShaderFunction(HgiShaderFunctionDesc const& desc)
219{
220    return HgiShaderFunctionHandle(
221        new HgiMetalShaderFunction(this, desc), GetUniqueId());
222}
223
224void
225HgiMetal::DestroyShaderFunction(HgiShaderFunctionHandle* shaderFunctionHandle)
226{
227    _TrashObject(shaderFunctionHandle);
228}
229
230HgiShaderProgramHandle
231HgiMetal::CreateShaderProgram(HgiShaderProgramDesc const& desc)
232{
233    return HgiShaderProgramHandle(
234        new HgiMetalShaderProgram(desc), GetUniqueId());
235}
236
237void
238HgiMetal::DestroyShaderProgram(HgiShaderProgramHandle* shaderProgramHandle)
239{
240    _TrashObject(shaderProgramHandle);
241}
242
243
244HgiResourceBindingsHandle
245HgiMetal::CreateResourceBindings(HgiResourceBindingsDesc const& desc)
246{
247    return HgiResourceBindingsHandle(
248        new HgiMetalResourceBindings(desc), GetUniqueId());
249}
250
251void
252HgiMetal::DestroyResourceBindings(HgiResourceBindingsHandle* resHandle)
253{
254    _TrashObject(resHandle);
255}
256
257HgiGraphicsPipelineHandle
258HgiMetal::CreateGraphicsPipeline(HgiGraphicsPipelineDesc const& desc)
259{
260    return HgiGraphicsPipelineHandle(
261        new HgiMetalGraphicsPipeline(this, desc), GetUniqueId());
262}
263
264void
265HgiMetal::DestroyGraphicsPipeline(HgiGraphicsPipelineHandle* pipeHandle)
266{
267    _TrashObject(pipeHandle);
268}
269
270HgiComputePipelineHandle
271HgiMetal::CreateComputePipeline(HgiComputePipelineDesc const& desc)
272{
273    return HgiComputePipelineHandle(
274        new HgiMetalComputePipeline(this, desc), GetUniqueId());
275}
276
277void
278HgiMetal::DestroyComputePipeline(HgiComputePipelineHandle* pipeHandle)
279{
280    _TrashObject(pipeHandle);
281}
282
283TfToken const&
284HgiMetal::GetAPIName() const {
285    return HgiTokens->Metal;
286}
287
288HgiMetalCapabilities const*
289HgiMetal::GetCapabilities() const
290{
291    return _capabilities.get();
292}
293
294void
295HgiMetal::StartFrame()
296{
297    if (_frameDepth++ == 0) {
298        [_captureScopeFullFrame beginScope];
299
300        if ([[MTLCaptureManager sharedCaptureManager] isCapturing]) {
301            // We need to grab a new command buffer otherwise the previous one
302            // (if it was allocated at the end of the last frame) won't appear in
303            // this frame's capture, and it will confuse us!
304            CommitPrimaryCommandBuffer(CommitCommandBuffer_NoWait, true);
305        }
306    }
307}
308
309void
310HgiMetal::EndFrame()
311{
312    if (--_frameDepth == 0) {
313        [_captureScopeFullFrame endScope];
314    }
315}
316
317id<MTLCommandQueue>
318HgiMetal::GetQueue() const
319{
320    return _commandQueue;
321}
322
323id<MTLCommandBuffer>
324HgiMetal::GetPrimaryCommandBuffer(bool flush)
325{
326    if (_workToFlush) {
327        if (_currentCmds) {
328            return nil;
329        }
330    }
331    if (flush) {
332        _workToFlush = true;
333    }
334    return _commandBuffer;
335}
336
337id<MTLCommandBuffer>
338HgiMetal::GetSecondaryCommandBuffer()
339{
340    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
341    [commandBuffer retain];
342    return commandBuffer;
343}
344
345int
346HgiMetal::GetAPIVersion() const
347{
348    return _apiVersion;
349}
350
351void
352HgiMetal::CommitPrimaryCommandBuffer(CommitCommandBufferWaitType waitType,
353                              bool forceNewBuffer)
354{
355    if (!_workToFlush && !forceNewBuffer) {
356        return;
357    }
358
359    CommitSecondaryCommandBuffer(_commandBuffer, waitType);
360    [_commandBuffer release];
361    _commandBuffer = [_commandQueue commandBuffer];
362    [_commandBuffer retain];
363
364    _workToFlush = false;
365}
366
367void
368HgiMetal::CommitSecondaryCommandBuffer(
369    id<MTLCommandBuffer> commandBuffer,
370    CommitCommandBufferWaitType waitType)
371{
372    [commandBuffer commit];
373    if (waitType == CommitCommandBuffer_WaitUntilScheduled) {
374        [commandBuffer waitUntilScheduled];
375    }
376    else if (waitType == CommitCommandBuffer_WaitUntilCompleted) {
377        [commandBuffer waitUntilCompleted];
378    }
379}
380
381void
382HgiMetal::ReleaseSecondaryCommandBuffer(id<MTLCommandBuffer> commandBuffer)
383{
384    [commandBuffer release];
385}
386
387bool
388HgiMetal::_SubmitCmds(HgiCmds* cmds, HgiSubmitWaitType wait)
389{
390    TRACE_FUNCTION();
391
392    if (cmds) {
393        _workToFlush = Hgi::_SubmitCmds(cmds, wait);
394        if (cmds == _currentCmds) {
395            _currentCmds = nullptr;
396        }
397    }
398
399    return _workToFlush;
400}
401
402PXR_NAMESPACE_CLOSE_SCOPE
403