Browse Source

metal: nogame and error screens work.

- implement love.graphics.setColor
- implement default textures
- hacky implementation of default uniform data
- use texture swizzles to set up LA8 (text) texture data
- shader reflection work
Alex Szpakowski 5 years ago
parent
commit
9d2dca5740

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

@@ -159,7 +159,7 @@ love::Type Texture::type("Texture", &Drawable::type);
 int Texture::textureCount = 0;
 int64 Texture::totalGraphicsMemory = 0;
 
-Texture::Texture(const Settings &settings, const Slices *slices)
+Texture::Texture(Graphics *gfx, const Settings &settings, const Slices *slices)
 	: texType(settings.type)
 	, format(settings.format)
 	, renderTarget(settings.renderTarget)
@@ -178,8 +178,6 @@ Texture::Texture(const Settings &settings, const Slices *slices)
 	, graphicsMemorySize(0)
 	, usingDefaultTexture(false)
 {
-	auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
-
 	if (slices != nullptr && slices->getMipmapCount() > 0 && slices->getSliceCount() > 0)
 	{
 		texType = slices->getTextureType();

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

@@ -201,7 +201,7 @@ public:
 
 	static int64 totalGraphicsMemory;
 
-	Texture(const Settings &settings, const Slices *slices);
+	Texture(Graphics *gfx, const Settings &settings, const Slices *slices);
 	virtual ~Texture();
 
 	// Drawable.

+ 15 - 1
src/modules/graphics/metal/Graphics.h

@@ -109,12 +109,17 @@ public:
 
 	id<MTLSamplerState> getCachedSampler(const SamplerState &s);
 
-	static Graphics *getInstance() { return Module::getInstance<Graphics>(M_GRAPHICS); }
+	StreamBuffer *getUniformBuffer() const { return uniformBuffer; }
+	Buffer *getDefaultAttributesBuffer() const { return defaultAttributesBuffer; }
+
+	static Graphics *getInstance() { return graphicsInstance; }
 
 	id<MTLDevice> device;
 
 private:
 
+	static Graphics *graphicsInstance;
+
 	enum StateType
 	{
 		STATE_BLEND,
@@ -166,6 +171,7 @@ private:
 
 	id<MTLDepthStencilState> getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil);
 	void applyRenderState(id<MTLRenderCommandEncoder> renderEncoder, const vertex::Attributes &attributes);
+	void applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, Shader *shader);
 
 	id<MTLCommandQueue> commandQueue;
 
@@ -180,6 +186,14 @@ private:
 	uint32 dirtyRenderState;
 	bool windowHasStencil;
 
+	StreamBuffer *uniformBuffer;
+	StreamBuffer::MapInfo uniformBufferData;
+	size_t uniformBufferOffset;
+
+	Buffer *defaultAttributesBuffer;
+
+	Texture *defaultTextures[TEXTURE_MAX_ENUM];
+
 	std::map<uint64, void *> cachedSamplers;
 
 }; // Graphics

+ 131 - 9
src/modules/graphics/metal/Graphics.mm

@@ -26,6 +26,7 @@
 #include "ShaderStage.h"
 #include "window/Window.h"
 #include "image/Image.h"
+#include "common/memory.h"
 
 #import <QuartzCore/CAMetalLayer.h>
 
@@ -110,6 +111,8 @@ love::graphics::Graphics *createInstance()
 	return instance;
 }
 
+Graphics *Graphics::graphicsInstance = nullptr;
+
 Graphics::Graphics()
 	: device(nil)
 	, commandQueue(nil)
@@ -121,7 +124,11 @@ Graphics::Graphics()
 	, passDesc(nil)
 	, dirtyRenderState(STATEBIT_ALL)
 	, windowHasStencil(false)
