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