src/render/metal/SDL_render_metal.m
author Sam Lantinga <slouken@libsdl.org>
Sat, 09 Dec 2017 19:48:38 -0800
changeset 11755 e9a1b9ea8da0
parent 11754 09f7bead4e7c
child 11759 05cdab5a00f8
permissions -rw-r--r--
Backed out using pixel texture coordinates, it had weird visual side effects
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
slouken@11735
   327
    static const float clearverts[] = { -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 };
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
icculus@11729
   508
METAL_UpdateViewport(SDL_Renderer * renderer)
slouken@11743
   509
{ @autoreleasepool {
slouken@11735
   510
    METAL_ActivateRenderer(renderer);
slouken@11732
   511
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11735
   512
    MTLViewport viewport;
slouken@11735
   513
    viewport.originX = renderer->viewport.x;
slouken@11735
   514
    viewport.originY = renderer->viewport.y;
slouken@11735
   515
    viewport.width = renderer->viewport.w;
slouken@11735
   516
    viewport.height = renderer->viewport.h;
slouken@11735
   517
    viewport.znear = 0.0;
slouken@11735
   518
    viewport.zfar = 1.0;
slouken@11735
   519
    [data.mtlcmdencoder setViewport:viewport];
icculus@11729
   520
    return 0;
slouken@11743
   521
}}
icculus@11729
   522
icculus@11729
   523
static int
icculus@11729
   524
METAL_UpdateClipRect(SDL_Renderer * renderer)
slouken@11743
   525
{ @autoreleasepool {
slouken@11735
   526
    METAL_ActivateRenderer(renderer);
slouken@11732
   527
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11735
   528
    MTLScissorRect mtlrect;
slouken@11743
   529
    // !!! FIXME: should this care about the viewport?
slouken@11735
   530
    if (renderer->clipping_enabled) {
slouken@11735
   531
        const SDL_Rect *rect = &renderer->clip_rect;
slouken@11735
   532
        mtlrect.x = renderer->viewport.x + rect->x;
slouken@11735
   533
        mtlrect.y = renderer->viewport.x + rect->y;
slouken@11735
   534
        mtlrect.width = rect->w;
slouken@11735
   535
        mtlrect.height = rect->h;
slouken@11735
   536
    } else {
slouken@11735
   537
        mtlrect.x = renderer->viewport.x;
slouken@11735
   538
        mtlrect.y = renderer->viewport.y;
slouken@11735
   539
        mtlrect.width = renderer->viewport.w;
slouken@11735
   540
        mtlrect.height = renderer->viewport.h;
slouken@11735
   541
    }
slouken@11735
   542
    if (mtlrect.width > 0 && mtlrect.height > 0) {
slouken@11735
   543
        [data.mtlcmdencoder setScissorRect:mtlrect];
icculus@11729
   544
    }
icculus@11729
   545
    return 0;
slouken@11743
   546
}}
icculus@11729
   547
icculus@11729
   548
static int
icculus@11729
   549
METAL_RenderClear(SDL_Renderer * renderer)
slouken@11743
   550
{ @autoreleasepool {
icculus@11729
   551
    // We could dump the command buffer and force a clear on a new one, but this will respect the scissor state.
slouken@11735
   552
    METAL_ActivateRenderer(renderer);
slouken@11732
   553
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   554
icculus@11729
   555
    // !!! FIXME: render color should live in a dedicated uniform buffer.
icculus@11729
   556
    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
   557
icculus@11729
   558
    MTLViewport viewport;  // RenderClear ignores the viewport state, though, so reset that.
icculus@11729
   559
    viewport.originX = viewport.originY = 0.0;
icculus@11750
   560
    viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width;
icculus@11750
   561
    viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height;
icculus@11729
   562
    viewport.znear = 0.0;
icculus@11729
   563
    viewport.zfar = 1.0;
icculus@11729
   564
icculus@11729
   565
    // Draw as if we're doing a simple filled rect to the screen now.
slouken@11732
   566
    [data.mtlcmdencoder setViewport:viewport];
slouken@11732
   567
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
slouken@11732
   568
    [data.mtlcmdencoder setVertexBuffer:data.mtlbufclearverts offset:0 atIndex:0];
slouken@11732
   569
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
slouken@11732
   570
    [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
icculus@11729
   571
icculus@11729
   572
    // reset the viewport for the rest of our usual drawing work...
icculus@11729
   573
    viewport.originX = renderer->viewport.x;
icculus@11729
   574
    viewport.originY = renderer->viewport.y;
icculus@11729
   575
    viewport.width = renderer->viewport.w;
icculus@11729
   576
    viewport.height = renderer->viewport.h;
icculus@11729
   577
    viewport.znear = 0.0;
icculus@11729
   578
    viewport.zfar = 1.0;
slouken@11732
   579
    [data.mtlcmdencoder setViewport:viewport];
icculus@11729
   580
icculus@11729
   581
    return 0;
slouken@11743
   582
}}
icculus@11729
   583
icculus@11729
   584
// normalize a value from 0.0f to len into -1.0f to 1.0f.
icculus@11729
   585
static inline float
slouken@11751
   586
normx(const float _val, const float len)
icculus@11729
   587
{
icculus@11729
   588
    const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
slouken@11751
   589
    return (((val + 0.5f) / len) * 2.0f) - 1.0f;
icculus@11729
   590
}
icculus@11729
   591
icculus@11729
   592
// normalize a value from 0.0f to len into -1.0f to 1.0f.
icculus@11729
   593
static inline float
icculus@11729
   594
normy(const float _val, const float len)
icculus@11729
   595
{
slouken@11751
   596
    const float val = (_val <= 0.0f) ? len : (_val >= len) ? 0.0f : (len - _val);
slouken@11751
   597
    return (((val - 0.5f) / len) * 2.0f) - 1.0f;
icculus@11729
   598
}
icculus@11729
   599
slouken@11755
   600
// normalize a value from 0.0f to len into 0.0f to 1.0f.
slouken@11755
   601
static inline float
slouken@11755
   602
normtex(const float _val, const float len)
slouken@11755
   603
{
slouken@11755
   604
    const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
slouken@11755
   605
    return ((val + 0.5f) / len);
slouken@11755
   606
}
slouken@11755
   607
icculus@11729
   608
static int
icculus@11729
   609
DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
icculus@11729
   610
          const MTLPrimitiveType primtype)
