/*
 * Copyright 2011-2016 Attila Kocsis. All rights reserved.
 * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause
 */

#include "bgfx_p.h"

#if BGFX_CONFIG_RENDERER_METAL

#include "renderer_mtl.h"
#include "renderer.h"
#include "bx/bx.h"

#if BX_PLATFORM_OSX
#	include <Cocoa/Cocoa.h>
#endif

#import <Foundation/Foundation.h>

#define UNIFORM_BUFFER_SIZE (8*1024*1024)

/*
 // known metal shader generation issues:
   03-raymarch: OSX10.11.3 nothing is visible ( depth/color swap in fragment output struct fixed this )
   14-shadowvolumes: in texture as stencil mode - columns/bunny are dark. in fs_shadowvolume_color_lighting SAMPLER2D(s_texStencil, 1) is
		converted to "texture2d<float> s_texStencil [[texture(0)]], sampler _mtlsmp_s_texStencil [[sampler(0)]]". Slot is 1 -> 0.
   15-shadowmaps-simple: shader compilation error
   16-shadowmaps:  //problem with essl -> metal: SAMPLER2D(u_shadowMap0, 4);  sampler index is lost. Shadowmap is set to slot 4, but
      metal shader uses sampler/texture slot 0. this could require changes outside of renderer_mtl?
	  packFloatToRGBA needs highp. currently it uses half.
   24-nbody: no generated compute shaders for metal
   27-terrain: shaderc generates invalid metal shader for vs_terrain_height_texture. vertex output: half4 gl_Position [[position]], should be float4
   31-rsm:
      <program source>:6:23: error: type 'half4' (aka 'vector_half4') is not valid for attribute 'position'
      half4 gl_Position [[position]];


Known issues(driver problems??):
  OSX mac mini(late 2014), OSX10.11.3 : nanovg-rendering: color writemask off causes problem...
		03-raymarch: OSX nothing is visible  ( depth/color order should be swapped in fragment output struct)
					works fine with newer OSX
  iPad mini 2,  iOS 8.1.1:  21-deferred: scissor not working properly
							26-occlusion: query doesn't work with two rendercommandencoders
			Only on this device ( no problem on iPad Air 2 with iOS9.3.1)

  TODOs:
 - remove sync points at mesh update. clearquad: 13-stencil, 26-occlusion, 30-picking
 - framebufferMtl and TextureMtl resolve

 - FrameBufferMtl::postReset recreate framebuffer???

	renderpass load/resolve
 - capture with msaa: 07-callback
 - implement fb discard. problematic with multiple views that has same fb...
 - msaa color/depth/stencil is not saved. could have problem when we switch back to msaa framebuffer
 - refactor store/load actions to support msaa/discard/capture/readback etc...

 - finish savescreenshot with screenshotbegin/end

 - support multiple windows: 22-windows
 - multithreading with multiple commandbuffer

 - compute and drawindirect: 24-nbody (needs compute shaders)

 INFO:
  - 15-shadowmaps-simple (example needs modification mtxCrop znew = z * 0.5 + 0.5 is not needed ) could be hacked in shader too

 ASK:
	BGFX_RESET_FLIP_AFTER_RENDER on low level renderers should be true?
	Do I have absolutely need to send result to screen at flip or can I do it in submit?
 */

namespace bgfx { namespace mtl
{
	static char s_viewName[BGFX_CONFIG_MAX_VIEWS][BGFX_CONFIG_MAX_VIEW_NAME];

	struct PrimInfo
	{
		MTLPrimitiveType m_type;
		uint32_t m_min;
		uint32_t m_div;
		uint32_t m_sub;
	};

	static const PrimInfo s_primInfo[] =
	{
		{ MTLPrimitiveTypeTriangle,      3, 3, 0 },
		{ MTLPrimitiveTypeTriangleStrip, 3, 1, 2 },
		{ MTLPrimitiveTypeLine,          2, 2, 0 },
		{ MTLPrimitiveTypeLineStrip,     2, 1, 1 },
		{ MTLPrimitiveTypePoint,         1, 1, 0 },
	};

	static const char* s_primName[] =
	{
		"TriList",
		"TriStrip",
		"Line",
		"LineStrip",
		"Point",
	};
	BX_STATIC_ASSERT(BX_COUNTOF(s_primInfo) == BX_COUNTOF(s_primName));

	static const char* s_attribName[] =
	{
		"a_position",
		"a_normal",
		"a_tangent",
		"a_bitangent",
		"a_color0",
		"a_color1",
		"a_indices",
		"a_weight",
		"a_texcoord0",
		"a_texcoord1",
		"a_texcoord2",
		"a_texcoord3",
		"a_texcoord4",
		"a_texcoord5",
		"a_texcoord6",
		"a_texcoord7",
	};
	BX_STATIC_ASSERT(Attrib::Count == BX_COUNTOF(s_attribName) );

	static const char* s_instanceDataName[] =
	{
		"i_data0",
		"i_data1",
		"i_data2",
		"i_data3",
		"i_data4",
	};
	BX_STATIC_ASSERT(BGFX_CONFIG_MAX_INSTANCE_DATA_COUNT == BX_COUNTOF(s_instanceDataName) );

	static const MTLVertexFormat s_attribType[][4][2] = //type, count, normalized
	{
		// Uint8
		{
			{ MTLVertexFormatUChar2, MTLVertexFormatUChar2Normalized },
			{ MTLVertexFormatUChar2, MTLVertexFormatUChar2Normalized },
			{ MTLVertexFormatUChar3, MTLVertexFormatUChar3Normalized },
			{ MTLVertexFormatUChar4, MTLVertexFormatUChar4Normalized }
		},

		//Uint10
		//Note: unnormalized is handled as normalized now
		{
			{ MTLVertexFormatUInt1010102Normalized, MTLVertexFormatUInt1010102Normalized },
			{ MTLVertexFormatUInt1010102Normalized, MTLVertexFormatUInt1010102Normalized },
			{ MTLVertexFormatUInt1010102Normalized, MTLVertexFormatUInt1010102Normalized },
			{ MTLVertexFormatUInt1010102Normalized, MTLVertexFormatUInt1010102Normalized }
		},

		//Int16
		{
			{ MTLVertexFormatShort2, MTLVertexFormatShort2Normalized },
			{ MTLVertexFormatShort2, MTLVertexFormatShort2Normalized },
			{ MTLVertexFormatShort3, MTLVertexFormatShort3Normalized },
			{ MTLVertexFormatShort4, MTLVertexFormatShort4Normalized }
		},

		//Half
		{
			{ MTLVertexFormatHalf2, MTLVertexFormatHalf2 },
			{ MTLVertexFormatHalf2, MTLVertexFormatHalf2 },
			{ MTLVertexFormatHalf3, MTLVertexFormatHalf3 },
			{ MTLVertexFormatHalf4, MTLVertexFormatHalf4 }
		},

		//Float
		{
			{ MTLVertexFormatFloat,  MTLVertexFormatFloat  },
			{ MTLVertexFormatFloat2, MTLVertexFormatFloat2 },
			{ MTLVertexFormatFloat3, MTLVertexFormatFloat3 },
			{ MTLVertexFormatFloat4, MTLVertexFormatFloat4 }
		},
	};
	BX_STATIC_ASSERT(AttribType::Count == BX_COUNTOF(s_attribType) );

	static const MTLCullMode s_cullMode[] =
	{
		MTLCullModeNone,
		MTLCullModeFront,
		MTLCullModeBack,
		MTLCullModeNone
	};

	static const MTLBlendFactor s_blendFactor[][2] =
	{
		{ (MTLBlendFactor)0,                      (MTLBlendFactor)0                      }, // ignored
		{ MTLBlendFactorZero,                     MTLBlendFactorZero                     }, // ZERO
		{ MTLBlendFactorOne,                      MTLBlendFactorOne                      }, // ONE
		{ MTLBlendFactorSourceColor,              MTLBlendFactorSourceAlpha              }, // SRC_COLOR
		{ MTLBlendFactorOneMinusSourceColor,      MTLBlendFactorOneMinusSourceAlpha      }, // INV_SRC_COLOR
		{ MTLBlendFactorSourceAlpha,              MTLBlendFactorSourceAlpha              }, // SRC_ALPHA
		{ MTLBlendFactorOneMinusSourceAlpha,      MTLBlendFactorOneMinusSourceAlpha      }, // INV_SRC_ALPHA
		{ MTLBlendFactorDestinationAlpha,         MTLBlendFactorDestinationAlpha         }, // DST_ALPHA
		{ MTLBlendFactorOneMinusDestinationAlpha, MTLBlendFactorOneMinusDestinationAlpha }, // INV_DST_ALPHA
		{ MTLBlendFactorDestinationColor,         MTLBlendFactorDestinationAlpha         }, // DST_COLOR
		{ MTLBlendFactorOneMinusDestinationColor, MTLBlendFactorOneMinusDestinationAlpha }, // INV_DST_COLOR
		{ MTLBlendFactorSourceAlphaSaturated,     MTLBlendFactorOne                      }, // SRC_ALPHA_SAT
		{ MTLBlendFactorBlendColor,               MTLBlendFactorBlendColor               }, // FACTOR
		{ MTLBlendFactorOneMinusBlendColor,       MTLBlendFactorOneMinusBlendColor       }, // INV_FACTOR
	};

	static const MTLBlendOperation s_blendEquation[] =
	{
		MTLBlendOperationAdd,
		MTLBlendOperationSubtract,
		MTLBlendOperationReverseSubtract,
		MTLBlendOperationMin,
		MTLBlendOperationMax,
	};

	static const MTLCompareFunction s_cmpFunc[] =
	{
		MTLCompareFunctionAlways,
		MTLCompareFunctionLess,
		MTLCompareFunctionLessEqual,
		MTLCompareFunctionEqual,
		MTLCompareFunctionGreaterEqual,
		MTLCompareFunctionGreater,
		MTLCompareFunctionNotEqual,
		MTLCompareFunctionNever,
		MTLCompareFunctionAlways
	};

	static const MTLStencilOperation s_stencilOp[] =
	{
		MTLStencilOperationZero,
		MTLStencilOperationKeep,
		MTLStencilOperationReplace,
		MTLStencilOperationIncrementWrap,
		MTLStencilOperationIncrementClamp,
		MTLStencilOperationDecrementWrap,
		MTLStencilOperationDecrementClamp,
		MTLStencilOperationInvert
	};

	static const MTLSamplerAddressMode s_textureAddress[] =
	{
		MTLSamplerAddressModeRepeat,
		MTLSamplerAddressModeMirrorRepeat,
		MTLSamplerAddressModeClampToEdge,
		MTLSamplerAddressModeClampToZero,
	};

	static const MTLSamplerMinMagFilter s_textureFilterMinMag[] =
	{
		MTLSamplerMinMagFilterLinear,
		MTLSamplerMinMagFilterNearest,
		MTLSamplerMinMagFilterLinear,
	};

	static const MTLSamplerMipFilter s_textureFilterMip[] =
	{
		MTLSamplerMipFilterLinear,
		MTLSamplerMipFilterNearest,
	};

	struct TextureFormatInfo
	{
		MTLPixelFormat m_fmt;
		MTLPixelFormat m_fmtSrgb;
	};

	static TextureFormatInfo s_textureFormat[] =
	{
		{ MTLPixelFormat(130) /*BC1_RGBA*/,				MTLPixelFormat(131) /*BC1_RGBA_sRGB*/        }, // BC1
		{ MTLPixelFormat(132) /*BC2_RGBA*/,				MTLPixelFormat(133) /*BC2_RGBA_sRGB*/        }, // BC2
		{ MTLPixelFormat(134) /*BC3_RGBA*/,				MTLPixelFormat(135) /*BC3_RGBA_sRGB*/        }, // BC3
		{ MTLPixelFormat(140) /*BC4_RUnorm*/,			MTLPixelFormatInvalid                        }, // BC4
		{ MTLPixelFormat(142) /*BC5_RGUnorm*/,			MTLPixelFormatInvalid                        }, // BC5
		{ MTLPixelFormat(150) /*BC6H_RGBFloat*/,		MTLPixelFormatInvalid                        }, // BC6H
		{ MTLPixelFormat(152) /*BC7_RGBAUnorm*/,		MTLPixelFormat(153) /*BC7_RGBAUnorm_sRGB*/   }, // BC7
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // ETC1
		{ MTLPixelFormat(180) /*ETC2_RGB8*/,			MTLPixelFormat(181) /*ETC2_RGB8_sRGB*/       }, // ETC2
		{ MTLPixelFormat(178) /*EAC_RGBA8*/,			MTLPixelFormat(179) /*EAC_RGBA8_sRGB*/       }, // ETC2A
		{ MTLPixelFormat(182) /*ETC2_RGB8A1*/,			MTLPixelFormat(183) /*ETC2_RGB8A1_sRGB*/     }, // ETC2A1
		{ MTLPixelFormat(160) /*PVRTC_RGB_2BPP*/,		MTLPixelFormat(161) /*PVRTC_RGB_2BPP_sRGB*/  }, // PTC12
		{ MTLPixelFormat(162) /*PVRTC_RGB_4BPP*/,		MTLPixelFormat(163) /*PVRTC_RGB_4BPP_sRGB*/  }, // PTC14
		{ MTLPixelFormat(164) /*PVRTC_RGBA_2BPP*/,		MTLPixelFormat(165) /*PVRTC_RGBA_2BPP_sRGB*/ }, // PTC12A
		{ MTLPixelFormat(166) /*PVRTC_RGBA_4BPP*/,		MTLPixelFormat(167) /*PVRTC_RGBA_4BPP_sRGB*/ }, // PTC14A
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // PTC22
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // PTC24
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // Unknown
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // R1
		{ MTLPixelFormatA8Unorm,						MTLPixelFormatInvalid                        }, // A8
		{ MTLPixelFormatR8Unorm,						MTLPixelFormat(11) /*R8Unorm_sRGB*/          }, // R8
		{ MTLPixelFormatR8Sint,							MTLPixelFormatInvalid                        }, // R8I
		{ MTLPixelFormatR8Uint,							MTLPixelFormatInvalid                        }, // R8U
		{ MTLPixelFormatR8Snorm,						MTLPixelFormatInvalid                        }, // R8S
		{ MTLPixelFormatR16Unorm,						MTLPixelFormatInvalid                        }, // R16
		{ MTLPixelFormatR16Sint,						MTLPixelFormatInvalid                        }, // R16I
		{ MTLPixelFormatR16Uint,						MTLPixelFormatInvalid                        }, // R16U
		{ MTLPixelFormatR16Float,						MTLPixelFormatInvalid                        }, // R16F
		{ MTLPixelFormatR16Snorm,						MTLPixelFormatInvalid                        }, // R16S
		{ MTLPixelFormatR32Sint,						MTLPixelFormatInvalid                        }, // R32I
		{ MTLPixelFormatR32Uint,						MTLPixelFormatInvalid                        }, // R32U
		{ MTLPixelFormatR32Float,						MTLPixelFormatInvalid                        }, // R32F
		{ MTLPixelFormatRG8Unorm,						MTLPixelFormat(31) /*RG8Unorm_sRGB*/         }, // RG8
		{ MTLPixelFormatRG8Sint,						MTLPixelFormatInvalid                        }, // RG8I
		{ MTLPixelFormatRG8Uint,						MTLPixelFormatInvalid                        }, // RG8U
		{ MTLPixelFormatRG8Snorm,						MTLPixelFormatInvalid                        }, // RG8S
		{ MTLPixelFormatRG16Unorm,						MTLPixelFormatInvalid                        }, // RG16
		{ MTLPixelFormatRG16Sint,						MTLPixelFormatInvalid                        }, // RG16I
		{ MTLPixelFormatRG16Uint,						MTLPixelFormatInvalid                        }, // RG16U
		{ MTLPixelFormatRG16Float,						MTLPixelFormatInvalid                        }, // RG16F
		{ MTLPixelFormatRG16Snorm,						MTLPixelFormatInvalid                        }, // RG16S
		{ MTLPixelFormatRG32Sint,						MTLPixelFormatInvalid                        }, // RG32I
		{ MTLPixelFormatRG32Uint,						MTLPixelFormatInvalid                        }, // RG32U
		{ MTLPixelFormatRG32Float,						MTLPixelFormatInvalid                        }, // RG32F
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // RGB8
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // RGB8I
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // RGB8U
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // RGB8S
		{ MTLPixelFormatRGB9E5Float,					MTLPixelFormatInvalid                        }, // RGB9E5F
		{ MTLPixelFormatBGRA8Unorm,						MTLPixelFormatBGRA8Unorm_sRGB                }, // BGRA8
		{ MTLPixelFormatRGBA8Unorm,						MTLPixelFormatRGBA8Unorm_sRGB                }, // RGBA8
		{ MTLPixelFormatRGBA8Sint,						MTLPixelFormatInvalid                        }, // RGBA8I
		{ MTLPixelFormatRGBA8Uint,						MTLPixelFormatInvalid                        }, // RGBA8U
		{ MTLPixelFormatRGBA8Snorm,						MTLPixelFormatInvalid                        }, // RGBA8S
		{ MTLPixelFormatRGBA16Unorm,					MTLPixelFormatInvalid                        }, // RGBA16
		{ MTLPixelFormatRGBA16Sint,						MTLPixelFormatInvalid                        }, // RGBA16I
		{ MTLPixelFormatRGBA16Uint,						MTLPixelFormatInvalid                        }, // RGBA16U
		{ MTLPixelFormatRGBA16Float,					MTLPixelFormatInvalid                        }, // RGBA16F
		{ MTLPixelFormatRGBA16Snorm,					MTLPixelFormatInvalid                        }, // RGBA16S
		{ MTLPixelFormatRGBA32Sint,						MTLPixelFormatInvalid                        }, // RGBA32I
		{ MTLPixelFormatRGBA32Uint,						MTLPixelFormatInvalid                        }, // RGBA32U
		{ MTLPixelFormatRGBA32Float,					MTLPixelFormatInvalid                        }, // RGBA32F
		{ MTLPixelFormat(40) /*B5G6R5Unorm*/,			MTLPixelFormatInvalid                        }, // R5G6B5
		{ MTLPixelFormat(42) /*ABGR4Unorm*/,			MTLPixelFormatInvalid                        }, // RGBA4
		{ MTLPixelFormat(41) /*A1BGR5Unorm*/,			MTLPixelFormatInvalid                        }, // RGB5A1
		{ MTLPixelFormatRGB10A2Unorm,					MTLPixelFormatInvalid                        }, // RGB10A2
		{ MTLPixelFormatRG11B10Float,					MTLPixelFormatInvalid                        }, // R11G11B10F
		{ MTLPixelFormatInvalid,						MTLPixelFormatInvalid                        }, // UnknownDepth
		{ MTLPixelFormatDepth32Float,					MTLPixelFormatInvalid                        }, // D16
		{ MTLPixelFormatDepth32Float,					MTLPixelFormatInvalid                        }, // D24
		{ MTLPixelFormat(255) /*Depth24Unorm_Stencil8*/,MTLPixelFormatInvalid						 }, // D24S8
		{ MTLPixelFormatDepth32Float,					MTLPixelFormatInvalid                        }, // D32
		{ MTLPixelFormatDepth32Float,					MTLPixelFormatInvalid                        }, // D16F
		{ MTLPixelFormatDepth32Float,					MTLPixelFormatInvalid                        }, // D24F
		{ MTLPixelFormatDepth32Float,					MTLPixelFormatInvalid                        }, // D32F
		{ MTLPixelFormatStencil8,						MTLPixelFormatInvalid                        }, // D0S8
	};
	BX_STATIC_ASSERT(TextureFormat::Count == BX_COUNTOF(s_textureFormat) );

	int s_msaa[5] = { 1,2,4,8,16 };

	#define SHADER_FUNCTION_NAME ("xlatMtlMain")
	#define SHADER_UNIFORM_NAME ("_mtl_u")

