Browse Source

metal: initial shader texture binding implementation.

rework metal shader compilation/parsing.
Alex Szpakowski 4 years ago
parent
commit
52d4a5c679

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

@@ -243,6 +243,7 @@ public:
 	love::image::ImageData *newImageData(love::image::Image *module, int slice, int mipmap, const Rect &rect);
 
 	virtual ptrdiff_t getRenderTargetHandle() const = 0;
+	virtual ptrdiff_t getSamplerHandle() const = 0;
 
 	TextureType getTextureType() const;
 	PixelFormat getPixelFormat() const;

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

@@ -183,7 +183,7 @@ private:
 
 	id<MTLDepthStencilState> getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil);
 	void applyRenderState(id<MTLRenderCommandEncoder> renderEncoder, const VertexAttributes &attributes);
-	void applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, Shader *shader);
+	void applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, Shader *shader, Texture *maintex);
 
 	id<MTLCommandQueue> commandQueue;
 

+ 47 - 35
src/modules/graphics/metal/Graphics.mm

@@ -130,6 +130,11 @@ static inline id<MTLTexture> getMTLTexture(love::graphics::Texture *tex)
 	return tex ? (__bridge id<MTLTexture>)(void *) tex->getHandle() : nil;
 }
 
+static inline id<MTLSamplerState> getMTLSampler(love::graphics::Texture *tex)
+{
+	return tex ? (__bridge id<MTLSamplerState>)(void *) tex->getSamplerHandle() : nil;
+}
+
 static inline id<MTLTexture> getMTLRenderTarget(love::graphics::Texture *tex)
 {
 	return tex ? (__bridge id<MTLTexture>)(void *) tex->getRenderTargetHandle() : nil;
@@ -688,7 +693,7 @@ void Graphics::applyRenderState(id<MTLRenderCommandEncoder> encoder, const Verte
 	dirtyRenderState = 0;
 }
 
-void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, love::graphics::Shader *shader)
+void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, love::graphics::Shader *shader, love::graphics::Texture *maintex)
 {
 	Shader *s = (Shader *)shader;
 
@@ -747,14 +752,47 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
 	memcpy(uniformBufferData.data + uniformBufferOffset, bufferdata, size);
 
 	id<MTLBuffer> buffer = getMTLBuffer(uniformBuffer);
-	int index = Shader::getUniformBufferBinding();
-
-	// TODO: bind shader textures/samplers
+	int uniformindex = Shader::getUniformBufferBinding();
 
-	[renderEncoder setVertexBuffer:buffer offset:uniformBufferOffset atIndex:index];
-	[renderEncoder setFragmentBuffer:buffer offset:uniformBufferOffset atIndex:index];
+	[renderEncoder setVertexBuffer:buffer offset:uniformBufferOffset atIndex:uniformindex];
+	[renderEncoder setFragmentBuffer:buffer offset:uniformBufferOffset atIndex:uniformindex];
 
 	uniformBufferOffset += alignUp(size, alignment);
+
+	for (const Shader::TextureBinding &b : s->getTextureBindings())
+	{
+		id<MTLTexture> texture = b.texture;
+		id<MTLSamplerState> sampler = b.sampler;
+
+		if (b.isMainTexture)
+		{
+			if (maintex == nullptr)
+			{
+				auto textype = shader->getMainTextureType();
+				if (textype != TEXTURE_MAX_ENUM)
+					maintex = defaultTextures[textype];
+			}
+
+			texture = getMTLTexture(maintex);
+			sampler = getMTLSampler(maintex);
+		}
+
+		uint8 texindex = b.texturestages[ShaderStage::STAGE_VERTEX];
+		uint8 sampindex = b.samplerstages[ShaderStage::STAGE_VERTEX];
+
+		if (texindex != LOVE_UINT8_MAX)
+			[renderEncoder setVertexTexture:texture atIndex:texindex];
+		if (sampindex != LOVE_UINT8_MAX)
+			[renderEncoder setVertexSamplerState:sampler atIndex:sampindex];
+
+		texindex = b.texturestages[ShaderStage::STAGE_PIXEL];
+		sampindex = b.samplerstages[ShaderStage::STAGE_PIXEL];
+
+		if (texindex != LOVE_UINT8_MAX)
+			[renderEncoder setFragmentTexture:texture atIndex:texindex];
+		if (sampindex != LOVE_UINT8_MAX)
+			[renderEncoder setFragmentSamplerState:sampler atIndex:sampindex];
+	}
 }
 
 static void setVertexBuffers(id<MTLRenderCommandEncoder> encoder, const BufferBindings *buffers)
