| | |
| | | /* |
| | | Simple DirectMedia Layer |
| | | Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org> |
| | | Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> |
| | | |
| | | This software is provided 'as-is', without any express or implied |
| | | warranty. In no event will the authors be held liable for any damages |
| | |
| | | #include "SDL_log.h" |
| | | #include "SDL_assert.h" |
| | | #include "SDL_syswm.h" |
| | | #include "SDL_metal.h" |
| | | #include "../SDL_sysrender.h" |
| | | |
| | | #ifdef __MACOSX__ |
| | | #include "../../video/cocoa/SDL_cocoametalview.h" |
| | | #else |
| | | #include "../../video/uikit/SDL_uikitmetalview.h" |
| | | #endif |
| | | #include <Availability.h> |
| | | #import <Metal/Metal.h> |
| | | #import <QuartzCore/CAMetalLayer.h> |
| | | |
| | | #ifdef __MACOSX__ |
| | | #import <AppKit/NSView.h> |
| | | #endif |
| | | |
| | | /* Regenerate these with build-metal-shaders.sh */ |
| | | #ifdef __MACOSX__ |
| | | #include "SDL_shaders_metal_osx.h" |
| | | #elif defined(__TVOS__) |
| | | #include "SDL_shaders_metal_tvos.h" |
| | | #else |
| | | #include "SDL_shaders_metal_ios.h" |
| | | #endif |
| | | |
| | | /* 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_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); |
| | | 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. */ |
| | | /* Use native type alignments from https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf */ |
| | | #ifdef __MACOSX__ |
| | | #define CONSTANT_ALIGN 256 |
| | | #define CONSTANT_ALIGN(x) (256) |
| | | #else |
| | | #define CONSTANT_ALIGN 4 |
| | | #define CONSTANT_ALIGN(x) (x < 4 ? 4 : x) |
| | | #endif |
| | | |
| | | #define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1))) |
| | | #define DEVICE_ALIGN(x) (x < 4 ? 4 : x) |
| | | |
| | | #define ALIGN_CONSTANTS(align, size) ((size + CONSTANT_ALIGN(align) - 1) & (~(CONSTANT_ALIGN(align) - 1))) |
| | | |
| | | static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF; |
| | | static const size_t CONSTANTS_OFFSET_IDENTITY = 0; |
| | | static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16); |
| | | static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16); |
| | | static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4); |
| | | static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4); |
| | | static const size_t CONSTANTS_OFFSET_CLEAR_VERTS = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 4 * 4); |
| | | static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_CLEAR_VERTS + sizeof(float) * 6; |
| | | static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16); |
| | | static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16); |
| | | static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4); |
| | | static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(16, CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4); |
| | | static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 4 * 4; |
| | | |
| | | typedef enum SDL_MetalVertexFunction |
| | | { |
| | |
| | | @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest; |
| | | @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear; |
| | | @property (nonatomic, retain) id<MTLBuffer> mtlbufconstants; |
| | | @property (nonatomic, retain) id<MTLBuffer> mtlbufquadindices; |
| | | @property (nonatomic, assign) SDL_MetalView mtlview; |
| | | @property (nonatomic, retain) CAMetalLayer *mtllayer; |
| | | @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc; |
| | | @property (nonatomic, assign) METAL_ShaderPipelines *activepipelines; |
| | |
| | | [_mtlsamplernearest release]; |
| | | [_mtlsamplerlinear release]; |
| | | [_mtlbufconstants release]; |
| | | [_mtlbufquadindices release]; |
| | | [_mtllayer release]; |
| | | [_mtlpassdesc release]; |
| | | [super dealloc]; |
| | |
| | | @property (nonatomic, assign) BOOL yuv; |
| | | @property (nonatomic, assign) BOOL nv12; |
| | | @property (nonatomic, assign) size_t conversionBufferOffset; |
| | | @property (nonatomic, assign) BOOL hasdata; |
| | | |
| | | @property (nonatomic, retain) id<MTLBuffer> lockedbuffer; |
| | | @property (nonatomic, assign) SDL_Rect lockedrect; |
| | | @end |
| | | |
| | | @implementation METAL_TextureData |
| | |
| | | [_mtltexture release]; |
| | | [_mtltexture_uv release]; |
| | | [_mtlsampler release]; |
| | | [_lockedbuffer release]; |
| | | [super dealloc]; |
| | | } |
| | | #endif |
| | |
| | | mtlpipedesc.vertexFunction = mtlvertfn; |
| | | mtlpipedesc.fragmentFunction = mtlfragfn; |
| | | |
| | | MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0]; |
| | | MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor]; |
| | | |
| | | switch (cache->vertexFunction) { |
| | | case SDL_METAL_VERTEX_SOLID: |
| | | /* position (float2) */ |
| | | vertdesc.layouts[0].stride = sizeof(float) * 2; |
| | | vertdesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; |
| | | |
| | | vertdesc.attributes[0].format = MTLVertexFormatFloat2; |
| | | vertdesc.attributes[0].offset = 0; |
| | | vertdesc.attributes[0].bufferIndex = 0; |
| | | break; |
| | | case SDL_METAL_VERTEX_COPY: |
| | | /* position (float2), texcoord (float2) */ |
| | | vertdesc.layouts[0].stride = sizeof(float) * 4; |
| | | vertdesc.layouts[0].stepFunction = MTLVertexStepFunctionPerVertex; |
| | | |
| | | vertdesc.attributes[0].format = MTLVertexFormatFloat2; |
| | | vertdesc.attributes[0].offset = 0; |
| | | vertdesc.attributes[0].bufferIndex = 0; |
| | | |
| | | vertdesc.attributes[1].format = MTLVertexFormatFloat2; |
| | | vertdesc.attributes[1].offset = sizeof(float) * 2; |
| | | vertdesc.attributes[1].bufferIndex = 0; |
| | | break; |
| | | } |
| | | |
| | | mtlpipedesc.vertexDescriptor = vertdesc; |
| | | |
| | | MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0]; |
| | | rtdesc.pixelFormat = cache->renderTargetFormat; |
| | | |
| | | if (blendmode != SDL_BLENDMODE_NONE) { |
| | |
| | | MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND); |
| | | MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD); |
| | | MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD); |
| | | MakePipelineState(data, cache, @" (blend=mul)", SDL_BLENDMODE_MUL); |
| | | } |
| | | |
| | | static void |
| | |
| | | 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; |
| | | |
| | | 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]; |
| | | 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->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; |
| | | renderer->GetMetalLayer = METAL_GetMetalLayer; |
| | | renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder; |
| | | |
| | | renderer->info = METAL_RenderDriver.info; |
| | | renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); |
| | | |
| | | #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; |
| | | }} |
| | | |
| | | static void |
| | | METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load) |
| | | METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color, id<MTLBuffer> vertex_buffer) |
| | | { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | |
| | |
| | | SDL_assert(mtltexture); |
| | | |
| | | if (load == MTLLoadActionClear) { |
| | | MTLClearColor color = MTLClearColorMake(renderer->r/255.0, renderer->g/255.0, renderer->b/255.0, renderer->a/255.0); |
| | | data.mtlpassdesc.colorAttachments[0].clearColor = color; |
| | | SDL_assert(clear_color != NULL); |
| | | data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color; |
| | | } |
| | | |
| | | data.mtlpassdesc.colorAttachments[0].loadAction = load; |
| | |
| | | data.mtlcmdencoder.label = @"SDL metal renderer render target"; |
| | | } |
| | | |
| | | /* Set up buffer bindings for positions, texcoords, and color once here, |
| | | * the offsets are adjusted in the code that uses them. */ |
| | | if (vertex_buffer != nil) { |
| | | [data.mtlcmdencoder setVertexBuffer:vertex_buffer offset:0 atIndex:0]; |
| | | [data.mtlcmdencoder setFragmentBuffer:vertex_buffer offset:0 atIndex:0]; |
| | | } |
| | | |
| | | data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat); |
| | | |
| | | /* Make sure the viewport and clip rect are set on the new render pass. */ |
| | | METAL_UpdateViewport(renderer); |
| | | METAL_UpdateClipRect(renderer); |
| | | // 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]; |
| | | } |
| | | } |
| | | |
| | |
| | | return 0; |
| | | }} |
| | | |
| | | static void |
| | | METAL_UploadTextureData(id<MTLTexture> texture, SDL_Rect rect, int slice, |
| | | const void * pixels, int pitch) |
| | | { |
| | | [texture replaceRegion:MTLRegionMake2D(rect.x, rect.y, rect.w, rect.h) |
| | | mipmapLevel:0 |
| | | slice:slice |
| | | withBytes:pixels |
| | | bytesPerRow:pitch |
| | | bytesPerImage:0]; |
| | | } |
| | | |
| | | static MTLStorageMode |
| | | METAL_GetStorageMode(id<MTLResource> resource) |
| | | { |
| | | /* iOS 8 does not have this method. */ |
| | | if ([resource respondsToSelector:@selector(storageMode)]) { |
| | | return resource.storageMode; |
| | | } |
| | | return MTLStorageModeShared; |
| | | } |
| | | |
| | | static int |
| | | METAL_UpdateTextureInternal(SDL_Renderer * renderer, METAL_TextureData *texturedata, |
| | | id<MTLTexture> texture, SDL_Rect rect, int slice, |
| | | const void * pixels, int pitch) |
| | | { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | SDL_Rect stagingrect = {0, 0, rect.w, rect.h}; |
| | | MTLTextureDescriptor *desc; |
| | | |
| | | /* If the texture is managed or shared and this is the first upload, we can |
| | | * use replaceRegion to upload to it directly. Otherwise we upload the data |
| | | * to a staging texture and copy that over. */ |
| | | if (!texturedata.hasdata && METAL_GetStorageMode(texture) != MTLStorageModePrivate) { |
| | | METAL_UploadTextureData(texture, rect, slice, pixels, pitch); |
| | | return 0; |
| | | } |
| | | |
| | | desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:texture.pixelFormat |
| | | width:rect.w |
| | | height:rect.h |
| | | mipmapped:NO]; |
| | | |
| | | if (desc == nil) { |
| | | return SDL_OutOfMemory(); |
| | | } |
| | | |
| | | /* TODO: We could have a pool of textures or a MTLHeap we allocate from, |
| | | * and release a staging texture back to the pool in the command buffer's |
| | | * completion handler. */ |
| | | id<MTLTexture> stagingtex = [data.mtldevice newTextureWithDescriptor:desc]; |
| | | if (stagingtex == nil) { |
| | | return SDL_OutOfMemory(); |
| | | } |
| | | |
| | | #if !__has_feature(objc_arc) |
| | | [stagingtex autorelease]; |
| | | #endif |
| | | |
| | | METAL_UploadTextureData(stagingtex, stagingrect, 0, pixels, pitch); |
| | | |
| | | if (data.mtlcmdencoder != nil) { |
| | | [data.mtlcmdencoder endEncoding]; |
| | | data.mtlcmdencoder = nil; |
| | | } |
| | | |
| | | if (data.mtlcmdbuffer == nil) { |
| | | data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; |
| | | } |
| | | |
| | | id<MTLBlitCommandEncoder> blitcmd = [data.mtlcmdbuffer blitCommandEncoder]; |
| | | |
| | | [blitcmd copyFromTexture:stagingtex |
| | | sourceSlice:0 |
| | | sourceLevel:0 |
| | | sourceOrigin:MTLOriginMake(0, 0, 0) |
| | | sourceSize:MTLSizeMake(rect.w, rect.h, 1) |
| | | toTexture:texture |
| | | destinationSlice:slice |
| | | destinationLevel:0 |
| | | destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)]; |
| | | |
| | | [blitcmd endEncoding]; |
| | | |
| | | /* TODO: This isn't very efficient for the YUV formats, which call |
| | | * UpdateTextureInternal multiple times in a row. */ |
| | | [data.mtlcmdbuffer commit]; |
| | | data.mtlcmdbuffer = nil; |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | static int |
| | | METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture, |
| | | const SDL_Rect * rect, const void *pixels, int pitch) |
| | | 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 (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, pixels, pitch) < 0) { |
| | | return -1; |
| | | } |
| | | |
| | | if (texturedata.yuv) { |
| | | int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; |
| | | int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; |
| | | int UVpitch = (pitch + 1) / 2; |
| | | SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2}; |
| | | |
| | | /* 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]; |
| | | if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Uslice, pixels, UVpitch) < 0) { |
| | | return -1; |
| | | } |
| | | |
| | | /* 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]; |
| | | pixels = (const void*)((const Uint8*)pixels + UVrect.h * UVpitch); |
| | | if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Vslice, pixels, UVpitch) < 0) { |
| | | return -1; |
| | | } |
| | | } |
| | | |
| | | if (texturedata.nv12) { |
| | | SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2}; |
| | | int UVpitch = 2 * ((pitch + 1) / 2); |
| | | |
| | | /* 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]; |
| | | if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, 0, pixels, UVpitch) < 0) { |
| | | return -1; |
| | | } |
| | | } |
| | | |
| | | texturedata.hasdata = YES; |
| | | |
| | | return 0; |
| | | }} |
| | |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | const int Uslice = 0; |
| | | const int Vslice = 1; |
| | | SDL_Rect UVrect = {rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2}; |
| | | |
| | | /* 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]; |
| | | if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture, *rect, 0, Yplane, Ypitch) < 0) { |
| | | return -1; |
| | | } |
| | | if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Uslice, Uplane, Upitch)) { |
| | | return -1; |
| | | } |
| | | if (METAL_UpdateTextureInternal(renderer, texturedata, texturedata.mtltexture_uv, UVrect, Vslice, Vplane, Vpitch)) { |
| | | return -1; |
| | | } |
| | | |
| | | [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]; |
| | | texturedata.hasdata = YES; |
| | | |
| | | 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 |
| | | } |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | int buffersize = 0; |
| | | id<MTLBuffer> lockedbuffer = nil; |
| | | |
| | | if (rect->w <= 0 || rect->h <= 0) { |
| | | return SDL_SetError("Invalid rectangle dimensions for LockTexture."); |
| | | } |
| | | |
| | | *pitch = SDL_BYTESPERPIXEL(texture->format) * rect->w; |
| | | |
| | | if (texturedata.yuv || texturedata.nv12) { |
| | | buffersize = ((*pitch) * rect->h) + (2 * (*pitch + 1) / 2) * ((rect->h + 1) / 2); |
| | | } else { |
| | | buffersize = (*pitch) * rect->h; |
| | | } |
| | | |
| | | lockedbuffer = [data.mtldevice newBufferWithLength:buffersize options:MTLResourceStorageModeShared]; |
| | | if (lockedbuffer == nil) { |
| | | return SDL_OutOfMemory(); |
| | | } |
| | | |
| | | texturedata.lockedrect = *rect; |
| | | texturedata.lockedbuffer = lockedbuffer; |
| | | *pixels = [lockedbuffer contents]; |
| | | |
| | | /* METAL_TextureData.lockedbuffer retains. */ |
| | | #if !__has_feature(objc_arc) |
| | | [lockedbuffer release]; |
| | | #endif |
| | | |
| | | return 0; |
| | | }} |
| | | |
| | | static void |
| | | METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture) |
| | | { |
| | | // !!! FIXME: write me |
| | | } |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | SDL_Rect rect = texturedata.lockedrect; |
| | | int pitch = SDL_BYTESPERPIXEL(texture->format) * rect.w; |
| | | SDL_Rect UVrect = {rect.x / 2, rect.y / 2, (rect.w + 1) / 2, (rect.h + 1) / 2}; |
| | | |
| | | if (texturedata.lockedbuffer == nil) { |
| | | return; |
| | | } |
| | | |
| | | if (data.mtlcmdencoder != nil) { |
| | | [data.mtlcmdencoder endEncoding]; |
| | | data.mtlcmdencoder = nil; |
| | | } |
| | | |
| | | if (data.mtlcmdbuffer == nil) { |
| | | data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer]; |
| | | } |
| | | |
| | | id<MTLBlitCommandEncoder> blitcmd = [data.mtlcmdbuffer blitCommandEncoder]; |
| | | |
| | | [blitcmd copyFromBuffer:texturedata.lockedbuffer |
| | | sourceOffset:0 |
| | | sourceBytesPerRow:pitch |
| | | sourceBytesPerImage:0 |
| | | sourceSize:MTLSizeMake(rect.w, rect.h, 1) |
| | | toTexture:texturedata.mtltexture |
| | | destinationSlice:0 |
| | | destinationLevel:0 |
| | | destinationOrigin:MTLOriginMake(rect.x, rect.y, 0)]; |
| | | |
| | | if (texturedata.yuv) { |
| | | int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0; |
| | | int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1; |
| | | int UVpitch = (pitch + 1) / 2; |
| | | |
| | | [blitcmd copyFromBuffer:texturedata.lockedbuffer |
| | | sourceOffset:rect.h * pitch |
| | | sourceBytesPerRow:UVpitch |
| | | sourceBytesPerImage:UVpitch * UVrect.h |
| | | sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) |
| | | toTexture:texturedata.mtltexture_uv |
| | | destinationSlice:Uslice |
| | | destinationLevel:0 |
| | | destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; |
| | | |
| | | [blitcmd copyFromBuffer:texturedata.lockedbuffer |
| | | sourceOffset:(rect.h * pitch) + UVrect.h * UVpitch |
| | | sourceBytesPerRow:UVpitch |
| | | sourceBytesPerImage:UVpitch * UVrect.h |
| | | sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) |
| | | toTexture:texturedata.mtltexture_uv |
| | | destinationSlice:Vslice |
| | | destinationLevel:0 |
| | | destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; |
| | | } |
| | | |
| | | if (texturedata.nv12) { |
| | | int UVpitch = 2 * ((pitch + 1) / 2); |
| | | |
| | | [blitcmd copyFromBuffer:texturedata.lockedbuffer |
| | | sourceOffset:rect.h * pitch |
| | | sourceBytesPerRow:UVpitch |
| | | sourceBytesPerImage:0 |
| | | sourceSize:MTLSizeMake(UVrect.w, UVrect.h, 1) |
| | | toTexture:texturedata.mtltexture_uv |
| | | destinationSlice:0 |
| | | destinationLevel:0 |
| | | destinationOrigin:MTLOriginMake(UVrect.x, UVrect.y, 0)]; |
| | | } |
| | | |
| | | [blitcmd endEncoding]; |
| | | |
| | | [data.mtlcmdbuffer commit]; |
| | | data.mtlcmdbuffer = nil; |
| | | |
| | | texturedata.lockedbuffer = nil; /* Retained property, so it calls release. */ |
| | | texturedata.hasdata = YES; |
| | | }} |
| | | |
| | | static void |
| | | METAL_SetTextureScaleMode(SDL_Renderer * renderer, SDL_Texture * texture, SDL_ScaleMode scaleMode) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | |
| | | if (scaleMode == SDL_ScaleModeNearest) { |
| | | texturedata.mtlsampler = data.mtlsamplernearest; |
| | | } else { |
| | | texturedata.mtlsampler = data.mtlsamplerlinear; |
| | | } |
| | | }} |
| | | |
| | | static int |
| | | METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture) |
| | |
| | | return 0; |
| | | }} |
| | | |
| | | static int |
| | | METAL_SetOrthographicProjection(SDL_Renderer *renderer, int w, int h) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | float projection[4][4]; |
| | | |
| | | if (!w || !h) { |
| | | return 0; |
| | | } |
| | | |
| | | /* Prepare an orthographic projection */ |
| | | projection[0][0] = 2.0f / w; |
| | | projection[0][1] = 0.0f; |
| | | projection[0][2] = 0.0f; |
| | | projection[0][3] = 0.0f; |
| | | projection[1][0] = 0.0f; |
| | | projection[1][1] = -2.0f / h; |
| | | projection[1][2] = 0.0f; |
| | | projection[1][3] = 0.0f; |
| | | projection[2][0] = 0.0f; |
| | | projection[2][1] = 0.0f; |
| | | projection[2][2] = 0.0f; |
| | | projection[2][3] = 0.0f; |
| | | projection[3][0] = -1.0f; |
| | | projection[3][1] = 1.0f; |
| | | projection[3][2] = 0.0f; |
| | | projection[3][3] = 1.0f; |
| | | |
| | | // !!! FIXME: This should be in a buffer... |
| | | [data.mtlcmdencoder setVertexBytes:projection length:sizeof(float)*16 atIndex:2]; |
| | | return 0; |
| | | }} |
| | | |
| | | static int |
| | | METAL_UpdateViewport(SDL_Renderer * renderer) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | if (data.mtlcmdencoder) { |
| | | 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]; |
| | | METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h); |
| | | } |
| | | return 0; |
| | | }} |
| | | |
| | | static int |
| | | METAL_UpdateClipRect(SDL_Renderer * renderer) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | if (data.mtlcmdencoder) { |
| | | MTLScissorRect mtlrect; |
| | | // !!! FIXME: should this care about the viewport? |
| | | 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]; |
| | | } |
| | | } |
| | | return 0; |
| | | }} |
| | | |
| | | static int |
| | | METAL_RenderClear(SDL_Renderer * renderer) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | |
| | | /* Since we set up the render command encoder lazily when a draw is |
| | | * requested, we can do the fast path hardware clear if no draws have |
| | | * happened since the last SetRenderTarget. */ |
| | | if (data.mtlcmdencoder == nil) { |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear); |
| | | } else { |
| | | // !!! 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; |
| | | viewport.width = data.mtlpassdesc.colorAttachments[0].texture.width; |
| | | viewport.height = data.mtlpassdesc.colorAttachments[0].texture.height; |
| | | viewport.znear = 0.0; |
| | | viewport.zfar = 1.0; |
| | | |
| | | // Slow path for clearing: draw a filled fullscreen triangle. |
| | | METAL_SetOrthographicProjection(renderer, 1, 1); |
| | | [data.mtlcmdencoder setViewport:viewport]; |
| | | [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, SDL_METAL_FRAGMENT_SOLID, SDL_BLENDMODE_NONE)]; |
| | | [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_CLEAR_VERTS atIndex:0]; |
| | | [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3]; |
| | | [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; |
| | | |
| | | // 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; |
| | | [data.mtlcmdencoder setViewport:viewport]; |
| | | METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h); |
| | | } |
| | | |
| | | return 0; |
| | | }} |
| | | |
| | | // normalize a value from 0.0f to len into 0.0f to 1.0f. |
| | | static inline float |
| | |
| | | } |
| | | |
| | | static int |
| | | DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count, |
| | | const MTLPrimitiveType primtype) |
| | | { @autoreleasepool { |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | 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(16), &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, DEVICE_ALIGN(16), &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; |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | |
| | | // !!! 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 }; |
| | | |
| | | [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, SDL_METAL_FRAGMENT_SOLID, renderer->blendMode)]; |
| | | [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; |
| | | |
| | | [data.mtlcmdencoder setVertexBytes:points length:vertlen atIndex:0]; |
| | | [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM atIndex:3]; |
| | | [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count]; |
| | | |
| | | float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); |
| | | if (!verts) { |
| | | return -1; |
| | | } |
| | | cmd->data.draw.count = count; |
| | | SDL_memcpy(verts, points, vertlen); |
| | | return 0; |
| | | }} |
| | | |
| | | 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) |
| | | METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count) |
| | | { |
| | | return DrawVerts(renderer, points, count, MTLPrimitiveTypeLineStrip); |
| | | } |
| | | const size_t vertlen = (sizeof (float) * 8) * count; |
| | | float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); |
| | | if (!verts) { |
| | | return -1; |
| | | } |
| | | |
| | | static int |
| | | METAL_RenderFillRects(SDL_Renderer * renderer, const SDL_FRect * rects, int count) |
| | | { @autoreleasepool { |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | cmd->data.draw.count = count; |
| | | |
| | | // !!! 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 }; |
| | | |
| | | [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, SDL_METAL_FRAGMENT_SOLID, renderer->blendMode)]; |
| | | [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; |
| | | [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3]; |
| | | |
| | | /* Quads in the following vertex order (matches the quad index buffer): |
| | | * 1---3 |
| | | * | \ | |
| | | * 0---2 |
| | | */ |
| | | for (int i = 0; i < count; i++, rects++) { |
| | | if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue; |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | const float verts[] = { |
| | | rects->x, rects->y + rects->h, |
| | | rects->x, rects->y, |
| | | rects->x + rects->w, rects->y + rects->h, |
| | | rects->x + rects->w, rects->y |
| | | }; |
| | | |
| | | [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0]; |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; |
| | | if (cmd->data.draw.count == 0) { |
| | | cmd->command = SDL_RENDERCMD_NO_OP; // nothing to do, just skip this one later. |
| | | } |
| | | |
| | | return 0; |
| | | }} |
| | | |
| | | static void |
| | | METAL_SetupRenderCopy(METAL_RenderData *data, SDL_Texture *texture, METAL_TextureData *texturedata) |
| | | { |
| | | 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; |
| | | } |
| | | |
| | | [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.activepipelines, texturedata.fragmentFunction, texture->blendMode)]; |
| | | [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0]; |
| | | [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0]; |
| | | |
| | | [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0]; |
| | | |
| | | if (texturedata.yuv || texturedata.nv12) { |
| | | [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1]; |
| | | [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1]; |
| | | } |
| | | } |
| | | |
| | | static int |
| | | METAL_RenderCopy(SDL_Renderer * renderer, SDL_Texture * texture, |
| | | const SDL_Rect * srcrect, const SDL_FRect * dstrect) |
| | | { @autoreleasepool { |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | const float texw = (float) texturedata.mtltexture.width; |
| | | const float texh = (float) texturedata.mtltexture.height; |
| | | METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture, |
| | | const SDL_Rect * srcrect, const SDL_FRect * dstrect) |
| | | { |
| | | const float texw = (float) texture->w; |
| | | const float texh = (float) texture->h; |
| | | // !!! FIXME: use an index buffer |
| | | const size_t vertlen = (sizeof (float) * 16); |
| | | float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); |
| | | if (!verts) { |
| | | return -1; |
| | | } |
| | | |
| | | METAL_SetupRenderCopy(data, texture, texturedata); |
| | | cmd->data.draw.count = 1; |
| | | |
| | | const float xy[] = { |
| | | dstrect->x, dstrect->y + dstrect->h, |
| | | dstrect->x, dstrect->y, |
| | | dstrect->x + dstrect->w, dstrect->y + dstrect->h, |
| | | dstrect->x + dstrect->w, dstrect->y |
| | | }; |
| | | /* Interleaved positions and texture coordinates */ |
| | | *(verts++) = dstrect->x; |
| | | *(verts++) = dstrect->y + dstrect->h; |
| | | *(verts++) = normtex(srcrect->x, texw); |
| | | *(verts++) = normtex(srcrect->y + srcrect->h, texh); |
| | | |
| | | const float uv[] = { |
| | | 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 + srcrect->h, texh), |
| | | normtex(srcrect->x + srcrect->w, texw), normtex(srcrect->y, texh) |
| | | }; |
| | | *(verts++) = dstrect->x; |
| | | *(verts++) = dstrect->y; |
| | | *(verts++) = normtex(srcrect->x, texw); |
| | | *(verts++) = normtex(srcrect->y, texh); |
| | | |
| | | [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0]; |
| | | [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1]; |
| | | [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3]; |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; |
| | | *(verts++) = dstrect->x + dstrect->w; |
| | | *(verts++) = dstrect->y + dstrect->h; |
| | | *(verts++) = normtex(srcrect->x + srcrect->w, texw); |
| | | *(verts++) = normtex(srcrect->y + srcrect->h, texh); |
| | | |
| | | *(verts++) = dstrect->x + dstrect->w; |
| | | *(verts++) = dstrect->y; |
| | | *(verts++) = normtex(srcrect->x + srcrect->w, texw); |
| | | *(verts++) = normtex(srcrect->y, texh); |
| | | |
| | | return 0; |
| | | }} |
| | | } |
| | | |
| | | 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) |
| | | { @autoreleasepool { |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | const float texw = (float) texturedata.mtltexture.width; |
| | | const float texh = (float) texturedata.mtltexture.height; |
| | | float transform[16]; |
| | | 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) |
| | | { |
| | | const float texw = (float) texture->w; |
| | | const float texh = (float) texture->h; |
| | | const float rads = (float)(M_PI * (float) angle / 180.0f); |
| | | const float c = cosf(rads), s = sinf(rads); |
| | | float minu, maxu, minv, maxv; |
| | | const size_t vertlen = (sizeof (float) * 32); |
| | | float *verts; |
| | | |
| | | METAL_SetupRenderCopy(data, texture, texturedata); |
| | | // cheat and store this offset in (count) because it needs to be aligned in ways other fields don't and we aren't using count otherwise. |
| | | verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN(16), &cmd->data.draw.count); |
| | | if (!verts) { |
| | | return -1; |
| | | } |
| | | |
| | | minu = normtex(srcrect->x, texw); |
| | | maxu = normtex(srcrect->x + srcrect->w, texw); |
| | | minv = normtex(srcrect->y, texh); |
| | | maxv = normtex(srcrect->y + srcrect->h, texh); |
| | | // transform matrix |
| | | SDL_memset(verts, '\0', sizeof (*verts) * 16); |
| | | verts[10] = verts[15] = 1.0f; |
| | | // rotation |
| | | verts[0] = c; |
| | | verts[1] = s; |
| | | verts[4] = -s; |
| | | verts[5] = c; |
| | | |
| | | // translation |
| | | verts[12] = dstrect->x + center->x; |
| | | verts[13] = dstrect->y + center->y; |
| | | |
| | | // rest of the vertices don't need the aggressive alignment. Pack them in. |
| | | verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, DEVICE_ALIGN(8), &cmd->data.draw.first); |
| | | if (!verts) { |
| | | return -1; |
| | | } |
| | | |
| | | minu = normtex(srcquad->x, texw); |
| | | maxu = normtex(srcquad->x + srcquad->w, texw); |
| | | minv = normtex(srcquad->y, texh); |
| | | maxv = normtex(srcquad->y + srcquad->h, texh); |
| | | |
| | | if (flip & SDL_FLIP_HORIZONTAL) { |
| | | float tmp = maxu; |
| | |
| | | minv = tmp; |
| | | } |
| | | |
| | | const float uv[] = { |
| | | minu, maxv, |
| | | minu, minv, |
| | | maxu, maxv, |
| | | maxu, minv |
| | | }; |
| | | /* Interleaved positions and texture coordinates */ |
| | | *(verts++) = -center->x; |
| | | *(verts++) = dstrect->h - center->y; |
| | | *(verts++) = minu; |
| | | *(verts++) = maxv; |
| | | |
| | | const float xy[] = { |
| | | -center->x, dstrect->h - center->y, |
| | | -center->x, -center->y, |
| | | dstrect->w - center->x, dstrect->h - center->y, |
| | | dstrect->w - center->x, -center->y |
| | | }; |
| | | *(verts++) = -center->x; |
| | | *(verts++) = -center->y; |
| | | *(verts++) = minu; |
| | | *(verts++) = minv; |
| | | |
| | | { |
| | | float rads = (float)(M_PI * (float) angle / 180.0f); |
| | | float c = cosf(rads), s = sinf(rads); |
| | | SDL_memset(transform, 0, sizeof(transform)); |
| | | *(verts++) = dstrect->w - center->x; |
| | | *(verts++) = dstrect->h - center->y; |
| | | *(verts++) = maxu; |
| | | *(verts++) = maxv; |
| | | |
| | | transform[10] = transform[15] = 1.0f; |
| | | *(verts++) = dstrect->w - center->x; |
| | | *(verts++) = -center->y; |
| | | *(verts++) = maxu; |
| | | *(verts++) = minv; |
| | | |
| | | /* Rotation */ |
| | | transform[0] = c; |
| | | transform[1] = s; |
| | | transform[4] = -s; |
| | | transform[5] = c; |
| | | return 0; |
| | | } |
| | | |
| | | /* Translation */ |
| | | transform[12] = dstrect->x + center->x; |
| | | transform[13] = dstrect->y + center->y; |
| | | |
| | | typedef struct |
| | | { |
| | | #if __has_feature(objc_arc) |
| | | __unsafe_unretained id<MTLRenderPipelineState> pipeline; |
| | | __unsafe_unretained id<MTLBuffer> vertex_buffer; |
| | | #else |
| | | id<MTLRenderPipelineState> pipeline; |
| | | id<MTLBuffer> vertex_buffer; |
| | | #endif |
| | | size_t constants_offset; |
| | | SDL_Texture *texture; |
| | | SDL_bool cliprect_dirty; |
| | | SDL_bool cliprect_enabled; |
| | | SDL_Rect cliprect; |
| | | SDL_bool viewport_dirty; |
| | | SDL_Rect viewport; |
| | | size_t projection_offset; |
| | | SDL_bool color_dirty; |
| | | size_t color_offset; |
| | | } METAL_DrawStateCache; |
| | | |
| | | static void |
| | | SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader, |
| | | const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache) |
| | | { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | const SDL_BlendMode blend = cmd->data.draw.blend; |
| | | size_t first = cmd->data.draw.first; |
| | | id<MTLRenderPipelineState> newpipeline; |
| | | |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, statecache->vertex_buffer); |
| | | |
| | | if (statecache->viewport_dirty) { |
| | | MTLViewport viewport; |
| | | viewport.originX = statecache->viewport.x; |
| | | viewport.originY = statecache->viewport.y; |
| | | viewport.width = statecache->viewport.w; |
| | | viewport.height = statecache->viewport.h; |
| | | viewport.znear = 0.0; |
| | | viewport.zfar = 1.0; |
| | | [data.mtlcmdencoder setViewport:viewport]; |
| | | [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2]; // projection |
| | | statecache->viewport_dirty = SDL_FALSE; |
| | | } |
| | | |
| | | [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0]; |
| | | [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1]; |
| | | [data.mtlcmdencoder setVertexBytes:transform length:sizeof(transform) atIndex:3]; |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; |
| | | if (statecache->cliprect_dirty) { |
| | | MTLScissorRect mtlrect; |
| | | if (statecache->cliprect_enabled) { |
| | | const SDL_Rect *rect = &statecache->cliprect; |
| | | mtlrect.x = statecache->viewport.x + rect->x; |
| | | mtlrect.y = statecache->viewport.y + rect->y; |
| | | mtlrect.width = rect->w; |
| | | mtlrect.height = rect->h; |
| | | } else { |
| | | mtlrect.x = statecache->viewport.x; |
| | | mtlrect.y = statecache->viewport.y; |
| | | mtlrect.width = statecache->viewport.w; |
| | | mtlrect.height = statecache->viewport.h; |
| | | } |
| | | if (mtlrect.width > 0 && mtlrect.height > 0) { |
| | | [data.mtlcmdencoder setScissorRect:mtlrect]; |
| | | } |
| | | statecache->cliprect_dirty = SDL_FALSE; |
| | | } |
| | | |
| | | if (statecache->color_dirty) { |
| | | [data.mtlcmdencoder setFragmentBufferOffset:statecache->color_offset atIndex:0]; |
| | | statecache->color_dirty = SDL_FALSE; |
| | | } |
| | | |
| | | newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend); |
| | | if (newpipeline != statecache->pipeline) { |
| | | [data.mtlcmdencoder setRenderPipelineState:newpipeline]; |
| | | statecache->pipeline = newpipeline; |
| | | } |
| | | |
| | | if (constants_offset != statecache->constants_offset) { |
| | | if (constants_offset != CONSTANTS_OFFSET_INVALID) { |
| | | [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3]; |
| | | } |
| | | statecache->constants_offset = constants_offset; |
| | | } |
| | | |
| | | [data.mtlcmdencoder setVertexBufferOffset:first atIndex:0]; /* position/texcoords */ |
| | | } |
| | | |
| | | static void |
| | | SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset, |
| | | id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache) |
| | | { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | SDL_Texture *texture = cmd->data.draw.texture; |
| | | METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata; |
| | | |
| | | SetDrawState(renderer, cmd, texturedata.fragmentFunction, constants_offset, mtlbufvertex, statecache); |
| | | |
| | | if (texture != statecache->texture) { |
| | | METAL_TextureData *oldtexturedata = NULL; |
| | | if (statecache->texture) { |
| | | oldtexturedata = (__bridge METAL_TextureData *) statecache->texture->driverdata; |
| | | } |
| | | if (!oldtexturedata || (texturedata.mtlsampler != oldtexturedata.mtlsampler)) { |
| | | [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0]; |
| | | } |
| | | |
| | | [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0]; |
| | | if (texturedata.yuv || texturedata.nv12) { |
| | | [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1]; |
| | | [data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1]; |
| | | } |
| | | statecache->texture = texture; |
| | | } |
| | | } |
| | | |
| | | static int |
| | | METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_DrawStateCache statecache; |
| | | SDL_zero(statecache); |
| | | |
| | | id<MTLBuffer> mtlbufvertex = nil; |
| | | |
| | | statecache.pipeline = nil; |
| | | statecache.vertex_buffer = nil; |
| | | statecache.constants_offset = CONSTANTS_OFFSET_INVALID; |
| | | statecache.texture = NULL; |
| | | statecache.color_dirty = SDL_TRUE; |
| | | statecache.cliprect_dirty = SDL_TRUE; |
| | | statecache.viewport_dirty = SDL_TRUE; |
| | | statecache.projection_offset = 0; |
| | | statecache.color_offset = 0; |
| | | |
| | | // !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation? |
| | | if (vertsize > 0) { |
| | | /* We can memcpy to a shared buffer from the CPU and read it from the GPU |
| | | * without any extra copying. It's a bit slower on macOS to read shared |
| | | * data from the GPU than to read managed/private data, but we avoid the |
| | | * cost of copying the data and the code's simpler. Apple's best |
| | | * practices guide recommends this approach for streamed vertex data. |
| | | * TODO: this buffer is also used for constants. Is performance still |
| | | * good for those, or should we have a managed buffer for them? */ |
| | | mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared]; |
| | | #if !__has_feature(objc_arc) |
| | | [mtlbufvertex autorelease]; |
| | | #endif |
| | | mtlbufvertex.label = @"SDL vertex data"; |
| | | SDL_memcpy([mtlbufvertex contents], vertices, vertsize); |
| | | |
| | | statecache.vertex_buffer = mtlbufvertex; |
| | | } |
| | | |
| | | // If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh. |
| | | [data.mtlcmdencoder endEncoding]; |
| | | [data.mtlcmdbuffer commit]; |
| | | data.mtlcmdencoder = nil; |
| | | data.mtlcmdbuffer = nil; |
| | | |
| | | while (cmd) { |
| | | switch (cmd->command) { |
| | | case SDL_RENDERCMD_SETVIEWPORT: { |
| | | SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof (statecache.viewport)); |
| | | statecache.projection_offset = cmd->data.viewport.first; |
| | | statecache.viewport_dirty = SDL_TRUE; |
| | | statecache.cliprect_dirty = SDL_TRUE; |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_SETCLIPRECT: { |
| | | SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof (statecache.cliprect)); |
| | | statecache.cliprect_enabled = cmd->data.cliprect.enabled; |
| | | statecache.cliprect_dirty = SDL_TRUE; |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_SETDRAWCOLOR: { |
| | | statecache.color_offset = cmd->data.color.first; |
| | | statecache.color_dirty = SDL_TRUE; |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_CLEAR: { |
| | | /* If we're already encoding a command buffer, dump it without committing it. We'd just |
| | | clear all its work anyhow, and starting a new encoder will let us use a hardware clear |
| | | operation via MTLLoadActionClear. */ |
| | | if (data.mtlcmdencoder != nil) { |
| | | [data.mtlcmdencoder endEncoding]; |
| | | |
| | | // !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing. |
| | | [data.mtlcmdbuffer commit]; |
| | | data.mtlcmdencoder = nil; |
| | | data.mtlcmdbuffer = nil; |
| | | } |
| | | |
| | | // force all this state to be reconfigured on next command buffer. |
| | | statecache.pipeline = nil; |
| | | statecache.constants_offset = CONSTANTS_OFFSET_INVALID; |
| | | statecache.texture = NULL; |
| | | statecache.color_dirty = SDL_TRUE; |
| | | statecache.cliprect_dirty = SDL_TRUE; |
| | | statecache.viewport_dirty = SDL_TRUE; |
| | | |
| | | const Uint8 r = cmd->data.color.r; |
| | | const Uint8 g = cmd->data.color.g; |
| | | const Uint8 b = cmd->data.color.b; |
| | | const Uint8 a = cmd->data.color.a; |
| | | MTLClearColor color = MTLClearColorMake(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); |
| | | |
| | | // get new command encoder, set up with an initial clear operation. |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color, mtlbufvertex); |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_DRAW_POINTS: |
| | | case SDL_RENDERCMD_DRAW_LINES: { |
| | | const size_t count = cmd->data.draw.count; |
| | | const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip; |
| | | SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache); |
| | | [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count]; |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_FILL_RECTS: { |
| | | const size_t count = cmd->data.draw.count; |
| | | const size_t maxcount = UINT16_MAX / 4; |
| | | SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache); |
| | | if (count == 1) { |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; |
| | | } else { |
| | | /* Our index buffer has 16 bit indices, so we can only draw |
| | | * 65k vertices (16k rects) at a time. */ |
| | | for (size_t i = 0; i < count; i += maxcount) { |
| | | /* Set the vertex buffer offset for our current positions. |
| | | * The vertex buffer itself was bound in SetDrawState. */ |
| | | [data.mtlcmdencoder setVertexBufferOffset:cmd->data.draw.first + i*sizeof(float)*8 atIndex:0]; |
| | | [data.mtlcmdencoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle |
| | | indexCount:SDL_min(maxcount, count - i) * 6 |
| | | indexType:MTLIndexTypeUInt16 |
| | | indexBuffer:data.mtlbufquadindices |
| | | indexBufferOffset:0]; |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_COPY: { |
| | | SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache); |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_COPY_EX: { |
| | | SetCopyState(renderer, cmd, CONSTANTS_OFFSET_INVALID, mtlbufvertex, &statecache); |
| | | [data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.count atIndex:3]; // transform |
| | | [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4]; |
| | | break; |
| | | } |
| | | |
| | | case SDL_RENDERCMD_NO_OP: |
| | | break; |
| | | } |
| | | cmd = cmd->next; |
| | | } |
| | | |
| | | return 0; |
| | | }} |
| | |
| | | Uint32 pixel_format, void * pixels, int pitch) |
| | | { @autoreleasepool { |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); |
| | | |
| | | /* Make sure we have a valid MTLTexture to read from, and an active command |
| | | * buffer we can wait for. */ |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | |
| | | /* Wait for the current command buffer to finish, so we don't read from the |
| | | * texture before the GPU finishes rendering to it. */ |
| | | if (data.mtlcmdencoder) { |
| | | [data.mtlcmdencoder endEncoding]; |
| | | [data.mtlcmdbuffer commit]; |
| | | [data.mtlcmdbuffer waitUntilCompleted]; |
| | | |
| | | data.mtlcmdencoder = nil; |
| | | data.mtlcmdbuffer = nil; |
| | | } |
| | | |
| | | [data.mtlcmdencoder endEncoding]; |
| | | id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture; |
| | | |
| | | #ifdef __MACOSX__ |
| | | /* on macOS with managed-storage textures, we need to tell the driver to |
| | | * update the CPU-side copy of the texture data. |
| | | * NOTE: Currently all of our textures are managed on macOS. We'll need some |
| | | * extra copying for any private textures. */ |
| | | if (METAL_GetStorageMode(mtltexture) == MTLStorageModeManaged) { |
| | | id<MTLBlitCommandEncoder> blit = [data.mtlcmdbuffer blitCommandEncoder]; |
| | | [blit synchronizeResource:mtltexture]; |
| | | [blit endEncoding]; |
| | | } |
| | | #endif |
| | | |
| | | /* Commit the current command buffer and wait until it's completed, to make |
| | | * sure the GPU has finished rendering to it by the time we read it. */ |
| | | [data.mtlcmdbuffer commit]; |
| | | [data.mtlcmdbuffer waitUntilCompleted]; |
| | | data.mtlcmdencoder = nil; |
| | | data.mtlcmdbuffer = nil; |
| | | |
| | | MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h); |
| | | |
| | | // we only do BGRA8 or RGBA8 at the moment, so 4 will do. |
| | |
| | | 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); |
| | | |
| | | /* Set up an active command buffer and encoder once we're done. It will use |
| | | * the same texture that was active before (even if it's part of the swap |
| | | * chain), since we didn't clear that when waiting for the command buffer to |
| | | * complete. */ |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | |
| | | return status; |
| | | }} |
| | | |
| | |
| | | } |
| | | |
| | | DestroyAllPipelines(data.allpipelines, data.pipelinescount); |
| | | |
| | | SDL_Metal_DestroyView(data.mtlview); |
| | | } |
| | | |
| | | SDL_free(renderer); |
| | |
| | | static void * |
| | | METAL_GetMetalCommandEncoder(SDL_Renderer * renderer) |
| | | { @autoreleasepool { |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad); |
| | | METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL, nil); |
| | | METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata; |
| | | 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_MetalView view = NULL; |
| | | CAMetalLayer *layer = 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; |
| | | } |
| | | |
| | | view = SDL_Metal_CreateView(window); |
| | | |
| | | if (view == NULL) { |
| | | #if !__has_feature(objc_arc) |
| | | [mtldevice release]; |
| | | #endif |
| | | SDL_free(renderer); |
| | | return NULL; |
| | | } |
| | | |
| | | // !!! FIXME: error checking on all of this. |
| | | data = [[METAL_RenderData alloc] init]; |
| | | |
| | | if (data == nil) { |
| | | #if !__has_feature(objc_arc) |
| | | [mtldevice release]; |
| | | #endif |
| | | SDL_Metal_DestroyView(view); |
| | | SDL_free(renderer); |
| | | return NULL; |
| | | } |
| | | |
| | | renderer->driverdata = (void*)CFBridgingRetain(data); |
| | | renderer->window = window; |
| | | |
| | | data.mtlview = view; |
| | | |
| | | #ifdef __MACOSX__ |
| | | layer = (CAMetalLayer *)[(NSView *)view layer]; |
| | | #else |
| | | layer = (CAMetalLayer *)[(__bridge UIView *)view layer]; |
| | | #endif |
| | | |
| | | layer.device = mtldevice; |
| | | |
| | | /* 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 */ |
| | | }; |
| | | |
| | | id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared]; |
| | | #if !__has_feature(objc_arc) |
| | | [mtlbufconstantstaging autorelease]; |
| | | #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)); |
| | | |
| | | int quadcount = UINT16_MAX / 4; |
| | | size_t indicessize = sizeof(UInt16) * quadcount * 6; |
| | | id<MTLBuffer> mtlbufquadindicesstaging = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModeShared]; |
| | | #if !__has_feature(objc_arc) |
| | | [mtlbufquadindicesstaging autorelease]; |
| | | #endif |
| | | |
| | | /* Quads in the following vertex order (matches the FillRects vertices): |
| | | * 1---3 |
| | | * | \ | |
| | | * 0---2 |
| | | */ |
| | | UInt16 *indexdata = [mtlbufquadindicesstaging contents]; |
| | | for (int i = 0; i < quadcount; i++) { |
| | | indexdata[i * 6 + 0] = i * 4 + 0; |
| | | indexdata[i * 6 + 1] = i * 4 + 1; |
| | | indexdata[i * 6 + 2] = i * 4 + 2; |
| | | |
| | | indexdata[i * 6 + 3] = i * 4 + 2; |
| | | indexdata[i * 6 + 4] = i * 4 + 1; |
| | | indexdata[i * 6 + 5] = i * 4 + 3; |
| | | } |
| | | |
| | | id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate]; |
| | | data.mtlbufconstants = mtlbufconstants; |
| | | data.mtlbufconstants.label = @"SDL constant data"; |
| | | |
| | | id<MTLBuffer> mtlbufquadindices = [data.mtldevice newBufferWithLength:indicessize options:MTLResourceStorageModePrivate]; |
| | | data.mtlbufquadindices = mtlbufquadindices; |
| | | data.mtlbufquadindices.label = @"SDL quad index buffer"; |
| | | |
| | | id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer]; |
| | | id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder]; |
| | | |
| | | [blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH]; |
| | | [blitcmd copyFromBuffer:mtlbufquadindicesstaging sourceOffset:0 toBuffer:mtlbufquadindices destinationOffset:0 size:indicessize]; |
| | | |
| | | [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->SetTextureScaleMode = METAL_SetTextureScaleMode; |
| | | 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; |
| | | if (data.mtllayer.displaySyncEnabled) { |
| | | renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC; |
| | | } |
| | | } 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 |
| | | #pragma clang diagnostic push |
| | | #pragma clang diagnostic ignored "-Wunguarded-availability-new" |
| | | if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) { |
| | | maxtexsize = 16384; |
| | | } else |
| | | #pragma clang diagnostic pop |
| | | #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]; |
| | | [mtlbufquadindices 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: */ |