+	, uniformBufferOffset(0)
+	, defaultAttributesBuffer(nullptr)
+	, defaultTextures()
 { @autoreleasepool {
+	graphicsInstance = this;
 	device = MTLCreateSystemDefaultDevice();
 	if (device == nil)
 		throw love::Exception("Metal is not supported on this system.");
@@ -131,6 +138,23 @@ Graphics::Graphics()
 
 	initCapabilities();
 
+	uniformBuffer = CreateStreamBuffer(device, BUFFER_UNIFORM, 1024 * 1024 * 1);
+	uniformBufferData = uniformBuffer->map(uniformBuffer->getSize());
+
+	float defaultAttributes[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+	defaultAttributesBuffer = newBuffer(sizeof(float) * 4, defaultAttributes, BUFFER_VERTEX, vertex::USAGE_STATIC, 0);
+
+	uint8 defaultpixel[] = {255, 255, 255, 255};
+	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+	{
+		Texture::Settings settings;
+		settings.type = (TextureType) i;
+		settings.format = PIXELFORMAT_RGBA8_UNORM;
+		defaultTextures[i] = newTexture(settings);
+		Rect r = {0, 0, 1, 1};
+		defaultTextures[i]->replacePixels(defaultpixel, sizeof(defaultpixel), 0, 0, r, false);
+	}
+
 	auto window = Module::getInstance<love::window::Window>(M_WINDOW);
 
 	if (window != nullptr)
@@ -156,12 +180,19 @@ Graphics::Graphics()
 Graphics::~Graphics()
 { @autoreleasepool {
 	submitCommandBuffer();
+	delete uniformBuffer;
+	delete defaultAttributesBuffer;
 	passDesc = nil;
 	commandQueue = nil;
 	device = nil;
 
+	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
+		defaultTextures[i]->release();
+
 	for (auto &kvp : cachedSamplers)
 		CFBridgingRelease(kvp.second);
+
+	graphicsInstance = nullptr;
 }}
 
 love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t size)
@@ -171,7 +202,7 @@ love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t
 
 love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, const Texture::Slices *data)
 {
-	return new Texture(device, settings, data);
+	return new Texture(this, device, settings, data);
 }
 
 love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles)
@@ -322,6 +353,10 @@ id<MTLRenderCommandEncoder> Graphics::useRenderEncoder()
 		}
 
 		renderEncoder = [useCommandBuffer() renderCommandEncoderWithDescriptor:passDesc];
+
+		id<MTLBuffer> defaultbuffer = (__bridge id<MTLBuffer>)(void *)defaultAttributesBuffer->getHandle();
+		[renderEncoder setVertexBuffer:defaultbuffer offset:0 atIndex:DEFAULT_VERTEX_BUFFER_BINDING];
+
 		dirtyRenderState = STATEBIT_ALL;
 	}
 
@@ -530,16 +565,85 @@ void Graphics::applyRenderState(id<MTLRenderCommandEncoder> encoder, const verte
 	dirtyRenderState = 0;
 }
 
+void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, love::graphics::Shader *shader)
+{
+	Shader *s = (Shader *)shader;
+
+#ifdef LOVE_MACOS
+	size_t alignment = 256;
+#else
+	size_t alignment = 16;
+#endif
+
+	// TODO: use the size of all uniforms
+	size_t size = sizeof(Shader::BuiltinUniformData);
+
+	Shader::BuiltinUniformData data;
+
+	data.transformMatrix = getTransform();
+	data.projectionMatrix = getProjection();
+
+	// The normal matrix is the transpose of the inverse of the rotation portion
+	// (top-left 3x3) of the transform matrix.
+	{
+		Matrix3 normalmatrix = Matrix3(data.transformMatrix).transposedInverse();
+		const float *e = normalmatrix.getElements();
+		for (int i = 0; i < 3; i++)
+		{
+			data.normalMatrix[i].x = e[i * 3 + 0];
+			data.normalMatrix[i].y = e[i * 3 + 1];
+			data.normalMatrix[i].z = e[i * 3 + 2];
+			data.normalMatrix[i].w = 0.0f;
+		}
+	}
+
+	// FIXME: should be active RT dimensions
+	data.screenSizeParams.x = getPixelWidth();
+	data.screenSizeParams.y = getPixelHeight();
+
+	data.screenSizeParams.z = 1.0f;
+	data.screenSizeParams.w = 0.0f;
+
+	data.constantColor = getColor();
+	gammaCorrectColor(data.constantColor);
+
+	if (uniformBufferData.size < uniformBufferOffset + size)
+	{
+		size_t newsize = uniformBuffer->getSize() * 2;
+		delete uniformBuffer;
+		uniformBuffer = CreateStreamBuffer(device, BUFFER_UNIFORM, newsize);
+		uniformBufferData = uniformBuffer->map(uniformBuffer->getSize());
+		uniformBufferOffset = 0;
+	}
+
+	memcpy(uniformBufferData.data + uniformBufferOffset, &data, sizeof(Shader::BuiltinUniformData));
+
+	id<MTLBuffer> buffer = (__bridge id<MTLBuffer>)(void *)uniformBuffer->getHandle();
+	int index = Shader::getUniformBufferBinding();
+
+	// TODO: bind shader textures/samplers
+
+	[renderEncoder setVertexBuffer:buffer offset:uniformBufferOffset atIndex:index];
+	[renderEncoder setFragmentBuffer:buffer offset:uniformBufferOffset atIndex:index];
+
+	uniformBufferOffset += alignUp(size, alignment);
+}
+
 void Graphics::draw(const DrawCommand &cmd)
 { @autoreleasepool {
 	id<MTLRenderCommandEncoder> encoder = useRenderEncoder();
 
 	applyRenderState(encoder, *cmd.attributes);
+	applyShaderUniforms(encoder, Shader::current);
+
+	love::graphics::Texture *texture = cmd.texture;
+	if (texture == nullptr)
+		texture = defaultTextures[TEXTURE_2D];
 
-	id<MTLTexture> mtltexture = (__bridge id<MTLTexture>)(void *) cmd.texture->getHandle();
+	id<MTLTexture> mtltexture = (__bridge id<MTLTexture>)(void *) texture->getHandle();
 
 	[encoder setFragmentTexture:mtltexture atIndex:0];
-	[encoder setFragmentSamplerState:((Texture *)cmd.texture)->getMTLSampler() atIndex:0];
+	[encoder setFragmentSamplerState:((Texture *)texture)->getMTLSampler() atIndex:0];
 
 	[encoder setCullMode:MTLCullModeNone];
 
@@ -553,7 +657,7 @@ void Graphics::draw(const DrawCommand &cmd)
 		{
 			auto b = cmd.buffers->info[i];
 			id<MTLBuffer> buffer = (__bridge id<MTLBuffer>)(void *)b.buffer->getHandle();
-			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i];
+			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i + VERTEX_BUFFER_BINDING_START];
 		}
 
 		i++;