@@ -782,16 +820,7 @@ void Graphics::draw(const DrawCommand &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 = getMTLTexture(texture);
-
-	[encoder setFragmentTexture:mtltexture atIndex:0];
-	[encoder setFragmentSamplerState:((Texture *)texture)->getMTLSampler() atIndex:0];
+	applyShaderUniforms(encoder, Shader::current, cmd.texture);
 
 	[encoder setCullMode:MTLCullModeNone];
 
@@ -808,16 +837,7 @@ 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 = getMTLTexture(texture);
-
-	[encoder setFragmentTexture:mtltexture atIndex:0];
-	[encoder setFragmentSamplerState:((Texture *)texture)->getMTLSampler() atIndex:0];
+	applyShaderUniforms(encoder, Shader::current, cmd.texture);
 
 	[encoder setCullMode:MTLCullModeNone];
 
@@ -841,15 +861,7 @@ void Graphics::drawQuads(int start, int count, const VertexAttributes &attribute
 	id<MTLRenderCommandEncoder> encoder = useRenderEncoder();
 
 	applyRenderState(encoder, attributes);
-	applyShaderUniforms(encoder, Shader::current);
-
-	if (texture == nullptr)
-		texture = defaultTextures[TEXTURE_2D];
-
-	id<MTLTexture> mtltexture = getMTLTexture(texture);
-
-	[encoder setFragmentTexture:mtltexture atIndex:0];
-	[encoder setFragmentSamplerState:((Texture *)texture)->getMTLSampler() atIndex:0];
+	applyShaderUniforms(encoder, Shader::current, texture);
 
 	[encoder setCullMode:MTLCullModeNone];
 

+ 22 - 0
src/modules/graphics/metal/Shader.h

@@ -21,6 +21,7 @@
 #pragma once
 
 #include "libraries/xxHash/xxhash.h"
+#include "common/int.h"
 #include "graphics/Shader.h"
 #include "graphics/Graphics.h"
 #include "graphics/renderstate.h"
@@ -33,6 +34,11 @@
 #include <map>
 #include <string>
 
+namespace glslang
+{
+class TProgram;
+}
+
 namespace love
 {
 namespace graphics
@@ -67,6 +73,17 @@ public:
 		}
 	};
 
+	struct TextureBinding
+	{
+		id<MTLTexture> texture;
+		id<MTLSamplerState> sampler;
+
+		bool isMainTexture;
+
+		uint8 texturestages[ShaderStage::STAGE_MAX_ENUM];
+		uint8 samplerstages[ShaderStage::STAGE_MAX_ENUM];
+	};
+
 	Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel);
 	virtual ~Shader();
 
@@ -86,6 +103,7 @@ public:
 	id<MTLRenderPipelineState> getCachedRenderPipeline(const RenderPipelineKey &key);
 
 	static int getUniformBufferBinding();
+	const std::vector<TextureBinding> &getTextureBindings() const { return textureBindings; }
 
 	uint8 *getLocalUniformBufferData() { return localUniformBufferData; }
 	size_t getLocalUniformBufferSize() const { return localUniformBufferSize; }
@@ -101,6 +119,8 @@ private:
 		}
 	};
 
+	void compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &program);
+
 	id<MTLFunction> functions[ShaderStage::STAGE_MAX_ENUM];
 
 	UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];
@@ -112,6 +132,8 @@ private:
 
 	std::map<std::string, int> attributes;
 
+	std::vector<TextureBinding> textureBindings;
+
 	std::unordered_map<RenderPipelineKey, const void *, RenderPipelineHasher> cachedRenderPipelines;
 
 }; // Metal

+ 320 - 54
src/modules/graphics/metal/Shader.mm

@@ -39,6 +39,114 @@ namespace metal
 
 static_assert(MAX_COLOR_RENDER_TARGETS <= 8, "Metal pipeline cache key only stores 8 render target pixel formats.");
 
