Browse Source

metal: partial implementation of pipeline states.

Add BGRA8 pixel format.
Alex Szpakowski 5 years ago
parent
commit
9765485222

+ 24 - 9
src/common/pixelformat.cpp

@@ -44,7 +44,9 @@ static PixelFormatInfo formatInfo[] =
 	{ 2, 1, 1, 8, true, false, false, false }, // PIXELFORMAT_RG32_FLOAT
 
 	{ 4, 1, 1, 4,  true, false, false, false }, // PIXELFORMAT_RGBA8_UNORM
-	{ 4, 1, 1, 4,  true, false, false, false }, // PIXELFORMAT_sRGBA8_UNORM
+	{ 4, 1, 1, 4,  true, false, false, false }, // PIXELFORMAT_RGBA8_UNORM_sRGB
+	{ 4, 1, 1, 4,  true, false, false, false }, // PIXELFORMAT_BGRA8_UNORM
+	{ 4, 1, 1, 4,  true, false, false, false }, // PIXELFORMAT_BGRA8_UNORM_sRGB
 	{ 4, 1, 1, 8,  true, false, false, false }, // PIXELFORMAT_RGBA16_UNORM
 	{ 4, 1, 1, 8,  true, false, false, false }, // PIXELFORMAT_RGBA16_FLOAT
 	{ 4, 1, 1, 16, true, false, false, false }, // PIXELFORMAT_RGBA32_FLOAT
@@ -123,11 +125,13 @@ static StringMap<PixelFormat, PIXELFORMAT_MAX_ENUM>::Entry formatEntries[] =
 	{ "rg16f",   PIXELFORMAT_RG16_FLOAT   },
 	{ "rg32f",   PIXELFORMAT_RG32_FLOAT   },
 
-	{ "rgba8",   PIXELFORMAT_RGBA8_UNORM  },
-	{ "srgba8",  PIXELFORMAT_sRGBA8_UNORM },
-	{ "rgba16",  PIXELFORMAT_RGBA16_UNORM },
-	{ "rgba16f", PIXELFORMAT_RGBA16_FLOAT },
-	{ "rgba32f", PIXELFORMAT_RGBA32_FLOAT },
+	{ "rgba8",     PIXELFORMAT_RGBA8_UNORM      },
+	{ "srgba8",    PIXELFORMAT_RGBA8_UNORM_sRGB },
+	{ "bgra8",     PIXELFORMAT_RGBA8_UNORM      },
+	{ "bgra8srgb", PIXELFORMAT_RGBA8_UNORM_sRGB },
+	{ "rgba16",    PIXELFORMAT_RGBA16_UNORM     },
+	{ "rgba16f",   PIXELFORMAT_RGBA16_FLOAT     },
+	{ "rgba32f",   PIXELFORMAT_RGBA32_FLOAT     },
 
 	{ "rgba4",    PIXELFORMAT_RGBA4_UNORM    },
 	{ "rgb5a1",   PIXELFORMAT_RGB5A1_UNORM   },
@@ -220,17 +224,26 @@ bool isPixelFormatStencil(PixelFormat format)
 	return formatInfo[format].stencil;
 }
 
+bool isPixelFormatSRGB(PixelFormat format)
+{
+	return format == PIXELFORMAT_RGBA8_UNORM_sRGB || format == PIXELFORMAT_BGRA8_UNORM_sRGB;
+}
+
 PixelFormat getSRGBPixelFormat(PixelFormat format)
 {
 	if (format == PIXELFORMAT_RGBA8_UNORM)
-		return PIXELFORMAT_sRGBA8_UNORM;
+		return PIXELFORMAT_RGBA8_UNORM_sRGB;
+	else if (format == PIXELFORMAT_BGRA8_UNORM)
+		return PIXELFORMAT_BGRA8_UNORM_sRGB;
 	return format;
 }
 
 PixelFormat getLinearPixelFormat(PixelFormat format)
 {
-	if (format == PIXELFORMAT_sRGBA8_UNORM)
+	if (format == PIXELFORMAT_RGBA8_UNORM_sRGB)
 		return PIXELFORMAT_RGBA8_UNORM;
+	else if (format == PIXELFORMAT_BGRA8_UNORM_sRGB)
+		return PIXELFORMAT_BGRA8_UNORM;
 	return format;
 }
 
@@ -285,7 +298,9 @@ size_t getPixelFormatRowStride(PixelFormat format, int width)
 	case PIXELFORMAT_DEPTH16_UNORM:
 		return 2 * width;
 	case PIXELFORMAT_RGBA8_UNORM:
