12

Is it possible to import or include metal file into another metal file? Say I have a metal file with all the math functions and I will only include or import it if it is needed in my metal project. Is it possible?

I tried:

#include "sdf.metal"

and I got error:

metallib: Multiply defined symbols _Z4vmaxDv2_f Command/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/usr/bin/metallib failed with exit code 1

Update:

Here are both my shader files:

SDF.metal:

#ifndef MYAPP_METAL_CONSTANTS
#define MYAPP_METAL_CONSTANTS


#include <metal_stdlib>

namespace metal {

float kk(float2 v) {
    return max(v.x, v.y);
}

float kkk(float3 v) {
    return max(max(v.x, v.y), v.z);
}

}

#endif

And Shaders.metal:

#include <metal_stdlib>
#include "SDF.metal"
using namespace metal;


float fBoxCheap(float3 p, float3 b) { //cheap box
    return kkk(abs(p) - b);
}


float map( float3 p )
{
    float box2 = fBoxCheap(p-float3(0.0,3.0,0.0),float3(4.0,3.0,1.0));



    return box2;
}

float3 getNormal( float3 p )
{
    float3 e = float3( 0.001, 0.00, 0.00 );

    float deltaX = map( p + e.xyy ) - map( p - e.xyy );
    float deltaY = map( p + e.yxy ) - map( p - e.yxy );
    float deltaZ = map( p + e.yyx ) - map( p - e.yyx );

    return normalize( float3( deltaX, deltaY, deltaZ ) );
}

float trace( float3 origin, float3 direction, thread float3 &p )
{
    float totalDistanceTraveled = 0.0;

    for( int i=0; i <64; ++i)
    {
        p = origin + direction * totalDistanceTraveled;

        float distanceFromPointOnRayToClosestObjectInScene = map( p );
        totalDistanceTraveled += distanceFromPointOnRayToClosestObjectInScene;

        if( distanceFromPointOnRayToClosestObjectInScene < 0.0001 )
        {
            break;
        }

        if( totalDistanceTraveled > 10000.0 )
        {
            totalDistanceTraveled = 0.0000;
            break;
        }
    }

    return totalDistanceTraveled;
}

float3 calculateLighting(float3 pointOnSurface, float3 surfaceNormal, float3 lightPosition, float3 cameraPosition)
{
    float3 fromPointToLight = normalize(lightPosition - pointOnSurface);
    float diffuseStrength = clamp( dot( surfaceNormal, fromPointToLight ), 0.0, 1.0 );

    float3 diffuseColor = diffuseStrength * float3( 1.0, 0.0, 0.0 );
    float3 reflectedLightVector = normalize( reflect( -fromPointToLight, surfaceNormal ) );

    float3 fromPointToCamera = normalize( cameraPosition - pointOnSurface );
    float specularStrength = pow( clamp( dot(reflectedLightVector, fromPointToCamera), 0.0, 1.0 ), 10.0 );

    // Ensure that there is no specular lighting when there is no diffuse lighting.
    specularStrength = min( diffuseStrength, specularStrength );
    float3 specularColor = specularStrength * float3( 1.0 );

    float3 finalColor = diffuseColor + specularColor;

    return finalColor;
}

kernel void compute(texture2d<float, access::write> output [[texture(0)]],
                    constant float &timer [[buffer(1)]],
                    constant float &mousex [[buffer(2)]],
                    constant float &mousey [[buffer(3)]],
                    uint2 gid [[thread_position_in_grid]])
{
    int width = output.get_width();
    int height = output.get_height();
    float2 uv = float2(gid) / float2(width, height);
    uv = uv * 2.0 - 1.0;
    // scale proportionately.
    if(width > height) uv.x *= float(width)/float(height);
    if(width < height) uv.y *= float(height)/float(width);


    float posx = mousex * 2.0 - 1.0;
    float posy = mousey * 2.0 - 1.0;

    float3 cameraPosition = float3( posx * 0.01,posy * 0.01, -10.0 );


    float3 cameraDirection = normalize( float3( uv.x, uv.y, 1.0) );

    float3 pointOnSurface;
    float distanceToClosestPointInScene = trace( cameraPosition, cameraDirection, pointOnSurface );

    float3 finalColor = float3(1.0);
    if( distanceToClosestPointInScene > 0.0 )
    {
        float3 lightPosition = float3( 5.0, 2.0, -10.0 );
        float3 surfaceNormal = getNormal( pointOnSurface );
        finalColor = calculateLighting( pointOnSurface, surfaceNormal, lightPosition, cameraPosition );
    }
    output.write(float4(float3(finalColor), 1), gid);

}

Update2:

and my MetalView.swift:

import MetalKit

public class MetalView: MTKView, NSWindowDelegate {

    var queue: MTLCommandQueue! = nil
    var cps: MTLComputePipelineState! = nil

    var timer: Float = 0
    var timerBuffer: MTLBuffer!

    var mousexBuffer: MTLBuffer!
    var mouseyBuffer: MTLBuffer!
    var pos: NSPoint!
    var floatx: Float!
    var floaty: Float!

    required public init(coder: NSCoder) {
        super.init(coder: coder)
        self.framebufferOnly = false
        device = MTLCreateSystemDefaultDevice()
        registerShaders()
    }