@@ -571,11 +675,16 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 	id<MTLRenderCommandEncoder> encoder = useRenderEncoder();
 
 	applyRenderState(encoder, *cmd.attributes);
+	applyShaderUniforms(encoder, Shader::current);
+
+	love::graphics::Texture *texture = cmd.texture;
+	if (texture == nullptr)
+		texture = defaultTextures[TEXTURE_2D];
 
-	id<MTLTexture> mtltexture = (__bridge id<MTLTexture>)(void *) cmd.texture->getHandle();
+	id<MTLTexture> mtltexture = (__bridge id<MTLTexture>)(void *) texture->getHandle();
 
 	[encoder setFragmentTexture:mtltexture atIndex:0];
-	[encoder setFragmentSamplerState:((Texture *)cmd.texture)->getMTLSampler() atIndex:0];
+	[encoder setFragmentSamplerState:((Texture *)texture)->getMTLSampler() atIndex:0];
 
 	[encoder setCullMode:MTLCullModeNone];
 
@@ -589,7 +698,7 @@ void Graphics::draw(const DrawIndexedCommand &cmd)
 		{
 			auto b = cmd.buffers->info[i];
 			id<MTLBuffer> buffer = (__bridge id<MTLBuffer>)(void *)b.buffer->getHandle();
-			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i];
+			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i + VERTEX_BUFFER_BINDING_START];
 		}
 
 		i++;
