icculus@11729
|
1 |
/*
|
icculus@11729
|
2 |
Simple DirectMedia Layer
|
slouken@11811
|
3 |
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
|
icculus@11729
|
4 |
|
icculus@11729
|
5 |
This software is provided 'as-is', without any express or implied
|
icculus@11729
|
6 |
warranty. In no event will the authors be held liable for any damages
|
icculus@11729
|
7 |
arising from the use of this software.
|
icculus@11729
|
8 |
|
icculus@11729
|
9 |
Permission is granted to anyone to use this software for any purpose,
|
icculus@11729
|
10 |
including commercial applications, and to alter it and redistribute it
|
icculus@11729
|
11 |
freely, subject to the following restrictions:
|
icculus@11729
|
12 |
|
icculus@11729
|
13 |
1. The origin of this software must not be misrepresented; you must not
|
icculus@11729
|
14 |
claim that you wrote the original software. If you use this software
|
icculus@11729
|
15 |
in a product, an acknowledgment in the product documentation would be
|
icculus@11729
|
16 |
appreciated but is not required.
|
icculus@11729
|
17 |
2. Altered source versions must be plainly marked as such, and must not be
|
icculus@11729
|
18 |
misrepresented as being the original software.
|
icculus@11729
|
19 |
3. This notice may not be removed or altered from any source distribution.
|
icculus@11729
|
20 |
*/
|
icculus@11729
|
21 |
#include "../../SDL_internal.h"
|
icculus@11729
|
22 |
|
icculus@11729
|
23 |
#if SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED
|
icculus@11729
|
24 |
|
icculus@11729
|
25 |
#include "SDL_hints.h"
|
icculus@11729
|
26 |
#include "SDL_log.h"
|
icculus@11729
|
27 |
#include "SDL_assert.h"
|
icculus@11729
|
28 |
#include "SDL_syswm.h"
|
icculus@11729
|
29 |
#include "../SDL_sysrender.h"
|
icculus@11729
|
30 |
|
slouken@11732
|
31 |
#ifdef __MACOSX__
|
slime73@11798
|
32 |
#include "../../video/cocoa/SDL_cocoametalview.h"
|
slouken@11732
|
33 |
#else
|
slouken@11732
|
34 |
#include "../../video/uikit/SDL_uikitmetalview.h"
|
slouken@11732
|
35 |
#endif
|
slime73@11821
|
36 |
#include <Availability.h>
|
slime73@11798
|
37 |
#import <Metal/Metal.h>
|
slime73@11798
|
38 |
#import <QuartzCore/CAMetalLayer.h>
|
icculus@11729
|
39 |
|
slouken@11730
|
40 |
/* Regenerate these with build-metal-shaders.sh */
|
slouken@11730
|
41 |
#ifdef __MACOSX__
|
slouken@11730
|
42 |
#include "SDL_shaders_metal_osx.h"
|
slouken@11730
|
43 |
#else
|
slouken@11730
|
44 |
#include "SDL_shaders_metal_ios.h"
|
slouken@11730
|
45 |
#endif
|
icculus@11729
|
46 |
|
icculus@11729
|
47 |
/* Apple Metal renderer implementation */
|
icculus@11729
|
48 |
|
slime73@11810
|
49 |
/* macOS requires constants in a buffer to have a 256 byte alignment. */
|
slime73@11810
|
50 |
#ifdef __MACOSX__
|
slime73@11810
|
51 |
#define CONSTANT_ALIGN 256
|
slime73@11810
|
52 |
#else
|
slime73@11810
|
53 |
#define CONSTANT_ALIGN 4
|
slime73@11810
|
54 |
#endif
|
slime73@11810
|
55 |
|
slime73@11810
|
56 |
#define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1)))
|
slime73@11810
|
57 |
|
icculus@12213
|
58 |
static const size_t CONSTANTS_OFFSET_INVALID = 0xFFFFFFFF;
|
slime73@11810
|
59 |
static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
|
slime73@11810
|
60 |
static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
|
slime73@11819
|
61 |
static const size_t CONSTANTS_OFFSET_DECODE_JPEG = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
|
slime73@11819
|
62 |
static const size_t CONSTANTS_OFFSET_DECODE_BT601 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_JPEG + sizeof(float) * 4 * 4);
|
slime73@11819
|
63 |
static const size_t CONSTANTS_OFFSET_DECODE_BT709 = ALIGN_CONSTANTS(CONSTANTS_OFFSET_DECODE_BT601 + sizeof(float) * 4 * 4);
|
slime73@12392
|
64 |
static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_DECODE_BT709 + sizeof(float) * 4 * 4;
|
slime73@11810
|
65 |
|
slime73@11800
|
66 |
typedef enum SDL_MetalVertexFunction
|
slime73@11800
|
67 |
{
|
slime73@11800
|
68 |
SDL_METAL_VERTEX_SOLID,
|
slime73@11800
|
69 |
SDL_METAL_VERTEX_COPY,
|
slime73@11800
|
70 |
} SDL_MetalVertexFunction;
|
slime73@11800
|
71 |
|
slime73@11800
|
72 |
typedef enum SDL_MetalFragmentFunction
|
slime73@11800
|
73 |
{
|
slime73@11819
|
74 |
SDL_METAL_FRAGMENT_SOLID = 0,
|
slime73@11801
|
75 |
SDL_METAL_FRAGMENT_COPY,
|
slime73@11819
|
76 |
SDL_METAL_FRAGMENT_YUV,
|
slime73@11819
|
77 |
SDL_METAL_FRAGMENT_NV12,
|
slime73@11819
|
78 |
SDL_METAL_FRAGMENT_NV21,
|
slime73@11819
|
79 |
SDL_METAL_FRAGMENT_COUNT,
|
slime73@11800
|
80 |
} SDL_MetalFragmentFunction;
|
slime73@11800
|
81 |
|
slime73@11800
|
82 |
typedef struct METAL_PipelineState
|
slime73@11800
|
83 |
{
|
slime73@11800
|
84 |
SDL_BlendMode blendMode;
|
slime73@11800
|
85 |
void *pipe;
|
slime73@11800
|
86 |
} METAL_PipelineState;
|
slime73@11800
|
87 |
|
slime73@11800
|
88 |
typedef struct METAL_PipelineCache
|
slime73@11800
|
89 |
{
|
slime73@11800
|
90 |
METAL_PipelineState *states;
|
slime73@11800
|
91 |
int count;
|
slime73@11800
|
92 |
SDL_MetalVertexFunction vertexFunction;
|
slime73@11800
|
93 |
SDL_MetalFragmentFunction fragmentFunction;
|
slime73@11820
|
94 |
MTLPixelFormat renderTargetFormat;
|
slime73@11800
|
95 |
const char *label;
|
slime73@11800
|
96 |
} METAL_PipelineCache;
|
slime73@11800
|
97 |
|
slime73@11819
|
98 |
/* Each shader combination used by drawing functions has a separate pipeline
|
slime73@11820
|
99 |
* cache, and we have a separate list of caches for each render target pixel
|
slime73@11820
|
100 |
* format. This is more efficient than iterating over a global cache to find
|
slime73@11820
|
101 |
* the pipeline based on the specified shader combination and RT pixel format,
|
slime73@11820
|
102 |
* since we know what the RT pixel format is when we set the render target, and
|
slime73@11820
|
103 |
* we know what the shader combination is inside each drawing function's code. */
|
slime73@11819
|
104 |
typedef struct METAL_ShaderPipelines
|
slime73@11819
|
105 |
{
|
slime73@11820
|
106 |
MTLPixelFormat renderTargetFormat;
|
slime73@11819
|
107 |
METAL_PipelineCache caches[SDL_METAL_FRAGMENT_COUNT];
|
slime73@11819
|
108 |
} METAL_ShaderPipelines;
|
slime73@11819
|
109 |
|
slouken@11732
|
110 |
@interface METAL_RenderData : NSObject
|
slouken@11735
|
111 |
@property (nonatomic, retain) id<MTLDevice> mtldevice;
|
slouken@11735
|
112 |
@property (nonatomic, retain) id<MTLCommandQueue> mtlcmdqueue;
|
slouken@11735
|
113 |
@property (nonatomic, retain) id<MTLCommandBuffer> mtlcmdbuffer;
|
slouken@11735
|
114 |
@property (nonatomic, retain) id<MTLRenderCommandEncoder> mtlcmdencoder;
|
slouken@11735
|
115 |
@property (nonatomic, retain) id<MTLLibrary> mtllibrary;
|
slouken@11735
|
116 |
@property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
|
slime73@11801
|
117 |
@property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest;
|
slime73@11801
|
118 |
@property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear;
|
slime73@11810
|
119 |
@property (nonatomic, retain) id<MTLBuffer> mtlbufconstants;
|
slouken@11735
|
120 |
@property (nonatomic, retain) CAMetalLayer *mtllayer;
|
slouken@11735
|
121 |
@property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
|
slime73@11820
|
122 |
@property (nonatomic, assign) METAL_ShaderPipelines *activepipelines;
|
slime73@11820
|
123 |
@property (nonatomic, assign) METAL_ShaderPipelines *allpipelines;
|
slime73@11820
|
124 |
@property (nonatomic, assign) int pipelinescount;
|
slouken@11732
|
125 |
@end
|
icculus@11729
|
126 |
|
slouken@11732
|
127 |
@implementation METAL_RenderData
|
slime73@11805
|
128 |
#if !__has_feature(objc_arc)
|
slime73@11804
|
129 |
- (void)dealloc
|
slime73@11804
|
130 |
{
|
slime73@11804
|
131 |
[_mtldevice release];
|
slime73@11804
|
132 |
[_mtlcmdqueue release];
|
slime73@11804
|
133 |
[_mtlcmdbuffer release];
|
slime73@11804
|
134 |
[_mtlcmdencoder release];
|
slime73@11804
|
135 |
[_mtllibrary release];
|
slime73@11804
|
136 |
[_mtlbackbuffer release];
|
slime73@11804
|
137 |
[_mtlsamplernearest release];
|
slime73@11804
|
138 |
[_mtlsamplerlinear release];
|
slime73@11810
|
139 |
[_mtlbufconstants release];
|
slime73@11804
|
140 |
[_mtllayer release];
|
slime73@11804
|
141 |
[_mtlpassdesc release];
|
slime73@11804
|
142 |
[super dealloc];
|
slime73@11804
|
143 |
}
|
slime73@11804
|
144 |
#endif
|
slouken@11732
|
145 |
@end
|
icculus@11729
|
146 |
|
slouken@11753
|
147 |
@interface METAL_TextureData : NSObject
|
slouken@11753
|
148 |
@property (nonatomic, retain) id<MTLTexture> mtltexture;
|
slime73@11819
|
149 |
@property (nonatomic, retain) id<MTLTexture> mtltexture_uv;
|
slime73@11801
|
150 |
@property (nonatomic, retain) id<MTLSamplerState> mtlsampler;
|
slime73@11819
|
151 |
@property (nonatomic, assign) SDL_MetalFragmentFunction fragmentFunction;
|
slime73@11819
|
152 |
@property (nonatomic, assign) BOOL yuv;
|
slime73@11819
|
153 |
@property (nonatomic, assign) BOOL nv12;
|
slime73@11819
|
154 |
@property (nonatomic, assign) size_t conversionBufferOffset;
|
slouken@11753
|
155 |
@end
|
slouken@11753
|
156 |
|
slouken@11753
|
157 |
@implementation METAL_TextureData
|
slime73@11805
|
158 |
#if !__has_feature(objc_arc)
|
slime73@11804
|
159 |
- (void)dealloc
|
slime73@11804
|
160 |
{
|
slime73@11804
|
161 |
[_mtltexture release];
|
slime73@11819
|
162 |
[_mtltexture_uv release];
|
slime73@11804
|
163 |
[_mtlsampler release];
|
slime73@11804
|
164 |
[super dealloc];
|
slime73@11804
|
165 |
}
|
slime73@11804
|
166 |
#endif
|
slouken@11753
|
167 |
@end
|
slouken@11753
|
168 |
|
icculus@11729
|
169 |
static int
|
icculus@11729
|
170 |
IsMetalAvailable(const SDL_SysWMinfo *syswm)
|
icculus@11729
|
171 |
{
|
slouken@11733
|
172 |
if (syswm->subsystem != SDL_SYSWM_COCOA && syswm->subsystem != SDL_SYSWM_UIKIT) {
|
slouken@11733
|
173 |
return SDL_SetError("Metal render target only supports Cocoa and UIKit video targets at the moment.");
|
icculus@11729
|
174 |
}
|
icculus@11729
|
175 |
|
icculus@11729
|
176 |
// this checks a weak symbol.
|
icculus@11742
|
177 |
#if (defined(__MACOSX__) && (MAC_OS_X_VERSION_MIN_REQUIRED < 101100))
|
icculus@11729
|
178 |
if (MTLCreateSystemDefaultDevice == NULL) { // probably on 10.10 or lower.
|
icculus@11729
|
179 |
return SDL_SetError("Metal framework not available on this system");
|
icculus@11729
|
180 |
}
|
icculus@11729
|
181 |
#endif
|
icculus@11729
|
182 |
|
icculus@11729
|
183 |
return 0;
|
icculus@11729
|
184 |
}
|
icculus@11729
|
185 |
|
slime73@11800
|
186 |
static const MTLBlendOperation invalidBlendOperation = (MTLBlendOperation)0xFFFFFFFF;
|
slime73@11800
|
187 |
static const MTLBlendFactor invalidBlendFactor = (MTLBlendFactor)0xFFFFFFFF;
|
slime73@11800
|
188 |
|
slime73@11800
|
189 |
static MTLBlendOperation
|
slime73@11800
|
190 |
GetBlendOperation(SDL_BlendOperation operation)
|
slime73@11800
|
191 |
{
|
slime73@11800
|
192 |
switch (operation) {
|
slime73@11800
|
193 |
case SDL_BLENDOPERATION_ADD: return MTLBlendOperationAdd;
|
slime73@11800
|
194 |
case SDL_BLENDOPERATION_SUBTRACT: return MTLBlendOperationSubtract;
|
slime73@11800
|
195 |
case SDL_BLENDOPERATION_REV_SUBTRACT: return MTLBlendOperationReverseSubtract;
|
slime73@11800
|
196 |
case SDL_BLENDOPERATION_MINIMUM: return MTLBlendOperationMin;
|
slime73@11800
|
197 |
case SDL_BLENDOPERATION_MAXIMUM: return MTLBlendOperationMax;
|
slime73@11800
|
198 |
default: return invalidBlendOperation;
|
slime73@11800
|
199 |
}
|
slime73@11800
|
200 |
}
|
slime73@11800
|
201 |
|
slime73@11800
|
202 |
static MTLBlendFactor
|
slime73@11800
|
203 |
GetBlendFactor(SDL_BlendFactor factor)
|
slime73@11800
|
204 |
{
|
slime73@11800
|
205 |
switch (factor) {
|
slime73@11800
|
206 |
case SDL_BLENDFACTOR_ZERO: return MTLBlendFactorZero;
|
slime73@11800
|
207 |
case SDL_BLENDFACTOR_ONE: return MTLBlendFactorOne;
|
slime73@11800
|
208 |
case SDL_BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
|
slime73@11800
|
209 |
case SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
|
slime73@11800
|
210 |
case SDL_BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
|
slime73@11800
|
211 |
case SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
|
slime73@11800
|
212 |
case SDL_BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
|
slime73@11800
|
213 |
case SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
|
slime73@11800
|
214 |
case SDL_BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
|
slime73@11800
|
215 |
case SDL_BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
|
slime73@11800
|
216 |
default: return invalidBlendFactor;
|
slime73@11800
|
217 |
}
|
slime73@11800
|
218 |
}
|
slime73@11800
|
219 |
|
slime73@11800
|
220 |
static NSString *
|
slime73@11800
|
221 |
GetVertexFunctionName(SDL_MetalVertexFunction function)
|
slime73@11800
|
222 |
{
|
slime73@11800
|
223 |
switch (function) {
|
slime73@11800
|
224 |
case SDL_METAL_VERTEX_SOLID: return @"SDL_Solid_vertex";
|
slime73@11800
|
225 |
case SDL_METAL_VERTEX_COPY: return @"SDL_Copy_vertex";
|
slime73@11800
|
226 |
default: return nil;
|
slime73@11800
|
227 |
}
|
slime73@11800
|
228 |
}
|
slime73@11800
|
229 |
|
slime73@11800
|
230 |
static NSString *
|
slime73@11800
|
231 |
GetFragmentFunctionName(SDL_MetalFragmentFunction function)
|
slime73@11800
|
232 |
{
|
slime73@11800
|
233 |
switch (function) {
|
slime73@11800
|
234 |
case SDL_METAL_FRAGMENT_SOLID: return @"SDL_Solid_fragment";
|
slime73@11801
|
235 |
case SDL_METAL_FRAGMENT_COPY: return @"SDL_Copy_fragment";
|
slime73@11819
|
236 |
case SDL_METAL_FRAGMENT_YUV: return @"SDL_YUV_fragment";
|
slime73@11819
|
237 |
case SDL_METAL_FRAGMENT_NV12: return @"SDL_NV12_fragment";
|
slime73@11819
|
238 |
case SDL_METAL_FRAGMENT_NV21: return @"SDL_NV21_fragment";
|
slime73@11800
|
239 |
default: return nil;
|
slime73@11800
|
240 |
}
|
slime73@11800
|
241 |
}
|
slime73@11800
|
242 |
|
icculus@11729
|
243 |
static id<MTLRenderPipelineState>
|
slime73@11800
|
244 |
MakePipelineState(METAL_RenderData *data, METAL_PipelineCache *cache,
|
slime73@11800
|
245 |
NSString *blendlabel, SDL_BlendMode blendmode)
|
icculus@11729
|
246 |
{
|
slime73@11800
|
247 |
id<MTLFunction> mtlvertfn = [data.mtllibrary newFunctionWithName:GetVertexFunctionName(cache->vertexFunction)];
|
slime73@11800
|
248 |
id<MTLFunction> mtlfragfn = [data.mtllibrary newFunctionWithName:GetFragmentFunctionName(cache->fragmentFunction)];
|
icculus@11729
|
249 |
SDL_assert(mtlvertfn != nil);
|
icculus@11729
|
250 |
SDL_assert(mtlfragfn != nil);
|
icculus@11729
|
251 |
|
icculus@11729
|
252 |
MTLRenderPipelineDescriptor *mtlpipedesc = [[MTLRenderPipelineDescriptor alloc] init];
|
icculus@11729
|
253 |
mtlpipedesc.vertexFunction = mtlvertfn;
|
icculus@11729
|
254 |
mtlpipedesc.fragmentFunction = mtlfragfn;
|
icculus@11729
|
255 |
|
slime73@11800
|
256 |
MTLRenderPipelineColorAttachmentDescriptor *rtdesc = mtlpipedesc.colorAttachments[0];
|
icculus@11729
|
257 |
|
slime73@11820
|
258 |
rtdesc.pixelFormat = cache->renderTargetFormat;
|
icculus@11729
|
259 |
|
slime73@11800
|
260 |
if (blendmode != SDL_BLENDMODE_NONE) {
|
slime73@11800
|
261 |
rtdesc.blendingEnabled = YES;
|
slime73@11800
|
262 |
rtdesc.sourceRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcColorFactor(blendmode));
|
slime73@11800
|
263 |
rtdesc.destinationRGBBlendFactor = GetBlendFactor(SDL_GetBlendModeDstColorFactor(blendmode));
|
slime73@11800
|
264 |
rtdesc.rgbBlendOperation = GetBlendOperation(SDL_GetBlendModeColorOperation(blendmode));
|
slime73@11800
|
265 |
rtdesc.sourceAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeSrcAlphaFactor(blendmode));
|
slime73@11800
|
266 |
rtdesc.destinationAlphaBlendFactor = GetBlendFactor(SDL_GetBlendModeDstAlphaFactor(blendmode));
|
slime73@11800
|
267 |
rtdesc.alphaBlendOperation = GetBlendOperation(SDL_GetBlendModeAlphaOperation(blendmode));
|
slime73@11800
|
268 |
} else {
|
slime73@11800
|
269 |
rtdesc.blendingEnabled = NO;
|
icculus@11729
|
270 |
}
|
icculus@11729
|
271 |
|
slime73@11800
|
272 |
mtlpipedesc.label = [@(cache->label) stringByAppendingString:blendlabel];
|
icculus@11729
|
273 |
|
icculus@11729
|
274 |
NSError *err = nil;
|
slime73@11800
|
275 |
id<MTLRenderPipelineState> state = [data.mtldevice newRenderPipelineStateWithDescriptor:mtlpipedesc error:&err];
|
icculus@11729
|
276 |
SDL_assert(err == nil);
|
slime73@11800
|
277 |
|
slime73@11800
|
278 |
METAL_PipelineState pipeline;
|
slime73@11800
|
279 |
pipeline.blendMode = blendmode;
|
slime73@11800
|
280 |
pipeline.pipe = (void *)CFBridgingRetain(state);
|
slime73@11800
|
281 |
|
slime73@11800
|
282 |
METAL_PipelineState *states = SDL_realloc(cache->states, (cache->count + 1) * sizeof(pipeline));
|
slime73@11800
|
283 |
|
slouken@11732
|
284 |
#if !__has_feature(objc_arc)
|
icculus@11729
|
285 |
[mtlpipedesc release]; // !!! FIXME: can these be reused for each creation, or does the pipeline obtain it?
|
icculus@11729
|
286 |
[mtlvertfn release];
|
icculus@11729
|
287 |
[mtlfragfn release];
|
slime73@11800
|
288 |
[state release];
|
slouken@11732
|
289 |
#endif
|
slime73@11800
|
290 |
|
slime73@11800
|
291 |
if (states) {
|
slime73@11800
|
292 |
states[cache->count++] = pipeline;
|
slime73@11800
|
293 |
cache->states = states;
|
slime73@11800
|
294 |
return (__bridge id<MTLRenderPipelineState>)pipeline.pipe;
|
slime73@11800
|
295 |
} else {
|
slime73@11800
|
296 |
CFBridgingRelease(pipeline.pipe);
|
slime73@11800
|
297 |
SDL_OutOfMemory();
|
slime73@11800
|
298 |
return NULL;
|
slime73@11800
|
299 |
}
|
slime73@11800
|
300 |
}
|
slime73@11800
|
301 |
|
slime73@11819
|
302 |
static void
|
slime73@11820
|
303 |
MakePipelineCache(METAL_RenderData *data, METAL_PipelineCache *cache, const char *label,
|
slime73@11820
|
304 |
MTLPixelFormat rtformat, SDL_MetalVertexFunction vertfn, SDL_MetalFragmentFunction fragfn)
|
slime73@11800
|
305 |
{
|
slime73@11800
|
306 |
SDL_zerop(cache);
|
slime73@11800
|
307 |
|
slime73@11800
|
308 |
cache->vertexFunction = vertfn;
|
slime73@11800
|
309 |
cache->fragmentFunction = fragfn;
|
slime73@11820
|
310 |
cache->renderTargetFormat = rtformat;
|
slime73@11800
|
311 |
cache->label = label;
|
slime73@11800
|
312 |
|
slime73@11800
|
313 |
/* Create pipeline states for the default blend modes. Custom blend modes
|
slime73@11800
|
314 |
* will be added to the cache on-demand. */
|
slime73@11819
|
315 |
MakePipelineState(data, cache, @" (blend=none)", SDL_BLENDMODE_NONE);
|
slime73@11819
|
316 |
MakePipelineState(data, cache, @" (blend=blend)", SDL_BLENDMODE_BLEND);
|
slime73@11819
|
317 |
MakePipelineState(data, cache, @" (blend=add)", SDL_BLENDMODE_ADD);
|
slime73@11819
|
318 |
MakePipelineState(data, cache, @" (blend=mod)", SDL_BLENDMODE_MOD);
|
icculus@11729
|
319 |
}
|
icculus@11729
|
320 |
|
icculus@11729
|
321 |
static void
|
slime73@11800
|
322 |
DestroyPipelineCache(METAL_PipelineCache *cache)
|
icculus@11729
|
323 |
{
|
slime73@11800
|
324 |
if (cache != NULL) {
|
slime73@11800
|
325 |
for (int i = 0; i < cache->count; i++) {
|
slime73@11800
|
326 |
CFBridgingRelease(cache->states[i].pipe);
|
slime73@11800
|
327 |
}
|
slime73@11800
|
328 |
|
slime73@11800
|
329 |
SDL_free(cache->states);
|
slime73@11819
|
330 |
}
|
slime73@11819
|
331 |
}
|
slime73@11819
|
332 |
|
slime73@11820
|
333 |
void
|
slime73@11820
|
334 |
MakeShaderPipelines(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, MTLPixelFormat rtformat)
|
slime73@11820
|
335 |
{
|
slime73@11820
|
336 |
SDL_zerop(pipelines);
|
slime73@11820
|
337 |
|
slime73@11820
|
338 |
pipelines->renderTargetFormat = rtformat;
|
slime73@11820
|
339 |
|
slime73@11820
|
340 |
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_SOLID], "SDL primitives pipeline", rtformat, SDL_METAL_VERTEX_SOLID, SDL_METAL_FRAGMENT_SOLID);
|
slime73@11820
|
341 |
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_COPY], "SDL copy pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_COPY);
|
slime73@11820
|
342 |
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_YUV], "SDL YUV pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_YUV);
|
slime73@11820
|
343 |
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV12], "SDL NV12 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV12);
|
slime73@11820
|
344 |
MakePipelineCache(data, &pipelines->caches[SDL_METAL_FRAGMENT_NV21], "SDL NV21 pipeline", rtformat, SDL_METAL_VERTEX_COPY, SDL_METAL_FRAGMENT_NV21);
|
slime73@11820
|
345 |
}
|
slime73@11820
|
346 |
|
slime73@11819
|
347 |
static METAL_ShaderPipelines *
|
slime73@11820
|
348 |
ChooseShaderPipelines(METAL_RenderData *data, MTLPixelFormat rtformat)
|
slime73@11819
|
349 |
{
|
slime73@11820
|
350 |
METAL_ShaderPipelines *allpipelines = data.allpipelines;
|
slime73@11820
|
351 |
int count = data.pipelinescount;
|
slime73@11820
|
352 |
|
slime73@11820
|
353 |
for (int i = 0; i < count; i++) {
|
slime73@11820
|
354 |
if (allpipelines[i].renderTargetFormat == rtformat) {
|
slime73@11820
|
355 |
return &allpipelines[i];
|
slime73@11820
|
356 |
}
|
slime73@11820
|
357 |
}
|
slime73@11820
|
358 |
|
slime73@11820
|
359 |
allpipelines = SDL_realloc(allpipelines, (count + 1) * sizeof(METAL_ShaderPipelines));
|
slime73@11820
|
360 |
|
slime73@11820
|
361 |
if (allpipelines == NULL) {
|
slime73@11819
|
362 |
SDL_OutOfMemory();
|
slime73@11819
|
363 |
return NULL;
|
slime73@11819
|
364 |
}
|
slime73@11819
|
365 |
|
slime73@11820
|
366 |
MakeShaderPipelines(data, &allpipelines[count], rtformat);
|
slime73@11819
|
367 |
|
slime73@11820
|
368 |
data.allpipelines = allpipelines;
|
slime73@11820
|
369 |
data.pipelinescount = count + 1;
|
slime73@11820
|
370 |
|
slime73@11820
|
371 |
return &data.allpipelines[count];
|
slime73@11819
|
372 |
}
|
slime73@11819
|
373 |
|
slime73@11819
|
374 |
static void
|
slime73@11820
|
375 |
DestroyAllPipelines(METAL_ShaderPipelines *allpipelines, int count)
|
slime73@11819
|
376 |
{
|
slime73@11820
|
377 |
if (allpipelines != NULL) {
|
slime73@11820
|
378 |
for (int i = 0; i < count; i++) {
|
slime73@11820
|
379 |
for (int cache = 0; cache < SDL_METAL_FRAGMENT_COUNT; cache++) {
|
slime73@11820
|
380 |
DestroyPipelineCache(&allpipelines[i].caches[cache]);
|
slime73@11820
|
381 |
}
|
slime73@11819
|
382 |
}
|
slime73@11819
|
383 |
|
slime73@11820
|
384 |
SDL_free(allpipelines);
|
slime73@11800
|
385 |
}
|
icculus@11729
|
386 |
}
|
icculus@11729
|
387 |
|
icculus@11729
|
388 |
static inline id<MTLRenderPipelineState>
|
slime73@11819
|
389 |
ChoosePipelineState(METAL_RenderData *data, METAL_ShaderPipelines *pipelines, SDL_MetalFragmentFunction fragfn, SDL_BlendMode blendmode)
|
icculus@11729
|
390 |
{
|
slime73@11819
|
391 |
METAL_PipelineCache *cache = &pipelines->caches[fragfn];
|
slime73@11819
|
392 |
|
slime73@11800
|
393 |
for (int i = 0; i < cache->count; i++) {
|
slime73@11800
|
394 |
if (cache->states[i].blendMode == blendmode) {
|
slime73@11800
|
395 |
return (__bridge id<MTLRenderPipelineState>)cache->states[i].pipe;
|
slime73@11800
|
396 |
}
|
icculus@11729
|
397 |
}
|
slime73@11800
|
398 |
|
slime73@11819
|
399 |
return MakePipelineState(data, cache, [NSString stringWithFormat:@" (blend=custom 0x%x)", blendmode], blendmode);
|
icculus@11729
|
400 |
}
|
icculus@11729
|
401 |
|
slime73@11804
|
402 |
static void
|
icculus@12213
|
403 |
METAL_ActivateRenderCommandEncoder(SDL_Renderer * renderer, MTLLoadAction load, MTLClearColor *clear_color)
|
slouken@11735
|
404 |
{
|
slouken@11735
|
405 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@11729
|
406 |
|
slime73@11817
|
407 |
/* Our SetRenderTarget just signals that the next render operation should
|
slime73@11817
|
408 |
* set up a new render pass. This is where that work happens. */
|
slime73@11817
|
409 |
if (data.mtlcmdencoder == nil) {
|
slime73@11817
|
410 |
id<MTLTexture> mtltexture = nil;
|
slime73@11817
|
411 |
|
slime73@11817
|
412 |
if (renderer->target != NULL) {
|
slime73@11817
|
413 |
METAL_TextureData *texdata = (__bridge METAL_TextureData *)renderer->target->driverdata;
|
slime73@11817
|
414 |
mtltexture = texdata.mtltexture;
|
slime73@11817
|
415 |
} else {
|
slime73@11817
|
416 |
if (data.mtlbackbuffer == nil) {
|
slime73@11817
|
417 |
/* The backbuffer's contents aren't guaranteed to persist after
|
slime73@11817
|
418 |
* presenting, so we can leave it undefined when loading it. */
|
slime73@11817
|
419 |
data.mtlbackbuffer = [data.mtllayer nextDrawable];
|
slime73@11817
|
420 |
if (load == MTLLoadActionLoad) {
|
slime73@11817
|
421 |
load = MTLLoadActionDontCare;
|
slime73@11817
|
422 |
}
|
slime73@11817
|
423 |
}
|
slime73@11817
|
424 |
mtltexture = data.mtlbackbuffer.texture;
|
slime73@11817
|
425 |
}
|
slime73@11817
|
426 |
|
slime73@11817
|
427 |
SDL_assert(mtltexture);
|
slime73@11817
|
428 |
|
slime73@11817
|
429 |
if (load == MTLLoadActionClear) {
|
icculus@12213
|
430 |
SDL_assert(clear_color != NULL);
|
icculus@12213
|
431 |
data.mtlpassdesc.colorAttachments[0].clearColor = *clear_color;
|
slime73@11817
|
432 |
}
|
slime73@11817
|
433 |
|
slime73@11817
|
434 |
data.mtlpassdesc.colorAttachments[0].loadAction = load;
|
slime73@11817
|
435 |
data.mtlpassdesc.colorAttachments[0].texture = mtltexture;
|
slime73@11817
|
436 |
|
slouken@11735
|
437 |
data.mtlcmdbuffer = [data.mtlcmdqueue commandBuffer];
|
slouken@11735
|
438 |
data.mtlcmdencoder = [data.mtlcmdbuffer renderCommandEncoderWithDescriptor:data.mtlpassdesc];
|
icculus@11729
|
439 |
|
slime73@11817
|
440 |
if (data.mtlbackbuffer != nil && mtltexture == data.mtlbackbuffer.texture) {
|
slime73@11817
|
441 |
data.mtlcmdencoder.label = @"SDL metal renderer backbuffer";
|
slime73@11817
|
442 |
} else {
|
slime73@11817
|
443 |
data.mtlcmdencoder.label = @"SDL metal renderer render target";
|
slime73@11817
|
444 |
}
|
slime73@11817
|
445 |
|
slime73@11820
|
446 |
data.activepipelines = ChooseShaderPipelines(data, mtltexture.pixelFormat);
|
icculus@12215
|
447 |
|
icculus@12215
|
448 |
// make sure this has a definite place in the queue. This way it will
|
icculus@12215
|
449 |
// execute reliably whether the app tries to make its own command buffers
|
icculus@12215
|
450 |
// or whatever. This means we can _always_ batch rendering commands!
|
icculus@12215
|
451 |
[data.mtlcmdbuffer enqueue];
|
slouken@11735
|
452 |
}
|
icculus@11729
|
453 |
}
|
icculus@11729
|
454 |
|
icculus@11729
|
455 |
static void
|
icculus@11729
|
456 |
METAL_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
|
icculus@11729
|
457 |
{
|
icculus@12179
|
458 |
if (event->event == SDL_WINDOWEVENT_SHOWN ||
|
icculus@11729
|
459 |
event->event == SDL_WINDOWEVENT_HIDDEN) {
|
icculus@11729
|
460 |
// !!! FIXME: write me
|
icculus@11729
|
461 |
}
|
icculus@11729
|
462 |
}
|
icculus@11729
|
463 |
|
icculus@11729
|
464 |
static int
|
icculus@11729
|
465 |
METAL_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
|
slouken@11743
|
466 |
{ @autoreleasepool {
|
slime73@11798
|
467 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
slime73@11798
|
468 |
if (w) {
|
slime73@11798
|
469 |
*w = (int)data.mtllayer.drawableSize.width;
|
slime73@11798
|
470 |
}
|
slime73@11798
|
471 |
if (h) {
|
slime73@11798
|
472 |
*h = (int)data.mtllayer.drawableSize.height;
|
slime73@11798
|
473 |
}
|
icculus@11729
|
474 |
return 0;
|
slouken@11743
|
475 |
}}
|
icculus@11729
|
476 |
|
slime73@11800
|
477 |
static SDL_bool
|
slime73@11800
|
478 |
METAL_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
|
slime73@11800
|
479 |
{
|
slime73@11800
|
480 |
SDL_BlendFactor srcColorFactor = SDL_GetBlendModeSrcColorFactor(blendMode);
|
slime73@11800
|
481 |
SDL_BlendFactor srcAlphaFactor = SDL_GetBlendModeSrcAlphaFactor(blendMode);
|
slime73@11800
|
482 |
SDL_BlendOperation colorOperation = SDL_GetBlendModeColorOperation(blendMode);
|
slime73@11800
|
483 |
SDL_BlendFactor dstColorFactor = SDL_GetBlendModeDstColorFactor(blendMode);
|
slime73@11800
|
484 |
SDL_BlendFactor dstAlphaFactor = SDL_GetBlendModeDstAlphaFactor(blendMode);
|
slime73@11800
|
485 |
SDL_BlendOperation alphaOperation = SDL_GetBlendModeAlphaOperation(blendMode);
|
slime73@11800
|
486 |
|
slime73@11800
|
487 |
if (GetBlendFactor(srcColorFactor) == invalidBlendFactor ||
|
slime73@11800
|
488 |
GetBlendFactor(srcAlphaFactor) == invalidBlendFactor ||
|
slime73@11800
|
489 |
GetBlendOperation(colorOperation) == invalidBlendOperation ||
|
slime73@11800
|
490 |
GetBlendFactor(dstColorFactor) == invalidBlendFactor ||
|
slime73@11800
|
491 |
GetBlendFactor(dstAlphaFactor) == invalidBlendFactor ||
|
slime73@11800
|
492 |
GetBlendOperation(alphaOperation) == invalidBlendOperation) {
|
slime73@11800
|
493 |
return SDL_FALSE;
|
slime73@11800
|
494 |
}
|
slime73@11800
|
495 |
return SDL_TRUE;
|
slime73@11800
|
496 |
}
|
slime73@11800
|
497 |
|
icculus@11729
|
498 |
static int
|
icculus@11729
|
499 |
METAL_CreateTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
slouken@11743
|
500 |
{ @autoreleasepool {
|
slouken@11732
|
501 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
slime73@11819
|
502 |
MTLPixelFormat pixfmt;
|
icculus@11729
|
503 |
|
icculus@11729
|
504 |
switch (texture->format) {
|
slime73@11819
|
505 |
case SDL_PIXELFORMAT_ABGR8888:
|
slime73@11819
|
506 |
pixfmt = MTLPixelFormatRGBA8Unorm;
|
slime73@11819
|
507 |
break;
|
slime73@11819
|
508 |
case SDL_PIXELFORMAT_ARGB8888:
|
slime73@11819
|
509 |
pixfmt = MTLPixelFormatBGRA8Unorm;
|
slime73@11819
|
510 |
break;
|
slime73@11819
|
511 |
case SDL_PIXELFORMAT_IYUV:
|
slime73@11819
|
512 |
case SDL_PIXELFORMAT_YV12:
|
slime73@11819
|
513 |
case SDL_PIXELFORMAT_NV12:
|
slime73@11819
|
514 |
case SDL_PIXELFORMAT_NV21:
|
slime73@11819
|
515 |
pixfmt = MTLPixelFormatR8Unorm;
|
slime73@11819
|
516 |
break;
|
slime73@11819
|
517 |
default:
|
slime73@11819
|
518 |
return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
|
icculus@11729
|
519 |
}
|
icculus@11729
|
520 |
|
slime73@11819
|
521 |
MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:pixfmt
|
icculus@11729
|
522 |
width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
|
slime73@11810
|
523 |
|
slime73@11810
|
524 |
/* Not available in iOS 8. */
|
slime73@11810
|
525 |
if ([mtltexdesc respondsToSelector:@selector(usage)]) {
|
slime73@11810
|
526 |
if (texture->access == SDL_TEXTUREACCESS_TARGET) {
|
slime73@11810
|
527 |
mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
|
slime73@11810
|
528 |
} else {
|
slime73@11810
|
529 |
mtltexdesc.usage = MTLTextureUsageShaderRead;
|
slime73@11810
|
530 |
}
|
icculus@11750
|
531 |
}
|
icculus@11750
|
532 |
|
slouken@11732
|
533 |
id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
icculus@11729
|
534 |
if (mtltexture == nil) {
|
icculus@11729
|
535 |
return SDL_SetError("Texture allocation failed");
|
icculus@11729
|
536 |
}
|
icculus@11729
|
537 |
|
slime73@11819
|
538 |
id<MTLTexture> mtltexture_uv = nil;
|
slime73@11819
|
539 |
|
slime73@11819
|
540 |
BOOL yuv = (texture->format == SDL_PIXELFORMAT_IYUV) || (texture->format == SDL_PIXELFORMAT_YV12);
|
slime73@11819
|
541 |
BOOL nv12 = (texture->format == SDL_PIXELFORMAT_NV12) || (texture->format == SDL_PIXELFORMAT_NV21);
|
slime73@11819
|
542 |
|
slime73@11819
|
543 |
if (yuv) {
|
slime73@11819
|
544 |
mtltexdesc.pixelFormat = MTLPixelFormatR8Unorm;
|
slime73@11819
|
545 |
mtltexdesc.width = (texture->w + 1) / 2;
|
slime73@11819
|
546 |
mtltexdesc.height = (texture->h + 1) / 2;
|
slime73@11819
|
547 |
mtltexdesc.textureType = MTLTextureType2DArray;
|
slime73@11819
|
548 |
mtltexdesc.arrayLength = 2;
|
slime73@11819
|
549 |
} else if (nv12) {
|
slime73@11819
|
550 |
mtltexdesc.pixelFormat = MTLPixelFormatRG8Unorm;
|
slime73@11819
|
551 |
mtltexdesc.width = (texture->w + 1) / 2;
|
slime73@11819
|
552 |
mtltexdesc.height = (texture->h + 1) / 2;
|
slime73@12320
|
553 |
}
|
slime73@12320
|
554 |
|
icculus@12382
|
555 |
if (yuv || nv12) {
|
slime73@11819
|
556 |
mtltexture_uv = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
|
slime73@12320
|
557 |
if (mtltexture_uv == nil) {
|
slime73@12320
|
558 |
#if !__has_feature(objc_arc)
|
slime73@12320
|
559 |
[mtltexture release];
|
slime73@12320
|
560 |
#endif
|
slime73@12320
|
561 |
return SDL_SetError("Texture allocation failed");
|
slime73@12320
|
562 |
}
|
slime73@11819
|
563 |
}
|
slime73@11819
|
564 |
|
slouken@11753
|
565 |
METAL_TextureData *texturedata = [[METAL_TextureData alloc] init];
|
slouken@11958
|
566 |
if (texture->scaleMode == SDL_ScaleModeNearest) {
|
slime73@11801
|
567 |
texturedata.mtlsampler = data.mtlsamplernearest;
|
slouken@11753
|
568 |
} else {
|
slime73@11801
|
569 |
texturedata.mtlsampler = data.mtlsamplerlinear;
|
slouken@11753
|
570 |
}
|
slouken@11753
|
571 |
texturedata.mtltexture = mtltexture;
|
slime73@11819
|
572 |
texturedata.mtltexture_uv = mtltexture_uv;
|
slime73@11819
|
573 |
|
slime73@11819
|
574 |
texturedata.yuv = yuv;
|
slime73@11819
|
575 |
texturedata.nv12 = nv12;
|
slime73@11819
|
576 |
|
slime73@11819
|
577 |
if (yuv) {
|
slime73@11819
|
578 |
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_YUV;
|
slime73@11819
|
579 |
} else if (texture->format == SDL_PIXELFORMAT_NV12) {
|
slime73@11819
|
580 |
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV12;
|
slime73@11819
|
581 |
} else if (texture->format == SDL_PIXELFORMAT_NV21) {
|
slime73@11819
|
582 |
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_NV21;
|
slime73@11819
|
583 |
} else {
|
slime73@11819
|
584 |
texturedata.fragmentFunction = SDL_METAL_FRAGMENT_COPY;
|
slime73@11819
|
585 |
}
|
slime73@11819
|
586 |
|
slime73@11819
|
587 |
if (yuv || nv12) {
|
slime73@11819
|
588 |
size_t offset = 0;
|
slime73@11819
|
589 |
SDL_YUV_CONVERSION_MODE mode = SDL_GetYUVConversionModeForResolution(texture->w, texture->h);
|
slime73@11819
|
590 |
switch (mode) {
|
slime73@11819
|
591 |
case SDL_YUV_CONVERSION_JPEG: offset = CONSTANTS_OFFSET_DECODE_JPEG; break;
|
slime73@11819
|
592 |
case SDL_YUV_CONVERSION_BT601: offset = CONSTANTS_OFFSET_DECODE_BT601; break;
|
slime73@11819
|
593 |
case SDL_YUV_CONVERSION_BT709: offset = CONSTANTS_OFFSET_DECODE_BT709; break;
|
slime73@11819
|
594 |
default: offset = 0; break;
|
slime73@11819
|
595 |
}
|
slime73@11819
|
596 |
texturedata.conversionBufferOffset = offset;
|
slime73@11819
|
597 |
}
|
slouken@11753
|
598 |
|
slouken@11753
|
599 |
texture->driverdata = (void*)CFBridgingRetain(texturedata);
|
icculus@11729
|
600 |
|
slime73@11799
|
601 |
#if !__has_feature(objc_arc)
|
slime73@11804
|
602 |
[texturedata release];
|
slime73@11799
|
603 |
[mtltexture release];
|
slime73@11819
|
604 |
[mtltexture_uv release];
|
slime73@11799
|
605 |
#endif
|
slime73@11799
|
606 |
|
icculus@11729
|
607 |
return 0;
|
slouken@11743
|
608 |
}}
|
icculus@11729
|
609 |
|
icculus@11729
|
610 |
static int
|
icculus@11729
|
611 |
METAL_UpdateTexture(SDL_Renderer * renderer, SDL_Texture * texture,
|
icculus@11729
|
612 |
const SDL_Rect * rect, const void *pixels, int pitch)
|
slouken@11743
|
613 |
{ @autoreleasepool {
|
slime73@11819
|
614 |
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
slime73@11819
|
615 |
|
slime73@11820
|
616 |
/* !!! FIXME: replaceRegion does not do any synchronization, so it might
|
slime73@11820
|
617 |
* !!! FIXME: stomp on a previous frame's data that's currently being read
|
slime73@11820
|
618 |
* !!! FIXME: by the GPU. */
|
slime73@11819
|
619 |
[texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
slime73@11819
|
620 |
mipmapLevel:0
|
slime73@11819
|
621 |
withBytes:pixels
|
slime73@11819
|
622 |
bytesPerRow:pitch];
|
slime73@11819
|
623 |
|
slime73@11819
|
624 |
if (texturedata.yuv) {
|
slime73@11819
|
625 |
int Uslice = texture->format == SDL_PIXELFORMAT_YV12 ? 1 : 0;
|
slime73@11819
|
626 |
int Vslice = texture->format == SDL_PIXELFORMAT_YV12 ? 0 : 1;
|
slime73@11819
|
627 |
|
slime73@11819
|
628 |
/* Skip to the correct offset into the next texture */
|
slime73@11819
|
629 |
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
slime73@11819
|
630 |
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
slime73@11819
|
631 |
mipmapLevel:0
|
slime73@11819
|
632 |
slice:Uslice
|
slime73@11819
|
633 |
withBytes:pixels
|
slime73@11819
|
634 |
bytesPerRow:(pitch + 1) / 2
|
slime73@11819
|
635 |
bytesPerImage:0];
|
slime73@11819
|
636 |
|
slime73@11819
|
637 |
/* Skip to the correct offset into the next texture */
|
slime73@11819
|
638 |
pixels = (const void*)((const Uint8*)pixels + ((rect->h + 1) / 2) * ((pitch + 1)/2));
|
slime73@11819
|
639 |
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
slime73@11819
|
640 |
mipmapLevel:0
|
slime73@11819
|
641 |
slice:Vslice
|
slime73@11819
|
642 |
withBytes:pixels
|
slime73@11819
|
643 |
bytesPerRow:(pitch + 1) / 2
|
slime73@11819
|
644 |
bytesPerImage:0];
|
slime73@11819
|
645 |
}
|
slime73@11819
|
646 |
|
slime73@11819
|
647 |
if (texturedata.nv12) {
|
slime73@11819
|
648 |
/* Skip to the correct offset into the next texture */
|
slime73@11819
|
649 |
pixels = (const void*)((const Uint8*)pixels + rect->h * pitch);
|
slime73@11819
|
650 |
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
slime73@11819
|
651 |
mipmapLevel:0
|
slime73@11819
|
652 |
slice:0
|
slime73@11819
|
653 |
withBytes:pixels
|
slime73@11819
|
654 |
bytesPerRow:2 * ((pitch + 1) / 2)
|
slime73@11819
|
655 |
bytesPerImage:0];
|
slime73@11819
|
656 |
}
|
slime73@11819
|
657 |
|
icculus@11729
|
658 |
return 0;
|
slouken@11743
|
659 |
}}
|
icculus@11729
|
660 |
|
icculus@11729
|
661 |
static int
|
icculus@11729
|
662 |
METAL_UpdateTextureYUV(SDL_Renderer * renderer, SDL_Texture * texture,
|
icculus@11729
|
663 |
const SDL_Rect * rect,
|
icculus@11729
|
664 |
const Uint8 *Yplane, int Ypitch,
|
icculus@11729
|
665 |
const Uint8 *Uplane, int Upitch,
|
icculus@11729
|
666 |
const Uint8 *Vplane, int Vpitch)
|
slime73@11819
|
667 |
{ @autoreleasepool {
|
slime73@11819
|
668 |
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
icculus@12169
|
669 |
const int Uslice = 0;
|
icculus@12169
|
670 |
const int Vslice = 1;
|
slime73@11819
|
671 |
|
slime73@11819
|
672 |
/* Bail out if we're supposed to update an empty rectangle */
|
slime73@11819
|
673 |
if (rect->w <= 0 || rect->h <= 0) {
|
slime73@11819
|
674 |
return 0;
|
slime73@11819
|
675 |
}
|
slime73@11819
|
676 |
|
slime73@11819
|
677 |
[texturedata.mtltexture replaceRegion:MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h)
|
slime73@11819
|
678 |
mipmapLevel:0
|
slime73@11819
|
679 |
withBytes:Yplane
|
slime73@11819
|
680 |
bytesPerRow:Ypitch];
|
slime73@11819
|
681 |
|
slime73@11819
|
682 |
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
slime73@11819
|
683 |
mipmapLevel:0
|
slime73@11819
|
684 |
slice:Uslice
|
slime73@11819
|
685 |
withBytes:Uplane
|
slime73@11819
|
686 |
bytesPerRow:Upitch
|
slime73@11819
|
687 |
bytesPerImage:0];
|
slime73@11819
|
688 |
|
slime73@11819
|
689 |
[texturedata.mtltexture_uv replaceRegion:MTLRegionMake2D(rect->x / 2, rect->y / 2, (rect->w + 1) / 2, (rect->h + 1) / 2)
|
slime73@11819
|
690 |
mipmapLevel:0
|
slime73@11819
|
691 |
slice:Vslice
|
slime73@11819
|
692 |
withBytes:Vplane
|
slime73@11819
|
693 |
bytesPerRow:Vpitch
|
slime73@11819
|
694 |
bytesPerImage:0];
|
slime73@11819
|
695 |
|
slime73@11819
|
696 |
return 0;
|
slime73@11819
|
697 |
}}
|
icculus@11729
|
698 |
|
icculus@11729
|
699 |
static int
|
icculus@11729
|
700 |
METAL_LockTexture(SDL_Renderer * renderer, SDL_Texture * texture,
|
icculus@11729
|
701 |
const SDL_Rect * rect, void **pixels, int *pitch)
|
icculus@11729
|
702 |
{
|
icculus@11729
|
703 |
return SDL_Unsupported(); // !!! FIXME: write me
|
icculus@11729
|
704 |
}
|
icculus@11729
|
705 |
|
icculus@11729
|
706 |
static void
|
icculus@11729
|
707 |
METAL_UnlockTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
icculus@11729
|
708 |
{
|
icculus@11729
|
709 |
// !!! FIXME: write me
|
icculus@11729
|
710 |
}
|
icculus@11729
|
711 |
|
icculus@11729
|
712 |
static int
|
icculus@11729
|
713 |
METAL_SetRenderTarget(SDL_Renderer * renderer, SDL_Texture * texture)
|
slouken@11743
|
714 |
{ @autoreleasepool {
|
slouken@11732
|
715 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@11750
|
716 |
|
slime73@11817
|
717 |
if (data.mtlcmdencoder) {
|
slime73@11817
|
718 |
/* End encoding for the previous render target so we can set up a new
|
slime73@11817
|
719 |
* render pass for this one. */
|
slime73@11817
|
720 |
[data.mtlcmdencoder endEncoding];
|
slime73@11817
|
721 |
[data.mtlcmdbuffer commit];
|
icculus@11750
|
722 |
|
slime73@11817
|
723 |
data.mtlcmdencoder = nil;
|
slime73@11817
|
724 |
data.mtlcmdbuffer = nil;
|
slime73@11817
|
725 |
}
|
icculus@11750
|
726 |
|
slime73@11817
|
727 |
/* We don't begin a new render pass right away - we delay it until an actual
|
slime73@11817
|
728 |
* draw or clear happens. That way we can use hardware clears when possible,
|
slime73@11817
|
729 |
* which are only available when beginning a new render pass. */
|
icculus@11729
|
730 |
return 0;
|
slouken@11743
|
731 |
}}
|
icculus@11729
|
732 |
|
icculus@11729
|
733 |
|
slouken@11755
|
734 |
// normalize a value from 0.0f to len into 0.0f to 1.0f.
|
slouken@11755
|
735 |
static inline float
|
slouken@11755
|
736 |
normtex(const float _val, const float len)
|
slouken@11755
|
737 |
{
|
slime73@11810
|
738 |
return _val / len;
|
slouken@11755
|
739 |
}
|
slouken@11755
|
740 |
|
icculus@11729
|
741 |
static int
|
icculus@12215
|
742 |
METAL_QueueSetViewport(SDL_Renderer * renderer, SDL_RenderCommand *cmd)
|
icculus@12215
|
743 |
{
|
icculus@12215
|
744 |
float projection[4][4]; /* Prepare an orthographic projection */
|
icculus@12215
|
745 |
const int w = cmd->data.viewport.rect.w;
|
icculus@12215
|
746 |
const int h = cmd->data.viewport.rect.h;
|
icculus@12215
|
747 |
const size_t matrixlen = sizeof (projection);
|
icculus@12215
|
748 |
float *matrix = (float *) SDL_AllocateRenderVertices(renderer, matrixlen, CONSTANT_ALIGN, &cmd->data.viewport.first);
|
icculus@12215
|
749 |
if (!matrix) {
|
icculus@12215
|
750 |
return -1;
|
icculus@12215
|
751 |
}
|
icculus@12215
|
752 |
|
icculus@12215
|
753 |
SDL_memset(projection, '\0', matrixlen);
|
icculus@12215
|
754 |
if (w && h) {
|
icculus@12215
|
755 |
projection[0][0] = 2.0f / w;
|
icculus@12215
|
756 |
projection[1][1] = -2.0f / h;
|
icculus@12215
|
757 |
projection[3][0] = -1.0f;
|
icculus@12215
|
758 |
projection[3][1] = 1.0f;
|
icculus@12215
|
759 |
projection[3][3] = 1.0f;
|
icculus@12215
|
760 |
}
|
icculus@12215
|
761 |
SDL_memcpy(matrix, projection, matrixlen);
|
icculus@12215
|
762 |
|
icculus@12215
|
763 |
return 0;
|
icculus@12215
|
764 |
}
|
icculus@12215
|
765 |
|
icculus@12215
|
766 |
static int
|
icculus@12215
|
767 |
METAL_QueueSetDrawColor(SDL_Renderer *renderer, SDL_RenderCommand *cmd)
|
icculus@12215
|
768 |
{
|
icculus@12215
|
769 |
const size_t vertlen = sizeof (float) * 4;
|
icculus@12215
|
770 |
float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.color.first);
|
icculus@12215
|
771 |
if (!verts) {
|
icculus@12215
|
772 |
return -1;
|
icculus@12215
|
773 |
}
|
icculus@12215
|
774 |
*(verts++) = ((float)cmd->data.color.r) / 255.0f;
|
icculus@12215
|
775 |
*(verts++) = ((float)cmd->data.color.g) / 255.0f;
|
icculus@12215
|
776 |
*(verts++) = ((float)cmd->data.color.b) / 255.0f;
|
icculus@12215
|
777 |
*(verts++) = ((float)cmd->data.color.a) / 255.0f;
|
icculus@12215
|
778 |
return 0;
|
icculus@12215
|
779 |
}
|
icculus@12215
|
780 |
|
icculus@12215
|
781 |
static int
|
icculus@12213
|
782 |
METAL_QueueDrawPoints(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FPoint * points, int count)
|
icculus@12213
|
783 |
{
|
slouken@11806
|
784 |
const size_t vertlen = (sizeof (float) * 2) * count;
|
icculus@12215
|
785 |
float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
icculus@12213
|
786 |
if (!verts) {
|
icculus@12213
|
787 |
return -1;
|
icculus@12213
|
788 |
}
|
icculus@12213
|
789 |
cmd->data.draw.count = count;
|
icculus@12213
|
790 |
SDL_memcpy(verts, points, vertlen);
|
icculus@11729
|
791 |
return 0;
|
icculus@11729
|
792 |
}
|
icculus@11729
|
793 |
|
icculus@11729
|
794 |
static int
|
icculus@12213
|
795 |
METAL_QueueFillRects(SDL_Renderer * renderer, SDL_RenderCommand *cmd, const SDL_FRect * rects, int count)
|
icculus@11729
|
796 |
{
|
icculus@12213
|
797 |
// !!! FIXME: use an index buffer
|
icculus@12213
|
798 |
const size_t vertlen = (sizeof (float) * 8) * count;
|
icculus@12215
|
799 |
float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
icculus@12213
|
800 |
if (!verts) {
|
icculus@12213
|
801 |
return -1;
|
icculus@12213
|
802 |
}
|
icculus@12213
|
803 |
|
icculus@12213
|
804 |
cmd->data.draw.count = count;
|
icculus@12213
|
805 |
|
icculus@12213
|
806 |
for (int i = 0; i < count; i++, rects++) {
|
icculus@12213
|
807 |
if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) {
|
icculus@12213
|
808 |
cmd->data.draw.count--;
|
icculus@12213
|
809 |
} else {
|
icculus@12213
|
810 |
*(verts++) = rects->x;
|
icculus@12213
|
811 |
*(verts++) = rects->y + rects->h;
|
icculus@12213
|
812 |
*(verts++) = rects->x;
|
icculus@12213
|
813 |
*(verts++) = rects->y;
|
icculus@12213
|
814 |
*(verts++) = rects->x + rects->w;
|
icculus@12213
|
815 |
*(verts++) = rects->y + rects->h;
|
icculus@12213
|
816 |
*(verts++) = rects->x + rects->w;
|
icculus@12213
|
817 |
*(verts++) = rects->y;
|
icculus@12213
|
818 |
}
|
icculus@12213
|
819 |
}
|
icculus@12213
|
820 |
|
icculus@12213
|
821 |
if (cmd->data.draw.count == 0) {
|
icculus@12213
|
822 |
cmd->command = SDL_RENDERCMD_NO_OP; // nothing to do, just skip this one later.
|
icculus@12213
|
823 |
}
|
icculus@12213
|
824 |
|
icculus@12213
|
825 |
return 0;
|
icculus@11729
|
826 |
}
|
icculus@11729
|
827 |
|
icculus@11729
|
828 |
static int
|
icculus@12213
|
829 |
METAL_QueueCopy(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
icculus@12213
|
830 |
const SDL_Rect * srcrect, const SDL_FRect * dstrect)
|
icculus@12213
|
831 |
{
|
icculus@12213
|
832 |
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
icculus@12213
|
833 |
const float texw = (float) texturedata.mtltexture.width;
|
icculus@12213
|
834 |
const float texh = (float) texturedata.mtltexture.height;
|
icculus@12213
|
835 |
// !!! FIXME: use an index buffer
|
icculus@12213
|
836 |
const size_t vertlen = (sizeof (float) * 16);
|
icculus@12215
|
837 |
float *verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
icculus@12213
|
838 |
if (!verts) {
|
icculus@12213
|
839 |
return -1;
|
icculus@11729
|
840 |
}
|
icculus@11729
|
841 |
|
icculus@12213
|
842 |
cmd->data.draw.count = 1;
|
icculus@12213
|
843 |
|
icculus@12213
|
844 |
*(verts++) = dstrect->x;
|
icculus@12213
|
845 |
*(verts++) = dstrect->y + dstrect->h;
|
icculus@12213
|
846 |
*(verts++) = dstrect->x;
|
icculus@12213
|
847 |
*(verts++) = dstrect->y;
|
icculus@12213
|
848 |
*(verts++) = dstrect->x + dstrect->w;
|
icculus@12213
|
849 |
*(verts++) = dstrect->y + dstrect->h;
|
icculus@12213
|
850 |
*(verts++) = dstrect->x + dstrect->w;
|
icculus@12213
|
851 |
*(verts++) = dstrect->y;
|
icculus@12213
|
852 |
|
icculus@12213
|
853 |
*(verts++) = normtex(srcrect->x, texw);
|
icculus@12213
|
854 |
*(verts++) = normtex(srcrect->y + srcrect->h, texh);
|
icculus@12213
|
855 |
*(verts++) = normtex(srcrect->x, texw);
|
icculus@12213
|
856 |
*(verts++) = normtex(srcrect->y, texh);
|
icculus@12213
|
857 |
*(verts++) = normtex(srcrect->x + srcrect->w, texw);
|
icculus@12213
|
858 |
*(verts++) = normtex(srcrect->y + srcrect->h, texh);
|
icculus@12213
|
859 |
*(verts++) = normtex(srcrect->x + srcrect->w, texw);
|
icculus@12213
|
860 |
*(verts++) = normtex(srcrect->y, texh);
|
icculus@12213
|
861 |
|
icculus@11729
|
862 |
return 0;
|
slime73@11819
|
863 |
}
|
slime73@11819
|
864 |
|
icculus@11729
|
865 |
static int
|
icculus@12213
|
866 |
METAL_QueueCopyEx(SDL_Renderer * renderer, SDL_RenderCommand *cmd, SDL_Texture * texture,
|
icculus@12213
|
867 |
const SDL_Rect * srcquad, const SDL_FRect * dstrect,
|
icculus@12213
|
868 |
const double angle, const SDL_FPoint *center, const SDL_RendererFlip flip)
|
icculus@12213
|
869 |
{
|
slouken@11753
|
870 |
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
slouken@11753
|
871 |
const float texw = (float) texturedata.mtltexture.width;
|
slouken@11753
|
872 |
const float texh = (float) texturedata.mtltexture.height;
|
icculus@12213
|
873 |
const float rads = (float)(M_PI * (float) angle / 180.0f);
|
icculus@12213
|
874 |
const float c = cosf(rads), s = sinf(rads);
|
icculus@12213
|
875 |
float minu, maxu, minv, maxv;
|
icculus@12213
|
876 |
const size_t vertlen = (sizeof (float) * 32);
|
icculus@12294
|
877 |
float *verts;
|
icculus@12294
|
878 |
|
icculus@12294
|
879 |
// 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.
|
icculus@12294
|
880 |
verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, CONSTANT_ALIGN, &cmd->data.draw.count);
|
icculus@12213
|
881 |
if (!verts) {
|
icculus@12213
|
882 |
return -1;
|
icculus@12213
|
883 |
}
|
icculus@11729
|
884 |
|
icculus@12294
|
885 |
// transform matrix
|
icculus@12294
|
886 |
SDL_memset(verts, '\0', sizeof (*verts) * 16);
|
icculus@12294
|
887 |
verts[10] = verts[15] = 1.0f;
|
icculus@12294
|
888 |
// rotation
|
icculus@12294
|
889 |
verts[0] = c;
|
icculus@12294
|
890 |
verts[1] = s;
|
icculus@12294
|
891 |
verts[4] = -s;
|
icculus@12294
|
892 |
verts[5] = c;
|
icculus@12294
|
893 |
|
icculus@12294
|
894 |
// translation
|
icculus@12294
|
895 |
verts[12] = dstrect->x + center->x;
|
icculus@12294
|
896 |
verts[13] = dstrect->y + center->y;
|
icculus@12294
|
897 |
|
icculus@12294
|
898 |
// rest of the vertices don't need the aggressive alignment. Pack them in.
|
icculus@12294
|
899 |
verts = (float *) SDL_AllocateRenderVertices(renderer, vertlen, 0, &cmd->data.draw.first);
|
icculus@12294
|
900 |
if (!verts) {
|
icculus@12294
|
901 |
return -1;
|
icculus@12294
|
902 |
}
|
slime73@11819
|
903 |
|
icculus@12213
|
904 |
minu = normtex(srcquad->x, texw);
|
icculus@12213
|
905 |
maxu = normtex(srcquad->x + srcquad->w, texw);
|
icculus@12213
|
906 |
minv = normtex(srcquad->y, texh);
|
icculus@12213
|
907 |
maxv = normtex(srcquad->y + srcquad->h, texh);
|
slime73@11799
|
908 |
|
slime73@11799
|
909 |
if (flip & SDL_FLIP_HORIZONTAL) {
|
slime73@11799
|
910 |
float tmp = maxu;
|
slime73@11799
|
911 |
maxu = minu;
|
slime73@11799
|
912 |
minu = tmp;
|
slime73@11799
|
913 |
}
|
slime73@11799
|
914 |
if (flip & SDL_FLIP_VERTICAL) {
|
slime73@11799
|
915 |
float tmp = maxv;
|
slime73@11799
|
916 |
maxv = minv;
|
slime73@11799
|
917 |
minv = tmp;
|
slime73@11799
|
918 |
}
|
slime73@11799
|
919 |
|
icculus@12213
|
920 |
// vertices
|
icculus@12213
|
921 |
*(verts++) = -center->x;
|
icculus@12213
|
922 |
*(verts++) = dstrect->h - center->y;
|
icculus@12213
|
923 |
*(verts++) = -center->x;
|
icculus@12213
|
924 |
*(verts++) = -center->y;
|
icculus@12213
|
925 |
*(verts++) = dstrect->w - center->x;
|
icculus@12213
|
926 |
*(verts++) = dstrect->h - center->y;
|
icculus@12213
|
927 |
*(verts++) = dstrect->w - center->x;
|
icculus@12213
|
928 |
*(verts++) = -center->y;
|
slime73@11799
|
929 |
|
icculus@12213
|
930 |
// texcoords
|
icculus@12213
|
931 |
*(verts++) = minu;
|
icculus@12213
|
932 |
*(verts++) = maxv;
|
icculus@12213
|
933 |
*(verts++) = minu;
|
icculus@12213
|
934 |
*(verts++) = minv;
|
icculus@12213
|
935 |
*(verts++) = maxu;
|
icculus@12213
|
936 |
*(verts++) = maxv;
|
icculus@12213
|
937 |
*(verts++) = maxu;
|
icculus@12213
|
938 |
*(verts++) = minv;
|
slime73@11799
|
939 |
|
icculus@12213
|
940 |
return 0;
|
icculus@12213
|
941 |
}
|
slime73@11821
|
942 |
|
icculus@12213
|
943 |
|
icculus@12213
|
944 |
typedef struct
|
icculus@12213
|
945 |
{
|
icculus@12215
|
946 |
#if __has_feature(objc_arc)
|
icculus@12215
|
947 |
__unsafe_unretained id<MTLRenderPipelineState> pipeline;
|
icculus@12215
|
948 |
#else
|
icculus@12213
|
949 |
id<MTLRenderPipelineState> pipeline;
|
icculus@12215
|
950 |
#endif
|
icculus@12213
|
951 |
size_t constants_offset;
|
icculus@12213
|
952 |
SDL_Texture *texture;
|
icculus@12213
|
953 |
SDL_bool cliprect_dirty;
|
icculus@12213
|
954 |
SDL_bool cliprect_enabled;
|
icculus@12215
|
955 |
SDL_Rect cliprect;
|
icculus@12213
|
956 |
SDL_bool viewport_dirty;
|
icculus@12213
|
957 |
SDL_Rect viewport;
|
icculus@12215
|
958 |
size_t projection_offset;
|
icculus@12215
|
959 |
SDL_bool color_dirty;
|
icculus@12215
|
960 |
size_t color_offset;
|
icculus@12213
|
961 |
} METAL_DrawStateCache;
|
icculus@12213
|
962 |
|
icculus@12213
|
963 |
static void
|
icculus@12213
|
964 |
SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const SDL_MetalFragmentFunction shader,
|
icculus@12213
|
965 |
const size_t constants_offset, id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
|
icculus@12213
|
966 |
{
|
icculus@12213
|
967 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@12213
|
968 |
const SDL_BlendMode blend = cmd->data.draw.blend;
|
icculus@12215
|
969 |
size_t first = cmd->data.draw.first;
|
icculus@12213
|
970 |
id<MTLRenderPipelineState> newpipeline;
|
icculus@12213
|
971 |
|
icculus@12213
|
972 |
METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
|
icculus@12213
|
973 |
|
icculus@12213
|
974 |
if (statecache->viewport_dirty) {
|
icculus@12213
|
975 |
MTLViewport viewport;
|
icculus@12213
|
976 |
viewport.originX = statecache->viewport.x;
|
icculus@12213
|
977 |
viewport.originY = statecache->viewport.y;
|
icculus@12213
|
978 |
viewport.width = statecache->viewport.w;
|
icculus@12213
|
979 |
viewport.height = statecache->viewport.h;
|
icculus@12213
|
980 |
viewport.znear = 0.0;
|
icculus@12213
|
981 |
viewport.zfar = 1.0;
|
icculus@12213
|
982 |
[data.mtlcmdencoder setViewport:viewport];
|
icculus@12215
|
983 |
[data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:statecache->projection_offset atIndex:2]; // projection
|
icculus@12213
|
984 |
statecache->viewport_dirty = SDL_FALSE;
|
slime73@11799
|
985 |
}
|
slime73@11799
|
986 |
|
icculus@12213
|
987 |
if (statecache->cliprect_dirty) {
|
icculus@12213
|
988 |
MTLScissorRect mtlrect;
|
icculus@12213
|
989 |
if (statecache->cliprect_enabled) {
|
icculus@12213
|
990 |
const SDL_Rect *rect = &statecache->cliprect;
|
icculus@12213
|
991 |
mtlrect.x = statecache->viewport.x + rect->x;
|
icculus@12213
|
992 |
mtlrect.y = statecache->viewport.y + rect->y;
|
icculus@12213
|
993 |
mtlrect.width = rect->w;
|
icculus@12213
|
994 |
mtlrect.height = rect->h;
|
icculus@12213
|
995 |
} else {
|
icculus@12213
|
996 |
mtlrect.x = statecache->viewport.x;
|
icculus@12213
|
997 |
mtlrect.y = statecache->viewport.y;
|
icculus@12213
|
998 |
mtlrect.width = statecache->viewport.w;
|
icculus@12213
|
999 |
mtlrect.height = statecache->viewport.h;
|
icculus@12213
|
1000 |
}
|
icculus@12213
|
1001 |
if (mtlrect.width > 0 && mtlrect.height > 0) {
|
icculus@12213
|
1002 |
[data.mtlcmdencoder setScissorRect:mtlrect];
|
icculus@12213
|
1003 |
}
|
icculus@12213
|
1004 |
statecache->cliprect_dirty = SDL_FALSE;
|
icculus@12213
|
1005 |
}
|
icculus@12213
|
1006 |
|
icculus@12215
|
1007 |
if (statecache->color_dirty) {
|
icculus@12215
|
1008 |
[data.mtlcmdencoder setFragmentBuffer:mtlbufvertex offset:statecache->color_offset atIndex:0];
|
icculus@12213
|
1009 |
statecache->color_dirty = SDL_FALSE;
|
icculus@12213
|
1010 |
}
|
icculus@12213
|
1011 |
|
icculus@12213
|
1012 |
newpipeline = ChoosePipelineState(data, data.activepipelines, shader, blend);
|
icculus@12213
|
1013 |
if (newpipeline != statecache->pipeline) {
|
icculus@12213
|
1014 |
[data.mtlcmdencoder setRenderPipelineState:newpipeline];
|
icculus@12213
|
1015 |
statecache->pipeline = newpipeline;
|
icculus@12213
|
1016 |
}
|
icculus@12213
|
1017 |
|
icculus@12213
|
1018 |
if (constants_offset != statecache->constants_offset) {
|
icculus@12213
|
1019 |
if (constants_offset != CONSTANTS_OFFSET_INVALID) {
|
icculus@12213
|
1020 |
[data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:constants_offset atIndex:3];
|
icculus@12213
|
1021 |
}
|
icculus@12213
|
1022 |
statecache->constants_offset = constants_offset;
|
icculus@12213
|
1023 |
}
|
icculus@12213
|
1024 |
|
icculus@12215
|
1025 |
[data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:first atIndex:0]; // position
|
icculus@12213
|
1026 |
}
|
icculus@12213
|
1027 |
|
icculus@12213
|
1028 |
static void
|
icculus@12213
|
1029 |
SetCopyState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, const size_t constants_offset,
|
icculus@12213
|
1030 |
id<MTLBuffer> mtlbufvertex, METAL_DrawStateCache *statecache)
|
icculus@12213
|
1031 |
{
|
icculus@12213
|
1032 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@12213
|
1033 |
SDL_Texture *texture = cmd->data.draw.texture;
|
icculus@12213
|
1034 |
METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
|
icculus@12213
|
1035 |
|
icculus@12213
|
1036 |
SetDrawState(renderer, cmd, texturedata.fragmentFunction, constants_offset, mtlbufvertex, statecache);
|
icculus@12213
|
1037 |
|
icculus@12213
|
1038 |
[data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.first+(8*sizeof (float)) atIndex:1]; // texcoords
|
icculus@12213
|
1039 |
|
icculus@12213
|
1040 |
if (texture != statecache->texture) {
|
icculus@12213
|
1041 |
METAL_TextureData *oldtexturedata = NULL;
|
icculus@12213
|
1042 |
if (statecache->texture) {
|
icculus@12213
|
1043 |
oldtexturedata = (__bridge METAL_TextureData *) statecache->texture->driverdata;
|
icculus@12213
|
1044 |
}
|
icculus@12213
|
1045 |
if (!oldtexturedata || (texturedata.mtlsampler != oldtexturedata.mtlsampler)) {
|
icculus@12213
|
1046 |
[data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
|
icculus@12213
|
1047 |
}
|
icculus@12213
|
1048 |
|
icculus@12213
|
1049 |
[data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
|
icculus@12213
|
1050 |
if (texturedata.yuv || texturedata.nv12) {
|
icculus@12213
|
1051 |
[data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture_uv atIndex:1];
|
icculus@12213
|
1052 |
[data.mtlcmdencoder setFragmentBuffer:data.mtlbufconstants offset:texturedata.conversionBufferOffset atIndex:1];
|
icculus@12213
|
1053 |
}
|
icculus@12213
|
1054 |
statecache->texture = texture;
|
icculus@12213
|
1055 |
}
|
icculus@12213
|
1056 |
}
|
icculus@12213
|
1057 |
|
icculus@12213
|
1058 |
static int
|
icculus@12213
|
1059 |
METAL_RunCommandQueue(SDL_Renderer * renderer, SDL_RenderCommand *cmd, void *vertices, size_t vertsize)
|
icculus@12213
|
1060 |
{ @autoreleasepool {
|
icculus@12213
|
1061 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@12213
|
1062 |
METAL_DrawStateCache statecache;
|
icculus@12293
|
1063 |
id<MTLBuffer> mtlbufvertex = nil;
|
icculus@12213
|
1064 |
|
icculus@12213
|
1065 |
statecache.pipeline = nil;
|
icculus@12213
|
1066 |
statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
|
icculus@12213
|
1067 |
statecache.texture = NULL;
|
icculus@12213
|
1068 |
statecache.color_dirty = SDL_TRUE;
|
icculus@12213
|
1069 |
statecache.cliprect_dirty = SDL_TRUE;
|
icculus@12215
|
1070 |
statecache.viewport_dirty = SDL_TRUE;
|
icculus@12215
|
1071 |
statecache.projection_offset = 0;
|
icculus@12215
|
1072 |
statecache.color_offset = 0;
|
icculus@12213
|
1073 |
|
icculus@12213
|
1074 |
// !!! FIXME: have a ring of pre-made MTLBuffers we cycle through? How expensive is creation?
|
icculus@12293
|
1075 |
if (vertsize > 0) {
|
slime73@12385
|
1076 |
/* We can memcpy to a shared buffer from the CPU and read it from the GPU
|
slime73@12385
|
1077 |
* without any extra copying. It's a bit slower on macOS to read shared
|
slime73@12385
|
1078 |
* data from the GPU than to read managed/private data, but we avoid the
|
slime73@12385
|
1079 |
* cost of copying the data and the code's simpler. Apple's best
|
slime73@12385
|
1080 |
* practices guide recommends this approach for streamed vertex data.
|
slime73@12385
|
1081 |
* TODO: this buffer is also used for constants. Is performance still
|
slime73@12385
|
1082 |
* good for those, or should we have a managed buffer for them? */
|
slime73@12385
|
1083 |
mtlbufvertex = [data.mtldevice newBufferWithLength:vertsize options:MTLResourceStorageModeShared];
|
icculus@12293
|
1084 |
#if !__has_feature(objc_arc)
|
icculus@12293
|
1085 |
[mtlbufvertex autorelease];
|
icculus@12293
|
1086 |
#endif
|
icculus@12293
|
1087 |
mtlbufvertex.label = @"SDL vertex data";
|
slime73@12385
|
1088 |
SDL_memcpy([mtlbufvertex contents], vertices, vertsize);
|
icculus@12293
|
1089 |
}
|
icculus@12213
|
1090 |
|
icculus@12213
|
1091 |
// If there's a command buffer here unexpectedly (app requested one?). Commit it so we can start fresh.
|
icculus@12213
|
1092 |
[data.mtlcmdencoder endEncoding];
|
icculus@12213
|
1093 |
[data.mtlcmdbuffer commit];
|
icculus@12213
|
1094 |
data.mtlcmdencoder = nil;
|
icculus@12213
|
1095 |
data.mtlcmdbuffer = nil;
|
icculus@12213
|
1096 |
|
icculus@12213
|
1097 |
while (cmd) {
|
icculus@12213
|
1098 |
switch (cmd->command) {
|
icculus@12213
|
1099 |
case SDL_RENDERCMD_SETVIEWPORT: {
|
icculus@12215
|
1100 |
SDL_memcpy(&statecache.viewport, &cmd->data.viewport.rect, sizeof (statecache.viewport));
|
icculus@12215
|
1101 |
statecache.projection_offset = cmd->data.viewport.first;
|
icculus@12215
|
1102 |
statecache.viewport_dirty = SDL_TRUE;
|
icculus@12213
|
1103 |
break;
|
icculus@12213
|
1104 |
}
|
icculus@12213
|
1105 |
|
icculus@12213
|
1106 |
case SDL_RENDERCMD_SETCLIPRECT: {
|
icculus@12215
|
1107 |
SDL_memcpy(&statecache.cliprect, &cmd->data.cliprect.rect, sizeof (statecache.cliprect));
|
icculus@12215
|
1108 |
statecache.cliprect_enabled = cmd->data.cliprect.enabled;
|
icculus@12215
|
1109 |
statecache.cliprect_dirty = SDL_TRUE;
|
icculus@12215
|
1110 |
break;
|
icculus@12215
|
1111 |
}
|
icculus@12215
|
1112 |
|
icculus@12215
|
1113 |
case SDL_RENDERCMD_SETDRAWCOLOR: {
|
icculus@12215
|
1114 |
statecache.color_offset = cmd->data.color.first;
|
icculus@12215
|
1115 |
statecache.color_dirty = SDL_TRUE;
|
icculus@12213
|
1116 |
break;
|
icculus@12213
|
1117 |
}
|
icculus@12213
|
1118 |
|
icculus@12213
|
1119 |
case SDL_RENDERCMD_CLEAR: {
|
icculus@12213
|
1120 |
/* If we're already encoding a command buffer, dump it without committing it. We'd just
|
icculus@12213
|
1121 |
clear all its work anyhow, and starting a new encoder will let us use a hardware clear
|
icculus@12213
|
1122 |
operation via MTLLoadActionClear. */
|
icculus@12213
|
1123 |
if (data.mtlcmdencoder != nil) {
|
icculus@12213
|
1124 |
[data.mtlcmdencoder endEncoding];
|
icculus@12215
|
1125 |
|
icculus@12215
|
1126 |
// !!! FIXME: have to commit, or an uncommitted but enqueued buffer will prevent the frame from finishing.
|
icculus@12215
|
1127 |
[data.mtlcmdbuffer commit];
|
icculus@12213
|
1128 |
data.mtlcmdencoder = nil;
|
icculus@12213
|
1129 |
data.mtlcmdbuffer = nil;
|
icculus@12213
|
1130 |
}
|
icculus@12213
|
1131 |
|
icculus@12213
|
1132 |
// force all this state to be reconfigured on next command buffer.
|
icculus@12213
|
1133 |
statecache.pipeline = nil;
|
icculus@12213
|
1134 |
statecache.constants_offset = CONSTANTS_OFFSET_INVALID;
|
icculus@12213
|
1135 |
statecache.texture = NULL;
|
icculus@12213
|
1136 |
statecache.color_dirty = SDL_TRUE;
|
icculus@12213
|
1137 |
statecache.cliprect_dirty = SDL_TRUE;
|
icculus@12213
|
1138 |
statecache.viewport_dirty = SDL_TRUE;
|
icculus@12213
|
1139 |
|
icculus@12215
|
1140 |
const Uint8 r = cmd->data.color.r;
|
icculus@12215
|
1141 |
const Uint8 g = cmd->data.color.g;
|
icculus@12215
|
1142 |
const Uint8 b = cmd->data.color.b;
|
icculus@12215
|
1143 |
const Uint8 a = cmd->data.color.a;
|
icculus@12215
|
1144 |
MTLClearColor color = MTLClearColorMake(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
icculus@12215
|
1145 |
|
icculus@12213
|
1146 |
// get new command encoder, set up with an initial clear operation.
|
icculus@12213
|
1147 |
METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionClear, &color);
|
icculus@12213
|
1148 |
break;
|
icculus@12213
|
1149 |
}
|
icculus@12213
|
1150 |
|
icculus@12213
|
1151 |
case SDL_RENDERCMD_DRAW_POINTS:
|
icculus@12213
|
1152 |
case SDL_RENDERCMD_DRAW_LINES: {
|
icculus@12213
|
1153 |
const size_t count = cmd->data.draw.count;
|
icculus@12213
|
1154 |
const MTLPrimitiveType primtype = (cmd->command == SDL_RENDERCMD_DRAW_POINTS) ? MTLPrimitiveTypePoint : MTLPrimitiveTypeLineStrip;
|
icculus@12213
|
1155 |
SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, mtlbufvertex, &statecache);
|
icculus@12213
|
1156 |
[data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
|
icculus@12213
|
1157 |
break;
|
icculus@12213
|
1158 |
}
|
icculus@12213
|
1159 |
|
icculus@12213
|
1160 |
case SDL_RENDERCMD_FILL_RECTS: {
|
icculus@12213
|
1161 |
const size_t count = cmd->data.draw.count;
|
icculus@12213
|
1162 |
size_t start = 0;
|
icculus@12213
|
1163 |
SetDrawState(renderer, cmd, SDL_METAL_FRAGMENT_SOLID, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
|
icculus@12213
|
1164 |
for (size_t i = 0; i < count; i++, start += 4) { // !!! FIXME: can we do all of these this with a single draw call, using MTLPrimitiveTypeTriangle and an index buffer?
|
icculus@12213
|
1165 |
[data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:start vertexCount:4];
|
icculus@12213
|
1166 |
}
|
icculus@12213
|
1167 |
break;
|
icculus@12213
|
1168 |
}
|
icculus@12213
|
1169 |
|
icculus@12213
|
1170 |
case SDL_RENDERCMD_COPY: {
|
icculus@12213
|
1171 |
SetCopyState(renderer, cmd, CONSTANTS_OFFSET_IDENTITY, mtlbufvertex, &statecache);
|
icculus@12213
|
1172 |
[data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
icculus@12213
|
1173 |
break;
|
icculus@12213
|
1174 |
}
|
icculus@12213
|
1175 |
|
icculus@12213
|
1176 |
case SDL_RENDERCMD_COPY_EX: {
|
icculus@12213
|
1177 |
SetCopyState(renderer, cmd, CONSTANTS_OFFSET_INVALID, mtlbufvertex, &statecache);
|
icculus@12294
|
1178 |
[data.mtlcmdencoder setVertexBuffer:mtlbufvertex offset:cmd->data.draw.count atIndex:3]; // transform
|
icculus@12213
|
1179 |
[data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
icculus@12213
|
1180 |
break;
|
icculus@12213
|
1181 |
}
|
icculus@12213
|
1182 |
|
icculus@12213
|
1183 |
case SDL_RENDERCMD_NO_OP:
|
icculus@12213
|
1184 |
break;
|
icculus@12213
|
1185 |
}
|
icculus@12213
|
1186 |
cmd = cmd->next;
|
icculus@12213
|
1187 |
}
|
slime73@11799
|
1188 |
|
slime73@11799
|
1189 |
return 0;
|
slime73@11799
|
1190 |
}}
|
icculus@11729
|
1191 |
|
icculus@11729
|
1192 |
static int
|
icculus@11729
|
1193 |
METAL_RenderReadPixels(SDL_Renderer * renderer, const SDL_Rect * rect,
|
icculus@11729
|
1194 |
Uint32 pixel_format, void * pixels, int pitch)
|
slouken@11743
|
1195 |
{ @autoreleasepool {
|
icculus@12213
|
1196 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@12213
|
1197 |
METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
|
slime73@11817
|
1198 |
|
icculus@12213
|
1199 |
// Commit any current command buffer, and waitUntilCompleted, so any output is ready to be read.
|
icculus@12213
|
1200 |
[data.mtlcmdencoder endEncoding];
|
icculus@12213
|
1201 |
[data.mtlcmdbuffer commit];
|
icculus@12213
|
1202 |
[data.mtlcmdbuffer waitUntilCompleted];
|
icculus@12213
|
1203 |
data.mtlcmdencoder = nil;
|
icculus@12213
|
1204 |
data.mtlcmdbuffer = nil;
|
icculus@12213
|
1205 |
|
slime73@11804
|
1206 |
id<MTLTexture> mtltexture = data.mtlpassdesc.colorAttachments[0].texture;
|
slime73@11800
|
1207 |
MTLRegion mtlregion = MTLRegionMake2D(rect->x, rect->y, rect->w, rect->h);
|
icculus@11729
|
1208 |
|
icculus@11729
|
1209 |
// we only do BGRA8 or RGBA8 at the moment, so 4 will do.
|
icculus@11729
|
1210 |
const int temp_pitch = rect->w * 4;
|
icculus@11729
|
1211 |
void *temp_pixels = SDL_malloc(temp_pitch * rect->h);
|
icculus@11729
|
1212 |
if (!temp_pixels) {
|
icculus@11729
|
1213 |
return SDL_OutOfMemory();
|
icculus@11729
|
1214 |
}
|
icculus@11729
|
1215 |
|
icculus@11729
|
1216 |
[mtltexture getBytes:temp_pixels bytesPerRow:temp_pitch fromRegion:mtlregion mipmapLevel:0];
|
icculus@11729
|
1217 |
|
icculus@11729
|
1218 |
const Uint32 temp_format = (mtltexture.pixelFormat == MTLPixelFormatBGRA8Unorm) ? SDL_PIXELFORMAT_ARGB8888 : SDL_PIXELFORMAT_ABGR8888;
|
icculus@11729
|
1219 |
const int status = SDL_ConvertPixels(rect->w, rect->h, temp_format, temp_pixels, temp_pitch, pixel_format, pixels, pitch);
|
icculus@11729
|
1220 |
SDL_free(temp_pixels);
|
icculus@11729
|
1221 |
return status;
|
slouken@11743
|
1222 |
}}
|
icculus@11729
|
1223 |
|
icculus@11729
|
1224 |
static void
|
icculus@11729
|
1225 |
METAL_RenderPresent(SDL_Renderer * renderer)
|
slouken@11743
|
1226 |
{ @autoreleasepool {
|
slouken@11732
|
1227 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
icculus@11729
|
1228 |
|
slime73@11817
|
1229 |
if (data.mtlcmdencoder != nil) {
|
slime73@11817
|
1230 |
[data.mtlcmdencoder endEncoding];
|
slime73@11817
|
1231 |
}
|
slime73@11817
|
1232 |
if (data.mtlbackbuffer != nil) {
|
slime73@11817
|
1233 |
[data.mtlcmdbuffer presentDrawable:data.mtlbackbuffer];
|
slime73@11817
|
1234 |
}
|
slime73@11817
|
1235 |
if (data.mtlcmdbuffer != nil) {
|
slime73@11817
|
1236 |
[data.mtlcmdbuffer commit];
|
slime73@11817
|
1237 |
}
|
slouken@11741
|
1238 |
data.mtlcmdencoder = nil;
|
slouken@11741
|
1239 |
data.mtlcmdbuffer = nil;
|
slouken@11741
|
1240 |
data.mtlbackbuffer = nil;
|
slouken@11743
|
1241 |
}}
|
icculus@11729
|
1242 |
|
icculus@11729
|
1243 |
static void
|
icculus@11729
|
1244 |
METAL_DestroyTexture(SDL_Renderer * renderer, SDL_Texture * texture)
|
slouken@11743
|
1245 |
{ @autoreleasepool {
|
slime73@11804
|
1246 |
CFBridgingRelease(texture->driverdata);
|
icculus@11729
|
1247 |
texture->driverdata = NULL;
|
slouken@11743
|
1248 |
}}
|
icculus@11729
|
1249 |
|
icculus@11729
|
1250 |
static void
|
icculus@11729
|
1251 |
METAL_DestroyRenderer(SDL_Renderer * renderer)
|
slouken@11743
|
1252 |
{ @autoreleasepool {
|
slouken@11732
|
1253 |
if (renderer->driverdata) {
|
slouken@11732
|
1254 |
METAL_RenderData *data = CFBridgingRelease(renderer->driverdata);
|
icculus@11729
|
1255 |
|
slouken@11741
|
1256 |
if (data.mtlcmdencoder != nil) {
|
slouken@11741
|
1257 |
[data.mtlcmdencoder endEncoding];
|
slouken@11741
|
1258 |
}
|
icculus@11746
|
1259 |
|
slime73@11820
|
1260 |
DestroyAllPipelines(data.allpipelines, data.pipelinescount);
|
icculus@11729
|
1261 |
}
|
icculus@11746
|
1262 |
|
icculus@11729
|
1263 |
SDL_free(renderer);
|
slouken@11743
|
1264 |
}}
|
icculus@11729
|
1265 |
|
slime73@11804
|
1266 |
static void *
|
slime73@11804
|
1267 |
METAL_GetMetalLayer(SDL_Renderer * renderer)
|
slouken@11744
|
1268 |
{ @autoreleasepool {
|
slouken@11744
|
1269 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
slouken@11744
|
1270 |
return (__bridge void*)data.mtllayer;
|
slouken@11744
|
1271 |
}}
|
slouken@11744
|
1272 |
|
slime73@11804
|
1273 |
static void *
|
slime73@11804
|
1274 |
METAL_GetMetalCommandEncoder(SDL_Renderer * renderer)
|
slouken@11744
|
1275 |
{ @autoreleasepool {
|
icculus@12213
|
1276 |
METAL_ActivateRenderCommandEncoder(renderer, MTLLoadActionLoad, NULL);
|
slouken@11744
|
1277 |
METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
|
slouken@11744
|
1278 |
return (__bridge void*)data.mtlcmdencoder;
|
slouken@11744
|
1279 |
}}
|
slouken@11744
|
1280 |
|
icculus@12218
|
1281 |
static SDL_Renderer *
|
icculus@12218
|
1282 |
METAL_CreateRenderer(SDL_Window * window, Uint32 flags)
|
icculus@12218
|
1283 |
{ @autoreleasepool {
|
icculus@12218
|
1284 |
SDL_Renderer *renderer = NULL;
|
icculus@12218
|
1285 |
METAL_RenderData *data = NULL;
|
icculus@12218
|
1286 |
id<MTLDevice> mtldevice = nil;
|
icculus@12218
|
1287 |
SDL_SysWMinfo syswm;
|
icculus@12218
|
1288 |
|
icculus@12218
|
1289 |
SDL_VERSION(&syswm.version);
|
icculus@12218
|
1290 |
if (!SDL_GetWindowWMInfo(window, &syswm)) {
|
icculus@12218
|
1291 |
return NULL;
|
icculus@12218
|
1292 |
}
|
icculus@12218
|
1293 |
|
icculus@12218
|
1294 |
if (IsMetalAvailable(&syswm) == -1) {
|
icculus@12218
|
1295 |
return NULL;
|
icculus@12218
|
1296 |
}
|
icculus@12218
|
1297 |
|
icculus@12218
|
1298 |
renderer = (SDL_Renderer *) SDL_calloc(1, sizeof(*renderer));
|
icculus@12218
|
1299 |
if (!renderer) {
|
icculus@12218
|
1300 |
SDL_OutOfMemory();
|
icculus@12218
|
1301 |
return NULL;
|
icculus@12218
|
1302 |
}
|
icculus@12218
|
1303 |
|
icculus@12218
|
1304 |
// !!! FIXME: MTLCopyAllDevices() can find other GPUs on macOS...
|
icculus@12218
|
1305 |
mtldevice = MTLCreateSystemDefaultDevice();
|
icculus@12218
|
1306 |
|
icculus@12218
|
1307 |
if (mtldevice == nil) {
|
icculus@12218
|
1308 |
SDL_free(renderer);
|
icculus@12218
|
1309 |
SDL_SetError("Failed to obtain Metal device");
|
icculus@12218
|
1310 |
return NULL;
|
icculus@12218
|
1311 |
}
|
icculus@12218
|
1312 |
|
icculus@12218
|
1313 |
// !!! FIXME: error checking on all of this.
|
icculus@12218
|
1314 |
data = [[METAL_RenderData alloc] init];
|
icculus@12218
|
1315 |
|
icculus@12218
|
1316 |
renderer->driverdata = (void*)CFBridgingRetain(data);
|
icculus@12218
|
1317 |
renderer->window = window;
|
icculus@12218
|
1318 |
|
icculus@12218
|
1319 |
#ifdef __MACOSX__
|
icculus@12218
|
1320 |
NSView *view = Cocoa_Mtl_AddMetalView(window);
|
icculus@12218
|
1321 |
CAMetalLayer *layer = (CAMetalLayer *)[view layer];
|
icculus@12218
|
1322 |
|
icculus@12218
|
1323 |
layer.device = mtldevice;
|
icculus@12218
|
1324 |
|
icculus@12218
|
1325 |
//layer.colorspace = nil;
|
icculus@12218
|
1326 |
|
icculus@12218
|
1327 |
#else
|
icculus@12218
|
1328 |
UIView *view = UIKit_Mtl_AddMetalView(window);
|
icculus@12218
|
1329 |
CAMetalLayer *layer = (CAMetalLayer *)[view layer];
|
icculus@12218
|
1330 |
#endif
|
icculus@12218
|
1331 |
|
icculus@12218
|
1332 |
// Necessary for RenderReadPixels.
|
icculus@12218
|
1333 |
layer.framebufferOnly = NO;
|
icculus@12218
|
1334 |
|
icculus@12218
|
1335 |
data.mtldevice = layer.device;
|
icculus@12218
|
1336 |
data.mtllayer = layer;
|
icculus@12218
|
1337 |
id<MTLCommandQueue> mtlcmdqueue = [data.mtldevice newCommandQueue];
|
icculus@12218
|
1338 |
data.mtlcmdqueue = mtlcmdqueue;
|
icculus@12218
|
1339 |
data.mtlcmdqueue.label = @"SDL Metal Renderer";
|
icculus@12218
|
1340 |
data.mtlpassdesc = [MTLRenderPassDescriptor renderPassDescriptor];
|
icculus@12218
|
1341 |
|
icculus@12218
|
1342 |
NSError *err = nil;
|
icculus@12218
|
1343 |
|
icculus@12218
|
1344 |
// The compiled .metallib is embedded in a static array in a header file
|
icculus@12218
|
1345 |
// but the original shader source code is in SDL_shaders_metal.metal.
|
icculus@12218
|
1346 |
dispatch_data_t mtllibdata = dispatch_data_create(sdl_metallib, sdl_metallib_len, dispatch_get_global_queue(0, 0), ^{});
|
icculus@12218
|
1347 |
id<MTLLibrary> mtllibrary = [data.mtldevice newLibraryWithData:mtllibdata error:&err];
|
icculus@12218
|
1348 |
data.mtllibrary = mtllibrary;
|
icculus@12218
|
1349 |
SDL_assert(err == nil);
|
icculus@12218
|
1350 |
#if !__has_feature(objc_arc)
|
icculus@12218
|
1351 |
dispatch_release(mtllibdata);
|
icculus@12218
|
1352 |
#endif
|
icculus@12218
|
1353 |
data.mtllibrary.label = @"SDL Metal renderer shader library";
|
icculus@12218
|
1354 |
|
icculus@12218
|
1355 |
/* Do some shader pipeline state loading up-front rather than on demand. */
|
icculus@12218
|
1356 |
data.pipelinescount = 0;
|
icculus@12218
|
1357 |
data.allpipelines = NULL;
|
icculus@12218
|
1358 |
ChooseShaderPipelines(data, MTLPixelFormatBGRA8Unorm);
|
icculus@12218
|
1359 |
|
icculus@12218
|
1360 |
MTLSamplerDescriptor *samplerdesc = [[MTLSamplerDescriptor alloc] init];
|
icculus@12218
|
1361 |
|
icculus@12218
|
1362 |
samplerdesc.minFilter = MTLSamplerMinMagFilterNearest;
|
icculus@12218
|
1363 |
samplerdesc.magFilter = MTLSamplerMinMagFilterNearest;
|
icculus@12218
|
1364 |
id<MTLSamplerState> mtlsamplernearest = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
|
icculus@12218
|
1365 |
data.mtlsamplernearest = mtlsamplernearest;
|
icculus@12218
|
1366 |
|
icculus@12218
|
1367 |
samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
|
icculus@12218
|
1368 |
samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
|
icculus@12218
|
1369 |
id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
|
icculus@12218
|
1370 |
data.mtlsamplerlinear = mtlsamplerlinear;
|
icculus@12218
|
1371 |
|
icculus@12218
|
1372 |
/* Note: matrices are column major. */
|
icculus@12218
|
1373 |
float identitytransform[16] = {
|
icculus@12218
|
1374 |
1.0f, 0.0f, 0.0f, 0.0f,
|
icculus@12218
|
1375 |
0.0f, 1.0f, 0.0f, 0.0f,
|
icculus@12218
|
1376 |
0.0f, 0.0f, 1.0f, 0.0f,
|
icculus@12218
|
1377 |
0.0f, 0.0f, 0.0f, 1.0f,
|
icculus@12218
|
1378 |
};
|
icculus@12218
|
1379 |
|
icculus@12218
|
1380 |
float halfpixeltransform[16] = {
|
icculus@12218
|
1381 |
1.0f, 0.0f, 0.0f, 0.0f,
|
icculus@12218
|
1382 |
0.0f, 1.0f, 0.0f, 0.0f,
|
icculus@12218
|
1383 |
0.0f, 0.0f, 1.0f, 0.0f,
|
icculus@12218
|
1384 |
0.5f, 0.5f, 0.0f, 1.0f,
|
icculus@12218
|
1385 |
};
|
icculus@12218
|
1386 |
|
icculus@12218
|
1387 |
/* Metal pads float3s to 16 bytes. */
|
icculus@12218
|
1388 |
float decodetransformJPEG[4*4] = {
|
icculus@12218
|
1389 |
0.0, -0.501960814, -0.501960814, 0.0, /* offset */
|
icculus@12218
|
1390 |
1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
|
icculus@12218
|
1391 |
1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
|
icculus@12218
|
1392 |
1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
|
icculus@12218
|
1393 |
};
|
icculus@12218
|
1394 |
|
icculus@12218
|
1395 |
float decodetransformBT601[4*4] = {
|
icculus@12218
|
1396 |
-0.0627451017, -0.501960814, -0.501960814, 0.0, /* offset */
|
icculus@12218
|
1397 |
1.1644, 0.0000, 1.5960, 0.0, /* Rcoeff */
|
icculus@12218
|
1398 |
1.1644, -0.3918, -0.8130, 0.0, /* Gcoeff */
|
icculus@12218
|
1399 |
1.1644, 2.0172, 0.0000, 0.0, /* Bcoeff */
|
icculus@12218
|
1400 |
};
|
icculus@12218
|
1401 |
|
icculus@12218
|
1402 |
float decodetransformBT709[4*4] = {
|
icculus@12218
|
1403 |
0.0, -0.501960814, -0.501960814, 0.0, /* offset */
|
icculus@12218
|
1404 |
1.0000, 0.0000, 1.4020, 0.0, /* Rcoeff */
|
icculus@12218
|
1405 |
1.0000, -0.3441, -0.7141, 0.0, /* Gcoeff */
|
icculus@12218
|
1406 |
1.0000, 1.7720, 0.0000, 0.0, /* Bcoeff */
|
icculus@12218
|
1407 |
};
|
icculus@12218
|
1408 |
|
icculus@12218
|
1409 |
id<MTLBuffer> mtlbufconstantstaging = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModeShared];
|
icculus@12218
|
1410 |
#if !__has_feature(objc_arc)
|
icculus@12218
|
1411 |
[mtlbufconstantstaging autorelease];
|
icculus@12218
|
1412 |
#endif
|
icculus@12218
|
1413 |
mtlbufconstantstaging.label = @"SDL constant staging data";
|
icculus@12218
|
1414 |
|
icculus@12218
|
1415 |
id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:MTLResourceStorageModePrivate];
|
icculus@12218
|
1416 |
data.mtlbufconstants = mtlbufconstants;
|
icculus@12218
|
1417 |
data.mtlbufconstants.label = @"SDL constant data";
|
icculus@12218
|
1418 |
|
icculus@12218
|
1419 |
char *constantdata = [mtlbufconstantstaging contents];
|
icculus@12218
|
1420 |
SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
|
icculus@12218
|
1421 |
SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
|
icculus@12218
|
1422 |
SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_JPEG, decodetransformJPEG, sizeof(decodetransformJPEG));
|
icculus@12218
|
1423 |
SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT601, decodetransformBT601, sizeof(decodetransformBT601));
|
icculus@12218
|
1424 |
SDL_memcpy(constantdata + CONSTANTS_OFFSET_DECODE_BT709, decodetransformBT709, sizeof(decodetransformBT709));
|
icculus@12218
|
1425 |
|
icculus@12218
|
1426 |
id<MTLCommandBuffer> cmdbuffer = [data.mtlcmdqueue commandBuffer];
|
icculus@12218
|
1427 |
id<MTLBlitCommandEncoder> blitcmd = [cmdbuffer blitCommandEncoder];
|
icculus@12218
|
1428 |
|
icculus@12218
|
1429 |
[blitcmd copyFromBuffer:mtlbufconstantstaging sourceOffset:0 toBuffer:data.mtlbufconstants destinationOffset:0 size:CONSTANTS_LENGTH];
|
icculus@12218
|
1430 |
|
icculus@12218
|
1431 |
[blitcmd endEncoding];
|
icculus@12218
|
1432 |
[cmdbuffer commit];
|
icculus@12218
|
1433 |
|
icculus@12218
|
1434 |
// !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
|
icculus@12218
|
1435 |
|
icculus@12218
|
1436 |
renderer->WindowEvent = METAL_WindowEvent;
|
icculus@12218
|
1437 |
renderer->GetOutputSize = METAL_GetOutputSize;
|
icculus@12218
|
1438 |
renderer->SupportsBlendMode = METAL_SupportsBlendMode;
|
icculus@12218
|
1439 |
renderer->CreateTexture = METAL_CreateTexture;
|
icculus@12218
|
1440 |
renderer->UpdateTexture = METAL_UpdateTexture;
|
icculus@12218
|
1441 |
renderer->UpdateTextureYUV = METAL_UpdateTextureYUV;
|
icculus@12218
|
1442 |
renderer->LockTexture = METAL_LockTexture;
|
icculus@12218
|
1443 |
renderer->UnlockTexture = METAL_UnlockTexture;
|
icculus@12218
|
1444 |
renderer->SetRenderTarget = METAL_SetRenderTarget;
|
icculus@12218
|
1445 |
renderer->QueueSetViewport = METAL_QueueSetViewport;
|
icculus@12218
|
1446 |
renderer->QueueSetDrawColor = METAL_QueueSetDrawColor;
|
icculus@12218
|
1447 |
renderer->QueueDrawPoints = METAL_QueueDrawPoints;
|
icculus@12218
|
1448 |
renderer->QueueDrawLines = METAL_QueueDrawPoints; // lines and points queue the same way.
|
icculus@12218
|
1449 |
renderer->QueueFillRects = METAL_QueueFillRects;
|
icculus@12218
|
1450 |
renderer->QueueCopy = METAL_QueueCopy;
|
icculus@12218
|
1451 |
renderer->QueueCopyEx = METAL_QueueCopyEx;
|
icculus@12218
|
1452 |
renderer->RunCommandQueue = METAL_RunCommandQueue;
|
icculus@12218
|
1453 |
renderer->RenderReadPixels = METAL_RenderReadPixels;
|
icculus@12218
|
1454 |
renderer->RenderPresent = METAL_RenderPresent;
|
icculus@12218
|
1455 |
renderer->DestroyTexture = METAL_DestroyTexture;
|
icculus@12218
|
1456 |
renderer->DestroyRenderer = METAL_DestroyRenderer;
|
icculus@12218
|
1457 |
renderer->GetMetalLayer = METAL_GetMetalLayer;
|
icculus@12218
|
1458 |
renderer->GetMetalCommandEncoder = METAL_GetMetalCommandEncoder;
|
icculus@12218
|
1459 |
|
icculus@12218
|
1460 |
renderer->info = METAL_RenderDriver.info;
|
icculus@12218
|
1461 |
renderer->info.flags = (SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
|
icculus@12218
|
1462 |
|
icculus@12218
|
1463 |
renderer->always_batch = SDL_TRUE;
|
icculus@12218
|
1464 |
|
icculus@12218
|
1465 |
#if defined(__MACOSX__) && defined(MAC_OS_X_VERSION_10_13)
|
icculus@12218
|
1466 |
if (@available(macOS 10.13, *)) {
|
icculus@12218
|
1467 |
data.mtllayer.displaySyncEnabled = (flags & SDL_RENDERER_PRESENTVSYNC) != 0;
|
slime73@12393
|
1468 |
if (data.mtllayer.displaySyncEnabled) {
|
slime73@12393
|
1469 |
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
slime73@12393
|
1470 |
}
|
icculus@12218
|
1471 |
} else
|
icculus@12218
|
1472 |
#endif
|
icculus@12218
|
1473 |
{
|
icculus@12218
|
1474 |
renderer->info.flags |= SDL_RENDERER_PRESENTVSYNC;
|
icculus@12218
|
1475 |
}
|
icculus@12218
|
1476 |
|
icculus@12218
|
1477 |
/* https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf */
|
icculus@12218
|
1478 |
int maxtexsize = 4096;
|
icculus@12218
|
1479 |
#if defined(__MACOSX__)
|
icculus@12218
|
1480 |
maxtexsize = 16384;
|
icculus@12218
|
1481 |
#elif defined(__TVOS__)
|
icculus@12218
|
1482 |
maxtexsize = 8192;
|
icculus@12218
|
1483 |
#ifdef __TVOS_11_0
|
icculus@12218
|
1484 |
if (@available(tvOS 11.0, *)) {
|
icculus@12218
|
1485 |
if ([mtldevice supportsFeatureSet:MTLFeatureSet_tvOS_GPUFamily2_v1]) {
|
icculus@12218
|
1486 |
maxtexsize = 16384;
|
icculus@12218
|
1487 |
}
|
icculus@12218
|
1488 |
}
|
icculus@12218
|
1489 |
#endif
|
icculus@12218
|
1490 |
#else
|
icculus@12218
|
1491 |
#ifdef __IPHONE_11_0
|
icculus@12218
|
1492 |
if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1]) {
|
icculus@12218
|
1493 |
maxtexsize = 16384;
|
icculus@12218
|
1494 |
} else
|
icculus@12218
|
1495 |
#endif
|
icculus@12218
|
1496 |
#ifdef __IPHONE_10_0
|
icculus@12218
|
1497 |
if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) {
|
icculus@12218
|
1498 |
maxtexsize = 16384;
|
icculus@12218
|
1499 |
} else
|
icculus@12218
|
1500 |
#endif
|
icculus@12218
|
1501 |
if ([mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v2] || [mtldevice supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily1_v2]) {
|
icculus@12218
|
1502 |
maxtexsize = 8192;
|
icculus@12218
|
1503 |
} else {
|
icculus@12218
|
1504 |
maxtexsize = 4096;
|
icculus@12218
|
1505 |
}
|
icculus@12218
|
1506 |
#endif
|
icculus@12218
|
1507 |
|
icculus@12218
|
1508 |
renderer->info.max_texture_width = maxtexsize;
|
icculus@12218
|
1509 |
renderer->info.max_texture_height = maxtexsize;
|
icculus@12218
|
1510 |
|
icculus@12218
|
1511 |
#if !__has_feature(objc_arc)
|
icculus@12218
|
1512 |
[mtlcmdqueue release];
|
icculus@12218
|
1513 |
[mtllibrary release];
|
icculus@12218
|
1514 |
[samplerdesc release];
|
icculus@12218
|
1515 |
[mtlsamplernearest release];
|
icculus@12218
|
1516 |
[mtlsamplerlinear release];
|
icculus@12218
|
1517 |
[mtlbufconstants release];
|
icculus@12218
|
1518 |
[view release];
|
icculus@12218
|
1519 |
[data release];
|
icculus@12218
|
1520 |
[mtldevice release];
|
icculus@12218
|
1521 |
#endif
|
icculus@12218
|
1522 |
|
icculus@12218
|
1523 |
return renderer;
|
icculus@12218
|
1524 |
}}
|
icculus@12218
|
1525 |
|
icculus@12218
|
1526 |
SDL_RenderDriver METAL_RenderDriver = {
|
icculus@12218
|
1527 |
METAL_CreateRenderer,
|
icculus@12218
|
1528 |
{
|
icculus@12218
|
1529 |
"metal",
|
icculus@12218
|
1530 |
(SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_TARGETTEXTURE),
|
icculus@12218
|
1531 |
6,
|
icculus@12218
|
1532 |
{
|
icculus@12218
|
1533 |
SDL_PIXELFORMAT_ARGB8888,
|
icculus@12218
|
1534 |
SDL_PIXELFORMAT_ABGR8888,
|
icculus@12218
|
1535 |
SDL_PIXELFORMAT_YV12,
|
icculus@12218
|
1536 |
SDL_PIXELFORMAT_IYUV,
|
icculus@12218
|
1537 |
SDL_PIXELFORMAT_NV12,
|
icculus@12218
|
1538 |
SDL_PIXELFORMAT_NV21
|
icculus@12218
|
1539 |
},
|
icculus@12218
|
1540 |
0, 0,
|
icculus@12218
|
1541 |
}
|
icculus@12218
|
1542 |
};
|
icculus@12218
|
1543 |
|
icculus@11729
|
1544 |
#endif /* SDL_VIDEO_RENDER_METAL && !SDL_RENDER_DISABLED */
|
icculus@11729
|
1545 |
|
icculus@11729
|
1546 |
/* vi: set ts=4 sw=4 expandtab: */
|