src/render/metal/SDL_render_metal.m
author Alex Szpakowski <slime73@gmail.com>
Sat, 30 Dec 2017 20:32:22 -0400
changeset 11789 01f9851b2c9d
parent 11788 d16d4c766611
child 11790 a5620934cf56
permissions -rw-r--r--
metal: use a projection matrix instead of manually transforming vertices into clip space on the CPU.
icculus@11729
     1
/*
icculus@11729
     2
  Simple DirectMedia Layer
slouken@11730
     3
  Copyright (C) 1997-2017 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__
icculus@11729
    32
#include <Cocoa/Cocoa.h>
slouken@11732
    33
#else
slouken@11732
    34
#include "../../video/uikit/SDL_uikitmetalview.h"
slouken@11732
    35
#endif
icculus@11729
    36
#include <Metal/Metal.h>
icculus@11729
    37
#include <QuartzCore/CAMetalLayer.h>
icculus@11729
    38
slouken@11730
    39
/* Regenerate these with build-metal-shaders.sh */
slouken@11730
    40
#ifdef __MACOSX__
slouken@11730
    41
#include "SDL_shaders_metal_osx.h"
slouken@11730
    42
#else
slouken@11730
    43
#include "SDL_shaders_metal_ios.h"
slouken@11730
    44
#endif
icculus@11729
    45
icculus@11729
    46
/* Apple Metal renderer implementation */
icculus@11729
    47
icculus@11729
    48
static SDL_Renderer *METAL_CreateRenderer(SDL_Window * window, Uint32 flags);
icculus@11729
    49
static void METAL_WindowEvent(SDL_Renderer * renderer,
icculus@11729
    50
                           const SDL_WindowEvent *event);
icculus@11729
    51
static int METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h);
icculus@11729
    52
static int METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture);
icculus@11729
    53
static int METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
    54
                            const SDL_Rect * rect, const void *pixels,
icculus@11729
    55
                            int pitch);
icculus@11729
    56
static int METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
    57
                               const SDL_Rect * rect,
icculus@11729
    58
                               const Uint8 *Yplane, int Ypitch,
icculus@11729
    59
                               const Uint8 *Uplane, int Upitch,
icculus@11729
    60
                               const Uint8 *Vplane, int Vpitch);
icculus@11729
    61
static int METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
    62
                          const SDL_Rect * rect, void **pixels, int *pitch);
icculus@11729
    63
static void METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture);
icculus@11729
    64
static int METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture);
icculus@11729
    65
static int METAL_UpdateViewport(SDL_Renderer * renderer);
icculus@11729
    66
static int METAL_UpdateClipRect(SDL_Renderer * renderer);
icculus@11729
    67
static int METAL_RenderClear(SDL_Renderer * renderer);
icculus@11729
    68
static int METAL_RenderDrawPoints(SDL_Renderer * renderer,
icculus@11729
    69
                               const SDL_FPoint * points, int count);
icculus@11729
    70
static int METAL_RenderDrawLines(SDL_Renderer * renderer,
icculus@11729
    71
                              const SDL_FPoint * points, int count);
icculus@11729
    72
static int METAL_RenderFillRects(SDL_Renderer * renderer,
icculus@11729
    73
                              const SDL_FRect * rects, int count);
icculus@11729
    74
static int METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
    75
                         const SDL_Rect * srcrect, const SDL_FRect * dstrect);
icculus@11729
    76
static int METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
    77
                         const SDL_Rect * srcrect, const SDL_FRect * dstrect,
icculus@11729
    78
                         const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
icculus@11729
    79
static int METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
icculus@11729
    80
                               Uint32 pixel_format, void * pixels, int pitch);
icculus@11729
    81
static void METAL_RenderPresent(SDL_Renderer * renderer);
icculus@11729
    82
static void METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture);
icculus@11729
    83
static void METAL_DestroyRenderer(SDL_Renderer * renderer);
slouken@11744
    84
static void *METAL_GetMetalLayer(SDL_Renderer * renderer);
slouken@11744
    85
static void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer);
icculus@11729
    86
icculus@11729
    87