@@ -614,6 +723,10 @@ void Graphics::drawQuads(int start, int count, const vertex::Attributes &attribu
 	id<MTLRenderCommandEncoder> encoder = useRenderEncoder();
 
 	applyRenderState(encoder, attributes);
+	applyShaderUniforms(encoder, Shader::current);
+
+	if (texture == nullptr)
+		texture = defaultTextures[TEXTURE_2D];
 
 	id<MTLTexture> mtltexture = (__bridge id<MTLTexture>)(void *) texture->getHandle();
 
@@ -632,7 +745,7 @@ void Graphics::drawQuads(int start, int count, const vertex::Attributes &attribu
 		{
 			auto b = buffers.info[i];
 			id<MTLBuffer> buffer = (__bridge id<MTLBuffer>)(void *)b.buffer->getHandle();
-			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i];
+			[encoder setVertexBuffer:buffer offset:b.offset atIndex:i + VERTEX_BUFFER_BINDING_START];
 		}
 
 		i++;
@@ -915,6 +1028,10 @@ void Graphics::present(void *screenshotCallbackData)
 		buffer->nextFrame();
 	batchedDrawState.indexBuffer->nextFrame();
 
+	uniformBuffer->nextFrame();
+	uniformBufferData = uniformBuffer->map(uniformBuffer->getSize());
+	uniformBufferOffset = 0;
+
 	id<MTLCommandBuffer> cmd = getCommandBuffer();
 
 	if (cmd != nil && activeDrawable != nil)
@@ -950,7 +1067,12 @@ void Graphics::present(void *screenshotCallbackData)
 
 void Graphics::setColor(Colorf c)
 {
-	// TODO
+	c.r = std::min(std::max(c.r, 0.0f), 1.0f);
+	c.g = std::min(std::max(c.g, 0.0f), 1.0f);
+	c.b = std::min(std::max(c.b, 0.0f), 1.0f);
+	c.a = std::min(std::max(c.a, 0.0f), 1.0f);
+
+	states.back().color = c;
 }
 
 void Graphics::setScissor(const Rect &rect)

+ 8 - 1
src/modules/graphics/metal/Metal.h

@@ -36,7 +36,14 @@ class Metal
 {
 public:
 
-	static MTLPixelFormat convertPixelFormat(PixelFormat format, bool &isSRGB);
+	struct PixelFormatDesc
+	{
+		MTLPixelFormat format;
+		bool swizzled = false;
+		MTLTextureSwizzleChannels swizzle;
+	};
+
+	static PixelFormatDesc convertPixelFormat(PixelFormat format, bool &isSRGB);
 
 }; // Metal
 

+ 6 - 3
src/modules/graphics/metal/Metal.mm

@@ -28,9 +28,10 @@ namespace graphics
 namespace metal
 {
 
-MTLPixelFormat Metal::convertPixelFormat(PixelFormat format, bool &isSRGB)
+Metal::PixelFormatDesc Metal::convertPixelFormat(PixelFormat format, bool &isSRGB)
 {
 	MTLPixelFormat mtlformat = MTLPixelFormatRGBA8Unorm;
+	PixelFormatDesc desc = {};
 
 	if (isSRGB)
 		format = getSRGBPixelFormat(format);
@@ -87,10 +88,11 @@ MTLPixelFormat Metal::convertPixelFormat(PixelFormat format, bool &isSRGB)
 		break;
 
 	case PIXELFORMAT_LA8_UNORM:
-		// TODO: Swizzle
 		// TODO: fall back to RGBA8 when swizzle isn't available. Pixel format
 		// size calculation will need to be adjusted as well
 		mtlformat = MTLPixelFormatRG8Unorm;
+		desc.swizzled = true;
+		desc.swizzle = MTLTextureSwizzleChannelsMake(MTLTextureSwizzleRed, MTLTextureSwizzleRed, MTLTextureSwizzleRed, MTLTextureSwizzleGreen);
 		break;
 
 	case PIXELFORMAT_RGBA4_UNORM:
@@ -346,7 +348,8 @@ MTLPixelFormat Metal::convertPixelFormat(PixelFormat format, bool &isSRGB)
 		break;
 	}
 
-	return mtlformat;
+	desc.format = mtlformat;
+	return desc;
 }
 
 Metal metal;

+ 14 - 3
src/modules/graphics/metal/Shader.h

@@ -30,6 +30,7 @@
 #import <Metal/MTLRenderPipeline.h>
 
 #include <unordered_map>
+#include <map>
 #include <string>
 
 namespace love
@@ -39,6 +40,9 @@ namespace graphics
 namespace metal
 {
 
+static const int DEFAULT_VERTEX_BUFFER_BINDING = 1;
+static const int VERTEX_BUFFER_BINDING_START = 2;
+
 class Shader final : public love::graphics::Shader
 {
 public:
@@ -69,9 +73,9 @@ public:
 	// Implements Shader.
 	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; }
-	const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override { return nullptr; }
+	int getVertexAttributeIndex(const std::string &name) override;
+	const UniformInfo *getUniformInfo(const std::string &name) const override;
+	const UniformInfo *getUniformInfo(BuiltinUniform builtin) const override;
 	void updateUniform(const UniformInfo *info, int count) override {}
 	void sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count) override {}
 	bool hasUniform(const std::string &name) const override { return false; }
@@ -80,6 +84,8 @@ public:
 
 	id<MTLRenderPipelineState> getCachedRenderPipeline(const RenderPipelineKey &key);
 
+	static int getUniformBufferBinding();
+
 private:
 
 	struct RenderPipelineHasher
@@ -92,6 +98,11 @@ private:
 
 	id<MTLFunction> functions[ShaderStage::STAGE_MAX_ENUM];
 
+	UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
+	std::map<std::string, UniformInfo> uniforms;
+
+	std::map<std::string, int> attributes;
+
 	std::unordered_map<RenderPipelineKey, const void *, RenderPipelineHasher> cachedRenderPipelines;
 
 }; // Metal

+ 102 - 12
src/modules/graphics/metal/Shader.mm

@@ -20,6 +20,7 @@
 
 #include "Shader.h"
 #include "Graphics.h"
+#include "common/int.h"
 
 // glslang
 #include "libraries/glslang/glslang/Public/ShaderLang.h"