+// TODO: Use love.graphics to determine actual limits?
+static const TBuiltInResource defaultTBuiltInResource = {
+	/* .MaxLights = */ 32,
+	/* .MaxClipPlanes = */ 6,
+	/* .MaxTextureUnits = */ 32,
+	/* .MaxTextureCoords = */ 32,
+	/* .MaxVertexAttribs = */ 64,
+	/* .MaxVertexUniformComponents = */ 16384,
+	/* .MaxVaryingFloats = */ 128,
+	/* .MaxVertexTextureImageUnits = */ 32,
+	/* .MaxCombinedTextureImageUnits = */ 80,
+	/* .MaxTextureImageUnits = */ 32,
+	/* .MaxFragmentUniformComponents = */ 16384,
+	/* .MaxDrawBuffers = */ 8,
+	/* .MaxVertexUniformVectors = */ 4096,
+	/* .MaxVaryingVectors = */ 32,
+	/* .MaxFragmentUniformVectors = */ 4096,
+	/* .MaxVertexOutputVectors = */ 32,
+	/* .MaxFragmentInputVectors = */ 31,
+	/* .MinProgramTexelOffset = */ -8,
+	/* .MaxProgramTexelOffset = */ 7,
+	/* .MaxClipDistances = */ 8,
+	/* .MaxComputeWorkGroupCountX = */ 65535,
+	/* .MaxComputeWorkGroupCountY = */ 65535,
+	/* .MaxComputeWorkGroupCountZ = */ 65535,
+	/* .MaxComputeWorkGroupSizeX = */ 1024,
+	/* .MaxComputeWorkGroupSizeY = */ 1024,
+	/* .MaxComputeWorkGroupSizeZ = */ 64,
+	/* .MaxComputeUniformComponents = */ 1024,
+	/* .MaxComputeTextureImageUnits = */ 32,
+	/* .MaxComputeImageUniforms = */ 16,
+	/* .MaxComputeAtomicCounters = */ 4096,
+	/* .MaxComputeAtomicCounterBuffers = */ 8,
+	/* .MaxVaryingComponents = */ 128,
+	/* .MaxVertexOutputComponents = */ 128,
+	/* .MaxGeometryInputComponents = */ 128,
+	/* .MaxGeometryOutputComponents = */ 128,
+	/* .MaxFragmentInputComponents = */ 128,
+	/* .MaxImageUnits = */ 192,
+	/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144,
+	/* .MaxCombinedShaderOutputResources = */ 144,
+	/* .MaxImageSamples = */ 32,
+	/* .MaxVertexImageUniforms = */ 16,
+	/* .MaxTessControlImageUniforms = */ 16,
+	/* .MaxTessEvaluationImageUniforms = */ 16,
+	/* .MaxGeometryImageUniforms = */ 16,
+	/* .MaxFragmentImageUniforms = */ 16,
+	/* .MaxCombinedImageUniforms = */ 80,
+	/* .MaxGeometryTextureImageUnits = */ 16,
+	/* .MaxGeometryOutputVertices = */ 256,
+	/* .MaxGeometryTotalOutputComponents = */ 1024,
+	/* .MaxGeometryUniformComponents = */ 1024,
+	/* .MaxGeometryVaryingComponents = */ 64,
+	/* .MaxTessControlInputComponents = */ 128,
+	/* .MaxTessControlOutputComponents = */ 128,
+	/* .MaxTessControlTextureImageUnits = */ 16,
+	/* .MaxTessControlUniformComponents = */ 1024,
+	/* .MaxTessControlTotalOutputComponents = */ 4096,
+	/* .MaxTessEvaluationInputComponents = */ 128,
+	/* .MaxTessEvaluationOutputComponents = */ 128,
+	/* .MaxTessEvaluationTextureImageUnits = */ 16,
+	/* .MaxTessEvaluationUniformComponents = */ 1024,
+	/* .MaxTessPatchComponents = */ 120,
+	/* .MaxPatchVertices = */ 32,
+	/* .MaxTessGenLevel = */ 64,
+	/* .MaxViewports = */ 16,
+	/* .MaxVertexAtomicCounters = */ 4096,
+	/* .MaxTessControlAtomicCounters = */ 4096,
+	/* .MaxTessEvaluationAtomicCounters = */ 4096,
+	/* .MaxGeometryAtomicCounters = */ 4096,
+	/* .MaxFragmentAtomicCounters = */ 4096,
+	/* .MaxCombinedAtomicCounters = */ 4096,
+	/* .MaxAtomicCounterBindings = */ 8,
+	/* .MaxVertexAtomicCounterBuffers = */ 8,
+	/* .MaxTessControlAtomicCounterBuffers = */ 8,
+	/* .MaxTessEvaluationAtomicCounterBuffers = */ 8,
+	/* .MaxGeometryAtomicCounterBuffers = */ 8,
+	/* .MaxFragmentAtomicCounterBuffers = */ 8,
+	/* .MaxCombinedAtomicCounterBuffers = */ 8,
+	/* .MaxAtomicCounterBufferSize = */ 16384,
+	/* .MaxTransformFeedbackBuffers = */ 4,
+	/* .MaxTransformFeedbackInterleavedComponents = */ 64,
+	/* .MaxCullDistances = */ 8,
+	/* .MaxCombinedClipAndCullDistances = */ 8,
+	/* .MaxSamples = */ 32,
+	/* .maxMeshOutputVerticesNV = */ 256,
+	/* .maxMeshOutputPrimitivesNV = */ 512,
+	/* .maxMeshWorkGroupSizeX_NV = */ 32,
+	/* .maxMeshWorkGroupSizeY_NV = */ 1,
+	/* .maxMeshWorkGroupSizeZ_NV = */ 1,
+	/* .maxTaskWorkGroupSizeX_NV = */ 32,
+	/* .maxTaskWorkGroupSizeY_NV = */ 1,
+	/* .maxTaskWorkGroupSizeZ_NV = */ 1,
+	/* .maxMeshViewCountNV = */ 4,
+	/* .maxDualSourceDrawBuffersEXT = */ 1,
+	/* .limits = */ {
+		/* .nonInductiveForLoops = */ 1,
+		/* .whileLoops = */ 1,
+		/* .doWhileLoops = */ 1,
+		/* .generalUniformIndexing = */ 1,
+		/* .generalAttributeMatrixVectorIndexing = */ 1,
+		/* .generalVaryingIndexing = */ 1,
+		/* .generalSamplerIndexing = */ 1,
+		/* .generalVariableIndexing = */ 1,
+		/* .generalConstantMatrixVectorIndexing = */ 1,
+	}
+};
+
 static MTLVertexFormat getMTLVertexFormat(DataFormat format)
 {
 	switch (format)
@@ -114,6 +222,16 @@ static MTLBlendFactor getMTLBlendFactor(BlendFactor factor)
 	return MTLBlendFactorZero;
 }
 
