src/render/metal/SDL_render_metal.m
author Alex Szpakowski <slime73@gmail.com>
Thu, 01 Nov 2018 20:24:21 -0300
changeset 12385 45038f8422c9
parent 12384 b1f5162fd621
child 12392 046dcbdbaa74
permissions -rw-r--r--
metal: avoid an extra buffer allocation and GPU data copy in RunCommandQueue, it's not needed. Improves overall performance.
icculus@11729
     1
/*
icculus@11729
     2
  Simple DirectMedia Layer
slouken@11811
     3
  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
icculus@11729
     4
icculus@11729
     5
  This software is provided 'as-is', without any express or implied
icculus@11729
     6
  warranty.  In no event will the authors be held liable for any damages
icculus@11729
     7
  arising from the use of this software.
icculus@11729
     8
icculus@11729
     9
  Permission is granted to anyone to use this software for any purpose,
icculus@11729
    10
  including commercial applications, and to alter it and redistribute it
icculus@11729
    11
  freely, subject to the following restrictions:
icculus@11729
    12
icculus@11729
    13
  1. The origin of this software must not be misrepresented; you must not
icculus@11729
    14
     claim that you wrote the original software. If you use this software
icculus@11729
    15
     in a product, an acknowledgment in the product documentation would be
icculus@11729
    16
     appreciated but is not required.
icculus@11729
    17
  2. Altered source versions must be plainly marked as such, and must not be
icculus@11729
    18
     misrepresented as being the original software.
icculus@11729
    19
  3. This notice may not be removed or altered from any source distribution.
icculus@11729
    20
*/
icculus@11729
    21
#include "../../SDL_internal.h"
icculus@11729
    22
icculus@11729
    23
#if SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED
icculus@11729
    24
icculus@11729
    25
#include "SDL_hints.h"
icculus@11729
    26
#include "SDL_log.h"
icculus@11729
    27
#include "SDL_assert.h"
icculus@11729
    28
#include "SDL_syswm.h"
icculus@11729
    29
#include "../SDL_sysrender.h"
icculus@11729
    30
slouken@11732
    31
#ifdef __MACOSX__
slime73@11798
    32
#include "../../video/cocoa/SDL_cocoametalview.h"
slouken@11732
    33
#else
slouken@11732
    34
#include "../../video/uikit/SDL_uikitmetalview.h"
slouken@11732
    35
#endif
slime73@11821
    36
#include <Availability.h>
slime73@11798
    37
#import <Metal/Metal.h>
slime73@11798
    38
#import <QuartzCore/CAMetalLayer.h>
icculus@11729
    39
slouken@11730
    40
/* Regenerate these with build-metal-shaders.sh */
slouken@11730
    41
#ifdef __MACOSX__
slouken@11730
    42
#include "SDL_shaders_metal_osx.h"
slouken@11730
    43
#else
slouken@11730
    44
#include "SDL_shaders_metal_ios.h"
slouken@11730
    45
#endif
icculus@11729
    46
icculus@11729
    47
/* Apple Metal renderer implementation */
icculus@11729
    48
slime73@11810
    49
/* macOS requires constants in a buffer to have a 256 byte alignment. */
slime73@11810
    50
#ifdef __MACOSX__
slime73@11810
    51
#define CONSTANT_ALIGN 256
slime73@11810
    52
#else
slime73@11810
    53
#define CONSTANT_ALIGN 4
slime73@11810
    54
#endif
slime73@11810
    55
slime73@11810
    56
#define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1)))
slime73@11810
    57
icculus@12213
    58
static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF;
slime73@11810
    59
static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
slime73@11810
    60
static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
slime73@11819
    61
static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
slime73@11819
    62
static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4);
slime73@11819
    63
static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4);
slime73@12384
    64
static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 6;
slime73@11810
    65
slime73@11800
    66
typedef enum SDL_MetalVertexFunction
slime73@11800
    67
{
slime73@11800
    68
    SDL_METAL_VERTEX_SOLID,
slime73@11800
    69
    SDL_METAL_VERTEX_COPY,
slime73@11800
    70
} SDL_MetalVertexFunction;
slime73@11800
    71
slime73@11800
    72
typedef enum SDL_MetalFragmentFunction
slime73@11800
    73
{
slime73@11819
    74
    SDL_METAL_FRAGMENT_SOLID = 0,
slime73@11801
    75
    SDL_METAL_FRAGMENT_COPY,
slime73@11819
    76
    SDL_METAL_FRAGMENT_YUV,
slime73@11819
    77
    SDL_METAL_FRAGMENT_NV12,
slime73@11819
    78
    SDL_METAL_FRAGMENT_NV21,
slime73@11819
    79
    SDL_METAL_FRAGMENT_COUNT,
slime73@11800
    80
} SDL_MetalFragmentFunction;
slime73@11800
    81
slime73@11800
    82
typedef struct METAL_PipelineState
slime73@11800
    83
{
slime73@11800
    84
    SDL_BlendMode blendMode;
slime73@11800
    85
    void *pipe;
slime73@11800
    86
} METAL_PipelineState;
slime73@11800
    87
slime73@11800
    88
typedef struct METAL_PipelineCache
slime73@11800
    89
{
slime73@11800
    90
    METAL_PipelineState *states;
slime73@11800
    91
    int count;
slime73@11800
    92
    SDL_MetalVertexFunction vertexFunction;
slime73@11800
    93
    SDL_MetalFragmentFunction fragmentFunction;
slime73@11820
    94
    MTLPixelFormat renderTargetFormat;
slime73@11800
    95
    const char *label;
slime73@11800
    96
} METAL_PipelineCache;
slime73@11800
    97
slime73@11819
    98
/* Each shader combination used by drawing functions has a separate pipeline
slime73@11820
    99
 * cache, and we have a separate list of caches for each render target pixel
slime73@11820
   100
 * format. This is more efficient than iterating over a global cache to find
slime73@11820
   101
 * the pipeline based on the specified shader combination and RT pixel format,
slime73@11820
   102
 * since we know what the RT pixel format is when we set the render target, and
slime73@11820
   103
 * we know what the shader combination is inside each drawing function's code. */
slime73@11819
   104
typedef struct METAL_ShaderPipelines
slime73@11819
   105
{
slime73@11820
   106
    MTLPixelFormat renderTargetFormat;
slime73@11819
   107
    METAL_PipelineCache caches[SDL_METAL_FRAGMENT_COUNT];
slime73@11819
   108
} METAL_ShaderPipelines;
slime73@11819
   109
slouken@11732
   110
@interface METAL_RenderData : NSObject
slouken@11735
   111
    @property (nonatomic, retain) id<MTLDevice> mtldevice;
slouken@11735
   112
    @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
slouken@11735
   113
    @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
slouken@11735
   114
    @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
slouken@11735
   115
    @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
slouken@11735
   116
    @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
slime73@11801
   117
    @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest;
slime73@11801
   118
    @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear;
slime73@11810
   119
    @property (nonatomic, retain) id<MTLBuffer> mtlbufconstants;
slouken@11735
   120
    @property (nonatomic, retain) CAMetalLayer *mtllayer;
slouken@11735
   121
    @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
slime73@11820
   122
    @property (nonatomic, assign) METAL_ShaderPipelines *activepipelines;
slime73@11820
   123
    @property (nonatomic, assign) METAL_ShaderPipelines *allpipelines;
slime73@11820
   124
    @property (nonatomic, assign) int pipelinescount;
slouken@11732
   125
@end
icculus@11729
   126
slouken@11732
   127
@implementation METAL_RenderData
slime73@11805
   128
#if !__has_feature(objc_arc)
slime73@11804
   129
- (void)dealloc
slime73@11804
   130
{
slime73@11804
   131
    [_mtldevice release];
slime73@11804
   132
    [_mtlcmdqueue release];
slime73@11804
   133
    [_mtlcmdbuffer release];
slime73@11804
   134
    [_mtlcmdencoder release];
slime73@11804
   135
    [_mtllibrary release];
slime73@11804
   136
    [_mtlbackbuffer release];
slime73@11804
   137
    [_mtlsamplernearest release];
slime73@11804
   138
    [_mtlsamplerlinear release];
slime73@11810
   139
    [_mtlbufconstants release];
slime73@11804
   140
    [_mtllayer release];
slime73@11804
   141
    [_mtlpassdesc release];
slime73@11804
   142
    [super dealloc];
slime73@11804
   143
}
slime73@11804
   144
#endif
slouken@11732
   145
@end
icculus@11729
   146
slouken@11753
   147
@interface METAL_TextureData : NSObject
slouken@11753
   148
    @property (nonatomic, retain) id<MTLTexture> mtltexture;
slime73@11819
   149
    @property (nonatomic, retain) id<MTLTexture> mtltexture_uv;
slime73@11801
   150
    @property (nonatomic, retain) id<MTLSamplerState> mtlsampler;
slime73@11819
   151
    @property (nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction;
slime73@11819
   152
    @property (nonatomic, assign) BOOL yuv;
slime73@11819
   153
    @property (nonatomic, assign) BOOL nv12;
slime73@11819
   154
    @property (nonatomic, assign) size_t conversionBufferOffset;
slouken@11753
   155
@end
slouken@11753
   156
slouken@11753
   157
@implementation METAL_TextureData
slime73@11805
   158
#if !__has_feature(objc_arc)
slime73@11804
   159
- (void)dealloc
slime73@11804
   160
{
slime73@11804
   161
    [_mtltexture release];
slime73@11819
   162
    [_mtltexture_uv release];
slime73@11804
   163
    [_mtlsampler release];
slime73@11804
   164
    [super dealloc];
slime73@11804
   165
}
slime73@11804
   166
#endif
slouken@11753
   167
@end
slouken@11753
   168
icculus@11729
   169
static int
icculus@11729
   170
IsMetalAvailable(const SDL_SysWMinfo *syswm)
icculus@11729
   171
{
slouken@11733
   172
    if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
slouken@11733
   173
        return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
icculus@11729
   174
    }
icculus@11729
   175
icculus@11729
   176
    // this checks a weak symbol.
icculus@11742
   177
#if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
icculus@11729
   178
    if (MTLCreateSystemDefaultDevice == NULL) {  // probably on 10.10 or lower.
icculus@11729
   179
        return SDL_SetError("Metal framework not available on this system");
icculus@11729
   180
    }
icculus@11729
   181
#endif
icculus@11729
   182
icculus@11729
   183
    return 0;
icculus@11729
   184
}
icculus@11729
   185