-	case PIXELFORMAT_sRGBA8_UNORM:
+	case PIXELFORMAT_RGBA8_UNORM_sRGB:
+	case PIXELFORMAT_BGRA8_UNORM:
+	case PIXELFORMAT_BGRA8_UNORM_sRGB:
 	case PIXELFORMAT_RG16_UNORM:
 	case PIXELFORMAT_RG16_FLOAT:
 	case PIXELFORMAT_R32_FLOAT:

+ 8 - 1
src/common/pixelformat.h

@@ -48,7 +48,9 @@ enum PixelFormat
 
 	// 4-channel normal formats
 	PIXELFORMAT_RGBA8_UNORM,
-	PIXELFORMAT_sRGBA8_UNORM,
+	PIXELFORMAT_RGBA8_UNORM_sRGB,
+	PIXELFORMAT_BGRA8_UNORM,
+	PIXELFORMAT_BGRA8_UNORM_sRGB,
 	PIXELFORMAT_RGBA16_UNORM,
 	PIXELFORMAT_RGBA16_FLOAT,
 	PIXELFORMAT_RGBA32_FLOAT,
@@ -146,6 +148,11 @@ bool isPixelFormatDepth(PixelFormat format);
  **/
 bool isPixelFormatStencil(PixelFormat format);
 
+/**
+ * Gets whether the specified color pixel format is sRGB-encoded.
+ **/
+bool isPixelFormatSRGB(PixelFormat format);
+
 /**
  * Gets the sRGB version of a linear pixel format, if applicable.
  **/

+ 2 - 2
src/modules/graphics/Graphics.cpp

@@ -657,7 +657,7 @@ void Graphics::setRenderTargets(const RenderTargets &rts)
 	if (!firsttex->isValidSlice(firsttarget.slice))
 		throw love::Exception("Invalid slice index: %d.", firsttarget.slice + 1);
 
-	bool hasSRGBtexture = firstcolorformat == PIXELFORMAT_sRGBA8_UNORM;
+	bool hasSRGBtexture = isPixelFormatSRGB(firstcolorformat);
 	int pixelw = firsttex->getPixelWidth(firsttarget.mipmap);
 	int pixelh = firsttex->getPixelHeight(firsttarget.mipmap);
 	int reqmsaa = firsttex->getRequestedMSAA();
@@ -690,7 +690,7 @@ void Graphics::setRenderTargets(const RenderTargets &rts)
 		if (isPixelFormatDepthStencil(format))
 			throw love::Exception("Depth/stencil format textures must be used with the 'depthstencil' field of the table passed into setRenderTargets.");
 
-		if (format == PIXELFORMAT_sRGBA8_UNORM)
+		if (isPixelFormatSRGB(format))
 			hasSRGBtexture = true;
 	}
 

+ 1 - 1
src/modules/graphics/Texture.cpp

@@ -558,7 +558,7 @@ bool Texture::isCompressed() const
 
 bool Texture::isFormatLinear() const
 {
-	return isGammaCorrect() && !sRGB && format != PIXELFORMAT_sRGBA8_UNORM;
+	return isGammaCorrect() && !sRGB && !isPixelFormatSRGB(format);
 }
 
 bool Texture::isValidSlice(int slice) const

+ 0 - 9
src/modules/graphics/metal/Graphics.h

@@ -155,14 +155,6 @@ private:
 		Shader *shader;
 	};
 
-	struct PipelineState
-	{
-		vertex::Attributes vertexAttributes;
-		BlendState blend;
-		ColorChannelMask colorChannelMask;
-		Shader *shader;
-	};
-
 	love::graphics::ShaderStage *newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles) override;
 	love::graphics::Shader *newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel) override;
 	love::graphics::StreamBuffer *newStreamBuffer(BufferType type, size_t size) override;
@@ -172,7 +164,6 @@ private:
 
 	void endPass();
 
-	id<MTLRenderPipelineState> getCachedRenderPipelineState(const PipelineState &state);
 	id<MTLDepthStencilState> getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil);
 	void applyRenderState(id<MTLRenderCommandEncoder> renderEncoder);
 

+ 29 - 97
src/modules/graphics/metal/Graphics.mm

@@ -23,6 +23,7 @@
 #include "Buffer.h"
 #include "Texture.h"
 #include "Shader.h"
+#include "ShaderStage.h"
 #include "window/Window.h"
 #include "image/Image.h"
 
