|
@@ -46,62 +46,6 @@
|
|
|
|
|
|
/* 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 SDL_bool METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode);
|
|
|
-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_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd);
|
|
|
-static int METAL_QueueSetDrawColor(SDL_Renderer * renderer, SDL_RenderCommand *cmd);
|
|
|
-static int METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points,
|
|
|
- int count);
|
|
|
-static int METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects,
|
|
|
- int count);
|
|
|
-static int METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
|
|
- const SDL_Rect * srcrect, const SDL_FRect * dstrect);
|
|
|
-static int METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
|
|
- const SDL_Rect * srcquad, const SDL_FRect * dstrect,
|
|
|
- const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip);
|
|
|
-static int METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize);
|
|
|
-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);
|
|
|
-static void *METAL_GetMetalLayer(SDL_Renderer * renderer);
|
|
|
-static void *METAL_GetMetalCommandEncoder(SDL_Renderer * renderer);
|
|
|
-
|
|
|
-SDL_RenderDriver METAL_RenderDriver = {
|
|
|
- METAL_CreateRenderer,
|
|
|
- {
|
|
|
- "metal",
|
|
|
- (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
|
|
|
- 6,
|
|
|
- {
|
|
|
- SDL_PIXELFORMAT_ARGB8888,
|
|
|
- SDL_PIXELFORMAT_ABGR8888,
|
|
|
- SDL_PIXELFORMAT_YV12,
|
|
|
- SDL_PIXELFORMAT_IYUV,
|
|
|
- SDL_PIXELFORMAT_NV12,
|
|
|
- SDL_PIXELFORMAT_NV21
|
|
|
- },
|
|
|
- 0, 0,
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
/* macOS requires constants in a buffer to have a 256 byte alignment. */
|
|
|
#ifdef __MACOSX__
|
|
|
#define CONSTANT_ALIGN 256
|
|
@@ -456,716 +400,471 @@ ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SD
|
|
|
return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
|
|
|
}
|
|
|
|
|
|
-static SDL_Renderer *
|
|
|
-METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
|
|
-{ @autoreleasepool {
|
|
|
- SDL_Renderer *renderer = NULL;
|
|
|
- METAL_RenderData *data = NULL;
|
|
|
- id<MTLDevice> mtldevice = nil;
|
|
|
- SDL_SysWMinfo syswm;
|
|
|
+static void
|
|
|
+METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
|
|
|
+{
|
|
|
+ METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
|
|
|
- SDL_VERSION(&syswm.version);
|
|
|
- if (!SDL_GetWindowWMInfo(window, &syswm)) {
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ /* Our SetRenderTarget just signals that the next render operation should
|
|
|
+ * set up a new render pass. This is where that work happens. */
|
|
|
+ if (data.mtlcmdencoder == nil) {
|
|
|
+ id<MTLTexture> mtltexture = nil;
|
|
|
|
|
|
- if (IsMetalAvailable(&syswm) == -1) {
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ if (renderer->target != NULL) {
|
|
|
+ METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
|
|
|
+ mtltexture = texdata.mtltexture;
|
|
|
+ } else {
|
|
|
+ if (data.mtlbackbuffer == nil) {
|
|
|
+ /* The backbuffer's contents aren't guaranteed to persist after
|
|
|
+ * presenting, so we can leave it undefined when loading it. */
|
|
|
+ data.mtlbackbuffer = [data.mtllayer nextDrawable];
|
|
|
+ if (load == MTLLoadActionLoad) {
|
|
|
+ load = MTLLoadActionDontCare;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mtltexture = data.mtlbackbuffer.texture;
|
|
|
+ }
|
|
|
|
|
|
- renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
|
|
|
- if (!renderer) {
|
|
|
- SDL_OutOfMemory();
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ SDL_assert(mtltexture);
|
|
|
|
|
|
- // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
|
|
|
- mtldevice = MTLCreateSystemDefaultDevice();
|
|
|
+ if (load == MTLLoadActionClear) {
|
|
|
+ SDL_assert(clear_color != NULL);
|
|
|
+ data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
|
|
|
+ }
|
|
|
|
|
|
- if (mtldevice == nil) {
|
|
|
- SDL_free(renderer);
|
|
|
- SDL_SetError("Failed to obtain Metal device");
|
|
|
- return NULL;
|
|
|
- }
|
|
|
+ data.mtlpassdesc.colorAttachments[0].loadAction = load;
|
|
|
+ data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
|
|
|
|
|
|
- // !!! FIXME: error checking on all of this.
|
|
|
- data = [[METAL_RenderData alloc] init];
|
|
|
+ data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
|
|
|
+ data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
|
|
|
|
|
|
- renderer->driverdata = (void*)CFBridgingRetain(data);
|
|
|
- renderer->window = window;
|
|
|
+ if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
|
|
|
+ data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
|
|
|
+ } else {
|
|
|
+ data.mtlcmdencoder.label = @"SDL metal renderer render target";
|
|
|
+ }
|
|
|
|
|
|
-#ifdef __MACOSX__
|
|
|
- NSView *view = Cocoa_Mtl_AddMetalView(window);
|
|
|
- CAMetalLayer *layer = (CAMetalLayer *)[view layer];
|
|
|
+ data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
|
|
|
|
|
|
- layer.device = mtldevice;
|
|
|
+ // make sure this has a definite place in the queue. This way it will
|
|
|
+ // execute reliably whether the app tries to make its own command buffers
|
|
|
+ // or whatever. This means we can _always_ batch rendering commands!
|
|
|
+ [data.mtlcmdbuffer enqueue];
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- //layer.colorspace = nil;
|
|
|
+static void
|
|
|
+METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
|
|
|
+{
|
|
|
+ if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
|
|
+ METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
+ data.mtllayer.drawableSize = CGSizeMake(event->data1, event->data2);
|
|
|
+ }
|
|
|
|
|
|
-#else
|
|
|
- UIView *view = UIKit_Mtl_AddMetalView(window);
|
|
|
- CAMetalLayer *layer = (CAMetalLayer *)[view layer];
|
|
|
-#endif
|
|
|
+ if (event->event == SDL_WINDOWEVENT_SHOWN ||
|
|
|
+ event->event == SDL_WINDOWEVENT_HIDDEN) {
|
|
|
+ // !!! FIXME: write me
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- // Necessary for RenderReadPixels.
|
|
|
- layer.framebufferOnly = NO;
|
|
|
+static int
|
|
|
+METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
|
|
|
+{ @autoreleasepool {
|
|
|
+ METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
+ if (w) {
|
|
|
+ *w = (int)data.mtllayer.drawableSize.width;
|
|
|
+ }
|
|
|
+ if (h) {
|
|
|
+ *h = (int)data.mtllayer.drawableSize.height;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}}
|
|
|
|
|
|
- data.mtldevice = layer.device;
|
|
|
- data.mtllayer = layer;
|
|
|
- id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
|
|
|
- data.mtlcmdqueue = mtlcmdqueue;
|
|
|
- data.mtlcmdqueue.label = @"SDL Metal Renderer";
|
|
|
- data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
|
|
|
+static SDL_bool
|
|
|
+METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
|
|
|
+{
|
|
|
+ SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
|
|
|
+ SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
|
|
|
+ SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
|
|
|
+ SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
|
|
|
+ SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
|
|
|
+ SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
|
|
|
|
|
|
- NSError *err = nil;
|
|
|
+ if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
|
|
|
+ GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
|
|
|
+ GetBlendOperation(colorOperation) == invalidBlendOperation ||
|
|
|
+ GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
|
|
|
+ GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
|
|
|
+ GetBlendOperation(alphaOperation) == invalidBlendOperation) {
|
|
|
+ return SDL_FALSE;
|
|
|
+ }
|
|
|
+ return SDL_TRUE;
|
|
|
+}
|
|
|
|
|
|
- // 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), ^{});
|
|
|
- id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
|
|
|
- data.mtllibrary = mtllibrary;
|
|
|
- SDL_assert(err == nil);
|
|
|
-#if !__has_feature(objc_arc)
|
|
|
- dispatch_release(mtllibdata);
|
|
|
-#endif
|
|
|
- data.mtllibrary.label = @"SDL Metal renderer shader library";
|
|
|
+static int
|
|
|
+METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
|
|
+{ @autoreleasepool {
|
|
|
+ METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
+ MTLPixelFormat pixfmt;
|
|
|
|
|
|
- /* Do some shader pipeline state loading up-front rather than on demand. */
|
|
|
- data.pipelinescount = 0;
|
|
|
- data.allpipelines = NULL;
|
|
|
- ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
|
|
|
+ switch (texture->format) {
|
|
|
+ case SDL_PIXELFORMAT_ABGR8888:
|
|
|
+ pixfmt = MTLPixelFormatRGBA8Unorm;
|
|
|
+ break;
|
|
|
+ case SDL_PIXELFORMAT_ARGB8888:
|
|
|
+ pixfmt = MTLPixelFormatBGRA8Unorm;
|
|
|
+ break;
|
|
|
+ case SDL_PIXELFORMAT_IYUV:
|
|
|
+ case SDL_PIXELFORMAT_YV12:
|
|
|
+ case SDL_PIXELFORMAT_NV12:
|
|
|
+ case SDL_PIXELFORMAT_NV21:
|
|
|
+ pixfmt = MTLPixelFormatR8Unorm;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
|
|
|
+ }
|
|
|
|
|
|
- MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
|
|
|
+ MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
|
|
|
+ width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
|
|
|
|
|
|
- samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
|
|
|
- samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
|
|
|
- id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
|
|
|
- data.mtlsamplernearest = mtlsamplernearest;
|
|
|
+ /* Not available in iOS 8. */
|
|
|
+ if ([mtltexdesc respondsToSelector:@selector(usage)]) {
|
|
|
+ if (texture->access == SDL_TEXTUREACCESS_TARGET) {
|
|
|
+ mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
|
|
|
+ } else {
|
|
|
+ mtltexdesc.usage = MTLTextureUsageShaderRead;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
|
|
+ if (mtltexture == nil) {
|
|
|
+ return SDL_SetError("Texture allocation failed");
|
|
|
+ }
|
|
|
|
|
|
- samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
|
|
|
- samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
|
|
|
- id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
|
|
|
- data.mtlsamplerlinear = mtlsamplerlinear;
|
|
|
+ id<MTLTexture> mtltexture_uv = nil;
|
|
|
|
|
|
- /* Note: matrices are column major. */
|
|
|
- float identitytransform[16] = {
|
|
|
- 1.0f, 0.0f, 0.0f, 0.0f,
|
|
|
- 0.0f, 1.0f, 0.0f, 0.0f,
|
|
|
- 0.0f, 0.0f, 1.0f, 0.0f,
|
|
|
- 0.0f, 0.0f, 0.0f, 1.0f,
|
|
|
- };
|
|
|
+ BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
|
|
|
+ BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
|
|
|
|
|
|
- float halfpixeltransform[16] = {
|
|
|
- 1.0f, 0.0f, 0.0f, 0.0f,
|
|
|
- 0.0f, 1.0f, 0.0f, 0.0f,
|
|
|
- 0.0f, 0.0f, 1.0f, 0.0f,
|
|
|
- 0.5f, 0.5f, 0.0f, 1.0f,
|
|
|
- };
|
|
|
+ if (yuv) {
|
|
|
+ mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
|
|
|
+ mtltexdesc.width = (texture->w + 1) / 2;
|
|
|
+ mtltexdesc.height = (texture->h + 1) / 2;
|
|
|
+ mtltexdesc.textureType = MTLTextureType2DArray;
|
|
|
+ mtltexdesc.arrayLength = 2;
|
|
|
+ mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
|
|
+ } else if (nv12) {
|
|
|
+ mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
|
|
|
+ mtltexdesc.width = (texture->w + 1) / 2;
|
|
|
+ mtltexdesc.height = (texture->h + 1) / 2;
|
|
|
+ mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
|
|
+ }
|
|
|
|
|
|
- /* Metal pads float3s to 16 bytes. */
|
|
|
- float decodetransformJPEG[4*4] = {
|
|
|
- 0.0, -0.501960814, -0.501960814, 0.0, /* offset */
|
|
|
- 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
|
|
|
- 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
|
|
|
- 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
|
|
|
- };
|
|
|
+ METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
|
|
|
+ if (texture->scaleMode == SDL_ScaleModeNearest) {
|
|
|
+ texturedata.mtlsampler = data.mtlsamplernearest;
|
|
|
+ } else {
|
|
|
+ texturedata.mtlsampler = data.mtlsamplerlinear;
|
|
|
+ }
|
|
|
+ texturedata.mtltexture = mtltexture;
|
|
|
+ texturedata.mtltexture_uv = mtltexture_uv;
|
|
|
|
|
|
- float decodetransformBT601[4*4] = {
|
|
|
- -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
|
|
|
- 1.1644, 0.0000, 1.5960, 0.0, /* Rcoeff */
|
|
|
- 1.1644, -0.3918, -0.8130, 0.0, /* Gcoeff */
|
|
|
- 1.1644, 2.0172, 0.0000, 0.0, /* Bcoeff */
|
|
|
- };
|
|
|
+ texturedata.yuv = yuv;
|
|
|
+ texturedata.nv12 = nv12;
|
|
|
|
|
|
- float decodetransformBT709[4*4] = {
|
|
|
- 0.0, -0.501960814, -0.501960814, 0.0, /* offset */
|
|
|
- 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
|
|
|
- 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
|
|
|
- 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
|
|
|
- };
|
|
|
+ if (yuv) {
|
|
|
+ texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
|
|
|
+ } else if (texture->format == SDL_PIXELFORMAT_NV12) {
|
|
|
+ texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
|
|
|
+ } else if (texture->format == SDL_PIXELFORMAT_NV21) {
|
|
|
+ texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
|
|
|
+ } else {
|
|
|
+ texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
|
|
|
+ }
|
|
|
|
|
|
- float clearverts[6] = {0.0f, 0.0f, 0.0f, 2.0f, 2.0f, 0.0f};
|
|
|
+ if (yuv || nv12) {
|
|
|
+ size_t offset = 0;
|
|
|
+ SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
|
|
|
+ switch (mode) {
|
|
|
+ case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
|
|
|
+ case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
|
|
|
+ case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
|
|
|
+ default: offset = 0; break;
|
|
|
+ }
|
|
|
+ texturedata.conversionBufferOffset = offset;
|
|
|
+ }
|
|
|
|
|
|
- id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
|
|
|
- #if !__has_feature(objc_arc)
|
|
|
- [mtlbufconstantstaging autorelease];
|
|
|
- #endif
|
|
|
- mtlbufconstantstaging.label = @"SDL constant staging data";
|
|
|
+ texture->driverdata = (void*)CFBridgingRetain(texturedata);
|
|
|
|
|
|
- id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
|
|
|
- data.mtlbufconstants = mtlbufconstants;
|
|
|
- data.mtlbufconstants.label = @"SDL constant data";
|
|
|
+#if !__has_feature(objc_arc)
|
|
|
+ [texturedata release];
|
|
|
+ [mtltexture release];
|
|
|
+ [mtltexture_uv release];
|
|
|
+#endif
|
|
|
|
|
|
- char *constantdata = [mtlbufconstantstaging contents];
|
|
|
- SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
|
|
|
- SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
|
|
|
- SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
|
|
|
- SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
|
|
|
- SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
|
|
|
- SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts));
|
|
|
+ return 0;
|
|
|
+}}
|
|
|
|
|
|
- id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
|
|
|
- id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
|
|
|
+static int
|
|
|
+METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
|
|
|
+ const SDL_Rect * rect, const void *pixels, int pitch)
|
|
|
+{ @autoreleasepool {
|
|
|
+ METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
|
|
|
- [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
|
|
|
+ /* !!! FIXME: replaceRegion does not do any synchronization, so it might
|
|
|
+ * !!! FIXME: stomp on a previous frame's data that's currently being read
|
|
|
+ * !!! FIXME: by the GPU. */
|
|
|
+ [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
|
|
+ mipmapLevel:0
|
|
|
+ withBytes:pixels
|
|
|
+ bytesPerRow:pitch];
|
|
|
|
|
|
- [blitcmd endEncoding];
|
|
|
- [cmdbuffer commit];
|
|
|
+ if (texturedata.yuv) {
|
|
|
+ int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
|
|
|
+ int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
|
|
|
|
|
|
- // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
|
|
|
+ /* Skip to the correct offset into the next texture */
|
|
|
+ pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
|
|
+ [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
+ mipmapLevel:0
|
|
|
+ slice:Uslice
|
|
|
+ withBytes:pixels
|
|
|
+ bytesPerRow:(pitch + 1) / 2
|
|
|
+ bytesPerImage:0];
|
|
|
|
|
|
- renderer->WindowEvent = METAL_WindowEvent;
|
|
|
- renderer->GetOutputSize = METAL_GetOutputSize;
|
|
|
- renderer->SupportsBlendMode = METAL_SupportsBlendMode;
|
|
|
- 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->QueueSetViewport = METAL_QueueSetViewport;
|
|
|
- renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
|
|
|
- renderer->QueueDrawPoints = METAL_QueueDrawPoints;
|
|
|
- renderer->QueueDrawLines = METAL_QueueDrawPoints; // lines and points queue the same way.
|
|
|
- renderer->QueueFillRects = METAL_QueueFillRects;
|
|
|
- renderer->QueueCopy = METAL_QueueCopy;
|
|
|
- renderer->QueueCopyEx = METAL_QueueCopyEx;
|
|
|
- renderer->RunCommandQueue = METAL_RunCommandQueue;
|
|
|
- renderer->RenderReadPixels = METAL_RenderReadPixels;
|
|
|
- renderer->RenderPresent = METAL_RenderPresent;
|
|
|
- renderer->DestroyTexture = METAL_DestroyTexture;
|
|
|
- renderer->DestroyRenderer = METAL_DestroyRenderer;
|
|
|
- renderer->GetMetalLayer = METAL_GetMetalLayer;
|
|
|
- renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
|
|
|
+ /* Skip to the correct offset into the next texture */
|
|
|
+ pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
|
|
|
+ [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
+ mipmapLevel:0
|
|
|
+ slice:Vslice
|
|
|
+ withBytes:pixels
|
|
|
+ bytesPerRow:(pitch + 1) / 2
|
|
|
+ bytesPerImage:0];
|
|
|
+ }
|
|
|
|
|
|
- renderer->info = METAL_RenderDriver.info;
|
|
|
- renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
|
|
+ if (texturedata.nv12) {
|
|
|
+ /* Skip to the correct offset into the next texture */
|
|
|
+ pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
|
|
+ [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
+ mipmapLevel:0
|
|
|
+ slice:0
|
|
|
+ withBytes:pixels
|
|
|
+ bytesPerRow:2 * ((pitch + 1) / 2)
|
|
|
+ bytesPerImage:0];
|
|
|
+ }
|
|
|
|
|
|
- renderer->always_batch = SDL_TRUE;
|
|
|
+ return 0;
|
|
|
+}}
|
|
|
|
|
|
-#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
|
|
|
- if (@available(macOS 10.13, *)) {
|
|
|
- data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
|
|
|
- } else
|
|
|
-#endif
|
|
|
- {
|
|
|
- renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
|
|
- }
|
|
|
+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)
|
|
|
+{ @autoreleasepool {
|
|
|
+ METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
+ const int Uslice = 0;
|
|
|
+ const int Vslice = 1;
|
|
|
|
|
|
- /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
|
|
|
- int maxtexsize = 4096;
|
|
|
-#if defined(__MACOSX__)
|
|
|
- maxtexsize = 16384;
|
|
|
-#elif defined(__TVOS__)
|
|
|
- maxtexsize = 8192;
|
|
|
-#ifdef __TVOS_11_0
|
|
|
- if (@available(tvOS 11.0, *)) {
|
|
|
- if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
|
|
|
- maxtexsize = 16384;
|
|
|
- }
|
|
|
- }
|
|
|
-#endif
|
|
|
-#else
|
|
|
-#ifdef __IPHONE_11_0
|
|
|
- if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
|
|
|
- maxtexsize = 16384;
|
|
|
- } else
|
|
|
-#endif
|
|
|
-#ifdef __IPHONE_10_0
|
|
|
- if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
|
|
|
- maxtexsize = 16384;
|
|
|
- } else
|
|
|
-#endif
|
|
|
- if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
|
|
|
- maxtexsize = 8192;
|
|
|
- } else {
|
|
|
- maxtexsize = 4096;
|
|
|
+ /* Bail out if we're supposed to update an empty rectangle */
|
|
|
+ if (rect->w <= 0 || rect->h <= 0) {
|
|
|
+ return 0;
|
|
|
}
|
|
|
-#endif
|
|
|
|
|
|
- renderer->info.max_texture_width = maxtexsize;
|
|
|
- renderer->info.max_texture_height = maxtexsize;
|
|
|
+ [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
|
|
+ mipmapLevel:0
|
|
|
+ withBytes:Yplane
|
|
|
+ bytesPerRow:Ypitch];
|
|
|
|
|
|
-#if !__has_feature(objc_arc)
|
|
|
- [mtlcmdqueue release];
|
|
|
- [mtllibrary release];
|
|
|
- [samplerdesc release];
|
|
|
- [mtlsamplernearest release];
|
|
|
- [mtlsamplerlinear release];
|
|
|
- [mtlbufconstants release];
|
|
|
- [view release];
|
|
|
- [data release];
|
|
|
- [mtldevice release];
|
|
|
-#endif
|
|
|
+ [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
+ mipmapLevel:0
|
|
|
+ slice:Uslice
|
|
|
+ withBytes:Uplane
|
|
|
+ bytesPerRow:Upitch
|
|
|
+ bytesPerImage:0];
|
|
|
|
|
|
- return renderer;
|
|
|
+ [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
+ mipmapLevel:0
|
|
|
+ slice:Vslice
|
|
|
+ withBytes:Vplane
|
|
|
+ bytesPerRow:Vpitch
|
|
|
+ bytesPerImage:0];
|
|
|
+
|
|
|
+ return 0;
|
|
|
}}
|
|
|
|
|
|
+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_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
|
|
|
+METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
|
|
{
|
|
|
- METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
+ // !!! FIXME: write me
|
|
|
+}
|
|
|
|
|
|
- /* Our SetRenderTarget just signals that the next render operation should
|
|
|
- * set up a new render pass. This is where that work happens. */
|
|
|
- if (data.mtlcmdencoder == nil) {
|
|
|
- id<MTLTexture> mtltexture = nil;
|
|
|
+static int
|
|
|
+METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
|
|
|
+{ @autoreleasepool {
|
|
|
+ METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
|
|
|
- if (renderer->target != NULL) {
|
|
|
- METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
|
|
|
- mtltexture = texdata.mtltexture;
|
|
|
- } else {
|
|
|
- if (data.mtlbackbuffer == nil) {
|
|
|
- /* The backbuffer's contents aren't guaranteed to persist after
|
|
|
- * presenting, so we can leave it undefined when loading it. */
|
|
|
- data.mtlbackbuffer = [data.mtllayer nextDrawable];
|
|
|
- if (load == MTLLoadActionLoad) {
|
|
|
- load = MTLLoadActionDontCare;
|
|
|
- }
|
|
|
- }
|
|
|
- mtltexture = data.mtlbackbuffer.texture;
|
|
|
- }
|
|
|
+ if (data.mtlcmdencoder) {
|
|
|
+ /* End encoding for the previous render target so we can set up a new
|
|
|
+ * render pass for this one. */
|
|
|
+ [data.mtlcmdencoder endEncoding];
|
|
|
+ [data.mtlcmdbuffer commit];
|
|
|
|
|
|
- SDL_assert(mtltexture);
|
|
|
-
|
|
|
- if (load == MTLLoadActionClear) {
|
|
|
- SDL_assert(clear_color != NULL);
|
|
|
- data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
|
|
|
- }
|
|
|
-
|
|
|
- data.mtlpassdesc.colorAttachments[0].loadAction = load;
|
|
|
- data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
|
|
|
-
|
|
|
- data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
|
|
|
- data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
|
|
|
+ data.mtlcmdencoder = nil;
|
|
|
+ data.mtlcmdbuffer = nil;
|
|
|
+ }
|
|
|
|
|
|
- if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
|
|
|
- data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
|
|
|
- } else {
|
|
|
- data.mtlcmdencoder.label = @"SDL metal renderer render target";
|
|
|
- }
|
|
|
+ /* We don't begin a new render pass right away - we delay it until an actual
|
|
|
+ * draw or clear happens. That way we can use hardware clears when possible,
|
|
|
+ * which are only available when beginning a new render pass. */
|
|
|
+ return 0;
|
|
|
+}}
|
|
|
|
|
|
- data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
|
|
|
|
|
|
- // make sure this has a definite place in the queue. This way it will
|
|
|
- // execute reliably whether the app tries to make its own command buffers
|
|
|
- // or whatever. This means we can _always_ batch rendering commands!
|
|
|
- [data.mtlcmdbuffer enqueue];
|
|
|
- }
|
|
|
+// normalize a value from 0.0f to len into 0.0f to 1.0f.
|
|
|
+static inline float
|
|
|
+normtex(const float _val, const float len)
|
|
|
+{
|
|
|
+ return _val / len;
|
|
|
}
|
|
|
|
|
|
-static void
|
|
|
-METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
|
|
|
+static int
|
|
|
+METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
|
|
|
{
|
|
|
- if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
|
|
- METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
- data.mtllayer.drawableSize = CGSizeMake(event->data1, event->data2);
|
|
|
+ float projection[4][4]; /* Prepare an orthographic projection */
|
|
|
+ const int w = cmd->data.viewport.rect.w;
|
|
|
+ const int h = cmd->data.viewport.rect.h;
|
|
|
+ const size_t matrixlen = sizeof (projection);
|
|
|
+ float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
|
|
|
+ if (!matrix) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
- if (event->event == SDL_WINDOWEVENT_SHOWN ||
|
|
|
- event->event == SDL_WINDOWEVENT_HIDDEN) {
|
|
|
- // !!! FIXME: write me
|
|
|
+ SDL_memset(projection, '\0', matrixlen);
|
|
|
+ if (w && h) {
|
|
|
+ projection[0][0] = 2.0f / w;
|
|
|
+ projection[1][1] = -2.0f / h;
|
|
|
+ projection[3][0] = -1.0f;
|
|
|
+ projection[3][1] = 1.0f;
|
|
|
+ projection[3][3] = 1.0f;
|
|
|
}
|
|
|
+ SDL_memcpy(matrix, projection, matrixlen);
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
-METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
|
|
|
-{ @autoreleasepool {
|
|
|
- METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
- if (w) {
|
|
|
- *w = (int)data.mtllayer.drawableSize.width;
|
|
|
- }
|
|
|
- if (h) {
|
|
|
- *h = (int)data.mtllayer.drawableSize.height;
|
|
|
+METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
|
|
|
+{
|
|
|
+ const size_t vertlen = sizeof (float) * 4;
|
|
|
+ float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
|
|
|
+ if (!verts) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
+ *(verts++) = ((float)cmd->data.color.r) / 255.0f;
|
|
|
+ *(verts++) = ((float)cmd->data.color.g) / 255.0f;
|
|
|
+ *(verts++) = ((float)cmd->data.color.b) / 255.0f;
|
|
|
+ *(verts++) = ((float)cmd->data.color.a) / 255.0f;
|
|
|
return 0;
|
|
|
-}}
|
|
|
+}
|
|
|
|
|
|
-static SDL_bool
|
|
|
-METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
|
|
|
+static int
|
|
|
+METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
|
|
|
{
|
|
|
- SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
|
|
|
- SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
|
|
|
- SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
|
|
|
- SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
|
|
|
- SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
|
|
|
- SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
|
|
|
-
|
|
|
- if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
|
|
|
- GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
|
|
|
- GetBlendOperation(colorOperation) == invalidBlendOperation ||
|
|
|
- GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
|
|
|
- GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
|
|
|
- GetBlendOperation(alphaOperation) == invalidBlendOperation) {
|
|
|
- return SDL_FALSE;
|
|
|
+ const size_t vertlen = (sizeof (float) * 2) * count;
|
|
|
+ float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
|
|
+ if (!verts) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
- return SDL_TRUE;
|
|
|
+ cmd->data.draw.count = count;
|
|
|
+ SDL_memcpy(verts, points, vertlen);
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
-METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
|
|
-{ @autoreleasepool {
|
|
|
- METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
- MTLPixelFormat pixfmt;
|
|
|
-
|
|
|
- switch (texture->format) {
|
|
|
- case SDL_PIXELFORMAT_ABGR8888:
|
|
|
- pixfmt = MTLPixelFormatRGBA8Unorm;
|
|
|
- break;
|
|
|
- case SDL_PIXELFORMAT_ARGB8888:
|
|
|
- pixfmt = MTLPixelFormatBGRA8Unorm;
|
|
|
- break;
|
|
|
- case SDL_PIXELFORMAT_IYUV:
|
|
|
- case SDL_PIXELFORMAT_YV12:
|
|
|
- case SDL_PIXELFORMAT_NV12:
|
|
|
- case SDL_PIXELFORMAT_NV21:
|
|
|
- pixfmt = MTLPixelFormatR8Unorm;
|
|
|
- break;
|
|
|
- default:
|
|
|
- return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
|
|
|
+METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
|
|
|
+{
|
|
|
+ // !!! FIXME: use an index buffer
|
|
|
+ const size_t vertlen = (sizeof (float) * 8) * count;
|
|
|
+ float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
|
|
+ if (!verts) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
- MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
|
|
|
- width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
|
|
|
+ cmd->data.draw.count = count;
|
|
|
|
|
|
- /* Not available in iOS 8. */
|
|
|
- if ([mtltexdesc respondsToSelector:@selector(usage)]) {
|
|
|
- if (texture->access == SDL_TEXTUREACCESS_TARGET) {
|
|
|
- mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
|
|
|
+ for (int i = 0; i < count; i++, rects++) {
|
|
|
+ if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
|
|
|
+ cmd->data.draw.count--;
|
|
|
} else {
|
|
|
- mtltexdesc.usage = MTLTextureUsageShaderRead;
|
|
|
+ *(verts++) = rects->x;
|
|
|
+ *(verts++) = rects->y + rects->h;
|
|
|
+ *(verts++) = rects->x;
|
|
|
+ *(verts++) = rects->y;
|
|
|
+ *(verts++) = rects->x + rects->w;
|
|
|
+ *(verts++) = rects->y + rects->h;
|
|
|
+ *(verts++) = rects->x + rects->w;
|
|
|
+ *(verts++) = rects->y;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
|
|
- if (mtltexture == nil) {
|
|
|
- return SDL_SetError("Texture allocation failed");
|
|
|
- }
|
|
|
-
|
|
|
- id<MTLTexture> mtltexture_uv = nil;
|
|
|
-
|
|
|
- BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
|
|
|
- BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
|
|
|
-
|
|
|
- if (yuv) {
|
|
|
- mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
|
|
|
- mtltexdesc.width = (texture->w + 1) / 2;
|
|
|
- mtltexdesc.height = (texture->h + 1) / 2;
|
|
|
- mtltexdesc.textureType = MTLTextureType2DArray;
|
|
|
- mtltexdesc.arrayLength = 2;
|
|
|
- mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
|
|
- } else if (nv12) {
|
|
|
- mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
|
|
|
- mtltexdesc.width = (texture->w + 1) / 2;
|
|
|
- mtltexdesc.height = (texture->h + 1) / 2;
|
|
|
- mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
|
|
- }
|
|
|
|
|
|
- METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
|
|
|
- if (texture->scaleMode == SDL_ScaleModeNearest) {
|
|
|
- texturedata.mtlsampler = data.mtlsamplernearest;
|
|
|
- } else {
|
|
|
- texturedata.mtlsampler = data.mtlsamplerlinear;
|
|
|
+ if (cmd->data.draw.count == 0) {
|
|
|
+ cmd->command = SDL_RENDERCMD_NO_OP; // nothing to do, just skip this one later.
|
|
|
}
|
|
|
- texturedata.mtltexture = mtltexture;
|
|
|
- texturedata.mtltexture_uv = mtltexture_uv;
|
|
|
|
|
|
- texturedata.yuv = yuv;
|
|
|
- texturedata.nv12 = nv12;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
- if (yuv) {
|
|
|
- texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
|
|
|
- } else if (texture->format == SDL_PIXELFORMAT_NV12) {
|
|
|
- texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
|
|
|
- } else if (texture->format == SDL_PIXELFORMAT_NV21) {
|
|
|
- texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
|
|
|
- } else {
|
|
|
- texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
|
|
|
+static int
|
|
|
+METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
|
|
+ const SDL_Rect * srcrect, const SDL_FRect * dstrect)
|
|
|
+{
|
|
|
+ METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
+ const float texw = (float) texturedata.mtltexture.width;
|
|
|
+ const float texh = (float) texturedata.mtltexture.height;
|
|
|
+ // !!! FIXME: use an index buffer
|
|
|
+ const size_t vertlen = (sizeof (float) * 16);
|
|
|
+ float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
|
|
+ if (!verts) {
|
|
|
+ return -1;
|
|
|
}
|
|
|
|
|
|
- if (yuv || nv12) {
|
|
|
- size_t offset = 0;
|
|
|
- SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
|
|
|
- switch (mode) {
|
|
|
- case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
|
|
|
- case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
|
|
|
- case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
|
|
|
- default: offset = 0; break;
|
|
|
- }
|
|
|
- texturedata.conversionBufferOffset = offset;
|
|
|
- }
|
|
|
+ cmd->data.draw.count = 1;
|
|
|
|
|
|
- texture->driverdata = (void*)CFBridgingRetain(texturedata);
|
|
|
+ *(verts++) = dstrect->x;
|
|
|
+ *(verts++) = dstrect->y + dstrect->h;
|
|
|
+ *(verts++) = dstrect->x;
|
|
|
+ *(verts++) = dstrect->y;
|
|
|
+ *(verts++) = dstrect->x + dstrect->w;
|
|
|
+ *(verts++) = dstrect->y + dstrect->h;
|
|
|
+ *(verts++) = dstrect->x + dstrect->w;
|
|
|
+ *(verts++) = dstrect->y;
|
|
|
|
|
|
-#if !__has_feature(objc_arc)
|
|
|
- [texturedata release];
|
|
|
- [mtltexture release];
|
|
|
- [mtltexture_uv release];
|
|
|
-#endif
|
|
|
+ *(verts++) = normtex(srcrect->x, texw);
|
|
|
+ *(verts++) = normtex(srcrect->y + srcrect->h, texh);
|
|
|
+ *(verts++) = normtex(srcrect->x, texw);
|
|
|
+ *(verts++) = normtex(srcrect->y, texh);
|
|
|
+ *(verts++) = normtex(srcrect->x + srcrect->w, texw);
|
|
|
+ *(verts++) = normtex(srcrect->y + srcrect->h, texh);
|
|
|
+ *(verts++) = normtex(srcrect->x + srcrect->w, texw);
|
|
|
+ *(verts++) = normtex(srcrect->y, texh);
|
|
|
|
|
|
return 0;
|
|
|
-}}
|
|
|
+}
|
|
|
|
|
|
static int
|
|
|
-METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
|
|
|
- const SDL_Rect * rect, const void *pixels, int pitch)
|
|
|
-{ @autoreleasepool {
|
|
|
- METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
-
|
|
|
- /* !!! FIXME: replaceRegion does not do any synchronization, so it might
|
|
|
- * !!! FIXME: stomp on a previous frame's data that's currently being read
|
|
|
- * !!! FIXME: by the GPU. */
|
|
|
- [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
|
|
- mipmapLevel:0
|
|
|
- withBytes:pixels
|
|
|
- bytesPerRow:pitch];
|
|
|
-
|
|
|
- if (texturedata.yuv) {
|
|
|
- int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
|
|
|
- int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
|
|
|
-
|
|
|
- /* Skip to the correct offset into the next texture */
|
|
|
- pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
|
|
- [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
- mipmapLevel:0
|
|
|
- slice:Uslice
|
|
|
- withBytes:pixels
|
|
|
- bytesPerRow:(pitch + 1) / 2
|
|
|
- bytesPerImage:0];
|
|
|
-
|
|
|
- /* Skip to the correct offset into the next texture */
|
|
|
- pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
|
|
|
- [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
- mipmapLevel:0
|
|
|
- slice:Vslice
|
|
|
- withBytes:pixels
|
|
|
- bytesPerRow:(pitch + 1) / 2
|
|
|
- bytesPerImage:0];
|
|
|
- }
|
|
|
-
|
|
|
- if (texturedata.nv12) {
|
|
|
- /* Skip to the correct offset into the next texture */
|
|
|
- pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
|
|
- [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
- mipmapLevel:0
|
|
|
- slice:0
|
|
|
- withBytes:pixels
|
|
|
- bytesPerRow:2 * ((pitch + 1) / 2)
|
|
|
- bytesPerImage:0];
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
-}}
|
|
|
-
|
|
|
-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)
|
|
|
-{ @autoreleasepool {
|
|
|
- METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
- const int Uslice = 0;
|
|
|
- const int Vslice = 1;
|
|
|
-
|
|
|
- /* Bail out if we're supposed to update an empty rectangle */
|
|
|
- if (rect->w <= 0 || rect->h <= 0) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- [texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
|
|
- mipmapLevel:0
|
|
|
- withBytes:Yplane
|
|
|
- bytesPerRow:Ypitch];
|
|
|
-
|
|
|
- [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
- mipmapLevel:0
|
|
|
- slice:Uslice
|
|
|
- withBytes:Uplane
|
|
|
- bytesPerRow:Upitch
|
|
|
- bytesPerImage:0];
|
|
|
-
|
|
|
- [texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
|
|
- mipmapLevel:0
|
|
|
- slice:Vslice
|
|
|
- withBytes:Vplane
|
|
|
- bytesPerRow:Vpitch
|
|
|
- bytesPerImage:0];
|
|
|
-
|
|
|
- return 0;
|
|
|
-}}
|
|
|
-
|
|
|
-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)
|
|
|
-{ @autoreleasepool {
|
|
|
- METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
|
|
-
|
|
|
- if (data.mtlcmdencoder) {
|
|
|
- /* End encoding for the previous render target so we can set up a new
|
|
|
- * render pass for this one. */
|
|
|
- [data.mtlcmdencoder endEncoding];
|
|
|
- [data.mtlcmdbuffer commit];
|
|
|
-
|
|
|
- data.mtlcmdencoder = nil;
|
|
|
- data.mtlcmdbuffer = nil;
|
|
|
- }
|
|
|
-
|
|
|
- /* We don't begin a new render pass right away - we delay it until an actual
|
|
|
- * draw or clear happens. That way we can use hardware clears when possible,
|
|
|
- * which are only available when beginning a new render pass. */
|
|
|
- return 0;
|
|
|
-}}
|
|
|
-
|
|
|
-
|
|
|
-// normalize a value from 0.0f to len into 0.0f to 1.0f.
|
|
|
-static inline float
|
|
|
-normtex(const float _val, const float len)
|
|
|
-{
|
|
|
- return _val / len;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
|
|
|
-{
|
|
|
- float projection[4][4]; /* Prepare an orthographic projection */
|
|
|
- const int w = cmd->data.viewport.rect.w;
|
|
|
- const int h = cmd->data.viewport.rect.h;
|
|
|
- const size_t matrixlen = sizeof (projection);
|
|
|
- float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
|
|
|
- if (!matrix) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- SDL_memset(projection, '\0', matrixlen);
|
|
|
- if (w && h) {
|
|
|
- projection[0][0] = 2.0f / w;
|
|
|
- projection[1][1] = -2.0f / h;
|
|
|
- projection[3][0] = -1.0f;
|
|
|
- projection[3][1] = 1.0f;
|
|
|
- projection[3][3] = 1.0f;
|
|
|
- }
|
|
|
- SDL_memcpy(matrix, projection, matrixlen);
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
|
|
|
-{
|
|
|
- const size_t vertlen = sizeof (float) * 4;
|
|
|
- float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
|
|
|
- if (!verts) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
- *(verts++) = ((float)cmd->data.color.r) / 255.0f;
|
|
|
- *(verts++) = ((float)cmd->data.color.g) / 255.0f;
|
|
|
- *(verts++) = ((float)cmd->data.color.b) / 255.0f;
|
|
|
- *(verts++) = ((float)cmd->data.color.a) / 255.0f;
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
|
|
|
-{
|
|
|
- const size_t vertlen = (sizeof (float) * 2) * count;
|
|
|
- float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
|
|
- if (!verts) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
- cmd->data.draw.count = count;
|
|
|
- SDL_memcpy(verts, points, vertlen);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
|
|
|
-{
|
|
|
- // !!! FIXME: use an index buffer
|
|
|
- const size_t vertlen = (sizeof (float) * 8) * count;
|
|
|
- float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
|
|
- if (!verts) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- cmd->data.draw.count = count;
|
|
|
-
|
|
|
- for (int i = 0; i < count; i++, rects++) {
|
|
|
- if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
|
|
|
- cmd->data.draw.count--;
|
|
|
- } else {
|
|
|
- *(verts++) = rects->x;
|
|
|
- *(verts++) = rects->y + rects->h;
|
|
|
- *(verts++) = rects->x;
|
|
|
- *(verts++) = rects->y;
|
|
|
- *(verts++) = rects->x + rects->w;
|
|
|
- *(verts++) = rects->y + rects->h;
|
|
|
- *(verts++) = rects->x + rects->w;
|
|
|
- *(verts++) = rects->y;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (cmd->data.draw.count == 0) {
|
|
|
- cmd->command = SDL_RENDERCMD_NO_OP; // nothing to do, just skip this one later.
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
|
|
- const SDL_Rect * srcrect, const SDL_FRect * dstrect)
|
|
|
-{
|
|
|
- METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
- const float texw = (float) texturedata.mtltexture.width;
|
|
|
- const float texh = (float) texturedata.mtltexture.height;
|
|
|
- // !!! FIXME: use an index buffer
|
|
|
- const size_t vertlen = (sizeof (float) * 16);
|
|
|
- float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
|
|
- if (!verts) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
-
|
|
|
- cmd->data.draw.count = 1;
|
|
|
-
|
|
|
- *(verts++) = dstrect->x;
|
|
|
- *(verts++) = dstrect->y + dstrect->h;
|
|
|
- *(verts++) = dstrect->x;
|
|
|
- *(verts++) = dstrect->y;
|
|
|
- *(verts++) = dstrect->x + dstrect->w;
|
|
|
- *(verts++) = dstrect->y + dstrect->h;
|
|
|
- *(verts++) = dstrect->x + dstrect->w;
|
|
|
- *(verts++) = dstrect->y;
|
|
|
-
|
|
|
- *(verts++) = normtex(srcrect->x, texw);
|
|
|
- *(verts++) = normtex(srcrect->y + srcrect->h, texh);
|
|
|
- *(verts++) = normtex(srcrect->x, texw);
|
|
|
- *(verts++) = normtex(srcrect->y, texh);
|
|
|
- *(verts++) = normtex(srcrect->x + srcrect->w, texw);
|
|
|
- *(verts++) = normtex(srcrect->y + srcrect->h, texh);
|
|
|
- *(verts++) = normtex(srcrect->x + srcrect->w, texw);
|
|
|
- *(verts++) = normtex(srcrect->y, texh);
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int
|
|
|
-METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
|
|
- const SDL_Rect * srcquad, const SDL_FRect * dstrect,
|
|
|
- const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
|
|
|
-{
|
|
|
+METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
|
|
+ const SDL_Rect * srcquad, const SDL_FRect * dstrect,
|
|
|
+ const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
|
|
|
+{
|
|
|
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
|
|
const float texw = (float) texturedata.mtltexture.width;
|
|
|
const float texh = (float) texturedata.mtltexture.height;
|
|
@@ -1573,6 +1272,269 @@ METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
|
|
|
return (__bridge void*)data.mtlcmdencoder;
|
|
|
}}
|
|
|
|
|
|
+static SDL_Renderer *
|
|
|
+METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
|
|
+{ @autoreleasepool {
|
|
|
+ SDL_Renderer *renderer = NULL;
|
|
|
+ METAL_RenderData *data = NULL;
|
|
|
+ id<MTLDevice> mtldevice = nil;
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ // !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
|
|
|
+ mtldevice = MTLCreateSystemDefaultDevice();
|
|
|
+
|
|
|
+ if (mtldevice == nil) {
|
|
|
+ SDL_free(renderer);
|
|
|
+ SDL_SetError("Failed to obtain Metal device");
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // !!! FIXME: error checking on all of this.
|
|
|
+ data = [[METAL_RenderData alloc] init];
|
|
|
+
|
|
|
+ renderer->driverdata = (void*)CFBridgingRetain(data);
|
|
|
+ renderer->window = window;
|
|
|
+
|
|
|
+#ifdef __MACOSX__
|
|
|
+ NSView *view = Cocoa_Mtl_AddMetalView(window);
|
|
|
+ CAMetalLayer *layer = (CAMetalLayer *)[view layer];
|
|
|
+
|
|
|
+ layer.device = mtldevice;
|
|
|
+
|
|
|
+ //layer.colorspace = nil;
|
|
|
+
|
|
|
+#else
|
|
|
+ UIView *view = UIKit_Mtl_AddMetalView(window);
|
|
|
+ CAMetalLayer *layer = (CAMetalLayer *)[view layer];
|
|
|
+#endif
|
|
|
+
|
|
|
+ // Necessary for RenderReadPixels.
|
|
|
+ layer.framebufferOnly = NO;
|
|
|
+
|
|
|
+ data.mtldevice = layer.device;
|
|
|
+ data.mtllayer = layer;
|
|
|
+ id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
|
|
|
+ data.mtlcmdqueue = mtlcmdqueue;
|
|
|
+ data.mtlcmdqueue.label = @"SDL Metal Renderer";
|
|
|
+ data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
|
|
|
+
|
|
|
+ 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), ^{});
|
|
|
+ id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
|
|
|
+ data.mtllibrary = mtllibrary;
|
|
|
+ SDL_assert(err == nil);
|
|
|
+#if !__has_feature(objc_arc)
|
|
|
+ dispatch_release(mtllibdata);
|
|
|
+#endif
|
|
|
+ data.mtllibrary.label = @"SDL Metal renderer shader library";
|
|
|
+
|
|
|
+ /* Do some shader pipeline state loading up-front rather than on demand. */
|
|
|
+ data.pipelinescount = 0;
|
|
|
+ data.allpipelines = NULL;
|
|
|
+ ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
|
|
|
+
|
|
|
+ MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
|
|
|
+
|
|
|
+ samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
|
|
|
+ samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
|
|
|
+ id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
|
|
|
+ data.mtlsamplernearest = mtlsamplernearest;
|
|
|
+
|
|
|
+ samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
|
|
|
+ samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
|
|
|
+ id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
|
|
|
+ data.mtlsamplerlinear = mtlsamplerlinear;
|
|
|
+
|
|
|
+ /* Note: matrices are column major. */
|
|
|
+ float identitytransform[16] = {
|
|
|
+ 1.0f, 0.0f, 0.0f, 0.0f,
|
|
|
+ 0.0f, 1.0f, 0.0f, 0.0f,
|
|
|
+ 0.0f, 0.0f, 1.0f, 0.0f,
|
|
|
+ 0.0f, 0.0f, 0.0f, 1.0f,
|
|
|
+ };
|
|
|
+
|
|
|
+ float halfpixeltransform[16] = {
|
|
|
+ 1.0f, 0.0f, 0.0f, 0.0f,
|
|
|
+ 0.0f, 1.0f, 0.0f, 0.0f,
|
|
|
+ 0.0f, 0.0f, 1.0f, 0.0f,
|
|
|
+ 0.5f, 0.5f, 0.0f, 1.0f,
|
|
|
+ };
|
|
|
+
|
|
|
+ /* Metal pads float3s to 16 bytes. */
|
|
|
+ float decodetransformJPEG[4*4] = {
|
|
|
+ 0.0, -0.501960814, -0.501960814, 0.0, /* offset */
|
|
|
+ 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
|
|
|
+ 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
|
|
|
+ 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
|
|
|
+ };
|
|
|
+
|
|
|
+ float decodetransformBT601[4*4] = {
|
|
|
+ -0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
|
|
|
+ 1.1644, 0.0000, 1.5960, 0.0, /* Rcoeff */
|
|
|
+ 1.1644, -0.3918, -0.8130, 0.0, /* Gcoeff */
|
|
|
+ 1.1644, 2.0172, 0.0000, 0.0, /* Bcoeff */
|
|
|
+ };
|
|
|
+
|
|
|
+ float decodetransformBT709[4*4] = {
|
|
|
+ 0.0, -0.501960814, -0.501960814, 0.0, /* offset */
|
|
|
+ 1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
|
|
|
+ 1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
|
|
|
+ 1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
|
|
|
+ };
|
|
|
+
|
|
|
+ float clearverts[6] = {0.0f, 0.0f, 0.0f, 2.0f, 2.0f, 0.0f};
|
|
|
+
|
|
|
+ id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
|
|
|
+ #if !__has_feature(objc_arc)
|
|
|
+ [mtlbufconstantstaging autorelease];
|
|
|
+ #endif
|
|
|
+ mtlbufconstantstaging.label = @"SDL constant staging data";
|
|
|
+
|
|
|
+ id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
|
|
|
+ data.mtlbufconstants = mtlbufconstants;
|
|
|
+ data.mtlbufconstants.label = @"SDL constant data";
|
|
|
+
|
|
|
+ char *constantdata = [mtlbufconstantstaging contents];
|
|
|
+ SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
|
|
|
+ SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
|
|
|
+ SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
|
|
|
+ SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
|
|
|
+ SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
|
|
|
+ SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts));
|
|
|
+
|
|
|
+ id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
|
|
|
+ id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
|
|
|
+
|
|
|
+ [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
|
|
|
+
|
|
|
+ [blitcmd endEncoding];
|
|
|
+ [cmdbuffer commit];
|
|
|
+
|
|
|
+ // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
|
|
|
+
|
|
|
+ renderer->WindowEvent = METAL_WindowEvent;
|
|
|
+ renderer->GetOutputSize = METAL_GetOutputSize;
|
|
|
+ renderer->SupportsBlendMode = METAL_SupportsBlendMode;
|
|
|
+ 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->QueueSetViewport = METAL_QueueSetViewport;
|
|
|
+ renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
|
|
|
+ renderer->QueueDrawPoints = METAL_QueueDrawPoints;
|
|
|
+ renderer->QueueDrawLines = METAL_QueueDrawPoints; // lines and points queue the same way.
|
|
|
+ renderer->QueueFillRects = METAL_QueueFillRects;
|
|
|
+ renderer->QueueCopy = METAL_QueueCopy;
|
|
|
+ renderer->QueueCopyEx = METAL_QueueCopyEx;
|
|
|
+ renderer->RunCommandQueue = METAL_RunCommandQueue;
|
|
|
+ renderer->RenderReadPixels = METAL_RenderReadPixels;
|
|
|
+ renderer->RenderPresent = METAL_RenderPresent;
|
|
|
+ renderer->DestroyTexture = METAL_DestroyTexture;
|
|
|
+ renderer->DestroyRenderer = METAL_DestroyRenderer;
|
|
|
+ renderer->GetMetalLayer = METAL_GetMetalLayer;
|
|
|
+ renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
|
|
|
+
|
|
|
+ renderer->info = METAL_RenderDriver.info;
|
|
|
+ renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
|
|
+
|
|
|
+ renderer->always_batch = SDL_TRUE;
|
|
|
+
|
|
|
+#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
|
|
|
+ if (@available(macOS 10.13, *)) {
|
|
|
+ data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
|
|
|
+ } else
|
|
|
+#endif
|
|
|
+ {
|
|
|
+ renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
|
|
|
+ int maxtexsize = 4096;
|
|
|
+#if defined(__MACOSX__)
|
|
|
+ maxtexsize = 16384;
|
|
|
+#elif defined(__TVOS__)
|
|
|
+ maxtexsize = 8192;
|
|
|
+#ifdef __TVOS_11_0
|
|
|
+ if (@available(tvOS 11.0, *)) {
|
|
|
+ if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
|
|
|
+ maxtexsize = 16384;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+#else
|
|
|
+#ifdef __IPHONE_11_0
|
|
|
+ if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
|
|
|
+ maxtexsize = 16384;
|
|
|
+ } else
|
|
|
+#endif
|
|
|
+#ifdef __IPHONE_10_0
|
|
|
+ if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
|
|
|
+ maxtexsize = 16384;
|
|
|
+ } else
|
|
|
+#endif
|
|
|
+ if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
|
|
|
+ maxtexsize = 8192;
|
|
|
+ } else {
|
|
|
+ maxtexsize = 4096;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ renderer->info.max_texture_width = maxtexsize;
|
|
|
+ renderer->info.max_texture_height = maxtexsize;
|
|
|
+
|
|
|
+#if !__has_feature(objc_arc)
|
|
|
+ [mtlcmdqueue release];
|
|
|
+ [mtllibrary release];
|
|
|
+ [samplerdesc release];
|
|
|
+ [mtlsamplernearest release];
|
|
|
+ [mtlsamplerlinear release];
|
|
|
+ [mtlbufconstants release];
|
|
|
+ [view release];
|
|
|
+ [data release];
|
|
|
+ [mtldevice release];
|
|
|
+#endif
|
|
|
+
|
|
|
+ return renderer;
|
|
|
+}}
|
|
|
+
|
|
|
+SDL_RenderDriver METAL_RenderDriver = {
|
|
|
+ METAL_CreateRenderer,
|
|
|
+ {
|
|
|
+ "metal",
|
|
|
+ (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
|
|
|
+ 6,
|
|
|
+ {
|
|
|
+ SDL_PIXELFORMAT_ARGB8888,
|
|
|
+ SDL_PIXELFORMAT_ABGR8888,
|
|
|
+ SDL_PIXELFORMAT_YV12,
|
|
|
+ SDL_PIXELFORMAT_IYUV,
|
|
|
+ SDL_PIXELFORMAT_NV12,
|
|
|
+ SDL_PIXELFORMAT_NV21
|
|
|
+ },
|
|
|
+ 0, 0,
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
|
|
|
|
|
|
/* vi: set ts=4 sw=4 expandtab: */
|