SDL_RenderDriver METAL_RenderDriver = {
icculus@11729
    88
    METAL_CreateRenderer,
icculus@11729
    89
    {
icculus@11729
    90
     "metal",
icculus@11729
    91
     (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
icculus@11729
    92
     2,
icculus@11729
    93
     {SDL_PIXELFORMAT_ARGB8888, SDL_PIXELFORMAT_ABGR8888},
icculus@11749
    94
icculus@11749
    95
     // !!! FIXME: how do you query Metal for this?
icculus@11749
    96
     // (the weakest GPU supported by Metal on iOS has 4k texture max, and
icculus@11749
    97
     //  other models might be 2x or 4x more. On macOS, it's 16k across the
icculus@11749
    98
     //  board right now.)
slouken@11751
    99
#ifdef __MACOSX__
slouken@11751
   100
     16384, 16384
slouken@11751
   101
#else
slouken@11751
   102
     4096, 4096
slouken@11751
   103
#endif
slouken@11751
   104
    }
icculus@11729
   105
};
icculus@11729
   106
slouken@11732
   107
@interface METAL_RenderData : NSObject
slouken@11735
   108
    @property (nonatomic, assign) BOOL beginScene;
slouken@11735
   109
    @property (nonatomic, retain) id<MTLDevice> mtldevice;
slouken@11735
   110
    @property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
slouken@11735
   111
    @property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
slouken@11735
   112
    @property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
slouken@11735
   113
    @property (nonatomic, retain) id<MTLLibrary> mtllibrary;
slouken@11735
   114
    @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
slouken@11735
   115
    @property (nonatomic, retain) NSMutableArray *mtlpipelineprims;
slouken@11753
   116
    @property (nonatomic, retain) NSMutableArray *mtlpipelinecopynearest;
slouken@11753
   117
    @property (nonatomic, retain) NSMutableArray *mtlpipelinecopylinear;
slouken@11735
   118
    @property (nonatomic, retain) id<MTLBuffer> mtlbufclearverts;
slouken@11735
   119
    @property (nonatomic, retain) CAMetalLayer *mtllayer;
slouken@11735
   120
    @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
slouken@11732
   121
@end
icculus@11729
   122
slouken@11732
   123
@implementation METAL_RenderData
slouken@11732
   124
@end
icculus@11729
   125
slouken@11753
   126
@interface METAL_TextureData : NSObject
slouken@11753
   127
    @property (nonatomic, retain) id<MTLTexture> mtltexture;
slouken@11753
   128
    @property (nonatomic, retain) NSMutableArray *mtlpipeline;
slouken@11753
   129
@end
slouken@11753
   130
slouken@11753
   131
@implementation METAL_TextureData
slouken@11753
   132
@end
slouken@11753
   133
icculus@11729
   134
static int
icculus@11729
   135
IsMetalAvailable(const SDL_SysWMinfo *syswm)
icculus@11729
   136
{
slouken@11733
   137
    if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
slouken@11733
   138
        return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
icculus@11729
   139
    }
icculus@11729
   140
icculus@11729
   141
    // this checks a weak symbol.
icculus@11742
   142
#if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
icculus@11729
   143
    if (MTLCreateSystemDefaultDevice == NULL) {  // probably on 10.10 or lower.
icculus@11729
   144
        return SDL_SetError("Metal framework not available on this system");
icculus@11729
   145
    }
icculus@11729
   146
#endif
icculus@11729
   147
icculus@11729
   148
    return 0;
icculus@11729
   149
}
icculus@11729
   150
icculus@11729
   151
static id<MTLRenderPipelineState>
icculus@11729
   152
MakePipelineState(METAL_RenderData *data, NSString *label, NSString *vertfn,
icculus@11729
   153
                  NSString *fragfn, const SDL_BlendMode blendmode)
icculus@11729
   154
{
slouken@11732
   155
    id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:vertfn];
slouken@11732
   156
    id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:fragfn];
icculus@11729
   157
    SDL_assert(mtlvertfn != nil);
icculus@11729
   158
    SDL_assert(mtlfragfn != nil);
icculus@11729
   159
icculus@11729
   160
    MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
icculus@11729
   161
    mtlpipedesc.vertexFunction = mtlvertfn;
icculus@11729
   162
    mtlpipedesc.fragmentFunction = mtlfragfn;
slouken@11735
   163
    mtlpipedesc.colorAttachments[0].pixelFormat = data.mtllayer.pixelFormat;
icculus@11729
   164
icculus@11729
   165
    switch (blendmode) {
icculus@11729
   166
        case SDL_BLENDMODE_BLEND:
icculus@11729
   167
            mtlpipedesc.colorAttachments[0].blendingEnabled = YES;
icculus@11729
   168
            mtlpipedesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
icculus@11729
   169
            mtlpipedesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
icculus@11729
   170
            mtlpipedesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
icculus@11729
   171
            mtlpipedesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
icculus@11729
   172
            mtlpipedesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
icculus@11729
   173
            mtlpipedesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha;
icculus@11729
   174
            break;
icculus@11729
   175
icculus@11729
   176
        case SDL_BLENDMODE_ADD:
icculus@11729
   177
            mtlpipedesc.colorAttachments[0].blendingEnabled = YES;
icculus@11729
   178
            mtlpipedesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
icculus@11729
   179
            mtlpipedesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
icculus@11729
   180
            mtlpipedesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
icculus@11729
   181
            mtlpipedesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
icculus@11729
   182
            mtlpipedesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorZero;
icculus@11729
   183
            mtlpipedesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;
icculus@11729
   184
            break;
icculus@11729
   185
icculus@11729
   186
        case SDL_BLENDMODE_MOD:
icculus@11729
   187
            mtlpipedesc.colorAttachments[0].blendingEnabled = YES;
icculus@11729
   188
            mtlpipedesc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
icculus@11729
   189
            mtlpipedesc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
icculus@11729
   190
            mtlpipedesc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorZero;
icculus@11729
   191
            mtlpipedesc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorSourceColor;
icculus@11729
   192
            mtlpipedesc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorZero;
icculus@11729
   193
            mtlpipedesc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOne;
icculus@11729
   194
            break;
slouken@11730
   195
slouken@11730
   196
        default:
slouken@11730
   197
            mtlpipedesc.colorAttachments[0].blendingEnabled = NO;
slouken@11730
   198
            break;
icculus@11729
   199
    }
icculus@11729
   200
icculus@11729
   201
    mtlpipedesc.label = label;
icculus@11729
   202
icculus@11729
   203
    NSError *err = nil;
slouken@11732
   204
    id<MTLRenderPipelineState> retval = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
icculus@11729
   205
    SDL_assert(err == nil);
slouken@11732
   206
#if !__has_feature(objc_arc)
icculus@11729
   207
    [mtlpipedesc release];  // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
icculus@11729
   208
    [mtlvertfn release];
icculus@11729
   209
    [mtlfragfn release];
icculus@11729
   210
    [label release];
slouken@11732
   211
#endif
icculus@11729
   212
    return retval;
icculus@11729
   213
}
icculus@11729
   214
icculus@11729
   215
static void
slouken@11732
   216
MakePipelineStates(METAL_RenderData *data, NSMutableArray *states,
icculus@11729
   217
                   NSString *label, NSString *vertfn, NSString *fragfn)
