diff --git a/src/modules/graphics/Texture.h b/src/modules/graphics/Texture.h index 5d07b2fcf..01e1572a2 100644 --- a/src/modules/graphics/Texture.h +++ b/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; diff --git a/src/modules/graphics/metal/Graphics.h b/src/modules/graphics/metal/Graphics.h index 3883ba94a..b37204ca5 100644 --- a/src/modules/graphics/metal/Graphics.h +++ b/src/modules/graphics/metal/Graphics.h @@ -183,7 +183,7 @@ private: id getCachedDepthStencilState(const DepthState &depth, const StencilState &stencil); void applyRenderState(id renderEncoder, const VertexAttributes &attributes); - void applyShaderUniforms(id renderEncoder, Shader *shader); + void applyShaderUniforms(id renderEncoder, Shader *shader, Texture *maintex); id commandQueue; diff --git a/src/modules/graphics/metal/Graphics.mm b/src/modules/graphics/metal/Graphics.mm index f2710be7f..971504a5b 100644 --- a/src/modules/graphics/metal/Graphics.mm +++ b/src/modules/graphics/metal/Graphics.mm @@ -130,6 +130,11 @@ static inline id getMTLTexture(love::graphics::Texture *tex) return tex ? (__bridge id)(void *) tex->getHandle() : nil; } +static inline id getMTLSampler(love::graphics::Texture *tex) +{ + return tex ? (__bridge id)(void *) tex->getSamplerHandle() : nil; +} + static inline id getMTLRenderTarget(love::graphics::Texture *tex) { return tex ? (__bridge id)(void *) tex->getRenderTargetHandle() : nil; @@ -688,7 +693,7 @@ void Graphics::applyRenderState(id encoder, const Verte dirtyRenderState = 0; } -void Graphics::applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader) +void Graphics::applyShaderUniforms(id renderEncoder, love::graphics::Shader *shader, love::graphics::Texture *maintex) { Shader *s = (Shader *)shader; @@ -747,14 +752,47 @@ void Graphics::applyShaderUniforms(id renderEncoder, lo memcpy(uniformBufferData.data + uniformBufferOffset, bufferdata, size); id buffer = getMTLBuffer(uniformBuffer); - int index = Shader::getUniformBufferBinding(); + int uniformindex = Shader::getUniformBufferBinding(); - // TODO: bind shader textures/samplers - - [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 texture = b.texture; + id 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 encoder, const BufferBindings *buffers) @@ -782,16 +820,7 @@ void Graphics::draw(const DrawCommand &cmd) id 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 = 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 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 = 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 encoder = useRenderEncoder(); applyRenderState(encoder, attributes); - applyShaderUniforms(encoder, Shader::current); - - if (texture == nullptr) - texture = defaultTextures[TEXTURE_2D]; - - id mtltexture = getMTLTexture(texture); - - [encoder setFragmentTexture:mtltexture atIndex:0]; - [encoder setFragmentSamplerState:((Texture *)texture)->getMTLSampler() atIndex:0]; + applyShaderUniforms(encoder, Shader::current, texture); [encoder setCullMode:MTLCullModeNone]; diff --git a/src/modules/graphics/metal/Shader.h b/src/modules/graphics/metal/Shader.h index d5fc22638..8a99f031b 100644 --- a/src/modules/graphics/metal/Shader.h +++ b/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 #include +namespace glslang +{ +class TProgram; +} + namespace love { namespace graphics @@ -67,6 +73,17 @@ public: } }; + struct TextureBinding + { + id texture; + id sampler; + + bool isMainTexture; + + uint8 texturestages[ShaderStage::STAGE_MAX_ENUM]; + uint8 samplerstages[ShaderStage::STAGE_MAX_ENUM]; + }; + Shader(id device, love::graphics::ShaderStage *vertex, love::graphics::ShaderStage *pixel); virtual ~Shader(); @@ -86,6 +103,7 @@ public: id getCachedRenderPipeline(const RenderPipelineKey &key); static int getUniformBufferBinding(); + const std::vector &getTextureBindings() const { return textureBindings; } uint8 *getLocalUniformBufferData() { return localUniformBufferData; } size_t getLocalUniformBufferSize() const { return localUniformBufferSize; } @@ -101,6 +119,8 @@ private: } }; + void compileFromGLSLang(id device, const glslang::TProgram &program); + id functions[ShaderStage::STAGE_MAX_ENUM]; UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM]; @@ -112,6 +132,8 @@ private: std::map attributes; + std::vector textureBindings; + std::unordered_map cachedRenderPipelines; }; // Metal diff --git a/src/modules/graphics/metal/Shader.mm b/src/modules/graphics/metal/Shader.mm index fb5fc59c5..3f3bef8ae 100644 --- a/src/modules/graphics/metal/Shader.mm +++ b/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 getMTLTexture(love::graphics::Texture *tex) +{ + return tex ? (__bridge id)(void *) tex->getHandle() : nil; +} + +static inline id getMTLSampler(love::graphics::Texture *tex) +{ + return tex ? (__bridge id)(void *) tex->getSamplerHandle() : nil; +} + static EShLanguage getGLSLangStage(ShaderStage::StageType stage) { switch (stage) @@ -134,30 +252,107 @@ Shader::Shader(id 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] = {}; - glslang::TProgram program; + TProgram *program = new TProgram(); - if (vertex != nullptr) - program.addShader((TShader *) vertex->getHandle()); - - if (pixel != nullptr) - program.addShader((TShader *) pixel->getHandle()); - - if (!program.link(EShMsgDefault)) + auto cleanup = [&]() { - //err = "Cannot compile shader:\n\n" + std::string(program.getInfoLog()) + "\n" + std::string(program.getInfoDebugLog()); - throw love::Exception("link failed!\n"); + 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()); + + cleanup(); + throw love::Exception("%s", err.c_str()); + } + + program->addShader(tshader); } + 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->mapIO()) + { + cleanup(); + throw love::Exception("mapIO failed"); + } + + try + { + compileFromGLSLang(device, *program); + } + catch (love::Exception &) + { + cleanup(); + throw; + } + + cleanup(); +}} + +void Shader::compileFromGLSLang(id device, const glslang::TProgram &program) +{ + using namespace glslang; + using namespace spirv_cross; + std::map 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 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 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 device, love::graphics::ShaderStage *vertex, love:: UniformInfo u = {}; u.name = msl.get_name(membertypes[uindex]); u.dataSize = membersize; - u.count = std::max(1, membertype.array.size()); + u.count = membertype.array.empty() ? 1 : membertype.array[0]; switch (membertype.basetype) { @@ -333,7 +525,7 @@ Shader::Shader(id 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 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 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 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 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) { diff --git a/src/modules/graphics/metal/ShaderStage.h b/src/modules/graphics/metal/ShaderStage.h index 7152fc86e..dbea3750c 100644 --- a/src/modules/graphics/metal/ShaderStage.h +++ b/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 diff --git a/src/modules/graphics/metal/ShaderStage.mm b/src/modules/graphics/metal/ShaderStage.mm index d940842b8..fd3adfd33 100644 --- a/src/modules/graphics/metal/ShaderStage.mm +++ b/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 diff --git a/src/modules/graphics/metal/Texture.h b/src/modules/graphics/metal/Texture.h index 8e3578a23..605ea7092 100644 --- a/src/modules/graphics/metal/Texture.h +++ b/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*/; } diff --git a/src/modules/graphics/opengl/Texture.h b/src/modules/graphics/opengl/Texture.h index 8f43b0f01..cc100408a 100644 --- a/src/modules/graphics/opengl/Texture.h +++ b/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; }