@@ -93,46 +94,6 @@ static MTLCompareFunction getMTLCompareFunction(CompareMode mode)
 	return MTLCompareFunctionNever;
 }
 
-static MTLVertexFormat getMTLVertexFormat(vertex::DataType type, int components)
-{
-	// TODO
-	return MTLVertexFormatFloat4;
-}
-
-static MTLBlendOperation getMTLBlendOperation(BlendOperation op)
-{
-	switch (op)
-	{
-		case BLENDOP_ADD: return MTLBlendOperationAdd;
-		case BLENDOP_SUBTRACT: return MTLBlendOperationSubtract;
-		case BLENDOP_REVERSE_SUBTRACT: return MTLBlendOperationReverseSubtract;
-		case BLENDOP_MIN: return MTLBlendOperationMin;
-		case BLENDOP_MAX: return MTLBlendOperationMax;
-		case BLENDOP_MAX_ENUM: return MTLBlendOperationAdd;
-	}
-	return MTLBlendOperationAdd;
-}
-
-static MTLBlendFactor getMTLBlendFactor(BlendFactor factor)
-{
-	switch (factor)
-	{
-		case BLENDFACTOR_ZERO: return MTLBlendFactorZero;
-		case BLENDFACTOR_ONE: return MTLBlendFactorOne;
-		case BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
-		case BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
-		case BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
-		case BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
-		case BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
-		case BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
-		case BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
-		case BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
-		case BLENDFACTOR_SRC_ALPHA_SATURATED: return MTLBlendFactorSourceAlphaSaturated;
-		case BLENDFACTOR_MAX_ENUM: return MTLBlendFactorZero;
-	}
-	return MTLBlendFactorZero;
-}
-
 love::graphics::Graphics *createInstance()
 {
 	love::graphics::Graphics *instance = nullptr;
@@ -215,7 +176,7 @@ love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings,
 
 love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles)
 {
-	return nullptr; // TODO: new ShaderStage(this, stage, source, gles, cachekey);
+	return new ShaderStage(this, stage, source, gles, cachekey);
 }
 
 love::graphics::Shader *Graphics::newShaderInternal(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel)
@@ -253,6 +214,10 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 	this->windowHasStencil = windowhasstencil;
 
 	metalLayer.device = device;
+	metalLayer.pixelFormat = isGammaCorrect() ? MTLPixelFormatBGRA8Unorm_sRGB : MTLPixelFormatBGRA8Unorm;
+
+	// TODO: set to NO when we have pending screen captures
+	metalLayer.framebufferOnly = YES;
 
 	setViewportSize(width, height, pixelwidth, pixelheight);
 
@@ -281,14 +246,14 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 		if (!Shader::standardShaders[i])
 		{
 			const auto &code = defaultShaderCode[i][target][gammacorrect];
-//			Shader::standardShaders[i] = love::graphics::Graphics::newShader(code.source[ShaderStage::STAGE_VERTEX], code.source[ShaderStage::STAGE_PIXEL]);
+			Shader::standardShaders[i] = love::graphics::Graphics::newShader(code.source[ShaderStage::STAGE_VERTEX], code.source[ShaderStage::STAGE_PIXEL]);
 		}
 	}
 
 	// A shader should always be active, but the default shader shouldn't be
 	// returned by getShader(), so we don't do setShader(defaultShader).
-//	if (!Shader::current)
-//		Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach();
+	if (!Shader::current)
+		Shader::standardShaders[Shader::STANDARD_DEFAULT]->attach();
 
 	return true;
 }}
@@ -347,7 +312,7 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 		const auto &rts = states.back().renderTargets;
 		if (rts.getFirstTarget().texture.get() != nullptr)
 		{
-
+			// TODO
 		}
 		else
 		{
@@ -431,50 +396,6 @@ id<MTLSamplerState> Graphics::getCachedSampler(const SamplerState &s)
 	return sampler;
 }}
 