icculus@11729
   218
{
slouken@11732
   219
    [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=none)"], vertfn, fragfn, SDL_BLENDMODE_NONE)];
slouken@11732
   220
    [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=blend)"], vertfn, fragfn, SDL_BLENDMODE_BLEND)];
slouken@11732
   221
    [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=add)"], vertfn, fragfn, SDL_BLENDMODE_ADD)];
slouken@11732
   222
    [states addObject:MakePipelineState(data, [label stringByAppendingString:@" (blendmode=mod)"], vertfn, fragfn, SDL_BLENDMODE_MOD)];
icculus@11729
   223
}
icculus@11729
   224
icculus@11729
   225
static inline id<MTLRenderPipelineState>
slouken@11732
   226
ChoosePipelineState(NSMutableArray *states, const SDL_BlendMode blendmode)
icculus@11729
   227
{
icculus@11729
   228
    switch (blendmode) {
slouken@11732
   229
        case SDL_BLENDMODE_BLEND: return (id<MTLRenderPipelineState>)states[1];
slouken@11732
   230
        case SDL_BLENDMODE_ADD: return (id<MTLRenderPipelineState>)states[2];
slouken@11732
   231
        case SDL_BLENDMODE_MOD: return (id<MTLRenderPipelineState>)states[3];
slouken@11732
   232
        default: return (id<MTLRenderPipelineState>)states[0];
icculus@11729
   233
    }
icculus@11729
   234
    return nil;
icculus@11729
   235
}
icculus@11729
   236
icculus@11729
   237
static SDL_Renderer *
icculus@11729
   238
METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
icculus@11729
   239
{
icculus@11729
   240
    SDL_Renderer *renderer = NULL;
icculus@11729
   241
    METAL_RenderData *data = NULL;
icculus@11729
   242
    SDL_SysWMinfo syswm;
icculus@11729
   243
icculus@11729
   244
    SDL_VERSION(&syswm.version);
icculus@11729
   245
    if (!SDL_GetWindowWMInfo(window, &syswm)) {
icculus@11729
   246
        return NULL;
icculus@11729
   247
    }
icculus@11729
   248
icculus@11729
   249
    if (IsMetalAvailable(&syswm) == -1) {
icculus@11729
   250
        return NULL;
icculus@11729
   251
    }
icculus@11729
   252
slouken@11732
   253
    renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
slouken@11732
   254
    if (!renderer) {
icculus@11729
   255
        SDL_OutOfMemory();
icculus@11729
   256
        return NULL;
icculus@11729
   257
    }
icculus@11729
   258
slouken@11732
   259
    data = [[METAL_RenderData alloc] init];
slouken@11735
   260
    data.beginScene = YES;
icculus@11729
   261
slouken@11732
   262
#if __has_feature(objc_arc)
slouken@11732
   263
    renderer->driverdata = (void*)CFBridgingRetain(data);
slouken@11732
   264
#else
icculus@11729
   265
    renderer->driverdata = data;
slouken@11732
   266
#endif
icculus@11729
   267
    renderer->window = window;
icculus@11729
   268
slouken@11730
   269
#ifdef __MACOSX__
slouken@11730
   270
    id<MTLDevice> mtldevice = MTLCreateSystemDefaultDevice();  // !!! FIXME: MTLCopyAllDevices() can find other GPUs...
slouken@11730
   271
    if (mtldevice == nil) {
icculus@11729
   272
        SDL_free(renderer);
slouken@11732
   273
#if !__has_feature(objc_arc)
slouken@11732
   274
        [data release];
slouken@11732
   275
#endif
icculus@11729
   276
        SDL_SetError("Failed to obtain Metal device");
icculus@11729
   277
        return NULL;
icculus@11729
   278
    }
icculus@11729
   279
icculus@11729
   280
    // !!! FIXME: error checking on all of this.
icculus@11729
   281
icculus@11729
   282
    NSView *nsview = [syswm.info.cocoa.window contentView];
icculus@11729
   283
slouken@11730
   284
    // CAMetalLayer is available in QuartzCore starting at OSX 10.11
slouken@11730
   285
    CAMetalLayer *layer = [NSClassFromString( @"CAMetalLayer" ) layer];
icculus@11729
   286
slouken@11730
   287
    layer.device = mtldevice;
icculus@11729
   288
    //layer.pixelFormat = MTLPixelFormatBGRA8Unorm;  // !!! FIXME: MTLPixelFormatBGRA8Unorm_sRGB ?
icculus@11729
   289
    layer.framebufferOnly = YES;
icculus@11729
   290
    //layer.drawableSize = (CGSize) [nsview convertRectToBacking:[nsview bounds]].size;
icculus@11729
   291
    //layer.colorspace = nil;
icculus@11729
   292
icculus@11729
   293
    [nsview setWantsLayer:YES];
icculus@11729
   294
    [nsview setLayer:layer];
icculus@11729
   295
icculus@11729
   296
    [layer retain];
slouken@11730
   297
#else
slouken@11732
   298
    UIView *view = UIKit_Mtl_AddMetalView(window);
slouken@11732
   299
    CAMetalLayer *layer = (CAMetalLayer *)[view layer];
slouken@11730
   300
#endif
slouken@11730
   301
slouken@11732
   302
    data.mtldevice = layer.device;
slouken@11732
   303
    data.mtllayer = layer;
slouken@11732
   304
    data.mtlcmdqueue = [data.mtldevice newCommandQueue];
slouken@11732
   305
    data.mtlcmdqueue.label = @"SDL Metal Renderer";
icculus@11749
   306
    data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
icculus@11729
   307
slouken@11735
   308
    NSError *err = nil;
icculus@11729
   309
slouken@11735
   310
    // The compiled .metallib is embedded in a static array in a header file
slouken@11735
   311
    // but the original shader source code is in SDL_shaders_metal.metal.
slouken@11735
   312
    dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
slouken@11735
   313
    data.mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
slouken@11735
   314
    SDL_assert(err == nil);
slouken@11735
   315
#if !__has_feature(objc_arc)
slouken@11735
   316
    dispatch_release(mtllibdata);
slouken@11735
   317
#endif
slouken@11735
   318
    data.mtllibrary.label = @"SDL Metal renderer shader library";
icculus@11729
   319
slouken@11735
   320
    data.mtlpipelineprims = [[NSMutableArray alloc] init];
slouken@11751
   321
    MakePipelineStates(data, data.mtlpipelineprims, @"SDL primitives pipeline", @"SDL_Solid_vertex", @"SDL_Solid_fragment");
slouken@11753
   322
    data.mtlpipelinecopynearest = [[NSMutableArray alloc] init];
slouken@11753
   323
    MakePipelineStates(data, data.mtlpipelinecopynearest, @"SDL texture pipeline (nearest)", @"SDL_Copy_vertex", @"SDL_Copy_fragment_nearest");
slouken@11753
   324
    data.mtlpipelinecopylinear = [[NSMutableArray alloc] init];
slouken@11753
   325
    MakePipelineStates(data, data.mtlpipelinecopylinear, @"SDL texture pipeline (linear)", @"SDL_Copy_vertex", @"SDL_Copy_fragment_linear");
slouken@11735
   326
slime73@11789
   327
    static const float clearverts[] = { 0, 0,  0, 3,  3, 0 };
slouken@11735
   328
    data.mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
slouken@11735
   329
    data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
slouken@11735
   330
slouken@11735
   331
    // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
icculus@11729
   332
icculus@11729
   333
    renderer->WindowEvent = METAL_WindowEvent;
icculus@11729
   334
    renderer->GetOutputSize = METAL_GetOutputSize;
icculus@11729
   335
    renderer->CreateTexture = METAL_CreateTexture;
icculus@11729
   336
    renderer->UpdateTexture = METAL_UpdateTexture;
icculus@11729
   337
    renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
icculus@11729
   338
    renderer->LockTexture = METAL_LockTexture;
icculus@11729
   339
    renderer->UnlockTexture = METAL_UnlockTexture;
icculus@11729
   340
    renderer->SetRenderTarget = METAL_SetRenderTarget;
icculus@11729
   341
    renderer->UpdateViewport = METAL_UpdateViewport;
icculus@11729
   342
    renderer->UpdateClipRect = METAL_UpdateClipRect;
icculus@11729
   343
    renderer->RenderClear = METAL_RenderClear;
icculus@11729
   344
    renderer->RenderDrawPoints = METAL_RenderDrawPoints;
icculus@11729
   345
    renderer->RenderDrawLines = METAL_RenderDrawLines;
icculus@11729
   346
    renderer->RenderFillRects = METAL_RenderFillRects;
icculus@11729
   347
    renderer->RenderCopy = METAL_RenderCopy;
icculus@11729
   348
    renderer->RenderCopyEx = METAL_RenderCopyEx;
icculus@11729
   349
    renderer->RenderReadPixels = METAL_RenderReadPixels;
icculus@11729
   350
    renderer->RenderPresent = METAL_RenderPresent;
icculus@11729
   351
    renderer->DestroyTexture = METAL_DestroyTexture;
icculus@11729
   352
    renderer->DestroyRenderer = METAL_DestroyRenderer;
slouken@11744
   353
    renderer->GetMetalLayer = METAL_GetMetalLayer;
slouken@11744
   354
    renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
icculus@11729
   355
icculus@11729
   356
    renderer->info = METAL_RenderDriver.info;
icculus@11729
   357
    renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
icculus@11729
   358
icculus@11729
   359
    // !!! FIXME: how do you control this in Metal?
icculus@11729
   360
    renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
icculus@11729
   361
slouken@11735
   362
    return renderer;
slouken@11735
   363
}
icculus@11729
   364