@@ -123,6 +124,7 @@ static EShLanguage getGLSLangStage(ShaderStage::StageType stage)
 Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel)
 	: love::graphics::Shader(vertex, pixel)
 	, functions()
+	, builtinUniformInfo()
 { @autoreleasepool {
 	auto gfx = Graphics::getInstance();
 
@@ -145,6 +147,9 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 		throw love::Exception("link failed!\n");
 	}
 
+	std::map<std::string, int> varyings;
+	int nextVaryingLocation = 0;
+
 	for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
 	{
 		auto glslangstage = getGLSLangStage((ShaderStage::StageType) i);
@@ -169,11 +174,54 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 		// Compile to GLSL, ready to give to GL driver.
 		try
 		{
-
 //			printf("GLSL INPUT SOURCE:\n\n%s\n\n", pixel->getSource().c_str());
 
 			CompilerMSL msl(std::move(spirv));
 
+			auto interfacevars = msl.get_active_interface_variables();
+
+			msl.set_enabled_interface_variables(interfacevars);
+
+			ShaderResources resources = msl.get_shader_resources();
+
+			for (const auto &resource : resources.sampled_images)
+			{
+				BuiltinUniform builtin = BUILTIN_MAX_ENUM;
+				if (getConstant(resource.name.c_str(), builtin))
+				{
+					// TODO
+				}
+			}
+
+			for (const auto &resource : resources.uniform_buffers)
+			{
+				if (resource.name == "love_UniformsPerDrawBuffer")
+				{
+					msl.set_decoration(resource.id, spv::DecorationBinding, 0);
+				}
+			}
+
+			if (i == ShaderStage::STAGE_VERTEX)
+			{
+				for (const auto &varying : resources.stage_outputs)
+				{
+//					printf("vertex shader output %s: %d\n", inp.name.c_str(), msl.get_decoration(inp.id, spv::DecorationLocation));
+					varyings[varying.name] = nextVaryingLocation;
+					msl.set_decoration(varying.id, spv::DecorationLocation, nextVaryingLocation++);
+				}
+			}
+			else if (i == ShaderStage::STAGE_PIXEL)
+			{
+				for (const auto &varying : resources.stage_inputs)
+				{
+					const auto it = varyings.find(varying.name);
+					if (it != varyings.end())
+						msl.set_decoration(varying.id, spv::DecorationLocation, it->second);
+				}
+			}
+
+			printf("ubos: %d, storage: %d, inputs: %d, outputs: %d, images: %d, samplers: %d, push: %d\n", resources.uniform_buffers.size(), resources.storage_buffers.size(), resources.stage_inputs.size(), resources.stage_outputs.size(), resources.storage_images.size(), resources.sampled_images.size(), resources.push_constant_buffers.size());
+
 			CompilerMSL::Options options;
 
 #ifdef LOVE_IOS
@@ -185,7 +233,7 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 			msl.set_msl_options(options);
 
 			std::string source = msl.compile();
-//			printf("MSL SOURCE:\n\n%s\n\n", source.c_str());
+//			printf("MSL SOURCE for stage %d:\n\n%s\n\n", i, source.c_str());
 
 			NSString *nssource = [[NSString alloc] initWithBytes:source.c_str()
 														  length:source.length()
@@ -200,6 +248,15 @@ Shader::Shader(love::graphics::ShaderStage *vertex, love::graphics::ShaderStage
 			}
 
 			functions[i] = [library newFunctionWithName:library.functionNames[0]];
+
+			for (const auto &var : interfacevars)
+			{
+				spv::StorageClass storage = msl.get_storage_class(var);
+				const std::string &name = msl.get_name(var);
+
+				if (i == ShaderStage::STAGE_VERTEX && storage == spv::StorageClassInput)
+					attributes[name] = msl.get_decoration(var, spv::DecorationLocation);
+			}
 		}
 		catch (std::exception &e)
 		{
@@ -228,6 +285,23 @@ void Shader::attach()
 	}
 }
 
+int Shader::getVertexAttributeIndex(const std::string &name)
+{
+	const auto it = attributes.find(name);
+	return it != attributes.end() ? it->second : -1;
+}
+
+const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
+{
+	const auto it = uniforms.find(name);
+	return it != uniforms.end() ? &(it->second) : nullptr;
+}
+
+const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
+{
+	return builtinUniformInfo[(int)builtin];
+}
+
 id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineKey &key)
 {
 	auto it = cachedRenderPipelines.find(key);
@@ -253,9 +327,8 @@ id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineK
 		MTLRenderPipelineColorAttachmentDescriptor *attachment = desc.colorAttachments[i];
 
 		bool isSRGB = false;
-		MTLPixelFormat metalformat = Metal::convertPixelFormat(format, isSRGB);
-
-		attachment.pixelFormat = metalformat;
+		auto formatdesc = Metal::convertPixelFormat(format, isSRGB);
+		attachment.pixelFormat = formatdesc.format;
 
 		if (key.blend.enable)
 		{
@@ -283,35 +356,47 @@ id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineK
 		desc.colorAttachments[i] = attachment;
 	}
 
+	// TODO: depth/stencil attachment formats
+	
+
 	{
 		MTLVertexDescriptor *vertdesc = [MTLVertexDescriptor vertexDescriptor];
 
 		const auto &attributes = key.vertexAttributes;
 		uint32 allbits = attributes.enableBits;
-		uint32 i = 0;
-		while (allbits)
+
+		for (const auto &pair : this->attributes)
 		{
+			int i = pair.second;
 			uint32 bit = 1u << i;
 
 			if (attributes.enableBits & bit)
 			{
 				const auto &attrib = attributes.attribs[i];
+				int metalBufferIndex = attrib.bufferIndex + VERTEX_BUFFER_BINDING_START;
 
 				vertdesc.attributes[i].format = getMTLVertexFormat(attrib.type, attrib.components);
 				vertdesc.attributes[i].offset = attrib.offsetFromVertex;
-				vertdesc.attributes[i].bufferIndex = attrib.bufferIndex;
+				vertdesc.attributes[i].bufferIndex = metalBufferIndex;
 
 				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;
+				vertdesc.layouts[metalBufferIndex].stride = layout.stride;
+				vertdesc.layouts[metalBufferIndex].stepFunction = step;
 			}
+			else
+			{
+				vertdesc.attributes[i].format = MTLVertexFormatFloat4;
+				vertdesc.attributes[i].offset = 0;
+				vertdesc.attributes[i].bufferIndex = DEFAULT_VERTEX_BUFFER_BINDING;
 
-			i++;
-			allbits >>= 1;
+				vertdesc.layouts[DEFAULT_VERTEX_BUFFER_BINDING].stride = sizeof(float) * 4;
+				vertdesc.layouts[DEFAULT_VERTEX_BUFFER_BINDING].stepFunction = MTLVertexStepFunctionConstant;
+				vertdesc.layouts[DEFAULT_VERTEX_BUFFER_BINDING].stepRate = 0;
+			}
 		}
 
 		desc.vertexDescriptor = vertdesc;
@@ -331,6 +416,11 @@ id<MTLRenderPipelineState> Shader::getCachedRenderPipeline(const RenderPipelineK
 	return pipeline;
 }
 
+int Shader::getUniformBufferBinding()
+{
+	return spirv_cross::ResourceBindingPushConstantBinding;
+}
+
 } // metal
 } // graphics
 } // love