slime73@11800
   186
static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF;
slime73@11800
   187
static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF;
slime73@11800
   188
slime73@11800
   189
static MTLBlendOperation
slime73@11800
   190
GetBlendOperation(SDL_BlendOperation operation)
slime73@11800
   191
{
slime73@11800
   192
    switch (operation) {
slime73@11800
   193
        case SDL_BLENDOPERATION_ADD: return MTLBlendOperationAdd;
slime73@11800
   194
        case SDL_BLENDOPERATION_SUBTRACT: return MTLBlendOperationSubtract;
slime73@11800
   195
        case SDL_BLENDOPERATION_REV_SUBTRACT: return MTLBlendOperationReverseSubtract;
slime73@11800
   196
        case SDL_BLENDOPERATION_MINIMUM: return MTLBlendOperationMin;
slime73@11800
   197
        case SDL_BLENDOPERATION_MAXIMUM: return MTLBlendOperationMax;
slime73@11800
   198
        default: return invalidBlendOperation;
slime73@11800
   199
    }
slime73@11800
   200
}
slime73@11800
   201
slime73@11800
   202
static MTLBlendFactor
slime73@11800
   203
GetBlendFactor(SDL_BlendFactor factor)
slime73@11800
   204
{
slime73@11800
   205
    switch (factor) {
slime73@11800
   206
        case SDL_BLENDFACTOR_ZERO: return MTLBlendFactorZero;
slime73@11800
   207
        case SDL_BLENDFACTOR_ONE: return MTLBlendFactorOne;
slime73@11800
   208
        case SDL_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
slime73@11800
   209
        case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
slime73@11800
   210
        case SDL_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
slime73@11800
   211
        case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
slime73@11800
   212
        case SDL_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
slime73@11800
   213
        case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
slime73@11800
   214
        case SDL_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
slime73@11800
   215
        case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
slime73@11800
   216
        default: return invalidBlendFactor;
slime73@11800
   217
    }
slime73@11800
   218
}
slime73@11800
   219
slime73@11800
   220
static NSString *
slime73@11800
   221
GetVertexFunctionName(SDL_MetalVertexFunction function)
slime73@11800
   222
{
slime73@11800
   223
    switch (function) {
slime73@11800
   224
        case SDL_METAL_VERTEX_SOLID: return @"SDL_Solid_vertex";
slime73@11800
   225
        case SDL_METAL_VERTEX_COPY: return @"SDL_Copy_vertex";
slime73@11800
   226
        default: return nil;
slime73@11800
   227
    }
slime73@11800
   228
}
slime73@11800
   229
slime73@11800
   230
static NSString *
slime73@11800
   231
GetFragmentFunctionName(SDL_MetalFragmentFunction function)
slime73@11800
   232
{
slime73@11800
   233
    switch (function) {
slime73@11800
   234
        case SDL_METAL_FRAGMENT_SOLID: return @"SDL_Solid_fragment";
slime73@11801
   235
        case SDL_METAL_FRAGMENT_COPY: return @"SDL_Copy_fragment";
slime73@11819
   236
        case SDL_METAL_FRAGMENT_YUV: return @"SDL_YUV_fragment";
slime73@11819
   237
        case SDL_METAL_FRAGMENT_NV12: return @"SDL_NV12_fragment";
slime73@11819
   238
        case SDL_METAL_FRAGMENT_NV21: return @"SDL_NV21_fragment";
slime73@11800
   239
        default: return nil;
slime73@11800
   240
    }
slime73@11800
   241
}
slime73@11800
   242
icculus@11729
   243
static id<MTLRenderPipelineState>
slime73@11800
   244
MakePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache,
slime73@11800
   245
                  NSString *blendlabel, SDL_BlendMode blendmode)
icculus@11729
   246
{
slime73@11800
   247
    id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)];
slime73@11800
   248
    id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)];
icculus@11729
   249
    SDL_assert(mtlvertfn != nil);
icculus@11729
   250
    SDL_assert(mtlfragfn != nil);
icculus@11729
   251
icculus@11729
   252
    MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
icculus@11729
   253
    mtlpipedesc.vertexFunction = mtlvertfn;
icculus@11729
   254
    mtlpipedesc.fragmentFunction = mtlfragfn;
icculus@11729
   255
slime73@11800
   256
    MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0];
icculus@11729
   257
slime73@11820
   258
    rtdesc.pixelFormat = cache->renderTargetFormat;
icculus@11729
   259
slime73@11800
   260
    if (blendmode != SDL_BLENDMODE_NONE) {
slime73@11800
   261
        rtdesc.blendingEnabled = YES;
slime73@11800
   262
        rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode));
slime73@11800
   263
        rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode));
slime73@11800
   264
        rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode));
slime73@11800
   265
        rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode));
slime73@11800
   266
        rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode));
slime73@11800
   267
        rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode));
slime73@11800
   268
    } else {
slime73@11800
   269
        rtdesc.blendingEnabled = NO;
icculus@11729
   270
    }
icculus@11729
   271
slime73@11800
   272
    mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel];
icculus@11729
   273
icculus@11729
   274
    NSError *err = nil;
slime73@11800
   275
    id<MTLRenderPipelineState> state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
icculus@11729
   276
    SDL_assert(err == nil);
slime73@11800
   277
slime73@11800
   278
    METAL_PipelineState pipeline;
slime73@11800
   279
    pipeline.blendMode = blendmode;
slime73@11800
   280
    pipeline.pipe = (void *)CFBridgingRetain(state);
slime73@11800
   281
slime73@11800
   282
    METAL_PipelineState *states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline));
slime73@11800
   283
slouken@11732
   284
#if !__has_feature(objc_arc)
icculus@11729
   285
    [mtlpipedesc release];  // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
icculus@11729
   286
    [mtlvertfn release];
icculus@11729
   287
    [mtlfragfn release];
slime73@11800
   288
    [state release];
slouken@11732
   289
#endif
slime73@11800
   290
slime73@11800
   291
    if (states) {
slime73@11800
   292
        states[cache->count++] = pipeline;
slime73@11800
   293
        cache->states = states;
slime73@11800
   294
        return (__bridge id<MTLRenderPipelineState>)pipeline.pipe;
slime73@11800
   295
    } else {
slime73@11800
   296
        CFBridgingRelease(pipeline.pipe);
slime73@11800
   297
        SDL_OutOfMemory();
slime73@11800
   298
        return NULL;
slime73@11800
   299
    }
slime73@11800
   300
}
slime73@11800
   301
slime73@11819
   302
static void
slime73@11820
   303
MakePipelineCache(METAL_RenderData *data, METAL_PipelineCache *cache, const char *label,
slime73@11820
   304
                  MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn)
slime73@11800
   305
{
slime73@11800
   306
    SDL_zerop(cache);
slime73@11800
   307
slime73@11800
   308
    cache->vertexFunction = vertfn;
slime73@11800
   309
    cache->fragmentFunction = fragfn;
slime73@11820
   310
    cache->renderTargetFormat = rtformat;
slime73@11800
   311
    cache->label = label;
slime73@11800
   312
slime73@11800
   313
    /* Create pipeline states for the default blend modes. Custom blend modes
slime73@11800
   314
     * will be added to the cache on-demand. */
slime73@11819
   315
    MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE);
slime73@11819
   316
    MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND);
slime73@11819
   317
    MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD);
slime73@11819
   318
    MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD);
icculus@11729
   319
}
icculus@11729
   320
icculus@11729
   321
static void
slime73@11800
   322
DestroyPipelineCache(METAL_PipelineCache *cache)
icculus@11729
   323
{
slime73@11800
   324
    if (cache != NULL) {
slime73@11800
   325
        for (int i = 0; i < cache->count; i++) {
slime73@11800
   326
            CFBridgingRelease(cache->states[i].pipe);
slime73@11800
   327
        }
slime73@11800
   328
slime73@11800
   329
        SDL_free(cache->states);
slime73@11819
   330
    }
slime73@11819
   331
}
slime73@11819
   332
slime73@11820
   333
void
slime73@11820
   334
MakeShaderPipelines(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat)
slime73@11820
   335
{
slime73@11820
   336
    SDL_zerop(pipelines);
slime73@11820
   337
slime73@11820
   338
    pipelines->renderTargetFormat = rtformat;
slime73@11820
   339
slime73@11820
   340
    MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
slime73@11820
   341
    MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
slime73@11820
   342
    MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV);
slime73@11820
   343
    MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12);
slime73@11820
   344
    MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV21], "SDL NV21 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV21);
slime73@11820
   345
}
slime73@11820
   346
slime73@11819
   347
static METAL_ShaderPipelines *
slime73@11820
   348
ChooseShaderPipelines(METAL_RenderData *data, MTLPixelFormat rtformat)
slime73@11819
   349
{
slime73@11820
   350
    METAL_ShaderPipelines *allpipelines = data.allpipelines;
slime73@11820
   351
    int count = data.pipelinescount;
slime73@11820
   352
slime73@11820
   353
    for (int i = 0; i < count; i++) {
slime73@11820
   354
        if (allpipelines[i].renderTargetFormat == rtformat) {
slime73@11820
   355
            return &allpipelines[i];
slime73@11820
   356
        }
slime73@11820
   357
    }
slime73@11820
   358
slime73@11820
   359
    allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines));
slime73@11820
   360
slime73@11820
   361
    if (allpipelines == NULL) {
slime73@11819
   362
        SDL_OutOfMemory();
slime73@11819
   363
        return NULL;
slime73@11819
   364
    }
slime73@11819
   365
slime73@11820
   366
    MakeShaderPipelines(data, &allpipelines[count], rtformat);
slime73@11819
   367
slime73@11820
   368
    data.allpipelines = allpipelines;
slime73@11820
   369
    data.pipelinescount = count + 1;
slime73@11820
   370
slime73@11820
   371
    return &data.allpipelines[count];
slime73@11819
   372
}
slime73@11819
   373
slime73@11819
   374
static void
slime73@11820
   375
DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count)
slime73@11819
   376
{
slime73@11820
   377
    if (allpipelines != NULL) {
slime73@11820
   378
        for (int i = 0; i < count; i++) {
slime73@11820
   379
            for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) {
slime73@11820
   380
                DestroyPipelineCache(&allpipelines[i].caches[cache]);
slime73@11820
   381
            }
slime73@11819
   382
        }
slime73@11819
   383
slime73@11820
   384
        SDL_free(allpipelines);
slime73@11800
   385
    }
icculus@11729
   386
}
icculus@11729
   387
icculus@11729
   388
static inline id<MTLRenderPipelineState>
slime73@11819
   389
ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode)
icculus@11729
   390
{
slime73@11819
   391
    METAL_PipelineCache *cache = &pipelines->caches[fragfn];
slime73@11819
   392
slime73@11800
   393
    for (int i = 0; i < cache->count; i++) {
slime73@11800
   394
        if (cache->states[i].blendMode == blendmode) {
slime73@11800
   395
            return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe;
slime73@11800
   396
        }
icculus@11729
   397
    }
slime73@11800
   398
slime73@11819
   399
    return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
icculus@11729
   400
}
icculus@11729
   401
slime73@11804
   402
static void
icculus@12213
   403
METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
slouken@11735
   404
{
slouken@11735
   405
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   406
slime73@11817
   407
    /* Our SetRenderTarget just signals that the next render operation should
slime73@11817
   408
     * set up a new render pass. This is where that work happens. */
slime73@11817
   409
    if (data.mtlcmdencoder == nil) {
slime73@11817
   410
        id<MTLTexture> mtltexture = nil;
slime73@11817
   411
slime73@11817
   412
        if (renderer->target != NULL) {
slime73@11817
   413
            METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
slime73@11817
   414
            mtltexture = texdata.mtltexture;
slime73@11817
   415
        } else {
slime73@11817
   416
            if (data.mtlbackbuffer == nil) {
slime73@11817
   417
                /* The backbuffer's contents aren't guaranteed to persist after
slime73@11817
   418
                 * presenting, so we can leave it undefined when loading it. */
slime73@11817
   419
                data.mtlbackbuffer = [data.mtllayer nextDrawable];
slime73@11817
   420
                if (load == MTLLoadActionLoad) {
slime73@11817
   421
                    load = MTLLoadActionDontCare;
slime73@11817
   422
                }
slime73@11817
   423
            }
slime73@11817
   424
            mtltexture = data.mtlbackbuffer.texture;
slime73@11817
   425
        }
slime73@11817
   426
slime73@11817
   427
        SDL_assert(mtltexture);
slime73@11817
   428
slime73@11817
   429
        if (load == MTLLoadActionClear) {
icculus@12213
   430
            SDL_assert(clear_color != NULL);
icculus@12213
   431
            data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
slime73@11817
   432
        }
slime73@11817
   433
slime73@11817
   434
        data.mtlpassdesc.colorAttachments[0].loadAction = load;
slime73@11817
   435
        data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
slime73@11817
   436
slouken@11735
   437
        data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
slouken@11735
   438
        data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
icculus@11729
   439
slime73@11817
   440
        if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
slime73@11817
   441
            data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
slime73@11817
   442
        } else {
slime73@11817
   443
            data.mtlcmdencoder.label = @"SDL metal renderer render target";
slime73@11817
   444
        }
slime73@11817
   445
slime73@11820
   446
        data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
icculus@12215
   447
icculus@12215
   448
        // make sure this has a definite place in the queue. This way it will
icculus@12215
   449
        //  execute reliably whether the app tries to make its own command buffers
icculus@12215
   450
        //  or whatever. This means we can _always_ batch rendering commands!
icculus@12215
   451
        [data.mtlcmdbuffer enqueue];
slouken@11735
   452
    }
icculus@11729
   453
}
icculus@11729
   454
icculus@11729
   455
static void
icculus@11729
   456
METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
icculus@11729
   457
{
icculus@12179
   458
    if (event->event == SDL_WINDOWEVENT_SHOWN ||
icculus@11729
   459
        event->event == SDL_WINDOWEVENT_HIDDEN) {
icculus@11729
   460
        // !!! FIXME: write me
icculus@11729
   461
    }
icculus@11729
   462
}
icculus@11729
   463
icculus@11729
   464
static int
icculus@11729
   465
METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
slouken@11743
   466
{ @autoreleasepool {
slime73@11798
   467
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slime73@11798
   468
    if (w) {
slime73@11798
   469
        *w = (int)data.mtllayer.drawableSize.width;
slime73@11798
   470
    }
slime73@11798
   471
    if (h) {
slime73@11798
   472
        *h = (int)data.mtllayer.drawableSize.height;
slime73@11798
   473
    }
icculus@11729
   474
    return 0;
slouken@11743
   475
}}
icculus@11729
   476
slime73@11800
   477
static SDL_bool
slime73@11800
   478
METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
slime73@11800
   479
{
slime73@11800
   480
    SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
slime73@11800
   481
    SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
slime73@11800
   482
    SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
slime73@11800
   483
    SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
slime73@11800
   484
    SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
slime73@11800
   485
    SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
slime73@11800
   486
slime73@11800
   487
    if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
slime73@11800
   488
        GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
slime73@11800
   489
        GetBlendOperation(colorOperation) == invalidBlendOperation ||
slime73@11800
   490
        GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
slime73@11800
   491
        GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
slime73@11800
   492
        GetBlendOperation(alphaOperation) == invalidBlendOperation) {
slime73@11800
   493
        return SDL_FALSE;
slime73@11800
   494
    }
slime73@11800
   495
    return SDL_TRUE;
slime73@11800
   496
}
slime73@11800
   497
icculus@11729
   498
static int
icculus@11729
   499
METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
   500
{ @autoreleasepool {
slouken@11732
   501
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slime73@11819
   502
    MTLPixelFormat pixfmt;
icculus@11729
   503
icculus@11729
   504
    switch (texture->format) {
slime73@11819
   505
        case SDL_PIXELFORMAT_ABGR8888:
slime73@11819
   506
            pixfmt = MTLPixelFormatRGBA8Unorm;
slime73@11819
   507
            break;
slime73@11819
   508
        case SDL_PIXELFORMAT_ARGB8888:
slime73@11819
   509
            pixfmt = MTLPixelFormatBGRA8Unorm;
slime73@11819
   510
            break;
slime73@11819
   511
        case SDL_PIXELFORMAT_IYUV:
slime73@11819
   512
        case SDL_PIXELFORMAT_YV12:
slime73@11819
   513
        case SDL_PIXELFORMAT_NV12:
slime73@11819
   514
        case SDL_PIXELFORMAT_NV21:
slime73@11819
   515
            pixfmt = MTLPixelFormatR8Unorm;
slime73@11819
   516
            break;
slime73@11819
   517
        default:
slime73@11819
   518
            return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
icculus@11729
   519
    }
icculus@11729
   520
slime73@11819
   521
    MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
icculus@11729
   522
                                            width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
slime73@11810
   523
slime73@11810
   524
    /* Not available in iOS 8. */
slime73@11810
   525
    if ([mtltexdesc respondsToSelector:@selector(usage)]) {
slime73@11810
   526
        if (texture->access == SDL_TEXTUREACCESS_TARGET) {
slime73@11810
   527
            mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
slime73@11810
   528
        } else {
slime73@11810
   529
            mtltexdesc.usage = MTLTextureUsageShaderRead;
slime73@11810
   530
        }
icculus@11750
   531
    }
icculus@11750
   532
    
slouken@11732
   533
    id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
icculus@11729
   534
    if (mtltexture == nil) {
icculus@11729
   535
        return SDL_SetError("Texture allocation failed");
icculus@11729
   536
    }
icculus@11729
   537
slime73@11819
   538
    id<MTLTexture> mtltexture_uv = nil;
slime73@11819
   539
slime73@11819
   540
    BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
slime73@11819
   541
    BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
slime73@11819
   542
slime73@11819
   543
    if (yuv) {
slime73@11819
   544
        mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
slime73@11819
   545
        mtltexdesc.width = (texture->w + 1) / 2;
slime73@11819
   546
        mtltexdesc.height = (texture->h + 1) / 2;
slime73@11819
   547
        mtltexdesc.textureType = MTLTextureType2DArray;
slime73@11819
   548
        mtltexdesc.arrayLength = 2;
slime73@11819
   549
    } else if (nv12) {
slime73@11819
   550
        mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
slime73@11819
   551
        mtltexdesc.width = (texture->w + 1) / 2;
slime73@11819
   552
        mtltexdesc.height = (texture->h + 1) / 2;
slime73@12320
   553
    }
slime73@12320
   554
icculus@12382
   555
    if (yuv || nv12) {
slime73@11819
   556
        mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
slime73@12320
   557
        if (mtltexture_uv == nil) {
slime73@12320
   558
#if !__has_feature(objc_arc)
slime73@12320
   559
            [mtltexture release];
slime73@12320
   560
#endif
slime73@12320
   561
            return SDL_SetError("Texture allocation failed");
slime73@12320
   562
        }
slime73@11819
   563
    }
slime73@11819
   564
slouken@11753
   565
    METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
slouken@11958
   566
    if (texture->scaleMode == SDL_ScaleModeNearest) {
slime73@11801
   567
        texturedata.mtlsampler = data.mtlsamplernearest;
slouken@11753
   568
    } else {
slime73@11801
   569
        texturedata.mtlsampler = data.mtlsamplerlinear;
slouken@11753
   570
    }
slouken@11753
   571
    texturedata.mtltexture = mtltexture;
slime73@11819
   572
    texturedata.mtltexture_uv = mtltexture_uv;
slime73@11819
   573
slime73@11819
   574
    texturedata.yuv = yuv;
slime73@11819
   575
    texturedata.nv12 = nv12;
slime73@11819
   576
slime73@11819
   577
    if (yuv) {
slime73@11819
   578
        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
slime73@11819
   579
    } else if (texture->format == SDL_PIXELFORMAT_NV12) {
slime73@11819
   580
        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
slime73@11819
   581
    } else if (texture->format == SDL_PIXELFORMAT_NV21) {
slime73@11819
   582
        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
slime73@11819
   583
    } else {
slime73@11819
   584
        texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
slime73@11819
   585
    }
slime73@11819
   586
slime73@11819
   587
    if (yuv || nv12) {
slime73@11819
   588
        size_t offset = 0;
slime73@11819
   589
        SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
slime73@11819
   590
        switch (mode) {
slime73@11819
   591
            case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
slime73@11819
   592
            case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
slime73@11819
   593
            case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
slime73@11819
   594
            default: offset = 0; break;
slime73@11819
   595
        }
slime73@11819
   596
        texturedata.conversionBufferOffset = offset;
slime73@11819
   597
    }
slouken@11753
   598
slouken@11753
   599
    texture->driverdata = (void*)CFBridgingRetain(texturedata);
icculus@11729
   600
slime73@11799
   601
#if !__has_feature(objc_arc)
slime73@11804
   602
    [texturedata release];
slime73@11799
   603
    [mtltexture release];
slime73@11819
   604
    [mtltexture_uv release];
slime73@11799
   605
#endif
slime73@11799
   606
icculus@11729
   607
    return 0;
slouken@11743
   608
}}
icculus@11729
   609
