src/render/metal/SDL_render_metal.m
changeset 11810 30acd4dcfdb6
parent 11809 4b858abfb24d
child 11811 5d94cb6b24d3
equal deleted inserted replaced
11809:4b858abfb24d 11810:30acd4dcfdb6
   103      4096, 4096
   103      4096, 4096
   104 #endif
   104 #endif
   105     }
   105     }
   106 };
   106 };
   107 
   107 
       
   108 /* macOS requires constants in a buffer to have a 256 byte alignment. */
       
   109 #ifdef __MACOSX__
       
   110 #define CONSTANT_ALIGN 256
       
   111 #else
       
   112 #define CONSTANT_ALIGN 4
       
   113 #endif
       
   114 
       
   115 #define ALIGN_CONSTANTS(size) ((size + CONSTANT_ALIGN - 1) & (~(CONSTANT_ALIGN - 1)))
       
   116 
       
   117 static const size_t CONSTANTS_OFFSET_IDENTITY = 0;
       
   118 static const size_t CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM = ALIGN_CONSTANTS(CONSTANTS_OFFSET_IDENTITY + sizeof(float) * 16);
       
   119 static const size_t CONSTANTS_OFFSET_CLEAR_VERTS = ALIGN_CONSTANTS(CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM + sizeof(float) * 16);
       
   120 static const size_t CONSTANTS_LENGTH = CONSTANTS_OFFSET_CLEAR_VERTS + sizeof(float) * 6;
       
   121 
   108 typedef enum SDL_MetalVertexFunction
   122 typedef enum SDL_MetalVertexFunction
   109 {
   123 {
   110     SDL_METAL_VERTEX_SOLID,
   124     SDL_METAL_VERTEX_SOLID,
   111     SDL_METAL_VERTEX_COPY,
   125     SDL_METAL_VERTEX_COPY,
   112 } SDL_MetalVertexFunction;
   126 } SDL_MetalVertexFunction;
   142     @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
   156     @property (nonatomic, retain) id<CAMetalDrawable> mtlbackbuffer;
   143     @property (nonatomic, assign) METAL_PipelineCache *mtlpipelineprims;
   157     @property (nonatomic, assign) METAL_PipelineCache *mtlpipelineprims;
   144     @property (nonatomic, assign) METAL_PipelineCache *mtlpipelinecopy;
   158     @property (nonatomic, assign) METAL_PipelineCache *mtlpipelinecopy;
   145     @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest;
   159     @property (nonatomic, retain) id<MTLSamplerState> mtlsamplernearest;
   146     @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear;
   160     @property (nonatomic, retain) id<MTLSamplerState> mtlsamplerlinear;
   147     @property (nonatomic, retain) id<MTLBuffer> mtlbufclearverts;
   161     @property (nonatomic, retain) id<MTLBuffer> mtlbufconstants;
   148     @property (nonatomic, retain) id<MTLBuffer> mtlbufidentitytransform;
       
   149     @property (nonatomic, retain) CAMetalLayer *mtllayer;
   162     @property (nonatomic, retain) CAMetalLayer *mtllayer;
   150     @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
   163     @property (nonatomic, retain) MTLRenderPassDescriptor *mtlpassdesc;
   151 @end
   164 @end
   152 
   165 
   153 @implementation METAL_RenderData
   166 @implementation METAL_RenderData
   160     [_mtlcmdencoder release];
   173     [_mtlcmdencoder release];
   161     [_mtllibrary release];
   174     [_mtllibrary release];
   162     [_mtlbackbuffer release];
   175     [_mtlbackbuffer release];
   163     [_mtlsamplernearest release];
   176     [_mtlsamplernearest release];
   164     [_mtlsamplerlinear release];
   177     [_mtlsamplerlinear release];
   165     [_mtlbufclearverts release];
   178     [_mtlbufconstants release];
   166     [_mtlbufidentitytransform release];
       
   167     [_mtllayer release];
   179     [_mtllayer release];
   168     [_mtlpassdesc release];
   180     [_mtlpassdesc release];
   169     [super dealloc];
   181     [super dealloc];
   170 }
   182 }
   171 #endif
   183 #endif
   458     samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
   470     samplerdesc.minFilter = MTLSamplerMinMagFilterLinear;
   459     samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
   471     samplerdesc.magFilter = MTLSamplerMinMagFilterLinear;
   460     id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
   472     id<MTLSamplerState> mtlsamplerlinear = [data.mtldevice newSamplerStateWithDescriptor:samplerdesc];
   461     data.mtlsamplerlinear = mtlsamplerlinear;
   473     data.mtlsamplerlinear = mtlsamplerlinear;
   462 
   474 
   463     static const float clearverts[] = { 0, 0,  0, 3,  3, 0 };
   475     /* Note: matrices are column major. */
   464     id<MTLBuffer> mtlbufclearverts = [data.mtldevice newBufferWithBytes:clearverts length:sizeof(clearverts) options:MTLResourceCPUCacheModeWriteCombined];
   476     float identitytransform[16] = {
   465     data.mtlbufclearverts = mtlbufclearverts;
   477         1.0f, 0.0f, 0.0f, 0.0f,
   466     data.mtlbufclearverts.label = @"SDL_RenderClear vertices";
   478         0.0f, 1.0f, 0.0f, 0.0f,
   467 
   479         0.0f, 0.0f, 1.0f, 0.0f,
   468     float identitytx[16];
   480         0.0f, 0.0f, 0.0f, 1.0f,
   469     SDL_memset(identitytx, 0, sizeof(identitytx));
   481     };
   470     identitytx[0] = identitytx[5] = identitytx[10] = identitytx[15] = 1.0f;
   482 
   471     id<MTLBuffer> mtlbufidentitytransform = [data.mtldevice newBufferWithBytes:identitytx length:sizeof(identitytx) options:0];
   483     float halfpixeltransform[16] = {
   472     data.mtlbufidentitytransform = mtlbufidentitytransform;
   484         1.0f, 0.0f, 0.0f, 0.0f,
   473     data.mtlbufidentitytransform.label = @"SDL_RenderCopy identity transform";
   485         0.0f, 1.0f, 0.0f, 0.0f,
       
   486         0.0f, 0.0f, 1.0f, 0.0f,
       
   487         0.5f, 0.5f, 0.0f, 1.0f,
       
   488     };
       
   489 
       
   490     float clearverts[6] = {0.0f, 0.0f,  0.0f, 2.0f,  2.0f, 0.0f};
       
   491 
       
   492     MTLResourceOptions constantsopts = 0;
       
   493 #ifdef __MACOSX__
       
   494     constantsopts |= MTLResourceStorageModeManaged;
       
   495 #endif
       
   496 
       
   497     id<MTLBuffer> mtlbufconstants = [data.mtldevice newBufferWithLength:CONSTANTS_LENGTH options:constantsopts];
       
   498     data.mtlbufconstants = mtlbufconstants;
       
   499     data.mtlbufconstants.label = @"SDL constant data";
       
   500 
       
   501     char *constantdata = [data.mtlbufconstants contents];
       
   502     SDL_memcpy(constantdata + CONSTANTS_OFFSET_IDENTITY, identitytransform, sizeof(identitytransform));
       
   503     SDL_memcpy(constantdata + CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM, halfpixeltransform, sizeof(halfpixeltransform));
       
   504     SDL_memcpy(constantdata + CONSTANTS_OFFSET_CLEAR_VERTS, clearverts, sizeof(clearverts));
       
   505 #ifdef __MACOSX__
       
   506     [data.mtlbufconstants didModifyRange:NSMakeRange(0, CONSTANTS_LENGTH)];
       
   507 #endif
   474 
   508 
   475     // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
   509     // !!! FIXME: force more clears here so all the drawables are sane to start, and our static buffers are definitely flushed.
   476 
   510 
   477     renderer->WindowEvent = METAL_WindowEvent;
   511     renderer->WindowEvent = METAL_WindowEvent;
   478     renderer->GetOutputSize = METAL_GetOutputSize;
   512     renderer->GetOutputSize = METAL_GetOutputSize;
   514     [mtlcmdqueue release];
   548     [mtlcmdqueue release];
   515     [mtllibrary release];
   549     [mtllibrary release];
   516     [samplerdesc release];
   550     [samplerdesc release];
   517     [mtlsamplernearest release];
   551     [mtlsamplernearest release];
   518     [mtlsamplerlinear release];
   552     [mtlsamplerlinear release];
   519     [mtlbufclearverts release];
   553     [mtlbufconstants release];
   520     [mtlbufidentitytransform release];
       
   521     [view release];
   554     [view release];
   522     [data release];
   555     [data release];
   523 #ifdef __MACOSX__
   556 #ifdef __MACOSX__
   524     [mtldevice release];
   557     [mtldevice release];
   525 #endif
   558 #endif
   605         default: return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
   638         default: return SDL_SetError("Texture format %s not supported by Metal", SDL_GetPixelFormatName(texture->format));
   606     }
   639     }
   607 
   640 
   608     MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mtlpixfmt
   641     MTLTextureDescriptor *mtltexdesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mtlpixfmt
   609                                             width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
   642                                             width:(NSUInteger)texture->w height:(NSUInteger)texture->h mipmapped:NO];
   610  
   643 
   611     if (texture->access == SDL_TEXTUREACCESS_TARGET) {
   644     /* Not available in iOS 8. */
   612         mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
   645     if ([mtltexdesc respondsToSelector:@selector(usage)]) {
   613     } else {
   646         if (texture->access == SDL_TEXTUREACCESS_TARGET) {
   614         mtltexdesc.usage = MTLTextureUsageShaderRead;
   647             mtltexdesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
       
   648         } else {
       
   649             mtltexdesc.usage = MTLTextureUsageShaderRead;
       
   650         }
   615     }
   651     }
   616     //mtltexdesc.resourceOptions = MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged;
   652     //mtltexdesc.resourceOptions = MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeManaged;
   617     //mtltexdesc.storageMode = MTLStorageModeManaged;
   653     //mtltexdesc.storageMode = MTLStorageModeManaged;
   618     
   654     
   619     id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   655     id<MTLTexture> mtltexture = [data.mtldevice newTextureWithDescriptor:mtltexdesc];
   794 
   830 
   795     // Draw a simple filled fullscreen triangle now.
   831     // Draw a simple filled fullscreen triangle now.
   796     METAL_SetOrthographicProjection(renderer, 1, 1);
   832     METAL_SetOrthographicProjection(renderer, 1, 1);
   797     [data.mtlcmdencoder setViewport:viewport];
   833     [data.mtlcmdencoder setViewport:viewport];
   798     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, SDL_BLENDMODE_NONE)];
   834     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, SDL_BLENDMODE_NONE)];
   799     [data.mtlcmdencoder setVertexBuffer:data.mtlbufclearverts offset:0 atIndex:0];
   835     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_CLEAR_VERTS atIndex:0];
       
   836     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   800     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   837     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   801     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
   838     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
   802 
   839 
   803     // reset the viewport for the rest of our usual drawing work...
   840     // reset the viewport for the rest of our usual drawing work...
   804     viewport.originX = renderer->viewport.x;
   841     viewport.originX = renderer->viewport.x;
   811     METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   848     METAL_SetOrthographicProjection(renderer, renderer->viewport.w, renderer->viewport.h);
   812 
   849 
   813     return 0;
   850     return 0;
   814 }}
   851 }}
   815 
   852 
   816 // adjust pixel center for x and y coordinates
       
   817 static inline float
       
   818 adjustx(const float val)
       
   819 {
       
   820 	return (val + 0.5f);
       
   821 }
       
   822 static inline float
       
   823 adjusty(const float val)
       
   824 {
       
   825 	return (val + 0.5f);
       
   826 }
       
   827 
       
   828 // normalize a value from 0.0f to len into 0.0f to 1.0f.
   853 // normalize a value from 0.0f to len into 0.0f to 1.0f.
   829 static inline float
   854 static inline float
   830 normtex(const float _val, const float len)
   855 normtex(const float _val, const float len)
   831 {
   856 {
   832     const float val = (_val < 0.0f) ? 0.0f : (_val > len) ? len : _val;
   857     return _val / len;
   833     return ((val + 0.5f) / len);
       
   834 }
   858 }
   835 
   859 
   836 static int
   860 static int
   837 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   861 DrawVerts(SDL_Renderer * renderer, const SDL_FPoint * points, int count,
   838           const MTLPrimitiveType primtype)
   862           const MTLPrimitiveType primtype)
   839 { @autoreleasepool {
   863 { @autoreleasepool {
   840     METAL_ActivateRenderer(renderer);
   864     METAL_ActivateRenderer(renderer);
   841 
   865 
   842     const size_t vertlen = (sizeof (float) * 2) * count;
   866     const size_t vertlen = (sizeof (float) * 2) * count;
   843     float *verts = SDL_malloc(vertlen);
       
   844     if (!verts) {
       
   845         return SDL_OutOfMemory();
       
   846     }
       
   847 
       
   848     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   867     METAL_RenderData *data = (__bridge METAL_RenderData *) renderer->driverdata;
   849 
   868 
   850     // !!! FIXME: render color should live in a dedicated uniform buffer.
   869     // !!! FIXME: render color should live in a dedicated uniform buffer.
   851     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   870     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   852 
   871 
   853     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, renderer->blendMode)];
   872     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, renderer->blendMode)];
   854     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   873     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   855 
   874 
   856     float *ptr = verts;
   875     [data.mtlcmdencoder setVertexBytes:points length:vertlen atIndex:0];
   857     for (int i = 0; i < count; i++, points++) {
   876     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_HALF_PIXEL_TRANSFORM atIndex:3];
   858         *ptr = adjustx(points->x); ptr++;
       
   859         *ptr = adjusty(points->y); ptr++;
       
   860     }
       
   861 
       
   862     [data.mtlcmdencoder setVertexBytes:verts length:vertlen atIndex:0];
       
   863     [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
   877     [data.mtlcmdencoder drawPrimitives:primtype vertexStart:0 vertexCount:count];
   864 
   878 
   865     SDL_free(verts);
       
   866     return 0;
   879     return 0;
   867 }}
   880 }}
   868 
   881 
   869 static int
   882 static int
   870 METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   883 METAL_RenderDrawPoints(SDL_Renderer * renderer, const SDL_FPoint * points, int count)
   887     // !!! FIXME: render color should live in a dedicated uniform buffer.
   900     // !!! FIXME: render color should live in a dedicated uniform buffer.
   888     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   901     const float color[4] = { ((float)renderer->r) / 255.0f, ((float)renderer->g) / 255.0f, ((float)renderer->b) / 255.0f, ((float)renderer->a) / 255.0f };
   889 
   902 
   890     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, renderer->blendMode)];
   903     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelineprims, renderer->blendMode)];
   891     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   904     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
       
   905     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   892 
   906 
   893     for (int i = 0; i < count; i++, rects++) {
   907     for (int i = 0; i < count; i++, rects++) {
   894         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
   908         if ((rects->w <= 0.0f) || (rects->h <= 0.0f)) continue;
   895 
   909 
   896         const float verts[] = {
   910         const float verts[] = {
   897             adjustx(rects->x), adjusty(rects->y + rects->h),
   911             rects->x, rects->y + rects->h,
   898             adjustx(rects->x), adjusty(rects->y),
   912             rects->x, rects->y,
   899             adjustx(rects->x + rects->w), adjusty(rects->y + rects->h),
   913             rects->x + rects->w, rects->y + rects->h,
   900             adjustx(rects->x + rects->w), adjusty(rects->y)
   914             rects->x + rects->w, rects->y
   901         };
   915         };
   902 
   916 
   903         [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
   917         [data.mtlcmdencoder setVertexBytes:verts length:sizeof(verts) atIndex:0];
   904         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
   918         [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
   905     }
   919     }
   916     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   930     METAL_TextureData *texturedata = (__bridge METAL_TextureData *)texture->driverdata;
   917     const float texw = (float) texturedata.mtltexture.width;
   931     const float texw = (float) texturedata.mtltexture.width;
   918     const float texh = (float) texturedata.mtltexture.height;
   932     const float texh = (float) texturedata.mtltexture.height;
   919 
   933 
   920     const float xy[] = {
   934     const float xy[] = {
   921         adjustx(dstrect->x), adjusty(dstrect->y + dstrect->h),
   935         dstrect->x, dstrect->y + dstrect->h,
   922         adjustx(dstrect->x), adjusty(dstrect->y),
   936         dstrect->x, dstrect->y,
   923         adjustx(dstrect->x + dstrect->w), adjusty(dstrect->y + dstrect->h),
   937         dstrect->x + dstrect->w, dstrect->y + dstrect->h,
   924         adjustx(dstrect->x + dstrect->w), adjusty(dstrect->y)
   938         dstrect->x + dstrect->w, dstrect->y
   925     };
   939     };
   926 
   940 
   927     const float uv[] = {
   941     const float uv[] = {
   928         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   942         normtex(srcrect->x, texw), normtex(srcrect->y + srcrect->h, texh),
   929         normtex(srcrect->x, texw), normtex(srcrect->y, texh),
   943         normtex(srcrect->x, texw), normtex(srcrect->y, texh),
   940     }
   954     }
   941 
   955 
   942     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelinecopy, texture->blendMode)];
   956     [data.mtlcmdencoder setRenderPipelineState:ChoosePipelineState(data, data.mtlpipelinecopy, texture->blendMode)];
   943     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
   957     [data.mtlcmdencoder setVertexBytes:xy length:sizeof(xy) atIndex:0];
   944     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
   958     [data.mtlcmdencoder setVertexBytes:uv length:sizeof(uv) atIndex:1];
   945     [data.mtlcmdencoder setVertexBuffer:data.mtlbufidentitytransform offset:0 atIndex:3];
   959     [data.mtlcmdencoder setVertexBuffer:data.mtlbufconstants offset:CONSTANTS_OFFSET_IDENTITY atIndex:3];
   946     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   960     [data.mtlcmdencoder setFragmentBytes:color length:sizeof(color) atIndex:0];
   947     [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
   961     [data.mtlcmdencoder setFragmentTexture:texturedata.mtltexture atIndex:0];
   948     [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
   962     [data.mtlcmdencoder setFragmentSamplerState:texturedata.mtlsampler atIndex:0];
   949     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
   963     [data.mtlcmdencoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
   950 
   964 
   986         maxu, maxv,
  1000         maxu, maxv,
   987         maxu, minv
  1001         maxu, minv
   988     };
  1002     };
   989 
  1003 
   990     const float xy[] = {
  1004     const float xy[] = {
   991         adjustx(-center->x), adjusty(dstrect->h - center->y),
  1005         -center->x, dstrect->h - center->y,
   992         adjustx(-center->x), adjusty(-center->y),
  1006         -center->x, -center->y,
   993         adjustx(dstrect->w - center->x), adjusty(dstrect->h - center->y),
  1007         dstrect->w - center->x, dstrect->h - center->y,
   994         adjustx(dstrect->w - center->x), adjusty(-center->y)
  1008         dstrect->w - center->x, -center->y
   995     };
  1009     };
   996 
  1010 
   997     {
  1011     {
   998         float rads = (float)(M_PI * (float) angle / 180.0f);
  1012         float rads = (float)(M_PI * (float) angle / 180.0f);
   999         float c = cosf(rads), s = sinf(rads);
  1013         float c = cosf(rads), s = sinf(rads);