slouken@11743
   611
{ @autoreleasepool {
slouken@11735
   612
    METAL_ActivateRenderer(renderer);
slouken@11735
   613
icculus@11729
   614
    const size_t vertlen = (sizeof (float) * 2) * count;
icculus@11729
   615
    float *verts = SDL_malloc(vertlen);
icculus@11729
   616
    if (!verts) {
icculus@11729
   617
        return SDL_OutOfMemory();
icculus@11729
   618
    }
icculus@11729
   619
slouken@11732
   620
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   621
icculus@11729
   622
    // !!! FIXME: render color should live in a dedicated uniform buffer.
icculus@11729
   623
    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
   624
slouken@11732
   625
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
slouken@11732
   626
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
icculus@11729
   627
slouken@11754
   628
    const float w = (float)renderer->viewport.w;
slouken@11754
   629
    const float h = (float)renderer->viewport.h;
icculus@11729
   630
icculus@11729
   631
    // !!! FIXME: we can convert this in the shader. This will save the malloc and for-loop, but we still need to upload.
icculus@11729
   632
    float *ptr = verts;
icculus@11729
   633
    for (int i = 0; i < count; i++, points++) {
slouken@11751
   634
        *ptr = normx(points->x, w); ptr++;
icculus@11729
   635
        *ptr = normy(points->y, h); ptr++;
icculus@11729
   636
    }
icculus@11729
   637
slouken@11732
   638
    [data.mtlcmdencoder setVertexBytes:verts length:vertlen atIndex:0];
slouken@11732
   639
    [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
icculus@11729
   640
icculus@11729
   641
    SDL_free(verts);
icculus@11729
   642
    return 0;
slouken@11743
   643
}}
icculus@11729
   644
icculus@11729
   645
static int
icculus@11729
   646
METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
icculus@11729
   647
{
icculus@11729
   648
    return DrawVerts(renderer, points, count, MTLPrimitiveTypePoint);
icculus@11729
   649
}
icculus@11729
   650
icculus@11729
   651
static int
icculus@11729
   652
METAL_RenderDrawLines(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
icculus@11729
   653
{
icculus@11729
   654
    return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip);
icculus@11729
   655
}
icculus@11729
   656
icculus@11729
   657
static int
icculus@11729
   658
METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count)
slouken@11743
   659
{ @autoreleasepool {
slouken@11735
   660
    METAL_ActivateRenderer(renderer);
slouken@11732
   661
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   662
icculus@11729
   663
    // !!! FIXME: render color should live in a dedicated uniform buffer.
icculus@11729
   664
    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
   665
slouken@11732
   666
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data.mtlpipelineprims, renderer->blendMode)];
slouken@11732
   667
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
icculus@11729
   668
slouken@11754
   669
    const float w = (float)renderer->viewport.w;
slouken@11754
   670
    const float h = (float)renderer->viewport.h;
icculus@11729
   671