icculus@11729
   610
static int
icculus@11729
   611
METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   612
                 const SDL_Rect * rect, const void *pixels, int pitch)
slouken@11743
   613
{ @autoreleasepool {
slime73@11819
   614
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
slime73@11819
   615
slime73@11820
   616
    /* !!! FIXME: replaceRegion does not do any synchronization, so it might
slime73@11820
   617
     * !!! FIXME: stomp on a previous frame's data that's currently being read
slime73@11820
   618
     * !!! FIXME: by the GPU. */
slime73@11819
   619
    [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
slime73@11819
   620
                              mipmapLevel:0
slime73@11819
   621
                                withBytes:pixels
slime73@11819
   622
                              bytesPerRow:pitch];
slime73@11819
   623
slime73@11819
   624
    if (texturedata.yuv) {
slime73@11819
   625
        int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
slime73@11819
   626
        int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
slime73@11819
   627
slime73@11819
   628
        /* Skip to the correct offset into the next texture */
slime73@11819
   629
        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
slime73@11819
   630
        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
slime73@11819
   631
                                     mipmapLevel:0
slime73@11819
   632
                                           slice:Uslice
slime73@11819
   633
                                       withBytes:pixels
slime73@11819
   634
                                     bytesPerRow:(pitch + 1) / 2
slime73@11819
   635
                                   bytesPerImage:0];
slime73@11819
   636
slime73@11819
   637
        /* Skip to the correct offset into the next texture */
slime73@11819
   638
        pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
slime73@11819
   639
        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
slime73@11819
   640
                                     mipmapLevel:0
slime73@11819
   641
                                           slice:Vslice
slime73@11819
   642
                                       withBytes:pixels
slime73@11819
   643
                                     bytesPerRow:(pitch + 1) / 2
slime73@11819
   644
                                   bytesPerImage:0];
slime73@11819
   645
    }
slime73@11819
   646
slime73@11819
   647
    if (texturedata.nv12) {
slime73@11819
   648
        /* Skip to the correct offset into the next texture */
slime73@11819
   649
        pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
slime73@11819
   650
        [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
slime73@11819
   651
                                     mipmapLevel:0
slime73@11819
   652
                                           slice:0
slime73@11819
   653
                                       withBytes:pixels
slime73@11819
   654
                                     bytesPerRow:2 * ((pitch + 1) / 2)
slime73@11819
   655
                                   bytesPerImage:0];
slime73@11819
   656
    }
slime73@11819
   657
icculus@11729
   658
    return 0;
slouken@11743
   659
}}
icculus@11729
   660
icculus@11729
   661
static int
icculus@11729
   662
METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   663
                    const SDL_Rect * rect,
icculus@11729
   664
                    const Uint8 *Yplane, int Ypitch,
icculus@11729
   665
                    const Uint8 *Uplane, int Upitch,
icculus@11729
   666
                    const Uint8 *Vplane, int Vpitch)
slime73@11819
   667
{ @autoreleasepool {
slime73@11819
   668
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
icculus@12169
   669
    const int Uslice = 0;
icculus@12169
   670
    const int Vslice = 1;
slime73@11819
   671
slime73@11819
   672
    /* Bail out if we're supposed to update an empty rectangle */
slime73@11819
   673
    if (rect->w <= 0 || rect->h <= 0) {
slime73@11819
   674
        return 0;
slime73@11819
   675
    }
slime73@11819
   676
slime73@11819
   677
    [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
slime73@11819
   678
                              mipmapLevel:0
slime73@11819
   679
                                withBytes:Yplane
slime73@11819
   680
                              bytesPerRow:Ypitch];
slime73@11819
   681
slime73@11819
   682
    [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
slime73@11819
   683
                                 mipmapLevel:0
slime73@11819
   684
                                       slice:Uslice
slime73@11819
   685
                                   withBytes:Uplane
slime73@11819
   686
                                 bytesPerRow:Upitch
slime73@11819
   687
                               bytesPerImage:0];
slime73@11819
   688
slime73@11819
   689
    [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
slime73@11819
   690
                                 mipmapLevel:0
slime73@11819
   691
                                       slice:Vslice
slime73@11819
   692
                                   withBytes:Vplane
slime73@11819
   693
                                 bytesPerRow:Vpitch
slime73@11819
   694
                               bytesPerImage:0];
slime73@11819
   695
slime73@11819
   696
    return 0;
slime73@11819
   697
}}
icculus@11729
   698
icculus@11729
   699
static int
icculus@11729
   700
METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   701
               const SDL_Rect * rect, void **pixels, int *pitch)
icculus@11729
   702
{
icculus@11729
   703
    return SDL_Unsupported();   // !!! FIXME: write me
icculus@11729
   704
}
icculus@11729
   705
icculus@11729
   706
static void
icculus@11729
   707
METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
icculus@11729
   708
{
icculus@11729
   709
    // !!! FIXME: write me
icculus@11729
   710
}
icculus@11729
   711
icculus@11729
   712
static int
icculus@11729
   713
METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
   714
{ @autoreleasepool {
slouken@11732
   715
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11750
   716
slime73@11817
   717
    if (data.mtlcmdencoder) {
slime73@11817
   718
        /* End encoding for the previous render target so we can set up a new
slime73@11817
   719
         * render pass for this one. */
slime73@11817
   720
        [data.mtlcmdencoder endEncoding];
slime73@11817
   721
        [data.mtlcmdbuffer commit];
icculus@11750
   722
slime73@11817
   723
        data.mtlcmdencoder = nil;
slime73@11817
   724
        data.mtlcmdbuffer = nil;
slime73@11817
   725
    }
icculus@11750
   726
slime73@11817
   727
    /* We don't begin a new render pass right away - we delay it until an actual
slime73@11817
   728
     * draw or clear happens. That way we can use hardware clears when possible,
slime73@11817
   729
     * which are only available when beginning a new render pass. */
icculus@11729
   730
    return 0;
slouken@11743
   731
}}
icculus@11729
   732
icculus@11729
   733
slouken@11755
   734
// normalize a value from 0.0f to len into 0.0f to 1.0f.
slouken@11755
   735
static inline float
slouken@11755
   736
normtex(const float _val, const float len)
slouken@11755
   737
{
slime73@11810
   738
    return _val / len;
slouken@11755
   739
}
slouken@11755
   740
icculus@11729
   741
static int
icculus@12215
   742
METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
icculus@12215
   743
{
icculus@12215
   744
    float projection[4][4];    /* Prepare an orthographic projection */
icculus@12215
   745
    const int w = cmd->data.viewport.rect.w;
icculus@12215
   746
    const int h = cmd->data.viewport.rect.h;
icculus@12215
   747
    const size_t matrixlen = sizeof (projection);
icculus@12215
   748
    float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
icculus@12215
   749
    if (!matrix) {
icculus@12215
   750
        return -1;
icculus@12215
   751
    }
icculus@12215
   752
icculus@12215
   753
    SDL_memset(projection, '\0', matrixlen);
icculus@12215
   754
    if (w && h) {
icculus@12215
   755
        projection[0][0] = 2.0f / w;
icculus@12215
   756
        projection[1][1] = -2.0f / h;
icculus@12215
   757
        projection[3][0] = -1.0f;
icculus@12215
   758
        projection[3][1] = 1.0f;
icculus@12215
   759
        projection[3][3] = 1.0f;
icculus@12215
   760
    }
icculus@12215
   761
    SDL_memcpy(matrix, projection, matrixlen);
icculus@12215
   762
icculus@12215
   763
    return 0;
icculus@12215
   764
}
icculus@12215
   765
icculus@12215
   766
static int
icculus@12215
   767
METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
icculus@12215
   768
{
icculus@12215
   769
    const size_t vertlen = sizeof (float) * 4;
icculus@12215
   770
    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
icculus@12215
   771
    if (!verts) {
icculus@12215
   772
        return -1;
icculus@12215
   773
    }
icculus@12215
   774
    *(verts++) = ((float)cmd->data.color.r) / 255.0f;
icculus@12215
   775
    *(verts++) = ((float)cmd->data.color.g) / 255.0f;
icculus@12215
   776
    *(verts++) = ((float)cmd->data.color.b) / 255.0f;
icculus@12215
   777
    *(verts++) = ((float)cmd->data.color.a) / 255.0f;
icculus@12215
   778
    return 0;
icculus@12215
   779
}
icculus@12215
   780
icculus@12215
   781
static int
icculus@12213
   782
METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
icculus@12213
   783
{
slouken@11806
   784
    const size_t vertlen = (sizeof (float) * 2) * count;
icculus@12215
   785
    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
icculus@12213
   786
    if (!verts) {
icculus@12213
   787
        return -1;
icculus@12213
   788
    }
icculus@12213
   789
    cmd->data.draw.count = count;
icculus@12213
   790
    SDL_memcpy(verts, points, vertlen);
icculus@11729
   791
    return 0;
icculus@11729
   792
}
icculus@11729
   793
icculus@11729
   794
static int
icculus@12213
   795
METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
icculus@11729
   796
{
icculus@12213
   797
    // !!! FIXME: use an index buffer
icculus@12213
   798
    const size_t vertlen = (sizeof (float) * 8) * count;
icculus@12215
   799
    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
icculus@12213
   800
    if (!verts) {
icculus@12213
   801
        return -1;
icculus@12213
   802
    }
icculus@12213
   803
icculus@12213
   804
    cmd->data.draw.count = count;
icculus@12213
   805
icculus@12213
   806
    for (int i = 0; i < count; i++, rects++) {
icculus@12213
   807
        if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
icculus@12213
   808
            cmd->data.draw.count--;
icculus@12213
   809
        } else {
icculus@12213
   810
            *(verts++) = rects->x;
icculus@12213
   811
            *(verts++) = rects->y + rects->h;
icculus@12213
   812
            *(verts++) = rects->x;
icculus@12213
   813
            *(verts++) = rects->y;
icculus@12213
   814
            *(verts++) = rects->x + rects->w;
icculus@12213
   815
            *(verts++) = rects->y + rects->h;
icculus@12213
   816
            *(verts++) = rects->x + rects->w;
icculus@12213
   817
            *(verts++) = rects->y;
icculus@12213
   818
        }
icculus@12213
   819
    }
icculus@12213
   820
icculus@12213
   821
    if (cmd->data.draw.count == 0) {
icculus@12213
   822
        cmd->command = SDL_RENDERCMD_NO_OP;  // nothing to do, just skip this one later.
icculus@12213
   823
    }
icculus@12213
   824
icculus@12213
   825
    return 0;
icculus@11729
   826
}
icculus@11729
   827
icculus@11729
   828
static int
icculus@12213
   829
METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
icculus@12213
   830
                const SDL_Rect * srcrect, const SDL_FRect * dstrect)
icculus@12213
   831
{
icculus@12213
   832
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
icculus@12213
   833
    const float texw = (float) texturedata.mtltexture.width;
icculus@12213
   834
    const float texh = (float) texturedata.mtltexture.height;
icculus@12213
   835
    // !!! FIXME: use an index buffer
icculus@12213
   836
    const size_t vertlen = (sizeof (float) * 16);
icculus@12215
   837
    float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
icculus@12213
   838
    if (!verts) {
icculus@12213
   839
        return -1;
icculus@11729
   840
    }
icculus@11729
   841
icculus@12213
   842
    cmd->data.draw.count = 1;
icculus@12213
   843
icculus@12213
   844
    *(verts++) = dstrect->x;
icculus@12213
   845
    *(verts++) = dstrect->y + dstrect->h;
icculus@12213
   846
    *(verts++) = dstrect->x;
icculus@12213
   847
    *(verts++) = dstrect->y;
icculus@12213
   848
    *(verts++) = dstrect->x + dstrect->w;
icculus@12213
   849
    *(verts++) = dstrect->y + dstrect->h;
icculus@12213
   850
    *(verts++) = dstrect->x + dstrect->w;
icculus@12213
   851
    *(verts++) = dstrect->y;
icculus@12213
   852
icculus@12213
   853
    *(verts++) = normtex(srcrect->x, texw);
icculus@12213
   854
    *(verts++) = normtex(srcrect->y + srcrect->h, texh);
icculus@12213
   855
    *(verts++) = normtex(srcrect->x, texw);
icculus@12213
   856
    *(verts++) = normtex(srcrect->y, texh);
icculus@12213
   857
    *(verts++) = normtex(srcrect->x + srcrect->w, texw);
icculus@12213
   858
    *(verts++) = normtex(srcrect->y + srcrect->h, texh);
icculus@12213
   859
    *(verts++) = normtex(srcrect->x + srcrect->w, texw);
icculus@12213
   860
    *(verts++) = normtex(srcrect->y, texh);
icculus@12213
   861
icculus@11729
   862
    return 0;
slime73@11819
   863
}
slime73@11819
   864
icculus@11729
   865
static int
icculus@12213
   866
METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
icculus@12213
   867
                  const SDL_Rect * srcquad, const SDL_FRect * dstrect,
icculus@12213
   868
                  const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
icculus@12213
   869
{
slouken@11753
   870
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
slouken@11753
   871
    const float texw = (float) texturedata.mtltexture.width;
slouken@11753
   872
    const float texh = (float) texturedata.mtltexture.height;
icculus@12213
   873
    const float rads = (float)(M_PI * (float) angle / 180.0f);
icculus@12213
   874
    const float c = cosf(rads), s = sinf(rads);
icculus@12213
   875
    float minu, maxu, minv, maxv;
icculus@12213
   876
    const size_t vertlen = (sizeof (float) * 32);
icculus@12294
   877
    float *verts;
icculus@12294
   878
icculus@12294
   879
    // cheat and store this offset in (count) because it needs to be aligned in ways other fields don't and we aren't using count otherwise.
icculus@12294
   880
    verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.draw.count);
icculus@12213
   881
    if (!verts) {
icculus@12213
   882
        return -1;
icculus@12213
   883
    }
icculus@11729
   884
icculus@12294
   885
    // transform matrix
icculus@12294
   886
    SDL_memset(verts, '\0', sizeof (*verts) * 16);
icculus@12294
   887
    verts[10] = verts[15] = 1.0f;
icculus@12294
   888
    // rotation
icculus@12294
   889
    verts[0] = c;
icculus@12294
   890
    verts[1] = s;
icculus@12294
   891
    verts[4] = -s;
icculus@12294
   892
    verts[5] = c;
icculus@12294
   893
icculus@12294
   894
    // translation
icculus@12294
   895
    verts[12] = dstrect->x + center->x;
icculus@12294
   896
    verts[13] = dstrect->y + center->y;
icculus@12294
   897
icculus@12294
   898
    // rest of the vertices don't need the aggressive alignment. Pack them in.
icculus@12294
   899
    verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
icculus@12294
   900
    if (!verts) {
icculus@12294
   901
        return -1;
icculus@12294
   902
    }
slime73@11819
   903
icculus@12213
   904
    minu = normtex(srcquad->x, texw);
icculus@12213
   905
    maxu = normtex(srcquad->x + srcquad->w, texw);
icculus@12213
   906
    minv = normtex(srcquad->y, texh);
icculus@12213
   907
    maxv = normtex(srcquad->y + srcquad->h, texh);
slime73@11799
   908
slime73@11799
   909
    if (flip & SDL_FLIP_HORIZONTAL) {
slime73@11799
   910
        float tmp = maxu;
slime73@11799
   911
        maxu = minu;
slime73@11799
   912
        minu = tmp;
slime73@11799
   913
    }
slime73@11799
   914
    if (flip & SDL_FLIP_VERTICAL) {
slime73@11799
   915
        float tmp = maxv;
slime73@11799
   916
        maxv = minv;
slime73@11799
   917
        minv = tmp;
slime73@11799
   918
    }
slime73@11799
   919
icculus@12213
   920
    // vertices
icculus@12213
   921
    *(verts++) = -center->x;
icculus@12213
   922
    *(verts++) = dstrect->h - center->y;
icculus@12213
   923
    *(verts++) = -center->x;
icculus@12213
   924
    *(verts++) = -center->y;
icculus@12213
   925
    *(verts++) = dstrect->w - center->x;
icculus@12213
   926
    *(verts++) = dstrect->h - center->y;
icculus@12213
   927
    *(verts++) = dstrect->w - center->x;
icculus@12213
   928
    *(verts++) = -center->y;
slime73@11799
   929
icculus@12213
   930
    // texcoords
icculus@12213
   931
    *(verts++) = minu;
icculus@12213
   932
    *(verts++) = maxv;
icculus@12213
   933
    *(verts++) = minu;
icculus@12213
   934
    *(verts++) = minv;
icculus@12213
   935
    *(verts++) = maxu;
icculus@12213
   936
    *(verts++) = maxv;
icculus@12213
   937
    *(verts++) = maxu;
icculus@12213
   938
    *(verts++) = minv;
slime73@11799
   939
icculus@12213
   940
    return 0;
icculus@12213
   941
}
slime73@11821
   942
icculus@12213
   943
icculus@12213
   944
typedef struct
icculus@12213
   945
{
icculus@12215
   946
    #if __has_feature(objc_arc)
icculus@12215
   947
    __unsafe_unretained id<MTLRenderPipelineState> pipeline;
icculus@12215
   948
    #else
icculus@12213
   949
    id<MTLRenderPipelineState> pipeline;
icculus@12215
   950
    #endif
icculus@12213
   951
    size_t constants_offset;
icculus@12213
   952
    SDL_Texture *texture;
icculus@12213
   953
    SDL_bool cliprect_dirty;
icculus@12213
   954
    SDL_bool cliprect_enabled;
icculus@12215
   955
    SDL_Rect cliprect;
icculus@12213
   956
    SDL_bool viewport_dirty;
icculus@12213
   957
    SDL_Rect viewport;
icculus@12215
   958
    size_t projection_offset;
icculus@12215
   959
    SDL_bool color_dirty;
icculus@12215
   960
    size_t color_offset;
icculus@12213
   961
} METAL_DrawStateCache;
icculus@12213
   962
icculus@12213
   963
static void
icculus@12213
   964
SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader,
icculus@12213
   965
             const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
icculus@12213
   966
{
icculus@12213
   967
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@12213
   968
    const SDL_BlendMode blend = cmd->data.draw.blend;
icculus@12215
   969
    size_t first = cmd->data.draw.first;
icculus@12213
   970
    id<MTLRenderPipelineState> newpipeline;
icculus@12213
   971
icculus@12213
   972
    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
icculus@12213
   973
icculus@12213
   974
    if (statecache->viewport_dirty) {
icculus@12213
   975
        MTLViewport viewport;
icculus@12213
   976
        viewport.originX = statecache->viewport.x;
icculus@12213
   977
        viewport.originY = statecache->viewport.y;
icculus@12213
   978
        viewport.width = statecache->viewport.w;
icculus@12213
   979
        viewport.height = statecache->viewport.h;
icculus@12213
   980
        viewport.znear = 0.0;
icculus@12213
   981
        viewport.zfar = 1.0;
icculus@12213
   982
        [data.mtlcmdencoder setViewport:viewport];
icculus@12215
   983
        [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2];  // projection
icculus@12213
   984
        statecache->viewport_dirty = SDL_FALSE;
slime73@11799
   985
    }
slime73@11799
   986
icculus@12213
   987
    if (statecache->cliprect_dirty) {
icculus@12213
   988
        MTLScissorRect mtlrect;
icculus@12213
   989
        if (statecache->cliprect_enabled) {
icculus@12213
   990
            const SDL_Rect *rect = &statecache->cliprect;
icculus@12213
   991
            mtlrect.x = statecache->viewport.x + rect->x;
icculus@12213
   992
            mtlrect.y = statecache->viewport.y + rect->y;
icculus@12213
   993
            mtlrect.width = rect->w;
icculus@12213
   994
            mtlrect.height = rect->h;
icculus@12213
   995
        } else {
icculus@12213
   996
            mtlrect.x = statecache->viewport.x;
icculus@12213
   997
            mtlrect.y = statecache->viewport.y;
icculus@12213
   998
            mtlrect.width = statecache->viewport.w;
icculus@12213
   999
            mtlrect.height = statecache->viewport.h;
icculus@12213
  1000
        }
icculus@12213
  1001
        if (mtlrect.width > 0 && mtlrect.height > 0) {
icculus@12213
  1002
            [data.mtlcmdencoder setScissorRect:mtlrect];
icculus@12213
  1003
        }
icculus@12213
  1004
        statecache->cliprect_dirty = SDL_FALSE;
icculus@12213
  1005
    }
icculus@12213
  1006
icculus@12215
  1007
    if (statecache->color_dirty) {
icculus@12215
  1008
        [data.mtlcmdencoder setFragmentBuffer:mtlbufvertex offset:statecache->color_offset atIndex:0];
icculus@12213
  1009
        statecache->color_dirty = SDL_FALSE;
icculus@12213
  1010
    }
icculus@12213
  1011
icculus@12213
  1012
    newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend);