slouken@11735
   365
static void METAL_ActivateRenderer(SDL_Renderer * renderer)
slouken@11735
   366
{
slouken@11735
   367
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   368
slouken@11735
   369
    if (data.beginScene) {
slouken@11735
   370
        data.beginScene = NO;
slouken@11735
   371
        data.mtlbackbuffer = [data.mtllayer nextDrawable];
slouken@11735
   372
        SDL_assert(data.mtlbackbuffer);
slouken@11735
   373
        data.mtlpassdesc.colorAttachments[0].texture = data.mtlbackbuffer.texture;
slouken@11735
   374
        data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionDontCare;
slouken@11735
   375
        data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
slouken@11735
   376
        data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
icculus@11750
   377
        data.mtlcmdencoder.label = @"SDL metal renderer start of frame";
icculus@11729
   378
slouken@11735
   379
        // Set up our current renderer state for the next frame...
slouken@11735
   380
        METAL_UpdateViewport(renderer);
slouken@11735
   381
        METAL_UpdateClipRect(renderer);
slouken@11735
   382
    }
icculus@11729
   383
}
icculus@11729
   384
icculus@11729
   385
static void
icculus@11729
   386
METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
icculus@11729
   387
{
icculus@11729
   388
    if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
icculus@11729
   389
        event->event == SDL_WINDOWEVENT_SHOWN ||
icculus@11729
   390
        event->event == SDL_WINDOWEVENT_HIDDEN) {
icculus@11729
   391
        // !!! FIXME: write me
icculus@11729
   392
    }
icculus@11729
   393
}
icculus@11729
   394
icculus@11729
   395
static int
icculus@11729
   396
METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
slouken@11743
   397
{ @autoreleasepool {
slouken@11735
   398
    METAL_ActivateRenderer(renderer);
slouken@11732
   399
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11732
   400
    *w = (int) data.mtlbackbuffer.texture.width;
slouken@11732
   401
    *h = (int) data.mtlbackbuffer.texture.height;
icculus@11729
   402
    return 0;
slouken@11743
   403
}}
icculus@11729
   404
icculus@11729
   405