+static inline id<MTLTexture> getMTLTexture(love::graphics::Texture *tex)
+{
+	return tex ? (__bridge id<MTLTexture>)(void *) tex->getHandle() : nil;
+}
+
+static inline id<MTLSamplerState> getMTLSampler(love::graphics::Texture *tex)
+{
+	return tex ? (__bridge id<MTLSamplerState>)(void *) tex->getSamplerHandle() : nil;
+}
+
 static EShLanguage getGLSLangStage(ShaderStage::StageType stage)
 {
 	switch (stage)
@@ -134,30 +252,107 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 	, builtinUniformDataOffset(0)
 { @autoreleasepool {
 	using namespace glslang;
-	using namespace spirv_cross;
 
-	// TODO: can this be done in ShaderStage (no linking)?
+	TShader *glslangShaders[ShaderStage::STAGE_MAX_ENUM] = {};
+
+	TProgram *program = new TProgram();
+
+	auto cleanup = [&]()
+	{
+		delete program;
+		for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+			delete glslangShaders[i];
+	};
+
+	// We can't do this in ShaderStage because the mapIO call modifies the
+	// TShader internals in a manner that prevents it from being shared.
+	for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+	{
+		if (!stages[i])
+			continue;
+
+		auto stage = (ShaderStage::StageType) i;
+		auto glslangstage = getGLSLangStage(stage);
+		auto tshader = new TShader(glslangstage);
+
+		glslangShaders[i] = tshader;
+
+		tshader->setEnvInput(EShSourceGlsl, glslangstage, EShClientVulkan, 450);
+		tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
+		tshader->setEnvTarget(EShTargetSpv, EShTargetSpv_1_5);
+		tshader->setAutoMapLocations(true);
+		tshader->setAutoMapBindings(true);
+
+		// Needed for local uniforms to work (they will be converted into a
+		// uniform block).
+		// https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_vulkan_glsl_relaxed.txt
+		tshader->setEnvInputVulkanRulesRelaxed();
+		tshader->setGlobalUniformBinding(0);
+		tshader->setGlobalUniformSet(0);
+
+		const std::string &source = stages[i]->getSource();
+		const char *csrc = source.c_str();
+		int srclen = (int) source.length();
+		tshader->setStringsWithLengths(&csrc, &srclen, 1);
+
+		int defaultversion = 450;
+		EProfile defaultprofile = ECoreProfile;
+		bool forcedefault = false;
+		bool forwardcompat = true;
+
+		if (!tshader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
+		{
+			const char *stagename = "unknown";
+			ShaderStage::getConstant(stage, stagename);
+
+			std::string err = "Error parsing " + std::string(stagename) + " shader:\n\n"
+				+ std::string(tshader->getInfoLog()) + "\n"
+				+ std::string(tshader->getInfoDebugLog());
 
-	glslang::TProgram program;
+			cleanup();
+			throw love::Exception("%s", err.c_str());
+		}
 
-	if (vertex != nullptr)
-		program.addShader((TShader *) vertex->getHandle());
+		program->addShader(tshader);
+	}
 
-	if (pixel != nullptr)
-		program.addShader((TShader *) pixel->getHandle());
+	if (!program->link(EShMsgDefault))
+	{
+		//err = "Cannot compile shader:\n\n" + std::string(program->getInfoLog()) + "\n" + std::string(program->getInfoDebugLog());
+		cleanup();
+		throw love::Exception("link failed! %s\n", program->getInfoLog());
+	}
 
-	if (!program.link(EShMsgDefault))
+	if (!program->mapIO())
 	{
-		//err = "Cannot compile shader:\n\n" + std::string(program.getInfoLog()) + "\n" + std::string(program.getInfoDebugLog());
-		throw love::Exception("link failed!\n");
+		cleanup();
+		throw love::Exception("mapIO failed");
 	}
 
+	try
+	{
+		compileFromGLSLang(device, *program);
+	}
+	catch (love::Exception &)
+	{
+		cleanup();
+		throw;
+	}
+
+	cleanup();
+}}
+
+void Shader::compileFromGLSLang(id<MTLDevice> device, const glslang::TProgram &program)
+{
+	using namespace glslang;
+	using namespace spirv_cross;
+
 	std::map<std::string, int> varyings;
 	int nextVaryingLocation = 0;
 
-	for (int i = 0; i < ShaderStage::STAGE_MAX_ENUM; i++)
+	for (int stageindex = 0; stageindex < ShaderStage::STAGE_MAX_ENUM; stageindex++)
 	{
-		auto glslangstage = getGLSLangStage((ShaderStage::StageType) i);
+		auto glslangstage = getGLSLangStage((ShaderStage::StageType) stageindex);
 		auto intermediate = program.getIntermediate(glslangstage);
 		if (intermediate == nullptr)
 			continue;
@@ -186,44 +381,41 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 
 			for (const auto &resource : resources.sampled_images)
 			{
-				// TODO: set MainTex to binding 0
-				int binding = msl.get_decoration(resource.id, spv::DecorationBinding);
-				const SPIRType &type = msl.get_type(resource.base_type_id);
-
-				auto it = uniforms.find(resource.name);
-				if (it != uniforms.end())
-				{
-					if (it->second.ints[0] != binding)
-						throw love::Exception("texture binding mismatch for %s: %d vs %d", resource.name.c_str(), it->second.ints[0], binding);
-					continue;
-				}
+				const SPIRType &basetype = msl.get_type(resource.base_type_id);
+				const SPIRType &type = msl.get_type(resource.type_id);
 
 				UniformInfo u = {};
 				u.baseType = UNIFORM_SAMPLER;
 				u.name = resource.name;
+				u.count = type.array.empty() ? 1 : type.array[0];
 				u.location = 0;
-				u.data = malloc(sizeof(int) * 1);
-				u.ints[0] = binding;
-//				printf("binding for %s: %d\n", u.name.c_str(), binding);
+				u.data = malloc(sizeof(int) * u.count);
+				for (int i = 0; i < u.count; i++)
+					u.ints[i] = -1; // Will initialize below.
 
-				switch (type.image.dim)
+//				printf("%s binding: %d, set: %d\n", u.name.c_str(), msl.get_decoration(resource.id, spv::DecorationBinding), msl.get_decoration(resource.id, spv::DecorationDescriptorSet));
+
+				switch (basetype.image.dim)
 				{
 				case spv::Dim2D:
-					u.textureType = type.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
-					u.textures = new love::graphics::Texture*[1];
-					u.textures[0] = nullptr;
+					u.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
+					u.textures = new love::graphics::Texture*[u.count];
+					for (int i = 0; i < u.count; i++)
+						u.textures[i] = nullptr;
 					break;
 				case spv::Dim3D:
 					u.textureType = TEXTURE_VOLUME;
-					u.textures = new love::graphics::Texture*[1];
-					u.textures[0] = nullptr;
+					u.textures = new love::graphics::Texture*[u.count];
+					for (int i = 0; i < u.count; i++)
+						u.textures[i] = nullptr;
 					break;
 				case spv::DimCube:
-					if (type.image.arrayed)
+					if (basetype.image.arrayed)
 						throw love::Exception("Cubemap Arrays are not currently supported.");
 					u.textureType = TEXTURE_CUBE;
-					u.textures = new love::graphics::Texture*[1];
-					u.textures[0] = nullptr;
+					u.textures = new love::graphics::Texture*[u.count];
+					for (int i = 0; i < u.count; i++)
+						u.textures[i] = nullptr;
 					break;
 				case spv::DimBuffer:
 					// TODO: are texel buffers sampled images in glslang?
@@ -234,22 +426,22 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 
 				uniforms[u.name] = u;
 
-				BuiltinUniform builtin = BUILTIN_MAX_ENUM;
+				BuiltinUniform builtin;
 				if (getConstant(resource.name.c_str(), builtin))
 					builtinUniformInfo[builtin] = &uniforms[u.name];
 			}
 
 			for (const auto &resource : resources.uniform_buffers)
 			{
-				auto it = uniforms.find(resource.name);
-				if (it != uniforms.end())
-				{
-					continue;
-				}
-
 				if (resource.name == "gl_DefaultUniformBlock")
 				{
-					msl.set_decoration(resource.id, spv::DecorationBinding, 0);
+					MSLResourceBinding binding;
+					binding.stage = msl.get_execution_model();
+					binding.binding = msl.get_decoration(resource.id, spv::DecorationBinding);
+					binding.desc_set = msl.get_decoration(resource.id, spv::DecorationDescriptorSet);
+					binding.msl_buffer = getUniformBufferBinding();
+					msl.add_msl_resource_binding(binding);
+
 					const SPIRType &type = msl.get_type(resource.base_type_id);
 					const auto &membertypes = type.member_types;
 
@@ -276,7 +468,7 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 						UniformInfo u = {};
 						u.name = msl.get_name(membertypes[uindex]);
 						u.dataSize = membersize;
-						u.count = std::max<size_t>(1, membertype.array.size());
+						u.count = membertype.array.empty() ? 1 : membertype.array[0];
 
 						switch (membertype.basetype)
 						{
@@ -333,7 +525,7 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 				// TODO
 			}
 
-			if (i == ShaderStage::STAGE_VERTEX)
+			if (stageindex == ShaderStage::STAGE_VERTEX)
 			{
 				int nextattributeindex = ATTRIB_MAX_ENUM;
 
@@ -353,7 +545,6 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 							index = nextattributeindex++;
 
 						msl.set_decoration(var, spv::DecorationLocation, index);
-
 						attributes[name] = msl.get_decoration(var, spv::DecorationLocation);
 					}
 				}
@@ -365,7 +556,7 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 					msl.set_decoration(varying.id, spv::DecorationLocation, nextVaryingLocation++);
 				}
 			}
-			else if (i == ShaderStage::STAGE_PIXEL)
+			else if (stageindex == ShaderStage::STAGE_PIXEL)
 			{
 				for (const auto &varying : resources.stage_inputs)
 				{
@@ -375,12 +566,11 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 				}
 			}
 
-			printf("// ubos: %ld, storage: %ld, inputs: %ld, outputs: %ld, images: %ld, samplers: %ld, push: %ld\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());
+//			printf("// ubos: %ld, storage: %ld, inputs: %ld, outputs: %ld, images: %ld, samplers: %ld, push: %ld\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;
 
 			options.set_msl_version(2, 1);
-
 			options.texture_buffer_native = true;
 
 #ifdef LOVE_IOS
@@ -406,14 +596,56 @@ Shader::Shader(id<MTLDevice> device, love::graphics::ShaderStage *vertex, love::
 				throw love::Exception("Error compiling converted Metal shader code");
 			}
 
-			functions[i] = [library newFunctionWithName:library.functionNames[0]];
+			functions[stageindex] = [library newFunctionWithName:library.functionNames[0]];
+
+			for (const auto &resource : resources.sampled_images)
+			{
+				auto it = uniforms.find(resource.name);
+				if (it == uniforms.end())
+					continue;
+
+				UniformInfo &u = it->second;
+
+				uint32 texturebinding = msl.get_automatic_msl_resource_binding(resource.id);
+				uint32 samplerbinding = msl.get_automatic_msl_resource_binding_secondary(resource.id);
+
+				if (texturebinding == (uint32)-1)
+					continue;
+
+				for (int i = 0; i < u.count; i++)
+				{
+					if (u.ints[i] == -1)
+					{
+						u.ints[i] = (int)textureBindings.size();
+						TextureBinding b = {};
+
+						b.texture = nil; // TODO: matching default texture
+						b.sampler = nil;
+
+						BuiltinUniform builtin = BUILTIN_MAX_ENUM;
+						if (getConstant(u.name.c_str(), builtin) && builtin == BUILTIN_TEXTURE_MAIN)
+							b.isMainTexture = true;
+
+						for (uint8 &stagebinding : b.texturestages)
+							stagebinding = LOVE_UINT8_MAX;
+						for (uint8 &stagebinding : b.samplerstages)
+							stagebinding = LOVE_UINT8_MAX;
+
+						textureBindings.push_back(b);
+					}
+
+					auto &b = textureBindings[u.ints[i]];
+					b.texturestages[stageindex] = (uint8) texturebinding;
+					b.samplerstages[stageindex] = (uint8) samplerbinding;
+				}
+			}
 		}
 		catch (std::exception &e)
 		{
 			printf("Error parsing SPIR-V shader source: %s\n", e.what());
 		}
 	}
-}}
+}
 
 Shader::~Shader()
 { @autoreleasepool {
@@ -469,12 +701,46 @@ void Shader::updateUniform(const UniformInfo * /*info*/, int /*count*/)
 {
 	// Nothing needed here, All uniform data will be memcpy'd to the main
 	// uniform buffer before drawing.
+	// FIXME: do we need to copy to a second buffer here, in order for batch
+	// flushing to work (and possibly padding)?
 }
 
 void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **textures, int count)
-{
-	// TODO
-}
+{ @autoreleasepool {
+	if (info->baseType != UNIFORM_SAMPLER)
+		return;
+
+	bool shaderactive = current == this;
+
+	if (shaderactive)
+		Graphics::flushBatchedDrawsGlobal();
+
+	count = std::min(count, info->count);
+
+	for (int i = 0; i < count; i++)
+	{
+		love::graphics::Texture *tex = textures[i];
+
+		if (tex != nullptr)
+		{
+			if (!validateTexture(info, tex, false))
+				continue;
+			tex->retain();
+		}
+		else
+		{
+			// TODO: matching default texture
+		}
+
+		if (info->textures[i] != nullptr)
+			info->textures[i]->release();
+
+		info->textures[i] = tex;
+
+		textureBindings[info->ints[i]].texture = getMTLTexture(tex);
+		textureBindings[info->ints[i]].sampler = getMTLSampler(tex);
+	}
+}}
 
 void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffers, int count)
 {

+ 1 - 6
src/modules/graphics/metal/ShaderStage.h

@@ -35,12 +35,7 @@ public:
 
 	ShaderStage(love::graphics::Graphics *gfx, StageType stage, const std::string &source, bool gles, const std::string &cachekey);
 	virtual ~ShaderStage();
-
-	ptrdiff_t getHandle() const override { return (ptrdiff_t) glslangShader; }
-
-private:
-
-	glslang::TShader *glslangShader;
+	ptrdiff_t getHandle() const override { return 0; }
 
 }; // ShaderStage
 