icculus@12213
  1013
    if (newpipeline != statecache->pipeline) {
icculus@12213
  1014
        [data.mtlcmdencoder setRenderPipelineState:newpipeline];
icculus@12213
  1015
        statecache->pipeline = newpipeline;
icculus@12213
  1016
    }
icculus@12213
  1017
icculus@12213
  1018
    if (constants_offset != statecache->constants_offset) {
icculus@12213
  1019
        if (constants_offset != CONSTANTS_OFFSET_INVALID) {
icculus@12213
  1020
            [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3];
icculus@12213
  1021
        }
icculus@12213
  1022
        statecache->constants_offset = constants_offset;
icculus@12213
  1023
    }
icculus@12213
  1024
icculus@12215
  1025
    [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:first atIndex:0];  // position
icculus@12213
  1026
}
icculus@12213
  1027
icculus@12213
  1028
static void
icculus@12213
  1029
SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset,
icculus@12213
  1030
             id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
icculus@12213
  1031
{
icculus@12213
  1032
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@12213
  1033
    SDL_Texture *texture = cmd->data.draw.texture;
icculus@12213
  1034
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
icculus@12213
  1035
icculus@12213
  1036
    SetDrawState(renderer, cmd, texturedata.fragmentFunction, constants_offset, mtlbufvertex, statecache);
icculus@12213
  1037
icculus@12213
  1038
    [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.first+(8*sizeof (float)) atIndex:1];  // texcoords
icculus@12213
  1039
icculus@12213
  1040
    if (texture != statecache->texture) {
icculus@12213
  1041
        METAL_TextureData *oldtexturedata = NULL;
icculus@12213
  1042
        if (statecache->texture) {
icculus@12213
  1043
            oldtexturedata = (__bridge METAL_TextureData *) statecache->texture->driverdata;
icculus@12213
  1044
        }
icculus@12213
  1045
        if (!oldtexturedata || (texturedata.mtlsampler != oldtexturedata.mtlsampler)) {
icculus@12213
  1046
            [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
icculus@12213
  1047
        }
icculus@12213
  1048
icculus@12213
  1049
        [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
icculus@12213
  1050
        if (texturedata.yuv || texturedata.nv12) {
icculus@12213
  1051
            [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1];
icculus@12213
  1052
            [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1];
icculus@12213
  1053
        }
icculus@12213
  1054
        statecache->texture = texture;
icculus@12213
  1055
    }
icculus@12213
  1056
}
icculus@12213
  1057
icculus@12213
  1058
static int
icculus@12213
  1059
METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
icculus@12213
  1060
{ @autoreleasepool {
icculus@12213
  1061
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@12213
  1062
    METAL_DrawStateCache statecache;
icculus@12293
  1063
    id<MTLBuffer> mtlbufvertex = nil;
icculus@12213
  1064
icculus@12213
  1065
    statecache.pipeline = nil;
icculus@12213
  1066
    statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
icculus@12213
  1067
    statecache.texture = NULL;
icculus@12213
  1068
    statecache.color_dirty = SDL_TRUE;
icculus@12213
  1069
    statecache.cliprect_dirty = SDL_TRUE;
icculus@12215
  1070
    statecache.viewport_dirty = SDL_TRUE;
icculus@12215
  1071
    statecache.projection_offset = 0;
icculus@12215
  1072
    statecache.color_offset = 0;
icculus@12213
  1073
icculus@12213
  1074
    // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation?
icculus@12293
  1075
    if (vertsize > 0) {
slime73@12385
  1076
        /* We can memcpy to a shared buffer from the CPU and read it from the GPU
slime73@12385
  1077
         * without any extra copying. It's a bit slower on macOS to read shared
slime73@12385
  1078
         * data from the GPU than to read managed/private data, but we avoid the
slime73@12385
  1079
         * cost of copying the data and the code's simpler. Apple's best
slime73@12385
  1080
         * practices guide recommends this approach for streamed vertex data.
slime73@12385
  1081
         * TODO: this buffer is also used for constants. Is performance still
slime73@12385
  1082
         * good for those, or should we have a managed buffer for them? */
slime73@12385
  1083
        mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
icculus@12293
  1084
        #if !__has_feature(objc_arc)
icculus@12293
  1085
        [mtlbufvertex autorelease];
icculus@12293
  1086
        #endif
icculus@12293
  1087
        mtlbufvertex.label = @"SDL vertex data";
slime73@12385
  1088
        SDL_memcpy([mtlbufvertex contents], vertices, vertsize);
icculus@12293
  1089
    }
icculus@12213
  1090
icculus@12213
  1091
    // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh.
icculus@12213
  1092
    [data.mtlcmdencoder endEncoding];
icculus@12213
  1093
    [data.mtlcmdbuffer commit];
icculus@12213
  1094
    data.mtlcmdencoder = nil;
icculus@12213
  1095
    data.mtlcmdbuffer = nil;
icculus@12213
  1096
icculus@12213
  1097
    while (cmd) {
icculus@12213
  1098
        switch (cmd->command) {
icculus@12213
  1099
            case SDL_RENDERCMD_SETVIEWPORT: {
icculus@12215
  1100
                SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof (statecache.viewport));
icculus@12215
  1101
                statecache.projection_offset = cmd->data.viewport.first;
icculus@12215
  1102
                statecache.viewport_dirty = SDL_TRUE;
icculus@12213
  1103
                break;
icculus@12213
  1104
            }
icculus@12213
  1105
icculus@12213
  1106
            case SDL_RENDERCMD_SETCLIPRECT: {
icculus@12215
  1107
                SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof (statecache.cliprect));
icculus@12215
  1108
                statecache.cliprect_enabled = cmd->data.cliprect.enabled;
icculus@12215
  1109
                statecache.cliprect_dirty = SDL_TRUE;
icculus@12215
  1110
                break;
icculus@12215
  1111
            }
icculus@12215
  1112
icculus@12215
  1113
            case SDL_RENDERCMD_SETDRAWCOLOR: {
icculus@12215
  1114
                statecache.color_offset = cmd->data.color.first;
icculus@12215
  1115
                statecache.color_dirty = SDL_TRUE;
icculus@12213
  1116
                break;
icculus@12213
  1117
            }
icculus@12213
  1118
icculus@12213
  1119
            case SDL_RENDERCMD_CLEAR: {
icculus@12213
  1120
                /* If we're already encoding a command buffer, dump it without committing it. We'd just
icculus@12213
  1121
                    clear all its work anyhow, and starting a new encoder will let us use a hardware clear
icculus@12213
  1122
                    operation via MTLLoadActionClear. */
icculus@12213
  1123
                if (data.mtlcmdencoder != nil) {
icculus@12213
  1124
                    [data.mtlcmdencoder endEncoding];
icculus@12215
  1125
icculus@12215
  1126
                    // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing.
icculus@12215
  1127
                    [data.mtlcmdbuffer commit];
icculus@12213
  1128
                    data.mtlcmdencoder = nil;
icculus@12213
  1129
                    data.mtlcmdbuffer = nil;
icculus@12213
  1130
                }
icculus@12213
  1131
icculus@12213
  1132
                // force all this state to be reconfigured on next command buffer.
icculus@12213
  1133
                statecache.pipeline = nil;
icculus@12213
  1134
                statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
icculus@12213
  1135
                statecache.texture = NULL;
icculus@12213
  1136
                statecache.color_dirty = SDL_TRUE;
icculus@12213
  1137
                statecache.cliprect_dirty = SDL_TRUE;
icculus@12213
  1138
                statecache.viewport_dirty = SDL_TRUE;
icculus@12213
  1139
icculus@12215
  1140
                const Uint8 r = cmd->data.color.r;
icculus@12215
  1141
                const Uint8 g = cmd->data.color.g;
icculus@12215
  1142
                const Uint8 b = cmd->data.color.b;
icculus@12215
  1143
                const Uint8 a = cmd->data.color.a;
icculus@12215
  1144
                MTLClearColor color = MTLClearColorMake(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
icculus@12215
  1145
icculus@12213
  1146
                // get new command encoder, set up with an initial clear operation.
icculus@12213
  1147
                METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color);
icculus@12213
  1148
                break;
icculus@12213
  1149
            }
icculus@12213
  1150
icculus@12213
  1151
            case SDL_RENDERCMD_DRAW_POINTS:
icculus@12213
  1152
            case SDL_RENDERCMD_DRAW_LINES: {
icculus@12213
  1153
                const size_t count = cmd->data.draw.count;
icculus@12213
  1154
                const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
icculus@12213
  1155
                SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache);
icculus@12213
  1156
                [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
icculus@12213
  1157
                break;
icculus@12213
  1158
            }
icculus@12213
  1159
icculus@12213
  1160
            case SDL_RENDERCMD_FILL_RECTS: {
icculus@12213
  1161
                const size_t count = cmd->data.draw.count;
icculus@12213
  1162
                size_t start = 0;
icculus@12213
  1163
                SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
icculus@12213
  1164
                for (size_t i = 0; i < count; i++, start += 4) {   // !!! FIXME: can we do all of these this with a single draw call, using MTLPrimitiveTypeTriangle and an index buffer?
icculus@12213
  1165
                    [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:start vertexCount:4];
icculus@12213
  1166
                }
icculus@12213
  1167
                break;
icculus@12213
  1168
            }
icculus@12213
  1169
icculus@12213
  1170
            case SDL_RENDERCMD_COPY: {
icculus@12213
  1171
                SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
icculus@12213
  1172
                [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
icculus@12213
  1173
                break;
icculus@12213
  1174
            }
icculus@12213
  1175
icculus@12213
  1176
            case SDL_RENDERCMD_COPY_EX: {
icculus@12213
  1177
                SetCopyState(renderer, cmd, CONSTANTS_OFFSET_INVALID, mtlbufvertex, &statecache);
icculus@12294
  1178
                [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.count atIndex:3];  // transform
icculus@12213
  1179
                [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
icculus@12213
  1180
                break;
icculus@12213
  1181
            }
icculus@12213
  1182
icculus@12213
  1183
            case SDL_RENDERCMD_NO_OP:
icculus@12213
  1184
                break;
icculus@12213
  1185
        }
icculus@12213
  1186
        cmd = cmd->next;
icculus@12213
  1187
    }
slime73@11799
  1188
slime73@11799
  1189
    return 0;
slime73@11799
  1190
}}
icculus@11729
  1191
icculus@11729
  1192
static int
icculus@11729
  1193
METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
icculus@11729
  1194
                    Uint32 pixel_format, void * pixels, int pitch)
slouken@11743
  1195
{ @autoreleasepool {
icculus@12213
  1196
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@12213
  1197
    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
slime73@11817
  1198
icculus@12213
  1199
    // Commit any current command buffer, and waitUntilCompleted, so any output is ready to be read.
icculus@12213
  1200
    [data.mtlcmdencoder endEncoding];
icculus@12213
  1201
    [data.mtlcmdbuffer commit];
icculus@12213
  1202
    [data.mtlcmdbuffer waitUntilCompleted];
icculus@12213
  1203
    data.mtlcmdencoder = nil;
icculus@12213
  1204
    data.mtlcmdbuffer = nil;
icculus@12213
  1205
slime73@11804
  1206
    id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
slime73@11800
  1207
    MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
icculus@11729
  1208
icculus@11729
  1209
    // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
icculus@11729
  1210
    const int temp_pitch = rect->w * 4;
icculus@11729
  1211
    void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
icculus@11729
  1212
    if (!temp_pixels) {
icculus@11729
  1213
        return SDL_OutOfMemory();
icculus@11729
  1214
    }
icculus@11729
  1215
icculus@11729
  1216
    [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
icculus@11729
  1217
icculus@11729
  1218
    const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
icculus@11729
  1219
    const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
icculus@11729
  1220
    SDL_free(temp_pixels);
icculus@11729
  1221
    return status;
slouken@11743
  1222
}}
icculus@11729
  1223
icculus@11729
  1224
static void
icculus@11729
  1225
METAL_RenderPresent(SDL_Renderer * renderer)
slouken@11743
  1226
{ @autoreleasepool {
slouken@11732
  1227
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
  1228
slime73@11817
  1229
    if (data.mtlcmdencoder != nil) {
slime73@11817
  1230
        [data.mtlcmdencoder endEncoding];
slime73@11817
  1231
    }
slime73@11817
  1232
    if (data.mtlbackbuffer != nil) {
slime73@11817
  1233
        [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
slime73@11817
  1234
    }
slime73@11817
  1235
    if (data.mtlcmdbuffer != nil) {
slime73@11817
  1236
        [data.mtlcmdbuffer commit];
slime73@11817
  1237
    }
slouken@11741
  1238
    data.mtlcmdencoder = nil;
slouken@11741
  1239
    data.mtlcmdbuffer = nil;
slouken@11741
  1240
    data.mtlbackbuffer = nil;
slouken@11743
  1241
}}
icculus@11729
  1242
icculus@11729
  1243
static void
icculus@11729
  1244
METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
  1245
{ @autoreleasepool {
slime73@11804
  1246
    CFBridgingRelease(texture->driverdata);
icculus@11729
  1247
    texture->driverdata = NULL;
slouken@11743
  1248
}}
icculus@11729
  1249
icculus@11729
  1250
static void
icculus@11729
  1251
METAL_DestroyRenderer(SDL_Renderer * renderer)
slouken@11743
  1252
{ @autoreleasepool {
slouken@11732
  1253
    if (renderer->driverdata) {
slouken@11732
  1254
        METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
icculus@11729
  1255
slouken@11741
  1256
        if (data.mtlcmdencoder != nil) {
slouken@11741
  1257
            [data.mtlcmdencoder endEncoding];
slouken@11741
  1258
        }
icculus@11746
  1259
slime73@11820
  1260
        DestroyAllPipelines(data.allpipelines, data.pipelinescount);
icculus@11729
  1261
    }
icculus@11746
  1262
icculus@11729
  1263
    SDL_free(renderer);
slouken@11743
  1264
}}
icculus@11729
  1265
slime73@11804
  1266
static void *
slime73@11804
  1267
METAL_GetMetalLayer(SDL_Renderer * renderer)
slouken@11744
  1268
{ @autoreleasepool {
slouken@11744
  1269
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11744
  1270
    return (__bridge void*)data.mtllayer;
slouken@11744
  1271
}}
slouken@11744
  1272
slime73@11804
  1273
static void *
slime73@11804
  1274
METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
slouken@11744
  1275
{ @autoreleasepool {
icculus@12213
  1276
    METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
slouken@11744
  1277
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11744
  1278
    return (__bridge void*)data.mtlcmdencoder;
slouken@11744
  1279
}}
slouken@11744
  1280
icculus@12218
  1281
static SDL_Renderer *
icculus@12218
  1282
METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
icculus@12218
  1283
{ @autoreleasepool {
icculus@12218
  1284
    SDL_Renderer *renderer = NULL;
icculus@12218
  1285
    METAL_RenderData *data = NULL;
icculus@12218
  1286
    id<MTLDevice> mtldevice = nil;
icculus@12218
  1287
    SDL_SysWMinfo syswm;
icculus@12218
  1288
icculus@12218
  1289
    SDL_VERSION(&syswm.version);
icculus@12218
  1290
    if (!SDL_GetWindowWMInfo(window, &syswm)) {
icculus@12218
  1291
        return NULL;
icculus@12218
  1292
    }
icculus@12218
  1293
icculus@12218
  1294
    if (IsMetalAvailable(&syswm) == -1) {
icculus@12218
  1295
        return NULL;
icculus@12218
  1296
    }
icculus@12218
  1297
icculus@12218
  1298
    renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
icculus@12218
  1299
    if (!renderer) {
icculus@12218
  1300
        SDL_OutOfMemory();
icculus@12218
  1301
        return NULL;
icculus@12218
  1302
    }
icculus@12218
  1303
icculus@12218
  1304
    // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
icculus@12218
  1305
    mtldevice = MTLCreateSystemDefaultDevice();
icculus@12218
  1306
icculus@12218
  1307
    if (mtldevice == nil) {
icculus@12218
  1308
        SDL_free(renderer);
icculus@12218
  1309
        SDL_SetError("Failed to obtain Metal device");
icculus@12218
  1310
        return NULL;
icculus@12218
  1311
    }
icculus@12218
  1312
icculus@12218
  1313
    // !!! FIXME: error checking on all of this.
icculus@12218
  1314
    data = [[METAL_RenderData alloc] init];
icculus@12218
  1315
icculus@12218
  1316
    renderer->driverdata = (void*)CFBridgingRetain(data);
icculus@12218
  1317
    renderer->window = window;
icculus@12218
  1318
icculus@12218
  1319
#ifdef __MACOSX__
icculus@12218
  1320
    NSView *view = Cocoa_Mtl_AddMetalView(window);
icculus@12218
  1321
    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
icculus@12218
  1322
icculus@12218
  1323
    layer.device = mtldevice;
icculus@12218
  1324
icculus@12218
  1325
    //layer.colorspace = nil;
icculus@12218
  1326
icculus@12218
  1327
#else
icculus@12218
  1328
    UIView *view = UIKit_Mtl_AddMetalView(window);
icculus@12218
  1329
    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
icculus@12218
  1330
#endif
icculus@12218
  1331
icculus@12218
  1332
    // Necessary for RenderReadPixels.
icculus@12218
  1333
    layer.framebufferOnly = NO;
icculus@12218
  1334
icculus@12218
  1335
    data.mtldevice = layer.device;
icculus@12218
  1336
    data.mtllayer = layer;
icculus@12218
  1337
    id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
icculus@12218
  1338
    data.mtlcmdqueue = mtlcmdqueue;
icculus@12218
  1339
    data.mtlcmdqueue.label = @"SDL Metal Renderer";
icculus@12218
  1340
    data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
icculus@12218
  1341
icculus@12218
  1342
    NSError *err = nil;
icculus@12218
  1343
icculus@12218
  1344
    // The compiled .metallib is embedded in a static array in a header file
icculus@12218
  1345
    // but the original shader source code is in SDL_shaders_metal.metal.
icculus@12218
  1346
    dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
icculus@12218
  1347
    id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
icculus@12218
  1348
    data.mtllibrary = mtllibrary;
icculus@12218
  1349
    SDL_assert(err == nil);
icculus@12218
  1350
#if !__has_feature(objc_arc)
icculus@12218
  1351
    dispatch_release(mtllibdata);
icculus@12218
  1352
#endif
icculus@12218
  1353
    data.mtllibrary.label = @"SDL Metal renderer shader library";
icculus@12218
  1354
icculus@12218
  1355
    /* Do some shader pipeline state loading up-front rather than on demand. */
icculus@12218
  1356
    data.pipelinescount = 0;
icculus@12218
  1357
    data.allpipelines = NULL;
icculus@12218
  1358
    ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
icculus@12218
  1359
icculus@12218
  1360
    MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
icculus@12218
  1361
icculus@12218
  1362
    samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
icculus@12218
  1363
    samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
icculus@12218
  1364
    id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
icculus@12218
  1365
    data.mtlsamplernearest = mtlsamplernearest;
icculus@12218
  1366
icculus@12218
  1367
    samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
icculus@12218
  1368
    samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
icculus@12218
  1369
    id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
icculus@12218
  1370
    data.mtlsamplerlinear = mtlsamplerlinear;
icculus@12218
  1371
icculus@12218
  1372
    /* Note: matrices are column major. */
icculus@12218
  1373
    float identitytransform[16] = {
icculus@12218
  1374
        1.0f, 0.0f, 0.0f, 0.0f,
icculus@12218
  1375
        0.0f, 1.0f, 0.0f, 0.0f,
icculus@12218
  1376
        0.0f, 0.0f, 1.0f, 0.0f,
icculus@12218
  1377
        0.0f, 0.0f, 0.0f, 1.0f,
icculus@12218
  1378
    };
icculus@12218
  1379
icculus@12218
  1380
    float halfpixeltransform[16] = {
icculus@12218
  1381
        1.0f, 0.0f, 0.0f, 0.0f,
icculus@12218
  1382
        0.0f, 1.0f, 0.0f, 0.0f,
icculus@12218
  1383
        0.0f, 0.0f, 1.0f, 0.0f,
icculus@12218
  1384
        0.5f, 0.5f, 0.0f, 1.0f,
icculus@12218
  1385
    };
icculus@12218
  1386
icculus@12218
  1387
    /* Metal pads float3s to 16 bytes. */
icculus@12218
  1388
    float decodetransformJPEG[4*4] = {
icculus@12218
  1389
        0.0, -0.501960814, -0.501960814, 0.0, /* offset */
icculus@12218
  1390
        1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
icculus@12218
  1391
        1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
icculus@12218
  1392
        1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
icculus@12218
  1393
    };
icculus@12218
  1394
icculus@12218
  1395
    float decodetransformBT601[4*4] = {
icculus@12218
  1396
        -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
icculus@12218
  1397
        1.1644,  0.0000,  1.5960, 0.0,                  /* Rcoeff */
icculus@12218
  1398
        1.1644, -0.3918, -0.8130, 0.0,                  /* Gcoeff */
icculus@12218
  1399
        1.1644,  2.0172,  0.0000, 0.0,                  /* Bcoeff */
icculus@12218
  1400
    };
icculus@12218
  1401
icculus@12218
  1402
    float decodetransformBT709[4*4] = {
icculus@12218
  1403
        0.0, -0.501960814, -0.501960814, 0.0, /* offset */
icculus@12218
  1404
        1.0000,  0.0000,  1.4020, 0.0,        /* Rcoeff */
icculus@12218
  1405
        1.0000, -0.3441, -0.7141, 0.0,        /* Gcoeff */
icculus@12218
  1406
        1.0000,  1.7720,  0.0000, 0.0,        /* Bcoeff */
icculus@12218
  1407
    };
icculus@12218
  1408
icculus@12218
  1409
    id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
icculus@12218
  1410
    #if !__has_feature(objc_arc)
icculus@12218
  1411
    [mtlbufconstantstaging autorelease];
icculus@12218
  1412
    #endif
icculus@12218
  1413
    mtlbufconstantstaging.label = @"SDL constant staging data";
icculus@12218
  1414
icculus@12218
  1415
    id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
icculus@12218
  1416
    data.mtlbufconstants = mtlbufconstants;
icculus@12218
  1417
    data.mtlbufconstants.label = @"SDL constant data";
icculus@12218
  1418
icculus@12218
  1419
    char *constantdata = [mtlbufconstantstaging contents];
icculus@12218
  1420
    SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
icculus@12218
  1421
    SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
icculus@12218
  1422
    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
icculus@12218
  1423
    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
icculus@12218
  1424
    SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
icculus@12218
  1425
icculus@12218
  1426
    id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
icculus@12218
  1427
    id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
icculus@12218
  1428
icculus@12218
  1429
    [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
icculus@12218
  1430
icculus@12218
  1431
    [blitcmd endEncoding];
icculus@12218
  1432
    [cmdbuffer commit];
icculus@12218
  1433
icculus@12218
  1434
    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
icculus@12218
  1435
icculus@12218
  1436
    renderer->WindowEvent = METAL_WindowEvent;
icculus@12218
  1437
    renderer->GetOutputSize = METAL_GetOutputSize;
icculus@12218
  1438
    renderer->SupportsBlendMode = METAL_SupportsBlendMode;
icculus@12218
  1439
    renderer->CreateTexture = METAL_CreateTexture;
icculus@12218
  1440
    renderer->UpdateTexture = METAL_UpdateTexture;
icculus@12218
  1441
    renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
icculus@12218
  1442
    renderer->LockTexture = METAL_LockTexture;
icculus@12218
  1443
    renderer->UnlockTexture = METAL_UnlockTexture;
icculus@12218
  1444
    renderer->SetRenderTarget = METAL_SetRenderTarget;
icculus@12218
  1445
    renderer->QueueSetViewport = METAL_QueueSetViewport;
icculus@12218
  1446
    renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
icculus@12218
  1447
    renderer->QueueDrawPoints = METAL_QueueDrawPoints;
icculus@12218
  1448
    renderer->QueueDrawLines = METAL_QueueDrawPoints;  // lines and points queue the same way.
icculus@12218
  1449
    renderer->QueueFillRects = METAL_QueueFillRects;
icculus@12218
  1450
    renderer->QueueCopy = METAL_QueueCopy;
icculus@12218
  1451
    renderer->QueueCopyEx = METAL_QueueCopyEx;
icculus@12218
  1452
    renderer->RunCommandQueue = METAL_RunCommandQueue;
icculus@12218
  1453
    renderer->RenderReadPixels = METAL_RenderReadPixels;
icculus@12218
  1454
    renderer->RenderPresent = METAL_RenderPresent;
icculus@12218
  1455
    renderer->DestroyTexture = METAL_DestroyTexture;
icculus@12218
  1456
    renderer->DestroyRenderer = METAL_DestroyRenderer;
icculus@12218
  1457
    renderer->GetMetalLayer = METAL_GetMetalLayer;
icculus@12218
  1458
    renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
icculus@12218
  1459
icculus@12218
  1460
    renderer->info = METAL_RenderDriver.info;
icculus@12218
  1461
    renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
icculus@12218
  1462
icculus@12218
  1463
    renderer->always_batch = SDL_TRUE;
icculus@12218
  1464
icculus@12218
  1465
#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
icculus@12218
  1466
    if (@available(macOS 10.13, *)) {
icculus@12218
  1467
        data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
icculus@12218
  1468
    } else
icculus@12218
  1469
#endif
icculus@12218
  1470
    {
icculus@12218
  1471
        renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
icculus@12218
  1472
    }
icculus@12218
  1473
icculus@12218
  1474
    /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
icculus@12218
  1475
    int maxtexsize = 4096;
icculus@12218
  1476
#if defined(__MACOSX__)
icculus@12218
  1477
    maxtexsize = 16384;
icculus@12218
  1478
#elif defined(__TVOS__)
icculus@12218
  1479
    maxtexsize = 8192;
icculus@12218
  1480
#ifdef __TVOS_11_0
icculus@12218
  1481
    if (@available(tvOS 11.0, *)) {
icculus@12218
  1482
        if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
icculus@12218
  1483
            maxtexsize = 16384;
icculus@12218
  1484
        }
icculus@12218
  1485
    }
icculus@12218
  1486
#endif
icculus@12218
  1487
#else
icculus@12218
  1488
#ifdef __IPHONE_11_0
icculus@12218
  1489
    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
icculus@12218
  1490
        maxtexsize = 16384;
icculus@12218
  1491
    } else
icculus@12218
  1492
#endif
icculus@12218
  1493
#ifdef __IPHONE_10_0
icculus@12218
  1494
    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
icculus@12218
  1495
        maxtexsize = 16384;
icculus@12218
  1496
    } else
icculus@12218
  1497
#endif
icculus@12218
  1498
    if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
icculus@12218
  1499
        maxtexsize = 8192;
icculus@12218
  1500
    } else {
icculus@12218
  1501
        maxtexsize = 4096;
icculus@12218
  1502
    }
icculus@12218
  1503
#endif
icculus@12218
  1504
icculus@12218
  1505
    renderer->info.max_texture_width = maxtexsize;
icculus@12218
  1506
    renderer->info.max_texture_height = maxtexsize;
icculus@12218
  1507
icculus@12218
  1508
#if !__has_feature(objc_arc)
icculus@12218
  1509
    [mtlcmdqueue release];
icculus@12218
  1510
    [mtllibrary release];
icculus@12218
  1511
    [samplerdesc release];
icculus@12218
  1512
    [mtlsamplernearest release];
icculus@12218
  1513
    [mtlsamplerlinear release];
icculus@12218
  1514
    [mtlbufconstants release];
icculus@12218
  1515
    [view release];
icculus@12218
  1516
    [data release];
icculus@12218
  1517
    [mtldevice release];
icculus@12218
  1518
#endif
icculus@12218
  1519
icculus@12218
  1520
    return renderer;
icculus@12218
  1521
}}
icculus@12218
  1522
icculus@12218
  1523
SDL_RenderDriver METAL_RenderDriver = {
icculus@12218
  1524
    METAL_CreateRenderer,
icculus@12218
  1525
    {
icculus@12218
  1526
        "metal",
icculus@12218
  1527
        (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
icculus@12218
  1528
        6,
icculus@12218
  1529
        {
icculus@12218
  1530
            SDL_PIXELFORMAT_ARGB8888,
icculus@12218
  1531
            SDL_PIXELFORMAT_ABGR8888,
icculus@12218
  1532
            SDL_PIXELFORMAT_YV12,
icculus@12218
  1533
            SDL_PIXELFORMAT_IYUV,
icculus@12218
  1534
            SDL_PIXELFORMAT_NV12,
icculus@12218
  1535
            SDL_PIXELFORMAT_NV21
icculus@12218
  1536
        },
icculus@12218
  1537
    0, 0,
icculus@12218
  1538
    }
icculus@12218
  1539
};
icculus@12218
  1540
icculus@11729
  1541
#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
icculus@11729
  1542
icculus@11729
  1543
/* vi: set ts=4 sw=4 expandtab: */