static int
icculus@11729
   406
METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
   407
{ @autoreleasepool {
slouken@11732
   408
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   409
    MTLPixelFormat mtlpixfmt;
icculus@11729
   410
icculus@11729
   411
    switch (texture->format) {
icculus@11729
   412
        case SDL_PIXELFORMAT_ABGR8888: mtlpixfmt = MTLPixelFormatRGBA8Unorm; break;
icculus@11729
   413
        case SDL_PIXELFORMAT_ARGB8888: mtlpixfmt = MTLPixelFormatBGRA8Unorm; break;
icculus@11729
   414
        default: return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
icculus@11729
   415
    }
icculus@11729
   416
icculus@11729
   417
    MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mtlpixfmt
icculus@11729
   418
                                            width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
icculus@11750
   419
 
icculus@11750
   420
    if (texture->access == SDL_TEXTUREACCESS_TARGET) {
icculus@11750
   421
        mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
icculus@11750
   422
    } else {
icculus@11750
   423
        mtltexdesc.usage = MTLTextureUsageShaderRead;
icculus@11750
   424
    }
icculus@11750
   425
    //mtltexdesc.resourceOptions = MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged;
icculus@11750
   426
    //mtltexdesc.storageMode = MTLStorageModeManaged;
icculus@11750
   427
    
slouken@11732
   428
    id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
icculus@11729
   429
    if (mtltexture == nil) {
icculus@11729
   430
        return SDL_SetError("Texture allocation failed");
icculus@11729
   431
    }
icculus@11729
   432
slouken@11753
   433
    METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
slouken@11753
   434
    const char *hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY);
slouken@11753
   435
    if (!hint || *hint == '0' || SDL_strcasecmp(hint, "nearest") == 0) {
slouken@11753
   436
        texturedata.mtlpipeline = data.mtlpipelinecopynearest;
slouken@11753
   437
    } else {
slouken@11753
   438
        texturedata.mtlpipeline = data.mtlpipelinecopylinear;
slouken@11753
   439
    }
slouken@11753
   440
    texturedata.mtltexture = mtltexture;
slouken@11753
   441
slouken@11753
   442
    texture->driverdata = (void*)CFBridgingRetain(texturedata);
icculus@11729
   443
icculus@11729
   444
    return 0;
slouken@11743
   445
}}
icculus@11729
   446
icculus@11729
   447
static int
icculus@11729
   448
METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   449
                 const SDL_Rect * rect, const void *pixels, int pitch)
slouken@11743
   450
{ @autoreleasepool {
icculus@11729
   451
    // !!! FIXME: this is a synchronous call; it doesn't return until data is uploaded in some form.
icculus@11729
   452
    // !!! FIXME:  Maybe move this off to a thread that marks the texture as uploaded and only stall the main thread if we try to
icculus@11729
   453
    // !!! FIXME:  use this texture before the marking is done? Is it worth it? Or will we basically always be uploading a bunch of
icculus@11729
   454
    // !!! FIXME:  stuff way ahead of time and/or using it immediately after upload?
slouken@11753
   455
    id<MTLTexture> mtltexture = ((__bridge METAL_TextureData *)texture->driverdata).mtltexture;
icculus@11729
   456
    [mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h) mipmapLevel:0 withBytes:pixels bytesPerRow:pitch];
icculus@11729
   457
    return 0;
slouken@11743
   458
}}
icculus@11729
   459
icculus@11729
   460
static int
icculus@11729
   461
METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   462
                    const SDL_Rect * rect,
icculus@11729
   463
                    const Uint8 *Yplane, int Ypitch,
icculus@11729
   464
                    const Uint8 *Uplane, int Upitch,
icculus@11729
   465
                    const Uint8 *Vplane, int Vpitch)
icculus@11729
   466
{
icculus@11729
   467
    return SDL_Unsupported();  // !!! FIXME
icculus@11729
   468
}
icculus@11729
   469
icculus@11729
   470
static int
icculus@11729
   471
METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   472
               const SDL_Rect * rect, void **pixels, int *pitch)
icculus@11729
   473
{
icculus@11729
   474
    return SDL_Unsupported();   // !!! FIXME: write me
icculus@11729
   475
}
icculus@11729
   476
icculus@11729
   477
static void
icculus@11729
   478
METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
icculus@11729
   479
{
icculus@11729
   480
    // !!! FIXME: write me
icculus@11729
   481
}
icculus@11729
   482
icculus@11729
   483
static int
icculus@11729
   484
METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
   485
{ @autoreleasepool {
slouken@11735
   486
    METAL_ActivateRenderer(renderer);
slouken@11732
   487
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11750
   488
icculus@11750
   489
    // commit the current command buffer, so that any work on a render target
icculus@11750
   490
    //  will be available to the next one we're about to queue up.
icculus@11750
   491
    [data.mtlcmdencoder endEncoding];
icculus@11750
   492
    [data.mtlcmdbuffer commit];
icculus@11750
   493
slouken@11753
   494
    id<MTLTexture> mtltexture = texture ? ((__bridge METAL_TextureData *)texture->driverdata).mtltexture : data.mtlbackbuffer.texture;
slouken@11732
   495
    data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
icculus@11750
   496
    // !!! FIXME: this can be MTLLoadActionDontCare for textures (not the backbuffer) if SDL doesn't guarantee the texture contents should survive.
icculus@11750
   497
    data.mtlpassdesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
icculus@11750
   498
    data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
icculus@11750
   499
    data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
icculus@11750
   500
    data.mtlcmdencoder.label = texture ? @"SDL metal renderer render texture" : @"SDL metal renderer backbuffer";
icculus@11750
   501
icculus@11750
   502
    // The higher level will reset the viewport and scissor after this call returns.
icculus@11750
   503
icculus@11729
   504
    return 0;
slouken@11743
   505
}}
icculus@11729
   506