	struct RendererContextMtl : public RendererContextI
	{
		RendererContextMtl()
			: m_metalLayer(NULL)
			, m_backBufferPixelFormatHash(0)
			, m_maxAnisotropy(1)
			, m_bufferIndex(0)
			, m_numWindows(1)
			, m_rtMsaa(false)
			, m_capture(NULL)
			, m_captureSize(0)
			, m_drawable(NULL)
			, m_saveScreenshot(false)
		{
		}

		~RendererContextMtl()
		{
		}

		bool init()
		{
			BX_TRACE("Init.");

			m_fbh.idx = invalidHandle;
			memset(m_uniforms, 0, sizeof(m_uniforms) );
			memset(&m_resolution, 0, sizeof(m_resolution) );

			if (NULL != NSClassFromString(@"MTKView") )
			{
				MTKView *view = (MTKView *)g_platformData.nwh;
				if (NULL != view && [view isKindOfClass:NSClassFromString(@"MTKView")])
				{
					m_metalLayer = (CAMetalLayer *)view.layer;
				}
			}

			if (NULL != NSClassFromString(@"CAMetalLayer") )
			{
                if (NULL == m_metalLayer)
#if BX_PLATFORM_IOS
				{
					CAMetalLayer* metalLayer = (CAMetalLayer*)g_platformData.nwh;
					if (NULL == metalLayer
					|| ![metalLayer isKindOfClass:NSClassFromString(@"CAMetalLayer")])
					{
						BX_WARN(NULL != m_device, "Unable to create Metal device. Please set platform data window to a CAMetalLayer");
						return false;
					}

					m_metalLayer = metalLayer;
				}
#elif BX_PLATFORM_OSX
				{
					NSWindow* nsWindow = (NSWindow*)g_platformData.nwh;
					[nsWindow.contentView setWantsLayer:YES];
					m_metalLayer = [CAMetalLayer layer];
					[nsWindow.contentView setLayer:m_metalLayer];
				}
#endif // BX_PLATFORM_*

				m_device = (id<MTLDevice>)g_platformData.context;

				if (NULL == m_device)
				{
					m_device = MTLCreateSystemDefaultDevice();
				}
			}

			if (NULL == m_device
			||  NULL == m_metalLayer)
			{
				BX_WARN(NULL != m_device, "Unable to create Metal device.");
				return false;
			}

			m_metalLayer.device      = m_device;
			m_metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;

			m_commandQueue = m_device.newCommandQueue();
			BGFX_FATAL(NULL != m_commandQueue, Fatal::UnableToInitialize, "Unable to create Metal device.");

			m_renderPipelineDescriptor   = newRenderPipelineDescriptor();
			m_depthStencilDescriptor     = newDepthStencilDescriptor();
			m_frontFaceStencilDescriptor = newStencilDescriptor();
			m_backFaceStencilDescriptor  = newStencilDescriptor();
			m_vertexDescriptor  = newVertexDescriptor();
			m_textureDescriptor = newTextureDescriptor();
			m_samplerDescriptor = newSamplerDescriptor();

			m_framesSemaphore.post(MTL_MAX_FRAMES_IN_FLIGHT);
			for (uint8_t i=0; i < MTL_MAX_FRAMES_IN_FLIGHT; ++i)
			{
				m_uniformBuffers[i] = m_device.newBufferWithLength(UNIFORM_BUFFER_SIZE, 0);
			}
			m_uniformBufferVertexOffset = 0;
			m_uniformBufferFragmentOffset = 0;

			const char* vshSource =
				"using namespace metal;\n"
				"struct xlatMtlShaderOutput { float4 gl_Position [[position]]; float2 v_texcoord0; }; \n"
				"vertex xlatMtlShaderOutput xlatMtlMain (uint v_id [[ vertex_id ]]) \n"
				"{\n"
				"	xlatMtlShaderOutput _mtl_o;\n"
				"   if (v_id==0) { _mtl_o.gl_Position = float4(-1.0,-1.0,0.0,1.0); _mtl_o.v_texcoord0 = float2(0.0,1.0); } \n"
				"   else if (v_id==1) { _mtl_o.gl_Position = float4(3.0,-1.0,0.0,1.0); _mtl_o.v_texcoord0 = float2(2.0,1.0); } \n"
				"   else { _mtl_o.gl_Position = float4(-1.0,3.0,0.0,1.0); _mtl_o.v_texcoord0 = float2(0.0,-1.0); }\n"
				"   return _mtl_o;\n"
				"}\n";

			 const char* fshSource = "using namespace metal; \n"
				" struct xlatMtlShaderInput { float2 v_texcoord0; }; \n"
				" fragment half4 xlatMtlMain (xlatMtlShaderInput _mtl_i[[stage_in]], texture2d<float> s_texColor [[texture(0)]], sampler _mtlsmp_s_texColor [[sampler(0)]] ) \n"
				" {	 return half4(s_texColor.sample(_mtlsmp_s_texColor, _mtl_i.v_texcoord0)); } \n";

			Library lib = m_device.newLibraryWithSource(vshSource);
			if (NULL != lib)
			{
				m_screenshotBlitProgramVsh.m_function = lib.newFunctionWithName(SHADER_FUNCTION_NAME);
			}
			lib = m_device.newLibraryWithSource(fshSource);
			if (NULL != lib)
			{
				m_screenshotBlitProgramFsh.m_function = lib.newFunctionWithName(SHADER_FUNCTION_NAME);
			}
			m_screenshotBlitProgram.create(&m_screenshotBlitProgramVsh, &m_screenshotBlitProgramFsh);

			reset(m_renderPipelineDescriptor);
			m_renderPipelineDescriptor.colorAttachments[0].pixelFormat = m_metalLayer.pixelFormat;
			m_renderPipelineDescriptor.vertexFunction = m_screenshotBlitProgram.m_vsh->m_function;
			m_renderPipelineDescriptor.fragmentFunction = m_screenshotBlitProgram.m_fsh->m_function;
			m_screenshotBlitRenderPipelineState = m_device.newRenderPipelineStateWithDescriptor(m_renderPipelineDescriptor);

			g_caps.supported |= (0
				| BGFX_CAPS_TEXTURE_COMPARE_LEQUAL	//NOTE: on IOS Gpu Family 1/2 have to set compare in shader
				| BGFX_CAPS_TEXTURE_COMPARE_ALL
				| BGFX_CAPS_TEXTURE_3D
				| BGFX_CAPS_VERTEX_ATTRIB_HALF
				| BGFX_CAPS_VERTEX_ATTRIB_UINT10
				| BGFX_CAPS_INSTANCING
				| BGFX_CAPS_FRAGMENT_DEPTH
				| BGFX_CAPS_BLEND_INDEPENDENT
//				| BGFX_CAPS_COMPUTE // TODO: api/hw supports it but metal compute shaders are not yet supported
				| BGFX_CAPS_INDEX32
//				| BGFX_CAPS_DRAW_INDIRECT // TODO: support on iOS9+gpu family3+ and on macOS
				| BGFX_CAPS_TEXTURE_BLIT
				| BGFX_CAPS_TEXTURE_READ_BACK
				| BGFX_CAPS_OCCLUSION_QUERY
				| BGFX_CAPS_ALPHA_TO_COVERAGE
				| BGFX_CAPS_TEXTURE_2D_ARRAY // supported on all platforms
				);

			if (BX_ENABLED(BX_PLATFORM_IOS) )
			{
				if (iOSVersionEqualOrGreater("9.0.0") )
				{
					g_caps.limits.maxTextureSize = m_device.supportsFeatureSet((MTLFeatureSet)4 /* iOS_GPUFamily3_v1 */) ? 16384 : 8192;
				}
				else
				{
					g_caps.limits.maxTextureSize = 4096;
				}

				g_caps.limits.maxFBAttachments = uint8_t(bx::uint32_min(m_device.supportsFeatureSet((MTLFeatureSet)1 /* MTLFeatureSet_iOS_GPUFamily2_v1 */) ? 8 : 4, BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS));
			}
			else if (BX_ENABLED(BX_PLATFORM_OSX) )
			{
				g_caps.limits.maxTextureSize   = 16384;
				g_caps.limits.maxFBAttachments = 8;
				g_caps.supported |= BGFX_CAPS_TEXTURE_CUBE_ARRAY;
			}

			//todo: vendor id, device id, gpu enum

			m_hasPixelFormatDepth32Float_Stencil8 =	false
				||  BX_ENABLED(BX_PLATFORM_OSX)
				|| (BX_ENABLED(BX_PLATFORM_IOS) && iOSVersionEqualOrGreater("9.0.0") )
				;
			m_macOS11Runtime = true
				&& BX_ENABLED(BX_PLATFORM_OSX)
				&& macOSVersionEqualOrGreater(10,11,0)
				;
			m_iOS9Runtime = true
				&& BX_ENABLED(BX_PLATFORM_IOS)
				&& iOSVersionEqualOrGreater("9.0.0")
				;

			if (BX_ENABLED(BX_PLATFORM_OSX) )
			{
				s_textureFormat[TextureFormat::R8].m_fmtSrgb = MTLPixelFormatInvalid;
				s_textureFormat[TextureFormat::RG8].m_fmtSrgb = MTLPixelFormatInvalid;
			}

			for (uint32_t ii = 0; ii < TextureFormat::Count; ++ii)
			{
				uint16_t support = 0;

				support |= MTLPixelFormatInvalid != s_textureFormat[ii].m_fmt
					? BGFX_CAPS_FORMAT_TEXTURE_2D
					| BGFX_CAPS_FORMAT_TEXTURE_3D
					| BGFX_CAPS_FORMAT_TEXTURE_CUBE
					| BGFX_CAPS_FORMAT_TEXTURE_VERTEX
					: BGFX_CAPS_FORMAT_TEXTURE_NONE
					;

				support |= MTLPixelFormatInvalid != s_textureFormat[ii].m_fmtSrgb
					? BGFX_CAPS_FORMAT_TEXTURE_2D_SRGB
					| BGFX_CAPS_FORMAT_TEXTURE_3D_SRGB
					| BGFX_CAPS_FORMAT_TEXTURE_CUBE_SRGB
					| BGFX_CAPS_FORMAT_TEXTURE_VERTEX
					: BGFX_CAPS_FORMAT_TEXTURE_NONE
					;

				if (!isCompressed((TextureFormat::Enum)(ii)))
				{
					support |= BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER
						| BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA;
				}

					//TODO: additional caps flags
//				support |= BGFX_CAPS_FORMAT_TEXTURE_IMAGE : BGFX_CAPS_FORMAT_TEXTURE_NONE;

				g_caps.formats[ii] = support;
			}

			g_caps.formats[TextureFormat::A8] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER | BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
			g_caps.formats[TextureFormat::RG32I] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
			g_caps.formats[TextureFormat::RG32U] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
			g_caps.formats[TextureFormat::RGBA32I] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
			g_caps.formats[TextureFormat::RGBA32U] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);


			if (BX_ENABLED(BX_PLATFORM_IOS) )
			{
				s_textureFormat[TextureFormat::D24S8].m_fmt = MTLPixelFormatDepth32Float;

				g_caps.formats[TextureFormat::BC1] =
				g_caps.formats[TextureFormat::BC2] =
				g_caps.formats[TextureFormat::BC3] =
				g_caps.formats[TextureFormat::BC4] =
				g_caps.formats[TextureFormat::BC5] =
				g_caps.formats[TextureFormat::BC6H] =
				g_caps.formats[TextureFormat::BC7] = BGFX_CAPS_FORMAT_TEXTURE_NONE;

				g_caps.formats[TextureFormat::RG32F] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
				g_caps.formats[TextureFormat::RGBA32F] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
			}

			if (BX_ENABLED(BX_PLATFORM_OSX) )
			{
				s_textureFormat[TextureFormat::D24S8].m_fmt = (MTLPixelFormat)(m_device.depth24Stencil8PixelFormatSupported() ?
									255 /* Depth24Unorm_Stencil8 */ :
									MTLPixelFormatDepth32Float_Stencil8);

				g_caps.formats[TextureFormat::ETC2  ] =
				g_caps.formats[TextureFormat::ETC2A ] =
				g_caps.formats[TextureFormat::ETC2A1] =
				g_caps.formats[TextureFormat::PTC12 ] =
				g_caps.formats[TextureFormat::PTC14 ] =
				g_caps.formats[TextureFormat::PTC12A] =
				g_caps.formats[TextureFormat::PTC14A] =
				g_caps.formats[TextureFormat::R5G6B5] =
				g_caps.formats[TextureFormat::RGBA4 ] =
				g_caps.formats[TextureFormat::RGB5A1] = BGFX_CAPS_FORMAT_TEXTURE_NONE;

				g_caps.formats[TextureFormat::RGB9E5F] &= ~(BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER | BGFX_CAPS_FORMAT_TEXTURE_FRAMEBUFFER_MSAA);
			}

			for (uint32_t ii = 0; ii < TextureFormat::Count; ++ii)
			{
				if (BGFX_CAPS_FORMAT_TEXTURE_NONE == g_caps.formats[ii])
				{
					s_textureFormat[ii].m_fmt     = MTLPixelFormatInvalid;
					s_textureFormat[ii].m_fmtSrgb = MTLPixelFormatInvalid;
				}
			}

			for(uint32_t ii=1; ii<5; ++ii)
			{
				if (!m_device.supportsTextureSampleCount(s_msaa[ii]))
				{
					s_msaa[ii] = s_msaa[ii-1];
				}
			}


			// Init reserved part of view name.
			for (uint32_t ii = 0; ii < BGFX_CONFIG_MAX_VIEWS; ++ii)
			{
				bx::snprintf(s_viewName[ii], BGFX_CONFIG_MAX_VIEW_NAME_RESERVED+1, "%3d   ", ii);
			}

			m_occlusionQuery.preReset();
			m_gpuTimer.init();

			g_internalData.context = m_device;
			return true;
		}

		void shutdown()
		{
			m_occlusionQuery.postReset();
			m_gpuTimer.shutdown();

			for (uint32_t ii = 0; ii < BX_COUNTOF(m_shaders); ++ii)
			{
				m_shaders[ii].destroy();
			}

			for (uint32_t ii = 0; ii < BX_COUNTOF(m_textures); ++ii)
			{
				m_textures[ii].destroy();
			}

			captureFinish();

			MTL_RELEASE(m_depthStencilDescriptor);
			MTL_RELEASE(m_frontFaceStencilDescriptor);
			MTL_RELEASE(m_backFaceStencilDescriptor);
			MTL_RELEASE(m_renderPipelineDescriptor);
			MTL_RELEASE(m_vertexDescriptor);
			MTL_RELEASE(m_textureDescriptor);
			MTL_RELEASE(m_samplerDescriptor);

			MTL_RELEASE(m_backBufferDepth);
			if (BX_ENABLED(BX_PLATFORM_IOS) )
			{
				MTL_RELEASE(m_backBufferStencil);
			}

			for (uint8_t i=0; i < MTL_MAX_FRAMES_IN_FLIGHT; ++i)
			{
				MTL_RELEASE(m_uniformBuffers[i]);
			}
			MTL_RELEASE(m_commandQueue);
			MTL_RELEASE(m_device);
		}

		RendererType::Enum getRendererType() const BX_OVERRIDE
		{
			return RendererType::Metal;
		}

		const char* getRendererName() const BX_OVERRIDE
		{
			return BGFX_RENDERER_METAL_NAME;
		}

		void createIndexBuffer(IndexBufferHandle _handle, Memory* _mem, uint16_t _flags) BX_OVERRIDE
		{
			m_indexBuffers[_handle.idx].create(_mem->size, _mem->data, _flags);
		}

		void destroyIndexBuffer(IndexBufferHandle _handle) BX_OVERRIDE
		{
			m_indexBuffers[_handle.idx].destroy();
		}

		void createVertexDecl(VertexDeclHandle _handle, const VertexDecl& _decl) BX_OVERRIDE
		{
			VertexDecl& decl = m_vertexDecls[_handle.idx];
			memcpy(&decl, &_decl, sizeof(VertexDecl) );
			dump(decl);
		}

		void destroyVertexDecl(VertexDeclHandle /*_handle*/) BX_OVERRIDE
		{
		}

		void createVertexBuffer(VertexBufferHandle _handle, Memory* _mem, VertexDeclHandle _declHandle, uint16_t _flags) BX_OVERRIDE
		{
			m_vertexBuffers[_handle.idx].create(_mem->size, _mem->data, _declHandle, _flags);
		}

		void destroyVertexBuffer(VertexBufferHandle _handle) BX_OVERRIDE
		{
			m_vertexBuffers[_handle.idx].destroy();
		}

		void createDynamicIndexBuffer(IndexBufferHandle _handle, uint32_t _size, uint16_t _flags) BX_OVERRIDE
		{
			m_indexBuffers[_handle.idx].create(_size, NULL, _flags);
		}

		void updateDynamicIndexBuffer(IndexBufferHandle _handle, uint32_t _offset, uint32_t _size, Memory* _mem) BX_OVERRIDE
		{
			m_indexBuffers[_handle.idx].update(_offset, bx::uint32_min(_size, _mem->size), _mem->data);
		}

		void destroyDynamicIndexBuffer(IndexBufferHandle _handle) BX_OVERRIDE
		{
			m_indexBuffers[_handle.idx].destroy();
		}

		void createDynamicVertexBuffer(VertexBufferHandle _handle, uint32_t _size, uint16_t _flags) BX_OVERRIDE
		{
			VertexDeclHandle decl = BGFX_INVALID_HANDLE;
			m_vertexBuffers[_handle.idx].create(_size, NULL, decl, _flags);
		}

		void updateDynamicVertexBuffer(VertexBufferHandle _handle, uint32_t _offset, uint32_t _size, Memory* _mem) BX_OVERRIDE
		{
			m_vertexBuffers[_handle.idx].update(_offset, bx::uint32_min(_size, _mem->size), _mem->data);
		}

		void destroyDynamicVertexBuffer(VertexBufferHandle _handle) BX_OVERRIDE
		{
			m_vertexBuffers[_handle.idx].destroy();
		}

		void createShader(ShaderHandle _handle, Memory* _mem) BX_OVERRIDE
		{
			m_shaders[_handle.idx].create(_mem);
		}

		void destroyShader(ShaderHandle _handle) BX_OVERRIDE
		{
			m_shaders[_handle.idx].destroy();
		}

		void createProgram(ProgramHandle _handle, ShaderHandle _vsh, ShaderHandle _fsh) BX_OVERRIDE
		{
			m_program[_handle.idx].create(&m_shaders[_vsh.idx], &m_shaders[_fsh.idx]);
		}

		void destroyProgram(ProgramHandle _handle) BX_OVERRIDE
		{
			m_program[_handle.idx].destroy();
		}

		void createTexture(TextureHandle _handle, Memory* _mem, uint32_t _flags, uint8_t _skip) BX_OVERRIDE
		{
			m_textures[_handle.idx].create(_mem, _flags, _skip);
		}

		void updateTextureBegin(TextureHandle /*_handle*/, uint8_t /*_side*/, uint8_t /*_mip*/) BX_OVERRIDE
		{
		}

		void updateTexture(TextureHandle _handle, uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, uint16_t _pitch, const Memory* _mem) BX_OVERRIDE
		{
			m_textures[_handle.idx].update(_side, _mip, _rect, _z, _depth, _pitch, _mem);
		}

		void updateTextureEnd() BX_OVERRIDE
		{
		}