+ 1 - 1
src/modules/graphics/metal/Texture.h

@@ -37,7 +37,7 @@ class Texture final : public love::graphics::Texture
 {
 public:
 
-	Texture(id<MTLDevice> device, const Settings &settings, const Slices *data);
+	Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const Slices *data);
 	virtual ~Texture();
 
 	void generateMipmaps() override;

+ 7 - 3
src/modules/graphics/metal/Texture.mm

@@ -41,8 +41,8 @@ static MTLTextureType getMTLTextureType(TextureType type, int msaa)
 	return MTLTextureType2D;
 }
 
-Texture::Texture(id<MTLDevice> device, const Settings &settings, const Slices *data)
-	: love::graphics::Texture(settings, data)
+Texture::Texture(love::graphics::Graphics *gfx, id<MTLDevice> device, const Settings &settings, const Slices *data)
+	: love::graphics::Texture(gfx, settings, data)
 	, texture(nil)
 	, msaaTexture(nil)
 	, sampler(nil)
@@ -52,13 +52,17 @@ Texture::Texture(id<MTLDevice> device, const Settings &settings, const Slices *d
 	int w = pixelWidth;
 	int h = pixelHeight;
 
+	auto formatdesc = Metal::convertPixelFormat(format, sRGB);
+
 	desc.width = w;
 	desc.height = h;
 	desc.depth = depth;
 	desc.arrayLength = layers;
 	desc.mipmapLevelCount = mipmapCount;
 	desc.textureType = getMTLTextureType(texType, 1);
-	desc.pixelFormat = Metal::convertPixelFormat(format, sRGB);
+	desc.pixelFormat = formatdesc.format;
+	if (formatdesc.swizzled)
+		desc.swizzle = formatdesc.swizzle;
 	desc.storageMode = MTLStorageModePrivate;
 
 	if (readable)

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

@@ -149,7 +149,7 @@ love::graphics::StreamBuffer *Graphics::newStreamBuffer(BufferType type, size_t
 
 love::graphics::Texture *Graphics::newTexture(const Texture::Settings &settings, const Texture::Slices *data)
 {
-	return new Texture(settings, data);
+	return new Texture(this, settings, data);
 }
 
 love::graphics::ShaderStage *Graphics::newShaderStageInternal(ShaderStage::StageType stage, const std::string &cachekey, const std::string &source, bool gles)

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

@@ -188,8 +188,8 @@ static GLenum newRenderbuffer(int width, int height, int &samples, PixelFormat p
 	return status;
 }
 
-Texture::Texture(const Settings &settings, const Slices *data)
-	: love::graphics::Texture(settings, data)
+Texture::Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data)
+	: love::graphics::Texture(gfx, settings, data)
 	, slices(settings.type)
 	, fbo(0)
 	, texture(0)

+ 1 - 1
src/modules/graphics/opengl/Texture.h

@@ -38,7 +38,7 @@ class Texture final : public love::graphics::Texture, public Volatile
 {
 public:
 
-	Texture(const Settings &settings, const Slices *data);
+	Texture(love::graphics::Graphics *gfx, const Settings &settings, const Slices *data);
 
 	virtual ~Texture();
 

+ 3 - 2
src/modules/graphics/vertex.h

@@ -57,6 +57,7 @@ enum BufferType
 {
 	BUFFER_VERTEX = 0,
 	BUFFER_INDEX,
+	BUFFER_UNIFORM,
 	BUFFER_MAX_ENUM
 };
 
@@ -223,9 +224,9 @@ struct BufferBindings
 
 struct AttributeInfo
 {
+	DataType type;
+	uint8 components;
 	uint8 bufferIndex;
-	DataType type : 4;
-	uint8 components : 4;
 	uint16 offsetFromVertex;
 };
 

+ 31 - 12
src/modules/graphics/wrap_GraphicsShader.lua

@@ -44,6 +44,11 @@ GLSL.SYNTAX = [[
 #else
 	#define LOVE_HIGHP_OR_MEDIUMP mediump
 #endif
+#if __VERSION__ >= 300
+#define LOVE_IO_LOCATION(x) layout (location = x)
+#else
+#define LOVE_IO_LOCATION(x)
+#endif
 #define number float
 #define Image sampler2D
 #define ArrayImage sampler2DArray
@@ -71,7 +76,13 @@ GLSL.UNIFORMS = [[
 // According to the GLSL ES 1.0 spec, uniform precision must match between stages,
 // but we can't guarantee that highp is always supported in fragment shaders...
 // We *really* don't want to use mediump for these in vertex shaders though.
+#ifdef LOVE_USE_UNIFORM_BUFFERS
+layout (std140) uniform love_UniformsPerDrawBuffer {
+	LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
+};
+#else
 uniform LOVE_HIGHP_OR_MEDIUMP vec4 love_UniformsPerDraw[13];
+#endif
 
 // These are initialized in love_initializeBuiltinUniforms below. GLSL ES can't
 // do it as an initializer.
@@ -91,6 +102,7 @@ LOVE_HIGHP_OR_MEDIUMP vec4 ConstantColor;
 #define ViewNormalFromLocal NormalMatrix
 
 void love_initializeBuiltinUniforms() {
+#if 1
 	TransformMatrix = mat4(
 	   love_UniformsPerDraw[0],
 	   love_UniformsPerDraw[1],
@@ -113,6 +125,7 @@ void love_initializeBuiltinUniforms() {
 
 	love_ScreenSize = love_UniformsPerDraw[11];
 	ConstantColor = love_UniformsPerDraw[12];
+#endif
 }
 ]]
 
@@ -247,9 +260,9 @@ void setPointSize() {
 }]],
 
 	MAIN = [[
-attribute vec4 VertexPosition;
-attribute vec4 VertexTexCoord;
-attribute vec4 VertexColor;
+LOVE_IO_LOCATION(0) attribute vec4 VertexPosition;
+LOVE_IO_LOCATION(1) attribute vec4 VertexTexCoord;
+LOVE_IO_LOCATION(2) attribute vec4 VertexColor;
 
 varying vec4 VaryingTexCoord;
 varying vec4 VaryingColor;
@@ -280,10 +293,10 @@ GLSL.PIXEL = {
 	// TODO: We should use reflection or something instead of this, to determine
 	// how many outputs are actually used in the shader code.
 	#ifdef LOVE_MULTI_RENDER_TARGETS
-		layout(location = 0) out vec4 love_RenderTargets[love_MaxRenderTargets];
+		LOVE_IO_LOCATION(0) out vec4 love_RenderTargets[love_MaxRenderTargets];
 		#define love_PixelColor love_RenderTargets[0]
 	#else
-		layout(location = 0) out vec4 love_PixelColor;
+		LOVE_IO_LOCATION(0) out vec4 love_PixelColor;
 	#endif
 #else
 	#ifdef LOVE_MULTI_RENDER_TARGETS
@@ -352,7 +365,7 @@ local function getLanguageTarget(code)
 	return (code:match("^%s*#pragma language (%w+)")) or "glsl1"
 end
 
-local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacorrect, custom, multirendertarget)
+local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacorrect, custom, multirendertarget, useubo)
 	stage = stage:upper()
 	local lines = {
 		GLSL.VERSION[lang][gles],
@@ -360,6 +373,7 @@ local function createShaderStageCode(stage, code, lang, gles, glsl1on3, gammacor
 		glsl1on3 and "#define LOVE_GLSL1_ON_GLSL3 1" or "",
 		gammacorrect and "#define LOVE_GAMMA_CORRECT 1" or "",
 		multirendertarget and "#define LOVE_MULTI_RENDER_TARGETS 1" or "",
+		useubo and "#define LOVE_USE_UNIFORM_BUFFERS 1" or "",
 		GLSL.SYNTAX,
 		GLSL[stage].HEADER,
 		GLSL.UNIFORMS,
@@ -420,6 +434,8 @@ function love.graphics._shaderCodeToGLSL(gles, arg1, arg2)
 	local supportsGLSL3 = graphicsfeatures.glsl3
 	local supportsGLSL4 = graphicsfeatures.glsl4
 	local gammacorrect = love.graphics.isGammaCorrect()
+	local renderer = love.graphics.getRenderer()
+	local useubo = renderer == "Metal"
 
 	local targetlang = getLanguageTarget(pixelcode or vertexcode)
 	if getLanguageTarget(vertexcode or pixelcode) ~= targetlang then
@@ -446,10 +462,10 @@ function love.graphics._shaderCodeToGLSL(gles, arg1, arg2)
 	end
 
 	if vertexcode then
-		vertexcode = createShaderStageCode("VERTEX", vertexcode, lang, gles, glsl1on3, gammacorrect)
+		vertexcode = createShaderStageCode("VERTEX", vertexcode, lang, gles, glsl1on3, gammacorrect, false, false, useubo)
 	end
 	if pixelcode then
-		pixelcode = createShaderStageCode("PIXEL", pixelcode, lang, gles, glsl1on3, gammacorrect, is_custompixel, is_multicanvas)
+		pixelcode = createShaderStageCode("PIXEL", pixelcode, lang, gles, glsl1on3, gammacorrect, is_custompixel, is_multicanvas, useubo)
 	end
 
 	return vertexcode, pixelcode
@@ -485,14 +501,17 @@ local langs = {
 	essl3 = {target="glsl3", gles=true},
 }
 
+-- FIXME: this is temporary until a glslang pull request is merged in.
+local useubo = true
+
 for lang, info in pairs(langs) do
 	for _, gammacorrect in ipairs{false, true} do
 		local t = gammacorrect and defaults_gammacorrect or defaults
 		t[lang] = {
-			vertex = createShaderStageCode("VERTEX", defaultcode.vertex, info.target, info.gles, false, gammacorrect),
-			pixel = createShaderStageCode("PIXEL", defaultcode.pixel, info.target, info.gles, false, gammacorrect, false),
-			videopixel = createShaderStageCode("PIXEL", defaultcode.videopixel, info.target, info.gles, false, gammacorrect, true),
-			arraypixel = createShaderStageCode("PIXEL", defaultcode.arraypixel, info.target, info.gles, false, gammacorrect, true),
+			vertex = createShaderStageCode("VERTEX", defaultcode.vertex, info.target, info.gles, false, gammacorrect, false, false, useubo),
+			pixel = createShaderStageCode("PIXEL", defaultcode.pixel, info.target, info.gles, false, gammacorrect, false, false, useubo),
+			videopixel = createShaderStageCode("PIXEL", defaultcode.videopixel, info.target, info.gles, false, gammacorrect, true, false, useubo),
+			arraypixel = createShaderStageCode("PIXEL", defaultcode.arraypixel, info.target, info.gles, false, gammacorrect, true, false, useubo),
 		}
 	end
 end