icculus@11729
   507
static int
slime73@11789
   508
METAL_SetOrthographicProjection(SDL_Renderer *renderer, int w, int h)
slime73@11789
   509
{ @autoreleasepool {
slime73@11789
   510
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slime73@11789
   511
    float projection[4][4];
slime73@11789
   512
slime73@11789
   513
    if (!w || !h) {
slime73@11789
   514
        return 0;
slime73@11789
   515
    }
slime73@11789
   516
slime73@11789
   517
    /* Prepare an orthographic projection */
slime73@11789
   518
    projection[0][0] = 2.0f / w;
slime73@11789
   519
    projection[0][1] = 0.0f;
slime73@11789
   520
    projection[0][2] = 0.0f;
slime73@11789
   521
    projection[0][3] = 0.0f;
slime73@11789
   522
    projection[1][0] = 0.0f;
slime73@11789
   523
    projection[1][1] = -2.0f / h;
slime73@11789
   524
    projection[1][2] = 0.0f;
slime73@11789
   525
    projection[1][3] = 0.0f;
slime73@11789
   526
    projection[2][0] = 0.0f;
slime73@11789
   527
    projection[2][1] = 0.0f;
slime73@11789
   528
    projection[2][2] = 0.0f;
slime73@11789
   529
    projection[2][3] = 0.0f;
slime73@11789
   530
    projection[3][0] = -1.0f;
slime73@11789
   531
    projection[3][1] = 1.0f;
slime73@11789
   532
    projection[3][2] = 0.0f;
slime73@11789
   533
    projection[3][3] = 1.0f;
slime73@11789
   534
slime73@11789
   535
    // !!! FIXME: This should be in a buffer...
slime73@11789
   536
    [data.mtlcmdencoder setVertexBytes:projection length:sizeof(float)*16 atIndex:2];
slime73@11789
   537
    return 0;
slime73@11789
   538
}}
slime73@11789
   539
slime73@11789
   540
static int
icculus@11729
   541
METAL_UpdateViewport(SDL_Renderer * renderer)
slouken@11743
   542
{ @autoreleasepool {
slouken@11735
   543
    METAL_ActivateRenderer(renderer);
slouken@11732
   544
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11735
   545
    MTLViewport viewport;
slouken@11735
   546
    viewport.originX = renderer->viewport.x;
slouken@11735
   547
    viewport.originY = renderer->viewport.y;
slouken@11735
   548
    viewport.width = renderer->viewport.w;
slouken@11735
   549
    viewport.height = renderer->viewport.h;
slouken@11735
   550
    viewport.znear = 0.0;
slouken@11735
   551
    viewport.zfar = 1.0;
slouken@11735
   552
    [data.mtlcmdencoder setViewport:viewport];
slime73@11789
   553
    METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
icculus@11729
   554
    return 0;
slouken@11743
   555
}}
icculus@11729
   556
icculus@11729
   557
static int
icculus@11729
   558
METAL_UpdateClipRect(SDL_Renderer * renderer)
slouken@11743
   559
{ @autoreleasepool {
slouken@11735
   560
    METAL_ActivateRenderer(renderer);
slouken@11732
   561
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11735
   562
    MTLScissorRect mtlrect;
slouken@11743
   563
    // !!! FIXME: should this care about the viewport?
slouken@11735
   564
    if (renderer->clipping_enabled) {
slouken@11735
   565
        const SDL_Rect *rect = &renderer->clip_rect;
slouken@11735
   566
        mtlrect.x = renderer->viewport.x + rect->x;
slouken@11735
   567
        mtlrect.y = renderer->viewport.x + rect->y;
slouken@11735
   568
        mtlrect.width = rect->w;
slouken@11735
   569
        mtlrect.height = rect->h;
slouken@11735
   570
    } else {
slouken@11735
   571
        mtlrect.x = renderer->viewport.x;
slouken@11735
   572
        mtlrect.y = renderer->viewport.y;
slouken@11735
   573
        mtlrect.width = renderer->viewport.w;
slouken@11735
   574
        mtlrect.height = renderer->viewport.h;
slouken@11735
   575
    }
slouken@11735
   576
    if (mtlrect.width > 0 && mtlrect.height > 0) {
slouken@11735
   577
        [data.mtlcmdencoder setScissorRect:mtlrect];
icculus@11729
   578
    }
icculus@11729
   579
    return 0;
slouken@11743
   580
}}
icculus@11729
   581
icculus@11729
   582
static int
icculus@11729
   583
METAL_RenderClear(SDL_Renderer * renderer)
slouken@11743
   584
{ @autoreleasepool {
icculus@11729
   585
    // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
slouken@11735
   586
    METAL_ActivateRenderer(renderer);
slouken@11732
   587
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   588
icculus@11729
   589
    // !!! FIXME: render color should live in a dedicated uniform buffer.
icculus@11729
   590
    const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
icculus@11729
   591
icculus@11729
   592
    MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
icculus@11729
   593
    viewport.originX = viewport.originY = 0.0;
icculus@11750
   594
    viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
icculus@11750
   595
    viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
icculus@11729
   596
    viewport.znear = 0.0;
icculus@11729
   597
    viewport.zfar = 1.0;
icculus@11729
   598
slime73@11788
   599
    // Draw a simple filled fullscreen triangle now.
slime73@11789
   600
    METAL_SetOrthographicProjection(renderer, 1, 1);
slouken@11732
   601
    [data.mtlcmdencoder setViewport:viewport];
slouken@11732
   602
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
slouken@11732
   603
    [data.mtlcmdencoder setVertexBuffer:data.mtlbufclearverts offset:0 atIndex:0];
slouken@11732
   604
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
slime73@11788
   605
    [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
icculus@11729
   606
icculus@11729
   607
    // reset the viewport for the rest of our usual drawing work...
icculus@11729
   608
    viewport.originX = renderer->viewport.x;
icculus@11729
   609
    viewport.originY = renderer->viewport.y;
icculus@11729
   610
    viewport.width = renderer->viewport.w;
icculus@11729
   611
    viewport.height = renderer->viewport.h;
icculus@11729
   612
    viewport.znear = 0.0;
icculus@11729
   613
    viewport.zfar = 1.0;
slouken@11732
   614
    [data.mtlcmdencoder setViewport:viewport];
slime73@11789
   615
    METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
icculus@11729
   616
icculus@11729
   617
    return 0;
slouken@11743
   618
}}
icculus@11729
   619
slouken@11755
   620
// normalize a value from 0.0f to len into 0.0f to 1.0f.
slouken@11755
   621
static inline float
slouken@11755
   622
normtex(const float _val, const float len)
slouken@11755
   623
{
slouken@11755
   624
    const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
slouken@11755
   625
    return ((val + 0.5f) / len);
slouken@11755
   626
}
slouken@11755
   627
icculus@11729
   628
static int
icculus@11729
   629
DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
icculus@11729
   630
          const MTLPrimitiveType primtype)