		void readTexture(TextureHandle _handle, void* _data, uint8_t _mip) BX_OVERRIDE
		{
			m_commandBuffer.commit();
			m_commandBuffer.waitUntilCompleted();
			MTL_RELEASE(m_commandBuffer)

			const TextureMtl& texture = m_textures[_handle.idx];

			BX_CHECK(_mip<texture.m_numMips,"Invalid mip: %d num mips:",_mip,texture.m_numMips);

			uint32_t srcWidth  = bx::uint32_max(1, texture.m_ptr.width()  >> _mip);
			uint32_t srcHeight = bx::uint32_max(1, texture.m_ptr.height() >> _mip);
			const uint8_t bpp = getBitsPerPixel(TextureFormat::Enum(texture.m_textureFormat) );

			MTLRegion region = { { 0, 0, 0 }, { srcWidth, srcHeight, 1 } };

			texture.m_ptr.getBytes(_data, srcWidth*bpp/8, 0, region, _mip, 0);

			m_commandBuffer = m_commandQueue.commandBuffer();
			retain(m_commandBuffer); //NOTE: keep alive to be useable at 'flip'
		}

		void resizeTexture(TextureHandle _handle, uint16_t _width, uint16_t _height, uint8_t _numMips) BX_OVERRIDE
		{
			TextureMtl& texture = m_textures[_handle.idx];

			uint32_t size = sizeof(uint32_t) + sizeof(TextureCreate);
			const Memory* mem = alloc(size);

			bx::StaticMemoryBlockWriter writer(mem->data, mem->size);
			uint32_t magic = BGFX_CHUNK_MAGIC_TEX;
			bx::write(&writer, magic);

			TextureCreate tc;
			tc.m_width     = _width;
			tc.m_height    = _height;
			tc.m_depth     = 0;
			tc.m_numLayers = 1;
			tc.m_numMips   = _numMips;
			tc.m_format    = TextureFormat::Enum(texture.m_requestedFormat);
			tc.m_cubeMap   = false;
			tc.m_mem       = NULL;
			bx::write(&writer, tc);

			texture.destroy();
			texture.create(mem, texture.m_flags, 0);

			release(mem);
		}

		void overrideInternal(TextureHandle _handle, uintptr_t _ptr) BX_OVERRIDE
		{
			BX_UNUSED(_handle, _ptr);
		}

		uintptr_t getInternal(TextureHandle _handle) BX_OVERRIDE
		{
			BX_UNUSED(_handle);
			return 0;
		}

		void destroyTexture(TextureHandle _handle) BX_OVERRIDE
		{
			m_textures[_handle.idx].destroy();
		}

		void createFrameBuffer(FrameBufferHandle _handle, uint8_t _num, const Attachment* _attachment) BX_OVERRIDE
		{
			m_frameBuffers[_handle.idx].create(_num, _attachment);
		}

		void createFrameBuffer(FrameBufferHandle _handle, void* _nwh, uint32_t _width, uint32_t _height, TextureFormat::Enum _depthFormat) BX_OVERRIDE
		{
			uint16_t denseIdx = m_numWindows++;
			m_windows[denseIdx] = _handle;
			m_frameBuffers[_handle.idx].create(denseIdx, _nwh, _width, _height, _depthFormat);
		}

		void destroyFrameBuffer(FrameBufferHandle _handle) BX_OVERRIDE
		{
			uint16_t denseIdx = m_frameBuffers[_handle.idx].destroy();
			if (UINT16_MAX != denseIdx)
			{
				--m_numWindows;
				if (m_numWindows > 1)
				{
					FrameBufferHandle handle = m_windows[m_numWindows];
					m_windows[denseIdx] = handle;
					m_frameBuffers[handle.idx].m_denseIdx = denseIdx;
				}
			}
		}

		void createUniform(UniformHandle _handle, UniformType::Enum _type, uint16_t _num, const char* _name) BX_OVERRIDE
		{
			if (NULL != m_uniforms[_handle.idx])
			{
				BX_FREE(g_allocator, m_uniforms[_handle.idx]);
			}

			uint32_t size = BX_ALIGN_16(g_uniformTypeSize[_type]*_num);
			void* data = BX_ALLOC(g_allocator, size);
			memset(data, 0, size);
			m_uniforms[_handle.idx] = data;
			m_uniformReg.add(_handle, _name, data);
		}

		void destroyUniform(UniformHandle _handle) BX_OVERRIDE
		{
			BX_FREE(g_allocator, m_uniforms[_handle.idx]);
			m_uniforms[_handle.idx] = NULL;
			m_uniformReg.remove(_handle);
		}

		//cmdPre
		void saveScreenShotPre(const char* _filePath) BX_OVERRIDE
		{
			BX_UNUSED(_filePath);
			m_saveScreenshot = true;
		}

		//cmdPost
		void saveScreenShot(const char* _filePath) BX_OVERRIDE
		{
			if (NULL == m_screenshotTarget)
				return;

			m_commandBuffer.commit();
			m_commandBuffer.waitUntilCompleted();
			MTL_RELEASE(m_commandBuffer)

			uint32_t width  = m_screenshotTarget.width();
			uint32_t height = m_screenshotTarget.height();
			uint32_t length = width*height*4;
			uint8_t* data = (uint8_t*)BX_ALLOC(g_allocator, length);

			MTLRegion region = { { 0, 0, 0 }, { width, height, 1 } };

			m_screenshotTarget.getBytes(data, 4*width, 0, region, 0, 0);

			g_callback->screenShot(_filePath
					, m_screenshotTarget.width()
					, m_screenshotTarget.height()
					, width*4
					, data
					, length
					, false
					);

			BX_FREE(g_allocator, data);

			m_commandBuffer = m_commandQueue.commandBuffer();
			retain(m_commandBuffer); //NOTE: keep alive to be useable at 'flip'
		}

		void updateViewName(uint8_t _id, const char* _name) BX_OVERRIDE
		{
			if (BX_ENABLED(BGFX_CONFIG_DEBUG_PIX) )
			{
				bx::strlcpy(&s_viewName[_id][BGFX_CONFIG_MAX_VIEW_NAME_RESERVED]
						, _name
						, BX_COUNTOF(s_viewName[0])-BGFX_CONFIG_MAX_VIEW_NAME_RESERVED
						);
			}
		}

		void updateUniform(uint16_t _loc, const void* _data, uint32_t _size) BX_OVERRIDE
		{
			memcpy(m_uniforms[_loc], _data, _size);
		}

		void setMarker(const char* _marker, uint32_t /*_size*/) BX_OVERRIDE
		{
			if (BX_ENABLED(BGFX_CONFIG_DEBUG_MTL) )
			{
				m_renderCommandEncoder.insertDebugSignpost(_marker);
			}
		}

		void submit(Frame* _render, ClearQuad& _clearQuad, TextVideoMemBlitter& _textVideoMemBlitter) BX_OVERRIDE;

		void blitSetup(TextVideoMemBlitter& _blitter) BX_OVERRIDE
		{
			RenderCommandEncoder rce = m_renderCommandEncoder;

			uint32_t width  = m_resolution.m_width;
			uint32_t height  = m_resolution.m_height;

			//if (m_ovr.isEnabled() )
			//{
			//	m_ovr.getSize(width, height);
			//}

			FrameBufferHandle fbh = BGFX_INVALID_HANDLE;

			if ( NULL == rce || m_renderCommandEncoderFrameBufferHandle.idx != invalidHandle )
			{
				if ( m_renderCommandEncoder )
					m_renderCommandEncoder.endEncoding();

				RenderPassDescriptor renderPassDescriptor = newRenderPassDescriptor();

				setFrameBuffer(renderPassDescriptor, fbh);

				renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;
				renderPassDescriptor.colorAttachments[0].storeAction = NULL != renderPassDescriptor.colorAttachments[0].resolveTexture ?
					MTLStoreActionMultisampleResolve : MTLStoreActionStore;

				rce = m_commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor);
				m_renderCommandEncoder = rce;
				m_renderCommandEncoderFrameBufferHandle = fbh;
				MTL_RELEASE(renderPassDescriptor);
			}

			MTLViewport viewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f};
			rce.setViewport(viewport);
			MTLScissorRect rc = { 0,0,width,height };
			rce.setScissorRect(rc);
			rce.setCullMode(MTLCullModeNone);

			uint64_t state = 0
				| BGFX_STATE_RGB_WRITE
				| BGFX_STATE_ALPHA_WRITE
				| BGFX_STATE_DEPTH_TEST_ALWAYS
				;

			setDepthStencilState(state);

			ProgramMtl& program = m_program[_blitter.m_program.idx];
			RenderPipelineState pipelineState = program.getRenderPipelineState(state, 0, fbh, _blitter.m_vb->decl, 0);
			rce.setRenderPipelineState(pipelineState);

			uint32_t vertexUniformBufferSize = program.m_vshConstantBufferSize;
			uint32_t fragmentUniformBufferSize = program.m_fshConstantBufferSize;

			if (vertexUniformBufferSize )
			{
				m_uniformBufferVertexOffset = BX_ALIGN_MASK(m_uniformBufferVertexOffset, program.m_vshConstantBufferAlignmentMask);
				rce.setVertexBuffer(m_uniformBuffer, m_uniformBufferVertexOffset, 0);
			}

			m_uniformBufferFragmentOffset = m_uniformBufferVertexOffset + vertexUniformBufferSize;
			if (fragmentUniformBufferSize )
			{
				m_uniformBufferFragmentOffset = BX_ALIGN_MASK(m_uniformBufferFragmentOffset, program.m_fshConstantBufferAlignmentMask);
				rce.setFragmentBuffer(m_uniformBuffer, m_uniformBufferFragmentOffset, 0);
			}

			float proj[16];
			bx::mtxOrtho(proj, 0.0f, (float)width, (float)height, 0.0f, 0.0f, 1000.0f);

			PredefinedUniform& predefined = program.m_predefined[0];
			uint8_t flags = predefined.m_type;
			setShaderUniform(flags, predefined.m_loc, proj, 4);

