• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2// Copyright 2019 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6// mtl_resources.mm:
7//    Implements wrapper classes for Metal's MTLTexture and MTLBuffer.
8//
9
10#include "libANGLE/renderer/metal/mtl_resources.h"
11
12#include <TargetConditionals.h>
13
14#include <algorithm>
15
16#include "common/debug.h"
17#include "libANGLE/renderer/metal/ContextMtl.h"
18#include "libANGLE/renderer/metal/DisplayMtl.h"
19#include "libANGLE/renderer/metal/mtl_command_buffer.h"
20#include "libANGLE/renderer/metal/mtl_format_utils.h"
21
22namespace rx
23{
24namespace mtl
25{
26namespace
27{
28inline NSUInteger GetMipSize(NSUInteger baseSize, NSUInteger level)
29{
30    return std::max<NSUInteger>(1, baseSize >> level);
31}
32
33void SetTextureSwizzle(ContextMtl *context,
34                       const Format &format,
35                       MTLTextureDescriptor *textureDescOut)
36{
37// Texture swizzle functions's declarations are only available if macos 10.15 sdk is present
38#if defined(__MAC_10_15)
39    if (context->getDisplay()->getFeatures().hasTextureSwizzle.enabled)
40    {
41        // Work around Metal doesn't have native support for DXT1 without alpha.
42        switch (format.intendedFormatId)
43        {
44            case angle::FormatID::BC1_RGB_UNORM_BLOCK:
45            case angle::FormatID::BC1_RGB_UNORM_SRGB_BLOCK:
46                textureDescOut.swizzle =
47                    MTLTextureSwizzleChannelsMake(MTLTextureSwizzleRed, MTLTextureSwizzleGreen,
48                                                  MTLTextureSwizzleBlue, MTLTextureSwizzleOne);
49                break;
50            default:
51                break;
52        }
53    }
54#endif
55}
56}  // namespace
57// Resource implementation
58Resource::Resource() : mUsageRef(std::make_shared<UsageRef>()) {}
59
60// Share the GPU usage ref with other resource
61Resource::Resource(Resource *other) : mUsageRef(other->mUsageRef)
62{
63    ASSERT(mUsageRef);
64}
65
66bool Resource::isBeingUsedByGPU(Context *context) const
67{
68    return context->cmdQueue().isResourceBeingUsedByGPU(this);
69}
70
71void Resource::setUsedByCommandBufferWithQueueSerial(uint64_t serial, bool writing)
72{
73    auto curSerial = mUsageRef->cmdBufferQueueSerial.load(std::memory_order_relaxed);
74    do
75    {
76        if (curSerial >= serial)
77        {
78            return;
79        }
80    } while (!mUsageRef->cmdBufferQueueSerial.compare_exchange_weak(
81        curSerial, serial, std::memory_order_release, std::memory_order_relaxed));
82
83    // NOTE(hqle): This is not thread safe, if multiple command buffers on multiple threads
84    // are writing to it.
85    if (writing)
86    {
87        mUsageRef->cpuReadMemDirty = true;
88    }
89}
90
91// Texture implemenetation
92/** static */
93angle::Result Texture::Make2DTexture(ContextMtl *context,
94                                     const Format &format,
95                                     uint32_t width,
96                                     uint32_t height,
97                                     uint32_t mips,
98                                     bool renderTargetOnly,
99                                     bool allowTextureView,
100                                     TextureRef *refOut)
101{
102    ANGLE_MTL_OBJC_SCOPE
103    {
104        MTLTextureDescriptor *desc =
105            [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format.metalFormat
106                                                               width:width
107                                                              height:height
108                                                           mipmapped:mips == 0 || mips > 1];
109
110        SetTextureSwizzle(context, format, desc);
111        refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView));
112    }  // ANGLE_MTL_OBJC_SCOPE
113
114    if (!refOut || !refOut->get())
115    {
116        ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
117    }
118
119    return angle::Result::Continue;
120}
121
122/** static */
123angle::Result Texture::MakeCubeTexture(ContextMtl *context,
124                                       const Format &format,
125                                       uint32_t size,
126                                       uint32_t mips,
127                                       bool renderTargetOnly,
128                                       bool allowTextureView,
129                                       TextureRef *refOut)
130{
131    ANGLE_MTL_OBJC_SCOPE
132    {
133        MTLTextureDescriptor *desc =
134            [MTLTextureDescriptor textureCubeDescriptorWithPixelFormat:format.metalFormat
135                                                                  size:size
136                                                             mipmapped:mips == 0 || mips > 1];
137        SetTextureSwizzle(context, format, desc);
138        refOut->reset(new Texture(context, desc, mips, renderTargetOnly, allowTextureView));
139    }  // ANGLE_MTL_OBJC_SCOPE
140
141    if (!refOut || !refOut->get())
142    {
143        ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
144    }
145
146    return angle::Result::Continue;
147}
148
149/** static */
150TextureRef Texture::MakeFromMetal(id<MTLTexture> metalTexture)
151{
152    ANGLE_MTL_OBJC_SCOPE { return TextureRef(new Texture(metalTexture)); }
153}
154
155Texture::Texture(id<MTLTexture> metalTexture)
156    : mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll))
157{
158    set(metalTexture);
159}
160
161Texture::Texture(ContextMtl *context,
162                 MTLTextureDescriptor *desc,
163                 uint32_t mips,
164                 bool renderTargetOnly,
165                 bool supportTextureView)
166    : mColorWritableMask(std::make_shared<MTLColorWriteMask>(MTLColorWriteMaskAll))
167{
168    ANGLE_MTL_OBJC_SCOPE
169    {
170        id<MTLDevice> metalDevice = context->getMetalDevice();
171
172        if (mips > 1 && mips < desc.mipmapLevelCount)
173        {
174            desc.mipmapLevelCount = mips;
175        }
176
177        // Every texture will support being rendered for now
178        desc.usage = 0;
179
180        if (Format::FormatRenderable(desc.pixelFormat))
181        {
182            desc.usage |= MTLTextureUsageRenderTarget;
183        }
184
185        if (!Format::FormatCPUReadable(desc.pixelFormat))
186        {
187            desc.resourceOptions = MTLResourceStorageModePrivate;
188        }
189
190        if (!renderTargetOnly)
191        {
192            desc.usage = desc.usage | MTLTextureUsageShaderRead;
193        }
194
195        if (supportTextureView)
196        {
197            desc.usage = desc.usage | MTLTextureUsagePixelFormatView;
198        }
199
200        set([[metalDevice newTextureWithDescriptor:desc] ANGLE_MTL_AUTORELEASE]);
201    }
202}
203
204Texture::Texture(Texture *original, MTLPixelFormat format)
205    : Resource(original),
206      mColorWritableMask(original->mColorWritableMask)  // Share color write mask property
207{
208    ANGLE_MTL_OBJC_SCOPE
209    {
210        auto view = [original->get() newTextureViewWithPixelFormat:format];
211
212        set([view ANGLE_MTL_AUTORELEASE]);
213    }
214}
215
216Texture::Texture(Texture *original, MTLTextureType type, NSRange mipmapLevelRange, uint32_t slice)
217    : Resource(original),
218      mColorWritableMask(original->mColorWritableMask)  // Share color write mask property
219{
220    ANGLE_MTL_OBJC_SCOPE
221    {
222        auto view = [original->get() newTextureViewWithPixelFormat:original->pixelFormat()
223                                                       textureType:type
224                                                            levels:mipmapLevelRange
225                                                            slices:NSMakeRange(slice, 1)];
226
227        set([view ANGLE_MTL_AUTORELEASE]);
228    }
229}
230
231void Texture::syncContent(ContextMtl *context, mtl::BlitCommandEncoder *blitEncoder)
232{
233#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
234    if (blitEncoder)
235    {
236        blitEncoder->synchronizeResource(shared_from_this());
237    }
238#endif
239}
240
241void Texture::syncContent(ContextMtl *context)
242{
243#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
244    // Make sure GPU & CPU contents are synchronized.
245    // NOTE: Only MacOS has separated storage for resource on CPU and GPU and needs explicit
246    // synchronization
247    if (this->isCPUReadMemDirty())
248    {
249        mtl::BlitCommandEncoder *blitEncoder = context->getBlitCommandEncoder();
250        syncContent(context, blitEncoder);
251
252        this->resetCPUReadMemDirty();
253    }
254#endif
255}
256
257bool Texture::isCPUAccessible() const
258{
259#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
260    if (get().storageMode == MTLStorageModeManaged)
261    {
262        return true;
263    }
264#endif
265    return get().storageMode == MTLStorageModeShared;
266}
267
268void Texture::replaceRegion(ContextMtl *context,
269                            MTLRegion region,
270                            uint32_t mipmapLevel,
271                            uint32_t slice,
272                            const uint8_t *data,
273                            size_t bytesPerRow)
274{
275    if (mipmapLevel >= this->mipmapLevels())
276    {
277        return;
278    }
279
280    ASSERT(isCPUAccessible());
281
282    CommandQueue &cmdQueue = context->cmdQueue();
283
284    syncContent(context);
285
286    // NOTE(hqle): what if multiple contexts on multiple threads are using this texture?
287    if (this->isBeingUsedByGPU(context))
288    {
289        context->flushCommandBufer();
290    }
291
292    cmdQueue.ensureResourceReadyForCPU(this);
293
294    [get() replaceRegion:region
295             mipmapLevel:mipmapLevel
296                   slice:slice
297               withBytes:data
298             bytesPerRow:bytesPerRow
299           bytesPerImage:0];
300}
301
302void Texture::getBytes(ContextMtl *context,
303                       size_t bytesPerRow,
304                       MTLRegion region,
305                       uint32_t mipmapLevel,
306                       uint8_t *dataOut)
307{
308    ASSERT(isCPUAccessible());
309
310    CommandQueue &cmdQueue = context->cmdQueue();
311
312    syncContent(context);
313
314    // NOTE(hqle): what if multiple contexts on multiple threads are using this texture?
315    if (this->isBeingUsedByGPU(context))
316    {
317        context->flushCommandBufer();
318    }
319
320    cmdQueue.ensureResourceReadyForCPU(this);
321
322    [get() getBytes:dataOut bytesPerRow:bytesPerRow fromRegion:region mipmapLevel:mipmapLevel];
323}
324
325TextureRef Texture::createCubeFaceView(uint32_t face)
326{
327    ANGLE_MTL_OBJC_SCOPE
328    {
329        switch (textureType())
330        {
331            case MTLTextureTypeCube:
332                return TextureRef(
333                    new Texture(this, MTLTextureType2D, NSMakeRange(0, mipmapLevels()), face));
334            default:
335                UNREACHABLE();
336                return nullptr;
337        }
338    }
339}
340
341TextureRef Texture::createSliceMipView(uint32_t slice, uint32_t level)
342{
343    ANGLE_MTL_OBJC_SCOPE
344    {
345        switch (textureType())
346        {
347            case MTLTextureTypeCube:
348            case MTLTextureType2D:
349                return TextureRef(
350                    new Texture(this, MTLTextureType2D, NSMakeRange(level, 1), slice));
351            default:
352                UNREACHABLE();
353                return nullptr;
354        }
355    }
356}
357
358TextureRef Texture::createViewWithDifferentFormat(MTLPixelFormat format)
359{
360    return TextureRef(new Texture(this, format));
361}
362
363MTLPixelFormat Texture::pixelFormat() const
364{
365    return get().pixelFormat;
366}
367
368MTLTextureType Texture::textureType() const
369{
370    return get().textureType;
371}
372
373uint32_t Texture::mipmapLevels() const
374{
375    return static_cast<uint32_t>(get().mipmapLevelCount);
376}
377
378uint32_t Texture::width(uint32_t level) const
379{
380    return static_cast<uint32_t>(GetMipSize(get().width, level));
381}
382
383uint32_t Texture::height(uint32_t level) const
384{
385    return static_cast<uint32_t>(GetMipSize(get().height, level));
386}
387
388gl::Extents Texture::size(uint32_t level) const
389{
390    gl::Extents re;
391
392    re.width  = width(level);
393    re.height = height(level);
394    re.depth  = static_cast<uint32_t>(GetMipSize(get().depth, level));
395
396    return re;
397}
398
399gl::Extents Texture::size(const gl::ImageIndex &index) const
400{
401    // Only support these texture types for now
402    ASSERT(!get() || textureType() == MTLTextureType2D || textureType() == MTLTextureTypeCube);
403
404    return size(index.getLevelIndex());
405}
406
407void Texture::set(id<MTLTexture> metalTexture)
408{
409    ParentClass::set(metalTexture);
410}
411
412// Buffer implementation
413angle::Result Buffer::MakeBuffer(ContextMtl *context,
414                                 size_t size,
415                                 const uint8_t *data,
416                                 BufferRef *bufferOut)
417{
418    bufferOut->reset(new Buffer(context, size, data));
419
420    if (!bufferOut || !bufferOut->get())
421    {
422        ANGLE_MTL_CHECK(context, false, GL_OUT_OF_MEMORY);
423    }
424
425    return angle::Result::Continue;
426}
427
428Buffer::Buffer(ContextMtl *context, size_t size, const uint8_t *data)
429{
430    (void)reset(context, size, data);
431}
432
433angle::Result Buffer::reset(ContextMtl *context, size_t size, const uint8_t *data)
434{
435    ANGLE_MTL_OBJC_SCOPE
436    {
437        MTLResourceOptions options;
438
439        id<MTLBuffer> newBuffer;
440        id<MTLDevice> metalDevice = context->getMetalDevice();
441
442        options = 0;
443#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
444        options |= MTLResourceStorageModeManaged;
445#endif
446
447        if (data)
448        {
449            newBuffer = [metalDevice newBufferWithBytes:data length:size options:options];
450        }
451        else
452        {
453            newBuffer = [metalDevice newBufferWithLength:size options:options];
454        }
455
456        set([newBuffer ANGLE_MTL_AUTORELEASE]);
457
458        return angle::Result::Continue;
459    }
460}
461
462uint8_t *Buffer::map(ContextMtl *context)
463{
464    CommandQueue &cmdQueue = context->cmdQueue();
465
466    // NOTE(hqle): what if multiple contexts on multiple threads are using this buffer?
467    if (this->isBeingUsedByGPU(context))
468    {
469        context->flushCommandBufer();
470    }
471
472    // NOTE(hqle): currently not support reading data written by GPU
473    cmdQueue.ensureResourceReadyForCPU(this);
474
475    return reinterpret_cast<uint8_t *>([get() contents]);
476}
477
478void Buffer::unmap(ContextMtl *context)
479{
480#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
481    [get() didModifyRange:NSMakeRange(0, size())];
482#endif
483}
484
485size_t Buffer::size() const
486{
487    return get().length;
488}
489}
490}
491