-id<MTLRenderPipelineState> Graphics::getCachedRenderPipelineState(const PipelineState &state)
-{
-	MTLRenderPipelineDescriptor *pipedesc = [MTLRenderPipelineDescriptor new];
-
-	MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor];
-
-	const auto &attributes = state.vertexAttributes;
-	uint32 allbits = attributes.enableBits;
-	uint32 i = 0;
-	while (allbits)
-	{
-		uint32 bit = 1u << i;
-
-		if (attributes.enableBits & bit)
-		{
-			const auto &attrib = attributes.attribs[i];
-
-			vertdesc.attributes[i].format = getMTLVertexFormat(attrib.type, attrib.components);
-			vertdesc.attributes[i].offset = attrib.offsetFromVertex;
-			vertdesc.attributes[i].bufferIndex = attrib.bufferIndex;
-
-			const auto &layout = attributes.bufferLayouts[attrib.bufferIndex];
-
-			bool instanced = attributes.instanceBits & (1u << attrib.bufferIndex);
-			auto step = instanced ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
-
-			vertdesc.layouts[attrib.bufferIndex].stride = layout.stride;
-			vertdesc.layouts[attrib.bufferIndex].stepFunction = step;
-		}
-
-		i++;
-		allbits >>= 1;
-	}
-
-	pipedesc.vertexDescriptor = vertdesc;
-
-//	pipedesc.
-
-	NSError *err = nil;
-	id<MTLRenderPipelineState> pipestate = [device newRenderPipelineStateWithDescriptor:pipedesc error:&err];
-
-	return pipestate;
-}
-
 id<MTLDepthStencilState> Graphics::getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil)
 {
 	id<MTLDepthStencilState> state = nil;
@@ -573,20 +494,31 @@ void Graphics::applyRenderState(id<MTLRenderCommandEncoder> encoder)
 	// TODO: attributes
 	if (dirtyState & pipelineStateBits)
 	{
-		if (dirtyState & STATEBIT_BLEND)
+//		Shader *shader = (Shader *) state.shader.get();
+		Shader *shader = (Shader *) Shader::current;
+		id<MTLRenderPipelineState> pipeline = nil;
+
+		if (shader)
 		{
+			Shader::RenderPipelineKey key;
 
-		}
+			key.blend = state.blend;
+			key.colorChannelMask = state.colorMask;
 
-		if (dirtyState & STATEBIT_SHADER)
-		{
+			const auto &rts = state.renderTargets.colors;
 
-		}
+			for (size_t i = 0; i < rts.size(); i++)
+				key.colorRenderTargetFormats |= (rts[i].texture->getPixelFormat()) << (8 * i);
 
-		if (dirtyState & STATEBIT_COLORMASK)
-		{
+			if (state.renderTargets.getFirstTarget().texture.get() == nullptr)
+				key.colorRenderTargetFormats = isGammaCorrect() ? PIXELFORMAT_BGRA8_UNORM_sRGB : PIXELFORMAT_BGRA8_UNORM;
 
+			// TODO: depth/stencil
+
+			pipeline = shader->getCachedRenderPipeline(key);
 		}
+
+		[encoder setRenderPipelineState:pipeline];
 	}
 
 	if (dirtyState & (STATEBIT_DEPTH | STATEBIT_STENCIL))
@@ -1072,7 +1004,7 @@ PixelFormat Graphics::getSizedFormat(PixelFormat format, bool /*rendertarget*/,
 	{
 	case PIXELFORMAT_NORMAL:
 		if (isGammaCorrect())
-			return PIXELFORMAT_sRGBA8_UNORM;
+			return PIXELFORMAT_RGBA8_UNORM_sRGB;
 		else
 			return PIXELFORMAT_RGBA8_UNORM;
 	case PIXELFORMAT_HDR:

+ 10 - 4
src/modules/graphics/metal/Metal.mm

@@ -32,10 +32,10 @@ MTLPixelFormat Metal::convertPixelFormat(PixelFormat format, bool &isSRGB)
 {
 	MTLPixelFormat mtlformat = MTLPixelFormatRGBA8Unorm;
 
-	if (format == PIXELFORMAT_RGBA8_UNORM && isSRGB)
-		format = PIXELFORMAT_sRGBA8_UNORM;
+	if (isSRGB)
+		format = getSRGBPixelFormat(format);
 
-	if (!isPixelFormatCompressed(format) && format != PIXELFORMAT_sRGBA8_UNORM)
+	if (!isPixelFormatCompressed(format) && !isPixelFormatSRGB(format))
 		isSRGB = false;
 
 	switch (format)
@@ -49,9 +49,15 @@ MTLPixelFormat Metal::convertPixelFormat(PixelFormat format, bool &isSRGB)
 	case PIXELFORMAT_RGBA8_UNORM:
 		mtlformat = MTLPixelFormatRGBA8Unorm;
 		break;
-	case PIXELFORMAT_sRGBA8_UNORM:
+	case PIXELFORMAT_RGBA8_UNORM_sRGB:
 		mtlformat = MTLPixelFormatRGBA8Unorm_sRGB;
 		break;
+	case PIXELFORMAT_BGRA8_UNORM:
+		mtlformat = MTLPixelFormatBGRA8Unorm;
+		break;
+	case PIXELFORMAT_BGRA8_UNORM_sRGB:
+		mtlformat = MTLPixelFormatBGRA8Unorm_sRGB;
+		break;
 	case PIXELFORMAT_R16_UNORM:
 		mtlformat = MTLPixelFormatR16Unorm;
 		break;

+ 42 - 2
src/modules/graphics/metal/Shader.h

@@ -20,10 +20,18 @@
 
 #pragma once
 
+#include "libraries/xxHash/xxhash.h"
 #include "graphics/Shader.h"
 #include "graphics/Graphics.h"
+#include "graphics/renderstate.h"
+#include "graphics/vertex.h"
 #include "Metal.h"
 
+#import <Metal/MTLRenderPipeline.h>
+
+#include <unordered_map>
+#include <string>
+
 namespace love
 {
 namespace graphics
@@ -35,11 +43,31 @@ class Shader final : public love::graphics::Shader
 {
 public:
 
+	struct RenderPipelineKey
+	{
+		vertex::Attributes vertexAttributes;
+		BlendState blend;
+		uint64 colorRenderTargetFormats;
+		uint32 depthStencilFormat;
+		ColorChannelMask colorChannelMask;
+		uint8 msaa;
+
+		RenderPipelineKey()
+		{
+			memset(this, 0, sizeof(RenderPipelineKey));
+		}
+
+		bool operator == (const RenderPipelineKey &other) const
+		{
+			return memcmp(this, &other, sizeof(RenderPipelineKey)) == 0;
+		}
+	};
+
 	Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel);
 	virtual ~Shader();
 
 	// Implements Shader.
-	void attach() override {}
+	void attach() override;
 	std::string getWarnings() const override { return ""; }
 	int getVertexAttributeIndex(const std::string &name) override { return -1; }
 	const UniformInfo *getUniformInfo(const std::string &name) const override { return nullptr; }
@@ -50,9 +78,21 @@ public:
 	ptrdiff_t getHandle() const override { return 0; }
 	void setVideoTextures(love::graphics::Texture *ytexture, love::graphics::Texture *cbtexture, love::graphics::Texture *crtexture) override {}
 
+	id<MTLRenderPipelineState> getCachedRenderPipeline(const RenderPipelineKey &key);
+
 private:
 
-	id<MTLLibrary> library;
+	struct RenderPipelineHasher
+	{
+		size_t operator() (const RenderPipelineKey &key) const
+		{
+			return XXH32(&key, sizeof(RenderPipelineKey), 0);
+		}
+	};
+
+	id<MTLFunction> functions[ShaderStage::STAGE_MAX_ENUM];
+
+	std::unordered_map<RenderPipelineKey, const void *, RenderPipelineHasher> cachedRenderPipelines;
 
 }; // Metal
 

+ 185 - 2
src/modules/graphics/metal/Shader.mm

@@ -25,6 +25,9 @@
 #include "libraries/glslang/glslang/Public/ShaderLang.h"
 #include "libraries/glslang/SPIRV/GlslangToSpv.h"
 #include "libraries/spirv_cross/spirv_msl.hpp"
+#include "libraries/spirv_cross/spirv_reflect.hpp"
+
+#include <algorithm>
 
 namespace love
 {
@@ -33,8 +36,62 @@ namespace graphics
 namespace metal
 {
 
+static_assert(MAX_COLOR_RENDER_TARGETS <= 8, "Metal pipeline cache key only stores 8 render target pixel formats.");
+
+static MTLVertexFormat getMTLVertexFormat(vertex::DataType type, int components)
+{
+	// TODO
+	return MTLVertexFormatFloat4;
+}
+
+static MTLBlendOperation getMTLBlendOperation(BlendOperation op)
+{
+	switch (op)
+	{
+		case BLENDOP_ADD: return MTLBlendOperationAdd;
+		case BLENDOP_SUBTRACT: return MTLBlendOperationSubtract;
+		case BLENDOP_REVERSE_SUBTRACT: return MTLBlendOperationReverseSubtract;
+		case BLENDOP_MIN: return MTLBlendOperationMin;
+		case BLENDOP_MAX: return MTLBlendOperationMax;
+		case BLENDOP_MAX_ENUM: return MTLBlendOperationAdd;
+	}
+	return MTLBlendOperationAdd;
+}
+
+static MTLBlendFactor getMTLBlendFactor(BlendFactor factor)
+{
+	switch (factor)
+	{
+		case BLENDFACTOR_ZERO: return MTLBlendFactorZero;
+		case BLENDFACTOR_ONE: return MTLBlendFactorOne;
+		case BLENDFACTOR_SRC_COLOR: return MTLBlendFactorSourceColor;
+		case BLENDFACTOR_ONE_MINUS_SRC_COLOR: return MTLBlendFactorOneMinusSourceColor;
+		case BLENDFACTOR_SRC_ALPHA: return MTLBlendFactorSourceAlpha;
+		case BLENDFACTOR_ONE_MINUS_SRC_ALPHA: return MTLBlendFactorOneMinusSourceAlpha;
+		case BLENDFACTOR_DST_COLOR: return MTLBlendFactorDestinationColor;
+		case BLENDFACTOR_ONE_MINUS_DST_COLOR: return MTLBlendFactorOneMinusDestinationColor;
+		case BLENDFACTOR_DST_ALPHA: return MTLBlendFactorDestinationAlpha;
+		case BLENDFACTOR_ONE_MINUS_DST_ALPHA: return MTLBlendFactorOneMinusDestinationAlpha;
+		case BLENDFACTOR_SRC_ALPHA_SATURATED: return MTLBlendFactorSourceAlphaSaturated;
+		case BLENDFACTOR_MAX_ENUM: return MTLBlendFactorZero;
+	}
+	return MTLBlendFactorZero;
+}
+
+static EShLanguage getGLSLangStage(ShaderStage::StageType stage)
+{
+	switch (stage)
+	{
+		case ShaderStage::STAGE_VERTEX: return EShLangVertex;
+		case ShaderStage::STAGE_PIXEL: return EShLangFragment;
+		case ShaderStage::STAGE_MAX_ENUM: return EShLangCount;
+	}
+	return EShLangCount;
+}
+
 Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel)
 	: love::graphics::Shader(vertex, pixel)
+	, functions()
 { @autoreleasepool {
 	auto gfx = Graphics::getInstance();
 
@@ -54,11 +111,13 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 	if (!program.link(EShMsgDefault))
 	{
 		//err = "Cannot compile shader:\n\n" + std::string(program.getInfoLog()) + "\n" + std::string(program.getInfoDebugLog());
+		throw love::Exception("link failed!\n");
 	}
 
-	for (int i = 0; i < EShLangCount; i++)
+	for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
 	{
-		auto intermediate = program.getIntermediate((EShLanguage)i);
+		auto glslangstage = getGLSLangStage((ShaderStage::StageType) i);
+		auto intermediate = program.getIntermediate(glslangstage);
 		if (intermediate == nullptr)
 			continue;
 
@@ -103,7 +162,13 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 
 			NSError *err = nil;
 			id<MTLLibrary> library = [gfx->device newLibraryWithSource:nssource options:nil error:&err];
+			if (library == nil && err != nil)
+			{
+				NSLog(@"errors: %@", err);
+				throw love::Exception("Error compiling converted Metal shader code");
+			}
 
+			functions[i] = [library newFunctionWithName:library.functionNames[0]];
 		}
 		catch (std::exception &e)
 		{
@@ -114,9 +179,127 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 
 Shader::~Shader()
 { @autoreleasepool {
+	for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+		functions[i] = nil;
+
+	for (const auto &kvp : cachedRenderPipelines)
+		CFBridgingRelease(kvp.second);
 
+	cachedRenderPipelines.clear();
 }}
 
+void Shader::attach()
+{
+	if (current != this)
+	{
+		Graphics::flushBatchedDrawsGlobal();
+		current = this;
+	}
+}
+
+id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineKey &key)
+{
+	auto it = cachedRenderPipelines.find(key);
+
+	if (it != cachedRenderPipelines.end())
+		return (__bridge id<MTLRenderPipelineState>) it->second;
+
+	id<MTLDevice> device = Graphics::getInstance()->device;
+
+	MTLRenderPipelineDescriptor *desc = [MTLRenderPipelineDescriptor new];
+
+	desc.vertexFunction = functions[ShaderStage::STAGE_VERTEX];
+	desc.fragmentFunction = functions[ShaderStage::STAGE_PIXEL];
+
+	desc.rasterSampleCount = std::max((int) key.msaa, 1);
+
+	for (int i = 0; i < MAX_COLOR_RENDER_TARGETS; i++)
+	{
+		PixelFormat format = (PixelFormat)((key.colorRenderTargetFormats >> (i * 8)) & 0xFF);
+		if (format == PIXELFORMAT_UNKNOWN)
+			continue;
+
+		MTLRenderPipelineColorAttachmentDescriptor *attachment = desc.colorAttachments[i];
+
+		bool isSRGB = false;
+		MTLPixelFormat metalformat = Metal::convertPixelFormat(format, isSRGB);
+
+		attachment.pixelFormat = metalformat;
+
+		if (key.blend.enable)
+		{
+			attachment.blendingEnabled = YES;
+			attachment.sourceRGBBlendFactor = getMTLBlendFactor(key.blend.srcFactorRGB);
+			attachment.destinationRGBBlendFactor = getMTLBlendFactor(key.blend.dstFactorRGB);
+			attachment.rgbBlendOperation = getMTLBlendOperation(key.blend.operationRGB);
+			attachment.sourceAlphaBlendFactor = getMTLBlendFactor(key.blend.srcFactorA);
+			attachment.destinationAlphaBlendFactor = getMTLBlendFactor(key.blend.dstFactorA);
+			attachment.alphaBlendOperation = getMTLBlendOperation(key.blend.operationA);
+		}
+
+		MTLColorWriteMask writeMask = MTLColorWriteMaskNone;
+		if (key.colorChannelMask.r)
+			writeMask |= MTLColorWriteMaskRed;
+		if (key.colorChannelMask.g)
+			writeMask |= MTLColorWriteMaskGreen;
+		if (key.colorChannelMask.b)
+			writeMask |= MTLColorWriteMaskBlue;
+		if (key.colorChannelMask.a)
+			writeMask |= MTLColorWriteMaskAlpha;
+
+		attachment.writeMask = writeMask;
+
+		desc.colorAttachments[i] = attachment;
+	}
+
+	{
+		MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor];
+
+		const auto &attributes = key.vertexAttributes;
+		uint32 allbits = attributes.enableBits;
+		uint32 i = 0;
+		while (allbits)
+		{
+			uint32 bit = 1u << i;
+
+			if (attributes.enableBits & bit)
+			{
+				const auto &attrib = attributes.attribs[i];
+
+				vertdesc.attributes[i].format = getMTLVertexFormat(attrib.type, attrib.components);
+				vertdesc.attributes[i].offset = attrib.offsetFromVertex;
+				vertdesc.attributes[i].bufferIndex = attrib.bufferIndex;
+
+				const auto &layout = attributes.bufferLayouts[attrib.bufferIndex];
+
+				bool instanced = attributes.instanceBits & (1u << attrib.bufferIndex);
+				auto step = instanced ? MTLVertexStepFunctionPerInstance : MTLVertexStepFunctionPerVertex;
+
+				vertdesc.layouts[attrib.bufferIndex].stride = layout.stride;
+				vertdesc.layouts[attrib.bufferIndex].stepFunction = step;
+			}
+
+			i++;
+			allbits >>= 1;
+		}
+
+		desc.vertexDescriptor = vertdesc;
+	}
+
+	NSError *err = nil;
+	id<MTLRenderPipelineState> pipeline = [device newRenderPipelineStateWithDescriptor:desc error:&err];
+
+	if (err != nil)
+	{
+		NSLog(@"Error creating render pipeline: %@", err);
+		return nil;
+	}
+
+	cachedRenderPipelines[key] = CFBridgingRetain(pipeline);
+
+	return pipeline;
+}
+
 } // metal
 } // graphics
 } // love

+ 1 - 1
src/modules/graphics/metal/ShaderStage.mm

@@ -164,7 +164,7 @@ ShaderStage::ShaderStage(love::graphics::Graphics *gfx, StageType stage, const s
 	glslangShader->setStringsWithLengths(&csrc, &srclen, 1);
 
 	int defaultversion = gles ? 300 : 330;
-	EProfile defaultprofile = ENoProfile;
+	EProfile defaultprofile = gles ? EEsProfile : ECoreProfile;
 	bool forcedefault = false;
 	bool forwardcompat = true;
 

+ 2 - 2
src/modules/graphics/opengl/Graphics.cpp

@@ -1408,7 +1408,7 @@ PixelFormat Graphics::getSizedFormat(PixelFormat format, bool rendertarget, bool
 	{
 	case PIXELFORMAT_NORMAL:
 		if (isGammaCorrect())
-			return PIXELFORMAT_sRGBA8_UNORM;
+			return PIXELFORMAT_RGBA8_UNORM_sRGB;
 		else if (!OpenGL::isPixelFormatSupported(PIXELFORMAT_RGBA8_UNORM, rendertarget, readable, sRGB))
 			// 32-bit render targets don't have guaranteed support on GLES2.
 			return PIXELFORMAT_RGBA4_UNORM;
@@ -1425,7 +1425,7 @@ bool Graphics::isPixelFormatSupported(PixelFormat format, bool rendertarget, boo
 {
 	if (sRGB && format == PIXELFORMAT_RGBA8_UNORM)
 	{
-		format = PIXELFORMAT_sRGBA8_UNORM;
+		format = PIXELFORMAT_RGBA8_UNORM_sRGB;
 		sRGB = false;
 	}
 

+ 7 - 7
src/modules/graphics/opengl/OpenGL.cpp

@@ -1400,8 +1400,8 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 	f.framebufferAttachments[0] = GL_COLOR_ATTACHMENT0;
 	f.framebufferAttachments[1] = GL_NONE;
 
-	if (pixelformat == PIXELFORMAT_RGBA8_UNORM && isSRGB)
-		pixelformat = PIXELFORMAT_sRGBA8_UNORM;
+	if (isSRGB)
+		pixelformat = getSRGBPixelFormat(pixelformat);
 	else if (pixelformat == PIXELFORMAT_ETC1_UNORM)
 	{
 		// The ETC2 format can load ETC1 textures.
@@ -1435,7 +1435,7 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 		f.externalformat = GL_RGBA;
 		f.type = GL_UNSIGNED_BYTE;
 		break;
-	case PIXELFORMAT_sRGBA8_UNORM:
+	case PIXELFORMAT_RGBA8_UNORM_sRGB:
 		f.internalformat = GL_SRGB8_ALPHA8;
 		f.type = GL_UNSIGNED_BYTE;
 		if (GLAD_ES_VERSION_2_0 && !GLAD_ES_VERSION_3_0)
@@ -1755,7 +1755,7 @@ OpenGL::TextureFormat OpenGL::convertPixelFormat(PixelFormat pixelformat, bool r
 			f.internalformat = f.externalformat;
 		}
 
-		if (pixelformat != PIXELFORMAT_sRGBA8_UNORM)
+		if (!isPixelFormatSRGB(pixelformat))
 			isSRGB = false;
 	}
 
@@ -1767,8 +1767,8 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget,
 	if (rendertarget && isPixelFormatCompressed(pixelformat))
 		return false;
 
-	if (pixelformat == PIXELFORMAT_RGBA8_UNORM && isSRGB)
-		pixelformat = PIXELFORMAT_sRGBA8_UNORM;
+	if (isSRGB)
+		pixelformat = getSRGBPixelFormat(pixelformat);
 
 	switch (pixelformat)
 	{
@@ -1785,7 +1785,7 @@ bool OpenGL::isPixelFormatSupported(PixelFormat pixelformat, bool rendertarget,
 			return GLAD_VERSION_1_0 || GLAD_ES_VERSION_3_0 || GLAD_OES_rgb8_rgba8 || GLAD_ARM_rgba8;
 		else
 			return true;
-	case PIXELFORMAT_sRGBA8_UNORM:
+	case PIXELFORMAT_RGBA8_UNORM_sRGB:
 		if (rendertarget)
 		{
 			if (GLAD_VERSION_1_0)

+ 3 - 3
src/modules/graphics/renderstate.h

@@ -110,24 +110,24 @@ enum CompareMode
 
 struct BlendState
 {
-	bool enable = false;
 	BlendOperation operationRGB = BLENDOP_ADD;
 	BlendOperation operationA = BLENDOP_ADD;
 	BlendFactor srcFactorRGB = BLENDFACTOR_ONE;
 	BlendFactor srcFactorA = BLENDFACTOR_ONE;
 	BlendFactor dstFactorRGB = BLENDFACTOR_ZERO;
 	BlendFactor dstFactorA = BLENDFACTOR_ZERO;
+	bool enable = false;
 
 	BlendState() {}
 
 	BlendState(BlendOperation opRGB, BlendOperation opA, BlendFactor srcRGB, BlendFactor srcA, BlendFactor dstRGB, BlendFactor dstA)
-		: enable(true)
-		, operationRGB(opRGB)
+		: operationRGB(opRGB)
 		, operationA(opA)
 		, srcFactorRGB(srcRGB)
 		, srcFactorA(srcA)
 		, dstFactorRGB(dstRGB)
 		, dstFactorA(dstA)
+		, enable(true)
 	{}
 
 	bool operator == (const BlendState &b) const