			m_textures[_blitter.m_texture.idx].commit(0, false, true);
		}

		void blitRender(TextVideoMemBlitter& _blitter, uint32_t _numIndices) BX_OVERRIDE
		{
			const uint32_t numVertices = _numIndices*4/6;
			if (0 < numVertices)
			{
				m_indexBuffers [_blitter.m_ib->handle.idx].update(0, _numIndices*2, _blitter.m_ib->data, true);
				m_vertexBuffers[_blitter.m_vb->handle.idx].update(0, numVertices*_blitter.m_decl.m_stride, _blitter.m_vb->data, true);

				VertexBufferMtl& vb = m_vertexBuffers[_blitter.m_vb->handle.idx];
				m_renderCommandEncoder.setVertexBuffer(vb.getBuffer(), 0, 1);

				m_renderCommandEncoder.drawIndexedPrimitives(MTLPrimitiveTypeTriangle, _numIndices, MTLIndexTypeUInt16, m_indexBuffers[_blitter.m_ib->handle.idx].getBuffer(), 0, 1);
			}
		}

		static void commandBufferFinishedCallback(void* _data)
		{
			RendererContextMtl* renderer = (RendererContextMtl*)_data;
			if ( renderer )
				renderer->m_framesSemaphore.post();
		}

		void flip(HMD& /*_hmd*/) BX_OVERRIDE
		{
			if (NULL == m_commandBuffer)
			{
				return;
			}

			// Present and commit the command buffer
			if ( NULL != m_drawable)
			{
				m_commandBuffer.presentDrawable(m_drawable);
				MTL_RELEASE(m_drawable);
			}

			m_commandBuffer.addCompletedHandler(commandBufferFinishedCallback, this);

			m_commandBuffer.commit();

			MTL_RELEASE(m_prevCommandBuffer);
			m_prevCommandBuffer = m_commandBuffer;
			retain(m_prevCommandBuffer);

			MTL_RELEASE(m_commandBuffer);

			//TODO: support multiple windows on OSX
			/*
			   if (m_flip)
			   {
			   for (uint32_t ii = 1, num = m_numWindows; ii < num; ++ii)
			   {
			   m_glctx.swap(m_frameBuffers[m_windows[ii].idx].m_swapChain);
			   }

			   if (!m_ovr.swap(_hmd) )
			   {
			   m_glctx.swap();
			   }
			   }
			 */
		}

		void updateResolution(const Resolution& _resolution)
		{
			m_maxAnisotropy = !!(_resolution.m_flags & BGFX_RESET_MAXANISOTROPY)
				? 16
				: 1
				;

			//TODO: there should be a way to specify if backbuffer needs stencil/depth.
			const uint32_t maskFlags = ~(0
										 | BGFX_RESET_HMD_RECENTER
										 | BGFX_RESET_MAXANISOTROPY
										 | BGFX_RESET_DEPTH_CLAMP
										 | BGFX_RESET_SUSPEND
										 );

			if (m_resolution.m_width				!=  _resolution.m_width
				||  m_resolution.m_height           !=  _resolution.m_height
				|| (m_resolution.m_flags&maskFlags) != (_resolution.m_flags&maskFlags) )
			{
				int sampleCount = s_msaa[(_resolution.m_flags&BGFX_RESET_MSAA_MASK)>>BGFX_RESET_MSAA_SHIFT];

				MTLPixelFormat prevMetalLayerPixelFormat = m_metalLayer.pixelFormat;

				m_metalLayer.drawableSize = CGSizeMake(_resolution.m_width, _resolution.m_height);
				m_metalLayer.pixelFormat = (m_resolution.m_flags & BGFX_RESET_SRGB_BACKBUFFER)
								? MTLPixelFormatBGRA8Unorm_sRGB
								: MTLPixelFormatBGRA8Unorm
								;

				m_resolution = _resolution;
				m_resolution.m_flags &= ~BGFX_RESET_INTERNAL_FORCE;

				m_textureDescriptor.textureType = sampleCount > 1 ? MTLTextureType2DMultisample : MTLTextureType2D;

				if (m_hasPixelFormatDepth32Float_Stencil8)
					m_textureDescriptor.pixelFormat = MTLPixelFormatDepth32Float_Stencil8;
				else
					m_textureDescriptor.pixelFormat = MTLPixelFormatDepth32Float;

				m_textureDescriptor.width  = _resolution.m_width;
				m_textureDescriptor.height = _resolution.m_height;
				m_textureDescriptor.depth  = 1;
				m_textureDescriptor.mipmapLevelCount = 1;
				m_textureDescriptor.sampleCount = sampleCount;
				m_textureDescriptor.arrayLength = 1;
				if ( m_iOS9Runtime || m_macOS11Runtime )
				{
					m_textureDescriptor.cpuCacheMode = MTLCPUCacheModeDefaultCache;
					m_textureDescriptor.storageMode  = MTLStorageModePrivate;
					m_textureDescriptor.usage		 = MTLTextureUsageRenderTarget;
				}

				if (NULL != m_backBufferDepth)
				{
					release(m_backBufferDepth);
				}
				m_backBufferDepth   = m_device.newTextureWithDescriptor(m_textureDescriptor);


				if (m_hasPixelFormatDepth32Float_Stencil8)
					m_backBufferStencil = m_backBufferDepth;
				else
				{
					m_textureDescriptor.pixelFormat = MTLPixelFormatStencil8;
					m_backBufferStencil   = m_device.newTextureWithDescriptor(m_textureDescriptor);
				}

				if ( sampleCount > 1 )
				{
					if (NULL != m_backBufferColorMSAA)
					{
						release(m_backBufferColorMSAA);
					}
					m_textureDescriptor.pixelFormat = m_metalLayer.pixelFormat;
					m_backBufferColorMSAA   = m_device.newTextureWithDescriptor(m_textureDescriptor);
				}

				bx::HashMurmur2A murmur;
				murmur.begin();
				murmur.add(1);
				murmur.add((uint32_t)m_metalLayer.pixelFormat);
				murmur.add((uint32_t)m_backBufferDepth.pixelFormat());
				murmur.add((uint32_t)m_backBufferStencil.pixelFormat());
				murmur.add((uint32_t)sampleCount);
				m_backBufferPixelFormatHash = murmur.end();

				for (uint32_t ii = 0; ii < BX_COUNTOF(m_frameBuffers); ++ii)
				{
					m_frameBuffers[ii].postReset();
				}

				updateCapture();

				m_textVideoMem.resize(false, _resolution.m_width, _resolution.m_height);
				m_textVideoMem.clear();

				if ( prevMetalLayerPixelFormat != m_metalLayer.pixelFormat)
				{
					MTL_RELEASE(m_screenshotBlitRenderPipelineState)
					reset(m_renderPipelineDescriptor);
					m_renderPipelineDescriptor.colorAttachments[0].pixelFormat = m_metalLayer.pixelFormat;
					m_renderPipelineDescriptor.vertexFunction = m_screenshotBlitProgram.m_vsh->m_function;
					m_renderPipelineDescriptor.fragmentFunction = m_screenshotBlitProgram.m_fsh->m_function;
					m_screenshotBlitRenderPipelineState = m_device.newRenderPipelineStateWithDescriptor(m_renderPipelineDescriptor);
				}
			}
		}


		void updateCapture()
		{
			if (m_resolution.m_flags&BGFX_RESET_CAPTURE)
			{
				m_captureSize = m_resolution.m_width*m_resolution.m_height*4;
				m_capture = BX_REALLOC(g_allocator, m_capture, m_captureSize);
				g_callback->captureBegin(m_resolution.m_width, m_resolution.m_height, m_resolution.m_width*4, TextureFormat::BGRA8, false);
			}
			else
			{
				captureFinish();
			}
		}

		void capture()
		{
			if (NULL != m_capture)
			{
				if (NULL == m_screenshotTarget)
					return;

				m_renderCommandEncoder.endEncoding();

				m_commandBuffer.commit();
				m_commandBuffer.waitUntilCompleted();
				MTL_RELEASE(m_commandBuffer)

				MTLRegion region = { { 0, 0, 0 }, { m_resolution.m_width, m_resolution.m_height, 1 } };

				//TODO: enable screenshot target when capturing
				m_screenshotTarget.getBytes(m_capture, 4*m_resolution.m_width, 0, region, 0, 0);

				m_commandBuffer = m_commandQueue.commandBuffer();
				retain(m_commandBuffer); //NOTE: keep alive to be useable at 'flip'

				if (m_screenshotTarget.pixelFormat() == MTLPixelFormatRGBA8Uint)
				{
					imageSwizzleBgra8(m_resolution.m_width, m_resolution.m_height, m_resolution.m_width*4, m_capture, m_capture);
				}

				g_callback->captureFrame(m_capture, m_captureSize);

				RenderPassDescriptor renderPassDescriptor = newRenderPassDescriptor();
				setFrameBuffer(renderPassDescriptor, m_renderCommandEncoderFrameBufferHandle);

				for(uint32_t ii = 0; ii < g_caps.limits.maxFBAttachments; ++ii)
				{
					MTLRenderPassColorAttachmentDescriptor* desc = renderPassDescriptor.colorAttachments[ii];
					if ( desc.texture != NULL)
						desc.loadAction = MTLLoadActionLoad;
				}

				RenderPassDepthAttachmentDescriptor depthAttachment = renderPassDescriptor.depthAttachment;
				if (NULL != depthAttachment.texture)
				{
					depthAttachment.loadAction = MTLLoadActionLoad;
					depthAttachment.storeAction = MTLStoreActionStore;
				}

				RenderPassStencilAttachmentDescriptor stencilAttachment = renderPassDescriptor.stencilAttachment;
				if (NULL != stencilAttachment.texture)
				{
					stencilAttachment.loadAction   = MTLLoadActionLoad;
					stencilAttachment.storeAction  = MTLStoreActionStore;
				}

				m_renderCommandEncoder = m_commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor);
				MTL_RELEASE(renderPassDescriptor);
			}
		}

		void captureFinish()
		{
			if (NULL != m_capture)
			{
				g_callback->captureEnd();
				BX_FREE(g_allocator, m_capture);
				m_capture = NULL;
				m_captureSize = 0;
			}
		}


		void setShaderUniform(uint8_t _flags, uint32_t _loc, const void* _val, uint32_t _numRegs)
		{
			uint32_t offset = 0 != (_flags&BGFX_UNIFORM_FRAGMENTBIT)
				? m_uniformBufferFragmentOffset
				: m_uniformBufferVertexOffset
				;
			uint8_t* dst = (uint8_t*)m_uniformBuffer.contents();
			memcpy(&dst[offset + _loc], _val, _numRegs*16);
		}

		void setShaderUniform4f(uint8_t _flags, uint32_t _loc, const void* _val, uint32_t _numRegs)
		{
			setShaderUniform(_flags, _loc, _val, _numRegs);
		}

		void setShaderUniform4x4f(uint8_t _flags, uint32_t _loc, const void* _val, uint32_t _numRegs)
		{
			setShaderUniform(_flags, _loc, _val, _numRegs);
		}

		void commit(UniformBuffer& _uniformBuffer)
		{
			_uniformBuffer.reset();

			for (;;)
			{
				uint32_t opcode = _uniformBuffer.read();

				if (UniformType::End == opcode)
				{
					break;
				}

				UniformType::Enum type;
				uint16_t loc;
				uint16_t num;
				uint16_t copy;
				UniformBuffer::decodeOpcode(opcode, type, loc, num, copy);

				const char* data;
				if (copy)
				{
					data = _uniformBuffer.read(g_uniformTypeSize[type]*num);
				}
				else
				{
					UniformHandle handle;
					memcpy(&handle, _uniformBuffer.read(sizeof(UniformHandle) ), sizeof(UniformHandle) );
					data = (const char*)m_uniforms[handle.idx];
				}

#define CASE_IMPLEMENT_UNIFORM(_uniform, _dxsuffix, _type) \
				case UniformType::_uniform: \
				case UniformType::_uniform|BGFX_UNIFORM_FRAGMENTBIT: \
				{ \
					setShaderUniform(uint8_t(type), loc, data, num); \
				} \
				break;

				switch ( (uint32_t)type)
				{
				case UniformType::Mat3:
				case UniformType::Mat3|BGFX_UNIFORM_FRAGMENTBIT:
					{
						float* value = (float*)data;
						for (uint32_t ii = 0, count = num/3; ii < count; ++ii,  loc += 3*16, value += 9)
						{
							Matrix4 mtx;
							mtx.un.val[ 0] = value[0];
							mtx.un.val[ 1] = value[1];
							mtx.un.val[ 2] = value[2];
							mtx.un.val[ 3] = 0.0f;
							mtx.un.val[ 4] = value[3];
							mtx.un.val[ 5] = value[4];
							mtx.un.val[ 6] = value[5];
							mtx.un.val[ 7] = 0.0f;
							mtx.un.val[ 8] = value[6];
							mtx.un.val[ 9] = value[7];
							mtx.un.val[10] = value[8];
							mtx.un.val[11] = 0.0f;
							setShaderUniform(uint8_t(type), loc, &mtx.un.val[0], 3);
						}
					}
					break;

					CASE_IMPLEMENT_UNIFORM(Int1,    I, int);
					CASE_IMPLEMENT_UNIFORM(Vec4,   F, float);
					CASE_IMPLEMENT_UNIFORM(Mat4, F, float);

				case UniformType::End:
					break;

				default:
					BX_TRACE("%4d: INVALID 0x%08x, t %d, l %d, n %d, c %d", _uniformBuffer.getPos(), opcode, type, loc, num, copy);
					break;
				}

#undef CASE_IMPLEMENT_UNIFORM

			}
		}

		void clearQuad(ClearQuad& _clearQuad, const Rect& /*_rect*/, const Clear& _clear, const float _palette[][4])
		{
			uint32_t width;
			uint32_t height;

			if (isValid(m_fbh) )
			{
				const FrameBufferMtl& fb = m_frameBuffers[m_fbh.idx];
				width  = fb.m_width;
				height = fb.m_height;
			}
			else
			{
				width  = m_resolution.m_width;
				height = m_resolution.m_height;
			}


			uint64_t state = 0;
			state |= _clear.m_flags & BGFX_CLEAR_COLOR ? BGFX_STATE_RGB_WRITE|BGFX_STATE_ALPHA_WRITE : 0;
			state |= _clear.m_flags & BGFX_CLEAR_DEPTH ? BGFX_STATE_DEPTH_TEST_ALWAYS|BGFX_STATE_DEPTH_WRITE : 0;

			uint64_t stencil = 0;
			stencil |= _clear.m_flags & BGFX_CLEAR_STENCIL ? 0
			| BGFX_STENCIL_TEST_ALWAYS
			| BGFX_STENCIL_FUNC_REF(_clear.m_stencil)
			| BGFX_STENCIL_FUNC_RMASK(0xff)
			| BGFX_STENCIL_OP_FAIL_S_REPLACE
			| BGFX_STENCIL_OP_FAIL_Z_REPLACE
			| BGFX_STENCIL_OP_PASS_Z_REPLACE
			: 0
			;

			setDepthStencilState(state, stencil);

			uint32_t numMrt = 1;
			FrameBufferHandle fbh = m_fbh;
			if (isValid(fbh) )
			{
				const FrameBufferMtl& fb = m_frameBuffers[fbh.idx];
				numMrt = bx::uint32_max(1, fb.m_num);
			}

			ProgramMtl& program = m_program[_clearQuad.m_program[numMrt-1].idx];
			m_renderCommandEncoder.setRenderPipelineState(program.getRenderPipelineState(state, 0, fbh, _clearQuad.m_vb->decl, 0));

			uint32_t fragmentUniformBufferSize = program.m_fshConstantBufferSize;

			m_uniformBufferFragmentOffset = m_uniformBufferVertexOffset;
			if (fragmentUniformBufferSize)
			{
				m_uniformBufferFragmentOffset = BX_ALIGN_MASK(m_uniformBufferFragmentOffset, program.m_fshConstantBufferAlignmentMask);
				m_renderCommandEncoder.setFragmentBuffer(m_uniformBuffer, m_uniformBufferFragmentOffset, 0);
			}

			if (BGFX_CLEAR_COLOR_USE_PALETTE & _clear.m_flags)
			{
				float mrtClear[BGFX_CONFIG_MAX_FRAME_BUFFER_ATTACHMENTS][4];
				for (uint32_t ii = 0; ii < numMrt; ++ii)
				{
					uint8_t index = (uint8_t)bx::uint32_min(BGFX_CONFIG_MAX_COLOR_PALETTE-1, _clear.m_index[ii]);
					memcpy(mrtClear[ii], _palette[index], 16);
				}

				memcpy((uint8_t*)m_uniformBuffer.contents() + m_uniformBufferFragmentOffset,
					   mrtClear,
					   bx::uint32_min(fragmentUniformBufferSize, sizeof(mrtClear)));
			}
			else
			{
				float rgba[4] =
				{
					_clear.m_index[0]*1.0f/255.0f,
					_clear.m_index[1]*1.0f/255.0f,
					_clear.m_index[2]*1.0f/255.0f,
					_clear.m_index[3]*1.0f/255.0f,
				};

				memcpy((uint8_t*)m_uniformBuffer.contents() + m_uniformBufferFragmentOffset,
					   rgba,
					   bx::uint32_min(fragmentUniformBufferSize, sizeof(rgba)));
			}

			m_uniformBufferFragmentOffset += fragmentUniformBufferSize;
			m_uniformBufferVertexOffset = m_uniformBufferFragmentOffset;

			const VertexBufferMtl& vb = m_vertexBuffers[_clearQuad.m_vb->handle.idx];
			const VertexDecl& vertexDecl = m_vertexDecls[_clearQuad.m_vb->decl.idx];
			const uint32_t stride = vertexDecl.m_stride;
			const uint32_t offset = 0;

			{
				struct Vertex
				{
					float m_x;
					float m_y;
					float m_z;
				};

				Vertex* vertex = (Vertex*)_clearQuad.m_vb->data;
				BX_CHECK(stride == sizeof(Vertex)
					, "Stride/Vertex mismatch (stride %d, sizeof(Vertex) %d)"
					, stride
					, sizeof(Vertex)
					);
				BX_UNUSED(stride);

				const float depth = _clear.m_depth;

				vertex->m_x = -1.0f;
				vertex->m_y = -1.0f;
				vertex->m_z = depth;
				vertex++;
				vertex->m_x =  1.0f;
				vertex->m_y = -1.0f;
				vertex->m_z = depth;
				vertex++;
				vertex->m_x = -1.0f;
				vertex->m_y =  1.0f;
				vertex->m_z = depth;
				vertex++;
				vertex->m_x =  1.0f;
				vertex->m_y =  1.0f;
				vertex->m_z = depth;
			}

			m_vertexBuffers[_clearQuad.m_vb->handle.idx].update(0, 4*_clearQuad.m_decl.m_stride, _clearQuad.m_vb->data);
			m_renderCommandEncoder.setCullMode(MTLCullModeNone);
			m_renderCommandEncoder.setVertexBuffer(vb.getBuffer(), offset, 1);
			m_renderCommandEncoder.drawPrimitives(MTLPrimitiveTypeTriangleStrip, 0, 4, 1);
		}

		void setFrameBuffer(RenderPassDescriptor renderPassDescriptor, FrameBufferHandle _fbh, bool _msaa = true)
		{
			if (!isValid(_fbh) )
			{
				if ( NULL != m_backBufferColorMSAA )
				{
					renderPassDescriptor.colorAttachments[0].texture = m_backBufferColorMSAA;
					renderPassDescriptor.colorAttachments[0].resolveTexture = ((NULL != m_screenshotTarget) ?
																			   m_screenshotTarget.m_obj :
																			   currentDrawable().texture);
				}
				else
				{
					renderPassDescriptor.colorAttachments[0].texture = ((NULL != m_screenshotTarget) ?
																		m_screenshotTarget.m_obj :
																		currentDrawable().texture);
				}
				renderPassDescriptor.depthAttachment.texture = m_backBufferDepth;
				renderPassDescriptor.stencilAttachment.texture = m_backBufferStencil;
			}
			else
			{
				FrameBufferMtl& frameBuffer = m_frameBuffers[_fbh.idx];

				for (uint32_t ii = 0; ii < frameBuffer.m_num; ++ii)
				{
					const TextureMtl& texture = m_textures[frameBuffer.m_colorHandle[ii].idx];
					renderPassDescriptor.colorAttachments[ii].texture = texture.m_ptrMSAA ? texture.m_ptrMSAA : texture.m_ptr;
					renderPassDescriptor.colorAttachments[ii].resolveTexture = texture.m_ptrMSAA ? texture.m_ptr.m_obj : NULL;
				}

				if (isValid(frameBuffer.m_depthHandle) )
				{
					const TextureMtl& texture = m_textures[frameBuffer.m_depthHandle.idx];
					renderPassDescriptor.depthAttachment.texture = texture.m_ptrMSAA ? texture.m_ptrMSAA : texture.m_ptr;
					renderPassDescriptor.stencilAttachment.texture = texture.m_ptrStencil;

					if ( texture.m_textureFormat == TextureFormat::D24S8)//TODO: msaa and stencil iOS8 hack
					{
						if ( texture.m_ptr.pixelFormat() == 255 /* Depth24Unorm_Stencil8 */||
							 texture.m_ptr.pixelFormat() == 260 /* Depth32Float_Stencil8 */ )
						{
							renderPassDescriptor.stencilAttachment.texture = renderPassDescriptor.depthAttachment.texture;
						}
						else
							renderPassDescriptor.stencilAttachment.texture = texture.m_ptrMSAA ? texture.m_ptrMSAA : texture.m_ptrStencil;
					}
				}
			}

			m_fbh    = _fbh;
			m_rtMsaa = _msaa;
		}

		void setDepthStencilState(uint64_t _state, uint64_t _stencil = 0)
		{
			_state &= BGFX_STATE_DEPTH_WRITE|BGFX_STATE_DEPTH_TEST_MASK;

			uint32_t fstencil = unpackStencil(0, _stencil);
			uint32_t ref      = (fstencil&BGFX_STENCIL_FUNC_REF_MASK)>>BGFX_STENCIL_FUNC_REF_SHIFT;

			_stencil &= packStencil(~BGFX_STENCIL_FUNC_REF_MASK, BGFX_STENCIL_MASK);

			bx::HashMurmur2A murmur;
			murmur.begin();
			murmur.add(_state);
			murmur.add(_stencil);
			uint32_t hash = murmur.end();

			DepthStencilState dss = m_depthStencilStateCache.find(hash);
			if (NULL == dss)
			{
				DepthStencilDescriptor desc = m_depthStencilDescriptor;
				uint32_t func = (_state&BGFX_STATE_DEPTH_TEST_MASK)>>BGFX_STATE_DEPTH_TEST_SHIFT;
				desc.depthWriteEnabled = !!(BGFX_STATE_DEPTH_WRITE & _state);
				desc.depthCompareFunction = s_cmpFunc[func];

				uint32_t bstencil = unpackStencil(1, _stencil);
				uint32_t frontAndBack = bstencil != BGFX_STENCIL_NONE && bstencil != fstencil;
				bstencil = frontAndBack ? bstencil : fstencil;

				if (0 != _stencil)
				{
					StencilDescriptor frontFaceDesc = m_frontFaceStencilDescriptor;
					StencilDescriptor backfaceDesc = m_backFaceStencilDescriptor;

					uint32_t readMask  = (fstencil&BGFX_STENCIL_FUNC_RMASK_MASK)>>BGFX_STENCIL_FUNC_RMASK_SHIFT;
					uint32_t writeMask = 0xff;

					frontFaceDesc.stencilFailureOperation   = s_stencilOp[(fstencil&BGFX_STENCIL_OP_FAIL_S_MASK)>>BGFX_STENCIL_OP_FAIL_S_SHIFT];
					frontFaceDesc.depthFailureOperation     = s_stencilOp[(fstencil&BGFX_STENCIL_OP_FAIL_Z_MASK)>>BGFX_STENCIL_OP_FAIL_Z_SHIFT];
					frontFaceDesc.depthStencilPassOperation = s_stencilOp[(fstencil&BGFX_STENCIL_OP_PASS_Z_MASK)>>BGFX_STENCIL_OP_PASS_Z_SHIFT];
					frontFaceDesc.stencilCompareFunction    = s_cmpFunc[(fstencil&BGFX_STENCIL_TEST_MASK)>>BGFX_STENCIL_TEST_SHIFT];
					frontFaceDesc.readMask  = readMask;
					frontFaceDesc.writeMask = writeMask;

					backfaceDesc.stencilFailureOperation   = s_stencilOp[(bstencil&BGFX_STENCIL_OP_FAIL_S_MASK)>>BGFX_STENCIL_OP_FAIL_S_SHIFT];
					backfaceDesc.depthFailureOperation     = s_stencilOp[(bstencil&BGFX_STENCIL_OP_FAIL_Z_MASK)>>BGFX_STENCIL_OP_FAIL_Z_SHIFT];
					backfaceDesc.depthStencilPassOperation = s_stencilOp[(bstencil&BGFX_STENCIL_OP_PASS_Z_MASK)>>BGFX_STENCIL_OP_PASS_Z_SHIFT];
					backfaceDesc.stencilCompareFunction    = s_cmpFunc[(bstencil&BGFX_STENCIL_TEST_MASK)>>BGFX_STENCIL_TEST_SHIFT];
					backfaceDesc.readMask  = readMask;
					backfaceDesc.writeMask = writeMask;

					desc.frontFaceStencil = frontFaceDesc;
					desc.backFaceStencil = backfaceDesc;
				}
				else
				{
					desc.backFaceStencil  = NULL;
					desc.frontFaceStencil = NULL;
				}

				dss = m_device.newDepthStencilStateWithDescriptor(desc);

				m_depthStencilStateCache.add(hash, dss);
			}

			m_renderCommandEncoder.setDepthStencilState(dss);
			m_renderCommandEncoder.setStencilReferenceValue(ref);
		}

		SamplerState getSamplerState(uint32_t _flags)
		{
			_flags &= BGFX_TEXTURE_SAMPLER_BITS_MASK;
			SamplerState sampler = m_samplerStateCache.find(_flags);

			if (NULL == sampler)
			{

				m_samplerDescriptor.sAddressMode = s_textureAddress[(_flags&BGFX_TEXTURE_U_MASK)>>BGFX_TEXTURE_U_SHIFT];
				m_samplerDescriptor.tAddressMode = s_textureAddress[(_flags&BGFX_TEXTURE_V_MASK)>>BGFX_TEXTURE_V_SHIFT];
				m_samplerDescriptor.rAddressMode = s_textureAddress[(_flags&BGFX_TEXTURE_W_MASK)>>BGFX_TEXTURE_W_SHIFT];
				m_samplerDescriptor.minFilter = s_textureFilterMinMag[(_flags&BGFX_TEXTURE_MIN_MASK)>>BGFX_TEXTURE_MIN_SHIFT];
				m_samplerDescriptor.magFilter = s_textureFilterMinMag[(_flags&BGFX_TEXTURE_MAG_MASK)>>BGFX_TEXTURE_MAG_SHIFT];
				m_samplerDescriptor.mipFilter = s_textureFilterMip[(_flags&BGFX_TEXTURE_MIP_MASK)>>BGFX_TEXTURE_MIP_SHIFT];
				m_samplerDescriptor.lodMinClamp = 0;
				m_samplerDescriptor.lodMaxClamp = FLT_MAX;
				m_samplerDescriptor.normalizedCoordinates = TRUE;
				m_samplerDescriptor.maxAnisotropy =  (0 != (_flags & (BGFX_TEXTURE_MIN_ANISOTROPIC|BGFX_TEXTURE_MAG_ANISOTROPIC) ) ) ? m_maxAnisotropy : 1;

				//NOTE: Comparison function can be specified in shader on all metal hw.
				if ( m_macOS11Runtime || [m_device supportsFeatureSet:(MTLFeatureSet)4/*MTLFeatureSet_iOS_GPUFamily3_v1*/])
				{
					const uint32_t cmpFunc = (_flags&BGFX_TEXTURE_COMPARE_MASK)>>BGFX_TEXTURE_COMPARE_SHIFT;
					m_samplerDescriptor.compareFunction = 0 == cmpFunc ? MTLCompareFunctionNever : s_cmpFunc[cmpFunc];
				}

				sampler = m_device.newSamplerStateWithDescriptor(m_samplerDescriptor);
				m_samplerStateCache.add(_flags, sampler);
			}

			return sampler;
		}

		bool isVisible(Frame* _render, OcclusionQueryHandle _handle, bool _visible)
		{
			m_occlusionQuery.resolve(_render);
			return _visible == (0 != _render->m_occlusion[_handle.idx]);
		}

		void sync()
		{
			if ( m_prevCommandBuffer )
				m_prevCommandBuffer.waitUntilCompleted();
		}

		BlitCommandEncoder getBlitCommandEncoder()
		{
			if ( m_blitCommandEncoder == NULL)
			{
				if ( m_commandBuffer == NULL )
				{
					m_commandBuffer = m_commandQueue.commandBuffer();
					retain(m_commandBuffer);
				}

				m_blitCommandEncoder = m_commandBuffer.blitCommandEncoder();
			}

			return m_blitCommandEncoder;
		}

		id<CAMetalDrawable> currentDrawable()
		{
			if (m_drawable == nil)
			{
				m_drawable = m_metalLayer.nextDrawable;
				if (BX_ENABLED(BX_PLATFORM_IOS) )
				{
					retain(m_drawable); // keep alive to be useable at 'flip'
				}
			}

			return m_drawable;
		}


		Device				m_device;
		OcclusionQueryMTL	m_occlusionQuery;
		TimerQueryMtl		m_gpuTimer;

		CommandQueue  m_commandQueue;
		CAMetalLayer* m_metalLayer;
		Texture       m_backBufferColorMSAA;
		Texture       m_backBufferDepth;
		Texture       m_backBufferStencil;
		uint32_t      m_backBufferPixelFormatHash;
		uint32_t      m_maxAnisotropy;

		bool m_iOS9Runtime;
		bool m_macOS11Runtime;
		bool m_hasPixelFormatDepth32Float_Stencil8;



		bx::Semaphore m_framesSemaphore;

		Buffer   m_uniformBuffer;
		Buffer   m_uniformBuffers[MTL_MAX_FRAMES_IN_FLIGHT];
		uint32_t m_uniformBufferVertexOffset;
		uint32_t m_uniformBufferFragmentOffset;

		uint8_t  m_bufferIndex;

		uint16_t          m_numWindows;
		FrameBufferHandle m_windows[BGFX_CONFIG_MAX_FRAME_BUFFERS];

		IndexBufferMtl  m_indexBuffers[BGFX_CONFIG_MAX_INDEX_BUFFERS];
		VertexBufferMtl m_vertexBuffers[BGFX_CONFIG_MAX_VERTEX_BUFFERS];
		ShaderMtl       m_shaders[BGFX_CONFIG_MAX_SHADERS];
		ProgramMtl      m_program[BGFX_CONFIG_MAX_PROGRAMS];
		TextureMtl      m_textures[BGFX_CONFIG_MAX_TEXTURES];
		FrameBufferMtl  m_frameBuffers[BGFX_CONFIG_MAX_FRAME_BUFFERS];
		VertexDecl      m_vertexDecls[BGFX_CONFIG_MAX_VERTEX_DECLS];
		UniformRegistry m_uniformReg;
		void*           m_uniforms[BGFX_CONFIG_MAX_UNIFORMS];

		StateCacheT<DepthStencilState> m_depthStencilStateCache;
		StateCacheT<SamplerState>      m_samplerStateCache;

		TextVideoMem m_textVideoMem;

		FrameBufferHandle m_fbh;
		bool m_rtMsaa;

		Resolution m_resolution;
		void* m_capture;
		uint32_t m_captureSize;

		// descriptors
		RenderPipelineDescriptor m_renderPipelineDescriptor;
		DepthStencilDescriptor   m_depthStencilDescriptor;
		StencilDescriptor        m_frontFaceStencilDescriptor;
		StencilDescriptor        m_backFaceStencilDescriptor;
		VertexDescriptor         m_vertexDescriptor;
		TextureDescriptor        m_textureDescriptor;
		SamplerDescriptor        m_samplerDescriptor;

		// currently active objects data
		id <CAMetalDrawable>	m_drawable;
		bool					m_saveScreenshot;
		Texture					m_screenshotTarget;
		ShaderMtl				m_screenshotBlitProgramVsh;
		ShaderMtl				m_screenshotBlitProgramFsh;
		ProgramMtl				m_screenshotBlitProgram;
		RenderPipelineState		m_screenshotBlitRenderPipelineState;

		CommandBuffer			m_commandBuffer;
		CommandBuffer			m_prevCommandBuffer;
		BlitCommandEncoder		m_blitCommandEncoder;
		RenderCommandEncoder	m_renderCommandEncoder;
		FrameBufferHandle		m_renderCommandEncoderFrameBufferHandle;
	};

	static RendererContextMtl* s_renderMtl;

	RendererContextI* rendererCreate()
	{
		s_renderMtl = BX_NEW(g_allocator, RendererContextMtl);
		if (!s_renderMtl->init())
		{
			BX_DELETE(g_allocator, s_renderMtl);
			s_renderMtl = NULL;
		}
		return s_renderMtl;
	}

	void rendererDestroy()
	{
		s_renderMtl->shutdown();
		BX_DELETE(g_allocator, s_renderMtl);
		s_renderMtl = NULL;
	}

	void writeString(bx::WriterI* _writer, const char* _str)
	{
		bx::write(_writer, _str, (int32_t)strlen(_str) );
	}

	void ShaderMtl::create(const Memory* _mem)
	{
		bx::MemoryReader reader(_mem->data, _mem->size);

		uint32_t magic;
		bx::read(&reader, magic);

		switch (magic)
		{
			case BGFX_CHUNK_MAGIC_CSH:
			case BGFX_CHUNK_MAGIC_FSH:
			case BGFX_CHUNK_MAGIC_VSH:
				break;

			default:
				BGFX_FATAL(false, Fatal::InvalidShader, "Unknown shader format %x.", magic);
				break;
		}

		uint32_t iohash;
		bx::read(&reader, iohash);

		uint16_t count;
		bx::read(&reader, count);

		BX_TRACE("%s Shader consts %d"
				 , BGFX_CHUNK_MAGIC_FSH == magic ? "Fragment" : BGFX_CHUNK_MAGIC_VSH == magic ? "Vertex" : "Compute"
				 , count
				 );

		for (uint32_t ii = 0; ii < count; ++ii)
		{
			uint8_t nameSize;
			bx::read(&reader, nameSize);

			char name[256];
			bx::read(&reader, &name, nameSize);
			name[nameSize] = '\0';

			uint8_t type;
			bx::read(&reader, type);

			uint8_t num;
			bx::read(&reader, num);

			uint16_t regIndex;
			bx::read(&reader, regIndex);

			uint16_t regCount;
			bx::read(&reader, regCount);
		}

		uint32_t shaderSize;
		bx::read(&reader, shaderSize);

		const char* code = (const char*)reader.getDataPtr();
		bx::skip(&reader, shaderSize+1);

		Library lib = s_renderMtl->m_device.newLibraryWithSource(code);

		if (NULL != lib)
		{
			m_function = lib.newFunctionWithName(SHADER_FUNCTION_NAME);
		}

		BGFX_FATAL(NULL != m_function
			, bgfx::Fatal::InvalidShader
			, "Failed to create %s shader."
			, BGFX_CHUNK_MAGIC_FSH == magic ? "Fragment" : BGFX_CHUNK_MAGIC_VSH == magic ? "Vertex" : "Compute"
			);
	}

	void ProgramMtl::create(const ShaderMtl* _vsh, const ShaderMtl* _fsh)
	{
		BX_CHECK(NULL != _vsh->m_function.m_obj, "Vertex shader doesn't exist.");
		m_vsh = _vsh;

		if (NULL != _fsh)
		{
			BX_CHECK(NULL != _fsh->m_function.m_obj, "Fragment shader doesn't exist.");
			m_fsh = _fsh;
		}

		// get attributes
		memset(m_attributes, 0xff, sizeof(m_attributes) );
		uint32_t used = 0;
		uint32_t instUsed = 0;
		if (NULL != _vsh->m_function.m_obj )
		{
			for (MTLVertexAttribute* attrib in _vsh->m_function.m_obj.vertexAttributes)
			{
				if (attrib.active )
				{
					const char* name = utf8String(attrib.name);
					uint32_t loc = (uint32_t)attrib.attributeIndex;
					BX_TRACE("attr %s: %d", name, loc);

					for (uint8_t ii = 0; ii < Attrib::Count; ++ii)
					{
						if (!strcmp(s_attribName[ii],name))
						{
							m_attributes[ii] = loc;
							m_used[used++] = ii;
							break;
						}
					}

					for (uint32_t ii = 0; ii < BX_COUNTOF(s_instanceDataName); ++ii)
					{
						if (!strcmp(s_instanceDataName[ii],name))
						{
							m_instanceData[instUsed++] = loc;
						}
					}

				}
			}
		}
		m_used[used] = Attrib::Count;
		m_instanceData[instUsed] = UINT16_MAX;
	}

	void ProgramMtl::destroy()
	{
		m_vsh = NULL;
		m_fsh = NULL;

		if (NULL != m_vshConstantBuffer)
		{
			UniformBuffer::destroy(m_vshConstantBuffer);
			m_vshConstantBuffer = NULL;
		}

		if (NULL != m_fshConstantBuffer)
		{
			UniformBuffer::destroy(m_fshConstantBuffer);
			m_fshConstantBuffer = NULL;
		}

		m_vshConstantBufferSize = 0;
		m_vshConstantBufferAlignmentMask = 0;
		m_fshConstantBufferSize = 0;
		m_fshConstantBufferAlignmentMask = 0;

		m_processedUniforms = false;
		m_numPredefined = 0;

		m_renderPipelineStateCache.invalidate();
	}

	UniformType::Enum convertMtlType(MTLDataType _type)
	{
		switch (_type)
		{
			case MTLDataTypeUInt:
			case MTLDataTypeInt:
				return UniformType::Int1;

			case MTLDataTypeFloat:
			case MTLDataTypeFloat2:
			case MTLDataTypeFloat3:
			case MTLDataTypeFloat4:
				return UniformType::Vec4;

			case MTLDataTypeFloat3x3:
				return UniformType::Mat3;

			case MTLDataTypeFloat4x4:
				return UniformType::Mat4;

			default:
				break;
		};

		BX_CHECK(false, "Unrecognized Mtl Data type 0x%04x.", _type);
		return UniformType::End;
	}

	RenderPipelineState ProgramMtl::getRenderPipelineState(uint64_t _state, uint32_t _rgba, FrameBufferHandle _fbHandle, VertexDeclHandle _declHandle,  uint16_t _numInstanceData)
	{
		_state &= (BGFX_STATE_BLEND_MASK|BGFX_STATE_BLEND_EQUATION_MASK|BGFX_STATE_ALPHA_WRITE|BGFX_STATE_RGB_WRITE|BGFX_STATE_BLEND_INDEPENDENT|BGFX_STATE_MSAA|BGFX_STATE_BLEND_ALPHA_TO_COVERAGE);

		bool independentBlendEnable = !!(BGFX_STATE_BLEND_INDEPENDENT & _state);

		bx::HashMurmur2A murmur;
		murmur.begin();
		murmur.add(_state);
		murmur.add(independentBlendEnable ? _rgba : 0);
		if (!isValid(_fbHandle) )
		{
			murmur.add(s_renderMtl->m_backBufferPixelFormatHash);
		}
		else
		{
			FrameBufferMtl& frameBuffer = s_renderMtl->m_frameBuffers[_fbHandle.idx];
			murmur.add(frameBuffer.m_pixelFormatHash);
		}
		murmur.add(_declHandle.idx);
		murmur.add(_numInstanceData);
		uint32_t hash = murmur.end();

		RenderPipelineState rps = m_renderPipelineStateCache.find(hash);
		if (NULL == rps)
		{
			RenderPipelineDescriptor& pd = s_renderMtl->m_renderPipelineDescriptor;
			reset(pd);

			pd.alphaToCoverageEnabled  = !!(BGFX_STATE_BLEND_ALPHA_TO_COVERAGE & _state);

			uint32_t frameBufferAttachment = 1;

			if (!isValid(_fbHandle) )
			{
				pd.sampleCount = NULL != s_renderMtl->m_backBufferColorMSAA ? s_renderMtl->m_backBufferColorMSAA.sampleCount() : 1;
				pd.colorAttachments[0].pixelFormat = s_renderMtl->currentDrawable().texture.pixelFormat;
				pd.depthAttachmentPixelFormat      = s_renderMtl->m_backBufferDepth.m_obj.pixelFormat;
				pd.stencilAttachmentPixelFormat    = s_renderMtl->m_backBufferStencil.m_obj.pixelFormat;
			}
			else
			{
				FrameBufferMtl& frameBuffer = s_renderMtl->m_frameBuffers[_fbHandle.idx];
				frameBufferAttachment = frameBuffer.m_num;

				for (uint32_t ii = 0; ii < frameBuffer.m_num; ++ii)
				{
					const TextureMtl& texture = s_renderMtl->m_textures[frameBuffer.m_colorHandle[ii].idx];
					pd.sampleCount = NULL != texture.m_ptrMSAA ? texture.m_ptrMSAA.sampleCount() : 1;
					pd.colorAttachments[ii].pixelFormat = texture.m_ptr.m_obj.pixelFormat;
				}

				if (isValid(frameBuffer.m_depthHandle))
				{
					const TextureMtl& texture = s_renderMtl->m_textures[frameBuffer.m_depthHandle.idx];
					pd.depthAttachmentPixelFormat = texture.m_ptr.m_obj.pixelFormat;
					if (NULL != texture.m_ptrStencil)
					{
						pd.stencilAttachmentPixelFormat = texture.m_ptrStencil.m_obj.pixelFormat;
					}
					else
					{
						if ( texture.m_textureFormat == TextureFormat::D24S8)
							pd.stencilAttachmentPixelFormat = texture.m_ptr.m_obj.pixelFormat;
					}
				}
			}

			const uint32_t blend    = uint32_t( (_state&BGFX_STATE_BLEND_MASK)>>BGFX_STATE_BLEND_SHIFT);
			const uint32_t equation = uint32_t( (_state&BGFX_STATE_BLEND_EQUATION_MASK)>>BGFX_STATE_BLEND_EQUATION_SHIFT);

			const uint32_t srcRGB = (blend    )&0xf;
			const uint32_t dstRGB = (blend>> 4)&0xf;
			const uint32_t srcA   = (blend>> 8)&0xf;
			const uint32_t dstA   = (blend>>12)&0xf;

			const uint32_t equRGB = (equation   )&0x7;
			const uint32_t equA   = (equation>>3)&0x7;

			uint8_t writeMask = (_state&BGFX_STATE_ALPHA_WRITE) ? MTLColorWriteMaskAlpha : 0;
			writeMask |= (_state&BGFX_STATE_RGB_WRITE) ? MTLColorWriteMaskRed|MTLColorWriteMaskGreen|MTLColorWriteMaskBlue : 0;

			for (uint32_t ii = 0; ii < (independentBlendEnable ? 1 : frameBufferAttachment); ++ii)
			{
				RenderPipelineColorAttachmentDescriptor drt = pd.colorAttachments[ii];

				drt.blendingEnabled = !!(BGFX_STATE_BLEND_MASK & _state);

				drt.sourceRGBBlendFactor        = s_blendFactor[srcRGB][0];
				drt.destinationRGBBlendFactor   = s_blendFactor[dstRGB][0];
				drt.rgbBlendOperation           = s_blendEquation[equRGB];

				drt.sourceAlphaBlendFactor      = s_blendFactor[srcA][1];
				drt.destinationAlphaBlendFactor = s_blendFactor[dstA][1];
				drt.alphaBlendOperation         = s_blendEquation[equA];

				drt.writeMask = writeMask;
			}

			if (independentBlendEnable)
			{
				for (uint32_t ii = 1, rgba = _rgba; ii < frameBufferAttachment; ++ii, rgba >>= 11)
				{
					RenderPipelineColorAttachmentDescriptor drt = pd.colorAttachments[ii];

					drt.blendingEnabled = 0 != (rgba&0x7ff);

					const uint32_t src           = (rgba   )&0xf;
					const uint32_t dst           = (rgba>>4)&0xf;
					const uint32_t equationIndex = (rgba>>8)&0x7;

					drt.sourceRGBBlendFactor      = s_blendFactor[src][0];
					drt.destinationRGBBlendFactor = s_blendFactor[dst][0];
					drt.rgbBlendOperation         = s_blendEquation[equationIndex];

					drt.sourceAlphaBlendFactor      = s_blendFactor[src][1];
					drt.destinationAlphaBlendFactor = s_blendFactor[dst][1];
					drt.alphaBlendOperation         = s_blendEquation[equationIndex];

					drt.writeMask = writeMask;
				}
			}

			pd.vertexFunction = m_vsh->m_function;
			pd.fragmentFunction = m_fsh->m_function;

			if (isValid(_declHandle))
			{
				VertexDescriptor vertexDesc = s_renderMtl->m_vertexDescriptor;
				reset(vertexDesc);

				VertexDecl &vertexDecl = s_renderMtl->m_vertexDecls[_declHandle.idx];
				for (uint32_t ii = 0; Attrib::Count != m_used[ii]; ++ii)
				{
					Attrib::Enum attr = Attrib::Enum(m_used[ii]);
					uint32_t loc = m_attributes[attr];

					uint8_t num;
					AttribType::Enum type;
					bool normalized;
					bool asInt;
					vertexDecl.decode(attr, num, type, normalized, asInt);
					BX_CHECK(num <= 4, "num must be <=4");

					if (UINT16_MAX != vertexDecl.m_attributes[attr])
					{
						vertexDesc.attributes[loc].format = s_attribType[type][num-1][normalized?1:0];
						vertexDesc.attributes[loc].bufferIndex = 1;
						vertexDesc.attributes[loc].offset = vertexDecl.m_offset[attr];

						BX_TRACE("attrib:%s format: %d offset:%d", s_attribName[attr], (int)vertexDesc.attributes[loc].format, (int)vertexDesc.attributes[loc].offset);
					}
					else
					{	// NOTE: missing attribute: using dummy attribute with smallest possible size
						vertexDesc.attributes[loc].format = MTLVertexFormatUChar2;
						vertexDesc.attributes[loc].bufferIndex = 1;
						vertexDesc.attributes[loc].offset = 0;
					}
				}

				vertexDesc.layouts[1].stride = vertexDecl.getStride();
				vertexDesc.layouts[1].stepFunction = MTLVertexStepFunctionPerVertex;

				BX_TRACE("stride: %d", (int)vertexDesc.layouts[1].stride);

				if (_numInstanceData > 0)
				{
					for (uint32_t ii = 0; UINT16_MAX != m_instanceData[ii]; ++ii)
					{
						uint32_t loc = m_instanceData[ii];
						vertexDesc.attributes[loc].format = MTLVertexFormatFloat4;
						vertexDesc.attributes[loc].bufferIndex = 2;
						vertexDesc.attributes[loc].offset = ii*16;
					}

					vertexDesc.layouts[2].stride = _numInstanceData * 16;
					vertexDesc.layouts[2].stepFunction = MTLVertexStepFunctionPerInstance;
					vertexDesc.layouts[2].stepRate = 1;
				}

				pd.vertexDescriptor = vertexDesc;
			}

			if (m_processedUniforms)
			{
				rps = s_renderMtl->m_device.newRenderPipelineStateWithDescriptor(pd);
			}
			else
			{
				m_numPredefined = 0;
				RenderPipelineReflection reflection = NULL;
				rps = s_renderMtl->m_device.newRenderPipelineStateWithDescriptor(pd, MTLPipelineOptionBufferTypeInfo, &reflection);

				if (NULL != reflection)
				{
					for (uint32_t shaderType = 0; shaderType < 2; ++shaderType)
					{
						UniformBuffer*& constantBuffer = (shaderType == 0 ? m_vshConstantBuffer : m_fshConstantBuffer);
						uint8_t fragmentBit = (1 == shaderType ? BGFX_UNIFORM_FRAGMENTBIT : 0);

						for (MTLArgument* arg in (shaderType == 0 ? reflection.vertexArguments : reflection.fragmentArguments))
						{
							BX_TRACE("arg: %s type:%d", utf8String(arg.name), arg.type);
							if (arg.active)
							{
								if (arg.type == MTLArgumentTypeBuffer
								&& 0 == strcmp(utf8String(arg.name), SHADER_UNIFORM_NAME) )
								{
									BX_CHECK( arg.index == 0, "Uniform buffer must be in the buffer slot 0.");
									BX_CHECK( MTLDataTypeStruct == arg.bufferDataType, "%s's type must be a struct",SHADER_UNIFORM_NAME );

									if (MTLDataTypeStruct == arg.bufferDataType)
									{
										if (shaderType == 0)
										{
											m_vshConstantBufferSize = (uint32_t)arg.bufferDataSize;
											m_vshConstantBufferAlignmentMask = (uint32_t)arg.bufferAlignment - 1;
										}
										else
										{
											m_fshConstantBufferSize = (uint32_t)arg.bufferDataSize;
											m_fshConstantBufferAlignmentMask = (uint32_t)arg.bufferAlignment - 1;
										}

										for (MTLStructMember* uniform in arg.bufferStructType.members )
										{
											const char* name = utf8String(uniform.name);
											BX_TRACE("uniform: %s type:%d", name, uniform.dataType);

											MTLDataType dataType = uniform.dataType;
											uint32_t num = 1;

											if (dataType == MTLDataTypeArray)
											{
												dataType = uniform.arrayType.elementType;
												num = (uint32_t)uniform.arrayType.arrayLength;
											}

											switch (dataType) {
												case MTLDataTypeFloat4 :
													num *= 1;
													break;
												case MTLDataTypeFloat4x4:
													num *= 4;
													break;
												case MTLDataTypeFloat3x3:
													num *= 3;
													break;
												default:
													BX_WARN(0, "Unsupported uniform MTLDataType: %d", uniform.dataType);
													break;
											}

											PredefinedUniform::Enum predefined = nameToPredefinedUniformEnum(name);
											if (PredefinedUniform::Count != predefined)
											{
												m_predefined[m_numPredefined].m_loc   = uint32_t(uniform.offset);
												m_predefined[m_numPredefined].m_count = uint16_t(num);
												m_predefined[m_numPredefined].m_type  = uint8_t(predefined|fragmentBit);
												m_numPredefined++;
											}
											else
											{
												const UniformRegInfo* info = s_renderMtl->m_uniformReg.find(name);
												BX_WARN(NULL != info, "User defined uniform '%s' is not found, it won't be set.", name);

												if (NULL != info)
												{
													if (NULL == constantBuffer)
													{
														constantBuffer = UniformBuffer::create(1024);
													}

													UniformType::Enum type = convertMtlType(dataType);
													constantBuffer->writeUniformHandle((UniformType::Enum)(type|fragmentBit), uint32_t(uniform.offset), info->m_handle, uint16_t(num) );
													BX_TRACE("store %s %d offset:%d", name, info->m_handle, uint32_t(uniform.offset));
												}
											}

										}
									}
								}
								else if (arg.type == MTLArgumentTypeTexture)
								{
									if ( shaderType == 0 ) m_usedVertexSamplerStages |= 1<<arg.index;
									else m_usedFragmentSamplerStages |= 1<<arg.index;

									BX_TRACE("texture: %s index:%d", utf8String(arg.name), arg.index);
								}
								else if (arg.type == MTLArgumentTypeSampler)
								{
									BX_TRACE("sampler: %s index:%d", utf8String(arg.name), arg.index);
								}
							}
						}

						if (NULL != constantBuffer)
						{
							constantBuffer->finish();
						}
					}
				}

				m_processedUniforms = true;
			}

			m_renderPipelineStateCache.add(hash, rps);
		}

		return rps;
	}


	void BufferMtl::create(uint32_t _size, void* _data, uint16_t _flags, uint16_t _stride, bool _vertex)
	{
		BX_UNUSED(_stride, _vertex);

		m_size = _size;
		m_flags = _flags;
		m_dynamic = (NULL == _data);

		if (NULL == _data)
		{
			for (uint32_t ii = 0; ii < MTL_MAX_FRAMES_IN_FLIGHT; ++ii)
				m_buffers[ii] = s_renderMtl->m_device.newBufferWithLength(_size, 0);
		}
		else
		{
			m_buffers[0] = s_renderMtl->m_device.newBufferWithBytes(_data, _size, 0);
		}
	}

	void BufferMtl::update(uint32_t _offset, uint32_t _size, void* _data, bool _discard)
	{
		BX_UNUSED(_discard);

			//TODO: cannot call this more than once per frame
		if ( m_dynamic && _discard )
		{
			m_bufferIndex = (m_bufferIndex + 1) % MTL_MAX_FRAMES_IN_FLIGHT;
			memcpy( (uint8_t*)getBuffer().contents() + _offset, _data, _size);
		}
		else if ( NULL != s_renderMtl->m_renderCommandEncoder )
		{	// NOTE: cannot blit while rendercommander is active. have to sync. slow. remove these.
			// ClearQuad triggers this now
			s_renderMtl->sync();
			memcpy( (uint8_t*)getBuffer().contents() + _offset, _data, _size);
		}
		else
		{
			BlitCommandEncoder bce = s_renderMtl->getBlitCommandEncoder();

			Buffer temp = s_renderMtl->m_device.newBufferWithBytes(_data, _size, 0);
			bce.copyFromBuffer(temp, 0, getBuffer(), _offset, _size);
			release(temp);
		}
	}

	void VertexBufferMtl::create(uint32_t _size, void* _data, VertexDeclHandle _declHandle, uint16_t _flags)
	{
		m_decl = _declHandle;
		uint16_t stride = isValid(_declHandle)
		? s_renderMtl->m_vertexDecls[_declHandle.idx].m_stride
		: 0
		;

		BufferMtl::create(_size, _data, _flags, stride, true);
	}

	void TextureMtl::create(const Memory* _mem, uint32_t _flags, uint8_t _skip)
	{
		m_sampler = s_renderMtl->getSamplerState(_flags);

		ImageContainer imageContainer;

		if (imageParse(imageContainer, _mem->data, _mem->size) )
		{
			uint8_t numMips = imageContainer.m_numMips;
			const uint8_t startLod = uint8_t(bx::uint32_min(_skip, numMips-1) );
			numMips -= startLod;
			const ImageBlockInfo& blockInfo = getBlockInfo(TextureFormat::Enum(imageContainer.m_format) );
			const uint32_t textureWidth  = bx::uint32_max(blockInfo.blockWidth,  imageContainer.m_width >>startLod);
			const uint32_t textureHeight = bx::uint32_max(blockInfo.blockHeight, imageContainer.m_height>>startLod);
			const uint16_t numLayers     = imageContainer.m_numLayers;

			m_flags   = _flags;
			m_width   = textureWidth;
			m_height  = textureHeight;
			m_depth   = imageContainer.m_depth;
			m_requestedFormat  = uint8_t(imageContainer.m_format);
			m_textureFormat    = uint8_t(getViableTextureFormat(imageContainer) );
			const bool convert = m_textureFormat != m_requestedFormat;
			const uint8_t bpp = getBitsPerPixel(TextureFormat::Enum(m_textureFormat) );

			TextureDescriptor desc = s_renderMtl->m_textureDescriptor;

			if (1 < numLayers)
			{
				if (imageContainer.m_cubeMap)
				{
					desc.textureType = MTLTextureType(6); // MTLTextureTypeCubeArray
				}
				else
				{
					desc.textureType = MTLTextureType2DArray;
				}

				desc.arrayLength = numLayers;
			}
			else if (imageContainer.m_cubeMap)
			{
				desc.textureType = MTLTextureTypeCube;
			}
			else if (imageContainer.m_depth > 1)
			{
				desc.textureType = MTLTextureType3D;
			}
			else
			{
				desc.textureType = MTLTextureType2D;
			}

			m_numMips = numMips;
			const uint16_t numSides = numLayers * (imageContainer.m_cubeMap ? 6 : 1);

			const bool compressed   = isCompressed(TextureFormat::Enum(m_textureFormat) );
			const bool writeOnly    = 0 != (_flags&BGFX_TEXTURE_RT_WRITE_ONLY);
			const bool computeWrite = 0 != (_flags&BGFX_TEXTURE_COMPUTE_WRITE);
			const bool renderTarget = 0 != (_flags&BGFX_TEXTURE_RT_MASK);
			const bool srgb			= 0 != (_flags&BGFX_TEXTURE_SRGB) || imageContainer.m_srgb;

			BX_TRACE("Texture %3d: %s (requested: %s), layers %d, %dx%d%s RT[%c], WO[%c], CW[%c], sRGB[%c]"
					 , this - s_renderMtl->m_textures
					 , getName( (TextureFormat::Enum)m_textureFormat)
					 , getName( (TextureFormat::Enum)m_requestedFormat)
					 , numLayers
					 , textureWidth
					 , textureHeight
					 , imageContainer.m_cubeMap ? "x6" : ""
					 , renderTarget ? 'x' : '.'
					 , writeOnly ? 'x' : '.'
					 , computeWrite ? 'x' : '.'
					 , srgb ? 'x' : '.'
					 );

			const uint32_t msaaQuality = bx::uint32_satsub( (_flags&BGFX_TEXTURE_RT_MSAA_MASK)>>BGFX_TEXTURE_RT_MSAA_SHIFT, 1);
			int sampleCount = s_msaa[msaaQuality];

			MTLPixelFormat format = MTLPixelFormatInvalid;
			if (srgb)
			{
				format = s_textureFormat[m_textureFormat].m_fmtSrgb;
				BX_WARN(format != MTLPixelFormatInvalid
					, "sRGB not supported for texture format %d"
					, m_textureFormat
					);
			}

			if (format == MTLPixelFormatInvalid)
			{
				// not swizzled and not sRGB, or sRGB unsupported
				format = s_textureFormat[m_textureFormat].m_fmt;
			}

			desc.pixelFormat = format;
			desc.width  = textureWidth;
			desc.height = textureHeight;
			desc.depth  = bx::uint32_max(1,imageContainer.m_depth);
			desc.mipmapLevelCount = imageContainer.m_numMips;
			desc.sampleCount      = 1;

			if (s_renderMtl->m_iOS9Runtime || s_renderMtl->m_macOS11Runtime)
			{
				desc.cpuCacheMode = MTLCPUCacheModeDefaultCache;

				desc.storageMode = (MTLStorageMode)(writeOnly||isDepth(TextureFormat::Enum(m_textureFormat))
													? 2 /*MTLStorageModePrivate*/
													: ((BX_ENABLED(BX_PLATFORM_IOS)) ? 0 /* MTLStorageModeShared */ :  1 /*MTLStorageModeManaged*/)
													);

				desc.usage = MTLTextureUsageShaderRead;
				if (computeWrite)
					desc.usage |= MTLTextureUsageShaderWrite;
				if (renderTarget)
					desc.usage |= MTLTextureUsageRenderTarget;
			}

			m_ptr = s_renderMtl->m_device.newTextureWithDescriptor(desc);

			if ( sampleCount > 1)
			{
				desc.textureType = MTLTextureType2DMultisample;
				desc.sampleCount = sampleCount;
				if (s_renderMtl->m_iOS9Runtime || s_renderMtl->m_macOS11Runtime)
					desc.storageMode = (MTLStorageMode)( 2 /*MTLStorageModePrivate*/);
				m_ptrMSAA = s_renderMtl->m_device.newTextureWithDescriptor(desc);
			}

			if (m_requestedFormat == TextureFormat::D24S8
			&&  desc.pixelFormat  == MTLPixelFormatDepth32Float)
			{
				desc.pixelFormat = MTLPixelFormatStencil8;
				m_ptrStencil = s_renderMtl->m_device.newTextureWithDescriptor(desc);
			}

			uint8_t* temp = NULL;
			if (convert)
			{
				temp = (uint8_t*)BX_ALLOC(g_allocator, textureWidth*textureHeight*4);
			}

			for (uint8_t side = 0; side < numSides; ++side)
			{
				uint32_t width  = textureWidth;
				uint32_t height = textureHeight;
				uint32_t depth  = imageContainer.m_depth;

				for (uint8_t lod = 0, num = numMips; lod < num; ++lod)
				{
					width  = bx::uint32_max(1, width);
					height = bx::uint32_max(1, height);
					depth  = bx::uint32_max(1, depth);

					ImageMip mip;
					if (imageGetRawData(imageContainer, side, lod+startLod, _mem->data, _mem->size, mip) )
					{
						const uint8_t* data = mip.m_data;

						if (convert)
						{
							imageDecodeToBgra8(temp
								, mip.m_data
								, mip.m_width
								, mip.m_height
								, mip.m_width*4
								, mip.m_format
								);
							data = temp;
						}

						MTLRegion region = { { 0, 0, 0 }, { width, height, depth } };

						uint32_t bytesPerRow   = 0;
						uint32_t bytesPerImage = 0;

						if (compressed && !convert)
						{
							if (format >= 160 /*PVRTC_RGB_2BPP*/
							&&  format <= 167 /*PVRTC_RGBA_4BPP_sRGB*/)
							{
								bytesPerRow   = 0;
								bytesPerImage = 0;
							}
							else
							{
								bytesPerRow   = (mip.m_width / blockInfo.blockWidth)*mip.m_blockSize;
								bytesPerImage = desc.textureType == MTLTextureType3D
									? (mip.m_height/blockInfo.blockHeight)*bytesPerRow
									: 0
									;
							}
						}
						else
						{
							bytesPerRow   = width * bpp / 8;
							bytesPerImage = desc.textureType == MTLTextureType3D
								? bytesPerRow * height
								: 0
								;
						}

						m_ptr.replaceRegion(region, lod, side, data, bytesPerRow, bytesPerImage);
					}

					width  >>= 1;
					height >>= 1;
					depth  >>= 1;
				}
			}

			if (NULL != temp)
			{
				BX_FREE(g_allocator, temp);
			}
		}
	}

	void TextureMtl::update(uint8_t _side, uint8_t _mip, const Rect& _rect, uint16_t _z, uint16_t _depth, uint16_t _pitch, const Memory* _mem)
	{
		const uint32_t bpp       = getBitsPerPixel(TextureFormat::Enum(m_textureFormat) );
		const uint32_t rectpitch = _rect.m_width*bpp/8;
		const uint32_t srcpitch  = UINT16_MAX == _pitch ? rectpitch : _pitch;

		const bool convert = m_textureFormat != m_requestedFormat;

		uint8_t* data = _mem->data;
		uint8_t* temp = NULL;

		if (convert)
		{
			temp = (uint8_t*)BX_ALLOC(g_allocator, rectpitch*_rect.m_height);
			imageDecodeToBgra8(temp
				, data
				, _rect.m_width
				, _rect.m_height
				, srcpitch
				, TextureFormat::Enum(m_requestedFormat)
				);
			data = temp;
		}

		if ( NULL != s_renderMtl->m_renderCommandEncoder )
		{
			s_renderMtl->sync();

			MTLRegion region =
			{
				{ _rect.m_x,     _rect.m_y,      _z     },
				{ _rect.m_width, _rect.m_height, _depth },
			};

			m_ptr.replaceRegion(region, _mip, _side, data, srcpitch, srcpitch * _rect.m_height);
		}
		else
		{
			BlitCommandEncoder bce = s_renderMtl->getBlitCommandEncoder();

			const uint32_t dstpitch = bx::strideAlign(rectpitch, 64);

			Buffer tempBuffer = s_renderMtl->m_device.newBufferWithLength(dstpitch*_rect.m_height, 0);

			const uint8_t* src = (uint8_t*)data;
			uint8_t* dst = (uint8_t*)tempBuffer.contents();

			for (uint32_t yy = 0; yy < _rect.m_height; ++yy, src += srcpitch, dst += dstpitch)
			{
				memcpy(dst, src, rectpitch);
			}

			bce.copyFromBuffer(tempBuffer, 0, dstpitch, dstpitch * _rect.m_height, MTLSizeMake(_rect.m_width, _rect.m_height, _depth),
							   m_ptr, _side, _mip, MTLOriginMake(_rect.m_x, _rect.m_y, _z));
			release(tempBuffer);
		}

		if (NULL != temp)
		{
			BX_FREE(g_allocator, temp);
		}
	}

	void TextureMtl::commit(uint8_t _stage, bool _vertex, bool _fragment, uint32_t _flags)
	{
		if (_vertex)
		{
			s_renderMtl->m_renderCommandEncoder.setVertexTexture(m_ptr, _stage);
			s_renderMtl->m_renderCommandEncoder.setVertexSamplerState(0 == (BGFX_TEXTURE_INTERNAL_DEFAULT_SAMPLER & _flags)
																	  ? s_renderMtl->getSamplerState(_flags)
																	  : m_sampler, _stage);
		}

		if (_fragment)
		{
			s_renderMtl->m_renderCommandEncoder.setFragmentTexture(m_ptr, _stage);
			s_renderMtl->m_renderCommandEncoder.setFragmentSamplerState(0 == (BGFX_TEXTURE_INTERNAL_DEFAULT_SAMPLER & _flags)
																		? s_renderMtl->getSamplerState(_flags)
																		: m_sampler, _stage);
		}
	}

	void FrameBufferMtl::create(uint8_t _num, const Attachment* _attachment)
	{
		m_num = 0;
		m_width = 0;
		m_height = 0;
		for (uint32_t ii = 0; ii < _num; ++ii)
		{
			TextureHandle handle = _attachment[ii].handle;
			if (isValid(handle) )
			{
				const TextureMtl& texture = s_renderMtl->m_textures[handle.idx];

				if ( 0 == m_width )
				{
					m_width = texture.m_width;
					m_height = texture.m_height;
				}

				if (isDepth( (TextureFormat::Enum)texture.m_textureFormat) )
				{
					m_depthHandle = handle;
				}
				else
				{
					m_colorHandle[m_num] = handle;
					m_num++;
				}
			}
		}

		bx::HashMurmur2A murmur;
		murmur.begin();

		murmur.add(m_num);
		for (uint32_t ii = 0; ii < m_num; ++ii)
		{
			const TextureMtl& texture = s_renderMtl->m_textures[m_colorHandle[ii].idx];
			murmur.add((uint32_t)texture.m_ptr.pixelFormat());
		}
		if (!isValid(m_depthHandle))
		{
			murmur.add((uint32_t)MTLPixelFormatInvalid);
			murmur.add((uint32_t)MTLPixelFormatInvalid);
		}
		else
		{
			const TextureMtl& depthTexture = s_renderMtl->m_textures[m_depthHandle.idx];
			murmur.add((uint32_t)depthTexture.m_ptr.pixelFormat());
			murmur.add((uint32_t)(NULL != depthTexture.m_ptrStencil ? depthTexture.m_ptrStencil.pixelFormat() : MTLPixelFormatInvalid));
		}
		murmur.add(1); //SampleCount

		m_pixelFormatHash = murmur.end();
	}

	void FrameBufferMtl::create(uint16_t _denseIdx, void* _nwh, uint32_t _width, uint32_t _height, TextureFormat::Enum _depthFormat)
	{
		BX_UNUSED(_denseIdx, _nwh, _width, _height, _depthFormat);

		BX_WARN(false, "FrameBufferMtl::create not yet implemented");
	}

	void FrameBufferMtl::postReset()
	{
		BX_WARN(false, "FrameBufferMtl::postReset not yet implemented");
		//TODO: what should we do here?
	}

	uint16_t FrameBufferMtl::destroy()
	{
		m_num = 0;
		m_depthHandle.idx = invalidHandle;

		uint16_t denseIdx = m_denseIdx;
		m_denseIdx = UINT16_MAX;

		return denseIdx;
	}

	void TimerQueryMtl::init()
	{
		m_frequency = bx::getHPFrequency();
	}

	void TimerQueryMtl::shutdown()
	{
	}

    static void setTimestamp(void* _data)
	{
		*((int64_t*)_data) = bx::getHPCounter();
	}

	void TimerQueryMtl::addHandlers(CommandBuffer& _commandBuffer)
	{
		while (0 == m_control.reserve(1) )
		{
			m_control.consume(1);
		}

		uint32_t offset = m_control.m_current * 2 + 0;

		_commandBuffer.addScheduledHandler(setTimestamp, &m_result[offset]);
		_commandBuffer.addCompletedHandler(setTimestamp, &m_result[offset+1]);
		m_control.commit(1);
	}

	bool TimerQueryMtl::get()
	{
		if (0 != m_control.available() )
		{
			uint32_t offset = m_control.m_read * 2;
			m_begin = m_result[offset+0];
			m_end   = m_result[offset+1];
			m_elapsed = m_end - m_begin;

			m_control.consume(1);

			return true;
		}

		return false;
	}

	void OcclusionQueryMTL::postReset()
	{
		MTL_RELEASE(m_buffer);
	}

	void OcclusionQueryMTL::preReset()
	{
		m_buffer = s_renderMtl->m_device.newBufferWithLength(BX_COUNTOF(m_query) * 8, 0);
	}

	void OcclusionQueryMTL::begin(RenderCommandEncoder& _rce, Frame* _render, OcclusionQueryHandle _handle)
	{
		while (0 == m_control.reserve(1) )
		{
			resolve(_render, true);
		}

		Query& query = m_query[m_control.m_current];
		query.m_handle = _handle;
		uint32_t offset = _handle.idx * 8;
		_rce.setVisibilityResultMode(MTLVisibilityResultModeBoolean, offset);
	}

	void OcclusionQueryMTL::end(RenderCommandEncoder& _rce)
	{
		Query& query = m_query[m_control.m_current];
		uint32_t offset = query.m_handle.idx * 8;
		_rce.setVisibilityResultMode(MTLVisibilityResultModeDisabled, offset);
		m_control.commit(1);
	}

	void OcclusionQueryMTL::resolve(Frame* _render, bool _wait)
	{
		BX_UNUSED(_wait);
		while (0 != m_control.available() )
		{
			Query& query = m_query[m_control.m_read];

			uint64_t result = ( (uint64_t*)m_buffer.contents() )[query.m_handle.idx];
			_render->m_occlusion[query.m_handle.idx] = 0 < result;
			m_control.consume(1);
		}
	}

	void RendererContextMtl::submit(Frame* _render, ClearQuad& _clearQuad, TextVideoMemBlitter& _textVideoMemBlitter) BX_OVERRIDE
	{
		m_framesSemaphore.wait();

		if ( m_commandBuffer == NULL )
		{
			m_commandBuffer = m_commandQueue.commandBuffer();
			retain(m_commandBuffer); // keep alive to be useable at 'flip'
		}

		int64_t elapsed = -bx::getHPCounter();
		int64_t captureElapsed = 0;

		m_gpuTimer.addHandlers(m_commandBuffer);

		if ( m_blitCommandEncoder )
		{
			m_blitCommandEncoder.endEncoding();
			m_blitCommandEncoder = 0;
		}

		updateResolution(_render->m_resolution);

		if ( m_saveScreenshot || NULL != m_capture )
		{
			if ( m_screenshotTarget )
			{
				if ( m_screenshotTarget.width() != m_resolution.m_width ||
					m_screenshotTarget.height() != m_resolution.m_height )
				{
					MTL_RELEASE(m_screenshotTarget);
				}
			}

			if ( NULL == m_screenshotTarget)
			{
				m_textureDescriptor.textureType = MTLTextureType2D;
				m_textureDescriptor.pixelFormat = m_metalLayer.pixelFormat;
				m_textureDescriptor.width  = m_resolution.m_width;
				m_textureDescriptor.height = m_resolution.m_height;
				m_textureDescriptor.depth  = 1;
				m_textureDescriptor.mipmapLevelCount = 1;
				m_textureDescriptor.sampleCount = 1;
				m_textureDescriptor.arrayLength = 1;
				if ( m_iOS9Runtime || m_macOS11Runtime )
				{
					m_textureDescriptor.cpuCacheMode = MTLCPUCacheModeDefaultCache;
					m_textureDescriptor.storageMode = (MTLStorageMode)(((BX_ENABLED(BX_PLATFORM_IOS)) ? 0 /* MTLStorageModeShared */ :  1 /*MTLStorageModeManaged*/)
														);
					m_textureDescriptor.usage		 = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
				}

				m_screenshotTarget   = m_device.newTextureWithDescriptor(m_textureDescriptor);
			}
			m_saveScreenshot = false;
		}
		else
		{
			MTL_RELEASE(m_screenshotTarget);
		}

		m_uniformBuffer = m_uniformBuffers[m_bufferIndex];
		m_bufferIndex = (m_bufferIndex + 1) % MTL_MAX_FRAMES_IN_FLIGHT;
		m_uniformBufferVertexOffset = 0;
		m_uniformBufferFragmentOffset = 0;



		if (0 < _render->m_iboffset)
		{
			TransientIndexBuffer* ib = _render->m_transientIb;
			m_indexBuffers[ib->handle.idx].update(0, _render->m_iboffset, ib->data, true);
		}

		if (0 < _render->m_vboffset)
		{
			TransientVertexBuffer* vb = _render->m_transientVb;
			m_vertexBuffers[vb->handle.idx].update(0, _render->m_vboffset, vb->data, true);
		}

		_render->sort();

		RenderDraw currentState;
		currentState.clear();
		currentState.m_stateFlags = BGFX_STATE_NONE;
		currentState.m_stencil    = packStencil(BGFX_STENCIL_NONE, BGFX_STENCIL_NONE);

		_render->m_hmdInitialized = false;

		const bool hmdEnabled = false;
		static ViewState viewState;
		viewState.reset(_render, hmdEnabled);
		uint32_t blendFactor = 0;

		bool wireframe = !!(_render->m_debug&BGFX_DEBUG_WIREFRAME);

		uint16_t programIdx = invalidHandle;
		SortKey key;
		uint16_t view = UINT16_MAX;
		FrameBufferHandle fbh = { BGFX_CONFIG_MAX_FRAME_BUFFERS };

		BlitKey blitKey;
		blitKey.decode(_render->m_blitKeys[0]);
		uint16_t numBlitItems = _render->m_numBlitItems;
		uint16_t blitItem = 0;

		const uint64_t primType = 0;
		uint8_t primIndex = uint8_t(primType>>BGFX_STATE_PT_SHIFT);
		PrimInfo prim = s_primInfo[primIndex];

		ProgramMtl* currentProgram = NULL;
		RenderCommandEncoder rce;

		bool wasCompute = false;
		bool viewHasScissor = false;
		Rect viewScissorRect;
		viewScissorRect.clear();

		uint32_t statsNumPrimsSubmitted[BX_COUNTOF(s_primInfo)] = {};
		uint32_t statsNumPrimsRendered[BX_COUNTOF(s_primInfo)] = {};
		uint32_t statsNumInstances[BX_COUNTOF(s_primInfo)] = {};
		uint32_t statsNumDrawIndirect[BX_COUNTOF(s_primInfo)] = {};
		uint32_t statsNumIndices = 0;
		uint32_t statsKeyType[2] = {};

		m_occlusionQuery.resolve(_render);

		if (0 == (_render->m_debug&BGFX_DEBUG_IFH) )
		{
			bool viewRestart = false;
			uint8_t eye = 0;
			uint8_t restartState = 0;
			viewState.m_rect = _render->m_rect[0];

			int32_t numItems = _render->m_num;
			for (int32_t item = 0, restartItem = numItems; item < numItems || restartItem < numItems;)
			{
				const bool isCompute = key.decode(_render->m_sortKeys[item], _render->m_viewRemap);
				statsKeyType[isCompute]++;

				const bool viewChanged = 0
					|| key.m_view != view
					|| item == numItems
					;
				const RenderItem& renderItem = _render->m_renderItem[_render->m_sortValues[item] ];
				++item;

				if (viewChanged)
				{
					if (1 == restartState)
					{
						restartState = 2;
						item = restartItem;
						restartItem = numItems;
						view = UINT16_MAX;
						continue;
					}

					view = key.m_view;
					programIdx = invalidHandle;

					viewRestart = ( (BGFX_VIEW_STEREO == (_render->m_viewFlags[view] & BGFX_VIEW_STEREO) ) );
					viewRestart &= hmdEnabled;

					if (viewRestart)
					{
						if (0 == restartState)
						{
							restartState = 1;
							restartItem  = item - 1;
						}

						eye = (restartState - 1) & 1;
						restartState &= 1;
					}
					else
					{
						eye = 0;
					}

					viewState.m_rect = _render->m_rect[view];

					if (viewRestart)
					{
						viewState.m_rect.m_x = eye * (viewState.m_rect.m_width+1)/2;
						viewState.m_rect.m_width /= 2;
					}


					for (; blitItem < numBlitItems && blitKey.m_view <= view; blitItem++)
					{
						if (0 != m_renderCommandEncoder)
						{
							m_renderCommandEncoder.endEncoding();
							m_renderCommandEncoder = 0;
						}
						m_blitCommandEncoder = getBlitCommandEncoder();

						const BlitItem& blit = _render->m_blitItem[blitItem];
						blitKey.decode(_render->m_blitKeys[blitItem+1]);

						const TextureMtl& src = m_textures[blit.m_src.idx];
						const TextureMtl& dst = m_textures[blit.m_dst.idx];

						uint32_t srcWidth  = bx::uint32_min(src.m_width,  blit.m_srcX + blit.m_width)  - blit.m_srcX;
						uint32_t srcHeight = bx::uint32_min(src.m_height, blit.m_srcY + blit.m_height) - blit.m_srcY;
						uint32_t srcDepth  = bx::uint32_min(src.m_depth,  blit.m_srcZ + blit.m_depth)  - blit.m_srcZ;
						uint32_t dstWidth  = bx::uint32_min(dst.m_width,  blit.m_dstX + blit.m_width)  - blit.m_dstX;
						uint32_t dstHeight = bx::uint32_min(dst.m_height, blit.m_dstY + blit.m_height) - blit.m_dstY;
						uint32_t dstDepth  = bx::uint32_min(dst.m_depth,  blit.m_dstZ + blit.m_depth)  - blit.m_dstZ;
						uint32_t width     = bx::uint32_min(srcWidth,  dstWidth);
						uint32_t height    = bx::uint32_min(srcHeight, dstHeight);
						uint32_t depth     = bx::uint32_min(srcDepth,  dstDepth);
#if BX_PLATFORM_OSX
						bool     readBack  = !!(dst.m_flags & BGFX_TEXTURE_READ_BACK);
#endif  // BX_PLATFORM_OSX

						if ( MTLTextureType3D == src.m_ptr.textureType())
						{
							m_blitCommandEncoder.copyFromTexture(src.m_ptr, 0, 0, MTLOriginMake(blit.m_srcX, blit.m_srcY, blit.m_srcZ), MTLSizeMake(width, height, bx::uint32_imax(depth, 1)),
																 dst.m_ptr, 0, 0, MTLOriginMake(blit.m_dstX, blit.m_dstY, blit.m_dstZ));
#if BX_PLATFORM_OSX
							if (m_macOS11Runtime &&readBack) {
								m_blitCommandEncoder.synchronizeResource(dst.m_ptr);
							}
#endif  // BX_PLATFORM_OSX
						}
						else
						{
							m_blitCommandEncoder.copyFromTexture(src.m_ptr, blit.m_srcZ, blit.m_srcMip, MTLOriginMake(blit.m_srcX, blit.m_srcY, 0), MTLSizeMake(width, height, 1),
																 dst.m_ptr, blit.m_dstZ, blit.m_dstMip, MTLOriginMake(blit.m_dstX, blit.m_dstY, 0));
#if BX_PLATFORM_OSX
							if (m_macOS11Runtime && readBack) {
								m_blitCommandEncoder.synchronizeTexture(dst.m_ptr, 0, blit.m_dstMip);
							}
#endif  // BX_PLATFORM_OSX
						}
					}

					if (0 != m_blitCommandEncoder)
					{
						m_blitCommandEncoder.endEncoding();
						m_blitCommandEncoder = 0;
					}

					const Rect& scissorRect = _render->m_scissor[view];
					viewHasScissor = !scissorRect.isZero();
					viewScissorRect = viewHasScissor ? scissorRect : viewState.m_rect;
					Clear& clr = _render->m_clear[view];

					Rect viewRect = viewState.m_rect;
					bool clearWithRenderPass = false;

					if ( NULL == m_renderCommandEncoder || fbh.idx != _render->m_fb[view].idx )
					{
						if (0 != m_renderCommandEncoder)
						{
							m_renderCommandEncoder.endEncoding();
						}

						RenderPassDescriptor renderPassDescriptor = newRenderPassDescriptor();
						renderPassDescriptor.visibilityResultBuffer = m_occlusionQuery.m_buffer;

						fbh = _render->m_fb[view];

						uint32_t width  = m_resolution.m_width;
						uint32_t height = m_resolution.m_height;

						if ( isValid(fbh) )
						{
							FrameBufferMtl& frameBuffer = m_frameBuffers[fbh.idx];
							width = frameBuffer.m_width;
							height = frameBuffer.m_height;
						}

						clearWithRenderPass = true
						&& 0 == viewRect.m_x
						&& 0      == viewRect.m_y
						&& width  == viewRect.m_width
						&& height == viewRect.m_height;

						setFrameBuffer(renderPassDescriptor, fbh);

						if ( clearWithRenderPass )
						{

							for(uint32_t ii = 0; ii < g_caps.limits.maxFBAttachments; ++ii)
							{
								MTLRenderPassColorAttachmentDescriptor* desc = renderPassDescriptor.colorAttachments[ii];

								if ( desc.texture != NULL)
								{
									if (0 != (BGFX_CLEAR_COLOR & clr.m_flags) )
									{
										if (0 != (BGFX_CLEAR_COLOR_USE_PALETTE & clr.m_flags) )
										{
											uint8_t index = (uint8_t)bx::uint32_min(BGFX_CONFIG_MAX_COLOR_PALETTE-1, clr.m_index[ii]);
											const float* rgba = _render->m_colorPalette[index];
											const float rr = rgba[0];
											const float gg = rgba[1];
											const float bb = rgba[2];
											const float aa = rgba[3];
											desc.clearColor = MTLClearColorMake(rr, gg, bb, aa);
										}
										else
										{
											float rr = clr.m_index[0]*1.0f/255.0f;
											float gg = clr.m_index[1]*1.0f/255.0f;
											float bb = clr.m_index[2]*1.0f/255.0f;
											float aa = clr.m_index[3]*1.0f/255.0f;
											desc.clearColor = MTLClearColorMake(rr, gg, bb, aa);
										}

										desc.loadAction = MTLLoadActionClear;
									}
									else
									{
										desc.loadAction = MTLLoadActionLoad;
									}
									desc.storeAction = desc.texture.sampleCount > 1 ? MTLStoreActionMultisampleResolve : MTLStoreActionStore;
								}
							}

							//TODO: optimize store actions use discard flag
							RenderPassDepthAttachmentDescriptor depthAttachment = renderPassDescriptor.depthAttachment;
							if (NULL != depthAttachment.texture)
							{
								depthAttachment.clearDepth = clr.m_depth;
								depthAttachment.loadAction = 0 != (BGFX_CLEAR_DEPTH & clr.m_flags)
								? MTLLoadActionClear
								: MTLLoadActionLoad
								;
								depthAttachment.storeAction = NULL != m_backBufferColorMSAA ? MTLStoreActionDontCare : MTLStoreActionStore;
							}

							RenderPassStencilAttachmentDescriptor stencilAttachment = renderPassDescriptor.stencilAttachment;
							if (NULL != stencilAttachment.texture)
							{
								stencilAttachment.clearStencil = clr.m_stencil;
								stencilAttachment.loadAction   = 0 != (BGFX_CLEAR_STENCIL & clr.m_flags)
								? MTLLoadActionClear
								: MTLLoadActionLoad
								;
								stencilAttachment.storeAction = NULL != m_backBufferColorMSAA ? MTLStoreActionDontCare : MTLStoreActionStore;
							}
						}
						else
						{
							for(uint32_t ii = 0; ii < g_caps.limits.maxFBAttachments; ++ii)
							{
								MTLRenderPassColorAttachmentDescriptor* desc = renderPassDescriptor.colorAttachments[ii];
								if ( desc.texture != NULL)
									desc.loadAction = MTLLoadActionLoad;
							}

							//TODO: optimize store actions use discard flag
							RenderPassDepthAttachmentDescriptor depthAttachment = renderPassDescriptor.depthAttachment;
							if (NULL != depthAttachment.texture)
							{
								depthAttachment.loadAction = MTLLoadActionLoad;
								depthAttachment.storeAction = MTLStoreActionStore;
							}

							RenderPassStencilAttachmentDescriptor stencilAttachment = renderPassDescriptor.stencilAttachment;
							if (NULL != stencilAttachment.texture)
							{
								stencilAttachment.loadAction   = MTLLoadActionLoad;
								stencilAttachment.storeAction  = MTLStoreActionStore;
							}
						}

						rce = m_commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor);
						m_renderCommandEncoder = rce;
						m_renderCommandEncoderFrameBufferHandle = fbh;
						MTL_RELEASE(renderPassDescriptor);
					}

					rce.setTriangleFillMode(wireframe? MTLTriangleFillModeLines : MTLTriangleFillModeFill);

					if (BX_ENABLED(BGFX_CONFIG_DEBUG_MTL) )
					{
						if (item != 1)
						{
							rce.popDebugGroup();
						}

						rce.pushDebugGroup(s_viewName[view]);
					}

					MTLViewport vp;
					vp.originX = viewState.m_rect.m_x;
					vp.originY = viewState.m_rect.m_y;
					vp.width   = viewState.m_rect.m_width;
					vp.height  = viewState.m_rect.m_height;
					vp.znear   = 0.0f;
					vp.zfar    = 1.0f;
					rce.setViewport(vp);

					if (BGFX_CLEAR_NONE != (clr.m_flags & BGFX_CLEAR_MASK)
					&& !clearWithRenderPass)
					{
						clearQuad(_clearQuad, viewState.m_rect, clr, _render->m_colorPalette);
					}
				}

				bool resetState = viewChanged || wasCompute;

				if (wasCompute)
				{
					wasCompute = false;

					programIdx = invalidHandle;
					currentProgram = NULL;

					//invalidateCompute();
				}

				const RenderDraw& draw = renderItem.draw;

				const bool hasOcclusionQuery = 0 != (draw.m_stateFlags & BGFX_STATE_INTERNAL_OCCLUSION_QUERY);
				if (isValid(draw.m_occlusionQuery)
				&&  !hasOcclusionQuery
				&&  !isVisible(_render, draw.m_occlusionQuery, 0 != (draw.m_submitFlags&BGFX_SUBMIT_INTERNAL_OCCLUSION_VISIBLE) ) )
				{
					continue;
				}

				const uint64_t newFlags = draw.m_stateFlags;
				uint64_t changedFlags = currentState.m_stateFlags ^ draw.m_stateFlags;
				currentState.m_stateFlags = newFlags;

				const uint64_t newStencil = draw.m_stencil;
				uint64_t changedStencil = currentState.m_stencil ^ draw.m_stencil;
				currentState.m_stencil = newStencil;

				if (resetState)
				{
					currentState.clear();
					currentState.m_scissor = !draw.m_scissor;
					changedFlags = BGFX_STATE_MASK;
					changedStencil = packStencil(BGFX_STENCIL_MASK, BGFX_STENCIL_MASK);
					currentState.m_stateFlags = newFlags;
					currentState.m_stencil    = newStencil;

					programIdx = invalidHandle;
					setDepthStencilState(newFlags, packStencil(BGFX_STENCIL_DEFAULT, BGFX_STENCIL_DEFAULT));

					const uint64_t pt = newFlags&BGFX_STATE_PT_MASK;
					primIndex = uint8_t(pt>>BGFX_STATE_PT_SHIFT);
				}

				if (prim.m_type != s_primInfo[primIndex].m_type)
				{
					prim = s_primInfo[primIndex];
				}

				uint16_t scissor = draw.m_scissor;
				if (currentState.m_scissor != scissor)
				{
					currentState.m_scissor = scissor;

					MTLScissorRect rc;
					if (UINT16_MAX == scissor)
					{
						if (viewHasScissor)
						{
							rc.x   = viewScissorRect.m_x;
							rc.y    = viewScissorRect.m_y;
							rc.width  = viewScissorRect.m_width;
							rc.height = viewScissorRect.m_height;
						}
						else
						{   // can't disable: set to view rect
							rc.x   = viewState.m_rect.m_x;
							rc.y    = viewState.m_rect.m_y;
							rc.width  = viewState.m_rect.m_width;
							rc.height = viewState.m_rect.m_height;
						}
					}
					else
					{
						Rect scissorRect;
						scissorRect.intersect(viewScissorRect, _render->m_rectCache.m_cache[scissor]);
						rc.x   = scissorRect.m_x;
						rc.y    = scissorRect.m_y;
						rc.width  = scissorRect.m_width;
						rc.height = scissorRect.m_height;

						if ( rc.width == 0 || rc.height == 0 )
							continue;
					}
					rce.setScissorRect(rc);
				}

				if ( (BGFX_STATE_DEPTH_WRITE|BGFX_STATE_DEPTH_TEST_MASK) & changedFlags
				|| 0 != changedStencil)
				{
					setDepthStencilState(newFlags,newStencil);
				}

				if ( (0
					 | BGFX_STATE_CULL_MASK
					 | BGFX_STATE_ALPHA_REF_MASK
					 | BGFX_STATE_PT_MASK
					 ) & changedFlags)
				{
					if (BGFX_STATE_CULL_MASK & changedFlags)
					{
						const uint64_t pt = newFlags&BGFX_STATE_CULL_MASK;
						uint8_t cullIndex = uint8_t(pt>>BGFX_STATE_CULL_SHIFT);
						rce.setCullMode(s_cullMode[cullIndex]);
					}

					if (BGFX_STATE_ALPHA_REF_MASK & changedFlags)
					{
						uint32_t ref = (newFlags&BGFX_STATE_ALPHA_REF_MASK)>>BGFX_STATE_ALPHA_REF_SHIFT;
						viewState.m_alphaRef = ref/255.0f;
					}

					const uint64_t pt = newFlags&BGFX_STATE_PT_MASK;
					primIndex = uint8_t(pt>>BGFX_STATE_PT_SHIFT);
					if (prim.m_type != s_primInfo[primIndex].m_type)
					{
						prim = s_primInfo[primIndex];
					}
				}

				if (blendFactor != draw.m_rgba
				&& !(newFlags & BGFX_STATE_BLEND_INDEPENDENT) )
				{
					const uint32_t rgba = draw.m_rgba;
					float rr = ( (rgba>>24)     )/255.0f;
					float gg = ( (rgba>>16)&0xff)/255.0f;
					float bb = ( (rgba>> 8)&0xff)/255.0f;
					float aa = ( (rgba    )&0xff)/255.0f;
					rce.setBlendColor(rr,gg,bb,aa);

					blendFactor = draw.m_rgba;
				}

				bool programChanged = false;
				bool constantsChanged = draw.m_constBegin < draw.m_constEnd;
				rendererUpdateUniforms(this, _render->m_uniformBuffer, draw.m_constBegin, draw.m_constEnd);

				if (key.m_program != programIdx
				|| (BGFX_STATE_BLEND_MASK|BGFX_STATE_BLEND_EQUATION_MASK|BGFX_STATE_ALPHA_WRITE|BGFX_STATE_RGB_WRITE|BGFX_STATE_BLEND_INDEPENDENT|BGFX_STATE_MSAA|BGFX_STATE_BLEND_ALPHA_TO_COVERAGE) & changedFlags
				||  currentState.m_streamMask             != draw.m_streamMask
				||  currentState.m_stream[0].m_handle.idx != draw.m_stream[0].m_handle.idx
				||  currentState.m_stream[0].m_decl.idx   != draw.m_stream[0].m_decl.idx
				||  currentState.m_instanceDataStride     != draw.m_instanceDataStride
				|| ( (blendFactor != draw.m_rgba) && !!(newFlags & BGFX_STATE_BLEND_INDEPENDENT) ) )
				{
					programIdx = key.m_program;
					currentState.m_streamMask         = draw.m_streamMask;
					currentState.m_stream[0].m_decl   = draw.m_stream[0].m_decl;
					currentState.m_instanceDataStride = draw.m_instanceDataStride;

					if (invalidHandle == programIdx)
					{
						currentProgram = NULL;
						continue;
					}
					else
					{
						ProgramMtl& program = m_program[programIdx];
						currentProgram = &program;

						RenderPipelineState pipelineState = NULL;

						if (isValid(draw.m_stream[0].m_handle) )
						{
							uint16_t handle = draw.m_stream[0].m_handle.idx;
							const VertexBufferMtl& vb = m_vertexBuffers[handle];
							VertexDeclHandle decl;
							decl.idx = !isValid(vb.m_decl) ? draw.m_stream[0].m_decl.idx : vb.m_decl.idx;

							pipelineState = program.getRenderPipelineState(newFlags, draw.m_rgba, fbh, decl, draw.m_instanceDataStride/16);
						}

						if (NULL == pipelineState)
						{
							currentProgram = NULL;
							programIdx = invalidHandle;
							continue;
						}

						rce.setRenderPipelineState(pipelineState);
					}

					programChanged =
					constantsChanged = true;
				}

				if (invalidHandle != programIdx)
				{
					ProgramMtl& program = m_program[programIdx];

					uint32_t vertexUniformBufferSize   = program.m_vshConstantBufferSize;
					uint32_t fragmentUniformBufferSize = program.m_fshConstantBufferSize;

					if (vertexUniformBufferSize)
					{
						m_uniformBufferVertexOffset = BX_ALIGN_MASK(m_uniformBufferVertexOffset, program.m_vshConstantBufferAlignmentMask);
						rce.setVertexBuffer(m_uniformBuffer, m_uniformBufferVertexOffset, 0);
					}

					m_uniformBufferFragmentOffset = m_uniformBufferVertexOffset + vertexUniformBufferSize;
					if (fragmentUniformBufferSize)
					{
						m_uniformBufferFragmentOffset = BX_ALIGN_MASK(m_uniformBufferFragmentOffset, program.m_fshConstantBufferAlignmentMask);
						rce.setFragmentBuffer(m_uniformBuffer, m_uniformBufferFragmentOffset, 0);
					}

					if (constantsChanged)
					{
						UniformBuffer* vcb = program.m_vshConstantBuffer;
						if (NULL != vcb)
						{
							commit(*vcb);
						}

						UniformBuffer* fcb = program.m_fshConstantBuffer;
						if (NULL != fcb)
						{
							commit(*fcb);
						}
					}

					viewState.setPredefined<4>(this, view, eye, program, _render, draw);

					m_uniformBufferFragmentOffset += fragmentUniformBufferSize;
					m_uniformBufferVertexOffset = m_uniformBufferFragmentOffset;
				}

				{
					uint32_t usedVertexSamplerStages = 0;
					uint32_t usedFragmentSamplerStages = 0;

					if (invalidHandle != programIdx)
					{
						ProgramMtl& program = m_program[programIdx];
						usedVertexSamplerStages = program.m_usedVertexSamplerStages;
						usedFragmentSamplerStages = program.m_usedFragmentSamplerStages;
					}

					for (uint8_t stage = 0; stage < BGFX_CONFIG_MAX_TEXTURE_SAMPLERS; ++stage)
					{
						const Binding& sampler = draw.m_bind[stage];
						Binding& current = currentState.m_bind[stage];
						if (current.m_idx != sampler.m_idx
						||  current.m_un.m_draw.m_textureFlags != sampler.m_un.m_draw.m_textureFlags
						||  programChanged)
						{
							if (invalidHandle != sampler.m_idx)
							{
								TextureMtl& texture = m_textures[sampler.m_idx];
								texture.commit(stage, (usedVertexSamplerStages&(1<<stage))!=0,
											   (usedFragmentSamplerStages&(1<<stage))!=0,
												sampler.m_un.m_draw.m_textureFlags);
							}
						}

						current = sampler;
					}
				}

				if (currentState.m_streamMask              != draw.m_streamMask
				||  currentState.m_stream[0].m_handle.idx  != draw.m_stream[0].m_handle.idx
				||  currentState.m_stream[0].m_startVertex != draw.m_stream[0].m_startVertex
				||  currentState.m_instanceDataBuffer.idx  != draw.m_instanceDataBuffer.idx
				||  currentState.m_instanceDataOffset      != draw.m_instanceDataOffset)
				{
					currentState.m_streamMask               = draw.m_streamMask;
					currentState.m_stream[0].m_handle       = draw.m_stream[0].m_handle;
					currentState.m_stream[0].m_startVertex  = draw.m_stream[0].m_startVertex;
					currentState.m_instanceDataBuffer.idx   = draw.m_instanceDataBuffer.idx;
					currentState.m_instanceDataOffset       = draw.m_instanceDataOffset;

					uint16_t handle = draw.m_stream[0].m_handle.idx;
					if (invalidHandle != handle)
					{
						const VertexBufferMtl& vb = m_vertexBuffers[handle];

						uint16_t decl = !isValid(vb.m_decl) ? draw.m_stream[0].m_decl.idx : vb.m_decl.idx;
						const VertexDecl& vertexDecl = m_vertexDecls[decl];
						uint32_t offset = draw.m_stream[0].m_startVertex  * vertexDecl.getStride();

						rce.setVertexBuffer(vb.getBuffer(), offset, 1);

						if (isValid(draw.m_instanceDataBuffer) )
						{
							const VertexBufferMtl& inst = m_vertexBuffers[draw.m_instanceDataBuffer.idx];
							rce.setVertexBuffer(inst.getBuffer(), draw.m_instanceDataOffset, 2);
						}
					}
				}

				if (0 != currentState.m_streamMask)
				{
					uint32_t numVertices = draw.m_numVertices;
					if (UINT32_MAX == numVertices)
					{
						const VertexBufferMtl& vb = m_vertexBuffers[currentState.m_stream[0].m_handle.idx];
						uint16_t decl = !isValid(vb.m_decl) ? draw.m_stream[0].m_decl.idx : vb.m_decl.idx;
						const VertexDecl& vertexDecl = m_vertexDecls[decl];
						numVertices = vb.m_size/vertexDecl.m_stride;
					}

					uint32_t numIndices        = 0;
					uint32_t numPrimsSubmitted = 0;
					uint32_t numInstances      = 0;
					uint32_t numPrimsRendered  = 0;
					uint32_t numDrawIndirect   = 0;

					if (hasOcclusionQuery)
					{
						m_occlusionQuery.begin(rce, _render, draw.m_occlusionQuery);
					}

					if (isValid(draw.m_indirectBuffer) )
					{
					}
					else
					{
						if (isValid(draw.m_indexBuffer) )
						{
							const IndexBufferMtl& ib = m_indexBuffers[draw.m_indexBuffer.idx];
							MTLIndexType indexType = 0 == (ib.m_flags & BGFX_BUFFER_INDEX32) ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32;

							if (UINT32_MAX == draw.m_numIndices)
							{
								const uint32_t indexSize = 0 == (ib.m_flags & BGFX_BUFFER_INDEX32) ? 2 : 4;
								numIndices        = ib.m_size/indexSize;
								numPrimsSubmitted = numIndices/prim.m_div - prim.m_sub;
								numInstances      = draw.m_numInstances;
								numPrimsRendered  = numPrimsSubmitted*draw.m_numInstances;

								rce.drawIndexedPrimitives(prim.m_type, numIndices, indexType, ib.getBuffer(), 0, draw.m_numInstances);
							}
							else if (prim.m_min <= draw.m_numIndices)
							{
								const uint32_t indexSize = 0 == (ib.m_flags & BGFX_BUFFER_INDEX32) ? 2 : 4;
								numIndices        = draw.m_numIndices;
								numPrimsSubmitted = numIndices/prim.m_div - prim.m_sub;
								numInstances      = draw.m_numInstances;
								numPrimsRendered  = numPrimsSubmitted*draw.m_numInstances;

								rce.drawIndexedPrimitives(prim.m_type, numIndices, indexType, ib.getBuffer(), draw.m_startIndex * indexSize,numInstances);
							}
						}
						else
						{
							numPrimsSubmitted = numVertices/prim.m_div - prim.m_sub;
							numInstances      = draw.m_numInstances;
							numPrimsRendered  = numPrimsSubmitted*draw.m_numInstances;

							rce.drawPrimitives(prim.m_type, 0, draw.m_numVertices, draw.m_numInstances);
						}
					}

					if (hasOcclusionQuery)
					{
						m_occlusionQuery.end(rce);
					}

					statsNumPrimsSubmitted[primIndex] += numPrimsSubmitted;
					statsNumPrimsRendered[primIndex]  += numPrimsRendered;
					statsNumInstances[primIndex]      += numInstances;
					statsNumDrawIndirect[primIndex]   += numDrawIndirect;
					statsNumIndices                   += numIndices;
				}
			}

			if (wasCompute)
			{
				//invalidateCompute();
			}

			if (0 < _render->m_num)
			{
				captureElapsed = -bx::getHPCounter();
				capture();
				rce = m_renderCommandEncoder; //TODO: ugly, can create new encoder
				captureElapsed += bx::getHPCounter();
			}
		}

		if (BX_ENABLED(BGFX_CONFIG_DEBUG_MTL) )
		{
			if (0 < _render->m_num)
			{
				rce.popDebugGroup();
			}
		}

		int64_t now = bx::getHPCounter();
		elapsed += now;

		static int64_t last = now;

		Stats& perfStats = _render->m_perfStats;
		perfStats.cpuTimeBegin = last;

		int64_t frameTime = now - last;
		last = now;

		static int64_t min = frameTime;
		static int64_t max = frameTime;
		min = bx::int64_min(min, frameTime);
		max = bx::int64_max(max, frameTime);

		static uint32_t maxGpuLatency = 0;
		static double   maxGpuElapsed = 0.0f;
		double elapsedGpuMs = 0.0;

		do
		{
			double toGpuMs = 1000.0 / double(m_gpuTimer.m_frequency);
			elapsedGpuMs   = m_gpuTimer.m_elapsed * toGpuMs;
			maxGpuElapsed  = elapsedGpuMs > maxGpuElapsed ? elapsedGpuMs : maxGpuElapsed;
		}
		while (m_gpuTimer.get() );

		maxGpuLatency = bx::uint32_imax(maxGpuLatency, m_gpuTimer.m_control.available()-1);

		const int64_t timerFreq = bx::getHPFrequency();

		perfStats.cpuTimeEnd   = now;
		perfStats.cpuTimerFreq = timerFreq;
		perfStats.gpuTimeBegin = m_gpuTimer.m_begin;
		perfStats.gpuTimeEnd   = m_gpuTimer.m_end;
		perfStats.gpuTimerFreq = m_gpuTimer.m_frequency;

		rce.setTriangleFillMode(MTLTriangleFillModeFill);
		if (_render->m_debug & (BGFX_DEBUG_IFH|BGFX_DEBUG_STATS) )
		{
			rce.pushDebugGroup("debugstats");

			TextVideoMem& tvm = m_textVideoMem;

			static int64_t next = now;

			if (now >= next)
			{
				next = now + bx::getHPFrequency();

				double freq = double(bx::getHPFrequency() );
				double toMs = 1000.0/freq;

				tvm.clear();
				uint16_t pos = 0;
				tvm.printf(0, pos++, BGFX_CONFIG_DEBUG ? 0x89 : 0x8f, " %s / " BX_COMPILER_NAME " / " BX_CPU_NAME " / " BX_ARCH_NAME " / " BX_PLATFORM_NAME " "
						, getRendererName()
						);

				pos = 10;
				tvm.printf(10, pos++, 0x8e, "        Frame: %7.3f, % 7.3f \x1f, % 7.3f \x1e [ms] / % 6.2f FPS "
						, double(frameTime)*toMs
						, double(min)*toMs
						, double(max)*toMs
						, freq/frameTime
						);

				const uint32_t msaa = (m_resolution.m_flags&BGFX_RESET_MSAA_MASK)>>BGFX_RESET_MSAA_SHIFT;
				tvm.printf(10, pos++, 0x8e, "  Reset flags: [%c] vsync, [%c] MSAAx%d, [%c] MaxAnisotropy "
						, !!(m_resolution.m_flags&BGFX_RESET_VSYNC) ? '\xfe' : ' '
						, 0 != msaa ? '\xfe' : ' '
						, 1<<msaa
						, !!(m_resolution.m_flags&BGFX_RESET_MAXANISOTROPY) ? '\xfe' : ' '
						);

				double elapsedCpuMs = double(elapsed)*toMs;
				tvm.printf(10, pos++, 0x8e, "    Submitted: %4d (draw %4d, compute %4d) / CPU %3.4f [ms] %c GPU %3.4f [ms] (latency %d)"
						, _render->m_num
						, statsKeyType[0]
						, statsKeyType[1]
						, elapsedCpuMs
						, elapsedCpuMs > maxGpuElapsed ? '>' : '<'
						, maxGpuElapsed
						, maxGpuLatency
						);
				maxGpuLatency = 0;
				maxGpuElapsed = 0.0;

				for (uint32_t ii = 0; ii < BX_COUNTOF(s_primName); ++ii)
				{
					tvm.printf(10, pos++, 0x8e, "   %10s: %7d (#inst: %5d), submitted: %7d"
							, s_primName[ii]
							, statsNumPrimsRendered[ii]
							, statsNumInstances[ii]
							, statsNumPrimsSubmitted[ii]
							);
				}

				tvm.printf(10, pos++, 0x8e, "      Indices: %7d ", statsNumIndices);
				tvm.printf(10, pos++, 0x8e, " Uniform size: %7d, Max: %7d ", _render->m_uniformEnd, _render->m_uniformMax);
				tvm.printf(10, pos++, 0x8e, "     DVB size: %7d ", _render->m_vboffset);
				tvm.printf(10, pos++, 0x8e, "     DIB size: %7d ", _render->m_iboffset);

				pos++;
				double captureMs = double(captureElapsed)*toMs;
				tvm.printf(10, pos++, 0x8e, "     Capture: %3.4f [ms]", captureMs);

				uint8_t attr[2] = { 0x89, 0x8a };
				uint8_t attrIndex = _render->m_waitSubmit < _render->m_waitRender;

				tvm.printf(10, pos++, attr[attrIndex&1], " Submit wait: %3.4f [ms]", _render->m_waitSubmit*toMs);
				tvm.printf(10, pos++, attr[(attrIndex+1)&1], " Render wait: %3.4f [ms]", _render->m_waitRender*toMs);

				min = frameTime;
				max = frameTime;
			}

			blit(this, _textVideoMemBlitter, tvm);
			rce = m_renderCommandEncoder; //TODO: ugly, blit can create encoder

			rce.popDebugGroup();
		}
		else if (_render->m_debug & BGFX_DEBUG_TEXT)
		{
			rce.pushDebugGroup("debugtext");

			blit(this, _textVideoMemBlitter, _render->m_textVideoMem);
			rce = m_renderCommandEncoder; //TODO: ugly, blit can create encoder

			rce.popDebugGroup();
		}

		rce.endEncoding();
		m_renderCommandEncoder = 0;
		m_renderCommandEncoderFrameBufferHandle.idx = invalidHandle;


		if ( m_screenshotTarget )
		{
			RenderPassDescriptor renderPassDescriptor = newRenderPassDescriptor();
			renderPassDescriptor.colorAttachments[0].texture = currentDrawable().texture;
			renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;

			rce =  m_commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor);

			rce.setCullMode(MTLCullModeNone);

			rce.setRenderPipelineState(m_screenshotBlitRenderPipelineState);

			rce.setFragmentSamplerState(getSamplerState(BGFX_TEXTURE_U_CLAMP|BGFX_TEXTURE_V_CLAMP|BGFX_TEXTURE_MIN_POINT|BGFX_TEXTURE_MAG_POINT|BGFX_TEXTURE_MIP_POINT), 0);
			rce.setFragmentTexture(m_screenshotTarget, 0);

			rce.drawPrimitives(MTLPrimitiveTypeTriangle, 0, 3, 1);

			rce.endEncoding();
		 }
	}

} /* namespace mtl */ } // namespace bgfx

#else

namespace bgfx { namespace mtl
	{
		RendererContextI* rendererCreate()
		{
			return NULL;
		}

		void rendererDestroy()
		{
		}
	} /* namespace mtl */ } // namespace bgfx

#endif // BGFX_CONFIG_RENDERER_METAL