slouken@11743
   631
{ @autoreleasepool {
slouken@11735
   632
    METAL_ActivateRenderer(renderer);
slouken@11735
   633
slime73@11789
   634
    const size_t vertlen = sizeof(SDL_FPoint) * count;
slouken@11732
   635
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   636
icculus@11729
   637
    // !!! FIXME: render color should live in a dedicated uniform buffer.
icculus@11729
   638
    const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
icculus@11729
   639
slouken@11732
   640
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
slouken@11732
   641
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
slime73@11789
   642
    [data.mtlcmdencoder setVertexBytes:points length:vertlen atIndex:0];
slouken@11732
   643
    [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
icculus@11729
   644
    return 0;
slouken@11743
   645
}}
icculus@11729
   646
icculus@11729
   647
static int
icculus@11729
   648
METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
icculus@11729
   649
{
icculus@11729
   650
    return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint);
icculus@11729
   651
}
icculus@11729
   652
icculus@11729
   653
static int
icculus@11729
   654
METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
icculus@11729
   655
{
icculus@11729
   656
    return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip);
icculus@11729
   657
}
icculus@11729
   658
icculus@11729
   659
static int
icculus@11729
   660
METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
slouken@11743
   661
{ @autoreleasepool {
slouken@11735
   662
    METAL_ActivateRenderer(renderer);
slouken@11732
   663
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   664
icculus@11729
   665
    // !!! FIXME: render color should live in a dedicated uniform buffer.
icculus@11729
   666
    const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
icculus@11729
   667
slouken@11732
   668
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
slouken@11732
   669
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
icculus@11729
   670
icculus@11729
   671
    for (int i = 0; i < count; i++, rects++) {
icculus@11729
   672
        if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
icculus@11729
   673
icculus@11729
   674
        const float verts[] = {
slime73@11789
   675
            rects->x, rects->y + rects->h,
slime73@11789
   676
            rects->x, rects->y,
slime73@11789
   677
            rects->x + rects->w, rects->y + rects->h,
slime73@11789
   678
            rects->x + rects->w, rects->y,
icculus@11729
   679
        };
icculus@11729
   680
slouken@11732
   681
        [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
slime73@11788
   682
        [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
icculus@11729
   683
    }
icculus@11729
   684
icculus@11729
   685
    return 0;
slouken@11743
   686
}}
icculus@11729
   687
icculus@11729
   688
static int
icculus@11729
   689
METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   690
              const SDL_Rect * srcrect, const SDL_FRect * dstrect)
slouken@11743
   691
{ @autoreleasepool {
slouken@11735
   692
    METAL_ActivateRenderer(renderer);
slouken@11732
   693
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11753
   694
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
slouken@11753
   695
    const float texw = (float) texturedata.mtltexture.width;
slouken@11753
   696
    const float texh = (float) texturedata.mtltexture.height;
icculus@11729
   697
icculus@11729
   698
    const float xy[] = {
slime73@11789
   699
        dstrect->x, dstrect->y + dstrect->h,
slime73@11789
   700
        dstrect->x, dstrect->y,
slime73@11789
   701
        dstrect->x + dstrect->w, dstrect->y + dstrect->h,
slime73@11789
   702
        dstrect->x + dstrect->w, dstrect->y
icculus@11729
   703
    };
icculus@11729
   704
icculus@11729
   705
    const float uv[] = {
slouken@11755
   706
        normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
slouken@11755
   707
        normtex(srcrect->x, texw), normtex(srcrect->y, texh),
slime73@11788
   708
        normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y + srcrect->h, texh),
slime73@11788
   709
        normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh)
icculus@11729
   710
    };
icculus@11729
   711
icculus@11729
   712
    float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
icculus@11729
   713
    if (texture->modMode) {
icculus@11729
   714
        color[0] = ((float)texture->r) / 255.0f;
icculus@11729
   715
        color[1] = ((float)texture->g) / 255.0f;
icculus@11729
   716
        color[2] = ((float)texture->b) / 255.0f;
icculus@11729
   717
        color[3] = ((float)texture->a) / 255.0f;
icculus@11729
   718
    }
icculus@11729
   719
slouken@11753
   720
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(texturedata.mtlpipeline, texture->blendMode)];
slouken@11732
   721
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
slouken@11753
   722
    [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
slouken@11732
   723
    [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
slouken@11732
   724
    [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
slime73@11788
   725
    [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
icculus@11729
   726
icculus@11729
   727
    return 0;
slouken@11743
   728
}}
icculus@11729
   729
icculus@11729
   730
static int
icculus@11729
   731
METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   732
              const SDL_Rect * srcrect, const SDL_FRect * dstrect,
icculus@11729
   733
              const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
icculus@11729
   734
{
icculus@11729
   735
    return SDL_Unsupported();  // !!! FIXME
icculus@11729
   736
}
icculus@11729
   737
icculus@11729
   738
static int
icculus@11729
   739
METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
icculus@11729
   740
                    Uint32 pixel_format, void * pixels, int pitch)
slouken@11743
   741
{ @autoreleasepool {
slouken@11735
   742
    METAL_ActivateRenderer(renderer);
icculus@11749
   743
    // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
slouken@11732
   744
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11732
   745
    MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
icculus@11729
   746
    id<MTLTexture> mtltexture = colorAttachment.texture;
icculus@11729
   747
    MTLRegion mtlregion;
icculus@11729
   748
icculus@11729
   749
    mtlregion.origin.x = rect->x;
icculus@11729
   750
    mtlregion.origin.y = rect->y;
icculus@11729
   751
    mtlregion.origin.z = 0;
icculus@11729
   752
    mtlregion.size.width = rect->w;
icculus@11729
   753
    mtlregion.size.height = rect->w;
icculus@11729
   754
    mtlregion.size.depth = 1;
icculus@11729
   755
icculus@11729
   756
    // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
icculus@11729
   757
    const int temp_pitch = rect->w * 4;
icculus@11729
   758
    void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
icculus@11729
   759
    if (!temp_pixels) {
icculus@11729
   760
        return SDL_OutOfMemory();
icculus@11729
   761
    }
icculus@11729
   762
icculus@11729
   763
    [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
icculus@11729
   764
icculus@11729
   765
    const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
icculus@11729
   766
    const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
icculus@11729
   767
    SDL_free(temp_pixels);
icculus@11729
   768
    return status;
slouken@11743
   769
}}
icculus@11729
   770
icculus@11729
   771
static void
icculus@11729
   772
METAL_RenderPresent(SDL_Renderer * renderer)
slouken@11743
   773
{ @autoreleasepool {
slouken@11735
   774
    METAL_ActivateRenderer(renderer);
slouken@11732
   775
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   776
slouken@11732
   777
    [data.mtlcmdencoder endEncoding];
slouken@11741
   778
    [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
slouken@11732
   779
    [data.mtlcmdbuffer commit];
slouken@11741
   780
    data.mtlcmdencoder = nil;
slouken@11741
   781
    data.mtlcmdbuffer = nil;
slouken@11741
   782
    data.mtlbackbuffer = nil;
slouken@11735
   783
    data.beginScene = YES;
slouken@11743
   784
}}
icculus@11729
   785
icculus@11729
   786
static void
icculus@11729
   787
METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
   788
{ @autoreleasepool {
slouken@11753
   789
    METAL_TextureData *texturedata = CFBridgingRelease(texture->driverdata);
slouken@11751
   790
#if __has_feature(objc_arc)
slouken@11753
   791
    texturedata = nil;
slouken@11751
   792
#else
slouken@11753
   793
    [texturedata.mtltexture release];
slouken@11753
   794
    [texturedata release];
slouken@11732
   795
#endif
icculus@11729
   796
    texture->driverdata = NULL;
slouken@11743
   797
}}
icculus@11729
   798
icculus@11729
   799
static void
icculus@11729
   800
METAL_DestroyRenderer(SDL_Renderer * renderer)
slouken@11743
   801
{ @autoreleasepool {
slouken@11732
   802
    if (renderer->driverdata) {
slouken@11732
   803
        METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
icculus@11729
   804
slouken@11741
   805
        if (data.mtlcmdencoder != nil) {
slouken@11741
   806
            [data.mtlcmdencoder endEncoding];
slouken@11741
   807
        }
icculus@11746
   808
icculus@11746
   809
#if !__has_feature(objc_arc)
icculus@11746
   810
        [data.mtlbackbuffer release];
icculus@11746
   811
        [data.mtlcmdencoder release];
icculus@11746
   812
        [data.mtlcmdbuffer release];
slouken@11732
   813
        [data.mtlcmdqueue release];
slouken@11741
   814
        for (int i = 0; i < 4; i++) {
slouken@11732
   815
            [data.mtlpipelineprims[i] release];
slouken@11759
   816
            [data.mtlpipelinecopynearest[i] release];
slouken@11759
   817
            [data.mtlpipelinecopylinear[i] release];
icculus@11729
   818
        }
slouken@11741
   819
        [data.mtlpipelineprims release];
slouken@11759
   820
        [data.mtlpipelinecopynearest release];
slouken@11759
   821
        [data.mtlpipelinecopylinear release];
slouken@11732
   822
        [data.mtlbufclearverts release];
slouken@11732
   823
        [data.mtllibrary release];
slouken@11732
   824
        [data.mtldevice release];
slouken@11732
   825
        [data.mtlpassdesc release];
slouken@11732
   826
        [data.mtllayer release];
slouken@11732
   827
#endif
icculus@11729
   828
    }
icculus@11746
   829
icculus@11729
   830
    SDL_free(renderer);
slouken@11743
   831
}}
icculus@11729
   832
slouken@11744
   833
void *METAL_GetMetalLayer(SDL_Renderer * renderer)
slouken@11744
   834
{ @autoreleasepool {
slouken@11744
   835
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11744
   836
    return (__bridge void*)data.mtllayer;
slouken@11744
   837
}}
slouken@11744
   838
slouken@11744
   839
void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
slouken@11744
   840
{ @autoreleasepool {
slouken@11744
   841
    METAL_ActivateRenderer(renderer);
slouken@11744
   842
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11744
   843
    return (__bridge void*)data.mtlcmdencoder;
slouken@11744
   844
}}
slouken@11744
   845
icculus@11729
   846
#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
icculus@11729
   847
icculus@11729
   848
/* vi: set ts=4 sw=4 expandtab: */