icculus@11729
   672
    for (int i = 0; i < count; i++, rects++) {
icculus@11729
   673
        if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
icculus@11729
   674
icculus@11729
   675
        const float verts[] = {
slouken@11751
   676
            normx(rects->x, w), normy(rects->y + rects->h, h),
slouken@11751
   677
            normx(rects->x, w), normy(rects->y, h),
slouken@11751
   678
            normx(rects->x + rects->w, w), normy(rects->y, h),
slouken@11751
   679
            normx(rects->x, w), normy(rects->y + rects->h, h),
slouken@11751
   680
            normx(rects->x + rects->w, w), normy(rects->y + rects->h, h)
icculus@11729
   681
        };
icculus@11729
   682
slouken@11732
   683
        [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
slouken@11732
   684
        [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
icculus@11729
   685
    }
icculus@11729
   686
icculus@11729
   687
    return 0;
slouken@11743
   688
}}
icculus@11729
   689
icculus@11729
   690
static int
icculus@11729
   691
METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   692
              const SDL_Rect * srcrect, const SDL_FRect * dstrect)
slouken@11743
   693
{ @autoreleasepool {
slouken@11735
   694
    METAL_ActivateRenderer(renderer);
slouken@11732
   695
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11753
   696
    METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
slouken@11754
   697
    const float w = (float)renderer->viewport.w;
slouken@11754
   698
    const float h = (float)renderer->viewport.h;
slouken@11753
   699
    const float texw = (float) texturedata.mtltexture.width;
slouken@11753
   700
    const float texh = (float) texturedata.mtltexture.height;
icculus@11729
   701
icculus@11729
   702
    const float xy[] = {
slouken@11751
   703
        normx(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
slouken@11751
   704
        normx(dstrect->x, w), normy(dstrect->y, h),
slouken@11751
   705
        normx(dstrect->x + dstrect->w, w), normy(dstrect->y, h),
slouken@11751
   706
        normx(dstrect->x, w), normy(dstrect->y + dstrect->h, h),
slouken@11751
   707
        normx(dstrect->x + dstrect->w, w), normy(dstrect->y + dstrect->h, h)
icculus@11729
   708
    };
icculus@11729
   709
icculus@11729
   710
    const float uv[] = {
slouken@11755
   711
        normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
slouken@11755
   712
        normtex(srcrect->x, texw), normtex(srcrect->y, texh),
slouken@11755
   713
        normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh),
slouken@11755
   714
        normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
slouken@11755
   715
        normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y + srcrect->h, texh)
icculus@11729
   716
    };
icculus@11729
   717
icculus@11729
   718
    float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
icculus@11729
   719
    if (texture->modMode) {
icculus@11729
   720
        color[0] = ((float)texture->r) / 255.0f;
icculus@11729
   721
        color[1] = ((float)texture->g) / 255.0f;
icculus@11729
   722
        color[2] = ((float)texture->b) / 255.0f;
icculus@11729
   723
        color[3] = ((float)texture->a) / 255.0f;
icculus@11729
   724
    }
icculus@11729
   725
slouken@11753
   726
    [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(texturedata.mtlpipeline, texture->blendMode)];
slouken@11732
   727
    [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
slouken@11753
   728
    [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
slouken@11732
   729
    [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
slouken@11732
   730
    [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
slouken@11732
   731
    [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:5];
icculus@11729
   732
icculus@11729
   733
    return 0;
slouken@11743
   734
}}
icculus@11729
   735
icculus@11729
   736
static int
icculus@11729
   737
METAL_RenderCopyEx(SDL_Renderer * renderer, SDL_Texture * texture,
icculus@11729
   738
              const SDL_Rect * srcrect, const SDL_FRect * dstrect,
icculus@11729
   739
              const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
icculus@11729
   740
{
icculus@11729
   741
    return SDL_Unsupported();  // !!! FIXME
icculus@11729
   742
}
icculus@11729
   743
icculus@11729
   744
static int
icculus@11729
   745
METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
icculus@11729
   746
                    Uint32 pixel_format, void * pixels, int pitch)
slouken@11743
   747
{ @autoreleasepool {
slouken@11735
   748
    METAL_ActivateRenderer(renderer);
icculus@11749
   749
    // !!! FIXME: this probably needs to commit the current command buffer, and probably waitUntilCompleted
slouken@11732
   750
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11732
   751
    MTLRenderPassColorAttachmentDescriptor *colorAttachment = data.mtlpassdesc.colorAttachments[0];
icculus@11729
   752
    id<MTLTexture> mtltexture = colorAttachment.texture;
icculus@11729
   753
    MTLRegion mtlregion;
icculus@11729
   754
icculus@11729
   755
    mtlregion.origin.x = rect->x;
icculus@11729
   756
    mtlregion.origin.y = rect->y;
icculus@11729
   757
    mtlregion.origin.z = 0;
icculus@11729
   758
    mtlregion.size.width = rect->w;
icculus@11729
   759
    mtlregion.size.height = rect->w;
icculus@11729
   760
    mtlregion.size.depth = 1;
icculus@11729
   761
icculus@11729
   762
    // we only do BGRA8 or RGBA8 at the moment, so 4 will do.
icculus@11729
   763
    const int temp_pitch = rect->w * 4;
icculus@11729
   764
    void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
icculus@11729
   765
    if (!temp_pixels) {
icculus@11729
   766
        return SDL_OutOfMemory();
icculus@11729
   767
    }
icculus@11729
   768
icculus@11729
   769
    [mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
icculus@11729
   770
icculus@11729
   771
    const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
icculus@11729
   772
    const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
icculus@11729
   773
    SDL_free(temp_pixels);
icculus@11729
   774
    return status;
slouken@11743
   775
}}
icculus@11729
   776
icculus@11729
   777
static void
icculus@11729
   778
METAL_RenderPresent(SDL_Renderer * renderer)
slouken@11743
   779
{ @autoreleasepool {
slouken@11735
   780
    METAL_ActivateRenderer(renderer);
slouken@11732
   781
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
icculus@11729
   782
slouken@11732
   783
    [data.mtlcmdencoder endEncoding];
slouken@11741
   784
    [data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
slouken@11732
   785
    [data.mtlcmdbuffer commit];
slouken@11741
   786
    data.mtlcmdencoder = nil;
slouken@11741
   787
    data.mtlcmdbuffer = nil;
slouken@11741
   788
    data.mtlbackbuffer = nil;
slouken@11735
   789
    data.beginScene = YES;
slouken@11743
   790
}}
icculus@11729
   791
icculus@11729
   792
static void
icculus@11729
   793
METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
slouken@11743
   794
{ @autoreleasepool {
slouken@11753
   795
    METAL_TextureData *texturedata = CFBridgingRelease(texture->driverdata);
slouken@11751
   796
#if __has_feature(objc_arc)
slouken@11753
   797
    texturedata = nil;
slouken@11751
   798
#else
slouken@11753
   799
    [texturedata.mtltexture release];
slouken@11753
   800
    [texturedata release];
slouken@11732
   801
#endif
icculus@11729
   802
    texture->driverdata = NULL;
slouken@11743
   803
}}
icculus@11729
   804
icculus@11729
   805
static void
icculus@11729
   806
METAL_DestroyRenderer(SDL_Renderer * renderer)
slouken@11743
   807
{ @autoreleasepool {
slouken@11732
   808
    if (renderer->driverdata) {
slouken@11732
   809
        METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
icculus@11729
   810
slouken@11741
   811
        if (data.mtlcmdencoder != nil) {
slouken@11741
   812
            [data.mtlcmdencoder endEncoding];
slouken@11741
   813
        }
icculus@11746
   814
icculus@11746
   815
#if !__has_feature(objc_arc)
icculus@11746
   816
        [data.mtlbackbuffer release];
icculus@11746
   817
        [data.mtlcmdencoder release];
icculus@11746
   818
        [data.mtlcmdbuffer release];
slouken@11732
   819
        [data.mtlcmdqueue release];
slouken@11741
   820
        for (int i = 0; i < 4; i++) {
slouken@11732
   821
            [data.mtlpipelineprims[i] release];
slouken@11732
   822
            [data.mtlpipelinecopy[i] release];
icculus@11729
   823
        }
slouken@11741
   824
        [data.mtlpipelineprims release];
slouken@11741
   825
        [data.mtlpipelinecopy release];
slouken@11732
   826
        [data.mtlbufclearverts release];
slouken@11732
   827
        [data.mtllibrary release];
slouken@11732
   828
        [data.mtldevice release];
slouken@11732
   829
        [data.mtlpassdesc release];
slouken@11732
   830
        [data.mtllayer release];
slouken@11732
   831
#endif
icculus@11729
   832
    }
icculus@11746
   833
icculus@11729
   834
    SDL_free(renderer);
slouken@11743
   835
}}
icculus@11729
   836
slouken@11744
   837
void *METAL_GetMetalLayer(SDL_Renderer * renderer)
slouken@11744
   838
{ @autoreleasepool {
slouken@11744
   839
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11744
   840
    return (__bridge void*)data.mtllayer;
slouken@11744
   841
}}
slouken@11744
   842
slouken@11744
   843
void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
slouken@11744
   844
{ @autoreleasepool {
slouken@11744
   845
    METAL_ActivateRenderer(renderer);
slouken@11744
   846
    METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
slouken@11744
   847
    return (__bridge void*)data.mtlcmdencoder;
slouken@11744
   848
}}
slouken@11744
   849
icculus@11729
   850
#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
icculus@11729
   851
icculus@11729
   852
/* vi: set ts=4 sw=4 expandtab: */