+ 3 - 155
src/modules/graphics/metal/ShaderStage.mm

@@ -22,114 +22,6 @@
 
 #include "libraries/glslang/glslang/Public/ShaderLang.h"
 
-// TODO: Use love.graphics to determine actual limits?
-static const TBuiltInResource defaultTBuiltInResource = {
-	/* .MaxLights = */ 32,
-	/* .MaxClipPlanes = */ 6,
-	/* .MaxTextureUnits = */ 32,
-	/* .MaxTextureCoords = */ 32,
-	/* .MaxVertexAttribs = */ 64,
-	/* .MaxVertexUniformComponents = */ 16384,
-	/* .MaxVaryingFloats = */ 128,
-	/* .MaxVertexTextureImageUnits = */ 32,
-	/* .MaxCombinedTextureImageUnits = */ 80,
-	/* .MaxTextureImageUnits = */ 32,
-	/* .MaxFragmentUniformComponents = */ 16384,
-	/* .MaxDrawBuffers = */ 8,
-	/* .MaxVertexUniformVectors = */ 4096,
-	/* .MaxVaryingVectors = */ 32,
-	/* .MaxFragmentUniformVectors = */ 4096,
-	/* .MaxVertexOutputVectors = */ 32,
-	/* .MaxFragmentInputVectors = */ 31,
-	/* .MinProgramTexelOffset = */ -8,
-	/* .MaxProgramTexelOffset = */ 7,
-	/* .MaxClipDistances = */ 8,
-	/* .MaxComputeWorkGroupCountX = */ 65535,
-	/* .MaxComputeWorkGroupCountY = */ 65535,
-	/* .MaxComputeWorkGroupCountZ = */ 65535,
-	/* .MaxComputeWorkGroupSizeX = */ 1024,
-	/* .MaxComputeWorkGroupSizeY = */ 1024,
-	/* .MaxComputeWorkGroupSizeZ = */ 64,
-	/* .MaxComputeUniformComponents = */ 1024,
-	/* .MaxComputeTextureImageUnits = */ 32,
-	/* .MaxComputeImageUniforms = */ 16,
-	/* .MaxComputeAtomicCounters = */ 4096,
-	/* .MaxComputeAtomicCounterBuffers = */ 8,
-	/* .MaxVaryingComponents = */ 128,
-	/* .MaxVertexOutputComponents = */ 128,
-	/* .MaxGeometryInputComponents = */ 128,
-	/* .MaxGeometryOutputComponents = */ 128,
-	/* .MaxFragmentInputComponents = */ 128,
-	/* .MaxImageUnits = */ 192,
-	/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144,
-	/* .MaxCombinedShaderOutputResources = */ 144,
-	/* .MaxImageSamples = */ 32,
-	/* .MaxVertexImageUniforms = */ 16,
-	/* .MaxTessControlImageUniforms = */ 16,
-	/* .MaxTessEvaluationImageUniforms = */ 16,
-	/* .MaxGeometryImageUniforms = */ 16,
-	/* .MaxFragmentImageUniforms = */ 16,
-	/* .MaxCombinedImageUniforms = */ 80,
-	/* .MaxGeometryTextureImageUnits = */ 16,
-	/* .MaxGeometryOutputVertices = */ 256,
-	/* .MaxGeometryTotalOutputComponents = */ 1024,
-	/* .MaxGeometryUniformComponents = */ 1024,
-	/* .MaxGeometryVaryingComponents = */ 64,
-	/* .MaxTessControlInputComponents = */ 128,
-	/* .MaxTessControlOutputComponents = */ 128,
-	/* .MaxTessControlTextureImageUnits = */ 16,
-	/* .MaxTessControlUniformComponents = */ 1024,
-	/* .MaxTessControlTotalOutputComponents = */ 4096,
-	/* .MaxTessEvaluationInputComponents = */ 128,
-	/* .MaxTessEvaluationOutputComponents = */ 128,
-	/* .MaxTessEvaluationTextureImageUnits = */ 16,
-	/* .MaxTessEvaluationUniformComponents = */ 1024,
-	/* .MaxTessPatchComponents = */ 120,
-	/* .MaxPatchVertices = */ 32,
-	/* .MaxTessGenLevel = */ 64,
-	/* .MaxViewports = */ 16,
-	/* .MaxVertexAtomicCounters = */ 4096,
-	/* .MaxTessControlAtomicCounters = */ 4096,
-	/* .MaxTessEvaluationAtomicCounters = */ 4096,
-	/* .MaxGeometryAtomicCounters = */ 4096,
-	/* .MaxFragmentAtomicCounters = */ 4096,
-	/* .MaxCombinedAtomicCounters = */ 4096,
-	/* .MaxAtomicCounterBindings = */ 8,
-	/* .MaxVertexAtomicCounterBuffers = */ 8,
-	/* .MaxTessControlAtomicCounterBuffers = */ 8,
-	/* .MaxTessEvaluationAtomicCounterBuffers = */ 8,
-	/* .MaxGeometryAtomicCounterBuffers = */ 8,
-	/* .MaxFragmentAtomicCounterBuffers = */ 8,
-	/* .MaxCombinedAtomicCounterBuffers = */ 8,
-	/* .MaxAtomicCounterBufferSize = */ 16384,
-	/* .MaxTransformFeedbackBuffers = */ 4,
-	/* .MaxTransformFeedbackInterleavedComponents = */ 64,
-	/* .MaxCullDistances = */ 8,
-	/* .MaxCombinedClipAndCullDistances = */ 8,
-	/* .MaxSamples = */ 32,
-	/* .maxMeshOutputVerticesNV = */ 256,
-	/* .maxMeshOutputPrimitivesNV = */ 512,
-	/* .maxMeshWorkGroupSizeX_NV = */ 32,
-	/* .maxMeshWorkGroupSizeY_NV = */ 1,
-	/* .maxMeshWorkGroupSizeZ_NV = */ 1,
-	/* .maxTaskWorkGroupSizeX_NV = */ 32,
-	/* .maxTaskWorkGroupSizeY_NV = */ 1,
-	/* .maxTaskWorkGroupSizeZ_NV = */ 1,
-	/* .maxMeshViewCountNV = */ 4,
-	/* .maxDualSourceDrawBuffersEXT = */ 1,
-	/* .limits = */ {
-		/* .nonInductiveForLoops = */ 1,
-		/* .whileLoops = */ 1,
-		/* .doWhileLoops = */ 1,
-		/* .generalUniformIndexing = */ 1,
-		/* .generalAttributeMatrixVectorIndexing = */ 1,
-		/* .generalVaryingIndexing = */ 1,
-		/* .generalSamplerIndexing = */ 1,
-		/* .generalVariableIndexing = */ 1,
-		/* .generalConstantMatrixVectorIndexing = */ 1,
-	}
-};
-
 namespace love
 {
 namespace graphics
@@ -140,57 +32,13 @@ namespace metal
 ShaderStage::ShaderStage(love::graphics::Graphics *gfx, StageType stage, const std::string &source, bool gles, const std::string &cachekey)
 	: love::graphics::ShaderStage(gfx, stage, source, gles, cachekey)
 {
-	using namespace glslang;
-
-	EShLanguage glslangStage = EShLangCount;
-	if (stage == STAGE_VERTEX)
-		glslangStage = EShLangVertex;
-	else if (stage == STAGE_PIXEL)
-		glslangStage = EShLangFragment;
-	else
-		throw love::Exception("Cannot compile shader stage: unknown stage type.");
-
-	glslangShader = new TShader(glslangStage);
-
-	// We can't reuse the validation glslang shader object in the base class,
-	// because we need these options set (and the language set to >= 300).
-	glslangShader->setEnvInput(EShSourceGlsl, glslangStage, EShClientVulkan, 450);
-	glslangShader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
-	glslangShader->setEnvTarget(EShTargetSpv, EShTargetSpv_1_5);
-	glslangShader->setAutoMapLocations(true);
-	glslangShader->setAutoMapBindings(true);
-
-	// Needed for local uniforms to work (they will be converted into a uniform
-	// block).
-	// https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_vulkan_glsl_relaxed.txt
-	glslangShader->setEnvInputVulkanRulesRelaxed();
-
-	const char *csrc = source.c_str();
-	int srclen = (int) source.length();
-	glslangShader->setStringsWithLengths(&csrc, &srclen, 1);
-
-	int defaultversion = gles ? 300 : 330;
-	EProfile defaultprofile = gles ? EEsProfile : ECoreProfile;
-	bool forcedefault = false;
-	bool forwardcompat = true;
-
-	if (!glslangShader->parse(&defaultTBuiltInResource, defaultversion, defaultprofile, forcedefault, forwardcompat, EShMsgSuppressWarnings))
-	{
-		const char *stagename = "unknown";
-		getConstant(stage, stagename);
-
-		std::string err = "Error parsing " + std::string(stagename) + " shader:\n\n"
-			+ std::string(glslangShader->getInfoLog()) + "\n"
-			+ std::string(glslangShader->getInfoDebugLog());
-
-		delete glslangShader;
-		throw love::Exception("%s", err.c_str());
-	}
+	// Can't store anything in here since the next part of the compilation
+	// pipeline (glslang to generate spir-v) requires linking stages together
+	// destructively.
 }
 
 ShaderStage::~ShaderStage()
 {
-	delete glslangShader;
 }
 
 } // metal

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

@@ -44,6 +44,7 @@ public:
 
 	ptrdiff_t getHandle() const override { return (ptrdiff_t) texture; }
 	ptrdiff_t getRenderTargetHandle() const override { return msaaTexture != nil ? (ptrdiff_t) msaaTexture : (ptrdiff_t) texture; }
+	ptrdiff_t getSamplerHandle() const override { return (ptrdiff_t) sampler; }
 
 	int getMSAA() const override { return 1 /* TODO*/; }
 

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

@@ -50,6 +50,7 @@ public:
 
 	ptrdiff_t getHandle() const override;
 	ptrdiff_t getRenderTargetHandle() const override;
+	ptrdiff_t getSamplerHandle() const override { return 0; }
 	int getMSAA() const override { return actualSamples; }
 
 	inline GLuint getFBO() const { return fbo; }