    override public func drawRect(dirtyRect: NSRect) {
        super.drawRect(dirtyRect)
        if let drawable = currentDrawable {
            let command_buffer = queue.commandBuffer()
            let command_encoder = command_buffer.computeCommandEncoder()
            command_encoder.setComputePipelineState(cps)
            command_encoder.setTexture(drawable.texture, atIndex: 0)
            command_encoder.setBuffer(timerBuffer, offset: 0, atIndex: 1)
            command_encoder.setBuffer(mousexBuffer, offset: 0, atIndex: 2)
            command_encoder.setBuffer(mouseyBuffer, offset: 0, atIndex: 3)
            update()
            let threadGroupCount = MTLSizeMake(8, 8, 1)
            let threadGroups = MTLSizeMake(drawable.texture.width / threadGroupCount.width, drawable.texture.height / threadGroupCount.height, 1)
            command_encoder.dispatchThreadgroups(threadGroups, threadsPerThreadgroup: threadGroupCount)
            command_encoder.endEncoding()
            command_buffer.presentDrawable(drawable)
            command_buffer.commit()
        }
    }

    func registerShaders() {
        queue = device!.newCommandQueue()
        do {
            let library = device!.newDefaultLibrary()!
            let kernel = library.newFunctionWithName("compute")!
            timerBuffer = device!.newBufferWithLength(sizeof(Float), options: [])
            mousexBuffer = device!.newBufferWithLength(sizeof(Float), options: [])
            mouseyBuffer = device!.newBufferWithLength(sizeof(Float), options: [])
            cps = try device!.newComputePipelineStateWithFunction(kernel)
        } catch let e {
            Swift.print("\(e)")
        }
    }

    func update() {
        timer += 0.01
        var bufferPointer = timerBuffer.contents()
        memcpy(bufferPointer, &timer, sizeof(Float))
        bufferPointer = mousexBuffer.contents()
        memcpy(bufferPointer, &floatx, sizeof(NSPoint))
        bufferPointer = mouseyBuffer.contents()
        memcpy(bufferPointer, &floaty, sizeof(NSPoint))
    }

    override public func mouseDragged(event: NSEvent) {
        pos = convertPointToLayer(convertPoint(event.locationInWindow, fromView: nil))
        let scale = layer!.contentsScale
        pos.x *= scale
        pos.y *= scale
        floatx = Float(pos.x)
        floaty = Float(pos.y)
        debugPrint("Hello",pos.x,pos.y)
    }
}

Update 3

After implement as per KickimusButticus's solution, the shader did compile. However I have another error: enter image description here

sooon
  • 4,718
  • 8
  • 63
  • 116

2 Answers2

21

Your setup is incorrect (EDIT: And so was my setup in my other answer and the previous version of this answer.)

You can use a header just like in C++ (Metal is based on C++11, after all...). All you need one is more file, I'll call it SDF.h. The file includes function prototype declarations without a namespace declaration. And you need to #include it after the using namespace metal; declaration in your other files. Make sure the header file is not a .metal file and that it is not in the Compile Sources list in your Build Phases. If the header is being treated as a compiled source, that's most likely what's causing the CompilerError.

SDF.h:

// SDFHeaders.metal
#ifndef SDF_HEADERS
#define SDF_HEADERS

float kk(float2 v);
float kkk(float3 v);

#endif

SDF.metal:

#include <metal_stdlib>

using namespace metal;
#include "SDF.h"

float kk(float2 v) {
    return max(v.x, v.y);
}

float kkk(float3 v) {
    return max(max(v.x, v.y), v.z);
}

Shaders.metal:

Here is where you use the functions after including SDF.h.

// Shaders.metal

#include <metal_stdlib>

using namespace metal;
#include "SDF.h"    

float fBoxCheap(float3 p, float3 b) { //cheap box
    return kkk(abs(p) - b);
}

// ...

And of course, build after cleaning. Good luck!

jperl
  • 1,066
  • 7
  • 14
  • I did as you mentioned and it did compile. However I have another runtime error at line `command_encoder.setComputePipelineState(cps)` - `fatal error: unexpectedly found nil while unwrapping an Optional value`. I updated my `MetalView` file above. – sooon Sep 07 '16 at 01:58
  • This is progress, that your metal files compile. Which optional is unexpectedly `nil`? Is it `cps`? Is your try/catch block in registerShaders() printing anything useful? – jperl Sep 07 '16 at 02:50
  • I updated a screen cap of the error. Please take a look. – sooon Sep 07 '16 at 03:57
  • 1
    OK, I think the problem is when you have SDFHeaders.metal in your "compile sources" list in your Build Phases. Try removing it there. Also, I made some other changes to my answer. Please take a look and make those adjustments too. – jperl Sep 10 '16 at 09:17
  • This does not work again when update to Xcode 8 and Swift3. Same error appear again. – sooon Sep 23 '16 at 01:27
  • 1
    May I note that Metal 1 is based on C++11, as you said, but Metal 2 is based on C++14? – Andreas is moving to Codidact Oct 22 '17 at 13:09
  • 3
    An important thing to note, which threw me for a while, is that the include statements (unlike import statements in the rest of the code) respect your project directory structure. This means that if you want to include a file that isn't in the same folder as the one doing the including, you have to give it the path to that file (i.e. `#include "../shared.h"` if it's in the parent folder) – Ash Nov 09 '18 at 10:13
  • It works! You just saved me from cloning tons of function boilerplate all over lots of little shaders, not to mention avoiding giving each identical function a unique name! – Pierre Dufresne May 10 '20 at 22:53
2

If you run a demangler on that symbol, you'll find that the compiler thinks you have two or more definitions of a function with the signature vmax(float2 v). Check whether the included file has multiple definitions of such a function, or if the included file and the file that includes it both provide such a definition.

warrenm
  • 31,094
  • 6
  • 92
  • 116